패커(packer) : PE 파일을 실행 가능한 형태로 파일을 압축시켜주는 프로그램
- 프로텍터(protector) : 압축과 동시에 보호(anti-debugging, encryption 등) 기능을 포함
- 패커 : 단순히 파일의 사이즈만 줄임
악성코드들은 분석가들이 자신을 분석하기 어렵도록 대부분 패킹 기술이 적용되어 있다.
7.1. PE 파일 패킹 (packing)
윈도우 PE 파일은 그 구조에서 정형화된 형식을 벗어날 수 있도록 허용하고 있다. PE 구조체 정보 중에서 몇 가지는 싲가 위치와 크기를 변경할 수 있다. PE 파일 구조에서 사용하지 않는 값도 있으며, 주어진 규격 외의 값을 사용할 수 있도록 허용된 부분도 있다. 예를 들어 아래와 같은 내용이 이에 해당된다. 이러한 PE 구조를 활용하여 다양한 PE 파일을 생성할 수 있다.
구조체 이름 | 주요 멤버 | 의미 |
IMAGE_DOS_HEADER | e_lfanew | NT 헤더 위치 |
IMAGE_FILE_HEADER | NumberOfSections | 섹션 수 |
SizeOfOptionalHeader | 옵션 헤더 크기 | |
IMAGE_OPTIONOAL_HEADER32 | SizeOfHeader | PE 헤더 크기 |
ImageBase | 메모리 로딩 주소 | |
SizeOfImage | 메모리 로딩 크기 | |
AddressOfEntryPoint | 코드 시작 주소 | |
IMAGE_SECTION_HEADER | VirtualAddress | 섹션 시작 주소 |
VirtualSize | 섹션 로딩 크기 |
7.1.1. 패커의 주요 기능
패커는 실행 압축 기능을 가진다. 실행 압축 기능은 실행하기 전에는 압축된 형태로 존재하고, 실행하는 도중에 이 부분의 압축을 해제하고 실행한다. 패커는 실행 파일의 코드 부분을 압축하고, 이 부분의 압축을 해제하는 코드를 추가한다. 그리고 실행 시작 위치(EP, Entry Point)를 이 추가된 코드로 변경한다. 이렇게 만들어진 파일을 실행 압축 파일이라 한다.
실행 압축 파일도 역시 PE 구조의 파일이다. 내부의 원본 PE 파일의 압축된 코드와 디코딩(decoding) 루틴이 존재한다. 그리고 EP는 디코딩 루틴을 가리킨다. 따라서 실행되는 순간에 메모리에서 압축을 해제시킨 후 원본 코드를 수행하게 된다.
초기에 패커는 PE 파일의 크기를 줄이는 목적이었지만, 최근에는 PE 파일의 내부 코드와 리소스를 감추는 목적이 크다.
PE 프로텍터는 리버싱으로부터 보호하기 위한 것이다. 압축 해제 기능 뿐만 아니라 안티 리버싱 기능이 적용된 것을 말한다. 안티 리버싱 기법으로는 안티 디버깅(anti-debugging), 가상머신 탐지(anti-vm), 코드 난독화(code obfuscating), 다형성 코드(polymorphic code), 더미 코드(garbage code), 디버거 탐지(debugger detection) 등의 기법이 있다.
프로텍터를 사용하는 목적은 크래킹(cracking) 방지와 코드 및 리소스 보호를 위한 것이다.
프로텍터 주요 사용 용도:
- 온라인 게임 설치 시 자동으로 설치되는 프로그램으로 게임 해킹 툴의 실행 방지
- 악성코드에서 백신의 분석을 방해할 목적
- 소프트웨어 제품에 대하여 주요 기능의 분석과 인증 과정 숨김
7.1.2. 패커 종류
가장 많이 알려진 패커로는 단순 암호화 및 파일 사이즈만을 줄여주는 UPX와 Aspack이 있다. 이 외에도 보호 기능이 포함된 Armadillo, MoleBox, YodaCrypter, PECompact, Themida 등이 있다.
UPX는 Ultimate Packer for Executabls의 약자로 1998년도에 시작되었으며, 공개용으로 패커 실습에 가장 많이 이용된다. UPX는 여러 도구에서 패킹과 언패킹을 지원하고 있다.
ASPack은 단순 암호화를 제공하는 목적으로 만들어졌으며, 상용 프로텍터 버전으로 ASProtect도 있다.
Armadilllo는 상용 버전으로, 이 패커는 압축 기능 외에 안티 디버깅 기능도 포함하고 있다.
7.1.3. UPX 패커
대표적인 패킹 도구인 UPX를 이용해 파일을 패킹할 수 있다.
upx.exe -o packed_file.exe original_file.exe
original_file.exe 파일을 패킹하여 packed_file.exe로 저장한다. UPX 패커를 사용하면 실행 압축 기술을 통해 파일 크기가 줄어든다. 패킹된 실행 파일을 실행하면, 원래 파일과 똑같은 동작을 수행한다. 다음 그림에서 notepad.exe 파일을 패킹한 결과 파일 크기가 71.21%로 줄어들었음을 확인할 수 있다.
UPX 패킹의 구조의 특징:
- 코드 섹션(.text)과 데이터 섹션(.data)이 패킹되고, 섹션 이름이 변경됨
- 코드 섹션은 실행 압축되어 UPX0으로 저장되고, 언패킹 루틴은 UPX1 섹션에 작성됨
- 패킹된 파일의 EP는 UPX1 섹션으로 변경됨
UPX 패킹 전과 후의 PE 헤더를 비교:
- 여러 개의 섹션이존재하던 파일이 패킹 후에 3개의 섹션으로 줄어듦
- 섹션에 대한 권한이 바뀜
7.1.4. 패킹의 증상
실행 파일이 아래와 같은 증상이 많이 보인다면 해당 실행파일은 패킹되었을 확률이 높다.
- 섹션 Name이 일반적이지 않거나 패커의 이름을 가진다.
- 패킹된 데이터가 언패킹 되면서 저장되어야 하는 섹션이 필요하므로, 비어있는 섹션(RawSzie = 0)이 있거나, (VirtualSize - RawSize)가 지나치게 큰 섹션이 존재한다.
- EP가 가리키는 섹션이 코드 섹션으로 실행 권한(MEM_EXECUTE)을 가지는데, 다른 섹션에 실행 권한이 있다.
- 코드 섹션보다 다른 섹션의 크기가 크다.
- EP가 가리키는 코드 섹션이 일반적으로 첫 번째 섹션인 경우가 많지만, 첫 번째 섹션이 아니다.
- 언패킹에 주로 사용되는 API가 많이 사용되었다.
- 파일 상에 존재하는 문자열(string)과 메모리에서 실행 상태의 문자열(string)에 차이가 많이 난다.
7.1.5. 패커 시그니처 탐지
패커 시그니처
패커는 특정 알고리즘과 코드가 수행되므로 일정한 시그니처를 가진다. 따라서 이러한 시그니처를 기반으로 패커를 쉽게 탐지할 수 있다. 시그니처가 정상적으로 탐지되기만 한다면, 빠른 속도로 언패킹 도구를 사용하여 언패킹을 수행할 수 있다.
윈도우즈에 대한 실행 파일 패커 시그니처는 데이터베이스화하여 탐지 및 식별에 활용할 수 있다.
OpenRCE - http://www.openrce.org/reference_library/packer_database
OpenRCE
This reference section was initially contributed to OpenRCE by quig. To contribute to this section, please contact Pedram for admin access to the packer database. The Packer Analysis Database provides the analysis and description for an array of commonly u
www.openrce.org
위 링크에서 그 정보를 찾아볼 수 있다.
위와 같은 패커 데이터베이스에는 패커 제작자, 안티 디버깅 여부, 패커 전달 명령 코드, EP에 대한 시그니처 정보 ,언패커 정보를 제공한다. 다음은 그 중에서 많이 알렺니 패커에 대한 정보이다.
- UPX 패커 정보
제작자 | Markus and Laszio | |
섹션 이름 | UPX0, UPX1 | |
안티 디버그 | 없음 | |
전달 명령 | 61 | POPAD |
E9 [4bytes] | JMP [Offset] | |
EP 시그니처 | 60 | PUSHAD |
BE [4bytes] | MOV ESI, [Value] | |
8DBE [4bytes] | LEA EDI, DWROD PTR DS:[ESI+Value] | |
57 | PUSH EDI | |
83CD FF | OR EBP, FFFFFFFF | |
EB 10 | JMP SHORT [Relative Jump] | |
90 | NOP |
- ASPack 패커 정보
제작자 | Alexey Solodovnikov | |
섹션 이름 | aspack | |
안티 디버그 | 없음 | |
전달 명령 | push [patched value] | |
ret | ||
EP 시그니처 | mov eax, 0A870h ; OEP 옵셋 | |
push eax | ||
add eax, [ebp+422h] ; ImageBase + OEP 옵셋 | ||
pop ecx | ||
or ecx, ecx | ||
mov [ebp+3A8h] eax ; 패치 정보를 옵셋으로 | ||
popa |
엔트로피(entropy) 기반 패킹 탐지
시그니처 기반 언패킹 방법은 시그니처가 변경되었을 때 언패킹을 수행할 수 없다. 시그니처 기반의 패커 탐지 도구는 기존의 시그니처 값을 변조함으로써 쉽게 우회가 가능하다. 시그니처가 변조되어 어떤 패킹 방식에 의해 패킹이 되었는지 알 수 없게 되면, 자동 언패킹은 불가능하게 된다.
시그니처를 파악할 수 없는 경우 정보 엔트로피를 기반으로 패킹 여부를 파악할 수 있다. 정보 엔트로피란 정보의 양을 수치화하여 공식을 만들어 정의한다. 일반적으로 정보 이론에서의 엔트로피는 메시지의 압축에 관한 분야에 대해서 연구할 때 많이 사용한다. 예를 들어, 압축 알고리즘의 압축률을 평가할 때 유용하게 사용한다. 엔트로피가 높은 데이터일수록 나타날 수 있는 모든 비트들이 골고루 존재함을 의미하기 때문에 어떤 압축 파일의 엔트로피 수치가 높을수록 압축률이 높다고 말할 수 있다.
패킹된 실행 파일은 일반적인 실행 파일보다 더 높은 엔트로피를 가진다. 따라서 엔트로피 계산 결과 값이 일반적인 엔트로피 값보다 높게 측정된다면 이는 패킹된 실행 파일일 가능성이 높다. 하지만 100% 정확하지는 않기 때문에 오탐이 발생할 수 있다.
패커 탐지 도구인 DIE(Detect It Easy)로 패커를 탐지해보자. DIE는 PE 구조를 파악하고, 패킹 여부를 판단하는 데 도움을 주는 도구이다. PE 파일에서 시그니처 기반으로 어떠한 패커로 패킹되어 있는지를 바로 확인할 수 있다. 패커 정보 오른쪽에 [S] 버튼을 클릭하면, DIE에서 사용하는 패커 시그니처를 확인할 수 있다.
위 그림에서 [Entropy] 버튼을 클릭하면 파일의 엔트로피를 확인할 수 있다. 엔트로피 정도는 원본 파일이 67%, UPX로 패킹된 파일은 77%, ASPack으로 패킹된 파일은 89%가 나왔다. 그리고 엔트로피에 따르면 ASPack으로 패킹된 경우만 패킹되었다고 판단하였다. 따라서 엔트로피에 의한 방법은 보조적인 수단일 뿐, 패커를 정확하게 판단하지는 못한다. 또한 패킹된 실행 파일인 것이 확인되었더라도, OEP 탐색과 수동 언패킹의 추가적인 작업이 필요하다.
7.2. PE 파일 언패킹(unpacking)
PE 파일이 패킹되어 내부 구조가 바뀌더라도 프로그램의 기본 구조 자체는 변하지 않는다. 언패킹은 패킹된 실행 파일을 패킹하기 전 상태로 되돌리는 것이다. 실행 압축된 프로그램은 실행되어 언패킹 코드가 먼저 수행된다. 언패킹 과정을 통해 전의 상태를 복원하고, EP를 실제 실행할 위치인 OEP(Original Entry Point)로 옮겨준다. 결국 OEP를 찾았다면 언패킹이 가능하다는 것을 의미한다.
7.2.1. 언패킹의 목표
언패킹은 패킹된 실행 파일에서 패킹되기 전 상태의 원본 코드를 추출하기 위한 것이다. 코드 분석을 수행하려면, 패킹된 실행 파일에서 패킹되기 전 상태의 원본 실행 파일을 추출하는 것이 궁극적인 목표가 된다.
언패킹을 수행하기 위해서는 패킹된 실행 파일에서 언패킹을 수행하는 코드를 추출할 필요가 있다. 언패킹 코드는 대부분 반복 작업을 수행하는 루프 형태의 코드가 많다. 그리고, 언패킹 작업이 모두 끝나면 원본 코드의 시작 주소로 이동한다. 그러나 패커 제작자에 따라서 원본 코드의 일부만을 언패킹하고, 실행한 다음 또다시 일부 코드를 언패킹하는 과정을 반복하도록 만들 수도 있다.
7.2.2. 언패킹 방법
- 자동 언패킹 방법
: 패커에서 지원하는 언패커를 사용하거나, PE 도구에서 언패커 플러그인을 이용 - 수동 언패킹 방법
: 디버거를 사용하여 OEP를 찾고 해당 영역의 메모리를 덤프하여 저장한 후에 파일을 재구성. OEP를 찾기 위해 디버거를 사용하여 언패킹 과정을 분석하고 디버거 스크립트를 작성. (IAT 복구와 EP 재설정과 같은 작업이 동반)
UPX로 패킹된 PE 파일을 UPX로 언패킹하는 과정을 알아보자.
upx.exe -d -o unpacked_file.exe packed_file.exe
패킹된 packed_file.exe 파일을 언패킹하여 unpacked_file.exe로 저장한다. 디버거를 이용하여 실행할 경우에는 UPX1 섹션에서 코드가 실행되는 동안에 UPX0 섹션의 코드가 변경되고 있음을 확인할 수 있다. 언패킹 루틴이 완료된 후, UPX1 섹션에 저장되어 있는 윈도우 API 문자열을 이용해 IAT를 복구할 수 있고, IAT 복구가 끝나면 OEP로 이동된다.
7.2.3. 도구를 이용한 자동 언패킹
PEiD 활용
PEiD 도구의 "Extra Information"에서 패킹된 정보를 알 수 있다. PEiD 도구의 [Plugins] → [generic OEP Finder]와 같은 플러그인을 이용하여 일부 패킹 프로그램의 OEP 주소를 찾을 수 있다.
사실 PEiD 플러그인을 이용한 방법은 제대로 언패킹이 되지 않는 경우가 있다. 따라서 전용 언패킹 도구나 자체 제작한 언패킹 도구의 사용을 권장한다. 대신에 PE 파일에 대한 패킹 정보나 OEP 주소를 알아내기 위해 사용한다. PEiD에서는 패커에 대한 시그니처(signature) 데이터베이스를 통해 탐지한다. 만약 새로운 패커 정보가 있다면, 이 패커를 분석하여 찾은 새로운 시그니처를 등록할 수 있다.
PE Explorer 활용
상용 도구로서 기본적으로 NsPack, Upack, UPX 패커에 대한 언패킹 플러그인을 제공하고 있다.
CFF Explorer 활용
CFF Explorer에는 UPX 패커에 대한 패킹과 언패킹 기능을 제공한다. UPX 패킹에 대해서도 압축 정도에 따라 실행을 구분할 수 있다. 패킹이나 언패킹을 수행한 후엔느 새로운 파일로 저장해야 한다.
7.3. 수동 언패킹 (manual unpacking)
대부분의 패킹 도구는 디버깅을 방지하기 위해 고의로 예외(exception) 처리를 발생시키는 코드를 삽입한다. 디버거를 사용할 경우, 실행 중에 예외가 발생한 코드를 무시하고 디버깅을 진행하게 된다. x64dbg에서는 여러 중단점이나 예외 상황에 대하여 설정을 바꿀 수 있다. 또한, 예외 처리 루틴을 참조한 지점을 검색하여 OEP에 가까운 지점을 찾을 수 있기도 한다.
7.3.1. UPX 패커의 언패킹 과정 디버깅
UPX 패킹의 경우 아래처럼 UPX0, UPX1, UPX2(.rsrc) 섹션으로 구성된다.
EP는 UPX1에 있으며, UPX1의 코드가 실행되는 동안에 UPX0 섹션에 코드를 저장한다. UPX0 섹션은 Raw_size가 0이지만, Virtual_Size가 0x6000으로 되어 있다 .이것은 파일의 섹션 크기는 0이지만, 메모리로 적재하면 0x6000까지 커진다는 말이다. 즉, 이 공간에 언패킹된 코드가 저장된다. UPX0와 UPX1 섹션의 특징(Characteristics)을 보면 0xE로 시작된다. 즉, 실행, 읽기, 쓰기 권한이 모두 있다. 이것은 언패킹을 수행하는 섹션과 언패킹 후에 코드가 저장되는 섹션에는 실행 권한이 필요하기 때문이다.
언패킹 루틴은 일반적으로 현재 상태의 레지스터 값을 저장한 후에 수행된다 .그리고 언패킹 과정이 모두 끝나면 저장된 레지스터 값을 복구한다. 이때, 흔히 나타나는 코드는 PUSHAD와 POPAD이다.
- PUSHAD : 모든 레지스터를 스택에 저장
- POPAD : 스택에 저장된 레지스터 값을 복원
이 코드를 찾으면 언패킹 루틴을 찾을 수 있다. UPX의 경우 레지스터 상태 보관 과정과 레지스터 복원 코드는 아래와 같다.
레지스터 상태 보관 (PUSHAD) |
60 | PUSHAD |
BE [4bytes] | MOV ESI, [Value] | |
8DBE [4bytes] | LEA EDI, DWORD PTR DS:[ESI+Value] | |
57 | PUSH EDI | |
83CD FF | OR EBP, FFFFFFFF | |
EB 10 | JMP SHORT [Relative Jump] | |
레지스터 상태 복구 (POPAD) |
61 | POPAD |
E9 [4bytes[ | JMP [offset] |
언패킹 루틴이 수행되면, 빈 섹션에 원본 코드를 복원하거나 특정 섹션의 값을 변경한다. 이러한 언패킹 과정이 완료되면 원본 코드가 복원되고, 이 코드로 EP를 옮기면 원본 코드가 수행될 수 있다. 따라서 언패킹 과정이 완료된 코드 섹션을 복사하고, IAT를 복구하면 원본 코드와 같은 동작을 수행시킬 수 있게 된다. UPX1 섹션에 있던 Windows API 문자열을 이용하여 IAT를 재구성할 수 있다. 이 과정이 끝나면 새로운 섹션의 OEP로 이동할 수 있을 것이다.
이 과정을 디버거로 확인해보자.
① 코드 섹션에 접근하는 상황을 확인한다. PEtite나 ASPack으로 패킹된 프로그램은 EP에 "pushad" 명령어가 있는데, 이것은 모든 레지스터를 스택에 저장하는 명령이다. UPX의 경우 진입점(EP)에 바로 "pushad" 코드가 나타난다. 다음은 UPX로 패킹된 프로그램에 대한 언패킹 루틴의 시작 지점이다.
언패킹 코드에서 특정 명령어를 찾으려면 아래와 같이 선택하여 명령어 창에 해당 명령어를 입력하면 된다.
② pushad 명령어를 실행하면, 레지스터 값이 스택에 저장된다. 이때, 레지스터 창의 ESP 또는 EDI 위에서 (오른쪽 버튼 → "덤프에서 따라가기(D)")하면 덤프 창에 해당 주소의 값이 나타난다.
③ 먼저 섹션의 ([보기(V)] → [메모리 맵(M)]) 또는 [Alt + m])으로 메모리 맵을 확인할 수 있다. 여기에서 코드 섹션에 메모리 중단점(break point)을 설정한 후 실행하면 해당 섹션의 메모리 접근 순간을 포착할 수 있다. 원래의 코드가 복원될 섹션이 UPX0이므로 이 섹션에 하드웨어 중단점을 설정한다. 덤프 창에서 (오른쪽 버튼 → 중단점(B) → 하드웨어, 쓰기(W)) 를 지정하면 중단점이 걸리고 ,이후에 해당 메모리에 접근할 때 중단되므로 그 이후에서 OEP 값을 찾아낼 수 있다.
④ 중단점이 설정된 상태에서 실행[F9]를 할 경우, UPX1메모리에 쓰기 위해 접근하는 순간에 이를 수행하는 코드 위에서 멈추게 된다. 코드의 표시되는 부분에서 EDI가 가리키는 곳에 한 바이트씩 기록한다.
⑤ 이를 통해 코드 섹션에 접근하는 상황을 확인할 수 있다. 이때, 목적지 주소를 저장하는 EDI 레지스터 값은 UPX0 섹션을 나타내는 0x401000이다. 메모리 맵에서 이 주소를 살펴보자. 해당 주소의 메모리 덤프 값을 보면 수행할 때마다 새롭게 생성됨을 확인할 수 있다.
7.3.2. 디버거와 IAT 복구 도구를 이용한 수동 언패킹
이제 수동 언패킹을 시도해보자.
① 우선 언패킹 루틴의 수행이 끝나는 지점을 찾아본다. 패커가 PUSHAD 명령으로 레지스터 상태 값을 저장했다면, POPAD로 그 값을 복원하는 코드가 있을 것이다. 패킹된 PE 파일의 EP에 위치한 PUSHAD 명령을 실행하면 스택에 레지스터 정보가 저장된다. 나중에 POPAD 명령으로 이 값을 복원하려면 이 스택 메모리에 접근할 것이다. 그래서 PUSHAD 명령으로 저장된 스택 메모리를 나타내는 ESP 메모리에 하드웨어 중단점을 설정한다.
② [F9]를 눌러 실행하면 중단점을 설정한 메모리 주소에 접근하는 순간 디버거의 실행이 멈춘다. 이 결과 POPAD 명령을 확인할 수 있을 것이다. 다른 방법으로 명령어 찾기로 POPAD 명령을 찾아볼 수도 있다.
③ 이제 레지스터 상태를 복구하고, [F7], [F8], [F4]를 적절히 사용하여 OEP 영역으로 이동해본다. UPX 패킹의 경우 UPX0 섹션에 도달할 수 있다. 그리고 UPX0 섹션을 덤프 메모리로 확인하면 데이터가 모두 채워졌음을 확인할 수 있다.
④ 발견한 OEP 영역에 대한 메모리 덤프를 수행한다. 코드 덤프는 x64dbg의 Scylla 플러그인을 활용한다. Scylla 플러그인을 동작시키면 아래와 같은 창이 생성된다.
⑤ 이제 덤프하기 전에 IAT를 자동으로 찾아 채워준다. 그리고, 메모리 내용을 덤프한다. 이렇게 저장된 파일은 실행 파일이지만 주소 연결이 완료되지 않아서 실행하면 오류가 발생할 수 있다.
⑥ 마지막으로 PE 파일의 헤더를 재구성해야 한다. 대부분의 패킹 프로그램은 IAT 정보를 변경하거나, IAT에 쓸모 없는 값을 넣는 경우가 많다. 따라서 IAT 정보를 재구성해줄 필요가 있다. PE 재구성은 LordPE로 수행해본다. 수동 언패킹이 완료되면 실행하여 오류가 없는지 확인한다.
'Security Book Study > x64dbg를 활용한 리버싱과 시스템 해킹의 원리' 카테고리의 다른 글
Chapter 9. 메모리 오염 공격 (0) | 2025.03.20 |
---|---|
Chapter 8. 안티 리버싱 (0) | 2025.03.18 |
Chapter 6. 윈도우 실행 파일 구조 (0) | 2025.03.14 |
Chapter 5. 코드 분석과 패치 (0) | 2025.03.12 |
Chapter 4. 어셈블리어의 이해 (0) | 2025.03.09 |