CSAPP(深入理解计算机系统),是一本从程序员的角度分析计算机系统组成的书。
今天在做书中的一个习题,有关于缓冲区溢出的,大家应该都听说过88年美国的著名的蠕虫病毒,它用的是figure应用程序的一个缓冲区溢出漏洞,才得以在互联网上大肆传播的,原理一样,但是具体的操作和难度就大不相同了。
我们实验的代码如下:
有写C程序经历的人都看的出来getbuf()提供的用于存放字符串的空间为12字节,但是getxs()或者getbuf()函数并没有对写入字符串的长度作一个限制,C语言不像java,本身也没有这个数组越界检测机制,这就算是程序中的一个缓冲区溢出漏洞,为我们的实验提供了机会。
getbuf()函数按照正常的运行顺序,它的返回值应该一直是1。我们要做的就是向程序输入特定的字符串,导致程序在运行是栈溢出,最后返回0xdeadbeef,而不是1。
网上有很多的将缓冲区溢出的例子,我这里就不详细讲解了,这里说一说两种不同的方法和我遇到的问题。
第一种方法
程序运行时的函数栈结构如下:
(高地址)
++-----------------++ <---进入test函数
++ Return Adresss(main) ++
++-----------------++
++ %ebp ++
++-----------------++
++ int var ++
++-----------------++
++ ....... ++
++-----------------++ <----进入getbuf函数
++ Return Adresss(test) ++
++-----------------++
++ %ebp ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++ <--------buf(从这里开始,向上12个字节的内存)
(低地址)
(高地址)
++-----------------++ <---进入test函数
++ Return Adresss(main) ++
++-----------------++
++ %ebp ++
++-----------------++
++ int var ++
++-----------------++
++ ....... ++
++-----------------++ <----进入getbuf函数
++ Return Adresss(test) ++
++-----------------++
++ %ebp ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++ <--------buf(从这里开始,向上12个字节的内存)
(低地址)
我们从buf函数的地址开始写入字符串,大家也都知道函数内所有的函数的内部的变量都是存储在栈结构中的,test()函数中的int val;局部变量也是存在栈结构中的。
上面的汇编代码 ,我们可以看到,test函数调用完(call 8048524 )函数之后要执行的下一条指令是
就是将函数getbuf的返回值(整数、指针的返回值一般存储在%eax中)存入-0×4(%ebp)中,就是上面栈结构中int val的位置,栈是向地地址增长的,int val;在%ebp的下面,所以这里是减去4。
上面的几行代码是对printf函数的调用操作,这里我们要通过栈溢出做的是
1, 直接更改int val的值,int val存储在-0×4(%ebp)
2, 更改getguf函数的返回地址,改掉他原来的返回地址
1, 直接更改int val的值,int val存储在-0×4(%ebp)
2, 更改getguf函数的返回地址,改掉他原来的返回地址
而让%eip直接返回到
这句代码执行。这样我们在不改变其他%ebp,直接覆盖val就可以达到让test输出自己想要的值。
这种方法我没有亲自做实验,就说这么多吧,要想理解这些,得需要计算机知识。
这种方法我没有亲自做实验,就说这么多吧,要想理解这些,得需要计算机知识。
第二中方法
这中方法是CSAPP书中提示我们使用的方法,也是我实验中使用的方法。
程序运行时的函数栈结构如下:
(高地址)
++-----------------++ <---进入test函数
++ Return Adresss(main) ++
++-----------------++
++ %ebp ++
++-----------------++
++ int var ++
++-----------------++
++ ....... ++
++-----------------++ <----进入getbuf函数
++ Return Adresss(test) ++
++-----------------++
++ %ebp ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++ <--------buf(从这里开始,向上12个字节的内存)
(低地址)
(高地址)
++-----------------++ <---进入test函数
++ Return Adresss(main) ++
++-----------------++
++ %ebp ++
++-----------------++
++ int var ++
++-----------------++
++ ....... ++
++-----------------++ <----进入getbuf函数
++ Return Adresss(test) ++
++-----------------++
++ %ebp ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++
++ buf ++
++-----------------++ <--------buf(从这里开始,向上12个字节的内存)
(低地址)
这种办法是在buf中注入自己想执行的代码,然后想办法让%eip跳转到buf地址执行我们的代码。这里我们改变Return Adresss(test)的值,它的值是test函数中的一个代码的地址,这里我们把它改成buf的地址,当getbuf函数要返回时,它就不是返回到test函数内执行了,而是返回到buf中执行我们注入的代码了。当然,为了不让程序崩溃,我们在buf中的代码出了做自己要作的事情外还要ret到test中正确的代码执行。注意Return Adresss(test)和buf之间的 %ebp 的值是不用更改的。
buf的地址,%ebp的值,Return Adresss(test)的地址我们可以通过GDB来获得。
我们的注入的代码,我是使用objdump这个程序来获得的,自己一个个编太麻烦。
汇编代码如下:
我们的注入的代码,我是使用objdump这个程序来获得的,自己一个个编太麻烦。
汇编代码如下:
gcc -c d.s 产生d.o文件
然后通过objdump -d d.o获得我们要的机器码
然后通过objdump -d d.o获得我们要的机器码
这里我们将
68 53 85 04 08 b8 ef be ad de c3 90 d8 ef ff bf ac ef ff bf 00 00 00 00
输入到程序中就会返回0xdeadbeef这个值了。
68 53 85 04 08 b8 ef be ad de c3 90 d8 ef ff bf ac ef ff bf 00 00 00 00
输入到程序中就会返回0xdeadbeef这个值了。
注入后的内存结构(小端结构)
(高地址)
++-----------------++ <---进入test函数
++ Return Adresss(main) ++
++-----------------++
++ %ebp ++
++-----------------++
++ int var ++
++-----------------++
++ ....... ++
++-----------------++ <----进入getbuf函数
++ Return Adresss(test)( bf ac ef ff bf) ++//buf地址
++-----------------++
++ %ebp(d8 ef ff bf ac) ++//与原来的值相同
++-----------------++
++ ad de c3 90 ++
++-----------------++
++ 08 b8 ef be ++
++-----------------++
++ 68 53 85 04 ++
++-----------------++ <--------buf(从这里开始,向上12个字节的内存)
(低地址)
(高地址)
++-----------------++ <---进入test函数
++ Return Adresss(main) ++
++-----------------++
++ %ebp ++
++-----------------++
++ int var ++
++-----------------++
++ ....... ++
++-----------------++ <----进入getbuf函数
++ Return Adresss(test)( bf ac ef ff bf) ++//buf地址
++-----------------++
++ %ebp(d8 ef ff bf ac) ++//与原来的值相同
++-----------------++
++ ad de c3 90 ++
++-----------------++
++ 08 b8 ef be ++
++-----------------++
++ 68 53 85 04 ++
++-----------------++ <--------buf(从这里开始,向上12个字节的内存)
(低地址)
运行结构如图:
接着写我遇到的问题
问题一(gcc的缓冲区防溢出机制)
我的的gcc版本是4.3.3版的,而gcc从4版之后就加入了函数栈防溢出机制。
汇编代码如下:
我的的gcc版本是4.3.3版的,而gcc从4版之后就加入了函数栈防溢出机制。
汇编代码如下:
这里的gcc产生的getbuf函数就上面多了几行代码,如下
因为我们每次的溢出操作,都是来覆盖%ebp,和Return Address的值,所以gcc把 %gs:0×14(gs段寄存器)的值写道内存中在,函数退出时,再来检测
xor %gs:0×14,%edx
这个值是否改变了。如果改变了就去执行缓冲区溢出处理函数(__stack_chk_fail),相同的程序每次执行%gs:0×14(gs段寄存器)的值都不相同,所以我们无法提前获得%gs的值,而我们想要溢出覆盖,必须的经过%gs:0×14,这个问题很难搞。
xor %gs:0×14,%edx
这个值是否改变了。如果改变了就去执行缓冲区溢出处理函数(__stack_chk_fail),相同的程序每次执行%gs:0×14(gs段寄存器)的值都不相同,所以我们无法提前获得%gs的值,而我们想要溢出覆盖,必须的经过%gs:0×14,这个问题很难搞。
问题二(linux系统提供的防溢出机制)
linux的内存寻址机制是线性寻址,每个进程就想使用了整个内存一样。
CSAPP作者使用的linux,是版本比较旧的。linux上程序每次的运行,进程的虚拟地址都是相同的,就是每次程序的执行
各函数的%ebp,Return Address都是相同的。
而我使用的linux系统比较新潮,提供了一种机制,每次运行时进程的虚拟地址都是随即生成的(我发现,这里的随即数产生的地址也不是那么随即),相同程序每次运行进程的虚拟地址都不同,这也使得我不能提前确定程序的的各种地址信息。
CSAPP作者使用的linux,是版本比较旧的。linux上程序每次的运行,进程的虚拟地址都是相同的,就是每次程序的执行
各函数的%ebp,Return Address都是相同的。
而我使用的linux系统比较新潮,提供了一种机制,每次运行时进程的虚拟地址都是随即生成的(我发现,这里的随即数产生的地址也不是那么随即),相同程序每次运行进程的虚拟地址都不同,这也使得我不能提前确定程序的的各种地址信息。
两个问题困扰了我一天,因为我之前没有在书上看到,也不知道有这两种差异,所以做实验老不成功。
解决的方法是,我可耻的将这两种机制都给关闭鸟,哈哈!
echo “0″ > randomize_va_space //关闭虚拟内存地址随即
gcc bufbomb.c -fno-stack-protector -g -o p //关闭gcc函数栈溢出保护
解决的方法是,我可耻的将这两种机制都给关闭鸟,哈哈!
echo “0″ > randomize_va_space //关闭虚拟内存地址随即
gcc bufbomb.c -fno-stack-protector -g -o p //关闭gcc函数栈溢出保护
这才得以成功。
没有评论:
发表评论