Logo Peter

ASCIS 2024

October 12, 2024
10 min read
Table of Contents

ASCIS 2024

Một vài lời nói đầu

Giải SVATTT năm nay mình được may mắn được chung đội với các anh năm 3 với team UIT.Efficiency V. Năm nay khác với các năm trước Final sẽ chia ra hai bảng bảng A là A/D, bảng B là jeopardy và lần này mình chơi bảng B. Trong lúc thi ngoài câu Calculator là câu warmup hầu như các team đều làm được thì mình còn first blood được câu Trust Me! câu này mình đã phải đầu tư 4 - 5h.

Khởi đầu

Đề cho một file exe và một file pcapng để làm, mình bắt đầu với việc phân tích file pcapng trước. Bởi mình cần nắm rõ hai điều

  • Thứ nhất server và client giao tiếp ra sao
  • Thứ hai là liệu data khi được giao tiếp có encrypt không hay vẫn là raw

Sau khi nhìn sơ qua thì mình biết được là server 192.168.89.136:31337, client sẽ là 192.168.89.1:51392. Và tất nhiên thì data đã bị encrypt tuy nhiên vẫn có một cái đáng chú ý. Đó là chuỗi I'm_4_Gat3_K33per từ server send về client ở packet thứ 6.

Tiếp tục phân tích tới file exe. Tại hàm main dường như là không có gì cả.

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4[100]; // [esp+0h] [ebp-74h] BYREF
 
  memset(v4, 0, sizeof(v4));
  if ( dword_4203A0() )
    sub_4033C0();
  MEMORY[0] = 0;
  JUMPOUT(0);
}

Tuy nhiên ở đây mình nhận ra đó là đoạn code này chắc chắn sẽ gây ra exception. Lý do tại sao các bạn nhìn kĩ vào đoạn này

MEMORY[0] = 0;
JUMPOUT(0);

Thế câu hỏi đặt ra nó handle exception ở đâu ? Nếu không có thì chả lẽ bài này còn gì đó khác 👀

Phân tích

Về tổng quan mình đã hình dung nó đang làm gì thế thì nếu ở main gây exception thì nó có thể ở đâu ? Mình đoán chắc chắn nó chỉ có thể ở init hoặc là TLSCallback

Đúng như mình nghĩ ở hàm init địa chỉ 001B71DC có một hàm dường như đang thực hiện kết nối tới server mà ở đây là địa chỉ mình đã nhắc tới ở trên.

