使用ROP技术突破Linux的NX防护研究

2018-06-29 02:01陈振伟孙歆
网络空间安全 2018年2期

陈振伟 孙歆

摘 要:缓冲区溢出是指数据缓冲区复制的过程中,由于没有注意缓冲区的边界,越过边界,覆盖了和缓冲区相邻内存区域而引起的内存问题。缓冲区溢出是最常见的内存错误之一,也是攻击者入侵系统时所用到的最强大、最经典的一类漏洞利用方式。成功利用缓冲区溢出漏洞可以修改内存中变量的值,甚至可以劫持进程,执行恶意代码,最终获得主机的控制权。

关键词:栈溢出;漏洞利用;数据执行保护;Linux

中图分类号:TP309 文献标识码:A

Research on bypassing the NX protection of Linux with ROP

Abstract: Buffer overflow refers to the memory anomaly where a program, while writing data to a buffer, overruns the buffer's boundary and overwrites adjacent memory locations.Buffer overflow is one of the most common memory anomaly, and is the most powerful and classic way to exploit vulnerabilities when attackers hack the system. Attackers can modify the value of variables in the memory, hijack the processes, executemalwares, and ultimately control the hosts.

Key words: stack overflow; vulnerability exploitation; data execution prevention; linux

1 引言

棧溢出核心思想是通过局部变量覆盖函数返回地址来修改EIP和注入 Shellcode,在函数返回时跳到Shellcode去执行。要防止这种攻击,最有效的办法就是让攻击者注入的Shellcode无法执行,这就是数据执行保护(Data Execution Prevention,DEP)安全机制的初衷。

数据执行保护(DEP)主要用于抵抗缓冲区溢出攻击。该安全策略可以控制程序对内存的访问方式,即被保护的程序内存可以被约束为只能被写或被执行(W XOR X),而不能先写后执行。目前,这种安全策略已经在系统中得到了广泛的应用。

DEP述语是微软公司提出来的,在Window XP操作系统开始支持该安全特性。DEP特性需要硬件页表机制来提供支持。Linux在X86_32位CPU没有提供软件的DEP机制,在64位CPU则利用NX位来实现DEP(当前Linux很少将该特性说成DEP)。

DEP就是将非代码段的地址空间设置成不可执行属性,一旦系统从这些地址空间进行取指令时,CPU就是报内存违例异常,进而杀死进程。栈空间也被操作系统设置了不可执行属性,因此注入的Shellcode就无法执行了。数据执行保护策略虽然对程序运行时的内存访问提供了安全保护,保证内存只能被写或者被执行而不能先写后执行。但是不幸的是,这种保护方式并不是完全有效的,其仍然不能抵御不违反 W XOR X 保护策略的攻击方式。

2 漏洞利用

通过对一个具有栈溢出漏洞的程序进行实例分析,该程序文件名为overflow,具体实验环境如表1所示。

2.1远程调试前IDA配置

本文将用IDA对Linux程序进行远程的动态调试。使用IDA对Linux程序进行动态调试之前需要对IDA和Linux环境进行配置,具体步骤简要概括为三点。

1) 将IDA安装目录/dbgsrv/linux_server文件拷贝到Linux系统中,增加执行权限并运行。

2) 打开IDA,选择 Debugger-Run-Remote Linux debug。

3) 在远程调试配置界面填入被调试文件的程序名、位置、参数、Linux主机IP、Linux用户密码等信息,如图1所示。

IDA的常用功能包括源码显示:F5;自定义名称:Ctrl+N;文字视图切换:空格,效果如图2所示。

2.2 栈溢出通用分析方式

2.2.1 禁用ASLR

在接下来的两章中将对Overflow文件进行漏洞利用。在漏洞利用之前需要对Linux系统的堆栈地址随机化功能进行禁用。否则每次程序载入时,都会为堆栈重新随机化的分配内存地址,将对调试产生极大的干扰。

禁用方法:在root权限下执行 cat 0 > /proc/sys/kernel/randomize_va_space

在系统重启之后,这个文件中的值会恢复,所以在电脑重启以后,如再次进行调试,需要重新执行该命令。

2.2.2 静态流程分析

先对Overflow文件进行静态分析,了解其代码的执行流程。

可获得几条信息。

a. 该程序首先创建了一个UDP的socket。

b. 与本地环路IP地址和12345端口进行绑定。

c. 显示一个“waiting for message ….”的字符串。

d. 然后recvfrom。

e. 再根据收到的信息转换一下IP地址,显示信息。

f. 将输入的字符串进行加密,使用“abcdefghijklmn”作为加密的密钥。

g. 执行Calc函数。

h. 将字符串“You have failed …”发送回去,进行sleep,最后循环到上面的接收数据,重复步骤c。

再仔细的分析一下可以看到,这个程序的利用点在Calc函数当中,如图3所示。

