实验目的
- 通过对程序输入的密码的长度、内容等修改用
Ollydbg来验证缓冲区溢出的发生; - 完成淹没相邻变量改变程序流程实验;
- 完成淹没返回地址改变程序流程实验。
理解程序
阅读并理解代码
1 |
|
在主函数内输入密码
password跳转到子函数
verify_password()判断输入的密码是否正确即判断输入的密码password与正确的密码1234567是否相等,如果相等则子函数返回0,否则返回非0,在函数返回之前将输入的password拷贝到数组buffer[8]里面主函数在判断子函数
verify_password()返回值:如果是0,则输出Congratulation! You have passed the verification!,结束程序,否则输出incorrect password!,继续输入密码password重复上述过程
验证缓冲区溢出
(通过对程序输入的密码的长度、内容等修改验证缓冲区溢出的发生)
原理
下图是栈中缓冲区示意图,其中每一行占四个字节,发生缓冲区溢出只需设置数组buffer[8]的长度大于8字节。是buffer的值覆盖authenticated等的值。

操作过程
使用
Ollydbg打开overflow_var.exe文件
同时按下
Alt和F9执行到用户代码如下图:在地址为00401724处CALL test.00401014
找到地址
00401014处JMP test.main,即此处向下为用户代码,通过分析用户代码,发现00401030至0040107F为子函数verify_password()反汇编代码
分析反汇编代码,找到源代码
strcpy(buffer,password)所在的位置发现在00401064处,设置断点。然后点击运行,输入密码为444程序走到这个语句时会自动停止,接着点击不进入函数的单步调试按钮
调试过程中观察右下角栈数据,发现两个地址被赋值为
444其中4的ASCII码为0x34

继续运行,输入密码为
AAAAAAAAABC,重复上述操作,得到结果如下,由于填写的字符串超过位数,已经将邻接的变量覆盖,即缓冲区已经溢出。其中A的ASCII码为0x41。其中buffer[8]的地址为0012FB18——0012FB1F,authenticated的地址为0012FB20——0012FB23。

淹没相邻变量改变程序流程
原理
下图是栈中缓冲区示意图,其中每一行占四个字节,只需增加数组buffer[8]的长度将原来authenticated的值覆盖掉,更改为想要的值即可,即如果想要跳过密码,只需要将authenticated的值覆盖为0。

操作过程
输入错误密码
1234,查看buffer[8]和authenticated的值
Buffer[0-7]:0x34333231 CCCCCC00(其中00是字符串结束符标识)Authenticated:0XFFFFFFFF(因为1234<1234567,authenticated的值为-1,写成补码的形式即为0xFFFFFFFF)运行结果如下

输入错误密码
2345,查看buffer[8]和authenticated的值
Buffer[0-7]:0x35343332 CCCCCC00(其中00是字符串结束符标识)Authenticated:0X00000001(因为2345>1234567,authenticated的值为1,写成补码的形式即为0x00000001)运行结果如下

输入正确密码
1234567,查看buffer[8]和authenticated的值
Buffer[0-7]:0x34333231 00373635(其中00是字符串结束符标识)Authenticated:0X00000000运行结果如下

根据上述三次密码输入和对代码的分析,当
authenticated的值为0x00000000时,才能得到正确的输出结果,即跳过密码。为此,采用将密码输入为
8位(这8个数要比1234567大,否则会导致authenticate补码较大如0Xffffff00),即末尾的空白结束符将authenticated的0x01淹没。随机输入密码
23456789查看buffer[8]和authenticated的值
Buffer[0-7]:0x35343332 39383736Authenticated:0X00000000Buffer的结束符淹没了authenticated的值使authenticated的值为0x00000000运行结果如下

显示密码输入正确,实现了淹没相邻变量改变程序流程。
淹没返回地址改变程序流程
原理
下图是栈中缓冲区示意图,其中每一行占四个字节。只需要先放置16个字符串,然后接下来的4个字节就能够淹没返回地址,达到控制返回地址的目的。

操作过程
由于返回地址的数据有些不是能通过可见的
ASCII字符表示的,修改程序,让文件作为输入源使用
ollydbg打开该overflow_ret.exe文件,通过查找字符串发现如果密码正确,程序会到地址0x0040112F出继续执行程序, 接下来的工作就是将返回地址修改为0x0040112F
在
overflow_ret\Dbug目录下新建password.txt文件,使用UltraEdit进行编辑(记事本只能输入可见的文字,有局限),利用Ctrl+H进入二进制编辑模式,前16个字节输入AAAAAAAAAAAAAAAA,在17-20字节上填下地址0x0040112F(注意从右向左填) ,保存文件
使用
ollydbg再次调试overflow_ret.exe文件,会直接弹出Congratulation! You have passed the verification!,即密码输入正确
由于修改了返回地址,导致栈平衡出错,最后会显示调试的程序无法处理例外

