반응형

Windows xp sp3 환경에서의 notepad.exe를 upx-3.96-dos로 패킹하여 분석하였다.

 

Windows 11 64bit 한경에서의 notepad.exe를 upx로 패킹하여 디버깅 하려 했으나 CFG 이슈로 인해 할 수 없었다.

 


 

위의 사진은 Windws xp SP3 환경에서의 notepad.exe와 UPX로 패킹한 notepad_upx.exe 파일이다.

 

기본 notepad.exe 파일의 크기는 66KB이지만, UPX로 패킹한 notepad_upx.exe 파일의 크기는 47KB로 줄어들었다.

 

 

하지만 notepad.exe를 일반적인 Zip 파일로 압축하면 35KB로 크기가 줄어든다.

 

즉, 일반적인 ZIP 압축보다 압축률이 떨어지는데 이는 패커로 압축한 결과물은 PE 파일이기 때문에 PE 헤더를 추가해야 하고 압축해제를 위한 코드도 넣어야 하기 때문이다.

 


 

 

- PE header의 크기는 동일(0 ~ 400h 영역)
- 섹션 이름 변경(".text" -> "UPX0", ".data" -> "UPX1")
- 첫 번째 섹션의 RawDataSize = 0(파일에서의 크기는 0)
- EP는 두 번째 섹션에 위치(원본 notepad.exe의 EP는 첫 번째 섹션에 위치)
- 리소스 섹션(.rsrc)의 크기는 거의 변하지 않았다.

 

위의 내용 중 첫 번째 섹션(UPX0)의 RawDataSize가 0이라는 점이 있다.

 

이는 첫 번째 섹션은 파일 내에서는 존재하지 않다는 것이다.

 

 

위의 사진을 보면 첫 번째 섹션(UPX0)의 VirtualSize 값은 10000이다.

 

반면 SizeOfRawData의 값은 0이다.

 

즉, UPX로 실행 압축된 파일은 실행되는 순간에 파일에 있는 압축 코드를 메모리에 있는 첫 번째 섹션에 풀어버린다.

자세히 말하자면, 압축해제 코드와 압축된 원본 코드는 두 번째 섹션에 존재하고, 파일이 실행되면 먼저 압축 해제 코드가 실행되어 압축된 원본 코드를 첫 번째 섹션에 해제시키는 것이다.

 

압축해제 과정이 끝나면 원본 EP 코드를 실행하는 것이다.

 


원본 notepad.exe의 EP 코드

 

 

위의 사진은 notepad.exe의 EP 코드 부분이다.

 

 

10073B2 주소에서 GetModuleHandleA 함수를 호출하여 notepad.exe 프로세스의 ImageBase를 구한다.

 

 

그리고 10073B4와 10073C0 주소에서 각각 “MZ”와 PE 시그니처를 비교한다.

 


notepade_upx.exe EP 코드

 

 

위의 사진은 notepad_upx.exe의 UPX EP 코드이다.

 

 

1015330이 EP 주소이며, 두 번째 섹션의 끝 부분이다.

 

실제로 압축된 notepad 원본 코드는 EP 주소인 1015330 위에 존재한다.

 

1015330 주소 부분을 보면 pushad로 EAX ~ EDI 레지스터 값을 스택에 저장한다.

 

그리고는 ESI와 EDI 레지스터를 각각 두 번째 섹션의 시작 주소(1011000)와 첫 번째 섹션 시작 주소(1001000)로 세팅한다.

 

UPX 파일에서는 첫 번째 섹션은 메모리에서만 존재하고, 첫 번재 섹션이 바로 압축 해제된 원본 파일의 코드가 저장될 곳이다.

 

디버깅할 때 이처럼 ESI와 EDI가 동시에 세팅되면 ESI가 가리키는 버퍼에서 EDI 가리키는 버퍼로 메모리 복사가 일어날 것이라고 예측할 수 있고

 

현재의 경우에는 Source(ESI)로부터 데이터를 읽어서 압축을 해제한 후 Destination(EDI)에 저장시킬 것이다.

목표는 UPX EP 코드를 전부 쫓아가서(트레이싱) 원본 notepad의 EP 코드를 찾아내는 것이다.

 

참고)

  • 리버싱에서는 원본 EP를 OEP(Original Entry Point)라고 표현한다.
  • 트레이스(Trace)란 단어의 의미 그대로 코드를 하나하나 실행하면서 쫓아가는 것을 말한다.
  • 실제 리버싱에서는 이렇나 실행 압축 코드를 하나하나 tracing 하지 않고 자동화 스크립트, 노하우 등을 통해 OEP로 바로 간다.

 


UPX 파일 트레이싱

 

- 방대한 코드를 tracing 할 때의 원칙은 루프를 만나면 그 역할을 살펴본 후 탈출한다.

(특히 압축 해제와 같은 과정은 무수히 많은 루프의 연속이기 때문에 적절히 루프를 탈출해야 빠르게 진행할 수 있다.)

 

x64dbg는 instruction tracing을 위한 독립된 창이 없어서 “로그” 창에 tracing 되고

