반응형

어플리케이션 후킹(Application Hooking) 설계(디자인)에 대한 설명입니다. 후킹 방법을 결정하고, 검증하는 과정을 보여드립니다. 



후킹 방법 결정 - 무엇을? 어떻게? 후킹 할 것인가?


우리의 목표는 [HxD.exe 프로그램에서 PE 파일을 열었을 때 상태바의 "Offset: XXXX" 문자열에 "RVA: YYYY" 문자열을 추가하기]  입니다.


기존 어플리케이션에 어떤 기능을 추가(혹은 수정) 하려고 마음을 먹었다면 그 다음에는 구체적인 방법에 대해 고민을 해야 합니다. 즉, 후킹 설계 과정으로 들어가야 합니다.


윈도우즈 OS의 대표적인 후킹 기법은 "메시지 후킹(Message Hooking)""API 후킹(API Hooking)" 입니다. 둘 중에서 목표 달성에 더 적합하고 구현이 편리한 방법을 사용하는 것이 좋을 것입니다.


일반적으로는 메시지 후킹 기법이 더 간단하고 더 안전하다고 볼 수 있습니다. 대신 경우에 따라(특히 GUI 관련) 더 많은 고려사항이 필요한 경우가 있습니다. 따라서 실전에서는 후킹 설계 단계에서 메시지 후킹 방법이 더 편리할지 확인해 보는 것이 좋습니다.



메시지 후킹 기법 검증


우리 목표에 메시지 후킹 기법이 잘 어울릴지 검증해 보도록 하겠습니다. 


Windows OS 는 어플리케이션 윈도우의 GUI 처리 작업을 위해 윈도우 메시지를 이용합니다. HxD.exe 의 상태바도 일종의 윈도우입니다. 프로그램 내부에서 상태바에 특정 문자열을 쓰기 위해 관련된 윈도우 메시지를 전달할 것입니다. 


* 참고 


GUI 관련 API 를 호출하면 GUI 작업 처리를 할 수 있기 때문에 마치 윈도우 메시지를 사용하지 않고도 작업이 가능한 것처럼 생각될 수도 있습니다. 그러나 실제로는 API 내부에서 윈도우 메시지를 보내는 것입니다. 일반적인 GUI 작업은 메시지 기반으로 동작한다는 것을 기억하시기 바랍니다.


HxD.exe 의 상태바에 있는 "Offset: XXXX" 문자열이 변경될 때 상태바 윈도우 프로시저(Window Procedure)에서 처리하는 윈도우 메시지들을 확인해 보겠습니다. 먼저 HxD.exe 프로그램을 실행 하신 후 적당한 PE 파일을 열어 주세요.


그리고 윈도우 메시지를 모니터링 하기 위해 Spy++ 유틸리티를 실행합니다.



<그림 3 - Spy++ 실행화면>


* 참고


윈도우 메시지 확인에 있어서 Spy++ 은 최고의 유틸리티입니다. 기본 동작 원리는 윈도우 메시지 후킹입니다. 모니터링 대상 프로세스에 DLL 을 인젝션 시켜서 사용자가 원하는 메시지를 모니터링 합니다. Spy++ 은 Microsoft Visual Studio 패키지에 포함된 유틸리티 입니다. 


Spy++의 툴바에서 "Log Messages... (Ctrl+M)" 버튼을 선택합니다. (또는 "Spy - Log Messages..." 메뉴를 선택하셔도 됩니다.)



<그림 4 - Log Messages... (Ctrl+M) 툴바 버튼>


Message Options 다이알로그가 나타납니다.



<그림 5 - Message Options 다이알로그>


Windows 탭의 Finder Tool 을 이용할 것입니다. Finder Tool 아이콘을 마우스 버튼을 누른 채로 드래그 하면 마우스 포인터가 과녁 모양으로 바뀝니다. 이 과녁 모양의 마우스 포인터를 HxD 의 상태바 윈도우에 위치시킨 후 마우스 버튼에서 손을 떼면 Spy++ 의 타겟(Target) 윈도우로 설정됩니다. (과녁 모양의 마우스 포인터 밑에 있는 윈도우는 두꺼운 검은색 테두리가 생겨서 잘 알아 볼 수 있습니다.)



<그림 6 - Finder Tool 로 상태바 설정>


이제 Spy++ 의 Message Options 다이알로그의 "Selected Object" 섹션에 지금 선택된 HxD 의 상태바 윈도우에 대한 정보들이 표시됩니다.



<그림 7 - HxD 의 상태바 윈도우 정보>