int doing_something()
{
  char *v0; // ebx
  int v1; // eax
  _DWORD *lib; // esi
  int v3; // eax
  PADDRINFOA *v4; // edi
  int (__stdcall *v5)(const char *, const char *, _DWORD *, _DWORD *); // eax
  int v6; // eax
  PADDRINFOA v7; // eax
  int v8; // eax
  int v9; // ecx
  int v10; // eax
  int v11; // eax
  _DWORD *v12; // esi
  _DWORD *v13; // eax
  int v14; // esi
  _BYTE *v15; // eax
  int v16; // esi
  _DWORD *v17; // edi
  void *v18; // edi
  int v19; // esi
  _DWORD *v20; // ebx
  int v21; // edi
  _BYTE *key; // eax
  char ***v23; // esi
  char **dll; // eax
  const char *v25; // eax
  int v26; // eax
  void *v28; // [esp+70h] [ebp-24h]
  int v29; // [esp+74h] [ebp-20h]
  char *v30; // [esp+78h] [ebp-1Ch]
  unsigned int random_shit; // [esp+8Ch] [ebp-8h] BYREF
  unsigned int v32; // [esp+90h] [ebp-4h] BYREF
  int savedregs; // [esp+94h] [ebp+0h] BYREF
 
  malloc(8u);
  sub_1A2720();
  random_shit = 4;
  v0 = (char *)malloc(0x14u);
  v28 = dword_1C0398;
  *(_OWORD *)v0 = 0i64;
  *((_DWORD *)v0 + 4) = 0;
  memset(v28, 0, 716u);
  v1 = dword_1C03B0(0);
  dword_1C03A4(v1);
  lib = load_lib();
  v3 = ((int (__stdcall *)(int, _DWORD *))lib[114])(514, lib);
  lib[111] = v3;
  if ( v3 )
    ExitProcess(1u);
  lib[103] = 0;
  v4 = (PADDRINFOA *)(lib + 101);
  lib[107] = 0;
  lib[108] = 0;
  lib[109] = 0;
  lib[110] = 0;
  v5 = (int (__stdcall *)(const char *, const char *, _DWORD *, _DWORD *))lib[115];
  lib[104] = 0;
  lib[105] = 1;
  lib[106] = 6;
  v6 = v5("192.168.89.136", "31337", lib + 103, lib + 101);// getaddrinfo
  lib[111] = v6;
  if ( v6 )
    goto LABEL_4;
  v7 = *v4;
  lib[102] = *v4;
  if ( v7 )
  {
    while ( 1 )
    {
      v8 = ((int (__stdcall *)(int, int, int))lib[116])(v7->ai_family, v7->ai_socktype, v7->ai_protocol);// socket
      lib[100] = v8;
      ((void (__stdcall *)(int, int, int, _DWORD *, int))lib[117])(v8, 0xFFFF, 4101, lib + 113, 4);// setsockopt
      v9 = lib[100];
      if ( v9 == -1 )
        break;
      v10 = ((int (__stdcall *)(int, _DWORD, _DWORD))lib[119])(// connect
              v9,
              *(_DWORD *)(lib[102] + 24),
              *(_DWORD *)(lib[102] + 16));
      lib[111] = v10;
      if ( v10 == -1 )
      {
        ((void (__stdcall *)(_DWORD))lib[120])(lib[100]);
        v11 = lib[102];
        lib[100] = -1;
        v7 = *(PADDRINFOA *)(v11 + 28);
        lib[102] = v7;
        if ( v7 )
          continue;
      }
      goto LABEL_9;
    }
LABEL_4:
    ((void (*)(void))lib[118])();
    ExitProcess(1u);
  }
LABEL_9:
  freeaddrinfo(*v4);
  if ( load_lib()[100] != -1 )                  // load_dll
  {
    v12 = load_lib();
    if ( !((int (__stdcall *)(_DWORD, char *, int, _DWORD))v12[122])(v12[100], v0, 512, 0) )// recv
    {
      v12[100] = 0;
      *((_BYTE *)v12 + 496) = 0;
    }
    v13 = load_lib();
    ((void (__stdcall *)(_DWORD, unsigned int *, int, _DWORD))v13[121])(v13[100], &random_shit, 1, 0);// send
    random_shit = strlen(random_az_AZ((int)&savedregs));
    rc4_send((int)&random_shit, 4, v0);         // send_len
    v14 = strlen(random_az_AZ((int)&savedregs));
    v15 = random_az_AZ((int)&savedregs);
    rc4_send((int)v15, v14, v0);                // send_random_str
    v16 = dword_1C03B4 + 4;
    v17 = load_lib();
    if ( !((int (__stdcall *)(_DWORD, int, int, _DWORD))v17[122])(v17[100], v16, 4, 0) )// ws2_32_recv
    {
      v17[100] = 0;
      *((_BYTE *)v17 + 496) = 0;
    }
    v18 = malloc(*(_DWORD *)(dword_1C03B4 + 4));
    v19 = *(_DWORD *)(dword_1C03B4 + 4);
    *(_DWORD *)(dword_1C03B4 + 8) = v18;
    v20 = load_lib();
    if ( !((int (__stdcall *)(_DWORD, void *, int, _DWORD))v20[122])(v20[100], v18, v19, 0) )// ws2_32_recv
    {
      v20[100] = 0;
      *((_BYTE *)v20 + 496) = 0;
    }
    v29 = strlen(random_az_AZ((int)&savedregs));
    v21 = dword_1C03B4;
    v30 = *(char **)(dword_1C03B4 + 8);
    key = random_az_AZ((int)&savedregs);
    rc4(*(_DWORD *)(v21 + 8), *(_DWORD *)(v21 + 4), (int)key, v29, v30);
    v23 = (char ***)dword_1C03B4;
    dll = load_dll_(*(_DWORD **)(v21 + 8), *(_DWORD *)(v21 + 4));// dll_load
    *v23 = dll;
    if ( !dll )
      sub_1A1600((int)"Can't load library from memory.\n");
    if ( !*(_DWORD *)dword_1C03B4 )
    {
      _loaddll((char *)0xFFFFFFFF);
      JUMPOUT(0x1A33B3);
    }
    dword_1C0394 = sub_1A2720();
    v32 = *(_DWORD *)dword_1C0394 / (unsigned int)dword_1BF8B0;
    v25 = random_az_AZ((int)&savedregs);
    rc4_send((int)&v32, 4, v25);
  }
  dword_1C039C(1, handler_excep);               // ntdll_RtlAddVectoredExceptionHandler
  v26 = dword_1C03B0(5);                        // GetConsoleWindow
  dword_1C03A4(v26);                            // ShowWindow
  return 0;
}