ui도 메뉴에서 하지 못하고 커맨드 창에서 해야 한다.

https://forum.tuts4you.com/topic/39374-tracing/

 

// log 파일을 따로 지정, 지정하지 않으면 log 창에 출력된다
1. TraceSetLogFile "C:\\[경로]\\[파일이름].log"

// 로그 출력 형식으로 주소와 명령어를 출력하는데 이 내용을 바꾸면 레지스터의 값도 확인 가능
// ex) rax:{rax} rcx:{rcx} - 레지스터의 값 출력
// ex) [rsp+0x24] = {a:[rsp+0x24]} 같이 해서 메모리 레퍼런스까지 할 수 있다.
2. TraceSetLog "{p:cip} {i:cip}"

// TraceIntoBeyondRecord의 약자로 다음 조건까지 Tracing 하라는 의미이다.
// 아래의 구문은 특정 주소까지 tracing 하라는 의미인데 이 명령을 실행하면 현재 주소부터 지정한 주소까지 step into로 실행하며 tracing을 기록한다.
// tibt의 두 번째 인자로 명령어 개수를 줄 수도 있다.
3. tibt rip==[주소]

 

그리고 일반적인 명령어로는 아래와 같다.

  • Ctrl + F7과 Ctrl + F8은 속도가 약간 느리며, Ctrl+Alt+F7과 Ctrl + Alt + F8은 미리 설정한 트레이스 조건에서 자동으로 멈출 수 있고, 로그를 남길 수도 있다.

명령어 단축키 설명

Animate into Ctrl+F7 안으로 자동 진행(Step Into 명령 반복)
Animate Over Ctrl+F8 건너서 자동 진행(Step Over 명령 반복)
Trace Into Ctrl + Alt + F7 안으로 추적(Step Into 명령 반복)
Trace Over Ctrl + Alt + F8 건너서 추적(Step Over 명령 반복)
일시 정지 F12 트레이싱을 멈춘다.

 


루프 #1

 

EP 코드에서 Animoate Over 명령을 내리면 트레이싱이 시작되는데, 이는 곧 정신없이 커서가 위아래로 움직인다.

 

 

Ctrl + F8을 누르고 조금 기다리다 보면 위의 부분에서 커서가 반복되는데 F12 키를 눌러 일시정지한다.

위의 부분이 첫 번째 루프인 것이다.

 

 

10153DD 주소에 BP를 걸고 Ctrl + F2 키를 눌러 재실행한뒤 EP 코드로 이동 후 Tracing을 한다.

 

 

그러면 10153DD 부분에서 트레이싱이 멈추게 될 것이고 이 상태에서 레지스터의 값을 보면 ECX 레지스터의 값이 36B로 설정되어 있다.

 

ECX 레지스터는 루프의 카운터 수 값을 가지는 레지스터로 첫 번째 루프의 회전 수는 총 36B(=875)번이라는 것이다.

 

그리고 첫 번째 루프의 내용은 EDX(1001000)에서 한 바이트를 읽어 EDI(1001001)에 쓰는 것이다.

 

EDX 레지스터가 가리키는 1001000 주소는 첫 번째 섹션(UPX0)의 시작 주소이며, 메모리에서만 존재하는 섹션이다.

(내용은 전부 NULL)

 

즉, 이 루프는 ECX 레지스터의 값은 한 번 회전할 때마다 1씩 감속하고 정반대로 EDX와 EDI는 1씩 증가하면서 EDX에서 1byte 값을 읽어 EDI에 쓰는 것이다.

 

실행 압축된 파일을 디버깅할 때 이러한 루프는 빠져 나가야 한다.

 

 

10153DD 주소의 BP는 제거하고, 10153B6 주소에 BP를 설치한 후 F9 키로 루프를 탈출한다.

 


루프 #2

 

10153B6 주소에 설정된 BP를 해제하고 다시 트레이싱을 시작한다.

 

트레이싱을 조금 더 진행하다보면 위의 루프를 만나게된다.

 

이 루프는 본격적인 디코딩(Decoding) 루프이다.(또는 압축해제 루프)

 

ESI가 가리키는 두 번째 섹션(UPX1)의 주소에서 차례대로 값을 읽어서 적절한 연산을 거쳐 압축을 해제하여 EDI가 가리키는 첫 번째 섹션(UPX0)의 주소에 값을 써준다.

 

이 과정에서 사용되는 명령어들은 아래와 같다.

 

0101534B | 8807                     | mov byte ptr ds:[edi],al                             |
0101534D | 47                       | inc edi                                              |

---

010153E0 | 8807                     | mov byte ptr ds:[edi],al                             |
010153E2 | 47                       | inc edi                                              |

---

010153F1 | 8907                     | mov dword ptr ds:[edi],eax                           |
010153F3 | 83C7 04                  | add edi,4                                            |

AL(EAX)에는 압축해제된 데이터가 있고, EDI는 첫 번째 섹션의 주소를 가리키고 있다.

 

 

이 두 번째 루프를 탈출하기 위해서는 위와 같이 1015402 주소에 BP를 설치하고 F9 키로 실행해야 한다.

 

 

 

