中级ROP之ret2_csu_init

64位的参数传递使用的寄存器顺序
前六个参数使用寄存器的顺序是:
1 | rdi,rsi,rdx,rcs,r8,r9 |
之后的参数就是放入栈中了。
例如参数大于七时
1 | fun(a,b,c,d,e,f,g,h)//只是示范就不写参数类型了 |
介绍_libc_csu_init
在大多数情况下,我们很难找到每一个寄存器的gdgets,这时候就可以利用x64下的_libc_csu_init中的gedgets。这个函数是用来对libc进行初始化的,一般的程序都会调用这个函数,所以这个函数一定会存在。
比如例题的,长这样:
1 | .text:0000000000400580 loc_400580: ; CODE XREF: __libc_csu_init+54↓j |
在loc_400596:
里都是pop,在loc_400580:
里再赋给其他我们需要的寄存器
这里r15赋给edi处,是低32位,需要确认高位为0才可以用,不过我们有pop rdi这个gadget
然后通过这些就可以系统调用**execve(‘/bin/sh’,0,0)**了
例题 ciscn_2019_s_3
查看保护和反汇编
checkesc

64位,堆栈不可执行
ida
main函数
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
vuln函数
1 | signed __int64 vuln() |
汇编

说一下汇编代码
xor rax, rax====== > 将rax设置为0
mov edx, 400h=== >将edx设置为0x400
lea rsi, [rsp+buf]== > 将buf参数的地址传入寄存器rsi
mov rdi, rax===== >将rax寄存器里的值(0)传入rdi寄存器(将rdi设置为0)
syscall ========= >进行系统调用
这段是执行了 read(0,buf,0x400)
下面的write也是一样
两个函数都是通过系统调用的方式实现
对于amd64程序sys_read 的调用号 为 0 ;sys_write 的调用号 为 1;stub_execve 的调用号 为 59;
buf只有0x10,read函数可以输入0x400,足以栈溢出
read之后有write,本题用它接收写入的/bin/sh地址
ida中有一个gadgets

汇编

可以看到有将rax设置为59的gadget
记下来execve=0x4004E2 //mov rax 59
接下来只要设置execve函数的参数,通过pop指令持续控制,让rdi=/bin/sh,rsi=0,rdx=0即可获得shell
但是gadget中没有控制rdx的(通过ROPgadget指令查找)
_libc_csu_init函数中的gadget

这里有控制rdx的指令
先调用loc_400596的函数(从pop rbx处的地址开始)设置rbp为1,r12为/bin/sh地址加8(后面说),其他的都为0。
再调用loc_400580函数将上函数设置的值传到目标寄存器里。
让rbx为0,rbp为1目的是在后面跳出循环(但是做题时不跳也能通,还不明白为啥,即rbp也可以是0)
这么做的目的:在loc_400580里有一步call,它的调用地址是由r12和rbx决定的,rbx已经设置为0,我们可以设置r12为我们想要的地址)
写binsh
本题没有/bin/sh,需要我们自己写。
write打印了0x30字节,会泄露一些栈上的地址
因为开启了保护,每次的站地址会不同,但是偏移是不会变的。
我们要找一个可以被write输出的地址,通过偏移来获得写入的/bin/sh地址
由vuln函数中的**lea rsi, [rsp+buf]**可知,rsi记录buf的地址
gdb调试(断点还是打在了nop处,输入了aaaa)

这个是buf地址,查看该地址

注意这三行,到上图标记的位置处就已经是0x28(40)了
1 | 0x7fffffffdf40: 0x00007f0a61616161 0x0000000000400540# 前八位是aaaa |

0x00007fffffffdf70是rbp栈底,0x00007fffffffe068这个地址比他高,处于栈中,所以他是栈中的一个地址(想到这,我觉得0x00007fffffffdf70应该也可以)

0x0000000000400536是main函数的nop,还不知道为啥它会在这里
0x00007fffffffdf70之前由0x20个字节,在接收地址时要先把他们过滤掉。
再计算它到输入内容的偏移
所以接收数据后减去0x128就是我们输入的位置
还需要syscall,在ida里就能找到,当然ROPgadget也可以
地址syscall=0x400501
exp
1 | from pwn import* |
注意上面的payload2,前面说将r12设置为我们想要执行的函数,我们将地址设置成了/bin/sh+8,而填充栈部分分别是/bin/sh和pop rdi,可能是我还没理解到位,遇到了一些目前无法理解的情况:/bin/sh后面接上其他pop一个寄存器的gadget都可以,直接把r12设置为pop rdi的地址不行。
另外,本地和远程计算出来的偏移量不同