본문 바로가기
게임해킹/리버싱 핵심원리(나뭇잎책)

8장 abex' crackme #2 분석

by HHack 2024. 10. 30.
반응형

8.1 abex' crackme #2 실행

일단 프로그램을 실행시켜 보겠습니다.

그림 8.1 실행화면
그림 8.2 Wrong Serial 메시지 박스

실행시켜 보면 시리얼 키를 알아내는 프로그램인걸 확인할 수 있습니다. Name을 따로 입력받는 걸로 봐서 Serial 값을 생성할 때 Name 문자열이 사용될 것으로 추측됩니다. 잘못된 값을 입력하면 "Wrong serial!" 메시지 박스가 출력되는걸 확인할 수 있습니다.

8.2 Visual Basic 파일 특징

8.2.1 VB 전용 엔진

VB 파일은 MSVBVM60.dll(Microsoft Visual Basic Virtual Machine 6.0)이라는 VB 전용 엔진을 사용합니다. VB엔진은 메시지 박스를 출력하고 싶을 때 VB 소스코드에서 MsgBox() 함수를 사용합니다. VB 컴파일러는 실제로 MSVBVM60.dll!rctMsgBox() 함수가 호출되도록 만들고, 이 함수 내부에서 Win32 API인 user32.dll!MessageBoxW() 함수를 호출해주는 방식으로 동작합니다.

8.2.2 N(Native) Code, P(Pseudo) code

  1. N code
    • Native Code의 약자로, VB N-코드라고 불립니다.
    • N-코드는 VB 코드를 기계어로 직접 컴파일하는 방식으로, 기계가 직접 이해하고 실행할 수 있는 바이너리 코드로 변환됩니다.
    • 이 방식은 P-코드보다 실행 속도가 빠르고 효율적이기 때문에 성능을 우선시하는 경우 유리합니다.
    • N-코드는 P-코드보다 크기가 커질 수 있으며, 특정 플랫폼에 종속될 가능성이 있습니다.
    • VB6부터 N-코드 컴파일 옵션이 제공되어 개발자가 P-코드와 N-코드 중 선택할 수 있게 되었습니다.
  2. P code
    • Pseudo Code의 약자로 VB P-코드라고도 불립니다.
    • 중간 언어(intermediate language)로 컴파일되는 방식으로, VB 소스 코드를 즉시 기계어로 변환하지 않고, 기계어와 유사한 P-코드로 변환합니다.
    • P-코드는 가상 머신(VB 런타임 엔진)에 의해 실행되며, 실행 시에 인터프리터가 P-코드를 읽어 실행합니다.
    • P-코드는 전체 프로그램의 최종 바이너리를 작게 만들고, 코드가 직접 실행되지 않기 때문에 높은 이식성을 제공합니다. 그러나, 인터프리팅 방식이기 때문에 성능 면에서 느릴 수 있습니다.
    • 일반적으로 VB6 이전 버전에서 많이 사용되었습니다.

※ 인터프리터 언어 : 프로그램의 코드를 한 줄씩 읽고 실행하는 방식의 언어. 소스 코드를 사전에 컴파일하여 기계어로 변환하지 않고, 실행 중에 한 줄씩 해석하여 곧바로 실행합니다. 이식성이 좋습니다.

ex) 파이썬, 자바스크립트, 루비 등

 특성  P 코드  N 코드
 컴파일 대상  중간 언어 (P-코드)  기계어 (Native Code)
 실행 방식  VB 가상 머신이 인터프리팅하여 실행  직접 실행
 성능  상대적으로 느림  빠름
 코드 크기  상대적으로 작음  상대적으로 큼
 플랫폼 종속성  낮음  높음 (특정 플랫폼용 기계어)
 VB 버전  VB6 이전 주로 사용  VB6 부터 선택 가능

8.2.3 Event Handler

VB는 주로 GUI 프로그래밍을 할 때 사용되며, IDE 인터페이스 자체도 GUI 프로그래밍에 최적화 되어 있습니다. 즉, VB 프로그램은 Windows 운영체제의 Event Driven 방식으로 동작하기 때문에 main() 혹은 WinMain()에 사용자 코드가 존재하는 것이 아니라, 각 Event Handler에 사용자 코드가 존재합니다.

Event Driven 방식

프로그램의 흐름을 이벤트(event)에 의해 결정되는 프로그래밍 패러다임입니다. 여기서 이벤트는 사용자의 동작(버튼 클릭, 키보드 입력 등)이나 시스템에서 발생하는 특정 상황(네트워크 데이터 수신, 타이머 만료 등) 같은 외부 및 내부 트리거를 말합니다.

