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_K33pertớ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àmrandom_az_AZvà 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 keyI'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

Peter