思考题
- 以
StackOverrun程序为靶子,通过自己使用ollydbg调试,两个要求:其一,要求分析PE格式加载到内存中的地址变化;其二,挑选其中一处函数的跳转,详细分析,跳转时sp,bp,ip的变化,要求以程序运行的顺序记录跳转时的这些寄存器的变化。 - 在不修改源代码的情况下,修改
StackOverrun程序的流程,通过淹没返回地址,用jmp esp的方式,让其调用bar函数并输出结果
分析源代码
程序由三个函数组成,一个main函数,两个子函数foo和bar,main函数打印两个子函数的起始地址并调用foo函数。foo函数打印当前栈顶向下40个字节的地址,bar函数打印一串字符串,正常情况下程序不会调用bar函数。
1 |
|
分析内存地址变化
根据程序显示,foo()的起始地址为0x00401000,bar()的起始地址为00401060,分析汇编代码知道main()函数的起始地址为0x00401070,在这三个位置分别设置断点,然后进行调试分析。
把入口地址
0x00401000压入栈中
把要打印的
Address of foo = %p的地址0x004070DC压入栈中并转到地址0x004010B0处
执行返回后,将栈顶指针
ESP地址增加8字节,ADD ESP,8把入口地址
0x00401000压入栈中
把要打印的
Address of bar = %p的地址0x004070C4压入栈中并转到地址0x004010B0处
将
ECX压入栈中
将返回地址
0x0040109E压入栈中,并跳转到地址0x00401000处即跳转到函数foo处
将
ESP地址降低0x0C字节SUB ESP,0C
将
ESI和EDI压入栈
把要打印的
My stack looks like: %p %p %p %p %p %p %p %p %p %p的地址0x00407070压入栈中并转到地址0x004010B0处
执行返回后,将栈顶指针
ESP地址增加4字节,ADD ESP,4
程序执行到
0x0040101B处便无法执行已经执行完毕
分析sp,bp,ip变化
分析程序由main()函数跳转到foo函数时sp,bp,ip的变化
在程序跳转之前位于地址
0x00401098处,操作是将ECX压入栈
此时各个寄存器的情况如下,
ECX的值为0x00000000,ESP的值为0x0012FF7C,EBP的值为0x0012FFC0,EIP的值为0x00401098
内存分布情况如下图所示

向下执行一步,此时
ECX已经入栈,各个寄存器的情况如下,ESP向上移动四个字节,值为0x0012FF78,EBP的值不变,为0x0012FFC0,EIP的值为0x00401099
内存分布情况如下图所示

执行
CALL stack0ve.00401000进行跳转,各个寄存器的情况如下,ESP向上移动四个字节,值为0x0012FF74,EBP的值不变,为0x0012FFC0,EIP的值为0x00401000
内存分布情况如下图所示,新增加的栈顶存储的是
main()函数的返回地址0x0040109E
继续执行
SUB ESP,0C,栈顶向上移动12字节,各个寄存器的情况如下,ESP值为0x0012FF68,EBP的值不变,为0x0012FFC0,EIP的值为0x00401003
内存分布情况如下图所示,新增加的栈从
0x0012FF6C至0x0012FF73没有存储任何值,0x0012FF68至0x0012FF6B存储的是地址0x00407128
小结:函数跳转时先将调用参数入栈(
0x0012FF78),然后将返回地址入栈(0X0012FF74),最后将局部参数入栈(0x0012FF68-0x0012FF73)
修改StackOverrun程序的流程
通过淹没返回地址,用jmp esp的方式,让其调用bar()函数并输出结果
程序运行结果如下

根据上述分析和程序运行结果可知输出的十个地址中,
0x0040109E为main()函数的返回地址。根据源代码分析,只需在程序后输入地址覆盖0x0040109E为0x00401060即可在程序后输入 “**AAAAAAAAAAAAAAAAAAAA
@**" ,其中 "**@ **”的十六进制ASCII码为0x601040得到结果如下
发现输出的是个地址中最上面的两个与程序后面输入的内容无关,故输入为“**AAAAAAAAAAAA
@** ”,程序运行结果如下,成功调用bar()`函数并输出结果