R136A1

[WEEK2] 패킹 & 패커 UPX & x32dbg로 수동 언패킹 본문

SWING/[21-2] REVERSING

[WEEK2] 패킹 & 패커 UPX & x32dbg로 수동 언패킹

r136a1x27 2021. 9. 25. 19:56

oopsys.tistory.com/177

일반 압축

비손실 압축 (=무손실 압축)

원본의 데이터 훼손 없이 용량을 줄이는 것

주 목적: 데이터 크기를 줄여 보관 및 이동에 용이하도록 함(.zip .7z)

파일 사용 시 해당 압축을 해제해서 사용(이 과정에서 데이터의 무결성 보장 필요)

 

손실 압축

데이터에 의도적으로 손상을 주어 압축률을 높임 (원본으로 되돌릴 수 없음)

주로 멀티미디어 파일에서 인간이 지각하기 힘든 범위의 데이터를 버리고 압축함 (.jpg .mp3 .mp4 .mkv)

 

 

 

실행 압축 = 패킹 Packing = Binary Packing

일반 압축       → 압축을 해제시켜야 해당 프로그램을 실행 시킬 수 있음

실행 압축       → 있는 그대로 일반 프로그램처럼 실행 가능

                              실행(PE)파일 내부에 압축해제코드를 포함하고 있어 실행되는 순간에 메모리에서 압축 해제시킨 후 실행시키는 기술

                              다시 말해, 수동으로 압축을 푸는 과정 없이 바로 프로그램을 실행시킬 수 있음(내부에서 자동으로 압축 해제)

 

                   대신, 실행할 때 마다 압축 해제 routine이 호출되기 때문에, 실행 시간이 미세하게 느려진다.

 

*실행(PE: Portable Executable)파일, (ex) exe, dll, sys

 

패킹을 하는 이유

  1. 압축: 데이터 크기 줄이기
  2. 악성코드: 작은 용량이 되어 빠르게, 많이 퍼지게 할 수 있으며 분석을 어렵게 한다.
  3. 데이터 보호: 코드 분석이 어렵도록 하여 취약하게 나타날 수 있는 중요정보를 포함하여
    어플리케이션에 대한 노출 최소화 (anti-debugging, anti-reversing, anti-disassembling, anti-dumpling)

 

관련 툴: PEiD, 실행파일 / 모듈이 어떤 구조로 패킹되어 있는지 알려줌

 

더보기

과거 하드디스크 용량이 적었을 때 자주 쓰지 않는 파일을 압축해 놓는 방법으로 디스크 사용량을 늘림
오늘날에는 짧은 시간에 프로그램을 다운받을 수 있기 때문에 온라인 상에 있는 파일 대부분은 압축되어있음
특히 실행 파일의 경우 zip 보다는 실행압축을 사용하는 것이 용량이 적게 사용됨

 

악성코드도 이런 점을 사용해 짧은 시간 안에 많은 곳으로 전파되도록 + 백신 제작자의 분석을 어렵게 하는 데 사용

즉, 백신 제작자는 실행압축을 빨리 해제하여 분석할 수 있다면 피해 규모를 현저히 줄일 수 있다.

과거 하드디스크 용량이 적었을 때 자주 쓰지 않는 파일을 압축해 놓는 방법으로 디스크 사용량을 늘림

※ 요즘 상용프로그램에는 잘 쓰이지 않음 / 악성코드 프로그램이 많이 사용

 

패커의 분류

Compressor: 파일의 크기를 줄여 배포를 쉽게 하기 위한 목적

                           주로 악성코드 전파 속도를 빠르게 하는데 목적이 있음

                           UPX, FSG, Upack, ASPack, Petite, MEW, MPress, KKrunchy, RLPack Basic, nPack

                           패커들 분석 sanseolab.tistory.com/10

                           * UPX(Ultimate Packer eXecutables): 다양한 OS, 다양한 파일포맷 지원, 오픈 소스, 패킹 및 언패킹 가능

Protector: 실행파일을 보호하는 것이 목적

                      크기는 늘어나지만 다양한 기법(Anti-Reversing, Emulating, 코드가상화, 난독화) 적용되어 언패킹과 디버깅 까다로움

                      프로그램이 쉽게 분석되어 악의적으로 동작한다면 보안사고를 초래할 수 있기때문에 분석 자체를 어렵게 함

                      (악성 프로그램 뿐만 아니라, 크래킹에 민감한 보안 프로그램에 사용됨)

                      패킹 시 원본 실행 코드를 암호화하여 저장하고, 이를 복호화하는 코드를 프로그램에 포함하는 방식으로 구현

                       Themida, VMProtect, Winlicense, Yoda, ASProtect, armadilo, UltraProtect, Morphine

