Challenge 08

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:

  • Tên repository này là “WSLReverse”, (WSL là “Windows Subsystem for Linux” Link).
  • Link github.

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

[+] Source code dùng để giải cho tất cả các bài nằm ở đây