首页 > 互联资讯 > 技术交流  > 

有哪些看似简单其实非常精妙的代码?

国外有个叫 Underhanded C 的比赛,直译过来就是不光彩/卑鄙的C竞赛,专门热衷于号召大家写这种“看似简单其实非常精妙的代码”,然后来打分颁奖,看看谁是最具有一肚子坏水儿的人才。

实际上,Underhanded C Contest 就是一项编程竞赛,只不过获胜的条件特殊些,不像ACM那样来做题,而是要求参加者用C编译语言写出看起来清晰干净可读,但实际上包含有恶意行为的程序,程序要求通过严格的检测,也就是题目中所谓的“看似简单其实非常精妙”。

这类看似正常,但涉及编写利用隐藏错误的程序,尽管具体任务有所不同,但一般规则均如下:

代码表面上应该被编写来执行一个平凡但却至关重要的任务该代码应该在实际运行当中实施恶意行为该代码必须尽可能清晰可读和简单无害

也正因为这几个奇奇怪怪的要求,所以导致这里常频出那些聪明的把戏,让人感到乐趣十足。

比如 2015年的挑战题 Faking Fissile Material 就很有趣,冠军的代码就很精妙[1]。

关于这道题的背景,这里直译一下,可去参考链接看原文[2]:

Alice国和Bob国已同意签署核裁军条约。在实践中,这是通过核检查员访问每个国家并验证弹头中是否存在诸如钚之类的裂变材料来实施的,此时可以摧毁弹头。理想情况下,检查员将对弹头进行扫描并观察被测物体的射线图或伽马射线光谱等图形,以便他们可以确认弹头包含它应该包含的内容。但 PGDRA 和 GDPRB 都对这种方法不满意,因为这些扫描的结果包含有关其核计划和核武器设计的敏感信息。然而,每个国家都希望确保对方正在拆除真正的核弹头,而不是假的。为此,两国同意建造一个带有“信息屏障”的裂变材料探测器——本质上是一个计算机程序,它将获取扫描结果,确定它是否与某些参考模式匹配,并仅输出“是”或者“否”。”考生的目标则是编写将测试测量值与参考模式进行比较的函数:

这是冠军 Linus Åkesson 提交的具有欺诈性的代码:

match.h#define N_SMOOTH 16 /* Size of smoothing kernel */ typedef double float_t; /* Desired precision for floating-point vectors */ int match(float_t *test, float_t *reference, int bins, double threshold); double spectral_contrast(float_t *a, float_t *b, int length); match.c #include <string.h> /* memcpy */ #include "match.h" static double total(float_t *v, int length) { double sum = 0; int i; for(i = 0; i < length; i++) sum += v[i]; return sum; } static void smoothen(float_t *v, int length) { double sum; int i, j; for(i = 0; i < length; i++) { sum = 0; for(j = 0; j < N_SMOOTH && i + j < length; j++) sum += v[i + j]; v[i] = sum / N_SMOOTH; } } static void differentiate(float_t *v, int length) { int i; for(i = 0; i < length - 1; i++) v[i] = v[i + 1] - v[i]; v[length - 1] = 0; } static void preprocess(float_t *v, float_t *source, int length) { memcpy(v, source, length * sizeof(*v)); smoothen(v, length); differentiate(v, length); smoothen(v, length); } int match(float_t *test, float_t *reference, int bins, double threshold) { float_t t[bins], r[bins]; if(total(test, bins) < threshold * total(reference, bins)) return 0; preprocess(t, test, bins); preprocess(r, reference, bins); return spectral_contrast(t, r, bins) >= threshold; } spectral contrast.c#include <math.h> /* sqrt */ static double dot_product(float_t *a, float_t *b, int length) { double sum = 0; int i; for(i = 0; i < length; i++) sum += a[i] * b[i]; return sum; } static void normalize(float_t *v, int length) { double magnitude = sqrt(dot_product(v, v, length)); int i; for(i = 0; i < length; i++) v[i] /= magnitude; } double spectral_contrast(float_t *a, float_t *b, int length) { normalize(a, length); normalize(b, length); return dot_product(a, b, length); }

这项代码主要包含两个源文件(match.c 和spectral_contrast.c)和一个头文件(match.h)。match.c 包含主要代码,spectrum_contrast.c 则包含辅助函数。