Event Driven 방식의 주요 개념

  1. 이벤트(event)
    • 사용자가 버튼을 클릭하거나, 창을 닫거나, 마우스를 움직이는 것 등과 같은 행동.
    • 시스템 자체에서 발생하는 이벤트, 예를 들어, 네트워크 연결 상태 변화나 데이터 수신 등.
  2. 이벤트 핸들러(event handler)
    • 특정 이벤트가 발생했을 때 실행되는 함수나 코드 블록.
    • 각 이벤트에 반응하기 위해 미리 정의된 동작을 수행합니다.
  3. 이벤트 루프(event loop)
    • 프로그램은 이벤트 루프를 통해 끊임없이 이벤트가 발생하는지 감시합니다.
    • 이벤트가 발생하면 해당 이벤트와 연결된 핸들러를 호출해 이벤트에 맞는 동작을 수행하게 합니다.

동작 원리

  • 대기 상태 : 프로그램은 이벤트 루프를 통해 모든 이벤트를 감지할 준비를 하고 대기 상태에 있습니다.
  • 이벤트 발생 : 사용자가 특정 동작을 하거나, 시스템에서 이벤트가 발생하면 이를 이벤트 큐에 추가합니다.
  • 이벤트 처리 : 이벤트 루프는 이벤트 큐에 있는 이벤트를 꺼내어 관련 이벤트 핸들러에 전달하고, 해당 핸들러는 그 이벤트에 맞는 작업을 실행합니다.
  • 반복 : 프로그램이 종료될 때까지 이 과정이 반복됩니다.

Event Driven 방식의 장점

  • 높은 응답성 : 사용자와 상호작용을 빠르게 처리하므로 반응형 응용 프로그램 제작에 적합합니다.
  • 효율성 : 필요한 작업만 수행하여 시스템 자원을 절약할 수 있습니다.
  • 모듈성 : 이벤트별로 핸들러가 따로 분리되어 있어 유지보수가 용이합니다.

8.2.4 undocumented 구조체

VB에서 사용되는 각종 정보들(Dialog, Control, Form, Module, Fuction 등)은 내부적으로 구조체 형식으로 파일에 저장됩니다. Undocumented 구조체는 공식 문서에 명확하게 기술되지 않았거나, 공개되지 않은 내부 구조체를 말합니다. 주로 시스템 프로그래밍, 리버스 엔지니어링, 커널 개발 등의 분야에서, 소프트웨어의 내부 동작을 이해하거나 특정 기능을 활용하기 위해 사용됩니다. Windows 운영 체제의 커널 구조체나 특정 라이브러리의 내부 구조체 등이 대표적인 예입니다.

주요 특징

  1. 공식 문서의 부재
    • 이들 구조체는 공개되지 않았기 때문에, 공식적인 문서나 사용법이 없습니다.
    • Microsoft Windows 커널 구조체, 일부 컴파일러 내부 구조체 등이 이에 해당됩니다.
  2. 리버스 엔지니어링을 통한 분석
    • 공식 문서가 없기 때문에 주로 리버스 엔지니어링을 통해 구조체를 파악합니다.
    • 이 과정에서는 디버거와 디스어셈블러를 사용해 메모리에서 구조체의 구성과 필드를 확인하고, 각각의 필드가 의미하는 바를 추론합니다.
  3. 유지보수의 어려움
    • 운영체제나 라이브러리가 업데이트될 때 구조체의 구성이나 필드가 변경될 수 있으므로, 이를 사용하는 프로그램이 쉽게 깨지거나 비호환성을 겪을 수 있습니다.
  4. 운영 체제나 애플리케이션에 따라 다름
    • undocumented 구조체는 종종 특정 운영 체제 버전이나 애플리케이션의 버전에 종속됩니다.

예시 : Windows의 Undocumented 구조체

Windows 커널과 관련한 undocumented 구조체의 예로는 EPROCESS와 ETHREAD가 있습니다. 이들 구조체는 프로세스나 스레드에 대한 상세한 정보를 포함하고 있지만, 공식적으로는 문서화되어 있지 않습니다.

  1. EPROCESS
    • EPROCESS는 Windows 커널의 프로세스에 대한 정보를 담고 있는 구조체입니다.
    • 이 구조체는 프로세스 ID, 부모 프로세스 정보, 메모리 정보 등의 프로세스 관리에 필요한 필드를 가지고 있습니다.
  2. ETHREAD
    • ETHREAD는 스레드에 관한 정보를 저장하는 구조체로, 스레드 ID, 상태, 우선순위 등의 필드를 포함하고 있습니다.

