[ PESpin이란? ]

- 순수 어셈블리어로 만들어진 Windows 파일 포맷(EXE, DLL) protector겸 compressor 
- http://pespin.w.interia.pl 에서 배포 
- 도메인 확장자가 pl인 것으로 보아 폴란드 개발자가 만든 것으로 보임
- code 영역 뿐만아니라, data, resource 영역까지 패킹 가능
- Windows XP/Vista/7에서 사용 가능
- 32/64bit 모두 지원
- 100% 무료 (기부 가능)

[ download ]

32비트
http://pespin.w.interia.pl/pespin132.rar

64비트
http://pespin.w.interia.pl/pespin_v12_x64.rar


[ 오늘의 예제 프로그램 ]

===========================================================================

#include 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MessageBox(HWND_DESKTOP, "Hello Win32", "", MB_OK);
	return 0;
}


===========================================================================

 
[ PESpin 인터페이스 (v1.32 기준) ]


- 실행 화면


- 인터페이스 요약
	[메뉴부]
	- File -> Open : Packing 할 파일을 Open합니다.
	- File -> Exit : 종료

	[탭부]
	- PE Spin 탭 : 
	- Settings : 각종 Packing 옵션을 선택합니다.

	[버튼부]
	- Protect File 버튼 : Packing을 시작합니다. 파일 선택이 먼저 되어있어야 합니다.
	- Open File 버튼 : Packing 할 파일을 Open합니다. 
	- Exit 버튼 : 종료 


[ 기본 Packing 테스트 ]

- 예제 프로그램 Open 후 Protect File 버튼 클릭




[ 결과 확인 ]

- 용량 비교

전 : 36846 Bytes
후 : 32768 Bytes


- PEID 결과 비교




- PEView 결과 비교




[ 매뉴얼 Unpacking 시작 (with 올리디버거) ]

- 파일 오픈

다음과 같은 경고 메시지가 뜹니다. (-> 확인)



이는 PE 헤더에 지정된 Entry Point가 역시 PE 헤더에 지정된 CODE 영역 범위에서 벗어나기 
때문임으로서, Packing 된 바이너리들의 특징이기도 합니다.



똑똑한 올리는 압축 된 파일이란 것도 알려줍니다. (-> 예) 




- 안티 디버깅 여부 확인

f9(run)를 눌러 예제 프로그램의 "Hello Win32" 메시지 박스가 뜨면 안티 디버깅은 없는 것

야호
(올리디버거의 설정에 따라 중간에 Access violation, division by zero, Privileged instruction 익셉션이 
뜰 수도 있고 안 뜰 수도 있습니다. 뜨면 shift+f9를 눌러 무시합니다.)




[ Manual Unpacking 첫 번째 방법 ]

패킹 된 파일을 열고 f8을 한 번 눌러 entry point에 위치하고 있는 jmp 명령을 실행합니다.



그럼 pushad 코드가 나오는데요,



이 pushad는 현재 레지스터들을 전부 스택에 백업하는 역할을 합니다.

pushad와 쌍을 이루는 명령이 popad인데, 이 때엔 백업한 레지스터들을 복구합니다.

많은 패커들이 자신의 코드를 실행하기 전에 pushad를 하는데요, 

이는 원래 프로그램의 흐름에 잘못된 영향을 미치지 않기 위해서입니다.

그래서 이처럼 먼저 pushad를 한 후, 패커의 역할이 끝나면 다시 popad를 합니다.

첫 번째 언패킹은 이 특징을 이용해서 해보겠습니다.

f8을 한 번 더 눌러서 pushad를 실행하고, 이 명령에 의해 스택에 쌓인 값들 중 하나에 

브레이크 포인트를 겁니다.

그럼 언패킹이 끝난 후 popad를 하는 시점에서 이 브레이크 포인트에 걸리게 되기 때문입니다.



