[2장] Hello World! 리버싱

beanii·2023년 9월 28일
0

Reversing Study

목록 보기
2/4
post-thumbnail

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 WindowCPU register 값을 실시간으로 표시하며 특정 register들은 수정도 가능
Dump Window프로세스에서 원하는 memory 주소 위치를 Hex와 ASCII/유니코드 값으로 표시하고 수정도 가능
Stack WindowESP 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) 문자열 버퍼를 직접 수정

  • 장점: 사용하기에 가장 간단함
  • 단점: 기존 문자열 버퍼 크기 이상의 문자 입력하기 어려움(제약 조건) -> 권장하지 않는 방법
  1. 덤프 창에서 [Ctrl+G] 단축키 이용해서 4092A0 주로로 이동

  2. [Ctrl+E] 단축키 이용해서 "Hello World!" 유니코드 문자열이 차지하는 영역을 새로운 문자열로 덮어씀

    • 유니코드 문자열은 2바이트 크기의 NULL로 끝나야 함
      • 유니코드 항목에서는 NULL을 입력할 수 없으므로 HEX 항목에서 입력
    • 원본 문자열보다 큰 문자열로 덮어쓸 때는 그 뒤의 데이터를 훼손하지 않도록 조심해야 함
  3. 다시 main() 함수(401000)로 들어가서 보면 명령어나 파라미터 주소는 그대로지만 전달되는 파라미터 내용이 바뀐 것을 확인할 수 있음

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

[파일로 저장하기]

  • 단순한 패치 작업은 임시적이어서 디버거가 종료되면(해당 실행파일 프로세스가 종료되면) 패치했던 내용 사라짐
  • 파일로 저장 방법
    1. dump 창에서 변경된 내용 선택해서 우클릭 - Copy to executable file 선택
    2. 새로 나타난 Hex 창에서 우클릭 - Save file 선택

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

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

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

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


2.6. 마무리

0개의 댓글