Undocumented 구조체 사용 시 주의사항

  1. 비공식 소스 : 비공식적으로 공개된 정보나 다른 개발자들의 분석 자료를 활용하되, 검증되지 않았다는 점을 항상 염두에 두어야 합니다.
  2. 호환성 : 구조체의 필드와 구조가 운영체제의 패치나 버전 업데이트 시 변경될 수 있기 때문에, 유지보수가 어려울 수 있습니다.
  3. 디버거와 도구 사용 : IDA Pro, WinDbg, x64dbg 같은 도구를 통해 구조체의 필드와 용도를 파악하고 주석을 추가하는 방식으로 연구합니다.

문서화되지 않은 구조체를 이해하기 위한 도구

  • 디버거 : x64dbg, WinDbg와 같은 도구를 통해 실행 중인 프로그램의 메모리에서 구조체의 필드를 파악.
  • 디스어셈블러 : IDA Pro, Ghidra를 통해 함수의 호출 규약과 파라미터를 분석.
  • 소스 코드 검색 : Windows 드라이버 샘플 코드나 오픈소스 프로젝트를 통해 undocumented 구조체의 일부 단서를 얻기도 합니다.

8.3 Start Debugging

x32dbg를 실행시켜 abex' crackme #2 파일의 디스어셈 코드를 살펴보겠습니다.

그림 8.3 Entry Point

위 그림을 보면 EP코드에서 처음 하는 일은 VB엔진의 메인함수 ThunRTMain를 호출하는 것입니다.