각 정보들이 나타내는 의미는 <표 1>과 같습니다.


 항목

 의미

 

 Window

 윈도우 핸들

 001B0362

 Text

 윈도우 텍스트

 Offset: 0

 Class

 윈도우 클래스 이름

 TXmStatusBar

 Style

 윈도우 스타일

 54000100

 Rect

 윈도우 위치와 크기

 (15, 663)-(589, 684) 574x21

 Thread ID

 윈도우를 생성한 스레드 ID

 00000150

 Process ID

 윈도우가 속한 프로세스 ID

 00000F30


<표 1 - Selected Object 항목들의 의미>


* 참고


Spy++ 에서 보여주는 상태바 윈도우의 정보는 나중에 메시지 후킹을 구현할 때 좋은 참고자료가 됩니다.


윈도우 클래스 이름이 "TXmStatusBar" 인 걸로 봐서 윈도우 기본 상태바(StatusBar)를 서브클래싱(SubClassing) 한 것으로 추정됩니다. 그렇다면 기본 동작은 윈도우 기본 상태바와 비슷할 것이라고 예상해 봅니다.


Message Options 다이알로그의 [확인] 버튼을 선택하면 이제부터 Spy++ 은 HxD 의 상태바 메시지를 모니터링하기 시작 합니다. 시험 삼아서 마우스 포인터를 상태바 위에서 이리저리 이동해 보시기 바랍니다. Spy++ 화면에 마우스 관련 메시지가 많이 나타날 것입니다.



<그림 8 - 마우스 이동 메시지>


Spy++ 의 메시지 캡쳐 옵션을 디폴트인 'ALL' 로 설정하였기 때문에 상태바로 전달되는 모든 메시지가 표시되는 것입니다. 우리가 진짜 궁금한 내용은 상태바에 문자열이 써지는 순간에 어떤 메시지를 받느냐 하는 것입니다. 


Spy++ 툴바의 'Clear Log (Del)' 버튼을 선택하여 지금까지 쌓인 로그를 지워버립니다. 그리고 HxD.exe 의 메인 화면 내의 아무 부분을 마우스로 클릭하시기 바랍니다. 마우스가 선택한 파일의 옵셋을 상태바에 출력하기 위해 메시지가 전달될 것입니다.



<그림 9 - 문자열 출력 메시지>


HxD 메인 윈도우에서 마우스 클릭을 한번 하면 정확히 9개의 메시지 로그가 출력됩니다. (제가 보기 편하게 들여쓰기를 하였습니다.)


<00001> 001B0362 S message:0x040A [User-defined:WM_USER+10] wParam:00000000 lParam:0012FA84

<00002> 001B0362 R message:0x040A [User-defined:WM_USER+10] lResult:00000001


<00003> 001B0362 P WM_PAINT hdc:00000000


<00004> 001B0362 S message:0x0401 [User-defined:WM_USER+1] wParam:00000000 lParam:01A06768

<00005> 001B0362 S   WM_PAINT hdc:00000000

<00006> 001B0362 S     WM_ERASEBKGND hdc:33010BC2

<00007> 001B0362 R     WM_ERASEBKGND fErased:True

<00008> 001B0362 R   WM_PAINT

<00009> 001B0362 R message:0x0401 [User-defined:WM_USER+1] lResult:00000001


1번 로그의 메시지는 WM_USER(0x400)+A 이고 그 때의 lParam 값은 0012FA84 입니다. lParam 값이 마치 스택의 주소를 가리키는 것으로 추정됩니다. 그리고 4번 로그의 메시지는 WM_USER(0x400)+1 이고 lParam 값은 01A06768 입니다. 이 값 또한 메모리 주소를 표시한다고 추정해 볼 수 있겠습니다. 


마우스(또는 키보드)를 이용하여 HxD 메인 윈도우의 커서를 다른 옵셋으로 이동시키면 기본적으로 이와 동일한 9 개의 메시지 로그가 기록됩니다. 그리고 4 번째 로그의 lParam 값은 계속 바뀌는 것을 알 수 있습니다. (아마 동적 할당 메모리로 추정됩니다.)


따라서 상태바 윈도우는 사용자 정의 메시지인 WM_USER+1 또는 WM_USER+A 메시지를 받아서 lParam 이 가리키는 주소의 문자열을 상태바 윈도우에 쓴다고 추정해 볼 수 있겠습니다. 그 중에서도 4번 로그 메시지 WM_USER+1 을 받은 직후 WM_PAINT 와 WM_ERASEBKGND 메시지가 연속해서 나타나는 걸로 봐서는 WM_USER+1 메시지가 상태바 윈도우에 실제 값을 쓰라는 명령으로 보입니다.


