用gdb查看汇编指令刚进入foo时栈顶的值(%esp),如下所示:

可见%esp值的确是调用foo后main中下条待执行指令的地址,而代码所修改的也正是该值。%eip则指向当前程序(foo)的指令地址。
【示例2】暂存RunAway函数的返回地址后修改其值,使函数返回后跳转到Detour函数的地址;Detour函数内尝试通过之前保存的返回地址重回main函数内。代码如下:
编译运行后输出:
Run Away!
Come Home!
Run Away!
Come Home!
Segmentation fault
运行后出现段错误?There must be something wrong!错误原因留待读者思考,下面给出上述代码的另一版本,借助汇编获取返回地址(而不是根据栈帧结构估算)。
【示例3】在被调函数内修改主调函数指针变量,造成后续访问该指针时程序崩溃。代码如下:
运行结果如下所示:

根据打印出的地址及其存储内容,可得到以下堆栈布局:

&ptStrt为形参地址0xbff8f090,该地址处在main函数栈帧中。(int*)&ptStrt - 2地址存储主调函数的EBP值,根据该值可直接定位到main函数栈帧底部。(*((int*)&ptStrt - 2) - 8)为主调函数中实参ptStrt的地址,而*(int*) (*((int*)&ptStrt - 2) - 4) = 0将该地址内容置零,即实参指针ptStrt设置为NULL(不再指向全局结构gtTestStrt)。这样,访问ptStrt->member1时就会发生段错误。
注意,虽然本例代码结构简单,但不能轻率地推断main函数中局部变量ptStrt位于帧基指针EBP-4处(实际上本例为EBP-8处)。以下改进版本用于自动计算该偏移量:
当然,该版本已失去原有意义(不借助寄存器层面手段),纯为示例。
【示例4】越界访问造成死循环。代码如下:
在循环内部,当访问不存在的数组元素aucArr[10]时,实际上在访问数组aucArr所在地址之后的那个位置,而该位置存放着变量ucIdx。因此aucArr[10] = 1将ucIdx重置为1,然后继续循环的条件仍然成立,最终将导致死循环。
【示例5】缓冲区溢出。代码如下:
编译运行结果如下:

可见,当CarelessPapa函数调用结束后,并未直接执行Come Home的输出,而是转而执行NaughtyBoy函数(输出Catch Me),然后回头输出Come Home。该过程重复一次后发生段错误(具体原因留待读者思考)。
结合下图所示的栈帧布局,详细分析本示例缓冲区溢出过程。注意,本示例中地址及其内容由内嵌汇编和打印输出获得,正常情况下应通过gdb调试器获得。

首先,main函数将字符数组szArr的地址作为参数(即pszStr)传递给函数CarelessPapa。该数组内容为"0123456789AB\xe4\x83\x4\x8\x23\x85\x4\x8",其中转义字符串"\xe4\x83\x4\x8"对应NaughtyBoy函数入口地址0x080483e4(小字节序),而"\x23\x85\x4\x8"对应调用CarelessPapa函数时的返回地址0x8048523(小字节序)。缓冲区溢出攻击CarelessPapa函数内部调用strcpy库函数,将pszStr所指字符串内容拷贝至szBuf数组。因为strcpy函数不进行越界检查,会逐字节拷贝直到遇见'\0'结束符。故pszStr字符串将从szBuf数组起始地址开始向高地址覆盖,原返回地址0x8048523被覆盖为NaughtyBoy函数地址0x080483e4。
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-30832-2.html
1还是没用