00401232	FF25 A0104000	JMP [<Ordinal#100>]			; JMP.&ThunRTMain
00401238	68 141E4000	PUSH 401E14				; ┌Arg1 = 401E14, EntryPoint
0040123D	E8 F0FFFFFF	CALL <JMP.&ThunRTMain>			; └ThunRTMain

책과 달리 x32dbg로 실습을 진행하여 책과 다르게 코드가 나타나고 있습니다.

  • EP주소는 401238입니다. 401238의 주소의 PUSH 401E14 명령에 의해 RT_MainStruct 구조체 구조(401E14)를 스택에 입력합니다.
  • 40123D 주소의 CALL <JMP.&ThunRTMain> 명령에 의해서 401232 주소의 JMP [<Ordinal#100>] 명령어가 실행됩니다.
  • 이 JMP 명령어에 의해서 VB 엔진의 메인 함수인 ThunRTMain() 함수로 갑니다.

8.3.1 간접 호출

40123D 주소의 CALL 401232 명령은 ThunRTMain() 함수 호출인데 조금 특이하게 직접 가는것이 아닌 401232 주소의 JMP 명령을 통해 가고 있습니다.

00401232	FF25 A0104000	JMP [<Ordinal#100>]			; JMP.&ThunRTMain

이 기법은 VC++, VB 컴파일러에서 많이 사용하는 간접 호출 기법입니다.

그림 8.4 간접호출

8.3.2 RT_MaintStruct 구조체

우리가 주목할 부분은 ThunRTMain() 함수의 파라미터인 RT_MainStruct 구조체입니다. 401E14 주소에 RT_MainStruct가 존재합니다.

그림 8.5 RT_MainStruct

MS에서 RT_MainStruct 구조를 공개하지 않았지만 실력있는 리버서들이 분석을 완료하고 인터넷에 공개하였습니다. RT_MainStruct 구조체의 멤버는 또 다른 구조체의 주소들이며 VB 엔진은 파라미터로 넘어온 RT_MainStruct 구조체를 가지고 프로그램의 실행에 필요한 모든 정보를 얻는 다는걸 알 수 있습니다.

8.3.3 ThunRTMain() 함수

그림 8.6 ThunRTMain() 코드 시작

위 코드는 ThunRTMain() 코드의 시작부분 입니다. 이 주소는 MSVBVM60.dll 모듈의 주소 영역입니다. 즉, 우리가 분석하는 프로그램의 코드가 아닌 VB 엔진의 코드입니다.

8.4 Crack Me 분석

8.4.1 문자열 검색

그림 8.7 All Referenced text strings

문자열 검색을 통해 메세지 박스에서 나온 문자열을 확인할 수 있습니다.

그림 8.8 403458 주소

해당 문자열의 주소로 가서 확인해보니 메시지 박스의 타이틀("Wrong serial!"), 내용("Nope, this serial is wrong!") 그리고 실제 메시지 박스 호출 코드(4034A6)까지 나타났습니다. 위 코드 전후로 사용자가 입력한 키와 문자열 비교를 통해서 각각 True, False를 판단하는 분기문이 있을 것입니다. 위 코드에서 조금만 위로 올리다 보면 해당 분기문을 포함한 코드가 나옵니다.

그림 8.9 조건 분기 명령어

403329 주소의 __vbaVarTstEq 함수를 호출해서 리턴 값(AX)을 비교(TEST 명령) 한 후 403332 주소의 조건 분기(JE 명령)에 의해서 참, 거짓 코드로 분기하게 됩니다.

 

※ 위 코드에서 사용된 어셈블리 명령어 설명

  1. TEST : 논리 비교(Logical Compare)
    • bit-wise logical 'AND' 연산과 동일
    • operand 값이 변경되지 않고 EFLAGS 레지스터만 변경됨
    • 두 operand 중 하나가 0이면 AND 연산 결과는 0 -> ZF = 1 로 세팅됨
  2. JE : 조건 분기(Jump if Equal)
    • ZF = 1 이면 점프

8.4.2 문자열 주소 찾기

403329 주소의 __vbaVarTstEq() 함수가 문자열 비교 함수라면 그 위에 있는 두 개의 PUSH 명령어는 비교 함수의 파라미터인 비교 문자열이 될 것입니다. 403329까지 디버깅을 진행합니다.

그림 8.10 403329까지 실행

00403321	8D55 BC			LEA EDX,[EBP-44]		; edx:EntryPoint
00403324	8D45 CC			LEA EAX,[EBP-34]
00403327	52			PUSH EDX			;┌Arg2 = edx:EntryPoint
00403328	50			PUSH EAX			;│Arg1
00403329	FF15 58104000		CALL [<vbaVarTstEq>]		;└vbaVarTstEq

00403321의 [EBP-44]는 스택 내의 주소를 의미하며, 함수에서 선언된 로컬 객체의 주소입니다. 문자열 Check를 누른 후 스택에 저장된 메모리 주소 19F284, 19F274를 확인해보면 아래와 같이 나옵니다.

그림 8.11 문자열 Check
그림 8.12 스택 메모리 - Hex

VB 문자열은 C++의 string 클래스와 마찬가지로 가변 길이 문자열 타입을 사용합니다. 따라서 그림 8.12에서 보는 바와 같이 바로 문자열이 나타나지 않고 16바이트 크기의 데이터가 나타납니다.71FF9C, 728CE4 위 두 주소는 내부에 동적으로 할당한 실제 문자열 버퍼 주소 입니다. 위의 19FE74의 값을 보기 형식을 바꿔 ASCII 코드로 확인해보면 확인해보면 사용자가 입력한 serial값과 실제 serial값이 나옵니다.

그림 8.13 스택 메모리 - Commnet
그림 8.14 Serial 문자열
그림 8.15 입력한 Serial 문자열

위에서 나온 실제 Serial값을 대입하면 그림 8.16과 같이 성공 메시지 박스가 출력됩니다.

그림 8.16 Congratulations 메세지 박스

8.4.3 Serial 생성 알고리즘

Serial 문자열 생성 알고리즘을 알아보겠습니다.

 

함수 시작 찾기

그림 8.9에 보이는 조건 분기 코드는 분명 어떤 함수에 속해있습니다. 아마 그 함수는 [Check] 버튼의 event handler일 것입니다. 이유는 Check 버튼을 선택했을 때 위 함수가 호출되었으며, 성공/실패 메세지 박스를 출력하는 사용자 코드를 포함하고 있기 때문입니다.

그림 8.17 Button Event Handler

위 쪽 방향으로 스크롤 하다보면 그림 8.17과 같은 코드를 만납니다. 00402ED0 주소 명령을 자세히 보겠습니다.

00402ED0	55	PUSH EBP
00402ED1	88EC	MOV EBP,ESP

위 코드는 함수가 시작할 때 나타나는 스택 프레임을 구성하는 전형적인 코드입니다. 따라서 이 위치가 함수의 시작임을 알 수 있고 바로 [Check] 버튼의 Event Handler입니다. 402ED0에 BP를 걸고 디버깅 해보겠습니다.

 

※ 참고

VB 파일에는 함수와 함수 사이에 NOP 명령어가 존재합니다.(402ECC ~ 402ECF)

8.4.4 코드 예측하기

Win32 API 프로그램이라면 아래와 비슷한 방식을 사용해 Serial키를 생성할 것입니다.

  • Name 문자열 읽기
  • 루프를 돌면서 문자를 암호화 하기

8.4.5 Name 문자열 읽는 코드

VB 엔진 API를 이용하여 사용자가 입력한 문자열을 가져올 것으로 예상되므로 CALL 명령어 위주로 디버깅 해보겠습니다. 디버깅을 진행하면 아래와 같은 4번째 CALL 명령어를 만나게 됩니다.

그림 8.18 SS [EBP-88]

  • 402F8E : Name 문자열을 저장할 스트링 객체
  • 402F98 : Name 문자열을 읽어온다.

402F8E에서 함수의 로컬 객체 [EBP-88] 주소를 함수의 파라미터로 전달(PUSH)하고 있습니다. 이 주소를 스택창에서 확인해보겠습니다. 우리가 찾는 Name 문자열이고 VB에서 문자열은 스트링 객체를 사용한다고 하였으니 메모리 보기 방식을 ASCII 방식으로 바꾼 후 402F98 주소의 CALL 명령까지 실행하면 그림 8.19와 같은 스트링 객체에 값이 저장됩니다.

그림 8.19 Name 문자열

[EBP-88] 주소에 Name 문자열이 저장되었습니다.

8.4.6 암호화 루프

00403102	MOV EBX,4						; EBX = 4 (loop count)
...
0040318B	CALL DWORD PTR DS:[<__vbaVarForInit>]
00403191	MOV EBX,DWORD PTR DS:[<Ordinal#632>]			; MSVBVM60.rtcMidCharVar
00403197	TEST EAX,EAX						; loop start
00403199	JE 4032A5
..
0040329A	CALL DWORD PTR DS:[<__vbaVarForNext>]			; vbaVarForNext
004032A0	JMP 403197						; loop end
004032A5	MOV EAX,DWORD PTR SS:[EBP+8]				; eax:TpCallbackIndependent+140

위 루프 동작 원리를 간단하게 설명하면 [<__vbaVarForInit>], [<__vbaVarForNext>]는 마치 linked list에서 next pointer를 이용해 다음 element를 참조하듯이 문자열 객체에서 한 글자씩 참조할 수 있게 해줍니다. 또한 loop count(EBX)를 세팅해 정해진 횟수만큼 루프를 돌게 합니다.

8.4.7 암호화 방법

입력한 Name 문자열은 "ReverseCore" 입니다.

004031F0	CALL DWORD PTR DS:[<__vbaStrVarVal>]
004031F6	PUSH EAX						; Name 문자열에서 가져온 하나의 문자(UNICODE)
									; 'R'

004031F7	CALL DWORD PTR DS:[<Ordinal#516>]			; rctAnsiValueBstr()
									; 유니코드 - ASCII 변환
                                                        		; 'R' = 52

00403221	LEA EDX,DWORD PTR SS:[EBP-54]
00403224	LEA EAX,DWORD PTR SS:[EBP-DC]				; eax:TpCallbackIndependent+140
0040322A	PUSH EDX						; Arg3 = edx:EntryPoint(52)
0040322B	LEA ECX,DWORD PTR SS:[EBP-9C]
00403231	PUSH EAX
00403232	PUSH ECX						; Arg1 = ecx:EntryPoint, dest
00403233	MOV DWORD PTR SS:[EBP-D4],64
0040323D	MOV DWORD PTR SS:[EBP-DC],EDI				; [EBP-DC] = EAX = 64

여기까지 디버깅하고 스택을 보겠습니다.

그림 8.20 스택에 저장된 ECX, EAX, EDX
그림 8.21 ECX

19F224 : 결과 저장용 버퍼

그림 8.22 EAX

19F1E4 : 암호화 키(64)

그림 8.23 EDX

19F26C : 52의 값은 사용자가 입력한 "ReverseCore" 문자열 중 첫 번째 문자 'R'을 의미한다.

 

아래 함수(403243)를 실행시키면 ECX 레지스터가 가리키는 버퍼에 암호화된 값이 저장됩니다.

00403243	CALL DWORD PTR DS:[<__vbaVarAdd>]	; vbaVarAdd : 52 + 64 = B6

그림 8.24 위 함수 실행 후 ECX

19F224 : B6은 원본값 52('R')에 암호화키 64를 더해서 생성한 값이며, Serial의 첫 두 글자를 의미합니다.

 

아래 코드에서 숫자 B6을 문자 'B6'로 변환합니다.

00403250	LEA EDX,DWORD PTR SS:[EBP-54]
00403253	LEA EAX,DWORD PTR SS:[EBP-9C]
00403259	PUSH EDX					; Arg2 = edx:EntryPoint(위 스택 주소 참조)
0040325A	PUSH EAX					; EAX = 19F21C
0040325B	CALL DWORD PTR DS:[<Ordinal#573>]		; rtcHexVarFormVar() : 유니코드로 변경!

위 함수를 실행시킨 후 EAX가 기리키는 버퍼를 보면 아래와 같이 "B6" 문자열이 생성되어 있습니다.

0019F21C  73470008    msvbvm60.ProcCallEngine+2FAB
0019F220  0019F264    
0019F224  005E4404    L"B6"
0019F228  0019F21C

실제 문자열 주소(005E4404)를 보면 숫자 B6가 유니코드 문자열 "B6"로 변경되었습니다.

그림 8.25 유니코드 문자열로 변경

끝으로 생성된 문자열을 이어 붙여주는 코드가 있습니다.

0040326C	LEA ECX,DWORD PTR SS:[EBP-44]
0040326F	LEA EDX,DWORD PTR SS:[EBP-54]
00403272	PUSH ECX					; Arg3 = ecx:EntryPoint, old
00403273	LEA EAX,DWORD PTR SS:[EBP-9C]			; [ebp-9C]:ProcCallEngine+2FAB
00403279	PUSH EDX					; Arg2 = edx:EntryPoint, add
0040327A	PUSH EAX					; Arg1, Serial
0040327B	CALL DWORD PTR DS:[<__vbaVarCat>]		; vbaVarCat() : 문자열 이어 붙이기
								; serial(EAX) = old(ECX) + add(EDX)

 

위 반복문의 마지막 루프를 실행하면 아래와 같이 문자열이 생성됩니다.

그림 8.25 Serial 문자열 생성

 

암호화 방법을 정리하면 아래와 같습니다.

  1. 주어진 Name 문자열을 앞에서부터 한 문자씩 읽기(총 4회)
  2. 문자를 숫자(ASCII 코드)로 변환
  3. 변환된 숫자에 64를 더함
  4. 숫자를 다시 문자로 변환
  5. 변환된 문자를 연결시킴

8.5 마무리 QnA

1. 그림 8.17가 Check 버튼의 Event Handler 라는 것을 어떻게 찾았나요?

A) 앞의 코드를 참고해서 "Wrong serial" 문자열을 타깃으로 정하고 이 문자열을 참조하는 코드를 찾는다. 그렇게 찾은 코드에서부터 스크롤을 위로 올려 Stack Frame 생성 부분(함수 시작)을 찾아냅니다.

 

2. 문자열 암호화 시키는 코드는 어떻게 찾았나요?

A) "Reverse Core" 문자열 주소를 알아낸 후 이 문자열을 access하는 코드를 찾아가는 와중에 암호화 코드를 발견하게 되었다. 그리고 한 줄씩 상세히 디버깅 하면서 알아낼 수 있었다.

 

3. Name을 읽는 위치가 StackFrame 구성 후 4번째 Call 이라는 것은 어떻게 알았나요?

A) 일단 Handler를 찾은 후 이 코드를 여러 번 반복해서 디버깅을 해보았습니다. 그러던 중 레지스터, 스택을 주의 깊게 보면서 코드를 디버깅하다 보면 아래와 같은 명령어들을 볼 수 있다.

00402F8E	LEA EDX, DWORD PTR SS:[EBP-88]
...
00402F98	CALL DWORD PTR DS:[ECX+A0]
...
00402FB6	MOV EAX, DWORD PTR SS:[EBP-88]

위 00402FB6 주소 명령에 의해 EAX 레지스터에 문자열 주소가 세팅됩니다. 따라서 [EBP-88] 변수가 스트링 객체라는 걸 유추할 수 있고, 00402F98 주소의 CALL 명령어에 의해서 값이 세팅된다는 것을 알 수 있습니다. 즉 반복적인 디버깅을 통해 알 수 있었다.

 

반응형

'게임해킹 > 리버싱 핵심원리(나뭇잎책)' 카테고리의 다른 글

10장 함수 호출 규약  (2) 2024.12.04
9장 Process Explorer - 최고의 작업 관리자  (0) 2024.11.26
7장 스택 프레임  (0) 2024.09.04
6장 abex' crackme #1 분석  (0) 2024.08.30
5장 스택  (1) 2024.08.29