본문 바로가기

Rerversing/Exercise reversing

[Reversing.kr] - Easy UnpackMe 문제 풀이

세 번째 문제 풀이를 시작해보자.

제목으로 봐서는 packing 이 된 파일을 unpack 시켜야 하는 것 같은데, 같이 첨부된 ReadMe.txt 파일을 열어보면은...


ReversingKr UnpackMe


Find the OEP

ex) 00401000


이렇게 나온다. 즉, 언팩을 한 다음에 Original Entry Point의 주소를 찾아내라는 소리이다.




[그림. 1] Entry Point


언팩이 된 파일은 일반 PE 구조와는 좀 다르다.

PEViewer 로 확인해보면 Address of Entry Point 가 0x0000A04B라는 것을 알 수 있다.

(이 파일도 ImageBase가 0x00400000 부터 시작일테니, 로드가 된다면 0x0040A04B 주소가 Entry Point로 나온다는 얘기..)


근데 일반적인 파일이라면 Entry Point는 .text (코드 섹션)에 있어야 할텐데, 저 주소는 .GWan 이라는 이상한 이름의 섹션에 포함되는 주소이다.

보통 Packing이 된 파일들은 이렇 듯 언팩킹 전에는 죄다 EP 저따구로 나온다.






[그림. 2] PEiD로 확인 


PEiD 툴로 확인해보니 역시 Entry Point 와 EP Section이 엄한데로 나온다. 또, 뭘로 만들어졌는지 알 수가 없다고 나온다.






[그림. 3] Compressed code ??


디버거로 로딩하여 Entry Point로 가려고 하면 저런 메시지가 나온다. 말 그대로 코드 섹션이 압축 되었거나, 암호화 되었거나 해서 거시기 하다는 내용이다. 계속 분석하겠냐고 하는데 "예"를 눌러서 분석을 시작해보자.






[그림. 4] EP 코드 시작 부분


EP 영역의 코드 시작 부분이다. 여기를 어떻게 접근했냐고 하는 분들은 없길 바란다. 윈xp라면 그냥 바로 나올 것이고, 비스타 이후 계열 OS라면은 ntdll.dll 영역서 부터 디버깅이 시작되므로 Ctrl + F9 눌러서 가볍게 넘어와주면 된다.

코드를 보면 kernel32.dll을 의 주소를 얻기 위해서 LoadLibrayA 함수를 CALL 하는 게 보인다.

함수는 EAX에 kernel32.dll의 주소를 셋팅해주는데, 그 EAX 값이 40A644 주소에 넣어지는 것이 3번째 줄에 보인다.

40A644 값은 그 아래에 PUSH 되는 아스키 값인 GetModuleHandleA 와 FreeLibrary 와 함께 PUSH 되고 각각

GetProcAddress 함수가 호출되는 것이 보일 것이다.

그나저나 이유를 잘 모르겠지만 GetProcAddress 함수를 바로 호출하지 않고 apphelp.dll 쪽을 타고 들어가서 간접 호출을 한다. 왜 저녀석을 타고 들어가서 하는지 모르지만 뭐 아무튼 GetProcAddress 함수 호출인 건 변함이 없다.

여기서 알 수 있는 건?? GetModuleHandleA 함수와 FreeLibrary의 함수 주소를 알아와서 각각 40A648, 40A64C에 주소를 넣는다는 것이다. 저 함수들을 사용하기 위해 함수 주소를 예제 파일 자신의 영역에 넣는 것이다.


여기서 알아둘 Tip이라면... 보통 이런 패킹 파일들은 코드가 얽히고 섥혀 있다고 해도 파일의 온전한 동작을 위해서는 자체적으로 언패킹 과정을 거치며 필요한 함수들의 정보를 가져와야 하고, Encrypting 되어있는 코드들을 풀어서 어딘가에 넣어야 하기 때문에 꼭 특정 메모리 공간 등을 사용해야 한다는 것이 거의 기본적인 동작이 된다.

물론 이렇게 디버깅 되는 것을 막기 위해 중간 중간 안티 디버깅 기법이 걸리는 애들도 많겠지만, 이건 Ea~~~sy  한 샘플임을 잊지 말자. -_-






[그림. 5] VirtualProtect 함수 사용


