实验目的
- 通过对程序输入的密码的长度、内容等修改用
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 39383736
Authenticated:0X00000000
Buffer
的结束符淹没了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()`函数并输出结果