주의
	- stack 윈도우에선 BP를 걸지 못하기 때문에 dump 윈도우에서 stack 주소와 동일한 주소로 이동 후 BP를 겁니다
		- dump 윈도우에서 ctrl + g 하셔도 되고, 
		- 현재 stack 주소는 ESP 레지스터와 같기 때문에 ESP에서 우측 버튼 누른 후 Follow in Dump 하면 됩니다.
		- stack 윈도우에서도 우측 버튼을 누르면 Follow in Dump 창이 나타나지만, 주소가 아닌 값으로 이동하므로 X

	- dump 윈도우에선 memroy BP 혹은 hardware BP 밖에 걸지 못합니다.
	- 그림과 같이 Hardware, on access를 걸어줍니다. size는 무엇이든 상관없습니다.
	- memory BP를 걸지 않는 이유는, memory BP 같은 경우엔 해당 메모리 영역에 통째로 BP를 걸어버리기 때문입니다.
	-  CPU 윈도우에서 해당 주소로 이동하면 software BP를 걸 수도 있는데, 그렇게 하지 않는 이유는 software BP는 
	   코드 실행(excution)에 대한 감지만 가능하기 때문입니다. 
		- 현재 우리는 해당 주소의 실행이 아닌, 데이터 참조(access)를 감지하려고 하는 것입니다.

이제 f9를 눌러 run 시킵니다.

만약 처음 했을 때와 마찬가지로 여러가지 exception이 발생하면, 역시 shift+f9를 눌러 무시해 줍니다.

그러다보면 어느 순간에 다음과 같이 NOT EDX 명령이 실행되기 직전에 hardware BP에 걸리게 됩니다.



그리고 이 시점에서 키보드 ↑ 키를 눌러보면, 역시 예상대로 POPAD 명령이 보입니다.
즉, POPAD 명령이 실행된 직후 hardware BP에 걸린 것입니다.



다른 언패킹 같은 경우엔 POPAD 후에 바로 Original Entry Point(패킹하기 전의 원래 Entry Point)로 갑니다.

하지만 PESpin은 무언가 다른 것들을 더 하고 있는데요, 코드를 잘 보면 대부분이 쓸데없는 짓들입니다.

====================================================================================
0040ACB4   61               POPAD
0040ACB5   F7D2             NOT EDX
0040ACB7   39C2             CMP EDX,EAX
0040ACB9   F7C0 74E7F921    TEST EAX,21F9E774
0040ACBF   0FACC2 48        SHRD EDX,EAX,48                      
0040ACC3   0FBDC8           BSR ECX,EAX
0040ACC6   C7C2 2431C7CD    MOV EDX,CDC73124
0040ACCC   85C0             TEST EAX,EAX
0040ACCE   0FBAEA 31        BTS EDX,31
0040ACD2   F7D2             NOT EDX
0040ACD4   F7C1 25C4A65C    TEST ECX,5CA6C425
0040ACDA   3BD0             CMP EDX,EAX
0040ACDC   0FABC2           BTS EDX,EAX


=====================================================================================

그리고 얘네를 쭈욱 따라가다보면..

이런 이상한 값들이 있는 곳으로 가게 됩니다. 



CPU 창에서 이런 이상한 값들이 나타나면 보통 올리디버거가 코드 분석에 실패했기 때문입니다.

이럴 땐 올리의 분석을 해제시켜 줍니다. (마우스 우측 -> analysis -> remove analysis from module)

그럼 제대로 된 코드가 나타납니다.



이제 이 코드를 잘 살펴보면..

WinMain()을 호출하는 StartUp Code임을 알 수 있습니다!



단, PESpin이 심볼 정보를 모두 삭제해버렸기 때문에 함수명이 아닌, 패턴을 보고 StartUp Code임을 알 수 있어야 합니다.
이는 StartUp Code를 여러번 경험하면 자연스럽게 익혀지기도하고, 혹은 StartUp Code가 아닐까? 하는 생각으로
진짜 StartUp Code와 비교를 해보시면 됩니다.

다음은 일반적인 Startup Code를 가져온 것입니다.

