문제
04_Quiz.exe 를 실행시키면 아래와 같은 그림이 나온다.
문제 풀이
위의 어셈블리 코드를 분석하면 아래와 같다.
00401000 /$ 55 PUSH EBP
00401001 |. 8BEC MOV EBP, ESP
//여기까지 스택 프레임 설정
00401003 |. 83EC 20 SUB ESP, 20
// 메모리를 32Byte 만큼 사용 예정
00401006 |. A1 30604000 MOV EAX, DWORD PTR DS:[406030]
// 함수 외부의 Data Segment 주소 406030에서 EAX로 복사 (Infi 문자열)
0040100B |. 8945 E0 MOV DWORD PTR SS:[EBP-20], EAX
// EAX 값 스택에 저장
0040100E |. 8B0D 34604000 MOV ECX, DWORD PTR DS:[406034]
// 함수 외부의 Data Segment 주소 406034에서 ECX로 복사 (scap 문자열)
00401014 |. 894D E4 MOV DWORD PTR SS:[EBP-1C], ECX
// ECX 값 스택에 저장
00401017 |. 8A15 38604000 MOV DL, BYTE PTR DS:[406038]
// 함수 외부의 Data Segment 주소 406038에서 1바이트 DL로 복사 (00)
0040101D |. 8855 E8 MOV BYTE PTR SS:[EBP-18], DL
// DL 값 스택에 저장 (문자열 00으로 종료시킨다.)
00401020 |. 33C0 XOR EAX, EAX
// XOR EAX, EAX 동일할 시, 0
00401022 |. 8845 E9 MOV BYTE PTR SS:[EBP-17], AL
// AL 값(0) 스택에 저장 (문자열 종료 이 후, 나머지 값 0으로 초기화)
00401025 |. 8D4D E0 LEA ECX, DWORD PTR SS:[EBP-20]
// LEA로 [EBP-20] 주소 값 ECX에 복사
00401028 |. 51 PUSH ECX
// 주소 값 ECX 매개변수로 PUSH
00401029 |. 68 3C604000 PUSH 01_04_03.0040603C ; ASCII "Hello, %s"
// ASCII "Hello, %s" 함수 매개변수로 PUSH
0040102E |. 8D55 EC LEA EDX, DWORD PTR SS:[EBP-14]
// LEA로 [EBP-14] 주소 값 EDX에 복사. (20바이트)
00401031 |. 52 PUSH EDX; ntdll.KiFastSystemCallRet
// 주소 값 EDX 매개변수로 PUSH
00401032 |. E8 1F000000 CALL 01_04_03.00401056
// sprintf( ) 함수 호출
00401037 |. 83C4 0C ADD ESP, 0C
// 매겨변수 스택 초기화
// 아래는 MessageBox( ) 함수 호출
0040103A |. 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
0040103C |. 68 48604000 PUSH 01_04_03.00406048 ; |Title = "AegisOne"
00401041 |. 68 54604000 PUSH 01_04_03.00406054 ; |Text = "Take count of variable"
00401046 |. 6A 00 PUSH 0 ; |hOwner = NULL
00401048 |. FF15 A8504000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>] ; \MessageBoxA
0040104E |. 33C0 XOR EAX, EAX
00401050 |. 8BE5 MOV ESP, EBP
00401052 |. 5D POP EBP ; kernel32.7C816D4F
00401053 \. C2 1000 RETN 10
위 코드를 해석해서 C언어 코드 복원을 시행한다.
이번 문제에서 필자는 어려움을 많이 겪었다. 우선 DS가 문제였다.
DS를 끌고 오기위해서는 함수내부에서가 아닌 외부 전역변수에서 가져와야 한다는 생각으로 WinMain 함수 밖에 정의했다.
우선 스택에 사용할 바이트도 기존 문제와 차이가 많이 나는 상태이다.
또한, 우측 00406044 / 00407038 을 보면 강제적으로 문자열을 대입해서 넣은 것이다.
이때의 C언어 코드는 아래와 같다.
#include <windows.h>
#include <stdio.h>
char * str1 = "Infi";
char * str2 = "scap";
unsigned char *ptr3 = (unsigned char *) 0x407038;
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
int address1 = (int)str1;
int address2 = (int)str2;
unsigned char n3 = *ptr3;
char buffer[100];
sprintf(buffer, "Hello, %s", address1);
// 메시지 박스를 표시합니다.
MessageBox(NULL, "Take count of variable??", "AegisOne", MB_OK);
return 0;
}
딱봐도 틀린 코드일 것 같았다.
아래는 해답을 보고 정리한 코드이다.
#include <windows.h>
#include <stdio.h>
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
char str1[20];
char str2[10] = "Infiscap";
sprintf(str1, "Hello, %s",str2);
MessageBox(NULL, "Take count of variable", "AegisOne", MB_OK);
return 0;
}
여기서 DS를 가져오는데 리터럴 문자열의 경우 Static으로 저장되어, Data Segment에 저장됨을 생각하지 못했다.
또한, str2[10]이기 때문에 "infi" "scap" "\0" "00" 으로 나뉘어져 들어간다고 생각하면 편하다.
"infi" "scap" 은 DWORD로 넘겨 사용하며, 문자열 종료인 "\0"은 1바이트 이므로 DL로 받아 처리한다.
마지막으로 "infiscap\0" 까지 처리하면 마지막 str2[ ] 배열의 10번째가 누락되므로, 문자열이 종료되었으니 초기화를 해주어야한다.(dump를 보면 보통 문자열 종료 이 후, 00 00 00 00 이런 식으로 초기화되어 있는 코드를 본적이 있을 것이다.)
마지막 나머지 값 초기화에 대해서는 10바이트 중 9바이트를 처리했으므로 1바이트 처리를 위한 AL을 사용해 진행한다.
빨간색은 매개변수이다.
C언어의 역순으로 되어있으며, (str2[10], "Hello, %s", "infiscap") 이다.
초록색은 매개변수를 등록하기 전 스택에 등록해놓은 값이다.
리틀 엔디언으로 역순으로 등록되어 있지만 12FF14 주소에 있는 값은 "Infi", 12FF18 주소에 있는 값은 "scap"이다.
12FF1C는 초기화 코드이고, 12FF20이 str1[20]을 의미한다.
고로, 마지막 매개변수 값이 2가지를 받은 값이므로, "Hello, Infiscap" 로 저장된다.
ADD ESP, 0C
0C로 12바이트를 삭제한다. 이유는 위에서 본 것과 같이 매개변수 3개를 삭제해야하므로 12바이트를 지워준다.
이후 MessageBox( ) 함수가 시작된다.
'reversing > 리버싱 코드 복원' 카테고리의 다른 글
01.Variable_Quiz_06 (0) | 2025.01.29 |
---|---|
01.Variable_Quiz_05 (0) | 2025.01.29 |
01.Variable_Quiz_03 (0) | 2025.01.08 |
01.Variable_Quiz_02 (0) | 2025.01.08 |
01.Variable_Quiz_01 (0) | 2025.01.08 |