※ Compressing과 Protecting에서 Protecting만 Packing으로 보는 경우가 있음

그런 의미에서 Compressor에 있는 패커들이 Protector에 있을 수도 있다.

실제로 UPX패킹 파일도 뜯어본 경우 암호화를 포함한 Protector처럼 보였다.

 

PE 패커 (=Run-Time Packer, PE 실행파일 전문 압축기)

파일의 크기 줄임

내부 코드와 리소스 감춤

 

        PE 패커 종류

        순수한 의도의 패커(VirusTotal에서 안전인증): 평범한 PE파일을 만들어냄 UPX, ASPack

        불순한 의도의 패커(VirusTotal에서 위험진단): PE헤더를 심하게 훼손시킴 UPack, PESpin, NSAnti

 

패킹된 파일의 특징

1. Section Name이 일반적이지 않거나 패커의 이름을 가짐

2. Code Section은 보통 첫번째 Section이나 패킹 시 다를 수 있음

3. Code Section은 보통 EP를 가리키므로 MEM_Excute권한을 가지지만 패킹 시 다를 수 있음

4. Unpacking 시 Packing된 데이터가 저장되어 있던 Section이 비어있거나

                                 Raw Size와 Virtual Size가 지나치게 큰 Section이 존재한다

 

패킹 원리

[원본 코드 A] ─패킹─> [코드 B+복호화 코드] ─실행─> 복호화 코드가 B를 읽어서 디스크가 아닌 메모리에 원본 A코드를 씀

- 디스크에 기록하는 것이 아니라 실행 시 마다 생겼다 없어짐

- 원본 프로그램과 동일한 기능을 수행한다

- 복호화 과정이 추가되어 원본보다 실행시간이 오래 걸림

 

haruhiism.tistory.com/49

암호화된 코드를 실행시점에 다시 복호화하려면 프로그램에 포함된 복호화 코드가 프로그램의 진입점(Entry Point)가 되어야 한다

그래서 프로그램이 메모리에 로드된 후 복호화 코드로 원본 실행 코드를 복호화하고 메모리의 어느 공간에 저장해 실행할 수 있기 때문

이 때 복호화된 원본 실행 코드의 진입점이 OEP(Original Entry Point)가 되며, 이를 찾는 것이 언패킹의 첫 번째 단계가 될 것이다.

 

그냥 프로그램을 실행시키고 메모리에 올라간 코드를 dump하면 되는 것 아닌가?

굳이 OEP를 찾아서 dump해야 하는 이유는?

→ 단순히 메모리에 올라간 코드를 dump 한다고 정상적인 실행 파일로 동작하지 않음

패커에 의해 프로그램이 패킹되면 PE 헤더에 명시된 Entry Point가 바뀌는데,

이것을 OEP로 바꿔줘야 프로그램이 원본 실행 코드의 올바른 곳에서 실행될 수 있기 때문이다.

그렇지 않으면 패킹된 프로그램의 Entry Point를 그대로 사용하게 되어 엉뚱한 곳에서 시작된다.

 

PE 분석프로그램에서 보면 Entry Point가 바뀌었고 컴파일러 정보 대신 패커 정보가 스캔된다.

또 Section에서 .text .rdata .data .rsrc 등 일반적으로 PE에서 볼 수 있는 섹션이 사라지고 UPX0, UPX1 등의 섹션이 생긴다.

특히 아무 데이터가 없는 UPX0 섹션에는 UPX 패커에 의해 패킹된 실행코드가 복호화된 후 저장되는 공간이다.

PE Header를 보면 패킹 전, 후 값은 다르지만 PE 구조는 유지하고 있다.

Windows OS에서 실행되어야하기때문에 어떤 프로그램이든 PE 구조는 망가뜨리지 않아야 하기 때문이다.

(이하 Scylla 플러그인으로 dump해서 복구하는 내용)

 

(이해 못함)

- 실제 패킹된 파일을 PEview로 열어보면 UPX0 섹션에서 Virtual Size는 7000인데 Raw Data의 크기는 0임을 확인할 수 있다.

☞ 이는 이 부분이 메모리상에서 쓰일 부분 즉, 복호화되는 A의 프로그램 코드가 여기에 적힐 것임을 유추가능하다.

- 복호화 코드는 실행권한이 있는 섹션에 존재할 것이다. UPX0는 복호화된 코드의 섹션이니 UPX1에 복호화코드가 있을 것이다.