如果忽略hook_foo函数,在Calc函数返回之前,紧跟一个memcpy函数,rdi和rsi分别为memcpy的两个参数,在执行memcpy函数之前没有检查是否存在参数越界的情况,所以这里具有栈溢出的风险。

2.2.3 使用IDA进行Linux的远程动态调试

在EncodeBuffer函数上下断点。执行,此时rdi、esi、esi当中的值分别是buffer地址,输入字符串长度,密钥所在地址。

按5次F8,执行到call Calc,F7步入。如图4所示,可以看出前面有3个跳转。

分别是判断输入是否为空,输入长度是否为1,输入长度是否大于0x63。此时需要记住一个值,即Calc的返回地址,如图5所示。

可以看出00007FFFFFFFBEB8当中存的就是calc的返回地址。在地址为400960处的代码上下断点,F9执行,此时观察寄存器的值可以看到RDI和RSI的值(分别为Memcpy的目的操作数和源操作数),如图6所示。

这是已知calc的返回地址是BEB8,Memcpy的目的操作数是BDA0。

0xBEB8 –0xBDA0 = 0x118.所以第0x118个字节将会覆盖返回地址。

注:实际实验中具体的地址可能有所变化,但是Memcpy目的操作数与Calc的返回地址的距離是不会变化的,所以不会产生影响。

我们可以通过这个设计我们的输入数据,如表2所示。

在输入数据的开始写入shellcode数据,第118个字节使用00007FFFFFFFBBE0来覆盖Calc的返回地址。

由于Overflow程序使用UDP socket来收发数据,所以首先要将数据数据写入文件(go.in)。然后使用cat go.in | nc–u 127.0.0.1 12345,将输入数据发送给Overflow。

由于此程序在执行Calc函数前使用了EncodeBuffer函数对输入的数据进行了加密,如图2所示。通过阅读代码可以发现在加密时使用字符串“abcdefghijklmn”作为加密的参数,所以可以判定此程序使用某种对称加密方式,以字符串“abcdefghijklmn”作为加密的密钥。根据对称加密算法的原理,对密文加密可以得到明文,我们可以将构造好的源POC文件作为第一次的输出,在程序对其加密后,将加密后的数据在IDA的内存窗口中进行读取,保存为新的POC文件。这样在漏洞利用时,对加密数据进行解密,就可以在程序获取正常的shellcode代码。

重新运行程序,在EncodeBuffer函数处下断点,右键点击RDI,选择Open register window,这时应该可以看到输入数据,一直向下浏览,可以看到连续的0,就是输入数据的结尾了。从第一个0,上面的一个数据开始,正是输入数据即00007FFFFFFFBEB0,这时按F8,此时的BEB0,就变成了加密后的数据。将该输入数据重新写到输入数据文件中。文件的数据应该是如图7所示。

重新运行程序,用修改后的输入数据作为输入。如图8所示。可以看到calc的返回地址成功被修改为输入buffer的内存地址。

由于程序使用了对称加密,只要将加密后的数据作为输入即可使用真正的数据。同理,将加密后的Shellcode作为输入,就可使用真正的Shellcode,如图9所示。

按F8执行后提示错误,如图10所示。

脱离IDA的调试环境,直接运行Overflow,用刚刚的数据作为输入,会发现段错误。

这说明该程序有数据执行保护(DEP|NX),还需要其他技术才能获取shell。但是对于一般的栈溢出程序,使用以上方法即可完全达到目的。

2.3 NX Exploit编写

虽然注入Shellcode无法执行,但是进程和动态库的代码段是必须要执行的,具有可执行属性,那么攻击者就可以利用进程空间现有的代码段进行攻击。

系统函数库(Linux称为libc)包含一个System函数,它通过/bin/sh命令去执行一个命令或者脚本,我们完全可以利用System来实现Shellcode的功能。根据Linux X86 32位函数调用约定,参数是压到栈上的,但是由于栈溢出漏洞,导致栈数据可以由我们控制,所以通过System函数可以执行任意代码。通过EIP将改写成System函数地址,从而去执行栈中执行的代码达到Shellcode的目的。这种攻击方法称之为ROP(即Return-Oriented Programming,,也称Return-to-Libc),即返回到系统库函数执行的攻击方法。

但是使用的环境是64位系统,它和32位系统的函数传参方式不同。32位系统使用堆栈来传参,而64位系统中使用RDI等寄存器来传递前六个参数,所以不仅需要控制系统栈,还需要控制RDI,这无疑给攻击增加了许多难度,但是这同样有方法办到,思路有三:(1)获取System函数的地址;(2)获取“/bin/sh”字符串的地址;(3)将RDI中的值,改成“/bin/sh”字符串的地址。

2.3.1 System函数地址

这个函数存在于libc.so中,一般都会被程序加载,也可以通过调试状态的IDA进行查看。获取System函数的方法有很多种,我们使用如下方法。编写一段程序代码如下:

intmain()

{

system();

}

然后使用如下命令:

gdb -p dummy

run

p system

一般来说结果如下,不同版本或环境产生的结果可能略有不同:

$1 = {} 0x7ffff7a5b640 <__libc_system>。

2.3.2 获取“/bin/sh”字符串地址

“/bin/sh”字符串的地址可以使用IDA的搜索方法,如图11所示,我们搜索得到该字符串的地址是00007FFFF7B91CDB。

2.3.3 设置RDI的值

由于该程序有数据执行保护,所以往栈中填充的数据并不能执行,因此如何控制RDI的值是一个难点。目前可采用的方法就是Ret2Lib,通过Calc函数的返回地址控制程序的RIP,同时也可以通过控制RIP来执行内存中已有的代码指令。只要在内存中找到“pop rdi, ret”语句,就可以根据控制栈中的数据为rdi赋值,再通过ret指令跳转到指定的地址。

由于pop rdi 的机器码是 5f c3,所以寻找pop rdi指令可以使用下面的命令:

objdump–d /lib/x86_64-linux-gun/libc.so.6 | grep–B1 c3 | grep–C3 5f

通过上面的指令可以搜索到libc.so文件中的5f c3 机器码所在的偏移地址,再通过IDA等工具可以获取libc.so的加载基址,计算出机器码所在的内存位置。在实际调试的时候我们使用的地址是00007FFFF7A3855E。

2.3.4 构造输入数据

现在已知信息如表3所示。

构造好的输入数据如图12所示。但是这个程序会将输入的数据加密,为了解密数据,我们将加密后的数据作为输入数据,如图13所示。

在这份输入数据中真正数据的前后各4个字节也要进行加密,这样在调试的时候可以方便的看到输入的3个地址数据的边界,成功后的效果如图14所示。

3 Linux内存防护总结

Linux系统对应用程序的保护主要有三个方面。

(1) SSP(Stack-Smashing Protectot):堆栈防溢出保护,它会在每个函数的栈帧底部添加一个随机字节,每次函数将要返回时,都会对个随机字节进行验证,如果这个随机字节被篡改,则说明该栈帧发生数据溢出,程序报错并终止运行。在编译时可以通过-fno-stack-protector选项取消这项保护。

(2)NX(Never eXecute):数据执行保护,在64位系统的CPU中增加了NX位,表示数据如果可写就不可执行。在Overflow这个程序中,我们对栈数据拥有写权限,但就没有了对栈数据的执行权限。

(3)ASLR(Address Space Layout Randomization):地址空间随机化,在每次程序加载运行的时候,堆栈数据的定位都会进行随机化处理。由于每次程序运行时堆栈地址都会发生变化,无疑给溢出利用增加了很大的难度。在本文中通过命令echo 0 > /proc/sys/kernel/randomize_va_space,取消ASLR保护。

整体上来说,操作系统层面上做的防护还是具有一定效果的,而且主流操作系统目前都将各种防护方式同时进行实施。在缓冲区溢出上的攻击与防御,一直是一个长期对抗的过程,今后,溢出漏洞利用的难度也会越来越高。

4 结束语

目前,在操作系统层面已经对缓冲区溢出攻击做了很多努力和防护策略。所谓“道高一尺魔高一丈”,这些安全策略也都有绕过的方式。SSP防护(即Windows中GS防护)可以通过攻击SEH链的方式,直接绕过栈帧底部的随机字节,使SSP防护直接失去作用。NX防护(即Windows中DEP防護)可以通过本文所利用的ROP技术,构造出一个ROP链,使Shellcode代码不保存在栈的数据区,通过其他模块的代码进行Shellcode代码执行从而绕过NX防护。ASLR给栈溢出攻击带来了很大难度,但是也出现一些攻击思路,包括寻找未启用ASLR的模块进行防护绕过,或者使用堆喷射技术在内存中添加大量空指令(NOP)代码,虽然地址空间不固定,但最后EIP依然会“滑落”到Shellcode代码中。

参考文献

[1] 王清.0day安全:软件漏洞分析技术[M].北京:电子工业出版社.

[2] 李承远.逆向工程核心原理[M].北京:人民邮电出版社.

[3] 林桠泉.漏洞战争:软件漏洞分析精要[M].北京:电子工业出版社.

[4] Matt Welsh & Lar Kaufman.Linux权威指南.[M]北京:中国电力出版社.

[5] Kris Kaspersky.Shellcoder编程揭秘[M].北京:电子工业出版社.

[6] 刘孜. Return-into-libc攻击及其防御.http://www.ibm.com/developerworks/cn/linux/1402_liumei_rilattack/.

[7] 0x2b0 Return into libc. http://www.dmi.unipg.it/bista/didattica/sicurezza-pg/buffer-overrun/hacking-book/0x2b0-exploit-in-not-executable-stack.html.

[8] Ben Lynn. 64-bit Linux Return-Oriented Programming.http://crypto.stanford.edu/~blynn/rop/.

[9] 海枫.使用ret2libc攻击方法绕过数据执行保护. http://blog.csdn.net/linyt/article/details/43643499.