View on GitHub

紫宸

计算机学院拔尖人才工程组

李忻洋:热爱底层和逆向的安全白帽子

简介:

李忻洋2012年电子科技大学本科入学,2019年电子科技大学研究生毕业。非bitman(紫宸的曾用名)成员。 凭自己优秀的底层能力,入职腾讯安全的核心小组,反外挂组。在暑期实习期间,用了一周就解决了腾讯一个安全扫描服务每运行几个小时就会崩溃的bug,重构了系统的相关设计,保证100小时以上稳定运行。获得了实习好评。一个在高中就喜欢汇编、喜欢后门,喜欢捣鼓安全软件的人。

忻洋在读研期间和韩老师合作做一些有意思的事,有不少值得称道的事。这里就举一个事情。

2018年逆向反汇编题目自动生成和批改系统在考试前几天发现了问题,本来此系统运行了3、4年多无此现象。即系统运行一段时间后,就无法正常自动批改作业了。系统由java代码和c++代码组成。这是个比较麻烦的故障,因为没有直接的问题爆发点,也无法如平常在出问题的地方设断点来分析。甚至问题出在java还是c++中都无法判断。忻洋在考试前一天果断用汇编级调试方式对在线系统进行了跟踪调试,这一方式不用源码,直接用OD调试整个运行的tomcat系统,分析反汇编,追寻蛛丝马迹。仅用了半小时,就发现系统依赖的一个dll中存在疑似未关闭文件。并发现,这是因为版本的问题,错误地用了以前有bug的dll。因而完美的解决了这个问题。(题外话,版本真的是个头痛的事,为什么一个三四年前修补了bug的版本,会突然倒退回三四年前的错误版本?@%!#)。

然而,忻洋给我最深的印象还不是技术上的坚实和执著。我最深的印象是,这是一个很有原则和某些可贵人格的家伙。


在学习C语言的时候,相信大家有时也会遇到这样的困惑:每条语句单看都懂,但连在一起就不知道在做什么了;代码运行结果不对,但找不出来哪里有问题。这时候就该“调试”登场了。在调试时,我觉得思路最为重要,为什么要看这个变量的值,为什么要监视这片内存区域的变化,为什么要在这行代码上下断点……这些远比如何查看变量的值、查看内存以及下断点更为重要。这需要经验的积累和不断的思考。程序没有按照预期执行而是得到了现在的结果,可能有几种情况,如果是情况A,那么该怎么验证?需要在哪里下断点,查看哪些变量的值。如果不是情况A造成的,还有什么可能?如何通过调试的手段加以验证……调试思路的积累和整理是一个漫长的过程,但这一切的开始,便需要我们敢于下断点、敢于按下F5。

我不是太赞成在编程实践过程中遇到问题就去请教别人求得答案,因为那样可能会丧失一次成长和积淀的机会。在有了调试的意识以后,大多数情况下我们都能凭此“化险为夷”。但还有关键的一点——我们无法站在CPU的角度去思考问题。C语言是很特殊的存在,一方面它和汇编语言保持着紧密的一一对应关系,其分支和循环语句经过编译后生成的汇编指令都有着固定的模式;另一方面它相对于C++等高级语言的面向对象特性,也具有很强的一一对应关系,诸如封装、继承和多态等特性都可以在C语言层面进行解释。我第一次意识到应该把C语言和汇编对应起来,是在解决一本C语言教材课后习题中的“3个i++”问题,时至今日回过头看,这确实是一道饱受诟病的题目,因为不同编译器编译生成的代码结果不尽相同。为了分析语句执行结果为什么和参考答案不同,我第一次按下了Alt+8,从此开启了一道进入新世界的大门。X86汇编掌握起来其实没有想象中那么难,在反汇编窗口中遇到了不明白含义的指令以后,去网上查查弄清其含义,然后自己在OD或者VC的内联汇编中构造该条指令执行,让结果符合预期即可。

在熟悉了汇编语言以后,对于二进制程序的神秘感和陌生感会逐渐减少,这时候“逆向工程”的大门就为我们敞开了。可以尝试把自己写的程序扔进OD里边跑边观察,当然也可以尝试弄清其他程序的原理并进行一些改动。比如Sublime编辑器在未注册时会时不时的弹框提示,可以尝试通过逆向调试的方式破解掉它;之前为了给MP3里的歌自动下载歌词,想写个程序通过千千静听的歌词搜索服务器自动查询并下载歌词,但网上所有的Demo都是基于一个C#版本源码衍生出来的,并且用它的算法经常获取不到歌词,这时候就可以直接逆向千千静听找到最正宗的歌词搜索相关的算法;如果一个程序对于用户输入校验不严或者逻辑存在问题可能会导致溢出,分析一个存在漏洞的程序是如何产生溢出漏洞的,也离不开逆向和调试。在这个过程中,我们也会有自己的思路,也可能站在作者的角度思考:如果让我来设计这个功能,我会怎么设计、如何实现,带着自己的结论去逆向去调试,往往会事半功倍。逆向是一个很大的话题,往后还有更多更有趣的专题在等着我们,在这条路上,我也是一个刚入门的新手,在此与诸君共勉。

需要注意的是,只向下深挖对于编码能力的提升可能在初期有一定帮助,但并不意味着一个人只要对于底层足够了解就一定能写出好代码。在进行日常开发时,我们经常强调一句话:代码是写给人看的,只是机器恰好能运行。这个说法确有偏颇,但其实它是在强调代码的可读性。我们都渴望写出高质量的代码,在互联网及其发达的今天,随便一搜就能搜出一大堆诸如“高质量编程”、“代码整洁之道”和“设计模式”类的文章和书籍,真是乱花渐欲迷人眼啊。曾经我也大量阅读这些书籍和文章,但读完后发现其实对于自己的帮助和提升并不大。

之前我更多的是在积累底层和逆向方面的经验,当时发现的问题是能够写出满足功能要求的程序,但一旦需要改动,代价就特别大。当时写一个解析PE格式的工具,为了实现对PE32+的支持,除了界面居然需要几乎推倒重来。在研究生阶段进行了画板的练习并实际上手原子力、码图等的开发以后,在韩老师的指导下,我发现或许学习这些经验最好的方式,就是把前辈们走过的路再走一遍,自己去把坑踩一遍。上面提到的诸如“设计模式”都是前辈们在工程实践中总结出的经验,当我们没有遇到类似场景时很难引发共鸣,并且大多数文章也只介绍经验本身,而没有说明这些经验是如何总结并得出的,主要的适用场景在哪里,它所要解决的痛点是什么。无论是写一个Windows程序还是做Web开发、移动端开发,还是接手一套新系统,许多新需求的加入,随着代码行数的增长,各式各样的痛点会逐一浮出水面。在解决这些痛点的过程中,可能就会重新发现各种“模式”。至少带着这些痛点再看这些资料,相信会有更多的收获和共鸣。

个人觉得在大学阶段,不妨追寻自己的内心多做一些自己真正喜欢的事情。对我来说,底层以及安全是我所热爱和追求的,因此整个大学阶段,我在上面投入了大量精力。我不认为自己是学霸,在大学阶段,我也会感到课业压力负担大,这时候我的做法是每学期选择2-3门自己最感兴趣的课程然后保质保量的学好,考研前期甚至每天下午还要带着电脑逆点东西、调调程序。现在回想起来,可能真的是因为在做自己喜欢的事情,所以不觉得焦虑。

在这里贴两段台大叶丙成老师在慕课中的视频,需要强调的是,文中所出现的都是我个人的观点和看法,仅供大家参考:

https://v.youku.com/v_show/id_XNjA2ODMwOTQ4.html

http://www.iqiyi.com/w_19rrx8rb5p.html

曾经教我C语言的老师说过这么一句话:计算机只有工程,没有科学。我对于这句话的理解是,与其说计算机科学,不如说是借助计算机用编程的手段去实现了各种已经发明的算法,或者提出新的算法并借助计算机加以证明或实践。这些算法本身与计算机并非是强耦合的,它们的基础可能是统计学、高等代数、数学分析、数论、图论等等,只是恰巧现阶段电子计算机的运算速度和能力满足实现这些算法的需求而已。因此除了算法本身的发明可能不需要利用计算机工程以外,其他环节都离不开计算机工程,而计算机工程又离不开编程,希望大家能在编程中收获自己的快乐。