analysis

"Hello World!" - 내 생애 첫 디버깅 (3)

reversecore 2009. 2. 28. 13:39
반응형

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 프로그래밍을 정복하셨듯이 디버깅 역시 정복하시기 바랍니다.

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

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

감사합니다.

반응형