디버거를 이용하여 lParam 이 가리키는 주소를 확인해보면 앞에서 추론한 내용이 맞는지 알 수 있겠지요.


* 참고


WM_PAINT 는 윈도우를 다시 그릴 때 발생되고, WM_ERASEBKGND 는 윈도우 배경을 지울 때 발생됩니다. WM_USER (0x400) 이후부터는 사용자 정의 메시지로서 프로그래머가 마음대로 지정해서 사용할 수 있습니다. 물론 메시지를 받는 윈도우 프로시저에서 사용자 정의 메시지를 잘 처리할 수 있도록 구현해야 합니다. 해당 메시지에 대한 더 자세한 설명은 MSDN 을 참고하시기 바랍니다.



디버깅


OllyDbg 를 실행하여 HxD.exe 프로세스에 Attach 시킵니다. 디버거는 ntdll.dll 메모리 영역의 System Break Point 위치에서 멈춥니다. 디버거를 실행[F9] 시켜 줍니다. 


윈도우 메시지를 디버깅 할 때는 주의사항이 있습니다. 그건 바로 디버기 프로세스(HxD.exe)의 윈도우를 가리면 안된다는 것입니다. 즉 OllyDbg 나 Spy++ 윈도우에 의해 HxD 윈도우가 가려졌다가 나타났다가 하게 되면 불필요한 로그가 Spy++ 에 쌓이게 됩니다. (물론 Spy++ 옵션을 고쳐서 모니터링 메시지를 필요한 것 만으로 제한해도 됩니다.) 


작업 편의상 각 윈도우들을 독립적으로 배치 시켜 주시기 바랍니다.



<그림 10 - 윈도우 배치>


다시 Spy++ 의 로그를 깨끗이 지우신 다음 마우스로 HxD 메인 윈도우 아무 곳이나 선택해 주세요. 제 경우에 Spy++ 의 WM_USER+1 로그의 lParam 주소는 01A0BA28 입니다. 



<그림 11 - 새로운 lParam 값>


이 주소를 OllyDbg 의 메모리 윈도우에서 검색해 보겠습니다.



<그림 12 - OllyDbg 에서 확인한 lParam 내용>


HxD 의 상태바에 나타난 것과 동일한 문자열이 ASCII 형태로 표시되어 있습니다. (이 경우에는 운좋게 예상과 잘 맞아서 쉽게 발견할 수 있었습니다.) 이 WM_USER+1 메시지를 후킹해서 lParam 이 가리키는 문자열("Offset: XXXX")을 읽은 후 RAW -> RVA 변환하여 표시해 주면 목표를 완수 할 수 있을 것 같습니다.


결론적으로 메시지 후킹 방법은 우리 목표에 잘 들어맞는 방법입니다. 다음 포스트에서 실제 메시지 후킹을 구현해 보도록 하겠습니다.


* 참고


사실 제가 처음 떠올렸던 아이디어는 단순한 마우스 메시지 후킹이었습니다. HxD.exe 상태바의 문자열이 바뀌려면 먼저 마우스로 커서 위치를 바꿔줘야 했으니 마우스 훅 프로시저에서 상태바의 문자열을 읽고 쓸 수 있지 않을까 하고 말이죠. 그러나 조금 더 생각해보니 좋은 아이디어가 아니라서 접었습니다. 이유는 키보드로도 커서 위치를 바꿀 수 있으니 키보드 메시지도 후킹 해야 할 테고, 상태바 윈도우가 다시 그려져야 하는 모든 상황(윈도우 이동, 윈도우가 가려졌다가 다시 나타나기, HxD 에서 다른 파일 열기, 기타)을 고려했을 때 후킹해야 할 메시지가 많아 질 것 같았기 때문입니다. 

따라서 덮어놓고 후킹을 시도하기보다는 먼저 차분히 생각을 정리해보고, 아이디어를 검증하는 단계가 매우 중요하다고 볼 수 있겠습니다.




ReverseCore

반응형
반응형

HelloReversing.exex




문자열 패치

목표 달성이 눈앞에 다가왔습니다.

MessageBoxW 호출하는 부분을 찾았으니 이제 "Hello World!" 문자열을 "Hello Reversing!" 으로 패치시킬 차례입니다.

