반응형

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)

반응형
반응형

요즘 Windows 어플리케이션을 개발할 때 Assembly 언어로 개발하시는 분들은 보기 드물죠.
보통은 C/C++, VB, Delphi 등의 4GL 로 개발을 하게 됩니다.

이렇게 만들어진 프로그램을 에디터로 열어보면 아래 그림과 같이
소스 코드는 보이지 않고 뭔가 이상한 기호들이 가득하지요.


<Fig. 1>

전 리버싱을 접하기 전에는 컴파일러와 링커가 소스 코드를 이상하게 변화시키기 때문에
그 내용을 아무도 이해 할 수 없을꺼라고 생각했었습니다.

더 정확히는 그 내용을 이해할 수 있는 사람은 몇명 되지 않을꺼라 생각했던 거지요.
컴파일러는 사람이 보기 편한 소스코드를 CPU 가 이해할 수 있는 이진 코드로 바꿔놓고,
링커는 OS 에서 실행이 가능한 형태로 파일을 재구성하는 것 뿐이기 때문에,
이진 코드를 이해할 수 있는 리버서가 봤을때는 그 파일의 내부구조가 훤히 보이게 되는 것입니다.

즉, 극소수(?)의 사람들에게는 소스코드가 그대로 노출된다고 봐야겠지요.
이것이 리버스 엔지니어링의 묘미이기도 하구요.

세상 모든 이치가 그렇듯이 모를 때는 어려워 보이지만, 일단 알고나면 쉬워집니다.
4GL 개발자가 리버스 엔지니어링을 몰랐을때는 뭔가 대단히 어려워 보이고,
자기가 할 수 없을것 같지만 사실 알고보면 쉽습니다.
(그 중에 더 열심히 한 사람은 물론 더 잘하겠지요.)

알.고.보.면. 이라는 말이 상당히 많은 내용을 함축하고 있어서 처음에 좀 힘든것 뿐입니다.
앞으로 올리게 될 'analysis' 포스트들을 보시고 직접 따라해 보시면
아마도 조금은 ‘리버스 엔지니어링 이라는게 대충 이런 거구나’라고 느낄 수 있으실겁니다.

반응형
반응형

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)

반응형
반응형



Reverse Engineering (RE)



일반적인 의미에서 Reverse Engineering (RE : 역공학)이라고 하면,
물건이나 기계장치 혹은 시스템 등의 구조, 기능, 동작등을 분석하여
원리를 이해하며 단점을 보완하고 새로운 아이디어를 추가시키는
일련의 작업이라고 할 수 있습니다.


Reverse Engineering(이하 리버싱) 의 개념은 여러분의 일상생활 속에서도 찾아 볼 수 있습니다.

회사에서 리버싱이 사용되는 예를 들어 보겠습니다.
가령 경쟁사에서 신제품을 출시했다고 해봅시다.
R&D 부서에서는 얼른 구입해서, 어떤 새로운 기능이 있으며 어떻게 만들었는지 살펴볼 것입니다.

구현 능력이 엇비슷 하다면 금방 따라 만들 수 있을것입니다.
혹은 원래 기능의 단점을 보완하고 더 새로운 기능을 추가시킬 수 있을지도 모릅니다.



Reverse Code Engineering (RCE)



소프트웨어 역공학의 의미는 일반적인 의미의 역공학과 기본적으로 같은 개념이지만,
분야를 소프트웨어로 한정지어 놓았다고 이해하시면 되겠습니다.

우리나라에서는 아직 정확한 용어의 통일이 되지 않아서 여러가지 용어들이 같이 사용되고 있습니다.
많은 분들께서 Reverse Code Engineering, Reverse Engineering, 역공학, 리버싱등의 용어를 마구 섞어서 쓰고 계십니다.

제 블로그에서도 'RCE', 'RE' 혹은 편하게 '리버싱' 이라고 표현하겠으며,
제가 가장 좋아하는 단어인 '분석'  또는 '상세분석' 이라는 말을 많이 쓰도록 하겠습니다.
말 그대로 소프트웨어를 리버싱 관점에서 상세하게 분석한다는 뜻입니다.


일반적인 소프트웨어 혹은 프로그램들의 제작방법을 보면,
먼저 소스 코드를 작성하고 컴파일/링크 과정을 거쳐서 실행가능한 binary 형태의 파일로 만들어 집니다.

여기서 실행가능하다는 말은 컴퓨터가 그 binary 를 이해할 수 있어서
프로그램을 제작자의 의도대로 실행 시킬 수 있다는 뜻입니다.

