C#不为人知的秘密-缓冲区溢出
开场白
各位朋友们,当你们看到网上传播关于微软windows、IE对黑客利用“缓冲区溢出”、0day漏洞攻击的新闻,是否有过自己也想试试身手,可惜无从下手的感慨?本文将完全使用C#语言,探索那些不为人知的秘密。
1.本文讲述在C#中利用堆栈缓冲区溢出动态修改内存,达到改变应用程序执行流程的目的。
2.如果你是高手,请指出本文的不足。
3.为了让本文通俗易懂,代码将极尽精简。
现在开始
我们知道,当数组下标越界时,.NET会自动抛出StackOverflowException,这样便让我们可以安全的读写内存,那么我们有没有逾越这个自动检测的屏障,达到我们非常操作的目的呢?答案是有的,而且我们可以修改一些关键变量如if、switch的判断值,for循环变量i值,甚至方法返回值,当然理论上还可以注入代码、转移代码执行区块,前提是必须在unsafe代码里。
方法在被调用时,系统会进行以下几项操作:将该方法入栈、参数入栈、返回地址入栈、控制代码区入栈(EIP入栈)。我们想要访问方法的栈内地址,常规的托管代码是不行的,只能使用unsafe代码,但也并不是说你非要精通C/C++语言和指针操作,本文的例子都非常简单,完全可以将指针就认为是简版C#数组。
改变临时变量的值
先给出一段代码,然后再详细解释原理。
代码
{
//在栈上申请一个只能保存一个int32的内存段
int* p = stackalloc int[1];
for (var i = 0; i < 30; i++)
{
System.Threading.Thread.Sleep(200);
Console.WriteLine("{0}\n", i);
p[i] = 0;
}
Console.ReadLine();
}
这是一个既简单,但是对于从没有尝试这样写过代码的开发者来说,又颇耐人寻味,C#(包括C/C++)不会去检查指针p的偏移量是否越界,那么这段代码将会顺利编译并运行,那么for循环会顺利执行30次吗?还是......
结论是,这将是一个死循环,因为p不断的递增1偏移,并将附近的内存的值全改为0,而局部变量i是靠p最近的变量,所有当p[i]的偏移地址等于i的地址时,代码p[i]=0就等价于i=0,实际上我在测试中i=6的时候i的值就被覆盖为0了,我在代码中添加了Thread.Sleep(200)和Console.WriteLine("{0}\n", i)就是让大家能更直观的看到程序的执行过程,当然这里也可以改为p[i]=1,p[i]=2等数字
搜索内存值并修改
还是先给出代码
{
Console.WriteLine(Change_Result());
Console.ReadLine();
}
static unsafe int Change_Result()
{
int i = 0;
//变量result,默认的返回值
int result = 123;
//申请一段栈内存,大小可随意设置
int* p = stackalloc int[1];
//从当前栈地址开始向下查找与函数返回值相匹配的地址,一旦匹配则修改为10000
while (true)
{
if (p[++i] == 123)
{
p[i] = 10000;
break;
}
};
return result;
}
变量result作为方法的返回值默认为123,并且没有任何显式修改其值的代码,关键在这里
while (true)
{
if (p[++i] == 123)
{
p[i] = 10000;
break;
}
}
这段代码找到值为123的内存地址(也就可能是变量result的地址),然后将其值修改为10000,当然,函数返回值就肯定不会再是原先的123咯
这就是经典的StackOverFlow的两个例子,希望通俗易懂能让大家所接受,另外缓冲区溢出并不只是改变内存的值,在高手的手里,他还可以执行任意代码,因为方法执行的时候总会有一个指针指向方法即将执行的下一条指令,如果控制了这个指针,就控制了进程。