디버깅을 재실행[Ctrl+F2] 시키고, main 함수 시작 부분까지 실행합니다.
(401000 에 BP 를 설정[F2]하고 실행[F9] 하세요. - 이 주소를 2 번째 베이스 캠프라고 하겠습니다.)




문자열을 패치하는 2 가지 방법

가장 쉬운 2 가지 방법을 소개합니다.

  • 문자열 버퍼를 직접 수정
  • 다른 메모리 영역에 새로운 문자열을 생성하여 전달



1) 문자열 버퍼를 직접 수정

MessageBoxW 함수의 전달인자 4092A4 의 메모리 주소 내용("Hello World!\)을 직접 수정해 버리는 것입니다.

메모리 윈도우에서 4092A4 로 갑니다 [Ctrl+G].
그리고 주소를 마우스로 선택한 후 [Ctrl+E] 단축키로 에디트 윈도우를 띄웁니다.


<Fig. 16>


'UNICODE' 항목에 "Hello Reversing!" 을 입력합니다.

변경된 코드는 아래와 같습니다.


<Fig. 17>


명령어는 그대로
이지만 MessageBoxW 함수에 전달되는 파라미터의 내용 자체가 변경되었습니다.
(파라미터의 주소도 그대로 입니다. 주소의 내용이 변경된 것입니다.)

이처럼 문자열 버퍼 내용을 직접 수정하는 방법은 사용하기에 가장 간단한 방법입니다.

* 하지만 기존 문자열 버퍼 크기를 잘 고려해서 수정해야만 프로그램이 에러 없이 잘 동작할 수 있습니다.
  즉, 기존 문자열 버퍼 크기 이상의 문자를 입력할 수 없다는 제약 조건이 있습니다.

변경된 내용을 영구히 보존하려면 파일로 만들어야 합니다.
<Fig. 16> 의 dump 윈도우에서 변경된 내용 ("Hello Reversing!" 문자열)을 선택하여
마우스 우측 버튼의 Copy to executable file 메뉴를 선택하면 아래와 같이 hexa 윈도우가 나타납니다.


<Fig. 18>


다시 마우스 우측 버튼의 Save file 메뉴를 선택하고 파일 이름을 HelloReversing.exe 로 해줍니다.

실행해보면 문자열이 성공적으로 변경되었습니다!


<Fig. 19>




2) 다른 메모리 영역에 새로운 문자열을 생성하여 전달

1) 방법은 MessageBoxW 함수의 파라미터인 문자열 버퍼의 내용을 직접 수정하는 방법이었습니다.

간단하지만 그만큼 단점도 존재합니다.
가령 훨씬 긴 문자열로 수정하고 싶을때 해당 버퍼 크기가 작다면
인접한 다른 메모리 영역을 침범하는 buffer overflow 가 발생할 것입니다.

이럴때 사용할 수 있는 방법이 바로 다른 버퍼 주소를 전달하는 것입니다.
적당한 메모리 주소에 변경하고자 하는 긴 문자열을 적어 놓고
MessageBoxW 함수에게 그 주소를 파라미터로 넘겨주는 것입니다.

즉, 버퍼 자체를 변경하는 것이죠.

아이디어가 좋긴 한데 한가지 고려해야 할 사항은 "메모리 어느 영역에 문자열을 써도 되는가?" 입니다.

자세한 설명은 PE header가상 메모리 구조를 알고 계셔야 하므로 나중에 자세히 다루도록 하고,
여기서는 임의로 적절한 영역을 선택하도록 하겠습니다.

우리가 방법 1) 에서 수정한 버퍼는 408000 ~ 40A000 영역 (.rdata section) 입니다.
이 부분을 다시 dump 윈도우로 살펴보죠. dump 윈도우에서 408000 주소로 갑니다. [Ctrl+G]

스크롤을 밑으로 내리다보면 .rdata section 은 아래와 같이 끝이 납니다.


<Fig. 20>


끝부분에 NULL 로 채워진 영역이 보입니다.

* 이곳은 보통 프로그램에서 사용되지 않는 NULL padding 영역입니다.
  프로그램이 메모리에 로딩될 때 최소 기본 단위(보통 1000)가 있습니다.
  비록 프로그램내에서 메모리를 100 크기만큼만 사용한다고 해도 실제로는 최소 기본 단위인 1000 크기가 잡히는 것입니다.
  (나머지 F00 크기의 사용되지 않는 영역은 그냥 NULL 로 채워집니다.)