☞ NT 헤더를 보면 Address of Entry Point가 AE50이다. UPX1 섹션에서 AE50 번지의 밑으로는 복호화 코드임을 알 수 있다.

☞ 그럼 UPX1 섹션에서 AE50 번지 위로는 압축된 코드일 것이다.

 

언패킹

1. 패킹이 되어있는 파일인지 체크 (PEid, ExeInfoPE)

2. (1) 툴로 unpack: 가장 정확, 간단, 빠름

     (2) 툴을 구할 수 없을 때: 수동으로 unpack - 그냥 실행시켜서 복호화 코드가 끝나는 지점을 찾는다

원본코드를 추출해내기 위해서는 복호화된 원본코드+원본코드의 헤더가 따로 필요함. 패킹된 코드의 헤더와는 조금 다름

                                                              ㄴ헤더를 제외한 섹션파일만 존재

 

중요한 것

"결과적으로 프로그램은 실행되어야 한다"

"실행 대상이 될 프로그램 기본구조 자체는 바꿀 수 없다(내용은 바뀔 수 있음)"

 

기본 원리

실행압축 프로그램이 실행되는 어느 시점에서는 압축되기 전 상태로 돌아가야 하며

그 때 메모리 상태를 dump 뜨고, Entry Point를 압축하기 전 위치로 설정해준다

OEP(Original Entry Point)와 PEP(Packed Entry Point)와 비교한다

이 OEP만 찾을 수 있다면 언패킹은 아주 쉽게 이루어질 수 있다.

 

ex) UPX 실행압축

처음 시작할 때 UPX1에 PEP가 있어 UPX1이 실행된다

UPX1의 Unpacker Routine이 끝나면 EIP가 UPX0의 어느 부분을 가리키게 되는데 이 부분이 OEP이다.

즉 EIP값을 검색하다가 UPX1에서 UPX0으로 넘어갈 때 프로그램 작동을 멈추면 됨

 

수동 언패킹(Manual Unpacking)

hyeonnii.tistory.com/66?category=694757

 

 

실습

haruhiism.tistory.com/49

blog.naver.com/suljang2/140201490368 i2sec 과정

 

더 상세한 이론

manggoo.tistory.com/3

 

안티리버싱

m.blog.naver.com/yheekeun/220754027725

 

패커 UPX - notepad.exe로 실습

UPX 다운로드받기: https://github.com/upx/upx/releases/tag/v3.96 

 

upx -o [output파일명] [패킹할 파일명]

 

더보기

 

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Accessories\notepad.exe은 바로가기 파일이고,

이것에 연결된 C:\Windows\System32\notepad.exe 파일을 사용했다.

 

원본을 사용하지 않고 파일을 복붙해서 사용하려 했으나, 복사본은 실행이 아예 되지 않는다.

그래서 그냥 원본을 사용하기로 했다.

 

그러나... 아래와 같이 순조롭게 진행되는가 했으나,

(사진은 notepad.exe지만 시도는 C:\Windows\System32\notepad.exe 로 했다)

백신프로그램에서 삭제하므로 잠깐 꺼주고 진행도 했다.
그러나 원래 패킹 파일은 실행시키면 원본 파일과 같게 실행되어야 했으나,
0xc00007b오류가 뜨거나 (이 경우 디버거에 올라가지도 않고 0xc00007b의 오류로 바로 종료된다)
아예 실행 낌새도 보이지 않는 등 (이 경우 디버거에 올라가긴 하는데, 여전히 실행되지 않고 오류떠서 종료된다.)

PEiD 결과

PEiD 결과에서 VisualStudio C++를 인식하지 못하였고,

PEview 결과

notepad_upx.exe의 경우 text, data, 등의 SECTION을 관찰할 수 없는 것은 괜찮았지만,
PEview에 올릴 때 오류창이 계속 떴다. (제대로 분석이 안되었을 가능성이 높다)

위 >더보기 와 같은 상황으로 인해, 인터넷에서 단독으로 실행할 수 있는 notepad.exe 구버전을 얻어오거나

Windows10이 아닌 Windows7 에서 실습을 진행하면 수월할 것이다.

 

upx -o [출력할파일명] [패킹할원본파일명] 으로 패킹을 진행한다

정상적으로 패킹되었다면, notepad_upx3.exe 도 notepad.exe와 다를 것 없이 실행된다.

PEiD 실행결과

PEview 실행결과

오류없이 매끄럽게 실행된다.

 

UPX 수동 언패킹 (MUP, Manual UnPacking)

https://plan0a-0z-entering-security.tistory.com/76

참고로 자동 언패킹은 각 패커에 내장된 경우가 많다.