좀 더 트레이싱을 해보면 VirtualProtect 함수의 주소를 알아낸 뒤에 그 함수를 바로 사용한다.

총 4개의 인자값을 PUSH 하고 함수를 호출한다.

대강 해석하면 0x00405000 의 주소에서 0x1000 크기만큼 Read/Write 속성으로 변경하겠다는 것이다. 현재 해당 영역은 읽기 전용으로 되어 있는데, 그 부분을 쓰기도 가능하도록 바꾸겠다는 얘기이다.

왜 쓰기를 할까? 당연히 뭔가를 쓰려고 저 영역의 설정을 바꾼 것이겠지...

이렇 듯 패킹 파일은 거의 대다수가 VirtualProtect 를 사용한다. 왜냐~~ 용량이 큰 영역이 필요하거나 할 땐 VirtualAlloc과 VirtualProtect  함수 등이 자주 쓰인다고 MS 쪽에서 그런 걸로 기억하기 때문에.. ㅡㅡ;






[그림. 6] 함수 주소 구하기


위에서 설명한 것들을 종합해보면 필요한 dll 주소도 구하고, 그 dll 내부에서 사용할 함수의 주소를 GetProcAddress 로 알아온 다음에 쓰기 가능한 영역에 쓰는 행위를 지속적으로 하게 될 것이다. 그렇기 때문에 영역에 대한 속성 변경이 필요하기 때문에 보통 VirtualProtect 함수가 자주 쓰이게 되며, 뭐 새로운 영역 생성이 필요할 때는 VirtualAlloc 함수도 같이 쓰이기도 한다.

GetProcAddress 쓰려면 함수 이름이 아스키 코드로 존재할텐데, 패커의 경우 보통 모두 Encrypting이 되어 있기 때문에 그것을 복호화 하기 위한 연산 작업이 수행이 된다.

그렇다면 아마 연산과 반복이 몰리는 코드 구간이 존재하겠지!? 대략적으로 보자면 [그림. 6] 처럼 뭔가 ADD 하고 CMP 하고 INC 하고 점프하고 그 사이에 LoadLibrayA도 하고, GetProcAddress 도 하고 하는 걸 보니 뺑뺑이 돌면서 뭔가 작업을 한다는 것을 알 수 있는데, 저런 연산 구간이 VirtualProtect 함수가 쓰이는 코드들의 전후로 몇 개 존재한다.

그 작업들이 코드의 복호화 작업 되시겠다.






[그림. 7] 함수 정리 후의 분기문


모든 루프문을 완료시키면 JMP 코드 2개를 보게 되는데, 위의 JMP는 아직 작업이 덜 끝났기 때문에 다시 루프를 돌리려는 JMP문이고, 작업이 다 완료가 되면 루프문이 돌다가 40A1FB 주소로 점프를 시켜버린다.

자.. 해당 주소의 JMP 코드가 가리키는 00401150 주소로 들어가보면...






[그림. 8] OEP 코드 영역??


이상하게도 어셈코드가 해석이 안 되어 있고 그냥 널부러져서 나온다.

당황하지 말고 Ctrl + A 를 눌러서 재해석을 시켜보자.






[그림. 9] Original Entry Point


드디어 OEP 코드가 등장했다.

해당 OEP 코드 화면은 1, 2번 예제 파일들과 똑같지 않은가?

매번 이런 식으로 패킹 파일을 풀다가는 실제 샘플 분석 시에 아주 많은 개고생을 할 것이다.

즉, "패킹 파일들에서 자주 사용되는 부분들을 중점으로 파팍~ 넘어가줘야 더욱 빠르게 OEP로 들어가서 메인 함수를 분석할 수 있을 터... "


라는 식으로 지인을 통해서 들었던 과거의 조언이 생각이 났다. -_-; 그 뒤로 VirtualProtect 함수에 BreakPoint 찍고 달리는 습관이 들었다는.. ㅎ






[그림. 10] PEiD 재확인


디버거를 이용해서 OEP 주소에 도달했다면 Dump 플러그인을 이용하여 Entry Point 주소를 fix 해주고 나서 메모리 영역을 덤프 떠주고 덤프 파일을 PEiD로 다시 확인한 결과 이제서야 제대로 표기가 된다.