문제 : https://dreamhack.io/wargame/challenges/49/
플래그에 현재 선을 긋는 코드가 추가되어 있는 것으로 추정됨
Static Analysis
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
HCURSOR CursorW; // rax
HWND Window; // rax
HWND v8; // rbx
HACCEL AcceleratorsW; // rbx
WNDCLASSEXW v11; // [rsp+60h] [rbp-A8h] BYREF
struct tagMSG Msg; // [rsp+B0h] [rbp-58h] BYREF
LoadStringW(hInstance, 0x67u, &WindowName, 100);
LoadStringW(hInstance, 0x6Du, &ClassName, 100);
v11.cbSize = 80;
v11.style = 3;
v11.lpfnWndProc = (WNDPROC)sub_1400032F0;
*(_QWORD *)&v11.cbClsExtra = 0i64;
v11.hInstance = hInstance;
v11.hIcon = LoadIconW(hInstance, (LPCWSTR)0x6B);
CursorW = LoadCursorW(0i64, (LPCWSTR)0x7F00);
*(__m128i *)&v11.hbrBackground = _mm_load_si128((const __m128i *)&xmmword_1400053F0);
v11.hCursor = CursorW;
v11.lpszClassName = &ClassName;
v11.hIconSm = LoadIconW(hInstance, (LPCWSTR)0x6C);
RegisterClassExW(&v11);
qword_140007880 = (__int64)&unk_1400078A0;
Window = CreateWindowExW(0, &ClassName, &WindowName, 0xC80000u, 0x80000000, 0, 600, 200, 0i64, 0i64, hInstance, 0i64);
v8 = Window;
if ( Window )
{
hWnd = Window;
dword_140007920 = 600;
dword_140007924 = 200;
GdiplusStartup(&unk_1400078A0, &dword_1400078A8, 0i64);
ShowWindow(v8, nShowCmd);
UpdateWindow(v8);
AcceleratorsW = LoadAcceleratorsW(hInstance, (LPCWSTR)0x6D);
while ( GetMessageW(&Msg, 0i64, 0, 0) )
{
if ( !TranslateAcceleratorW(Msg.hwnd, AcceleratorsW, &Msg) )
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
LODWORD(Window) = Msg.wParam;
}
return (int)Window;
}
WinMain 함수에서 시작함
해당 부분을 보면, v11을 변수로 RegisterClassExW함수를 사용하는 것을 볼 수 있음
RegisterClassExW함수는 Win32API 함수로 윈도우 클래스를 등록하며, 인자로는 v11을 활용하므로 해당 부분에서 콜백 함수를 찾아야함
[ v11 구조체 ]
typedef struct tagWNDCLASSEXA {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXA, *PWNDCLASSEXA, *NPWNDCLASSEXA, *LPWNDCLASSEXA;
https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
v11 구조체의
WNDPROC lpfnWndProc;
부분은 윈도우 메세지의 콜백을 처리하는 부분으로 이 함수가 어떤 역할을 하는지에 대해 알아야한다.
즉, 윈도우 창을 띄우고 어떤 역할을 하는지는 해당 콜백함수가 결정하는 것
sub_1400032F0가 콜백함수이므로 살펴보도록한다.
LRESULT __fastcall sub_1400032F0(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
_QWORD *v5; // rbx
__int64 v6; // rbx
__int64 v7; // [rsp+20h] [rbp-18h] BYREF
switch ( a2 )
{
case 2u:
PostQuitMessage(0);
return 0i64;
case 0xFu:
qword_140007910 = (__int64)BeginPaint(hWnd, &Paint);
v5 = (_QWORD *)GdipAlloc(16i64);
if ( v5 )
{
*v5 = 0i64;
v5[1] = 0i64;
v7 = 0i64;
*((_DWORD *)v5 + 2) = GdipCreateFromHDC(qword_140007910, &v7);
*v5 = v7;
}
else
{
v5 = 0i64;
}
qword_140007918 = (__int64)v5;
sub_140002C40();
v6 = qword_140007918;
if ( qword_140007918 )
{
GdipDeleteGraphics(*(_QWORD *)qword_140007918);
GdipFree(v6);
}
EndPaint(hWnd, &Paint);
return 0i64;
case 0x202u:
InvalidateRect(hWnd, 0i64, 1);
UpdateWindow(hWnd);
return 0i64;
default:
return DefWindowProcW(a1, a2, a3, a4);
}
}
Begin Paint와 EndPaint 사이에 현재 플래그를 출력하는 부분이 존재할 것이므로, 해당 부분 사이를 살펴본다.
Switch 문에서 0xF(unsigned) case임을 확인할 수 있음
현재 이 코드에서 그림을 그린다고 할 수 있는 부분을 확인하지 못했기 때문에 sub_140002C40()함수를 확인한다.
char __fastcall sub_140002C40(__int64 a1, int a2)
{
int v2; // ebx
int v3; // edx
int v4; // edx
int v5; // edx
int v6; // edx
int v7; // edx
int v8; // edx
int v9; // edx
int v10; // edx
int v11; // edx
int v12; // edx
int v13; // edx
int v14; // edx
int v15; // edx
int v16; // edx
int v17; // edx
int v18; // edx
int v19; // edx
int v20; // edx
int v21; // edx
int v22; // edx
int v23; // edx
int v24; // edx
int v25; // edx
int v26; // edx
__int64 v27; // rbx
__int64 v28; // r8
__int64 v29; // r8
__int64 v30; // rdx
__int64 v31; // r8
__int64 v32; // rdx
__int64 v33; // r8
__int64 v34; // rdx
__int64 v35; // r8
__int64 v36; // rdx
__int64 v37; // r8
__int64 v38; // rdx
__int64 v39; // r8
__int64 v40; // rdx
__int64 v41; // r8
__int64 v42; // r8
__int64 v43; // rdx
__int64 v44; // r8
__int64 v45; // r8
__int64 v46; // rdx
__int64 v47; // r8
v2 = qword_140007880;
sub_140002B80(qword_140007880, a2, 30, 470, 80, -16777216);
sub_140002B80(v2, v3, 35, 470, 75, -16777216);
sub_140002B80(v2, v4, 40, 470, 70, -16777216);
sub_140002B80(v2, v5, 45, 470, 65, -16777216);
sub_140002B80(v2, v6, 50, 470, 60, -16777216);
sub_140002B80(v2, v7, 55, 470, 55, -16777216);
sub_140002B80(v2, v8, 60, 470, 50, -16777216);
sub_140002B80(v2, v9, 65, 470, 45, -16777216);
sub_140002B80(v2, v10, 70, 470, 40, -16777216);
sub_140002B80(v2, v11, 75, 470, 75, -16777216);
sub_140002B80(v2, v12, 80, 400, 60, -16777216);
sub_140002B80(v2, v13, 30, 470, 90, -16777216);
sub_140002B80(v2, v14, 35, 470, 30, -16777216);
sub_140002B80(v2, v15, 40, 470, 35, -16777216);
sub_140002B80(v2, v16, 45, 470, 50, -16777216);
sub_140002B80(v2, v17, 50, 470, 40, -16777216);
sub_140002B80(v2, v18, 55, 400, 90, -16777216);
sub_140002B80(v2, v19, 60, 470, 60, -16777216);
sub_140002B80(v2, v20, 65, 470, 30, -16777216);
sub_140002B80(v2, v21, 70, 470, 80, -16777216);
sub_140002B80(v2, v22, 75, 470, 70, -16777216);
sub_140002B80(v2, v23, 80, 470, 60, -16777216);
sub_140002B80(v2, v24, 80, 470, 80, -16777216);
sub_140002B80(v2, v25, 80, 470, 70, -16777216);
sub_140002B80(v2, v26, 90, 470, 90, -16777216);
v27 = qword_140007880;
sub_1400017A0(qword_140007880, 40i64, v28, 4278190080i64);
sub_140001C80(v27, 80i64, v29, 4278190080i64);
sub_140002640(v27, v30, v31, 4278190080i64);
sub_1400020F0(v27, v32, v33, 4278190080i64);
sub_140002390(v27, v34, v35, 4278190080i64);
sub_140001240(v27, v36, v37, 4278190080i64);
sub_140001F20(v27, v38, v39, 4278190080i64);
sub_140001560(v27, v40, v41, 4278190080i64);
sub_140001C80(v27, 360i64, v42, 4278190080i64);
sub_1400019D0(v27, v43, v44, 4278190080i64);
sub_1400017A0(v27, 440i64, v45, 4278190080i64);
sub_140002870(v27, v46, v47, 4278190080i64);
return 0;
}
자세한 함수를 살펴보지는 않았지만, 반복적으로 시행하는 것으로 보아 반복적으로 그림을 그리는 작업임을 유추할 수 있음
sub_140002B80 이 함수는 같은 작업을 반복하고, 이 아래에 함수들은 모두 다른 작업을 하는데, 우리가 없애야하는 부분은 선을 반복적으로 그어놓은 곳이므로 해당 함수를 살펴봐야 할 것임
__int64 __fastcall sub_140002B80(__int64 a1, __int64 a2, unsigned int a3, int a4, int a5, unsigned int a6)
{
int v9; // eax
__int64 v10; // rsi
int v11; // eax
__int64 v12; // rbx
int v13; // eax
__int64 v15; // [rsp+30h] [rbp-38h] BYREF
__int64 v16; // [rsp+38h] [rbp-30h]
v16 = 0i64;
v15 = 0i64;
v9 = GdipCreatePen1(a6, a2, 0i64, &v15);
v10 = *(_QWORD *)(a1 + 120);
LODWORD(v16) = v9;
v11 = GdipDrawLineI(*(_QWORD *)v10, v15, 150i64, a3, a4, a5);
if ( v11 )
*(_DWORD *)(v10 + 8) = v11;
v12 = *(_QWORD *)(a1 + 120);
v13 = GdipSetSmoothingMode(*(_QWORD *)v12, 4i64);
if ( v13 )
*(_DWORD *)(v12 + 8) = v13;
return GdipDeletePen(v15);
}
GdipCreatePen1함수를 이용해 펜을 생성하고 GdipDrawLineI 함수를 사용해 선을 하나 그리는 함수
아마 저 위에 글자가 가려지는 부분은, 이 코드를 반복 적용하여 생기는 것으로 추측됨
따라서, 글자를 쓰는 부분보다 가려지는 부분이 먼저 생성된다는 것
Dynamic Analysis
WinDbg를 이용하여 동적분석 하기로 하였다IDA랑 WinDbg 간의 주소 베이스만 파악하여 그림이 그려지기 전에 bp를 걸었다.
해당 부분이 선을 그리는 부분임을 확인할 수 있음
Binary Patch
선을 그리는 sub_140002B80함수가 아무런 일도 하지 않고 바로 return 하도록 패치하여 플래그를 얻을 수 있게 하고자 함
IDA에서 Edit → Patch Program → Assemble을 통해 패치를 진행한다.
맨 처음에 있는 어셈블리를 바로 ret로 변경해줌
이를 apply 시켜준 후 exe 파일을 실행시키면, 해당 함수가 실행되지 않은 파일을 얻을 수 있음
피드백
정석적인 풀이방법이였다
패치하지 않고도 풀 수 있었는데, 그냥 저 함수를 실행하는 부분을 아예 건너 뛰어서 할 수 있었다.
rip를 아예 선 그리는 부분에서 옮겨주거나, 저 함수만 건너뛰어서 실행시키면 됨
생각보다 IDA에서 생성해주는 함수 이름이 정확하니 함수 이름을 보고 추론해서 분석하는 것도 중요한 능력
Reference >
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclassexw
'Hacking > Wargame' 카테고리의 다른 글
[Pwnable] Return to Library (2) | 2024.09.16 |
---|---|
[Pwnable] Return to Shellcode (4) | 2024.08.24 |
[Cryptography] Textbook-CBC (4) | 2024.08.03 |
[Reversing] Reversing Basic Challenge #1 (0) | 2024.07.28 |
[Reversing] Reversing Basic Challenge #0 (2) | 2024.07.28 |