UPX의 경우에도 upx.exe -o[출력될 파일명] -d [언패킹할 UPX패킹 파일명] 으로 언패킹할 수 있다.

 

OEP(=Original EP, 패킹 전 EP) 부분까지 진행을 해 두고, 메모리 덤프를 떠야한다.

그 이유는 패킹을 하게 되면 원래 코드가 파일 내에 직접 존재하지 않고

파일 실행 후 압축 해제 과정을 통해 "메모리"에 올라가기 때문이다

 

즉, OEP를 찾아내야 한다.

IMAGE_NT_HEADER → IMAGE_OPTIONAL_HEADER → ImageBase: 이미지베이스

IMAGE_NT_HEADER → IMAGE_OPTIONAL_HEADER → AddressOfEntryPoint: 프로그램의 시작위치(main)의 RVA

**프로세스가 가장 먼저 시작하는 위치가 아니다!

AddressOfEntryPoint의 경우 PEiD에서도 확인 가능하다(EntryPoint)

 

*Process가 시작하는 위치와 Program이 시작하는 위치가 다르기 때문에 한번 더 → 해야 EntryPoint를 볼 수 있다.

디버깅 전 notepad.exe의 Entry Point(EP) = 100739D 과 코드 형태.

위의 PEview에서 확인한 Image Base+Address of Entry Point 값과 동일한 것을 확인할 수 있다.

또는 PEiD에서 파악한 Entry Point가 739D였고, EP가 100739D ImageBase는 1000000이다. 로 추측할 수도 있다.

 

눈에 익혀두면 패킹된 notepad_upx.exe를 더 수월하게 파악할 수 있다. (정답지임. 공부하는 과정이니까 미리 알아둬도 O)

 

패킹 후 notepad_upx.exe의 EP=101C950

 

PUSHAD: 모든 범용레지스터를 스택에 저장

mov esi, 프로그램명_1011000

이 때, 1011000에서 1000000은 ImageBase이고, 10000은 섹션1(UPX0)의 Virtual Size(메모리에서의 크기), 1000이 RVA(시작위치)이므로, 섹션1의 마지막 위치=1011000=섹션2가 시작되는 위치 이다.

lea edi,dword ptr ds:[esi-10000]

위에서 저장한 esi에서 100000(섹션1의 Virtual Size)를 빼면 11000 섹션1이 시작되는 위치이다.

 

즉, 이 두 줄의 의미는ESI에 섹션2(UPX1)의 시작 주소 / EDI에 섹션1(UPX0)의 시작 주소를 저장한다는 의미이다.

 

또는 메모리 맵에서 바로 확인 가능

메모리맵 → 오른쪽 클릭 → 덤프로 따라가기

섹션1에는 아무것도 없고, 섹션2에는 뭐가 잔뜩 들어있다

 

(뒤의 흐름 요약: ESI가 가리키는 buffer(섹션2)에서 데이터를 읽어 압축 해제 후, EDI가 가리키는 buffer(섹션1)로 메모리 복사하여 압축 해제된 원본 파일의 코드가 저장되는 과정)

 

 

파일 트레이싱할 때는 루프에 진입하면 해당 루프가 어떤 기능을 하는지만 요약해서 파악한 다음,

루프의 바깥에 F2를 두고 실행시켜 빠져나오는 식으로 계속 진행하면 된다고 한다.

 

현재 디버깅할 파일은 UPX패킹된 파일이므로 UPX가 어떻게 패킹을 해제하고 푸는지를 중점적으로 보면 되고, (공부 목적으로는)

(언패킹 목적으로는) notepad의 원래 코드가 나오면 (memory에 올라가면) 해당 momory를 dump떠서 exe파일로 만들면 된다.

 

 

F8을 계속 누르고 있다보면 한 곳만 맴도는데, 그 곳이 바로 루프이다.

(또는 왼쪽에 보이는 화살표가 위로 올라가면 루프로 봐도 된다.)

 

루프1:

AL(EAX레지스터 AX, AH, AL 중 AL)

[리딩]

EDI←ESI (ESI의 값=주소 가 가리키는 값 1바이트)ESI++EDI++EBX+=EBXjne 101C971로 이동 # cmp 가 없는데 flag가 어디서 세팅되는건지? 그냥 무한 점프인건지?

EBX←ESI

ESI에서 FFFFFFFC 빼기

ADC carry bit 있게, ebx 더하기

jb 101C960로 이동 # cmp 가 없는데 flag가 어디서 세팅되는건지? 그냥 무한 점프인건지?

[요약]

ESI(섹션2)에서 한 바이트씩 읽어 EDI(섹션1)에 쓴다. 

 