Sau khi debug và reverse lại thì mình tổng kết flow nó như sau:

  • Thằng server sẽ gửi tới key I'm_4_Gat3_K33per tới thằng client và thằng client sẽ gửi option từ 0 - 4 cho server đồng thời sẽ gửi size data và data mà nó gen random hàm random_az_AZ và thằng server sẽ gửi về đầu tiên là size của encrypt data, hai là data bị encrypt key sẽ là key random của thằng client đã gửi lên mà cái key này bị RC4 bởi key I'm_4_Gat3_K33per. Khi decrypt ra ta sẽ có.

  • 86dad7bb -> 0x40 -> len 64

  • 918e87d161556ad2e40a89010adfe3aa41ca44764e786b738047456cc80d021e7f60b56776b858225d45099f0e99b62f5758977fde740bcc2be36dbf403eb860 -> WTPjWbJafqNPqrZFswaijmyVKMddOrKzukegbVDpXJqDfulPDmDwDasqTwxvibnM

Hiểu cơ chế là một chuyện vấn đề tiếp theo thằng server gửi một packet khá to lúc này làm sao để vừa setup debug vừa biết nó làm gì. Đó là replay attack đây là server cơ bản mình code để tiện lợi debug và dump ra khi cần gì đó.

from pwn import *
 
server = listen(31337)
client = server.wait_for_connection()
client.timeout = None
client.send(b"I'm_4_Gat3_K33per")
a =  client.recv(100)
print(a.hex())
a =  client.recv(4)
print('len: ', a.hex())
a =  client.recv(100)
print(a.hex())
 
client.send(bytes.fromhex("002a0000"))
 
encrypted_data = b''
for i in range(1,9):
    encrypted_data += open(f"{i}.bin","rb").read()
 
print(len(encrypted_data))
client.send(encrypted_data)
 
a =  client.recv(4)
print('len: ', a.hex())
 
client.send(b'\x03')
client.send(b'\x00')
client.interactive()

Từ đó ta biết được là tiếp theo thằng server nó sẽ gửi một file DLL đã bị encrypt và có thể decrypt bằng key ta mới tìm được ở trên. File DLL mình xem qua dường như không có gì đặc biệt lắm ngoại trừ hàm gen0 -> gen4 dùng để modify key. Vấn đề tiếp theo do bản chất nó sẽ call hàm random_azAZ để làm key làm sao để replay attack lại để xem nó làm gì. Giải pháp của mình là patch lại khi ở đây

import idaapi
 
def patch(address, arr):
    for i in range(len(arr)):
        idaapi.patch_byte(address + i, arr[i])
 
key = b'WTPjWbJafqNPqrZFswaijmyVKMddOrKzukegbVDpXJqDfulPDmDwDasqTwxvibnM'
 
patch(0x00C769C0, key)

Trước khi nó call RC4 để decrypt cái DLL bị encrypt đó mình sẽ patch lại.

Đó là tổng quan, tiếp tới câu hỏi mình đã đặt ra exception handle ở đâu?. Thì là tại đây

dword_1C039C(1, handler_excep);               // ntdll_RtlAddVectoredExceptionHandler
v26 = dword_1C03B0(5);                        // GetConsoleWindow
dword_1C03A4(v26);                            // ShowWindow
return 0;

Ngay tại cuối của hàm này cũng ngó qua xem nó làm gì.

int __stdcall handler_excep(int **a1)
{
  int error_value; // eax
  _DWORD *v2; // eax
  _DWORD *lib; // esi
  void *v5; // edx
  int *v6; // eax
  int v7; // ecx
  int v8; // ecx
  char v9; // [esp+13h] [ebp-1h] BYREF
 
  if ( load_lib()[100] == -1 || !*(_DWORD *)dword_1C03B4 )
  {
    a1[1][46] += 5;
    return -1;
  }
  error_value = **a1;
  if ( error_value == 0xC0000005 )
  {
    v2 = (_DWORD *)dword_1C0394;
    qmemcpy(dword_1C0398, a1[1], 0x2CCu);
    if ( dword_1C035C <= (int)(*v2 / (unsigned int)dword_1BF8B0) )
    {
      lib = load_lib();
      if ( !((int (__stdcall *)(_DWORD, char *, int, _DWORD))lib[122])(lib[100], &v9, 1, 0) )// ws2_32_recv
      {
        lib[100] = 0;
        *((_BYTE *)lib + 496) = 0;
      }
      switch ( v9 )
      {
        case 0:
          a1[1][46] = (int)gen0;
          return -1;
        case 1:
          a1[1][46] = (int)gen1;
          return -1;
        case 2:
          a1[1][46] = (int)gen2;
          return -1;
        case 3:
          a1[1][46] = (int)gen3;
          return -1;
      }
    }
    return -1;
  }
  if ( error_value != 0x80000003 )
    return -1;
  v5 = dword_1C0398;
  if ( !*((_DWORD *)dword_1C0398 + 46) )
    return -1;
  qmemcpy(a1[1], dword_1C0398, 0x2CCu);
  v6 = a1[1];
  v7 = v6[46];
  if ( dword_1BF8B0 )
    v8 = v7 - 2;
  else
    v8 = v7 + 5;
  v6[46] = v8;
  memset(v5, 0, 0x2CCu);
  return -1;
}

