论程序脆弱与不可预见输入(C语言)
L 0ft FC Shoo
首先在开篇前我们先来看一个例子:一天,与你相识的一个邻居迎面向你走来,邻居为以示礼貌,对你说:“hi!今天天气真好!”“哦,我刚准备去吃饭。”你无奈的应答道。接下来两人的对话将如何进行呢,或者一次本来简单的打招呼变成以如此尴尬的场景结束。邻居在给出输入提示后收到了未预期的输入结果,以至于对话难以进行。在这,我们不妨称这样的尴尬场景为“对话脆弱”。
所谓“对话脆弱”就是不在预期范围内的应答使对话难以进行下去的情况。不过这在日常生活中并不普遍。
“对话”与程序的执行也有着相似之处,从而我们将脆弱的概念又引申至了程序上,就有了程序脆弱。
所谓“程序脆弱”就是不在预期范围内的输入流使程序难以进行下去或得出不可预期的结果(往往程序是不会自己识错的),看似这定义与“对话脆弱”基本相同,但重要的区别在于程序脆弱是常见的。
可能直接给出的“程序脆弱”概念让大家难以理解,那就先从这概念入手,看看程序“脆弱”在哪。这里我们又不得不回到“对话”的那个例子了:什么是“对话脆弱”的源头?是我对邻居的应答。那什么是“程序脆弱”的源头呢?是测不准的输入流。这里引入了一个新概念——输入流。
何为输入流?我们又得用一段来说明,但这是必要的。因为在这里,输入的不再局限在一个scanf(“%d”,&a)对a输入一个数,而是一个不可测的庞大数据流(输入流本身就是一种特殊的数据流),存在各种可能的输入,回是数字或字符,英语或中文,10进制或2进制等等,这太庞大了,我无法穷举出全部,但你必须得认识到在这同时这个数据流也是你无法完全预测的,(这里我们又要用到“对话”的例子了,我好像特别钟情于这个例子)就好比“对话”例子中我的超乎寻常的回答是“哦,我刚准备去吃饭。”,其实又何只这一种回答,在这就不去答了(也没这个必要),而这些回答便也是输入流。
了解了输入流与它的不可测性后,大家此时是否明白了程序脆弱的定义了呢!
在以上“长篇大论”的阐述后,相信聪慧的您应该明白了什么是“程序脆弱”了吧!但这还不够,“程序脆弱”是存在了,它的存在对程序本身有什么影响呢?
接下来就让大家来看看“程序脆弱”的危害吧!下面我将以黑客攻击中常用的一种手法——溢出攻击——来解释程序脆弱的危害。总所周知,计算机执行的指令是人给机器输入,但人并未直接向cpu(其实也可以是gpu,甚至未来*pu,不过本篇文章是以c为基础语言写的)输入指令,那么指令会经过一个什么样的过程得以实现的呢?在这里我们将涉及一点计算机硬件的知识。我们都知道,计算机存在一个叫缓存的东西,它用于寄存欲处理指令。同样的,当我们输入指令时,指令并未立刻执行,而是成为即将执行的指令存在了缓存中,好比一个scanf()语句,它会将输入流存到缓存中,直到遇到空白符或缓存满,这时缓存的指令才会传至cpu去执行。问题就出在这了:缓存并非无限大的,是会满的,试想一下,倘若我一次性输入的指令宽度大于了缓存的容宽,将会出现什么?多余的部分就会溢出缓存,溢到哪呢?上帝知道。当然黑客想让这些特殊指令溢到系统内存区中去了。当黑客们有意识并掌握了溢出的原理和尺度,不可测的输入流便成为了黑客们入侵他人电脑的伪装,这难道还不可怕吗?当然这只是个例子,只是想警示一下大家不可测的输入流带来的“程序脆弱”性会十分可怕。
既然我们知道了“程序脆弱”的存在与它的厉害,难道就没有方法去防范吗?
如果没有,我也就不会写这篇文章了,这也正我写这篇文章的目的。下面来看这样一段代码块:
while(scanf(“%d”,a)==1)
{代码块}
该代码块的作用是循环输入一个“整数”,为什么我重点强调了是整数,因为这样一行代码的存在让你无法输入除整数以外的字符(限定在c语言集中)。我们来分析一下这个代码,while的测试(test)是重点,它的作用是测试scanf()函数的返回值是否是1,而scanf()返回值等于该函数成功读取的变量的个数,例如上面代码中,若输入一个整数,则成功读取一个变量a,scanf()返回1;若输入一个“#”字符,则scanf()成功读取0个变量(即一个也没读到),则返回0。那么,当一个程序需要输入流只能是整数,这个代码将会有多么重要!依此类推,在编写其它程序(特别是交互式程序时)也会出现限定的输入才能得到正确的结果,如何确认或者说检查输入,便成为一个值得程序员深思的问题。但也有一点我不得不提醒一下,像这样输入流为同一种类型的到好办,但往往输入流需要在限定类型的情况下还要好几种类型时,输入流的不可测性和的它的庞大在次给我们造成难题。至此,我们不得不承认“程序脆弱”往往是不可避免。
那既然“程序脆弱”不可避免,干嘛还提出它来,其实提出它的原因不是为了在短时间内找到一个解决方案彻底根除它,而是警醒那些程序员们不要再“关着门来写程序”,也就是不要把程序仅仅看作是得出结果的工具,一味寻求漂亮的算法和简明的写法,这些固然重要,但“程序脆弱”也应被重视起来。
的确没有完美的代码,在我们不违背程序简明性的特点时尽可能顾全输入流的不可测性可能还不够,但做程序的基本步骤(参见c primer plus 第一章)不也包含程序的后期工程(所谓后期工程就是一些根据程序在实际运用过程中出现的问题作的一些补丁修正)吗?甚至后期工程在某些程序上显得尤为重要(例如网络浏览器,杀毒软件等)。
不要把程序当成是一次性的工艺品,也不要自命不凡能写出一次性完美的代码,做程序有如种棵树,只有精心的培育加上精心的呵护才能长成健壮的参天大树,但同时即使这样也不会是最健壮的“树”,所以当你看见有比你的“树”更健壮的“树”时,请不要责怪我之前在胡说,毕竟技术是在进步的,可能其它会有更好的“育树法”呢?甚至再过若干年,“程序脆弱”也不再存在了呢?其实我相信大家还是蛮期待也与我一样相信上述的疑问会有漂亮的解答。
谢谢。