이곳을 문자열 버퍼로 사용해서 MessageBoxW 함수에 넘겨주면 좋을 것 같습니다.
끝부분의 적당한 위치 409F50 에 출력하고 싶은 문자열을 써주면 됩니다. [Ctrl+E]


<Fig. 21>


버퍼를 새로 구성하였으니 그 다음에 할 일은 MessageBoxW 함수에게 새로운 버퍼 주소를 전달하는 것입니다.

그러기 위해서는 코드를 수정해야 하는데,
이번에는 Code 윈도우에서 Assemble 명령을 사용해서 코드를 수정해 보겠습니다.

아래 그림처럼 cursor 를 401007 주소위치에 놓고 Assemble 명령(단축키 [Space])을 내리면
아래와 같은 Assemble 윈도우가 나타납니다.


<Fig. 22>


새로운 버퍼주소인 409F50 을 입력합니다.

* 디버깅의 강력한 기능중의 하나가 바로 위와 같이 실행중인 프로세스의 코드를 동적으로 패치 시킬 수 있다는 것입니다.

* 향후 실습해 볼 crackme 샘플에서 serial key 검사 코드를 건너뛰는 방법도 코드를 동적으로 패치하는 것입니다.

OllyDbg 에서 MessageBoxW 함수를 실행하면 결과는 <Fig. 19> 와 같습니다.

그런데 위 수정된 코드를 파일로 만들면 제대로 동작하지 않을 것입니다.
이유는 409F50 메모리 주소 때문입니다.

실행 파일이 메모리에 로딩되어 프로세스로써 실행될 때 그대로 1:1 로 로딩되는 것이 아니라,
어떤 규칙에 의해서 올라가게 되며, 보통은 파일과 메모리가 1:1 로 매칭 되지도 않습니다.

즉, 메모리 409F50 에 대응되는 파일 offset 이 존재하지 않는 것이죠.

방법 2)를 파일로 저장하기 위해서는 아래 2가지 방법중에 하나를 사용하시면 됩니다.

  • PE header 를 분석하여 파일에 존재하지만 프로그램에서 사용되지 않는 공간을 버퍼영역으로 선정
  • 파일 끝을 버퍼 영역 만큼 확장하고, PE header 를 수정하여 그 부분을 메모리에 로딩시킴


역시 PE header 를 알아야 하기에 여기서는 설명을 생략하였습니다.

* 앞으로 작성하게 될 PE header 강좌도 기대해 주세요~



배운내용

- OllyDbg 기초 사용법

Step Into [F7] : 하나의 OP code 실행 (CALL 명령을 만나면, 그 함수 코드 내부로 따라 들어감.)
Step Over [F8] : 하나의 OP code 실행 (CALL 명령을 만나면, 따라 들어가지 않고 그냥 함수자체를 실행함.)
Execute till Return [Ctrl+F9] : 함수 코드 내에서 RETN 명령어 까지 실행 (함수 탈출 목적)
Restart [Ctrl+F2] : 다시 처음부터 디버깅 시작. (디버깅 당하는 프로세스를 종료하고 재실행 시킴.)
Go to [Ctrl+G] : 원하는 주소를 찾아감. (코드를 확인할 때 사용. 실행되는 것은 아님.)
Execute till Cursor [F4] : cursor 위치까지 실행함 (디버깅 하고 싶은 주소까지 바로 갈 수 있음.)
Comment [;] : Comment 추가
User Defined Comments [마우스 우측 메뉴 -> Search for -> User defined Comment] : 사용자가 입력한 comment 목록 보기
Set/Reset BreakPoint [F2] : BP 설정/해제
Run [F9] : 실행 (BP 가 걸려있으면 그곳에서 실행이 정지됨.)
All referenced text strings [마우스 우측 메뉴 -> Search for -> All referenced text strings] : 코드에서 참조되는 문자열 보기
All intermodular calls [마우스 우측 메뉴 -> Search for -> All intermodular calls] : 코드에서 호출되는 모든 API 함수 보기
Name in all modules [마우스 우측 메뉴 -> Search for -> Name in all modules] : 모든 API 함수 보기
Edit data [Ctrl+E] : 메모리 수정
Assemble [Space] : Assembly 코드 작성
Copy to executable file [마우스 우측 메뉴 -> Copy to executable file] : 파일의 복사본 생성 (변경사항 반영됨)

- Assembly 기초 명령어

CALL XXXX : XXXX 주소의 함수를 호출
JMP XXXX : XXXX 주소로 점프
PUSH XXXX : 스택에 XXXX 저장
RETN : 스택에 저장된 복귀주소로 점프