루프이긴 한데, 체감상 매우 짧게 끝난다..

 

루프2: 

화면을 늘리고 늘려야 전체를 볼 수 있는 루프이다.

ESI(섹션2)에서 읽어들인 값을 여러 연산을 거쳐 디코딩된 값을 EDI(섹션1)에 쓴다.

 

 

루프3:

EDI(섹션1)를 계속 늘리면서, 그 값-E8 한 값이 1보다 클 때 탈출한다그리고 EDI(섹션1)값이 1이 아니면 다시 돌아가서 위 과정을 반복한다ESI(섹션2)와의 상호작용은 없다

 

루프4 (루프3이 포함됨):

BP 걸린 곳 아래 과정을 수행한다...결국 루프3으로 돌아간다무한루프 같다;원본 코드의 CALL/JMP 명령어의 destination 주소를 복원시켜주는 곳이라고 한다 (어떻게 안건진..)

 

루프5:

마지막 루프. 

바로 위를 보면 lea EDI, [ESI+1A000]이 있는데 현재 ESI(1001000_언제 초기화된거지) 101B000이다. (여전히 섹션2영역)

더 쉽게 따라가기: 주소를 클릭하고 덤프에서 따라가기 하면 현재 보고있는 덤프에서 따라갈 수 있다.
덤프를 보면 패킹 전 notepad.exe에서 사용되는 API이름들이 적힌 문자열이 저장되어 있다.UPX 패커가 패킹 시 notepad.exe의 IAT(Import Address Table)를 분석하여 따로 추출해서 섹션2에 저장해놓은 것

 

여기에서 연산을 통해 API 이름을 하나씩 뽑아서 GetProcAddress()에 전달하면 API 시작 주소를 얻을 수 있고,EBX 레지스터가 가리키는 원본 notepad.exe의 IAT 영역에 API 주소를 입력한다이 과정을 API 이름 문자열이 끝날 때 까지 반복하면 IAT 복원 과정 마무리

 

 

이하 남은 루프(과정)이 없기 때문에 OEP로 제어를 돌려주면 원본 notepad.exe의 압축 해제 모두 완료

POPAD로 스택에 저장된 값을 순서대로 레지스터에 세팅한다

두 개의 점프문 중 바로 위가 아닌 먼 곳으로 이동되는 두번째가 OEP 주소라 추측할 수 있다.

BP 걸고 들어가보면, OEP 주소와 원본 notepad.exe와 동일한 코드를 확인할 수 있다.

이 상태에서 x32dug의 내장 플러그인을 통해서 dump를 뜬다

1. 현재 EIP를 OEP에 위치시킨다

2. 플러그인-Scylla 선택

3. OEP 자동 설정됨 / IAT Autosearch 클릭 - 확인 - 확인 / Get Imports 클릭

4. Dump - 저장위치 선택 (이 때는 IAT가 복구되지 않은 version.exe)

5. Fix Dump - 4에서 저장한 dump파일을 선택

언패킹 대장정이 드디어 끝났다. (얘때문에 지각했다!!! 아니 다음부터 미리미리 해야지...)

 

https://doongdangdoongdangdong.tistory.com/201

https://m.blog.naver.com/wsi5555/221261289329

https://forensic-wetware.tistory.com/7

수동언패킹 문제 예시) https://r136a1x27.tistory.com/185 

 

그런데, 이렇게 진행하다 보면 뚜렷한 특징이 하나 보인다. POPAD!

이 명령어가 유일해보이는데, 이를 통해 OEP를 빠르게 찾을 수 있지 않을까?

※ 단, 이는 UPX 패커만의 특징으로 동작하지 않을 가능성이 있다. 

UPX 패커는 EP코드가 PUSHAD/POPAD 명령어로 둘러쌓여있다.

 

1) POPAD 명령어를 찾아서 그 이후의 JMP명령어에 BP를 설치한다

2) 스택에 Hardware Break Point를 설치한다

= CPU에서 지원, 특별한 DR(Debug Register)는 최대 4개의 주소를 저장할 수 있는데(주소저장DR0~DR3)

여기에 저장된 주소를 통해 접근(rwx)이 발생할 경우 특정한 exception을 날리게 되고,

실행이 멈추고, flow가 디버거로 넘어가게 된다.

 

PUSHAD에 HBP를 걸어놓으면, AD(레지스터 전부)가 저장되는 stack쪽에 HBP가 걸리는 것 같고,

POPAD를 통해 해당 AD(레지스터 전부)가 꺼내질 경우 HBP가 trigger되어 멈춘다.

 

Comments