본문 바로가기

Reversing/Introduction to reversing

abex crackme 1

프로그램 동작 방식

목적 : 프로그램을 실행했을 때 하드디스크가 CD-Rom으로 인식되도록 변경

 

Entry Point

Entry Point :

디버거를 통해 프로그램을 열면 실행되지 않고 멈추는 지점

OS가 사용자 프로그램에 최초로 제어를 넘기는 지점

프로그래머가 만든 실행 코드가 최초로 실행되는 지점

 

 

PE 구조의 모든 실행 파일은 Header에 Entry Point가 RVA(Relative Virtual Address)로 지정되어 있다.

프로그램은 로딩되면서 Base Address가 할당되고, 메모리에 Bass Address + RVA (베이스 주소와 상대주소가 더해진 위치)에 데이터가 저장된다.

 

Address of Entry Point의 Data 값 00001000 → 메모리에 저장될 때 사용될 상대주소가 1000

Image Base의 Data 값 0040000

⇒ Entry Point가 실제로 메모리에 저장되는 위치 = Bass Address + RVA = 00401000

 

 

Address 00401000(Entry Point)에서 00401066까지 모두 66byte의 크기가 저장되어 있음.

맨 마지막 주소는 004001061이고 그곳에서부터 모드 6byte의 코드가 저장 → 따라서 00401066까지 명령어가 저장되는 것임.

 

Step over (F8) & Step into (F7)

Step over
F8
프로그램을 한 줄씩 실행
Step into
F7
프로그램을 한 줄씩 실행 + CALL 주소를 실행하는 명령어에서 주소에 있는 함수 안으로 들어감

 

 

  • Step Over를 사용해서 프로그램을 실행하다가,
  • 0040100E에서 Step into를 사용해 함수 안으로 들어간다.

 

  • IAT(Import Address Table)에 있는 API를 호출하는 경우 JMP DWORD PRT DS: [함수주소]와 같은 방식이 사용됨

 

IAT : PE 파일에서 사용하는 외부 DLL에서 제공하는 함수 주소를 모아놓은 자료구조

FF25 [함수주소] JMP DWORD PTR DS:[함수주소] : IAT 테이블에 들어있는 함수 주소를 호출할 때 사용되는 명령어. 손상된 IAT 파일을 복구할 때 코드 영역에서 실제로 어떻게 호출되고 있는지 확인하기 위해 위와 같은 패턴을 검색한다.

 

일반적으로 USER32, KERNEL32와 같은 시스템 DLL은 Step into를 사용해서 분석하지 않는다.

대부분은 사용자가 만든 모듈에서 분석에 필요한 모든 정보를 얻을 수 있기 때문.

 

Breakpoint (F2) & 프로그램 실행 (F9)

Breakpoint
F2
Breakpoint가 설정된 부분을 실행하면 인터럽트가 발생하고 프로그램 실행이 잠시 범춤
프로그램 실행
F9
 

 

NumLock + & NumLock - 기능 사용하기

NumLock +
+
다시 분석 계속
NumLock -
-
전에 봤던 것
  • 프로그램의 실행 시점을 되돌리는 것이 아니라 Step Over & Step Into를 사용해서 분석했던 로그를 다시 되돌아가는 것

 

프로그램 다시 시작 Ctrl + F12

  • 디버거는 한 방향으로만 실행
  • 이전 명령어를 다시 실행하고자 한다면, 프로그램을 다시 실행해야함.

 

MessageBox() 함수 구조

  • 0040100E에서 USER32.dll에 있는 MessageBoxA()함수를 호출하고 있음.
int MessageBoxA(
  [in, optional] HWND   hWnd,
  [in, optional] LPCSTR lpText,
  [in, optional] LPCSTR lpCaption,
  [in]           UINT   uType
);
  • PUSH OFFSET 00402000과 PUSH OFFSET 00402012는 메모리에 있는 데이터를 스택에 넣는 역할
  • [Follow in Dump] → [Immediate constant] 메뉴를 통해 메모리에 있는 데이터를 확인할 수 있다.

 

  • 메모리에 문자열을 읽을 때는 NULL을 의미하는 00이 나오기 전까지 연속적으로 읽는다.

 