- 프로세스 data/code 패치 방법

OllyDbg 의 ‘Edit data’와’Assemble’기능 이용

- 용어

VA (Virtual Address) : 프로세스내의 가상 메모리
OP code (OPeration code) : CPU 명령어 (byte code)
PE (Portable Executable) : Windows 실행 파일(EXE, DLL, SYS)




배워야할 내용

Virtual memory
PE header
Stack frame
OP code (advanced)
OllyDbg (advanced)



Epilogue

여기까지 따라 오시느라고 수고 하셨습니다.

위에 나온 모든 내용을 한번에 이해하기는 어렵습니다.
2 ~ 3번 반복해서 읽고, 직접 실습해 보시기 바랍니다.

C 프로그래밍에서 Hello World! 는 가장 간단한 프로그램이었습니다.
마찬가지로 Hello World! 디버깅 또한 가장 간단한 디버깅입니다.

Hello World! 를 시작으로 C 프로그래밍을 정복하셨듯이 디버깅 역시 정복하시기 바랍니다.

리버싱에서 디버깅이 차지하는 비중은 매우 큽니다. 또한 가장 재미있습니다.

부디 제 글이 디버깅의 재미를 조금이나마 전달해 드렸으면 좋겠습니다.

감사합니다.

반응형
반응형

원하는 코드를 빨리 찾아내는 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() 함수입니다.


<Fig. 7>

즉 <Fig. 7> 의 401145 주소에 있는 CALL 명령어가 호출하는 주소 401000 로 가보면 [F7]
그곳이 바로 우리가 찾는 main() 함수 코드 영역입니다.


<Fig. 8>

"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' 명령을 사용하면 아래와 같은 윈도우가 뜨면서 코드에서 참조되는 문자열들을 보여줍니다.


<Fig. 9>

OllyDbg 는 "401007 주소의 PUSH 004092A4 명령이 있는데, 이 명령에서 참조되는 4092A4 주소에는 'Hello World!' 문자열이 존재합니다." 라고 말하고 있는 것이죠.

문자열을 더블 클릭하면 main() 함수의 MessageBoxW() 호출 코드로 갈 수 있습니다.

참고로 메모리상에 있는 문자열의 확인을 위하여 OllyDbg 덤프 윈도우에서 Go to[Ctrl+G] 명령을 써보겠습니다.
(포커스를 덤프 윈도우에 놓고 단축키 명령 [Ctrl+G] 을 내려주세요.)


<Fig. 10>

"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>

<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(Run Time Packer)
  실행압축 유틸리티. 실행파일의 코드, 데이타, 리소스 등을 압축시켜 버립니다.
  일반 압축 파일과 다른 점은 실행 압축된 파일 그 자체도 실행파일 이라는 것입니다.
  (나중에 대표적인 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 를 타이핑 하면 자동 검색됩니다.


<Fig. 13>

USER32 모듈에서 Export type 의 MessageBoxW 함수를 선택하세요.
(시스템 환경에 따라 버전이 틀려질 수 있습니다.)

더블 클릭 하시면 아래와 같이 USER32.dll 에 구현된 실제 MessageBoxW 함수가 나타납니다.


<Fig. 14>

주소를 보시면 HelloWorld.exe 에서 사용되는 주소와 확연히 틀리다는걸 아실 수 있습니다.

이곳에 BP 를 설치[F2]하고 실행[F9]해 보겠습니다.

만약 HelloWorld.exe 프로그램에서 MessageBoxW 함수를 호출한다면 결국 이곳에서 실행이 멈추게 될 것입니다.
(간단한 원리 입니다.)


<Fig. 15>


예상대로 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)

반응형
반응형

HelloWorld.exex

HelloWorld.cpp




Level

초급



Content

"Hello World!" 프로그램을 디버깅 해보고 간단한 패치를 해보도록 하겠습니다.
이를 통하여 디버깅에 대한 감을 잡으실 수 있습니다.



Goal

- 기본적인 디버거 사용방법의 이해
- 간단한 Disassembly code 이해
- 간단한 프로그램 패치(patch)



Tool

Visual C++ 2008 Express Edition
OllyDbg 1.10



Hello World!

모든 C 프로그래머가 최초로 만들어 본 프로그램 Hello World! 를 최초의 디버깅 프로그램으로 결정하였습니다. SW 업계에서의 차지하는 Hello World! 의 의미, 처음 C 를 배울때의 두근거림 그리고 소스 코드의 간결함까지... 이 모든 것이 최초의 디버깅 프로그램으로써 더 할 나위 없이 딱 들어맞는군요.