1015402 주소까지 잘 도착했다면 위의 덤프 창에 나타나 있는 것처럼 첫 번째 섹션(UPX0) 영역(1007000)에 압축 해제된 코드가 쓰여진 것을 확인할 수 있다.

(원래는 NULL로 채워져 있던 영역이다.)

 


루프 #3

 

1015402 주소에 설정된 BP를 해제하고 다시 트레이싱을 시작한다.

 

 

이것은 원본 코드의 CALL/JMP 명령어(op code : E8/E9)의 Destination 주소를 복원시켜주는 코드이다.

 

 

 

이 루프도 1015436 주소에 BP를 설정하여 탈출할 수 있다.

 

이제 IAT(Import Address Table) 세팅만 하면 UPX 압축해제 코드는 끝난다.

 

참고)

일반적인 실행 압축 파일은 원본 파일의 코드, 데이터, 리소스의 압축 해제 과정이 끝나면 IAT를 세팅하고 OEP로 간다.

 


루프 #4

 

 

위의 부분이 IAT를 세팅하는 루프이다.

 

1015436 주소부터 다시 트레이싱을 진행하면

 

 

위의 루프를 만난다.

 

1015436 주소에서 EDI = 1014000으로 세팅되며, 이곳은 두 번째 섹션(UPX1) 영역이다.

 

 

이곳에는 원본 notepad.exe에서 사용되는 API 이름 문자열이 저장되어 있다.

 

위의 문자열들은 UPX가 원본 notepad.exe를 실행 압축시킬 때 원본 파일의 IAT를 분석하여 프로그램에 사용되는 API 이름 목록을 뽑아 놓은 것이다.

 

이 API 이름 문자열들을 가지고 1015467 주소의 GetProcAddress()를 호출하여 API 시작 주소를 얻은 후 EBX 레지스터가 가리키는 원본 notepad.exe의 IAT 영역에 API 주소를 입력한다.

 

이 과정을 API 이름 문자열이 끝날 때까지 반복하면 원본 notepad.exe의 IAT 복원 과정이 마무리된다.

 

원본 notepad.exe의 압축을 모두 해제하였으므로, OEP로 제어를 돌려줘야 한다.

 

 

위의 코드 부분이 OEP로 점프하는 코드이다.

 

최종적으로 10154BB 주소의 JMP 명령어에 의해 OEP로 가게된다.

 

 

JMP 하는 주소 100739D는 원본 notepad.exe의 EP 주소이다.

 

참고)

10154AD 주소의 POPAD 명령은 바로 UPX 코드 중 가장 첫 번째 명령어인 PUSHAD에 대응되는 명령으로, 레지스터를 원래대로 복원시키는 명령이다.

 


UPX의 OEP 빨리 찾는 방법

 

패커로 압축된 파일을 디버깅 할 때 많은 루프를 만나지만, 매번 같은 방법으로 루프를 탈출해서 OEP로 가는 것은 힘들다.

 

실전에서는 보다 쉬운 방법으로 간단하게 OEP로 들어간다.

 

아래의 방법들은 모두 UPX 기준이다.

 

 

POPAD 명령어 이후의 JMP 명령어에 BP설치

 

UPX 패커의 특징 중 하나는 EP 코드가 PUSHAD/POPAD 명령어로 둘러싸여 있다는 것이다.

 

그리고 OEP 코드로 가는 JMP 명령어가 POPAD 명령어 바로 이후에 나타난다.

 

이 JMP 명령어에 BP를 설치하고 실행하면 바로 OEP로 갈 수 있다.

 

참고)

  • PUSHAD는 8개의 범용 레지스터(EAX ~ EDI)의 값을 스택에 저장하는 명령어이다.
  • POPAD는 PUSHAD 명령에 의해서 스택에 저장된 값을 다시 각 레지스터들에게 입력하는 명령어이다.

 

 

스택에 하드웨어 BP 설정

이 방법 역시 UPX 특징인 PUSHAD/POPAD 명령어의 특성을 이용한 것이다.

 

 

 

1015330 주소의 PUSHAD 명령을 실행한 후 스택이다.

 

EAX ~ EDI까지 스택에 차곡차곡 저장되어 있다.

 

 

Dump 창에서 DFF58 스택 주소로 가면 위와 같다.

 

 

위와 같이 DFF58 주소에 하드웨어 BP를 설정한다.

 

하드웨어 BP는 CPU에서 지원하는 BP이며, 4개까지 설치가 가능하다.

 

일반적인 BP와 다른 점은 BP가 설정된 명령어가 실행된 이후에 제어가 멈추게 된다는 것이다.

 

이 상태에서 실행하면 압축이 해제되면서 코드가 실행되고, POPAD가 호출되는 순간에 하드웨어 BP가 설정된 DFF58 주소를 엑세스하고, 그때 제어가 멈춘다.

 

바로 밑에는 OEP로 가는 JMP 명령어가 있다.

(이 기법의 동작 원리를 잘 익혀두면 향후 다양한 파일의 디버깅 시에 요긴하게 사용할 수 있다.)

 

 

반응형

+ Recent posts