2.1. Hello World! 프로그램

- Release 모드로 빌드해서
HelloWorld.cpp -> HelloWorld.exe 실행파일 생성
2.1.1. 디버거와 어셈블리 언어
-
개발 도구를 이용해서 소스코드(.cpp) -> 기계어(.exe)
-
디버거에 탑재된 디스어셈블러 모듈을 사용해서 기계어 -> 어셈블리 언어
-
어떤 프로그래밍 언어로 생성되더라도 실행파일을 빌드하면 모두 기계어로 변환됨
- 어셈블리 언어만 알면 어떤 실행파일이라도 분석 가능
- 어셈블리 언어는 CPU에 종속
-> CPU에 따라 어셈블리 명령어의 형태 다름
2.2. HelloWorld.exe 디버깅
2.2.1. 디버깅 목표
- 어셈블리어로 변환된
main() 함수 찾기
- 기본적인 디버거의 사용법과 어셈블리 명령어 익히기
2.2.2. 디버깅 시작
| 화면 구성 | 설명 |
|---|
| 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.2.3. EP
EP(Entry Point)
EP란 Windows 실행 파일(EXE, DLL, SYS 등)의 코드 시작점을 의미.
프로그램이 실행될 때 CPU에 의해 가장 먼저 실행되는 코드 시작 위치

- 실제로
HelloWorld.exe파일을 OllyDbg로 열어보면 디버거가 해당 파일의 EP인 4011A0에 멈춘 것을 볼 수 있음
- 맨 위의 두 줄이 의미하는 바는 다음과 같음.
-> "40270C 주소의 함수를 호출(CALL)한 후 40104F 주소로 점프(JUMP)하라"

2.2.4. 40270C 함수 따라가기
- OllyDbg 기본 명령어(Code Window에서 동작)
| 명령어 | 단축키 | 설명 |
|---|
| Restart | [Ctrl+F2] | 다시 처음부터 디버깅 시작(디버깅을 당하는 프로세스를 종료하고 재실행) |
| Step Into | [F7] | 하나의 OP code실행(CALL 명령을 만나면, 그 함수 코드 내부로 따라 들어감) |
| Step Over | [F8] | 하나의 OP code실행(CALL 명령을 만나면, 따라 들어가지 않고 그냥 함수 자체를 실행) |
| Execute till Return | [Ctrl+9] | 함수 코드 내에서 RETN 명령어까지 실행(함수 탈출 목적) |
4011A0에서 [F7]를 눌러 40270C함수 안으로 들어가면 다음과 같음

-
오른쪽의 주석에서 빨간색 글씨는 코드에서 호출되는 API 함수 이름인데, 원본 소스코드에서 사용된 적 없는 API들임.
-> 해당 함수는 우리가 찾던 main() 함수가 아님!
-
사실 Visual C++에서 프로그램 실행을 위해서 추가한 Visual C++ Stub Code임.
-
[F8] 또는 [Ctrl+F9]를 사용해서 RETN 명령어가 있는 4027A1까지 가기
-> RETN 명령어 실행하면 이 함수 안으로 들어오기 전에 봤던 4011A5로 감
2.2.5. 40104F 점프문 따라가기
4011A5 주소에서 JMP 0040104F를 실행

- 이 코드도 Visual C++ Stub Code임
- 디버깅을 자주 하면 이런 형식에 익숙해질 것임 -> 빨리 건너띄기
- 쭉 따라가보면 우리의 목표인
main()가 있을 것임!!
2.2.6. main( ) 함수 찾기
0040104F부터 CALL 함수 다 들어가보고 main() 함수 찾기
- [401056] CALL 00402524

- MessageBox() API 호출 코드가 보이지 않기 때문에 main() 함수라고 보기 어려움
- 함수에서 벗어나 함수 실행 위치 다음 줄로 돌아감(
40105B)
- [4010E4] CALL Kernel32.GetCommandLineW

- 계속해서 CALL 함수를 따라 들어가서 main() 함수인지 확인하는 방법으로 디버깅을 하다보면 위와 같은 코드를 보게 됨
4010E4 주소의 CALL Kernel32.GetCommandLineW 명령어는 Win32 API 호출 코드임(여기서는 필요없음. 참고하기)
- [401144] CALL 00401000

401144 주소에서 CALL 00401000 실행하여 해당 함수로 들어감

- 함수 코드를 보면 MessageBoxW() API 호출 코드의 파라미터가 "www.reversecore.com"과 "Hello World!" 문자열임
-> HelloWorld.cpp의 소스코드의 내용과 일치
-> 401000 함수 = 우리가 찾던 main() 함수!!
2.3. 디버거 좀 더 능숙하게 다루기
2.3.1. 디버거 명령어