Subroutine & Stack Frame

  • Subroutine은 전형적인 Stack Frame 구조로 되어있다.

[ Code ]

① 0040100E에서 Step into로 Subroutine 안으로 들어간다.

② Subroutine 안으로 들어갈 때 OS는 복귀주소인 00401013을 스택에 백업해 놓는다.

③ 7565FDE8에 있는 PUSH EBP는 Subroutine 안으로 들어와서 두 번째로 실행되는 명령어

이전 루틴에서 사용하고 Stack Base (RBP/EBP 레지스터 값)을 스택에 백업

이전 루틴으로 복귀할 때 이 값을 EBP 레지스터로 복구한다.

④ 7567FDE9에 있는 MOV EBP, ESP현재 스택(7567FDE9을 실행하는 시점)의 최상위 주소를 EBP 레지스터에 입력하는 명령어

앞으로 Subroutine에서 스택을 참조할 때는 EBP 레지스터 기준으로 값을 가져온다.

⑤ 7565FDEB ~ 7565FDF6는 또 다른 Subroutine을 호출하기 위해 인수를 설정하는 부분

값을 가져오기 위해서 EBP 레지스터를 기준으로 데이터가 있는 주소를 지정

⑥ 7565FDFF에 RETN 10은 Subroutine이 종료하면 ESP 레지스터(복귀주소가 저장되어있는 주소를 저장하고 있음)가 가리키는 주소로 이동.

Subroutine이 종료된 시점에 ESP 레지스터는 복귀주소가 저장되어있는 주소인 0018FF78이 들어있다.

10은 복귀주소로 돌아가면서 반환해야하는 스택 영역의 크기 → 16byte

0018FF7C ~ 0018FF88까지 반환

복귀한 시점에는 스택의 최상위를 가리키는 ESP 레지스터에는 그 다음 주소인 0018FF8C가 들어가있게된다.

 

[Stack]

⑦ 0018FF7C ~ 0018FF88까지는 Subroutine을 호출하면서 PUSH 명령어를 통해 인자로 넣어준 값이 들어있다.

⑧ 0018FF78에는 복귀주소가 들어가 있음.

⑨ 0018FF74에는 이전 루틴에서 사용하고 있던 EBP 값이 백업되어있다.

서브루틴이 종료되면 이 값은 다시 EBP 레지스터 값이 된다.

⑩ 0018FF60부터 0018FF70까지는 또 다른 서브루틴을 호출하는 데 필요한 인수

 

프로그램 구조 분석

 

① GetDriveTypeA() 함수 호출

  • GetDriveTypeA()는 드라이브 종류를 정수 형태로 반환하는 함수
  • 함수의 반환값은 EAX에 저장
  • 함수 실행이 끝나면 EAX에 3이 저장됨.
  • 반환값 3 = HDD

INC ⇒ 레지스터 값을 1 증가, DEC ⇒ 레지스터 값을 1 감소

ESI ⇒ ESI + 1 + 1 +1

EAX ⇒ 3 - 1 - 1 = 1

 

③, ④ CMP ⇒ 두 레지스터 값이 같으면 Zero Flag를 1로 설정

EAX ≠ ESI ⇒ Zero Flag = 0

 

JE ⇒ 앞에 수행한 비교 구문의 결과가 같으면 ( Zero Flag가 1이면) 지정된 주소로 점프하고 다르면 다음행 실행

Zero Flag가 0기 때문에 지정된 주소로 점프하지 않는다.

 

⑥ 만약 Zero Flag가 1이라면 이 부분이 실행된다.

 

 

문제 해결

1. 프로그램 코드 변경을 통한 문제 해결

2. Zero Flag 변경을 통한 문제 해결

  • 프로그램을 중간까지 실행시키고 Zero Flag를 바꿔야함.



'Reversing > Introduction to reversing' 카테고리의 다른 글

abex crackme 5  (0) 2025.05.05
abex crackme 4  (0) 2025.05.05
abex crackme 3  (1) 2025.05.05
abex crackme 2  (0) 2025.05.05
Introduction to reversing - Chapter 01  (0) 2025.05.05