==================================================================================================================
00401020 >/$ 55                     PUSH EBP
00401021  |. 8BEC                   MOV EBP,ESP
00401023  |. 6A FF                  PUSH -1
00401025  |. 68 A8504000            PUSH TEST2.004050A8
0040102A  |. 68 EC1C4000            PUSH TEST2.00401CEC                                 ;  SE handler installation
0040102F  |. 64:A1 00000000         MOV EAX,DWORD PTR FS:[0]
00401035  |. 50                     PUSH EAX
00401036  |. 64:8925 00000000       MOV DWORD PTR FS:[0],ESP
0040103D  |. 83EC 58                SUB ESP,58
00401040  |. 53                     PUSH EBX
00401041  |. 56                     PUSH ESI
00401042  |. 57                     PUSH EDI
00401043  |. 8965 E8                MOV DWORD PTR SS:[EBP-18],ESP
00401046  |. FF15 14504000          CALL DWORD PTR DS:[<&KERNEL32.GetVersion>]          ;  kernel32.GetVersion
0040104C  |. 33D2                   XOR EDX,EDX
0040104E  |. 8AD4                   MOV DL,AH
00401050  |. 8915 E4844000          MOV DWORD PTR DS:[4084E4],EDX
00401056  |. 8BC8                   MOV ECX,EAX
00401058  |. 81E1 FF000000          AND ECX,0FF
0040105E  |. 890D E0844000          MOV DWORD PTR DS:[4084E0],ECX
00401064  |. C1E1 08                SHL ECX,8
00401067  |. 03CA                   ADD ECX,EDX
00401069  |. 890D DC844000          MOV DWORD PTR DS:[4084DC],ECX
0040106F  |. C1E8 10                SHR EAX,10
00401072  |. A3 D8844000            MOV DWORD PTR DS:[4084D8],EAX
00401077  |. 33F6                   XOR ESI,ESI
00401079  |. 56                     PUSH ESI
0040107A  |. E8 160B0000            CALL TEST2.00401B95
0040107F  |. 59                     POP ECX
00401080  |. 85C0                   TEST EAX,EAX
00401082  |. 75 08                  JNZ SHORT TEST2.0040108C
00401084  |. 6A 1C                  PUSH 1C
00401086  |. E8 B0000000            CALL TEST2.0040113B
0040108B  |. 59                     POP ECX
0040108C  |> 8975 FC                MOV DWORD PTR SS:[EBP-4],ESI
0040108F  |. E8 E1070000            CALL TEST2.00401875
00401094  |. FF15 10504000          CALL DWORD PTR DS:[<&KERNEL32.GetCommandLineA>]     ; [GetCommandLineA
0040109A  |. A3 D8894000            MOV DWORD PTR DS:[4089D8],EAX
0040109F  |. E8 9F060000            CALL TEST2.00401743
004010A4  |. A3 C0844000            MOV DWORD PTR DS:[4084C0],EAX
004010A9  |. E8 48040000            CALL TEST2.004014F6
004010AE  |. E8 8A030000            CALL TEST2.0040143D
004010B3  |. E8 A7000000            CALL TEST2.0040115F
004010B8  |. 8975 D0                MOV DWORD PTR SS:[EBP-30],ESI
004010BB  |. 8D45 A4                LEA EAX,DWORD PTR SS:[EBP-5C]
004010BE  |. 50                     PUSH EAX                                            ; /pStartupinfo
004010BF  |. FF15 0C504000          CALL DWORD PTR DS:[<&KERNEL32.GetStartupInfoA>]     ; \GetStartupInfoA
004010C5  |. E8 1B030000            CALL TEST2.004013E5
004010CA  |. 8945 9C                MOV DWORD PTR SS:[EBP-64],EAX
004010CD  |. F645 D0 01             TEST BYTE PTR SS:[EBP-30],1
004010D1  |. 74 06                  JE SHORT TEST2.004010D9
004010D3  |. 0FB745 D4              MOVZX EAX,WORD PTR SS:[EBP-2C]
004010D7  |. EB 03                  JMP SHORT TEST2.004010DC
004010D9  |> 6A 0A                  PUSH 0A
004010DB  |. 58                     POP EAX
004010DC  |> 50                     PUSH EAX
004010DD  |. FF75 9C                PUSH DWORD PTR SS:[EBP-64]
004010E0  |. 56                     PUSH ESI
004010E1  |. 56                     PUSH ESI                                            ; /pModule
004010E2  |. FF15 08504000          CALL DWORD PTR DS:[<&KERNEL32.GetModuleHandleA>]    ; \GetModuleHandleA
004010E8  |. 50                     PUSH EAX
004010E9  |. E8 12FFFFFF            CALL TEST2.00401000					; 여기가 WinMain()
004010EE  |. 8945 A0                MOV DWORD PTR SS:[EBP-60],EAX
004010F1  |. 50                     PUSH EAX
004010F2  |. E8 95000000            CALL TEST2.0040118C
004010F7  |. 8B45 EC                MOV EAX,DWORD PTR SS:[EBP-14]
004010FA  |. 8B08                   MOV ECX,DWORD PTR DS:[EAX]
004010FC  |. 8B09                   MOV ECX,DWORD PTR DS:[ECX]
004010FE  |. 894D 98                MOV DWORD PTR SS:[EBP-68],ECX
00401101  |. 50                     PUSH EAX
00401102  |. 51                     PUSH ECX
00401103  |. E8 59010000            CALL TEST2.00401261
00401108  |. 59                     POP ECX
00401109  |. 59                     POP ECX
0040110A  \. C3                     RETN
==============================================================================================================

보시면, WinMain()이 호출될 때의 패턴이 다음과 같음을 알 수 있습니다.

004010D9  |> 6A 0A                  PUSH 0A
004010DB  |. 58                     POP EAX
004010DC  |> 50                     PUSH EAX
004010DD  |. FF75 9C                PUSH DWORD PTR SS:[EBP-64]
004010E0  |. 56                     PUSH ESI
004010E1  |. 56                     PUSH ESI                                            ; /pModule
004010E2  |. FF15 08504000          CALL DWORD PTR DS:[<&KERNEL32.GetModuleHandleA>]    ; \GetModuleHandleA
004010E8  |. 50                     PUSH EAX
004010E9  |. E8 12FFFFFF            CALL TEST2.00401000                                 ; 여기가 WinMain()

이제 분석하던 올리디버거에서 동일한 패턴이 나올 때까지 f8을 눌러 진행해 나가다보면..

찾았습니다!



마지막으로 WinMain()에 해당하는 코드로 step into(F7) 해보면, jmp 코드가 한 번 나오고,



따라가보면, WinMain() 함수가 나타납니다.



이렇게 해서 첫 번째 방법을 이용한 Manual Unpacking을 마쳤습니다.

그런데..

PESpin은 POPAD 이후에 이상한 코드들을 왜 넣어놓은 걸까요?

일단 Startup Code가 발견된 시점에서부터 그 시작점을 찾아 커서를 위로 쭈욱 올려봅시다.

엇.. 있어야 할 Startup Code의 윗 부분이 0000으로 채워져 있습니다.



이 같은 괴현상은 Stolen Bytes라는 기법에 의해 나타나는데요, 

Stolen Bytes 기법이란, 원래 코드의 일부를 떼어내어 Packer가 몰래 따로 실행하는 것을 말합니다. 

그리고 원래 있어야 할 코드 부분은 0000과 같은 의미없는 값으로 채워넣습니다.

이런 이상한 행동을 하는 이유가 무엇일까요?

이는 Unpacking을 완료한 리버서가 메모리를 dump 뜨는 것을 방지하기 위함이니다.

dump를 뜬다고 해도 Startup Code의 앞부분이 사라진 상태이기 때문에 비정상 작동할 것이기 때문입니다.

그럼 사라진 Stolen Bytes들은 어디로 갔을까요?

바로 아까 잠깐 보았던 의미없는 이상한 코드들 사이사이에 몰래 숨겨져 있었습니다.

==========================================================================================================

0040ACB4   61               POPAD
0040ACB5   F7D2             NOT EDX
0040ACB7   39C2             CMP EDX,EAX
0040ACB9   F7C0 74E7F921    TEST EAX,21F9E774
0040ACBF   0FACC2 48        SHRD EDX,EAX,48                          ; Shift constant out of range 1..31
0040ACC3   0FBDC8           BSR ECX,EAX
0040ACC6   C7C2 2431C7CD    MOV EDX,CDC73124
0040ACCC   85C0             TEST EAX,EAX
0040ACCE   0FBAEA 31        BTS EDX,31
0040ACD2   F7D2             NOT EDX
0040ACD4   F7C1 25C4A65C    TEST ECX,5CA6C425
0040ACDA   3BD0             CMP EDX,EAX
0040ACDC   0FABC2           BTS EDX,EAX
0040ACDF   55               PUSH EBP			 	<- 여기!!
0040ACE0   EB 01            JMP SHORT hello_wi.0040ACE3
0040ACE2   AD               LODS DWORD PTR DS:[ESI]
0040ACE3   8BEC             MOV EBP,ESP			<- 여기!!
0040ACE5   EB 01            JMP SHORT hello_wi.0040ACE8
0040ACE7   A2 6AFFEB01      MOV BYTE PTR DS:[1EBFF6A],AL
0040ACEC   3F               AAS

===========================================================================================================

이처럼 Stolen Bytes 당한 바이너리를 dump뜨려면 0000으로 채워진 영역을 다시 수동으로 복구해주어야 합니다.
의미없는 코드들 내에서 원래 코드를 찾는 것은 오래걸리기 때문에, 정상적인 Startup Code 샘플을 하나 가져와 
복구해주는 방법을 사용합니다.