8 - Aardvark
Expect difficulty running this one. I suggest investigating why each error is occuring. Or not, whatever. You do you.
Ở challenge này ta có 1 file .exe, giải nén file này và chạy thử (windows 7 VM):
Dùng Detect it easy để nhận diện file:
Đến đây ta mở file bằng IDA để tìm xem nguyên nhân gây ra lỗi “socket failed” như trên hình là gì.
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
// truncated ...
// ...
v8 = socket(1, 1, 0);
v6 = v8;
if ( v8 == -1i64 )
{
MessageBoxA(0i64, "socket failed", "Error", 0x10u);
v9 = "Error creating Unix domain socket";
LABEL_16:
MessageBoxA(0i64, v9, "Error", 0x10u);
goto LABEL_17;
}
// truncated ...
}
Ta để ý dòng:
v8 = socket(1, 1, 0); // socket(AF_UNIX, SOCK_STREAM, 0);
Theo mình biết thì AF_UNIX chỉ xuất hiện trên các hệ điều hành UNIX, nhưng ở các bản cập nhật Windows gần đây, Microsoft đã thêm nó vào hệ điều hành Windows 10, xem ở đây.
Vì vậy mình đã tải bản Windows 10 version 1909 về để chạy lại file này, và nhận được kết quả:
Ta quay lại IDA, xem strings window để tìm dòng trên:
Dùng xref để đi tới hàm sub_140001B10
(hàm này sử dụng chuỗi “CoCreateInstance failed”).
__int64 __usercall sub_140001B10@<rax>(__int64 a1@<rcx>, unsigned int a2@<edx>, __int64 a3@<r8>, __int128 *_XMM0@<xmm0>)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v4 = 0;
v5 = a3;
ppv = 0i64;
v6 = a2;
v13 = 0i64;
v7 = a1;
if ( CoCreateInstance(&rclsid, 0i64, 4u, &riid, &ppv) )
{
MessageBoxA(0i64, "CoCreateInstance failed", "Error", 0x10u);
}
else if ( (*(*ppv + 96i64))(ppv, &v14) )
{
MessageBoxA(0i64, "GetDefaultDistribution failed", "Error", 0x10u);
}
else if ( (*(*ppv + 24i64))(ppv, &v14, 0i64, &unk_14001E028, &v13) )
{
MessageBoxA(0i64, "CreateInstance failed", "Error", 0x10u);
}
else
{
v11 = 0;
v10 = 0;
GetCurrentDirectoryW(0x105u, &Buffer);
v15 = 0i64;
v16 = 0i64;
v12 = *(*(*(__readgsqword(0x30u) + 96) + 32i64) + 16i64);
if ( (*(*v13 + 48i64))(v13, v7, v6, v5, 0, 0i64, &Buffer, 0i64, 0i64, 0, 0, &v15, &v12, 0, &v11, &v10) )
MessageBoxA(0i64, "CreateLxProcess failed", "Error", 0x10u);
else
v4 = 1;
}
if ( v13 )
(*(*v13 + 16i64))(v13);
if ( ppv )
(*(*ppv + 16i64))(ppv);
return v4;
}
Ở đây ta thấy có string “CreateLxProcess failed” nên ta thử tìm Github hàm này:
Sau một lúc xem qua các kết quả thì mình thấy:
Vậy khả năng cao file bị lỗi là do chưa cài đặt WSL, nên ta cài đặt WSL trên Windows 10 , rồi chạy lại file:
Ta được 1 game Tic-tac-toe, mà computer đã đi trước 1 nước ở ngay giữa, tức là gần như chúng ta chỉ có thể hòa hoặc thua, chơi thử 1 vài trận, điều ở trên được xác nhận.
Quay lại IDA để phân tích code.
Đầu tiên, chương trình tạo ra 1 socket(AF_UNIX, ...)
để lắng nghe:
v8 = socket(1, 1, 0);
v6 = v8;
if...
if ( bind(v8, &name, 110) == -1 )
v10 = "bind failed";
else
{
if ( listen(v6, 0x7FFFFFFF) != -1 )
goto LABEL_12;
v10 = "listen failed";
}
Tiếp theo chương trình thực hiện hàm sub_140012B0
, trong hàm này, nó lấy resource ra và ghi ra thư mục “%tmp%”.
__int64 sub_1400012B0()
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v11 = &v13;
v12 = 0i64;
NumberOfBytesWritten[0] = 0;
v0 = 0;
v1 = -1i64;
if ( !GetTempFileNameA(".", PrefixString, 0, &FileName) )
// ...
wsprintfA(&v13, "%s", &FileName);
*sub_140003268((__int64)&v13, '\\') = '/';
v1 = (__int64)CreateFileA(&FileName, 0x40000000u, 0, 0i64, 3u, 0x80u, 0i64); // GENERIC_WRITE
// ....
v3 = FindResourceA(0i64, (LPCSTR)0x12C, (LPCSTR)0x100);
v4 = v3;
// ...
v6 = SizeofResource(0i64, v3);
v7 = LoadResource(0i64, v4);
v5 = v7;
// ...
v8 = LockResource(v7);
if ( WriteFile((HANDLE)v1, v8, v6, NumberOfBytesWritten, 0i64) && NumberOfBytesWritten[0] == v6 )
{
CloseHandle((HANDLE)v1);
FreeResource(v5);
sub_140001930((__int64)&v13, 1u, (__int64)&v11); // <---- here
v0 = 1;
}
// ...
}
Tiếp theo chương trình nhảy vào hàm sub_140001930
, hàm này sẽ dựa vào số build của Windows để tiếp tục thực thi:
if ( GetVersionExA(&VersionInformation) )
{
if ( VersionInformation.dwBuildNumber >= 0x42EE )
{
if ( VersionInformation.dwBuildNumber == 17134 )
{
sub_140001AB0();
sub_140001B10(v3, v5, v4, 0i64);
return 0i64;
}
if ( VersionInformation.dwBuildNumber == 17763 )
{
sub_140001AB0();
sub_140001D60(v3, v5, v4);
return 0i64;
}
if ( VersionInformation.dwBuildNumber - 18362 <= 1 )
{
sub_140001AB0();
sub_140001FB0(v3, v5, v4);
return 0i64;
}
if ( VersionInformation.dwBuildNumber - 19041 <= 1 || VersionInformation.dwBuildNumber > 0x4A62 )
{
sub_140001AB0();
sub_1400021E0(v3, v5, v4);
return 0i64;
}
}
Máy mình là Windows 10 build 18363, nên mình sẽ tiếp tục đi vào hàm sub_140001FB0
:
__int64 __fastcall sub_140001FB0(__int64 a1, unsigned int a2, __int64 a3)
{
v3 = 0;
v4 = a3;
ppv = 0i64;
v5 = a2;
v6 = a1;
if ( CoCreateInstance(&rclsid, 0i64, 4u, &riid, &ppv) )
{
MessageBoxA(0i64, "CoCreateInstance failed", "Error", 0x10u);
}
else if ( (*(*ppv + 88i64))(ppv, &v18) )
{
MessageBoxA(0i64, "GetDefaultDistribution failed", "Error", 0x10u);
}
else
{
v17 = 0i64;
v16 = 0i64;
v20 = 0i64;
v19 = 0i64;
v15 = 0i64;
v14 = 0i64;
v13 = 0i64;
v12 = 0i64;
v7 = *(*(*(__readgsqword(0x30u) + 96) + 32i64) + 16i64);
GetCurrentDirectoryW(0x105u, &Buffer);
v10 = 0;
v9 = 0;
if ( (*(*ppv + 112i64))(ppv,&v18,v6,v5,v4,&Buffer,0i64,0i64,0,L"root",
v9,v10,v7,&v19,&v22,&v21,&v17,&v16,&v15,&v14,&v13,&v12) )
{
MessageBoxA(0i64, "CreateLxProcess failed", "Error", 0x10u);
}
else
{
v3 = 1;
}
}
if ( ppv )
{
(*(*ppv + 16i64))(ppv);
}
return v3;
}
Trong đoạn code trên:
if ( (*(*ppv + 112i64))(ppv,&v18,v6,v5,v4,&Buffer,0i64,0i64,0,L"root",
v9,v10,v7,&v19,&v22,&v21,&v17,&v16,&v15,&v14,&v13,&v12) )
Đoạn code này gọi hàm trong vtable của object C++, ta vào link respository trên, đọc file LxssUserSession.h
Ta có thể thấy sự tương đồng, vậy ta có thể kết luận đây là hàm CreateLxProcess
.
Vậy hàm CreateLxProcess
làm gì ?
Ở windows 10, khi WSL ra đời, ta đã có thể chạy các file executable (ELF) trên hệ điều hành linux, ví dụ như cat, ls, …
Nhưng đường dẫn tới file này nằm ở đâu ? Ta nhớ lại lúc nãy chương trình có lấy resource của nó rồi ghi ra “%tmp%”, nên ta dùng “Resource hacker” extract resource này ra xem thử:
Resource hacker là tool dùng để xem và sửa phần resource của file PE, tải ở đây.
Ta thấy 1 file bắt đầu với \x7FELF
, chính là file executable của hệ điều hành linux. Ta extract file này ra và bỏ vào IDA:
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
addr.sa_data[13] = 0;
v47 = __readfsqword(0x28u);
v43 = 0LL;
memset(&v42, 0, 0x58uLL);
addr.sa_family = 1;
strcpy(addr.sa_data, "496b9b4b.ed5");
v3 = socket(1, 1, 0);
fd = v3;
if ( v3 < 0 )
{
perror("socket");
}
else
{
v4 = &addr;
v5 = (unsigned int)v3;
if ( connect(v3, &addr, 0x6Eu) >= 0 )
// ...
}
// ...
}
Vậy chương trình mới được drop ra sẽ connect
tới socket được tạo bởi file .exe.
Phân tích cả 2 file .exe và file ELF, ta có thể thấy đây là 1 game Tic-tac-toe theo cấu trúc Client-Server, nếu Server thắng, Client sẽ bắt đầu tạo 1 message từ /proc/modules, /proc/mounts, /proc/version_signature, … rồi gửi cho Server.
Nhưng như phân tích ở ban đầu, thì Server không bao giờ thắng được vì Client đã đi trước 1 nước ở ngay chính giữa, cách duy nhất để thắng chính là bằng cách nào đó patch Client để Server có cơ hội thắng.
Ở Server, khi ta click vào 1 ô, nó sẽ gửi tọa độ của ô đó (x,y) cho Client (hàm DialogFunc
ở 0x140001000):
if ( qword_14001EA78[v22] == 32 )
{
::buf = a3 >> 4;
v23 = s;
byte_14001EA71 = a3 & 0xF;
qword_14001EA78[v22] = 79;
send(v23, &::buf, 2, 0); // <----- send X,Y here
recv(s, qword_14001EA78, 10, 0);
sub_140001520(v4);
if ( byte_14001EA81 )
{
recv(s, &buf, 64, 0);
MessageBoxA(v4, &buf, "Game Over", 0);
sub_1400014E0();
sub_140001520(v4);
}
Ở Client, trước khi gửi trạng thái của game cho Server, nó sẽ kiểm tra xem với nước đi đó thì có ai “thắng” không (trạng thái của game là 1 chuỗi 10 ký tự, trong đó 9 ký tự đầu là “XXOOXO…” tùy vào các nước đi, ký tự cuối để xác định xem ai là người thắng với trạng thái hiện tại)
// function "main" on Client
v4[3 * v6 + i] = 88;
byte_2020A9 = sub_14B0();
send(fd, &byte_2020A0, 0xAuLL, 0); // send state
v5 = byte_2020A9;
if ( byte_2020A9 )
break;
recv(fd, &unk_2020AA, 2uLL, 0);
v15 = &v4[3 * unk_2020AA + byte_2020AB];
if ( *v15 != 32 )
goto LABEL_5;
*v15 = 79;
v5 = sub_14B0(); // check if someone wins, return 'X', 'O', or 0
Vậy ta chỉ cần breakpoint ngay tại hàm send
của Client, sửa State lại trước khi nó gửi để lừa Client.
Đầu tiên, mình dùng plugin gef cho gdb, tải ở đây.
Cách debug process chạy bởi WSL:
-
Chạy ttt2.exe.
-
Mở 1 windows terminal khác lên, gõ WSL để truy cập vào Linux Subsystem.
-
Gõ ps -aux để lấy list các process trong Linux Subsystem.
-
Ta thấy process đang chạy dưới tên “XXXX.tmp”, giờ ta attach debugger vào bằng lệnh “sudo gdb -p 9”, vì PID của nó là 9. (xem ở cột PID).
-
Gõ “vmmap” trong gdb để tìm base address của “XXXX.tmp”.
-
Như hình trên, base address sẽ là 0x00007fe9e6200000.
-
Vào IDA lấy Offset:
-
.text:0000000000000D47 call sub_14B0 ; check
-
Vậy Offset là 0xD47, ta gõ “b *0x00007fe9e6200000+0xD47” để đặt breakpoint ngay tại chỗ này.
-
Gõ c để chương trình tiếp tục chạy.
-
Bên Server, ta click vào bất kỳ ô nào (trừ ô giữa), khi đó Client đã dừng lại ngay tại breakpoint.
-
State ở Client là 1 mảng char, global, offset tại 0x2020A0, ta dùng lệnh sau để patch state:
patch string 0x00007fe9e6200000+0x2020A0 "OOOOOOOOO"
(9 chữ “O”). -
Bấm c để cho Client chạy tiếp, ở Server ta nhận được 1 message.
c1ArF/P2CjiDXQIZ@flare-on.com