二进制漏洞笔记—-栈溢出

二进制漏洞学习笔记_栈溢出

在阅读本文之前需要了解的一些东西如下

C语言
linux
逆向工程

刚开始所演示的漏洞会没有任何保护机制,到后来会逐步加上一些保护措施。

环境

Linux ubuntu 16.04.1 x86_64
ldd (Ubuntu GLIBC 2.23-0ubuntu9) 2.23

在正式开始讲解漏洞之前,引用一段维基百科关于 漏洞利用 的介绍

漏洞利用(英语:Exploit,本意为“利用”)是计算机安全术语,指的是利用程序中的某些漏洞,来得到计算机的控制权(使自己编写的代码越过具有漏洞的程序的限制,从而获得运行权限)。在英语中,本词也是名词,表示为了利用漏洞而编写的攻击程序,即漏洞利用程序。
经常还可以看到名为ExploitMe的程序。这样的程序是故意编写的具有安全漏洞的程序,通常是为了练习写Exploit程序。

我接下来就会演示一个”ExploitMe“的小程序。

<0>

这个程序非常简单,甚至不需要你写脚本,直接运行就能获得shell。
写这个程序的目的主要是为了使第一次接触漏洞的同学更好地理解栈溢出的原理。

废话不多说,先上代码。

image

由于现在针对栈溢出漏洞的保护措施也比较多,所以我们首先要关掉这些保护,这个过程和扒衣服差不多(捂脸)。

第一个关掉的就是 ASLR
简单讲,这个保护开启之后,程序的堆栈地址在程序每次启动的时候都是随机的。 想要了解详情可以百度。

image

输入上面命令,返回结果不是 0 就说明开了ASLR,想要关闭的话需要在root模式下输入如下命令

1
echo 0 > /proc/sys/kernel/randomize_va_space

第二个保护是 Canary.
简单讲,开启Canary之后,函数开始时在ebp和临时变量之间插入一个随机值,函数结束时验证这个值。如果不相等(也就是这个值被其他值覆盖了),就会调用 _stackchk_fail函数,终止进程。

第三个保护是 NX.
简单讲,开了NX保护之后程序的堆栈将会不可执行。

后两种保护可以在编译的时候一起关闭,编译命令如下:

1
gcc -fno-stack-protector -zexecstack -m32 -o 0 0.c

后面的那个 ‘0’ 是程序名,不是参数,不要误解了。

用如下命令可以查看程序的保护状态:

1
checksec filename

image

之后用gdb跑一下。

image

可以看到我们成功获得了一个shell。(撒花)

好了,我们要理解这个程序是如何获得shell的,我们首先要搞懂栈溢出是个什么东西。

简单讲,每个函数在调用的时候,都会在栈开辟一段新的空间,esp指向栈的顶部,ebp指向栈的底部,esp和ebp中间就是储存的一些参数和临时变量,紧接着ebp下面就是函数的返回地址。
如果我们临时变量所储存的字节超出了它的上限,那么多出来的部分会接着往下覆盖栈空间,这就造成了栈溢出。 如果足够多,就会覆盖到ebp的位置,然后就是返回地址。
那么,我们要是精心构造我们的输入,我们就可以控制其他变量的值,改变ebp的值(ebp里面的值保存的是上一个函数的ebp),甚至使函数返回到任意地址(控制eip的值)。这就是栈溢出的利用了。

下面附上一张函数栈帧示意图
image

那么,一个问题来了,我要怎么才能知道我需要覆盖多少数据才能覆盖到返回地址去控制程序流程呢?

当然这是可以手动计算的,想将数组填充满,然后使用gdb调试,计算数组末尾的数据所在地址与返回地址的位置的偏移就可以了。
其实我们有更简便的方法可以计算,不过由于这个程序的特殊性我们暂时用不了,这里就直接告诉你们。需要填充140个字节,140字节完了以后就是返回地址了。

清楚这个之后,我们就可以通过控制返回地址去执行我们想要执行的代码了,由于这里还没有能使我们达到目的的代码,所以我们还需要输入我们精心构造好的代码(也就是shellcode)去达到我们的目的。

image

这就是我们要执行的代码了。
大概意思就是使用linux的系统调用去执行 sh
这里给出linux系统调用的查询表。
linux SystemCall
具体什么是系统调用,百度一下。

但是我们是不能直接输入汇编语句的,所以需要将其转换成机器码输入。也就是开头给出的程序那个样子,数组开头的数据就是机器码。数组末尾写入的地址是shellcode第一个字节的地址。
返回到我们算好的这个地址之后就会直接执行shellcode了。

好了,那么还有一个问题,我们怎么把汇编语句转换成机器码呢?

可以将shellcode写进汇编程序,编译后再反汇编查看。(这样比较麻烦)
我以前是直接OD打开一个程序,直接在里面写汇编语句,左边就会有机器码了。(但是这不适合所有的情况)