栈溢出基础实验
TOC
修改函数返回地址,原理是利用栈溢出修改函数返回地址。
环境
//file: main.c |
编译器:VS2017 x86
编译选项:/GS- //GS为安全检测选项,vs2017默认开启/GS选项,/GS实验会使本实验失败
********************************************************************** |
分析
用键盘输入字符的ASCIl表示范围有限,很多值(如0x11、0x12等符号)无法直接用键盘输入,所以将程序的输入为从文件中读取字符串。
开始动手之前,我们先理清思路,看看要达到实验目的我们都需要做哪些工作。
- 要摸清楚栈中的状况,如函数地址距离缓冲区的偏移量等。这虽然可以通过分析代码得到,但我还是推荐从动态调试中获得这些信息。
- 要得到程序中密码验证通过的指令地址,以便程序直接跳去这个分支执行。
- 要在password.xt文件的相应偏移处填上这个地址。
这样verify-password函数返回后就会直接跳转到验证通过的正确分支去执行了。
首先用IDA加载得到可执行PE文件,如下所示。
.text:00131040 ; =============== S U B R O U T I N E ======================================= |
.text:00131000 ; =============== S U B R O U T I N E ======================================= |
阅读上面显示的反汇编代码,可以知道通过验证的程序分支的指令地址为0x001310C7
。0x001310A9处的函数调用就是verify_pasword函数,之后在0x001310B1处将EAX中的函数返回值取出,在0x001310B4处与0比较,然后决定跳转到提示验证错误的分支或提示验证通过的分支。
调用verify_password的时候。如果我们把返回地址覆盖成这个0x001310C9地址,那么在0x001310A9处的函数调用返回后,程序将跳转到验证通过的分支,而不是进入0x001310C7处分支判断代码。这样我们就可以按照如下方法构造password.xt中的数据。
仍然出于字节对齐、容易辨认的目的,我们将“1234”作为一个输入单元。buffer[8]共需要两个这样的单元。第3个输入单元将authenticated覆盖;第4个输入单元将前栈帧EBP值覆盖:第5个输入单元将返回地址覆盖。为了把第5个输入单元的ASCⅡ码值0x修改成验证通过分支的指令地址0x001310C9,我们将借助十六进制编辑工具Notepad++的HexEdit插件或者vscode的hexdump插件来完成(Ox40、0x11等ASCIⅡ码对应的符号很难用键盘输入)。
步骤1:创建一个名为password.xt的文件,并用编辑器打开,在其中写入5个“1234”后保存到与实验程序同名的目录下。
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F |
步骤2:使用编辑器的hex编辑模式
步骤3:将最后4个字节修改成新的返回地址,注意这里是按照“内存数据”排列的,由于“大顶机”的缘故,为了让最终的“数值数据”为0x001310C9,我们需要逆序输入这4个字节。
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F |
在运行该程序时便完成了栈溢出攻击。
由于栈内EBP等被覆盖为无效值,使得程序在退出时堆栈无法平衡,导致崩溃。虽然如此,我们已经成功地淹没了返回地址,并让处理器如我们设想的那样,在函数返回时直接跳转到了提示验证通过的分支。
改编自0day安全:软件漏洞分析技术(第二版)