본문 바로가기

[REVERSING]

UPX Manual unpacking

1. 개요

실행파일을 UPX로 Packing 하여 Unpacking 과정을 상세 분석하여 실행파일의 압축해제 원리를 파악한다.

파일 압축기법은 여러가지가 있으며 실행가능한 압축파일 또는 비실행 압축파일이 있습니다.

예로 실행가능한 압축파일은 UPX등이 있으며, 비실행 압축파일은 알X 빵X등이 있습니다.

여기서 실행가능한 압축파일 같은 경우는 손실 압축기법을 사용하여 실행가능한 형태의 파일의 용량을 줄여 압축을 하며 비손실 압축기법은 데이터손실 없이 압축되는 기법으로 알X 빵X등에 사용됩니다.


2. 테스트 환경 (해당 될시에만)

 운영 체제 (OS) : Windows XP Service Pack 3

 분석 도구 : IDA Pro 6.1, Hxd, PEView

 분석 대상 : 지뢰찾기 게임(윈도우용)

 

3. UPX 실행압축


UPX로 실행 압축 된 파일을 정적 분석으로 압축 원리를 파악합니다.


[그림.1-1]

 

winmne.exe 지뢰찾기 프로그램을 UPX 프로그램을 사용하여 winmine_upx1.exe 으로 압축합니다.

 

[그림.1-2]

 

실행압축 된 파일을 PEview 도구로 확인합니다.

왼쪽이 정상 파일이기 때문에 일반적인 PE형식을 갖추고 있는 반면 오른쪽 그림은 UPX로 압축 후의 상태이기 때문에 특이한 형태의 PE구조를 확인 할 수 있습니다.

정상PE파일과 비교해 볼 때 가장 큰 특징은 정상 PE파일의 .text .data .rsrc 섹션이 압축 후 UPX0, UPX1, rsrc로 변경되어 있다는 것이다 .text=UPX0, .data=UPX1,rsrc=rsrc로 볼 수 있다.

또한 UPX0의 섹션으로 가보면 그림.1-2에서와 같이 섹션에 데이터가 없으며, Size of Raw Data값도 0이다.

 

[그림.1-3]

 

정적 분석을 진행하다 보면 UPX1에서 UPX0에 압축을 해제하는 것을 확인 할 수 있습니다.

그림.1-3 과 같이 박스의 값들을UPX0에 복사를 합니다.

UPX압축이 풀리는 원리를 파악하기 위해 상세분석을 진행 합니다.

 

4. 상세 분석

 

[그림.2-1]

 

UPX Packing Binary파일은 PUSHAD명령으로 EAX~ESI레지스터까지 백업하고 압축해제 후 POPAD명령으로 백업한 레지스터를 복구하게 됩니다.

여기서 POPAD이후 JMP코드를 찾아가면 압축해제 후 원본 PE파일을 확인 할 수 있지만, UPX압축해제의 원리를 파악하기 위해 정적 분석을 진행 합니다.

그림2-1에서 각각 ESI레지스터에는 01011000주소 EDI레지스터에는 01001000주소로 세팅하게 되는데 01011000 UPX1(.data)주소이고, 01001000(.text)주소입니다.

그리고 01021C02라인에서UPX1섹션의 첫 4바이트 92A207FB EBX레지스터에 저장한다.