자신에게 익숙한 C/C++ 개발툴을 이용하여 Hello World! 를 만들어 봅니다.
(Release 모드로 빌드하면 코드가 좀 더 간결해져서 디버깅하기 편합니다.)


<Fig. 1>




디버깅 목표


위에서 만든 HelloWorld.exe 를 실행해보면 당.연.히. "Hello World!" 메시지 박스가 출력될 것입니다. 그냥 디버깅만 하면 재미없으니까 이 문자열을 "Hello Reversing!" 으로 바꾸는걸 목표로 하겠습니다.

그러기 위해서는 먼저 HelloWorld.exe 를 디버깅하여 main() 함수내의 MessageBox() 함수 호출 코드를 찾아야 합니다.

그리고 적절히 해당 문자열 위치를 알아내어 변경시키면 되겠지요.



디버깅 시작

첨부된 HelloWorld.exe 파일을 OllyDbg.exe 로 열어보겠습니다.

* VC++ 도  소스가 있을때 어셈블리 수준의 디버깅이 가능합니다만, 일반적으로는 분석할때 소스가 없으므로 OllyDbg 같은 Win32 전문 디버거를 사용하게 됩니다.


<Fig. 2>


디버깅을 시작하기 전에 간단히
<Fig. 2> 에 보이는 OllyDbg 의 메인 화면 구성에 대해 설명드리겠습니다.

- code :
기본적으로 disassembly code 를 표시하고 각종 comment, label 을 보여주며 코드를 분석하여 loop, jump 위치 등의 정보를 표시한다.
- register : CPU register 값을 실시간으로 표시하며 특정 register 들은 수정도 가능함.
- dump : 프로세스내의 원하는 memory 주소 위치를 hex ASCII 값으로 표시하고 수정도 가능함.
- stack : ESP register 가 가리키는 프로세스 stack memory 를 실시간으로 표시하고 수정도 가능함.

디버거가 멈춘 곳은
EntryPoint(EP) 코드로써 HelloWorld.exe 의 실행 시작 위치(4011A1)입니다. 일단 EP 코드에서 눈에 띄는건 CALL 명령과 그 밑의 JMP 명령입니다.

Address     OP code        Disassembly       comment
----------------------------------------------------------------------------
004011A1    EB A6160000    CALL 0040284C     ; 0040284C (40284C 주소의 함수를 호출
)
004011A6    E9 A4FEFFFF    JMP 0040104F      ; 0040104F (40104F 주소로 점프)

- Address : 프로세스의 가상메모리(Virtual Address:VA) 내의 주소 위치
- OP code : OPeration code 의 줄임말로써 IA32(또는 x86) CPU 명령어
- Disassembly : OP code 를 보기쉽게 디스어셈 해준 코드
- comment : 디버거에서 추가한 주석 (옵션에 따라 약간씩 다르게 보임)

디스어셈 코드를 처음 보신 분들이라도 위 두줄의 코드는 그 의미가 명확합니다.

"40284C 주소의 함수를 호출(CALL)한 후 40104F 주소로 점프(JMP) 하라"

계속 디버깅을 진행해 보겠습니다.
목표는 우리가 작성한 main() 함수내의 MessageBox() 함수 호출을 확인하는 것입니다.



40284C
함수 따라가기

- Step Into [F7] :
하나의 OP code 실행 (CALL 명령을 만나면, 그 함수 코드 내부로 따라 들어감.)
- Step Over [F8] : 하나의 OP code 실행 (CALL 명령을 만나면, 따라 들어가지 않고 그냥 함수자체를 실행함.)
- Execute till Return [Ctrl+F9] :
함수 코드 내에서 RETN 명령어 까지 실행 (함수 탈출 목적)

F7
단축키로 함수 안으로 따라갈 수 있습니다.


<Fig. 3>


위 코드를 얼핏 보아도
Hello World! 에서는 사용된 적 없는 API 들이 호출되고 있습니다.
이곳은 분명 우리가 찾는 main() 함수가 아니겠군요.
이곳은 VC++ 에서 프로그램 실행을 위해서 추가시킨 (우리 소스코드에는 없지만) VC++ stub code 입니다. (각 컴파일러 종류/버전별로 stub code 는 틀려집니다.)
지금은 신경쓰지 말고 우리의 목표 main() 을 찾아 계속 진행해 보겠습니다.

