

위의 Patch.exe를 실행한 화면을 보면 플래그 부분이 볼 수 없게 칠해져 있음을 확인할 수 있습니다.
Patch 문제는 WinAPI를 이용해 만들어진 GUI 프로그램이기 때문에 main함수 대신 WinMain 함수를 찾아야 합니다. 그럼 IDA로 WinMain 함수를 디컴파일 해보겠습니다.
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
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 = 0LL;
v11.hInstance = hInstance;
v11.hIcon = LoadIconW(hInstance, (LPCWSTR)0x6B);
CursorW = LoadCursorW(0LL, (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, 0LL, 0LL, hInstance, 0LL);
v8 = Window;
if ( Window )
{
hWnd = Window;
dword_140007920 = 600;
dword_140007924 = 200;
GdiplusStartup(&unk_1400078A0, &dword_1400078A8, 0LL);
ShowWindow(v8, nShowCmd);
UpdateWindow(v8);
AcceleratorsW = LoadAcceleratorsW(hInstance, (LPCWSTR)0x6D);
while ( GetMessageW(&Msg, 0LL, 0, 0) )
{
if ( !TranslateAcceleratorW(Msg.hwnd, AcceleratorsW, &Msg) )
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
LODWORD(Window) = Msg.wParam;
}
return (int)Window;
}
여기서 살펴봐야 할 부분은 윈도우의 메시지 콜백을 설정하는 부분인 14 라인(v11.lpfnWndProc = (WNDPROC)sub_1400032F0;) 입니다.
보편적으로 메시지 콜백함수에서 윈도우의 동작이 정의되어 있기 때문에 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 0LL;
case 0xFu:
qword_140007910 = (__int64)BeginPaint(hWnd, &Paint);
v5 = (_QWORD *)GdipAlloc(16LL);
if ( v5 )
{
*v5 = 0LL;
v5[1] = 0LL;
v7 = 0LL;
*((_DWORD *)v5 + 2) = GdipCreateFromHDC(qword_140007910, &v7);
*v5 = v7;
}
else
{
v5 = 0LL;
}
qword_140007918 = (__int64)v5;
sub_140002C40();
v6 = qword_140007918;
if ( qword_140007918 )
{
GdipDeleteGraphics(*(_QWORD *)qword_140007918);
GdipFree(v6);
}
EndPaint(hWnd, &Paint);
return 0LL;
case 0x202u:
InvalidateRect(hWnd, 0LL, 1);
UpdateWindow(hWnd);
return 0LL;
default:
return DefWindowProcW(a1, a2, a3, a4);
}
}
switch case 구문 내에 총 3개의 케이스가 존재하는데, 그 중 0xF에 해당하는 케이스에서 Paint와 관련된 역할을 합니다. BeginPaint와 EndPaint 사이에서 flag를 출력해 줄 것이기 때문에 사이에 있는 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, 40LL, v28, 4278190080LL);
sub_140001C80(v27, 80LL, v29, 4278190080LL);
sub_140002640(v27, v30, v31, 4278190080LL);
sub_1400020F0(v27, v32, v33, 4278190080LL);
sub_140002390(v27, v34, v35, 4278190080LL);
sub_140001240(v27, v36, v37, 4278190080LL);
sub_140001F20(v27, v38, v39, 4278190080LL);
sub_140001560(v27, v40, v41, 4278190080LL);
sub_140001C80(v27, 360LL, v42, 4278190080LL);
sub_1400019D0(v27, v43, v44, 4278190080LL);
sub_1400017A0(v27, 440LL, v45, 4278190080LL);
sub_140002870(v27, v46, v47, 4278190080LL);
return 0;
}
51번째 줄부터 sub_140002B80 함수가 반복되고 이후 각각 다른 주소의 함수들이 반복됩니다. 어떻게 작동되는지 확인하기 위해 51번째 줄부터 동적 분석을 해보겠습니다.
51번째 줄에 f2를 눌러 브레이크 포인트를 건 후 한 줄씩 실행해 보겠습니다. 그럼 sub_140002B80 함수가 실행이 될 때 마다 줄이 한 줄씩 생기는 것을 확인할 수 있습니다. 아래는 sub_140002B80 함수를 한 번 실행했을 때의 사진입니다.

그럼 이제 sub_140002B80 함수를 모두 실행해 보겠습니다. 아래는 그 결과입니다.

플래그를 가리는 부분이 그려졌습니다. 이후의 코드들도 실행해 보겠습니다.

플래그가 그려졌음을 확인할 수 있습니다.
플래그를 확인하기 위해서는 위의 플래그를 가리는 부분을 그리는 코드를 삭제하고 프로그램을 실행하면 됩니다.
함수를 패치하기 위해서는 g키를 눌러 선을 그리는 함수인 sub_140002B80으로 이동한 후 첫 줄을 Edit - Patch Program - Assemble.. 메뉴를 클릭해 ret으로 변경해 주면 더 이상 sub_140002B80 함수가 선을 그리지 않습니다.

이제 Edit - Patch Program - Apply patches to input file... 메뉴를 클릭해 패치를 적용한 후 프로그램을 다시 실행하면 플래그를 얻을 수 있습니다.
