黑客与C语言

zenny_chen 2017-06-24 12:27:30
“黑客”这个词想必我们已经如雷贯耳了。我们一听到黑客通常在大脑中的印象就是一群穿着黑衣,躲在小屋里偷偷用着数台电脑针对某组计算机神不知鬼不觉地进行攻击。他们通常会攻入一些网络或系统,潜伏在一些大型网站,窥探甚至窃取用户隐私,比如窃取你的QQ号、微信号、你的邮箱,诸如此类的事情。他们通常拥有高超的技术,于无形中做很多我们或惊叹或惊吓的事。他们就像《海盗船》的jack船长一样分明是“恶势力”,却又诡异、神秘,有超强的能力,而《骇客帝国》中的。。。
因而我们普遍对黑客们充满着恐惧感,同时也充满着神秘感。其实黑客有时候也是统称,也有灰客、白客。
然而,以上这只是狭义上的黑客,其实在现在的英语中用Cracker来描述这种转搞计算机系统以及网络系统破坏的人。而广义上讲,黑客(Hacker)对于程序员而言其实是指精通于计算机以及计算机网络的人。所以这么一来我们就能理解为何许多伟大的系统缔造者、编程语言缔造者能被称为黑客了,尽管他们并不是以破坏系统而闻名。
1. Unix系统创始人Dennis Ritchie
这里首先介绍的就是大名鼎鼎的Dennis Ritchie,于2011年12月逝世。他是伟大的Unix系统的创始人,同时也是著名经典的C编程语言的缔造者。曾在1983年从ACM获得图灵奖。在早些时候,Unix系统其实是用汇编语言开发的,那个时候Dennis Ritchie与另一个伟大的黑客Ken Thompson(现就职于Google,并打造了Go语言)在贝尔实验室一起实现了在DEC生产的PDP-7计算机上的Unix系统。那时,他俩准备将此操作系统移植到PDP-11上。刚开始,PDP-11上的Unix系统仍然是用汇编语言开发的,但是因为PDP-11与PDP-7的变化还是有不少的,所以那时候开发者打算用B语言来重写该系统。B语言是由Ken Thompson从BCPL编程语言简化而来的。然而,B语言无法很好利用PDP-11上的某些特性,比如字节寻址,这就使得Dennis Ritchie与Ken Thompson一起打造了更灵活、更强大的C编程语言。而C语言一开始也就是针对PDP-11计算机上的Unix系统而打造的。在1972年,Unix中的大部分代码都用C语言重写。到1973年,引入了结构体类型 struct 之后,C语言就基本成型了,因为它足够强大,所以足以担当Unix系统内核大部分功能的实现。而此时的C语言也被称作为“K&R C”。
当然,Dennis Ritchie也有他调皮的一面。在早期开发的Unix系统中,他特意留了一些后门。其他开发者用自己账号登录系统之后,他们发现自己的文件或某些资料被改动过,一直很纳闷。他们后来通过排查,发现了当时Unix系统的一个漏洞,把它堵上后,但没过多久自己的账号又被侵入了。后来才知道,原来是Dennis Ritchie在C语言编译器上埋下了后门,所以只要他们用编译器编一次程序,那么漏洞就会自动生成,哈哈……这个也让笔者联想起前两年很多iOS开发者通过百度网盘下载带有后门的Xcode,使得很多App受到木马侵袭,而且该木马能躲过Apple的代码审核机制。尽管该漏洞破坏性不大,因为iOS系统以及iOS设备处理器的本身运行安全机制很厉害,不过这也说明了来自编译器的后门是防不胜防的。
2. Linux系统内核缔造者Linus Torvalds
下面说的这位黑客应该大家非常熟悉了,就是大名鼎鼎的Linus Torvalds,Linux系统内核的缔造者,Git版本管理工具的缔造者。Linus Torvalds从1988到1996年在自己祖国芬兰的赫尔辛基大学修完了硕士学位。在此期间,他看了Andrew Tanenbaum的一本书《Operating Systems: Design and Implementation》,在此书中Andrew描述的是MINIX系统,该系统是Unix剥离下来的一个用于教学的版本。此时,由于在芬兰很难获得软件,所以这也促成了Linus Torvalds喜欢自己动手的习惯,他购买了一套Sinclair QL,然后自己为它写了一套汇编器以及编辑器,然后自己独立编写了一个类似吃豆人(Pac-Man)的小游戏,称为Cool Man。在1991年,他购买了基于Intel 80386的IBM PC,同时在此之前也收到了MINIX的一个拷贝,从而他就开始了在Intel 80386上的Linux内核开发。Linux的第一个版本正式版本1.0在1994年3月14号发布。在2005年,Linus创建了Git这一版本控制系统(VCS)的开源项目,基于GPLv2许可证。现在我们看到很多项目、工具以及网站都会默认使用Git工具进行版本控制,包括Xcode,GitHub等等。
当然,Linus Torvalds跟不少程序员一样,也有偏执、狂傲的一面。比如在开发Git项目过程中,有位开发者表示Git项目用的都是纯C语言而不是C++表示不可理解,而且也直言不讳:“别拿可移植性说事,那是屁话”。并且还指出,蛮力地直接操作文本,既啰嗦又易错,而且很难跟上高层代码逻辑。当时Linus Torvalds对此发出了强烈的不满!他一上来也爆粗口——“YOU * are full of bull shit”,紧接着他开始炮轰C++了,哈哈。大致意思如下:
C++是一种可怕的语言。而且因为有大量不够标准的程序员在使用而使情况更糟,以至于极容易产生彻头彻尾的垃圾(total and utter crap)。老实说,选择C就是为了把C++程序员踢出去。……我有这样的结论,任何喜欢用C++而不是C开发项目的程序员可能都是我希望踢出去的人,免得他们来搞乱我参与的项目。C++会导致非常非常糟糕的设计选择。你们这些C++程序员总是一上来就用语言的那些’漂亮的’库特性比如STL、Boost和其他彻头彻尾的垃圾,这可能对你们的程序有所‘帮助’,但是却会导致:
(1)当库无法工作时无穷无尽的折磨(别跟我说什么STL尤其是Boost很稳定而且可移植性很好,那全是屁话,而且一点都不可笑)
(2)低效的抽象编程模型,可能在两年之后你会注意到有些抽象效果不怎么样,但是所有代码已经依赖于围绕它设计的‘漂亮’对象模型了,如果不重写应用程序,就无法改正。
呵呵,其实Linus说得也确实没错。对于一些系统级项目,使用C++甚至更高级的编程语言可能反而会使整个项目难以维护,因为当代码量上升的时候,很多设计需要围绕原有的设计模型进行。这就好比洗衣机的滚筒,洗衣的次数多了,滚筒上就会慢慢沾满各种碎衣料,然后越滚越多。C语言的设计理念就是专注于功能模块,而不是以某个特定的设计模型为中心展开堆码,这也是C语言的灵活性所在。
以上我们谈到的是Hacker这个词,他是一个名词,用于表示某类人。而我们日常所说的黑客还能做动词使用,其实也就是对应英语中Hack这个单词。Hack本意是劈、砍、乱踢这类意思,因而当它用于计算机系统时就是指对系统进行猛烈攻击,从而找到其漏洞。现在由于Hack的使用范围又广了,它还能用于编程语言。像Apple在2014年推出Swift编程语言时就称它为Hackable programming language。这里的Hackable就是说该编程语言是可用来做各种另类玩法的,在现有语法体系中能玩出令人意想不到的效果,写出惊世骇俗的代码来。而C语言也是Hackable的。因为它灵活、强大,不死板,所以我们很多时候可以用C语言的语法糖实现各种相当不错的API封装以及功能实现。我这里举两个简单的例子。
像我们用C语言在开发一套程序时,有时为了调试方便会自己定义一个用于打印输出日志的接口,在调试模式将它开启,在发布模式将它屏蔽。对于遵循C99的编译器,我们通常会这么定义:

