本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!
《深入理解计算机系统》3.38题解——缓冲区溢出攻击实例(续1)
1. 问题描述
见http://blog.csdn.net/livelylittlefish/archive/2009/12/27/5087640.aspx
2. 目标分析与题解
看源程序可以发现,好像无论输入什么数据,其返回结果都是1,打印出来的都是0x1,似乎无从下手。另外,我们攻击的目标只是让其返回0xdeadbeef,不干别的。
实验环境:Winxp + cygwin + gcc(3.4.4)
2.1 修改源程序?
这是题目不允许的。题目的要求是输入一些数据,使其输出为0xdeadbeef。输入什么样的数字串?这就是本题要求解的。
2.2 如何得到这个数字串?
如果了解程序的编译、链接、执行的过程即可知道,此题不是无解。
由《过程调用与栈帧》一文可知,过程调用时要将参数和返回地址压入栈中,然后进入被调过程执行,待从被调过程返回时,弹出返回地址,将该地址存入%eip寄存器,并转到该地址开始执行。入栈和出栈均是对%esp指向的内存操作,其基地址为%ebp。
假设,如果我们输入的是机器码,存放于该缓冲区中,注意到程序中的缓冲区只有12字节,利用缓冲区溢出,将本应存放test调用getbuf的返回地址的内存单元修改为存放可执行的机器码的地址,而这个地址就是我们的缓冲区地址,那么在调用完getbuf后会返回到缓冲区地址处执行我们输入的机器码,在机器码中让程序输出oxdeadbeef,并正确返回继续执行。这样会不会达到目标?
这句话有些拗口,总结来讲,就是将存放test调用getbuf的返回地址的内存单元的内容修改为buf缓冲区的地址。
不对源程序进行编译、汇编,我们先手工画出test调用getbuf的栈帧结构。
stack bottom
high address |=================|<----+
| | |
| | |
| | |
| | 'main' stack frame
| | |
| | |
| | |
|=================|<----+
%ebp'-> | ... | |
| | |
| ... | 'test' stack frame
| | |
|-----------------| |
+4 | retAddr | | //retAddr is the address of 'printf' at line 13
|=================|<----+
%ebp-> | %ebp' | | //this saved %ebp' for test stack frame
|-----------------| |
| ... | |
| | |
|-----------------| |
^ | | |
| | | |
| | | 'getbuf' stack frame
buf-> | | |
|-----------------| |
| ... | |
|-----------------| |
| buf addr | | //this is parameter to getxs
|-----------------| |
%esp-> | retAddr' | | //retAddr' is the address of 'return' at line 5
|=================|<----+
| | |
| | |
| ... | 'getxs' stack frame
| | |
| | |
low address |-----------------|<----+
stack top
其中,
'main','test','getbuf','getxs'均为函数名
%ebp'即为'test'栈帧的基地址
%ebp保存'getbuf'栈帧的基地址
%ebp+4保存test调用'getbuf'的返回地址,即retAddr
%esp指向'getbuf'栈帧的栈顶,其中存放retAddr'
retAddr是test调用getbuf后的返回地址,即程序第13行printf函数的地址
retAddr'是getbuf调用getxs
我们输入的数据存放在buf指向的一段内存中,且数据由低地址到高地址存放(图中向上箭头所示)。
如果输入的数据不覆盖%ebp+4指向的内存,即buf不溢出,getbuf将永远返回1。
如果输入的数据覆盖%ebp+4指向的内存呢?即buf缓冲区溢出,会出现什么样的情况?
不能覆盖%ebp指向的内存,即其中的%ebp'不能被修改,如果被修改,程序将不能正确返回,甚至崩溃。
如果%ebp+4指向的内存被修改为newAddr,即retAddr被覆盖而存入newAddr,则test调用getbuf后将返回到newAddr处执行,newAddr要存放一段可执行的代码。
这个newAddr是谁?
如果新填入%ebp+4的newAddr是buf缓冲区的起始地址,是否能达到目标?即test调用getbuf后将返回到buf的起始地址处执行。如果newAddr是别的地址,这个地址无从得知,程序也变得不可控。因此,newAddr就是buf的起始地址。
到此,基本可以确定如下内容:
a. 输入的数据大致内容
可执行机器码
%ebp'
newAddr
其中%ebp'必须还是存放在%ebp指向的内存中,newAddr存放在%ebp+4指向的内存中,newAddr=buf。
b. 输入数据的长度
%ebp+8-buf
其中,可执行机器码的长度为%ebp-buf;接下来的任务就是获得这段可执行的机器码。
2.3 如何获得可执行机器码?
首先,我们应该确定可执行机器码的功能:
a. 让程序返回0xdeadbeef
b. 返回到%ebp+4指向的返回地址