다음 명령 라인01021C04에서는01011000  0FFFFFFFC = FFFFFFFF01011004연산을 해서 ESI레지스터에 저장하는데 ESI레지스터는 4바이트(32비트레지스터이기 때문에 FFFFFFFF를 제외한 01011004 ESI레지스터에 저장합니다. 즉 초기UPX1섹션의 첫 01011000주소의 4바이트 92A207FB 바로 다음주소인 01011004를 가르키는 것입니다.

 

[그림.2-2]

 

그림2-2에서는 1EB크기까지 0으로 채우고 10011EB부터 IMAGE_SECTION_HEADER를 채웁니다.

1EB크기의 공간을 제외하고 1EB이후의주소에 헤더를 채웁니다. 이 영역은 .SECTION .text IMPORT Adress Table(IAT)이가 압축해제 될 영역입니다.

 

[그림.2-3]

 

그림.2-3에서는 01011004주소의첫 1바이트 00 AL에저장을 하고, 01011004주소를 1증가하여 01011005로 변경합니다.

이후 EDI UPX0 값도 1증가합니다.

[그림.2-4]

 

01011004 ~ 0101100B까지 UPX0섹션에 00770069006E006D 값을 저장합니다.

그림.1-3을 확인하면 순차적으로UPX1의 데이터를 순차적으로 UPX0으로 이동하는 것을 확인 할 수 있습니다.

그러나 그림.2-1에서 01021C07에서 ADC EBX,EBX 명령이 있는데 EBX에는 UPX1의 첫 4바이트값을 저장 했었습니다.

분석을 진행 하다보면 EBX값을 계속 더하여 0인지 0이 아닌지 판단하여 분기문을 실행하는 것을 확인할 수 있습니다.


 

[그림.2-5]

 

이어서 0101100B까지 연산이 진행되었고 0101100C 00000005의 값의 1바이트 05 AL레지스터에 저장합니다.

저장 후 ESI레지스터를 1증가합니다.

0101C46라인까지 연산을 진행하면 ESI레지스터 주소는 0101100D이며 여기의 4바이트 1FFBBFFF값을 EBX레지스터에저장을 하고다음 라인에서 SUB ESI, 0FFFFFFFCh 연산을진행하는데 011100D  FFFFFFFC =FFFFFFFF00111011 값이 된다이때 저장하는ESI레지스터 즉 4바이트(32비트레지스터기 때문에 00111011값만 저장이 됩니다.

이 주소 또한 0101100D이후4바이트값 1FFBBFFF UPX0에 옮긴후 UPX1의 다음으로 이동 할 바이트 주소값을 계산한 것입니다.

 

[그림.2-6]

 

그림.2-6부터 .textSection을 이어서 복구합니다.


 

[그림.2-7]

 

이와 같이 압축 해제를 시작하며 아래 그림과 같이 .text섹션을 0100415F까지 해제를 합니다.

 

[그림.2-8]

 

EDX  01001001 IAT 영역의 주소로 세팅한 후  0100415C영역에 IAT(01001001)영역의 값으로 세팅 하는데 IAT영역은 현제 0으로 채워져 있으므로 100415C영역에 0으로 세팅합니다.

0100415C영역은.SECTION .text IMPORT Directory Talbe의 시작주소 입니다.

 



[그림.2-9]

 

0100415C ~ 01004348까지 IAT영역의 빈공간 0의값을IMPORT DIRECTORY TABLE에 채웁니다.


[그림.2-10]

 

01004348까지 IAT영역의값을 복사 하다가 다시 01004343주소로 세팅하여 이어서 ECX EB9만큼 0으로 채웁니다.

EB9크기만큼루프가 진행되면 01004FFF까지 진행이 됩니다.

 


 [그림.2-11]

 

 01005000(.data Section 시작주소)전까지 0으로 채우게 됩니다.

지금까지 그림.2-8영역을 제외한.text Section의 모든 부분을 0으로 채웠습니다.

그림.2-11의 왼쪽 하단 박스 부분입니다참고로 그림.2-11 UPX 압축을하지 않은 원본 지뢰찾기 프로그램입니다.


 
 

 [그림.2-12]

 

그림.2-12 ESI UPX1영역의 주소값으로 세팅하며해당 되는 값은 18 입니다.

이 값을 UPX0 영역의 .dataSection의 시작주소 01005000에 복사합니다.
그림.2-11의 원본 지뢰찾기.data Section으로 확인 할 수 있습니다.
 

[그림.2-13]

 

그림.2-12를 반복해서 그림.2-13과같이 .data Section을 압축 해제합니다. 이 부분에서 압축을 해제 하며 그림.2-7부분에서 UPX가 압축이 해제될 때 가장먼저 해제 되었던 부분 과 그림 2-1부분의 UPX1 영역을참조 하는데 이 부분은 PE구조 관련하여 연관이 있는것으로 예상됩니다.

이 후 그림.2-13의 오른쪽hex view를 참조하면 01005134까지 복구 했는데 다시 EDX값을 01004343(그림.2-10참조)주소값으로 세팅하여 0 01005135부터 010065DF까지 0으로 채워 넣습니다.


 [그림.2-14]

 

다음으로 010065E0 공간에 순차적으로 .rsrc SectionIMAGE_RESOURCE_DIRECTORY_STRING값을 저장합니다.


 

[그림.2-15]

 

IMAGE_RESOURCE_DIRECTORY_STRING 저장 후이어서 .rsrc Section WAVE 01B0 0412 값을압축 해제 합니다.


 [그림.2-16]

 

.rsrc Section을 압축해제 하다보면 기존의 UPX1영역 01011000영역까지 덮어 씌어 압축 해제가 이루어집니다.

 

[그림.2-17]

 
(1) 함수명 복원

압축 해제를 기존 UPX영역까지 진행하면서 ESI값과 EDI값을 살펴보면 복사하는 원본주소(src)가 복사하는 대상(dst) 주소보다 큰 것을 확인 할 수 있습니다.

이 의미는 UPX1 영역을 덮어 쓰면서 뒤에 있는 UPX1영역의 뒷 부분의 있는 값(src)들을 dst에 복사하고 해제된 정상 코드로 덮어 씌우는 것을 확인 할 수 있습니다.


 [그림.2-18]

 

이어서계속 진행 하다보면 0101E8FA부터 0으로 채워 넣습니다.


 [그림.2-19]

 

그리고중간에 0101EFAC ~ 0101EFFF까지 PADDINGXX값을채워넣고 다시 0 0101FFFF까지 채워 넣습니다.


 

[그림.2-20]

 

01020005부터는 함수 이름을 복원 합니다.

[그림.2-21] 
 

처음압축을 해제 할 때 IMAGE_DOS_HEADER부분을 건너 뛰고 해제 했습니다.(그림.2-2참조)

함수이름 복원이 끝나면 PE Signature부터 복원이 되는데 이것은IMAGE_NT_HEADERS를 해제 하는 것 입니다그림.2-20을 보면 5045(PE)EAX레지스터에 이동 후 EDI(UPX0)으로 이동하는 것을 확인 할 수 있습니다.

 

 [그림.2-22]

 

IMAGE_NT_HEADER를 복원했습니다.

 

[그림.2-23]

 

01021CAB에서 ADD EDI(01020758) + ECX(FFFFFFFF) = 01020757와 같이 연산을 하고 결과값을 EDI레지스터에 저장합니다.

[그림.2-24]
 

그리고 EBX값과 EAX값을 계속 더하여ADD EBX, EBX연산 결과로 Carry가 발생 될 때까지 루프를 합니다루프가 진행될때는 계속해서 EAX EBX값이 증가 됩니다.


 [그림.2-25]

 

그림.2-24부터는 지금까지 실행되지 않았던 로직으로 가기 위해 분기문에서 검증하여 조건에 맞아 분기하는 부분입니다.

우선그림의 01021C39 01021C3C를보면 [XOR EAX, 0FFFFFFFFh], [JZ short loc_1021CB2]명령어가 있는데 EAX값이 0FFFFFFFFh와 같이 세팅이 되어야지만 JZ에서 분기를 하게 됩니다.

다시그림 위쪽의 01021C2E [SUB EAX,3] 명령을실행하면 01000002 – 3 = 00FFFFF가 되고 여기서[SHL EAX, 8] 명령으로 이어지는데 연산을 하면 FFFFF00값이 됩니다.

마지막으로 01021C36에서 [MOV AL, [ESI]]명령을 실행하여 ESI주소가 가르키는 값의 1바이트FF AL에 저장함으로써 EAX값은 FFFFFFFF값이 만들어저 최종적으로 [JZ shortloc_1021CB2]에서 분기를 합니다.

 

 

[그림.2-26]


분기이후에 특정 값 2BYTE를 체크하고 값이 아닐 경우 계속 루프을 돕니다.

체크되는 값은 01021CBD 01021CC3주소를 보면 E8,01 임을 확인 할 수 있습니다.

시작은 1001000주소에 00 바이트부터 시작 하게 되며 E8, 01 2BYTE가 체크가 되면 1021CBA로 분기합니다.

그림. 2-25 01021CB2에 보면ECX레지스터에 FC값을 저장합니다이것은이후에 분기하여 루프문으로 빠지게 되는데 FC(252)값 만큼 반복을 하게됩니다.


 

[그림.2-27]


01021CC8~ 01021CCA의명령으로 EAX = 0f190001, EBX = 0000006a 값으로 세팅한다.

그림.2-26의 명령어 분석 내용 입니다.

[SHLAX,8] AX레지스터를왼쪽 쉬프트 8 하여 0Ff190001에서 0001 0으로 만든다.

[ROLEAX, 10h] 0f190000을비트 손실 없이 왼쪽 쉬프트(상위 16비트와 하위 16비트를 교환한다.) 그 결과로00000f19 세팅.

[XCHG AL, AH] AX의 상위 1바이트 와 하위 1바이트를교환한다. (eax 상위 1바이트 ah값과 하위 1바이트 al값과 교환한다. #결과는0000190f )

sub     eax, edi          (190F - 0100146F(0100190F가 들어있는 주소값)=FF0004A0

sub     bl, 0E8h          (ebx 하위 1바이트 값을 E8과 뺀다. #결과 82)

add     eax, esi           (FF0004A0+01001000=000014a0)

mov     [edi], eax        (edi100146f 에 eax 000014a0를 뒤집어a0140000 저장)

add     edi, 5              (edi주소값에 5더함)

mov     al,bl              (bl값82 al에 더함)

loop    loc_10             (루프 fc(252)만큼)


 

 

[그림.2-28]


(2) IAT 복구

 

그림.2-20에서 010200005부터 함수 이름을 복원 했었습니다.

다시 EDI레지스터에 01020000주소로 세팅하고 주소가 가르키는 값을 EAX레지스터에 저장합니다.

 

[그림.2-29]

  

다음 EAX레지스터를 01028958주소값으로 세팅하게 되는데 세팅 된값은 PEView에서 확인하면 UPX1 IMPORT DLL Names정보임을 확인할 수 있습니다.

다음으로 EBX 68 .text섹션 VA 01001000을 더하여01001068값을 EBX에 저장합니다.

그리고 01028958주소를 인자로 전달하여 LoadLibrary함수를 호출합니다.

여기서 LoadLibrary 함수에 KERNEL32.DLL 주소값을 인자로전달하고 KERNEL32.DLL BASE ADRRESS Return합니다.

 


 

[그림.2-30]

 

그림.2-30에서는 EDI01020009 ECX에 저장하고 스택에 저장합니다.

그리고 REPNE SCASB 명령어로 첫번째 함수명 FindResourceW를찾고찾으면 PUSH EBP하는데 이것은 GetProcAdress 함수의 인자로 KENEL32.DLL Base Adress를 전달하기 위한 것입니다.

 LoadLibray로 리턴한 KENEL32.DLL Base Adress를 인자로 GetProcAdress를 호출하여 FindResourceW의 함수의 번지주소를 찾고 이 함수를 사용할 수 있도록 함수의 포인터를 Return합니다.

GetProcAdress함수의 Return 7C7DBC6E값을EBX주소값 01001068에 저장을 합니다.


 

[그림.2-31]

 

UPX압축하지 않은 지뢰찾기의 PE구조를 보면 FindResourceW VA 주소값이 복구되는 주소값과 일치합니다.

이와같은 방법으로 루프를 돌며 IAT함수를 복구합니다.

 

[그림.2-32]

ImportAdress Table 복구후 다음 루틴입니다.

01021D45와 01021D5A에서 EBP주소 값을 CALL 하는데 호출하는 함수는 VirtualProtect 함수입니다.

첫 번째 01021D45주소의 함수 호출은 인자값 으로 001000000 UPX실행압축파일의 VA주소값 과 사이즈 1000h(4096)만큼 4h(PAGE_EXECUTE_READWRITE)를 인자로 전달합니다.

저메모리 영역의 실행,읽기,쓰기 권한이 주어진 상태에서 바로아래 루틴을 실행하여 010001F7, 0100021F 주소의 값을60h로 변경합니다.

변경이후 VirtualProtect함수를 다시 한번 호출하여PAGE_READONLY 속성으로 변경하고 마지막으로 압축해제전 백업해 두었던 레지스터를 POPA 명령으로 모두 복구합니다.


 

[그림.2-33]

 

지금까지모든 압축이 해제되었고 마지막으로 01003E21의 원본코드가 있는 주소로 점프하여 메인 프로그램을실행합니다.


참고 URL 및 도서

http://www.test.co.kr

- OOO 도서

 

'[REVERSING]' 카테고리의 다른 글

MBR 확인  (0) 2013.10.21
visual st 2010 에서 release모드 컴파일시 ollydbg 디버깅 상태  (1) 2012.07.27