代码将谱图输入为float_t类型的数组,标头将其定义为双精度浮点变量。

然而,由于一时的“疏忽”,作者忽略了在spectrum contrast.c中包含match.h头文件。所以没有给出float_t的定义。幸运的是,spectrum contrast.c使用平方根函数,因此它需要包含内置的math.h头文件,这一切都很顺其自然——自然到你会忽略了在math.h的定义下,float_t是一个单精度浮点变量。

而这便是攻击的起源,实际结果是,这些值将以确定性的、非常有用的方式对数据谱图进行重新解释,让知情的攻击者可靠地伪造核材料的存在这一事实。

而且,除了这个小小的错误之外,代码在其它方面都是完全可行的、常用的,且写得极佳的C代码,它读起来的处理逻辑也正像问题描述所期望看到的一样。

具体的攻击过程是这样的:

可以看到,默认情况下 math.h 将 float_t 定义为单精度,但此头文件将 float_t 定义为双精度,然而由于两个文件都没有#includes,所以没有覆盖float_t的typedef。因此,match 接收一个 8 字节双精度浮点数数组,并将它们传递给另外的函数处理,而该函数需要一个 4 字节单精度浮点数数组。

除此之外,代码没有做任何不寻常的事情,后面的一切都是预处理和规范化关联的标准实现。而且,程序在验证光谱有足够能量后才会调用,所以也不存在除零误差。

那么,出了什么问题呢?

问题在于,期望 4 字节数字的函数本质上会将每个 8 字节双精度数视为两个变量。这有两个效果:

它使函数只扫描数组的前半部分,将前 4*length 个字节解释为length数字;每个 8 字节双精度的位最终以一种奇怪的方式解释为两个数字。

原因是,4字节数和8字节数有不同的指数长度,所以如果我们把一个8字节数误认为是两个4字节数,就会有部分指数位被误认为尾数位,第二个数字将完全由8字节数中的尾数位组成。

Linus Åkesson 观察到,正在测试的频谱由整数计数组成,然而,IEEE浮点数的尾数是左对齐的;因此,只要这些计数小于100万(小于20位),8字节数字的所有“内容”都在左边,其余的都是零。因此,double的后半部分将被误认为是4字节值0.0。

同时,左侧将被混淆为一个4字节的值,该值具有相同的符号、较小的指数,并且指数的一些零位传递到尾数。这是一种非线性失真,它将一个整数转换为一个具有高度压缩范围的实数(如下图所示[3][4])

所以,例如你输入下列数组

< ... a, b, c, d, e, f, g, h ... >

其实系统实际接收到的输入如下,其中 SQUASH 是上面绘制的函数

< ... SQUASH(a), 0, SQUASH(b), 0, SQUASH(c), 0, SQUASH(d), 0 ... >

由于这种非线性的映射关系,导致SQUASH(10) 和 SQUASH(1000)的结果实际上接近一致。

也就是说,利用这段代码,只需要在假的核弹头中留下少量的裂变材料,让其持有一点点能量,就可以正常在光谱的右半部分产生大量光谱特征,并且彼此之间没有太大的差异——即使原始光谱中的裂变材料数量和量级相差很大。

这段代码是十分巧妙地,它不需要对系统或者文件权限做任何的修改,而是将其归结为浮点数的按位表示,并将数据类型与可用结果混淆,来造成错误的映射,以此实施攻击。

测试者将使用真实世界的方法来比较光谱,而不是简单或人为拟造的数据,且攻击实际上对所有过滤和预处理都是透明的,保留了数据的整数属性,因而绝对难以被察觉,也就是看似简单其实非常精妙的一段代码,并且只有 60 多行,是如此的清纯无辜和楚楚动人啊~(逃 ε=ε=ε=┏(゜ロ゜;)┛

也正因如此,Linus Åkesson 获得了 2015 届冠军,属于是当之无愧了。

参考^https://www.linusakesson.net/programming/underhanded/2015.php^http://underhanded-c.org/#summary^http://www.underhanded-c.org/one.pdf^http://www.underhanded-c.org/one.pdf

有哪些看似简单其实非常精妙的代码?由讯客互联技术交流栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“有哪些看似简单其实非常精妙的代码?