*
주의사항 : 처음에는 Win32 API 함수들(OllyDbg comment 상에서 빨간색 API 함수 호출 부분)은 따라가지 마세요.  너무 헤멜 수 있습니다. Step over[F8] 로 넘어가세요.

4028E1
주소에 RETN 명령어가 있습니다.
(RETN
은 함수의 끝을 나타내며 함수가 호출된 명령어 바로 다음 명령어로 되돌아 갑니다.)

그곳까지 Step over [F8] 하시거나 또는 Execute till Return [Ctrl+F9]로 한방에 가봅니다.

그리고
RETN 명령어를 실행하면[F7/F8] <Fig. 2> 에서 봤던 4011A6 주소로 오게됩니다.
(C
언어에서 함수를 호출하고 리턴하면 그 다음 명령어로 오는 것을 생각하시면 됩니다.)



40104F 따라가기

4011A6
주소의 JMP 0040104F 명령을 실행해서 40104F 로 갑니다.


 <Fig. 4>


<Fig. 3> 보다 좀 더 복잡해 보이는 VC++ stub code 가 나타났습니다
실력 향상을 원하시는 분들은 이 코드에서 Step In/Over 를 사용해서 마구 헤매셔야(?) 합니다
함수 호출을 너무 깊게 따라가서 너무 힘든 상황이라고 생각되시면 아래의 ‘디버거 좀 더 능숙하게 다루기’ 내용을 참고하셔서 다시 디버깅 하시면 됩니다.

* 헤매야 하는 이유
여러분이 초보자이기 때문입니다. ^^ 각 컴파일러/버전별로 stub code 를 눈에 익혀 놓으시면실전에서는 stub code 처럼 보이는 부분은 빠르게 뛰어 넘을 수 있습니다
마치 C언어 처음 배울때 컴파일 에러를 많이 내보고, 에러 메시지를 눈에 익히는 것과 같다고 할 수 있습니다실전에서 같은 에러 메시지가 발생했을때 능숙하게 해결하기 위함이지요.



디버거 좀 더 능숙하게 다루기

- Restart [Ctrl+F2] : 다시 처음부터 디버깅 시작. (디버깅 당하는 프로세스를 종료하고 재실행 시킴.)
- Go to [Ctrl+G] : 원하는 주소를 찾아감. (코드를 확인할 때 사용. 실행되는 것은 아님.)
- Execute till Cursor [F4] : cursor 위치까지 실행함 (디버깅 하고 싶은 주소까지 바로 갈 수 있음.)
- Comment [;] : Comment 추가
- User Defined Comments : 마우스 우측 메뉴 -> Search for -> User defined Comment
- Set/Reset BreakPoint [F2] : BP 설정/해제
- Run [F9] : 실행 (BP 가 걸려있으면 그곳에서 실행이 정지됨.)

디버깅을 재실행[Ctrl+F2] 하시고 [Ctrl+G] 단축키로 40104F 주소로 갑니다
40104F 주소에 커서가 놓여져 있을텐데요, [F4] 단축키로 바로 날라갑니다
이곳(40104F) 베이스 캠프라고 부르겠습니다

베이스 캠프로 가는 또 다른 방법은 BP(Break Point) 를 설정[F2] 하고 실행[F9] 하는 것입니다디버거는 현재 실행위치서부터 프로세스를 실행하다가 BP 가 걸린 곳에서 멈추게 됩니다. (BP 없으면 그대로 계속 실행)

그럼 이번에는 [;] 단축키로 주석을 달아 볼까요?


<Fig. 5>


프로그래밍에서와 마찬가지로 디버깅에서 주석은 매우 중요한데요, 커서 위치를 잠시 다른 곳에 두고 'User Defined Comments' 메뉴를 선택하면 아래와 같이 사용자가 입력한 주석들이 표시됩니다. 


<Fig. 6>


빨간 글씨로 표시된 부분이 커서 위치입니다. 
주석 위치와 겹쳐지면 빨간 글씨만 나타납니다. 
(그래서 커서위치를 잠시 다른 곳에 두시라고 한겁니다.)

해당 주석을 더블 클릭하면 그 주소로 갈 수 있습니다
(OllyDbg 를 종료하더라도 주석은 *.udd 파일에 보관되기 때문에 다음에 다시 볼 수 있습니다.)

이어지는 강좌에서 '원하는 코드를 빨리 찾는 방법' 을 가르쳐 드릴텐데요
나중에라도 베이스 캠프에서부터 API 호출을 제외한 모든 함수 호출을 일일이 따라가[F7] 보시는걸 권장합니다

(continue)

반응형

+ Recent posts