2.3.2. 베이스 캠프
- 히말라야 고봉 등정 과정과 비슷하게 파일의 중요 포인트를 정해서(베이스캠프 설치) 효율적인 디버깅 작업하기
2.3.3. 베이스 캠프를 설치하는 4가지 방법
1) Goto 명령
- 베이스캠프 주소(
40104F)를 기억해 두었다가 [Ctrl+G] 명령어로 이동
- 해당 주소에 커서가 놓여져 있으면 Execute till cursor[F4] 명령으로 그곳까지 실행
2) BP 설치
- BP(Break Point)를 설치[F2]하고 실행[F9]
- 가장 보편적인 방법
- 디버거는 현재 실행 위치에서부터 프로세스를 실행하다가 BP가 걸린 곳에서 멈춤
- BP 목록 조회: 메인메뉴의 View - Breakpoints[ALT+B](원하는 BP 더블클릭하면 해당 주소로 이동)
3) 주석
- [ ; ] 단축키로 주석 달아서 표시해놓고 찾아 가는 방법
- 사용자가 입력한 주석 조회: 마우스 우클릭 - Search for - User defined comment
4) 레이블
- 레이블(Label)은 원하는 주소에 특정 이름을 붙여주는 기능
- 단축키[ : ] 이용
- 레이블 목록 조회: 마우스 우클릭 - Serach for - User defined labels
2.4. 원하는 코드를 빨리 찾아내는 4가지 방법
2.4.1. 코드 실행 방법
- 디버거로 디버깅(Step Over[F8])하다보면 언젠가 원하는 코드에 이동 및 실행됨
- 그러나 코드의 크기가 작고 기능이 명확한 경우에 사용 가능
2.4.2. 문자열 검색 방법
- 마우스 우클릭 - Search for - All referenced text strings
- 프로그램 코드에서 참조되는 문자열 조회 가능
- 알아두기) 코드와 데이터 영역이 서로 나뉘어져 있음
2.4.3. API 검색 방법 (1) - 호출 코드에 BP
- Windows 프로그래밍에서 모니터 화면에 뭔가를 출력한다 = 프로그램 내부에서 Win32 API를 사용
- 코드에서 사용된 API 호출 목록 조회: All intermodular calls 명령 사용
2.4.4. API 검색 방법 (2) - API 코드에 직접 BP
Packer/Protextor를 사용해서 실행 파일을 압축(/보호)해버리면 파일 구조가 변경되어 OllyDbg에서 API 호출 목록 조회 불가능할 경우 있음
- Packer(Run Time Packer): 실행 압축 유틸리지. 실행 파일의 코드, 데이터, 리소스 등을 압축시킴. 일반 압축 파일과 다른 점은 실행 압축된 파일 그 자체도 실행 파일이라는 것
- Protector: 실행 압축 기능 외에 파일과 그 프로세스를 보호하려는 목적으로 anti-debugging, anti-emulating, anti-dump 등의 기능을 추가한 유틸리티. Protector를 상세 분석하려면 높은 수준의 리버싱 지식 요구됨
- 프로세스 메모리에 로딩된 라이브러리(DLL 코드)에 직접 BP 걸기
- 프로그램이 어떤 일을 하려면 반드시 OS에서 제공된 API 사용해서 OS에 요청해야 하고, API가 실제 구현된 시스템 DLL 파일들은 프로그램의 프로세스 메모리에 로딩되어야 하기 때문
- 프로세스 메모리에 로딩된 라이브러리 조회: View - Memory [Alt+M]
- 'Name in all modules' 명령으로 찾고자하는 라이브러리 검색
2.5. “Hello World!” 문자열 패치
2.5.1. 패치
- 기존 응용 프로그램의 버그 수정하거나 새로운 기능 추가
- 패치 대상은 파일 혹은 메모리, 코드와 데이터 모두 패치 가능
2.5.2. 문자열을 패치하는 두 가지 방법
1) 문자열 버퍼를 직접 수정
- 장점: 사용하기에 가장 간단함
- 단점: 기존 문자열 버퍼 크기 이상의 문자 입력하기 어려움(제약 조건) -> 권장하지 않는 방법
-
덤프 창에서 [Ctrl+G] 단축키 이용해서 4092A0 주로로 이동

-
[Ctrl+E] 단축키 이용해서 "Hello World!" 유니코드 문자열이 차지하는 영역을 새로운 문자열로 덮어씀
- 유니코드 문자열은 2바이트 크기의 NULL로 끝나야 함
- 유니코드 항목에서는 NULL을 입력할 수 없으므로 HEX 항목에서 입력
- 원본 문자열보다 큰 문자열로 덮어쓸 때는 그 뒤의 데이터를 훼손하지 않도록 조심해야 함

-
다시 main() 함수(401000)로 들어가서 보면 명령어나 파라미터 주소는 그대로지만 전달되는 파라미터 내용이 바뀐 것을 확인할 수 있음

-
[F9]를 눌러서 실행해보면 메시지 박스에 패치된 문자열("Hello Reversing")이 잘 출력되고 있음

[파일로 저장하기]
- 단순한 패치 작업은 임시적이어서 디버거가 종료되면(해당 실행파일 프로세스가 종료되면) 패치했던 내용 사라짐
- 파일로 저장 방법
- dump 창에서 변경된 내용 선택해서 우클릭 - Copy to executable file 선택
- 새로 나타난 Hex 창에서 우클릭 - Save file 선택
2) 다른 메모리 영역에 새로운 문자열을 생성하여 전달

- 디버거 재실행 후 main() 함수를 다시 보면
4092A0 주소에 저장된 문자열을 파라미터로 전달하고 있음
- 즉,
MessageBox() 함수는 파라미터로 입력된 주소의 문자열을 출력하고 있음
- 따라서 원본 문자열보다 긴 문자열로 패치해야 하는 경우, 다른 주소에 새로운 문자열을 저장하고
MessageBox()에 넘겨주는 주소를 변경하면 됨
- 단, 메모리 어느 영역에 문자열을 쓸지 정하는게 중요
-
(지금은 간단한 실습이므로) 임의로 프로그램에서 사용되지 않는 NULL padding 영역에 새로운 문자 패치

-
[Space] 단축키 이용해서 main() 함수의 401007 주소에서 MessageBox() 함수에 넘겨줄 새로운 버퍼 주소 입력

-
[F9]로 실행해보면 변경된 문자열이 출력되는 것을 확인 가능

2.6. 마무리