컴퓨터를 잘 이해하고 있는 사람 또한 같은 바이너리 코드를 보고 이해할 수 있습니다.
(물론 이해 속도는 떨어지겠지만요.)

일반인들이 실행가능 binary 파일의 내용을 보면 마치 고대의 해독 불가능한 상형문자를 보는 것과 마찬가지로 그 내용을 이해할 수 없지만, 소프트웨어 역공학 전문가들의 눈에는 그 내용이 훤히 보이게 되는 것입니다.

어떻게 그런 일이 가능할까요?
컴퓨터의 구조와 언어를 능숙하게 익히면 가능합니다.

어떤 리버싱 전문가는 농담삼아(?) 이렇게 이야기 하기도 합니다.
“남들이 작성한 지저분한 C 소스코드 보는 것보다, 그 실행파일의 disasm 코드를 보는게 더 편해.”



RCE 방법



크게 두가지 방법이 있습니다. 정적분석동적분석입니다.

1) 정적분석
: 파일을 실행 시켜 보지 않고 분석하는 방법입니다.
  파일의 종류(EXE, DLL, DOC, ZIP, etc), 크기, 헤더(PE)내용, 내부 문자열, 실행압축여부, 등록정보, 디버깅정보,
  디지탈 인증서 등을 확인하는 것입니다.
  또한 디스어셈블러(Disassembler)를 이용해서 내부 코드와 그 구조를 확인 할 수 있습니다.

2) 동적분석
: 파일을 직접 실행 시켜서 분석하는 방법입니다.
  실행 증상(파일, registry, 네트워크 등)을 모니터링 하고,
  디버깅(debugging) 하여 기능, 동작, 메모리 상태, 레지스터 값, 코드 흐름을 살펴보는 것입니다.

두 가지 분석 방법 모두 편리한 유틸리티들을 사용하여 분석이 이루어지며,
경우에 따라서 두 가지 방법 중에서 한가지 혹은 두가지 모두를 사용하여 분석을 완료합니다.

제 경우에는 보통 정적분석으로 최대한의 정보를 수집하고 해당 파일에 대한 감을 잡은 후에 동적분석을 해봅니다.
Debugging 은 가장 마지막에 쓰거나 경우에 따라서는 아예 쓰지 않습니다.



패치(patch) 또는 크랙(crack)




프로그램의 파일 혹은 실행중인 프로세스의 메모리 내용을 변경시키는 작업을 패치(patch) 라 하고,
그 중에서도 의도적으로 저작권을 침해하기 위하여 변경시키는 작업을 크랙(crack) 이라고 합니다.

이러한 예는 일상생활에서 많이 접할 수 있습니다.

iPhone 을 크랙하여 쉘을 띄우고, 게임기를 크랙하여 복제 CD 를 사용할 수 있도록 하며
각종 SW 를 크랙하여 불법 복제를 가능하도록 하는등 셀 수 없이 많은 크랙을 볼 수 있습니다. 

제 블로그에서는 정품 소프트웨어의 크랙 방법을 다루지 않습니다.
어디까지나 리버싱의 원리와 OS 내부구조에 대한 깊이있는 공부가 주된 관심사 입니다.



ReverseCore



리버싱 방법과 그 원리에 대해서 설명하는 블로그로 만들겠습니다.

ReverseCore 접속 준비물은 적당한 의욕  구글(Google) 입니다. 


- 적당한 의욕 

    과욕을 부리시면 안됩니다.
    리버싱은 인내, 인내, 인내의 연속입니다.
    과욕을 부리시다가 생각대로 안되면 중도 포기하게 됩니다.
    "안돼면 내일 하지 뭐~" 이런 자세가 좋은것 같습니다.


- 구글(Google) 

    뭐든지 다 있다고 그냥 믿으십시요.
    내가 궁금한 내용이 다 있다 라고 그냥 순수하게 믿고 찾으시기 바랍니다. 
    지금부터 배우려는 내용은 이미 수년전에 누군가가 했었던 것들이며,
    수년동안 많은 사람들이 따라 해봤던 내용들입니다.
    만약 찾는게 진짜로 없다면... 
    축하드립니다.
    드디어 당신은 리버싱 guru 의 경지에 도달하신겁니다. ^^


제가 강조하고 싶은 내용은
리버싱이란 컴퓨터와 OS 내부구조에 정통해야 좋은 성과를 얻을 수 있다
는 것입니다. 


 

반응형

+ Recent posts