ciscn_s_3
IDA伪代码分析
首先检查一下文件

拖入IDA,首先发现vuln函数

直接看汇编

前面rax为0,那么调用read的系统调用,向rsp+buf开始写入0x400大小的字符,buf只有0x10的大小,那么肯定有溢出,还要注意前面mov rbp,rsp之后就没有sub esp了;后面mov rax 1将rax设为1,那么调用write系统调用,从rsp+buf开始打印0x30大小的数据。
32位对应的系统调用号
| read | 3 |
|---|---|
| write | 4 |
| execve | 11 |
| sigreturn | 77 |
64位对应的系统调用号
| read | 0 |
|---|---|
| write | 1 |
| execve | 59 |
| sigreturn | 15 |
我们发现还有个gadgets函数

一个是mov rax, 0Fh,按上面的表格,就是将rax系统调用号设置为15,也就是sigreturn,那么就可以用srop去做这题,还有一个是mov rax, 3Bh,将rax设置为59,也就是execve,也可以用普通的rop去做。
第一种做法
程序一开始是先执行read(0,buf,0x400),然后执行write(1,buf,0x30),buf的位置距离rbp只有0x10,所以存在栈溢出,而且前面没有进行sub rsp抬高栈,所以rsp和rbp是相等的,retn相当于pop rip,所以这里覆盖rbp的时候,其实就需要将rbp覆盖成你想要的返回地址。所以这道题的偏移其实就是0x10就可以了。
思路
第一种做法就是通过系统调用59对应的execve,然后想办法执行execve(“/bin/sh”,0,0)
上面说到了可以进行栈溢出,执行execve就需要给寄存器赋值,那大概的布局就是这样的:
$rax==59
$rdi==“/bin/sh”
$rsi==0
$rdx==0
syscall
上面我们看到gadgets函数中有mov rax,3Bh,那么第一个条件可以达成,对于第二条件,通过查找程序也没发现有/bin/sh的字符,那么就要通过栈溢出将/bin/sh写入栈中,然后通过泄露栈的地址,加上偏移去得到/bin/sh的地址,然后通过ROPgadget查找pop rdi ret的gadget将/bin/sh的地址写入rdi,第三第四个条件可以利用csu去赋值。
那么,首先是泄露栈的地址
1 | |
write是会打印出0x30大小的数据,这里在打印到0x20的时候,接下来是打印出来一个地址,这个地址一看就是栈上面的,所以只要算出这个地址和binsh地址的相对偏移,就可以在程序每次执行的时候算出binsh的地址了
这里是c88-b70==0x118

那么下一步就是利用csu给rsi,rbx赋值了,发现csu只能给edi赋值,但我们要给rdi赋值,所以我们还要找pop rdi的gadget

1 | |
这里的r12赋的值就是binsh的地址+0x50,也就是下一个rop的地址,也就是rax_59_ret的地址,给rax赋值为59,这里注意的是[r12],是取地址操作,所以r12是栈上的地址,这个地址里保存着rax_59_ret的地址,这里是我踩的一个坑,那么之后的exp就没什么好分析的了,放一下最后的exp
exp
1 | |
第二种做法
用srop去做,首先介绍一下srop
SROP
SROP(Sigreturn Oriented Programming),sigreturn是一个系统调用,在 unix 系统发生 signal 的时候会被间接调用
当系统进程发起(deliver)一个 signal 的时候,该进程会被短暂的挂起(suspend),进入内核①,然后内核对该进程保留相应的上下文,跳转到之前注册好的 signal handler 中处理 signal②,当 signal 返回后③,内核为进程恢复之前保留的上下文,恢复进程的执行④

内核为进程保留相应的上下文的方法主要是:将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址,此时栈的情况是这样的:

我们称 ucontext 以及 siginfo 这一段为 signal frame,需要注意的是这一部分是在用户进程的地址空间,之后会跳转到注册过 signal handler 中处理相应的 signal,因此,当 signal handler 执行完成后就会执行 sigreturn 系统调用来恢复上下文,主要是将之前压入的寄存器的内容给还原回对应的寄存器,然后恢复进程的执行。
思路
我们发现gadgets函数中有个mov rax,0Fh,这是Sigreturn系统调用号,Sigreturn 从栈上读取数据,赋值到寄存器中,可以用来构造 syscall(59,”/bin/sh”,0,0)。那么我们可以通过构造一个fake signal frame,让程序调用了Sigreturn之后,恢复我们构造好的frame,给寄存器都赋值。这里需要用到SigreturnFrame(),我们用Python2去写EXP
1 | |
这里记得fakeframe的rip设置sys_call,不然恢复了上下文后也不会调用execve