ROP之ret2xx

发布于 2023-03-08  275 次阅读


ROP(Return Oriented Programming):面向返回编程。

1:ret2text

并且32位程序与64位程序ROP链构造有稍许不同,主要原因就是32位程序传参使用的是栈,64位前六个参数使用的是寄存器,当超过六个时才使用栈。所以pwn题时遇到ret2text类型时要注意区别是32位还是64位程序。

栗题:(jarvisoj_level2)

32位程序:

64位程序:(jarvisoj_level2 x64)

由于64位程序调用传参并不先使用栈,而是先使用寄存器,因此利用漏洞时我们需要设置rdi指向bin/sh,可以使用pop rdi ;ret代码片段设置rdi ,使用ROPgadget来寻找pop rdi ;ret 命令为:ROPgadget --binary 文件名 --only "pop|ret" 界面如下:

利用找到的gadgets来构造ROP链,payload=p64(pop_rdi_ret_address)+p64(binshaddress) 此payload功能是设置rdi指向binsh。

2:ret2shellcode

栗题:(题目为jarvisoj_level1)

该exp流程是:先获取printf泄露的buf地址,再利用pwntools生成一个shellcode,把shellcode输入buf地址里面,再覆盖返回地址执行该shellcode获取shell。

解释:

shellcode=asm(shellcraft.sh())的作用是生成一段获取shell的shllcode。

shellcode.ljust(0x88+4,b'a')的含义是将shellcode不足112的部分用a补齐。

3:ret2syscall

栗题:(题目:inndy_rop)

gadgets中的int 0x80的寻找命令 : ROPgadget --binary 文件名 | grep "int 0x80" ; 或者(推荐ropper)使用ropper 命令为:ropper --file 文件名 --search "int 0x80";
选择的读入地址一般选择可读可写的bss段,

4:ret2libc(题目:jarvisoj_level1)

不管有没有开启PIE,如果要执行system(bin/sh),先泄露libc的基地址,再根据偏移地址找到system的地址。

栗题:

先去执行write_plt,泄露read_got地址的值,因为前边的代码执行过read函数,所以got表会存储read函数真实的地址,得到read地址之后,继续38行根据libc的偏移找到read在libc中的偏移,计算出libc的基地址,然后根据这个基地址得到system函数的地址以及binsh的地址。 然后第一个payload返回地址填的是main函数,会再执行一边程序,这时候rop链payload2就执行了systembinshell,得到shell。

5:ret2csu

不是所有程序我们都能很方便的找到各种的gadgets,如pop rdi;ret, pop rsi;ret等等程序的gadgets,但在64位程序中,如果它利用了libc中的函数,就会存在__libc_csu_init这样一个函数,这个函数存在两段gadgets可以帮我们去对寄存器赋值并调用一些函数。

分析下这两段gadgets,gadget1可以利用栈上的值对rbx,rbp,r12,r13,r14,r15进行赋值,如果此时再将retn覆盖为gadget2的地址的话就能够利用gadget1对rdx,rsi,edi进行赋值;像64位程序,参数是先rdi,rsi,rdx,rcx,r8,r9, 这里的话,我们已经能够控制前三个,所以大部分函数我们都能调用,像上图的call qword ptr [r12+rbx*8],若括号里的值写的是write got 表的一个地址的话就会调用这个地址的一个write 函数,所以,通过这两个gadgets的组合我们就能实现edi,rsi,rdx寄存器的赋值,并且通过设置r12,rbx可以实现一个函数的调用。

那如何利用csu构造ROP链泄露libc呢

要使用call来调用write,需要使r12+rbx*8为write_got表的地址,第二步来设置参数,edi=r13d=1,rsi=r14=write_got, rdx=r15=8,这样的话就能输出write_got地址上的值;第三步使jnz不跳转,继续ROP链向下执行,所以设置rbx=0,rbp=1;此时栈中的参数布置如下
这样布置好gadget1后, 返回值地址覆盖为gadget2,这样跳转去执行gadget2,执行完gadget2再向下执行,这样就形成了三步走的过程。
看下图,当步骤三 也就是第二次执行gadget1的时候,会执行 add rsp ,38h, 所以栈rsp会发生改变,所以下次再覆盖返回地址时就需要先填充0x38的长度,就比如想不停的触发漏洞时,就可以将新的rsp 里的rop_addr改为mainaddr,这样就可以利用ret2csu不停的来执行一些函数。 所以payload大致顺序就可以参考下图

栗题:(题目:jarvisoj_level5)

gdb调试发送payload看其在栈里的情况:

继续单步执行就到了第一个gadget1:

地址为0x400628是返回地址,若覆盖为gadget2的地址就能执行第二步走。

结合这个图片与下图一起看更清楚,能明白栈上的哪些值赋值给了对应的寄存器。

看下payload发送后跳转到gadget1运行,对应参数的状态:

既然我们能利用rer2csu来调用一些got表中的函数的话,那我们的思路就会简单些,第一步泄露libc,然后根据泄露的libc计算出system函数的地址,第二步,调用read函数向bss段读入system地址和binsh字符串。第三步,调用system('/bin/sh')来get shell。

第55行为什么要输入binsh字符串,而不是利用libc中的字符串,因为我们在给rdi赋值时,实际上我们只能对edi进行赋值,所以没有办法将libc中的binsh地址赋值给它,只能将程序中的binsh地址给它,所以要手动输入。

所以分三步走:payload1先泄露libc,然后根据泄露的libc计算出system与binsh的地址,第二步payload把计算出的system地址与binsh地址调用read函数写入bss段,第三步再触发漏洞调用system(binsh);

总结:


穿过云层我试着努力向你奔跑