#ifdef DEBUG

#define DEBUG_LOG(...) (void)printf(__VA_ARGS__)

#else

#define DEBUG_LOG(...) (void)0

#endif

而对于不遵循C99标准的C语言编译器,并且不能使用不定参数个数的宏定义时我们如何定义呢?我们初步能想到的是以下这种方式:

#ifdef DEBUG

#define DEBUG_LOG (void)printf

#else

#define DEBUG_LOG (void)

#endif

这种定义方式基本没什么问题。不过当我们碰到以下这种代码时,这种定义方式在发布模式下的行为会与前面C99模式的有所不同。

int a = 10;

DEBUG_LOG("a = %d\n", ++a);

我们请注意看,这里在DEBUG_LOG中对所要打印的变量a进行了递增操作,从而使得变量a在调用DEBUG_LOG之前产生了副作用。依照C99那种定义方式,在发布模式下,由于整个DEBUG_LOG(…)这个宏被定义为了 (void)0,所以这里面的表达式都不会被扩展出来,因此++a这个表达式是不存在于源代码中的。而在上面C90模式下的实现方式由于没有屏蔽++a这个表达式,从而会使它产生副作用。那如果我们想在发布模式下与C99那种形式一样屏蔽掉DEBUG_LOG宏中所有表达式的副作用该如何实现呢?其实非常简单!在C90中就已经有了编译时行为的操作符——sizeof!sizeof操作符内的表达式在C90中是不会产生任何副作用的。同时,由于对于打印函数来说,必定存在一个表示字符串的表达式,因此我们也无需担心传入的表达式是否可能为void表达式。所以我们可以在发布模式下这么定义DENUG_LOG:

#define DEBUG_LOG (void)sizeof

非常神奇吧~正因为C语言有各种形式的类型、操作符、预处理器的存在,所以我们可以用它实现多种功能,从而达到自己期望的效果。
我们再举一个例子,是关于联合体的。联合体是个好东东!它一般用于抽象数据类型,从而可使得该数据类型在某一特定场合是一种类型,再另一种特定场合又是另一种类型,但是所占的存储空间是其中最大的成员那个。所以它既能做到类型抽象的作用,同时也能缩减存储空间,对于一些系统开发而言很有帮助。我们下面想定义一个联合体类型的常量数组,但是该常量数组中每个元素的数据类型可能是不同的。在遵循C99标准的代码中我们可能会这么写:

static const union MyData
{
int i;
float f;

}sc_list[] = {
{.i = 10},
{.f = 0.5f},
{.i = 20}
};

如果是在不遵循C99标准的C语言中该如何表示呢?我们这里能看到,第二个元素是直接对f单精度浮点类型的成员进行初始化,所以如果我们直接写0.5f那由于其实元素是int类型,编译器仍然会将它转为int类型,从而变为整数0。其实如果我们知道在当前C语言环境中倘若单精度浮点用的是遵循IEEE754规格化浮点数表示的话,那么我们可以直接用IEEE754对二进制浮点数的表示来做替换。这里,0.5在IEEE754标准中32位单精度浮点的表示为0x3F000000,所以我们可以用以下代码进行替代:

static const union MyData
{
int i;
float f;

}sc_list[] = {
10,
0x3F000000,
20
};

这就是一种Hack方法。感谢各位能看完此贴,本贴主要讨论了关于黑客的一些科普介绍,并且没有针对计算机与网络系统攻击做详细介绍。因为此类文章其实也已经有不少了,这里提供一篇比较不错的关于缓存溢出攻击的文章,讲得比较详细:http://www.cnblogs.com/fanzhidongyzby/archive/2013/08/10/3250405.html
此外,如果大家想了解Linus Torvalds炮轰C++更详细的信息,可见此贴:http://os.51cto.com/art/200709/55562.htm
最后推荐各位《C语言编程魔法书》,如果对Hack C语言感兴趣的,并想了解最新C11标准以及GNU11标准,掌握现代化C语言编程方法的朋友可以入手此书。京东链接为:https://item.jd.com/12737899067.html

作者:zenny_chen,C语言与汇编语言重度用户与拥趸者,现任上海证大喜马拉雅高级工程师,曾任安沃传媒移动客户端及HTML5技术研发总监兼首席科学家。多年高性能计算、嵌入式系统与移动互联网实践经验,深谙实时操作系统内核、设备驱动研发,对各种处理器架构、多媒体高性能计算编程以及移动端开发如数家珍,同时精通计算机底层基础技术与各种编程语言,尤其精通C/C++,Java、Objective-C以及Swift!现任CocoaChina社区的Swift编程语言讨论区以及代码例子区的版主。《OpenCL异构并行计算:原理、机制与优化实践》作者之一,App Store以及Mac App Store中CPU Dasher的作者。
...全文
587 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
zenny_chen 2017-06-27
  • 打赏
  • 举报
回复
引用 3 楼 kaijkl 的回复:
很牛逼的赶脚啊。黑客,科幻片即视感
啊哈哈哈……多谢捧场~
赵4老师 2017-06-26
  • 打赏
  • 举报
回复
Hack入门教程: http://edu.csdn.net/course/detail/2344 C语言指针与汇编内存地址-一.代码要素
cain-won 2017-06-26
  • 打赏
  • 举报
回复
很牛逼的赶脚啊。黑客,科幻片即视感
paschen 2017-06-24
  • 打赏
  • 举报
回复

15,440

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 非技术区
社区管理员
  • 非技术区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