"Hello World!" - 내 생애 첫 디버깅 (2)
원하는 코드를 빨리 찾아내는 4가지 방법
자신이 원하는 코드를 빨리 찾아내기 위해서는 여러가지 자신만의 노하우가 있습니다.
여기서는 가장 기본이 되면서 가장 유용한 4가지 방법을 소개합니다.
- 코드 실행 방법
- 문자열 검색 방법
- API 검색 방법 (1) - 호출 코드에 BP
- API 검색 방법 (2) - API 코드에 직접 BP
0) 이미 아는 사실
4가지 방법을 소개하기 전에 먼저 한번 생각을 해봅시다.
우리는 HelloWorld.exe 프로그램이 "Hello World!" 메시지 박스를 출력한다는 것을 이미 알고 있습니다.
물론 우리가 코드를 만들었기 때문이지만, 이 경우에는 그냥 실행만 해봐도 누구나 알 수 있는 것입니다.
C 언어 개발자들이라면 MessageBox 계열 함수가 머릿속에 떠오를 것입니다.
이렇게 프로그램의 기능이 명확한 경우는 그냥 실행만 해봐도 내부 구조를 대략적으로 추측할 수 있습니다.
(물론 개발/분석 경험이 요구됩니다.)
1) 코드 실행 방법
우리가 원하는 코드는 main() 함수내의 MessageBox() 함수 호출 코드 입니다.
OllyDbg 디버거로 HelloWorld.exe 를 디버깅하면 어느 순간 자동으로 메시지 박스를 띄워 주는데요,
디버깅을 해나가다 보면 언젠가 main() 함수내의 MessageBox() 함수가 실행되어 "Hello World!" 메시지박스가 출력되겠지요?
이것이 코드 실행 방법의 원리입니다.
기능이 명확한 경우에 소스 코드를 실행해 가면서 찾아가는 것입니다.
코드 크기가 작고 기능이 명확한 경우에 사용할 수 있습니다.
코드 크기가 크고 복잡한 경우에는 적절하지 않습니다.
OllyDbg 와 콘솔 윈도우를 적절한 크기로 조정하여 동시에 살펴 볼 수 있도록 하세요.
베이스 캠프(40104F)에서부터 명령어를 한줄한줄 실행[F8]해 봅니다.
어느 순간 "Hello World!" 메시지 박스가 출력 되어 있을 것입니다.
몇 번 반복해 보시면 특정 함수를 호출 한 이후에 메시지 박스가 나타나는 것을 파악할 수 있습니다.
바로 그 함수가 main() 함수입니다.
그곳이 바로 우리가 찾는 main() 함수 코드 영역입니다.
"Hello World!" 문자열과 MessageBoxW() 함수 호출 코드가 보이시죠?
정확히 찾아왔습니다.
* VC++ 2008 Express Edition 을 사용하면 기본 문자열은 UNICODE 가 되고,
문자열 처리 API 함수들도 전부 W(ide) character 계열의 함수로 변경됩니다.
2) 문자열 검색 방법
All referenced text strings : 마우스 우측 메뉴 -> Search for -> All referenced text strings
C 언어를 처음 배울때 문자열은 코드와 다른 영역에 저장된다라고 배웠습니다.
즉, 어딘가에 "Hello World!" 문자열이 저장되어 있을꺼란 얘기입니다.
프로그램내의 문자열을 확인할 수 있는 여러가지 방법이 있습니다만 여기서는 OllyDbg 기능을 설명드리겠습니다.
OllyDbg 가 디버깅할 프로그램을 로딩할 때 나름대로 분석과정을 거치게 되는데요,
코드를 좍~ 훑어서 참조되는 문자열과 호출되는 API 들을 뽑아내서 따로 목록으로 정리를 해놓습니다.
'All referenced text strings' 명령을 사용하면 아래와 같은 윈도우가 뜨면서 코드에서 참조되는 문자열들을 보여줍니다.
OllyDbg 는 "401007 주소의 PUSH 004092A4 명령이 있는데, 이 명령에서 참조되는 4092A4 주소에는 'Hello World!' 문자열이 존재합니다." 라고 말하고 있는 것이죠.
문자열을 더블 클릭하면 main() 함수의 MessageBoxW() 호출 코드로 갈 수 있습니다.
참고로 메모리상에 있는 문자열의 확인을 위하여 OllyDbg 덤프 윈도우에서 Go to[Ctrl+G] 명령을 써보겠습니다.
(포커스를 덤프 윈도우에 놓고 단축키 명령 [Ctrl+G] 을 내려주세요.)
"Hello World!\n" 문자열과 그 뒤의 NULL 들이 보이시죠?
(VC++ 2008 에서는 static 문자열을 UNICODE 로 저장한다고 아까 설명하였습니다.)
"Hello World!" 문자열 대신 "Reversing!" 문자열을 쓸 수 있는 공간이 충분히 존재하는군요.
(우리의 목표를 기억하시죠? 문자열을 패치시킬 것입니다.)
또 한가지 중요한 내용은 4092A4 라는 주소입니다.
지금까지 본 코드의 주소 401XXX 와는 다른 영역입니다.
HelloWorld.exe 프로세스에서 409XXX 주소는 프로그램에서 사용되는 데이타가 저장되는 영역입니다.
코드와 데이타가 파일에서 어떻게 저장되고 메모리에 어떻게 올라가는지
원리를 자세히 배우려면 PE header 를 공부해야 합니다.
(PE header 는 처음에 설명할 내용이 너무 많아서 나중에 따로 정리하여 올리도록 하겠습니다.)
3) API 검색 방법 (1) - 호출 코드에 BP
All intermodular calls : 마우스 우측 메뉴 -> Search for -> All intermodular calls
Windows 프로그래밍에서 모니터 화면(hardware)에 뭔가를 출력하려면
어쩔 수 없이 Win32 API 를 사용하여 OS 에게 화면출력을 요청해야 합니다.
즉, 프로그램이 화면에 뭔가를 출력했다는 얘기는 프로그램 내부에서 Win32 API 를 사용하였다는 뜻입니다.
그렇다면 프로그램의 기능을 보고 사용되었을법한 Win32 API 호출을 예상하고,
그 부분을 찾을 수 있다면 디버깅이 매우 간편해 질 것입니다.
OllyDbg 에는 디버깅 시작전에 미리 코드를 분석하여 사용되는 API 함수 목록을 뽑아내는 기능이 있습니다.
코드에서 사용된 API 호출 목록만 보고 싶을때는 'All intermodular calls' 명령을 사용하면 됩니다.
아래와 같이 프로그램에서 사용되는 API 함수 호출 목록이 나타납니다.
(OllyDbg 옵션에 따라서 표시되는 모양이 약간 틀려질 수 있습니다.)
<Fig. 11> 에 MessageBoxW 호출 코드가 보이시죠?
역시 더블클릭으로 해당 주소(40100E) 로 갈 수 있습니다.
이런 식으로 코드에서 사용된 API 를 예상할 수 있을때 이 방법을 사용하면 쉽게 원하는 부분을 찾아낼 수 있습니다.
* OllyDbg 가 어떻게 호출되는 API의 이름을 정확히 뽑아올 수 있을까요?
소스코드를 보고 있는것도 아닌데요.
이 원리를 이해하기 위해서는 역시 PE header 의 IAT(Import Address Table) 구조를 이해해야 합니다.
(나중에 따로 설명 하겠습니다.)
4) API 검색 방법 (2) - API 코드에 직접 BP
Name in all modules : 마우스 우측 메뉴 -> Search for -> Name in all modules
모든 실행 파일에 대해서 OllyDbg 가 API 함수 호출 목록을 추출할 수 있는것은 아닙니다.
Packer/Protector 를 사용하여 실행파일을 압축 또는 보호해 버리면,
IAT 구조가 변경되거나 OllyDbg 에서 보이지 않게 됩니다. (심지어는 디버깅 자체가 매우 어려워 집니다.)
실행압축 유틸리티. 실행파일의 코드, 데이타, 리소스 등을 압축시켜 버립니다.
일반 압축 파일과 다른 점은 실행 압축된 파일 그 자체도 실행파일 이라는 것입니다.
(나중에 대표적인 packer 를 분석해 보도록 하겠습니다.)
* Protector
실행압축 기능외에 파일과 그 프로세스를 보호하려는 목적으로
anti-debugging, anti-emulating, anti-dump 등의 기능을 추가한 유틸리티 입니다.
Protector 를 상세 분석하려면 높은 분석 지식이 요구됩니다.
(굉장히 고급 주제이고 너무 재밌는 내용입니다. 나중에 상세히 분석 해보겠습니다.)
이런 경우에는 프로세스 메모리에 로딩된 DLL 코드에 직접 BP 를 걸어 보는 겁니다.
API 라는 것은 OS 에서 제공한 함수이고, 실제로 API 코드는 %system32% 폴더에 *.dll 파일 내부에 구현되어 있습니다.
(kernel32.dll, user32.dll, gdi32.dll, advapi32.dll, ws2_32.dll 등입니다.)
간단히 말해서 우리가 만든 프로그램이 어떤 의미 있는 일(각종 I/O)을 하려면
반드시 OS 에서 제공된 API 를 사용해서 OS 에게 요청해야 하고,
그 API 가 실제 구현된 시스템 DLL 파일들은 우리 프로그램의 프로세스 메모리에 로딩(정확히는 매핑)되어야 합니다.
OllyDbg 에서 확인해 볼까요. View – Memory 메뉴를 선택해 주세요. (단축키 [Alt+M])
<Fig. 12>
<Fig. 12> 는 HelloWorld.exe 프로세스 메모리의 일부분을 보여주고 있습니다.
빨간색으로 표시된 부분이 바로 시스템 DLL 들이 로딩된 메모리 영역입니다.
* 참고로 MessageBoxW() API 는 USER32.DLL 에 속해 있습니다.
OllyDbg 의 또 다른 기본 해석 기능은 프로세스 실행을 위해서
같이 로딩된 시스템 DLL 파일이 제공하는 모든 API 목록을 보여주는 것입니다.
'Name in all modules' 명령을 사용해 보겠습니다.
나타나는 윈도우에서 'Name' 정렬시키고, MessageBoxW 를 타이핑 하면 자동 검색됩니다.
USER32 모듈에서 Export type 의 MessageBoxW 함수를 선택하세요.
(시스템 환경에 따라 버전이 틀려질 수 있습니다.)
더블 클릭 하시면 아래와 같이 USER32.dll 에 구현된 실제 MessageBoxW 함수가 나타납니다.
주소를 보시면 HelloWorld.exe 에서 사용되는 주소와 확연히 틀리다는걸 아실 수 있습니다.
이곳에 BP 를 설치[F2]하고 실행[F9]해 보겠습니다.
만약 HelloWorld.exe 프로그램에서 MessageBoxW 함수를 호출한다면 결국 이곳에서 실행이 멈추게 될 것입니다.
(간단한 원리 입니다.)
예상대로 MessageBoxW 코드 시작에 설치한 BP 에서 실행이 멈췄습니다.
레지스터(Register) 윈도우의 ESP 값이 12FF68 인데, 이것은 프로세스 스택(Stack)의 주소입니다.
스택 윈도우에서 빨간색으로 표시된 부분을 아래에 자세히 표시했습니다.
Stack
address Value Comment
-----------------------------------------------------------------------
0012FF68 00401014 CALL to MessageBoxW from HelloWor.0040100E
=> MessageBoxW 는 40100E주소에서 호출되었으며,
함수 실행이 종료되면 복귀주소는 401014 이다.
0012FF6C 00000000 hOwner = NULL
0012FF70 004092A4 Text = "Hello World!"
0012FF74 0040927C Title = "www.reversecore.com"
0012FF78 00000000 Style = MB_OK|MB_APPLMODAL
* 함수 호출과 스택의 동작 원리등은 나중에 "Stack Frame" 설명할 때 더 자세히 보도록 하겠습니다.
ESP 의 값 12FF68 에 있는 복귀 주소 401014 는
HelloWorld.exe 의 main 함수내의 MessageBoxW 함수 호출 바로 다음의 코드입니다.
간단히 MessageBoxW 함수의 RETN 명령까지 실행[Ctrl+F9]한 다음,
RETN 명령도 실행[F7]하면 복귀주소 401014 로 갈 수 있습니다.
바로 위에 MessageBoxW 함수 호출 코드가 있는 것을 확인할 수 있습니다. (<Fig. 8> 참고)
(continue)