一、strcpy()函数简介
首先附上一个strcpy()函数简单的溢出示例
参考链接:https://blog.csdn.net/yahohi/article/details/7724669
// test0219_2.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "string.h"
int main(int argc, char* argv[])
{
printf("Hello World!\n");
char s[]="123456789";
char d[]="888";
strcpy(d,s);
printf("result: %s, \n%s\n",d,s);
return 0;
}
代码分析
首先,将 s[] 中分为三次存入内存单元,首先将1234放入【ebp - 0Ch】开始的内存单元,然后5678放入【ebp - 8h】开始的内存单元,9+字符串结束符 “ \0 ” 。
汇编情况如下:
堆栈情况:可以发现123456789按顺序进入了内存单元(从右至左依次进入高内存地址单元),然后第二个内存单元进入低地址内存单元即两个字符串依次入栈)。 并且可以发现字符串最后的截至字符 “ \0 ” 在内存单元中为一个 “ . ” 点字符。
然后,我们可以发现 s= “123456789” 和 d = "888"存在一段的内存单元,顺序为:d + s , 即在内存中的数据为【888. 123456789. 】
如果我们将字符串S拷贝给d,那么d内存单元将会产生溢出,溢出的地址将会覆盖后面的数据(即字符串S的数据)。如下为溢出的覆盖内存空间。
以下是strcpy()函数的调用情况,可以看出 strcpy(d,s) 函数是先用 lea 将 s的首地址赋给ecx,然后将ecx的值push入栈,类似的后面将d的首地址赋值给edx,然后将edx的值入栈,之后调用call调用strcpy()函数,并且发现堆栈中ebp,esp的值没有发生变化,因此需要用add esp,8 平衡堆栈。
以下是溢出拷贝后的输出情况。
二、strcpy()函数溢出并定位到需要执行的代码地址
参考链接:https://www.cnblogs.com/Jimmy009/p/hsStackFlow.html
最终代码示例
// test0219_2.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "string.h"
void foo(){
printf("哈哈,我是被strcpy缓冲溢出的地方");
}
int main(int argc, char* argv[])
{
printf("Hello World!\n");
char output[8];
char name[] = "adcdefghijkl\x1E\x10\x40\x00";
//得到了foo的Address为0040101E,不同的电脑地址可能会不一样,系统地址可能会不一样。此段代码运行在xp系统中,其他系统暂时还没有进行试验
printf("foo Address : %p",&foo);//输出函数的首地址
strcpy(output,name);
return 0;
}
知识前提:
首先,由前面简单的shellcode溢出我们可以发现在系统按照定义变量的顺序将其从先到后依次压入高内存单元(即先高后低)。因此我们可以推测output[8]中的数据应该在name[] 数据的后面。
溢出查找
溢出测试代码:
注意:我们在这边用abc… 而不用aaa…进行设置是因为后期我们需要更具字母进行判断shellcode地址,而abc…的ASCII的不一样便于我们查找。
char name[] = "abcde";
首先,将这个代码进行单步调试,发现和我们设想的一样,strcpy之后,output[] 和 name[] 紧紧挨在一起。由于name为6(5个字母+字符串结束符 “\0” )不满8(output为8),因此后面内存中的数据两个字符,这样刚好是8个字符。
长度大于8个:output是刚好挨着name[]。
不断地改变 name[] 的长度,并进行单步调试,知道我们发现所有程序结束后,程序跳转到一个不知名的地址。(如下便是我们这个程序结束的最后一行代码,后面的汇编为 int main() 函数自带的平衡堆栈的一些操作。)
在不断改变name[] 长度时,我们发现在最后 call __chkesp(004011a0) 清理堆栈时,eip都会入栈,且入栈位置在output[8]的后四个元素的位置。如下图。
很好,,,关键时刻我的xp卡死了运行,不出来了,唉,也可能是担心我身体让我早点睡觉吧。算了,简要说一下吧。
由上图我们发现在执行完call __chesp后,此时的 eip 地址会进入内存单元中,然后会执行下一句,并且当我们不断增加name[]长度时,我们发现这段内存单元可能被覆盖,并且一旦内存单元发生覆盖(一个字节覆盖也是覆盖)生成新的地址(暂且叫它NewEip吧),我们会发现当执行下一句mov esp, ebp(xp坏死,,,不是很确定,但一定是这据或者call这句代码)时候,程序会将新地址NewEip赋值给eip,即跳转到NewEip所对应的内存空间。
因此,如果我们用自己的代码将 eip 的地址按照我们想要改变的方向进行改变(这里是将函数foo的地址干好刚好覆盖 eip 之前的地址),那么下一步程序将会执行我们设定好的程序。
我们可以用abc…的不断增加来判断覆盖的字符个数,即看地址中含有abc…16进制字符的个数。例如,a十六进制为61,