Ở đây khi mà chúng ta raise exception ở ngay tại hàm main (0xC0000005) thì nó sẽ recv thêm option từ thằng server sau đó sẽ jump vào case đấy. Ví dụ đây là hàm gen0

int __usercall gen0@<eax>(int a1@<ebp>)
{
  signed int v1; // esi
  int func_dll; // eax
  int v3; // edx
  int (__cdecl *v4)(_BYTE *); // edi
  _BYTE *v5; // eax
  const char *v6; // eax
  _BYTE *v7; // eax
  const char *v8; // eax
  int result; // eax
  _BYTE *v10; // eax
  const char *v11; // eax
  _BYTE *v12; // eax
  const char *v13; // eax
 
  v1 = *(_DWORD *)dword_1C0394 / (unsigned int)dword_1BF8B0;
  func_dll = load_func_dll(*(_DWORD **)dword_1C03B4, (unsigned int)"gen0");
  v3 = dword_1C035C;
  v4 = (int (__cdecl *)(_BYTE *))func_dll;
  if ( dword_1C035C >= v1 )
  {
    result = *(_DWORD *)dword_1C0394 - dword_1BF8B0 * dword_1C035C;
    dword_1BF8B0 = result;
    if ( result > 0 )
    {
      v10 = random_az_AZ(a1);
      v11 = (const char *)v4(v10);
      rc4_send((int)&dword_1BF8B0, 4, v11);
      v12 = random_az_AZ(a1);
      v13 = (const char *)v4(v12);
      rc4_send(*(_DWORD *)(dword_1C0394 + 4) + dword_1BF8B0 * dword_1C035C, dword_1BF8B0, v13);
      v3 = dword_1C035C;
    }
    dword_1BF8B0 = 0;
  }
  else
  {
    v5 = random_az_AZ(a1);
    v6 = (const char *)v4(v5); // gen0->gen4 in dll 
    rc4_send((int)&dword_1BF8B0, 4, v6);
    v7 = random_az_AZ(a1);
    v8 = (const char *)v4(v7);
    rc4_send(*(_DWORD *)(dword_1C0394 + 4) + dword_1BF8B0 * dword_1C035C, dword_1BF8B0, v8);
    v3 = dword_1C035C;
  }
  dword_1C035C = v3 + 1;
  __debugbreak();
  return result;
}

Nó sẽ modify key rồi dựa theo option thằng server gửi sau đó encrypt data rồi gửi ngược lại server. Ta đã hiểu nó làm gì bước cuối là giải quyết. Dump các packet liên quan theo option và dùng script này để solve.

Solve script của mình:

from arc4 import ARC4
index = [2, 1, 1, 2, 3, 2, 3, 1, 2, 3, 1, 0, 2, 3, 0, 0, 2, 1, 1, 0, 0, 3]
 
total = b''
 
for i in range(22):
    enc_file = open(f"dump/output{i}.txt","r").read()
    enc_file = bytes.fromhex(enc_file)
 
    if index[i] == 0:
        key = b'MnbivxwTqsaDwDmDPlufDqJXpDVbgekuzKrOddMKVymjiawsFZrqPNqfaJbWjPTW'
    elif index[i] == 1:
        key = b'wtpJwBjAFQnpQRzfSWAIJMYvkmDDoRkZUKEGBvdPxjQdFULpdMdWdASQtWXVIBNm'
    elif index[i] == 2:
        key = b'MWTPjWbJafqNPqrZFswaijmyVKMddOrKzukegbVDpXJqDfulPDmDwDasqTwxvibn'
    elif index[i] == 3:
        key = b'JGCwJoWnsdACdeMSfjnvwzlIXZqqBeXmhxrtoIQcKWdQshyCQzQjQnfdGjkivoaZ'
    cipher = ARC4(key)
    decrypted_data = cipher.decrypt(enc_file)
    total += decrypted_data
 
 
open("flag.bmp","wb").write(total)

Và flag: ASCIS{TruSt_m3_br0!_I_d1dn_T_D0_4nyTh1ng!_I_sw34r}

Lời kết

Năm nay là năm đầu mình tham gia SVATTT cũng may mắn là mình đã làm ra một bài, không làm gánh nặng cho các anh 😿 😿 😿

Đội mình đã về nhất trong bảng B, một kỉ niệm đáng nhớ :3