1. Hello World! 프로그램
- Release 모드로 빌드해서 실행 파일을 만든다.(Release 모드로 빌드하면, 코드가 좀 더 간결해져서 디버깅하기 편하다.)
2. HelloWorld.exe 디버깅
2-1. 목표
- HelloWorld.exe 실행 파일을 디버깅(Debugging)하여 어셈블리 언어로 변환된 main() 함수 찾기
- 기본적인 디버거의 사용법과 어셈블리 명령어 학습
2-2. 디버깅 시작
- OllyDbg: http://www.ollydbg.de
* 64bit 버전 사용
창 |
설명 |
Code Window |
disassembly code를 표시하여 각종 comment, label을 보여주며, 코드를 분석하여 loop, jump 위치 등의 정보를 표시 |
Register Window |
CPU register 값을 실시간으로 표시하며 특정 register들은 수정 가능 |
Dump Window |
프로세스에서 원하는 memory 주소 위치를 Hex와 ASCII/유니코드 값으로 표시하며 수정도 가능 |
Stack Window |
ESP register가 가리키는 프로세스 stack memory를 실시간으로 표시하며 수정도 가능 |
2-3. EP(EntryPoint)
- 디버거가 멈춘 곳은 EP(EntryPoint) 코드로, 실행파일의 시작 주소이다.
Address Instruction Disassembled code comment -------------------------------------------------------------------------- 004011A0 E8 67150000 CALL 0040270C ; 0040270C (40270C 주소의 함수를 호출) 004011A5 E9 A5FEFFFF JMP 0040104F ; 0040104F (40104F 주소로 점프) |
구분 |
설명 |
Address |
프로세스의 가상 메모리(Virtual Address:VA)내의 주소 |
Instruction |
IA32(또는 x86) CPU 명령어 |
Disassembled code |
OP code를 보기 쉽게 어셈블리로 변환한 코드 |
comment |
디버거에서 추가한 주석(옵션에 따라 약간씩 다르게 보임) |
3. OllyDbg 사용하기
3-1. OllyDbg 단축키
명령어 |
단축키 |
설명 |
Restart |
Ctrl+F2 |
다시 처음부터 디버깅 시작(디버깅 중인 프로세스를 종료하고 재실행) |
Step Into |
F7 |
하나의 OP code 실행(CALL 명령을 만나면 그 함수 코드 내부로 따라 들어감) |
Step Over |
F8 |
하나의 OP code 실행(CALL 명령을 만나면 따라 들어가지 않고 그냥 함수 자체를 실행) |
Execute till Return |
Ctrl+F9 |
함수 코드 내에서 RETN 명령어까지 실행(함수 탈출) |
Go to | Ctrl+G | 원하는 주소로 이동(코드/메모리를 확인할 때 사용, 실행되는 것은 아님) |
Execute till Cursor | F4 | cursor 위치까지 실행(디버깅하고 싶은 주소까지 바로 갈 수 있음) |
Comment | ; | Comment 추가 |
User-defined comment |
| 마우스 우측 메뉴 Search for User-defined comment |
Label | : | Label 추가 |
User-defined label |
| 마우스 우측 메뉴 Search for User-defined label |
Set/Reset BreakPoint | F2 | BP 설정/해제 |
Run | $9 | 실행(BP가 걸려있으면 그곳에서 실행이 정지됨) |
Show the current EIP | * | 현재 EIP 위치를 보여줌 |
Show the previous Cursor | - | 직전 커서 위치를 다시 보여줌 |
Preview Call/JMP address | Enter | 커서가 CALL/JMP 등의 명령어에 위치해 있다면, 해당 주소를 따라가서 보여줌 (실행되는 것이 아님, 간단히 함수 내용을 확인) |
3-2. 베이스캠프 설정하기
1) Goto 명령
- 베이스캠프 주소를 기억해 두었다가 Go to[Ctrl+G] 명령으로 베이스캠프로 이동
* Goto명령[Ctrl+G] 이후 주소 입력
2) BP(Break Point) 설치
- Break Point를 설치[F2]를 하고 실행[F9]
* BP 설정[F2]시 Address부분이 빨간색으로 변함
- BP 설정은 View - Breakpoints에서 확인 가능([Alt+B])
* 해당목록에서 더블클릭시 해당 주소로 이동
3) 주석
[;] 단축키로 주석(Comment)을 달고, 이 주석을 찾아가는 방법도 있다.
* BaseCamp라는 이름으로 주석 설정
* 설정 완료
* 오른쪽 마우스 > Search for > User comments에서 확인 가능
3-3. 원하는 코드 빨리 찾는 방법
1) 코드 실행하기
- 프로그램의 기능이 명확한 경우 명령어를 하나하나 실행(Step Over[F8])을 통해 원하는 위치를 찾아감
- 코드의 크기가 작고, 기능이 명확한 경우에 사용
- 코드의 크기가 크고 복잡한 경우에는 적절하지 못함
2) 문자열 검색 방법
- OllyDbg가 디버깅할 프로그램을 처음 로딩할 때 사전 분석 과정을 통해 참조되는 문자열과 호출되는 API들을 뽑아내서 따로 목록으로 정리
- 오른쪽마우스 > Search for > All referenced text strings
3) API 검색 방법(1) - 호출 코드에 BP
- 오른쪽마우스 > Search for > All intermodular calls
4) API 검색 방법(2) - API 코드에 직접 BP
- 오른쪽마우스 > Search for > Names
- OllyDbg가 모든 실행 파일에 대해서 API 함수 목록을 추출할 수 있는 것은 아니다.
- Packer/Protector를 사용하여 실행 파일을 압축(또는 보호)해버리면, 파일 구조가 변경되어 OllyDbg에서 API 호출 목록이 보이지 않음
용어 |
설명 |
Packer(Run Time Packer) |
- 실행 압축 유틸리티, 실행 파일의 코드, 데이터, 리소스 등을 압축 - 일반 압축 파일과 다르게 실행 압축된 파일 그 자체도 실행 파일임 |
Protector |
- 실행 압축 기능 외에 파일과 그 프로세스를 보호하려는 목적으로 - anti-debugging, anti-emulating, anti-dump 등을 추가 - Protector를 상세 분석하기 위해서는 높은 수준의 리버싱 지식이 요구 |
- 해당 파일을 리버깅하기 위해서는 프로세스 메모리에 로딩된 라이브러리(DLL 코드)에 직접 BP를 걸어보아야 한다.
- API는 OS에서 제공한 함수이고, 실제로 API는 C:\Windows\system32 폴더에 *.dll 파일 내부에 구현되어 있다.
- 프로그램이 OS에서 실행되기 위해서는 OS에서 제공하는 API를 사용해서 OS에 요청을 해야 하고, 그 API가 실제로 구현된 시스템 DLL 파일들은
프로그램의 프로세스 메모리에 로딩되어야 한다.
- OllyDbg에서 확인하는 방법은 View > Memory map[Alt+M]
* KERNEL32에 로딩되는 것을 확인 할 수 있다.
- KERNEL32에서 .text 부분에 들어가서 printf를 확인할 수 있다.
- 해당부분에 breakpoint를 걸고 확인해 보면 잘 되는 것을 확인 할 수 있다.
4. 문자열 패치하기
- 문자열 버퍼를 직접 수정
- 다른 메모리 영역에 새로운 문자열을 생성하여 전달
4-1. 문자열 버퍼를 직접 수정
- Dump window에서 "Hello World!"가 저장된 메모리인 '012A573C'로 이동
- 해당부분을 선택하고 Enter를 누르면 수정 창이 뜬다.
- 유니코드는 알파벳 하나당 2Byte의 크기를 차지한다.
* 원본 문자열에 덮어씌울 경우 그 뒤의 데이터가 훼손 되지 않게 조심하여야 한다. 훼손시 메모리 참조 에러가 발생할 위험이 있다.
* 유니코드 문자열은 2Byte 크기의 NULL로 끝나야 한다.
* 수정된후 실행화면
- 해당 방법은 수정후 프로그램을 다시 저장하지 않으면 프로그램 종료후 원상 복구 된다.
4-2. 다른 메모리 영역에 새로운 문자열을 생성하여 전달
- 적당한 메모리 영역에 패치하고자 하는 긴 문자열을 적어 놓고 printf 함수에게 그 주소를 파라미터로 넘겨 준다.
- Hello World!가 저장된 주소 '012A573C' 에서 조금만 내리다 보면 NULL로 덮여 진 부분이 나타난다.
- 이부분은 프로그램에서 사용하지 않는 NULL padding 영역이라고 한다.
- 프로그램이 메모리에 로딩될 때 최소 기본 단위(보통 1000)가 있다. 이 때문에 100의 크기만큼 메모리를 사용한다고 하면 나머지 900 크기는 NULL로 채워진다.
- 해당 부분에 문자열을 입력한다.
- printf 함수에서 Hello World가 있는 주소를 참조하는 부분에서 코드를 수정[space]를 통해 임의로 작성한 문구가 있는 주소로 바꿔준다.
- 잘 실행되는 것을 확인 할 수 있다.
- 하지만 수정된 코드로 파일을 저장후 실행한다면 제대로 동작하지 않을 수 있다.
- 파일이 그대로 메모리로 로딩되는 것이아니라 특정한 규칙에 의해서 올라가기 때문에 파일에 해당 offset이 존재하지 않을 수 있기 때문이다.