简明、现代而优雅的破解技术笔记 特别篇:扫盲及入门建议
简而言之,想要破解程序,就要先认识程序。想要认识程序,就要先明白世界上所有的程序都是怎么来的。本文旨在让你弄明白世界上所有的程序各是怎么来的,无一例外。清楚了这些,我们就可以根据程序的来源逐一击破。
第一节课已经发出来了,但是感觉还是不如一些直接教过时方法论的教程火。不过依然收获了很多关注,感谢一直关心这些最新技术的朋友。也衷心希望可以听到大家的建议,让我能够对大家有更大的帮助。
由于我是计算机科学与技术专业科班出身,有很多本科一年级就耳熟能详的常识,我想还是需要向大家普及一下。否则小白可能还是会觉得教程艰深,而网上盛传的各种说法也不是都自洽,所以我决定先插一期扫盲帖,从我们学习逆向工程所要分析的目标:软件的历史开始讲起。
为了使计算机更易于使用(编程),自电子计算机发明以来,无数人在它的基础上添加了一层又一层抽象和包装,每一层抽象和包装都可以称作一个平台,同一层的不同方法的抽象和包装又可以称为同一层的不同平台。这些平台大致可以分为操作系统、编程语言、软件框架三个大类,而且他们之间的界限存在,却不一定完全清晰。这些抽象和包装百花齐放,百家争鸣了六十多年,最终形成了我们现在看到的使用电子计算机构建的复杂世界。虽然这些抽象和包装是为了让人们不去在意机器的逻辑,而是使用人类的思维方式来告诉机器自己的意图,但是最终生成的程序是要由机器来执行的。当我们拿到一份由机器执行的程序时,我们必须把这一整个过程还原出来,从机器要做什么反推出人在最高层次的抽象和包装上想要让机器做什么,这就是逆向工程的本质。因此,想要学习逆向工程,最符合实践的方法是理解那些抽象和包装是如何得出的,并且在底层的基础上建构出整个逻辑链,从而找到原设计者本身的意图,并且在此之上添加自己的意图(破解)。
早期的电子计算机
虽然这一节的名称叫做“早期的电子计算机”,但即使是今年刚推出的各种架构的CPU和GPU,硬件上也依然和早期的计算机类似(冯·诺伊曼结构),当然也有些产品会有所不同(哈佛结构)。如果你喜欢研究裸机编程,依然可以在这些最新的硬件上进行裸机编程。现在流行将现代的裸机硬件称为“裸金属”(Bare Metal),可以搜索这个关键词来获取这个领域的最新信息。另外,一些低端计算机、MCU(单片机)等,也依然使用这种编程方式。
在早期的电子计算机上,我们直接在计算机的存储器上编写程序,并且计算机从存储器的固定位置开始执行代码,除代码外的存储空间,看情况,有的存储器可以用来随意存储数据,有的存储器则不能。直接在存储器上编写程序的时候,需要对照计算机的操作手册,查询对应指令的代码,并将代码和数据写在存储器上。这个时候,没有任何编程语言,也没有操作系统,更没有软件框架。你所拥有的一切就是一本说明书,以及你可以根据这本说明书来编写机器指令操作这台电子计算机。时至今日,英特尔、AMD等CPU设计企业依然在为他们的产品编写这种说明书,但这些说明书的受众变小了,只有微软和写其他操作系统以及开发BSP(板级支持包)的人才会去参考,但在操作系统和BSP出现之前,这是一名计算机操作员手头必备的资料。
为了便于理解直接在计算机的存储器上编写程序这件事情,大家可以看看非常早期的计算机。那时计算机的存储器是穿孔纸带,人们可以直接在纸带上穿孔,穿好孔的纸带便被写入了程序。这一切到今天为止依然类似,只是存储器也变成了电子元件而已。
研究早期计算机的人是幸运的。我本科时期刚好参与了“珠峰计划”,我校在这个项目中对计算机硬件课程做了一次重新整理,把一般本科学习的“计算机组成原理”和“计算机系统结构”两门课程统一成了一门大课“计算机硬件系统设计原理”,由退休返聘老教授刘子良先生亲自操刀写书授课,我很荣幸成为了他的学生。听他讲课时,经常能够听到一些六七十年代的奇闻轶事。他使用过打孔纸带编写计算机程序,也亲眼见过大型机的内部结构。只有这样的人才能亲眼看见AX、BX寄存器,才能亲手摸到RSP、RBP。我们现在再去学习这些的时候,只能对着课本上的示意图和调试器、仿真器中的参数想象,在小小的芯片中居然存在这么一个东西,我们永远也不能看得见摸得着了。
操作系统的出现
在使用打孔纸带编写机器指令并把数据直接写在机器指令里的年代,没有人觉得电子计算机开一次机只运行一条纸带,运行完毕后从计算机上连接寄存器的亮起的灯上读出数据,再把计算机停机复位,需要运行下一段程序的时候再开机有什么不对的地方。但是很快,存储器技术就出现了革新。磁鼓存储器让人们可以不再使用打孔纸带来承载数据,而是把数据变成了磁鼓上的磁场。电子化的存储器很快让寻找和安装纸带的过程变成了电信号,由一些控制电路来控制。
人们终于感觉每次开机只运行一条程序有点效率低下了(那是因为电子存储器解放了人类寻找纸带的双手)。于是有的计算机使用者便编写了一些让计算机在一段时间内保持运行,并且根据某些按钮输入可以从存储器的不同位置加载不同的程序来运行的程序,这便是最初的操作系统。是的,操作系统就是帮助人操作计算机的系统,从这个时候起,计算机装载程序变得更方便了,而且程序和数据也可以尽量分类好了。同时,数据存储技术百花齐放,有些存储器价格比较低,但性能也比较拉跨,有些存储器价格比较高,但性能也非常顶。所以,计算机系统慢慢发展出了内存储器和外存储器的设定,内存储器用来给程序打草稿,外存储器用来存放初始的某些数据和最终的结果。
其实这些史前时代的故事众说纷纭,本文的说法也不一定就完全符合史实。因为在历史的开端是没有人修史的,当人们开始意识到操作系统的重要价值时,操作系统就已经基本有一个雏形了。那就是用来装载和切换程序的那个“管理程序的程序”。当一些计算机制造商开始制造出性能非常强悍的计算机时,他们将自己的计算机高价卖给一些机构,并不是所有人都买得起计算机,所以这些机构租用计算机的生意就非常火热。为了方便管理计算机的租用,就有了更加高级的操作系统。这种操作系统可以让不同用户提交的任务以为自己独占了计算机,而实际上却是来回切换执行的,并且能够计时,然后按照用户的身份来记账。
这其实与现代的操作系统已经别无二致了,有随时装载程序的功能,还有进程和用户管理的功能。事实上,这种操作系统有一个大名鼎鼎的后代,就是Unix。Unix起初是直接用机器指令编写的,后来Unix的主要作者Dennis MacAlistair Ritchie觉得需要开发一门高级语言来改善Unix上的编程体验,于是就发明了C语言(写出了第一个C语言编译器),并把C语言编译器和Unix系统都使用C语言本身重写了一遍。
高级语言的产生
上文提到了C语言,不如我们直接开始讨论高级语言。C语言其实并不是最早的高级语言,高级语言的出现主要是为了提升计算机上的编程体验。你不再需要记住那些烦人的指令,现在你的心里只需要有逻辑控制结构,就可以轻松写出很多程序。高级语言的出现大大减低了编程时逻辑思考的难度,使得计算机程序的逻辑复杂度提升了不少(这个道理很简单,当你不再注重那些细节的时候,你就可以思考更多大体上的事情)。
所以高级语言的核心技术就是前文中提到的编译器。编译器做的事情就是把高级语言翻译成机器指令,这其中就涉及巨量的细节被编译器自动处理,而且编译器有不低的自由度。以前在使用机器指令编程时,你能控制机器如何执行代码的唯一方式就是跳转类指令。高级语言定义了一些新的逻辑,比如判断和循环,这些基本结构都是机器不能理解的,但是编译器可以把他们转换成一些包含跳转和判断的指令,让机器可以按照这些逻辑意图做出动作。从此开始,人和机器不再同步思考,人编写程序,编译器将人类逻辑翻译给机器,机器按照机器的方式执行。
高级语言是编译器的语言,使用高级语言编写程序,就相当于人指挥编译器制作和装配一个程序。除了可以直接一一对应机器指令的汇编语言外,其余所有的语言都叫做高级语言,所以现在很少有人再提高级语言的概念,因为高级语言内部也有很多分化,与操作系统和软件框架也息息相关。几乎都是某一个平台上为了提升本平台的编程体验而发明的东西,或者一些团体为了在多个平台上提供一致的体验发明的东西。
既然高级语言编写的程序最终要由编译器生成实际运行的程序,那么这种实际运行的程序或许可以反编译成高级语言的代码。如果是编译器直出,没有经过任何处理的可执行文件,的确可以被反编译成源代码——只需要把编译器中处理的那些语句和指令之间的对应关系反过来就好了。最初的高级语言和编译器确实是这样的。但是现代的编译器都会在编译完源代码之后,甚至在编译的过程中,对于生成的机器指令进行优化,这是一种自动的骚操作,大多数时候可以提升程序的性能,或者至少统一程序的性能。这样就有一个副作用,生成的机器指令无法被准确判断出来自什么样的源代码,也就需要具体问题具体分析了。这也是逆向工程中的第一步难点。
后续的发展
其实说了这么多,我主要是想表达,其实在机器指令之上,任何后续的发展都是为了让人“更加舒适地编程”,并且有很多人提出了很多解决问题的方法。并且任何一项技术,通常都不是一个单独的技术点,而是给人提供编程体验的一整套方案。只是大家通常对于这种方案的认知都仅仅在一个点上。例如操作系统,当今所有操作系统都选择使用C语言接口作为系统官方最基础的编程体验,但这并不妨碍很多人使用其他的方案在操作系统上编程。使用其他的方案在操作系统上编程,只要方案提供者允许,同样可以使用操作系统的各项功能,同时也包括方案提供者的各种扩展。所以,作为一个破解者,也许我们需要破解的程序是千变万化的,但如果我们能够根据方案的发展程度从合适的层面切入,我们就可以获得事半功倍的效果。
什么叫做根据方案的发展程度从合适的层面切入呢?首先还是要继续普及一下编程方案的发展。自从高级语言发明出来之后,高级语言编程方案的提供者就代替他的用户成为了计算机制造商的客户。高级语言方案的提供者(最初有些人甚至是志愿的),会想方设法把自己的高级语言移植到各种计算机上,这些计算机都不是一个地方产的,所以指令也不是同一套,这就意味着他要编写一个将自己的高级语言编译到这套指令(指令集)上的编译器。有些计算机的指令集功能多,有些计算机的指令集功能少,每一个都需要单独研究,才能写出一款用来编译高级语言的编译器。
那这就有点恼火了,更要命的是,其实计算机干的无非也就是计算的活,所以指令集虽然大家都不一样但是其实也差不多。所以就有人动起了脑筋,如果我把这些指令集里面相同或者相似的东西,都抽象出来,然后研究这个抽象层到具体指令集的转译方法,同时把我的高级语言编译到这个中间的指令集来,岂不是就能省好多事了?
是的,就是这种一遍一遍的抽象,构成了今天的计算机世界。
一般喜欢省事和弯道超车的人都喜欢踩别人上位,这种搞“中间编译器”的语言也不例外。C语言几乎已经成为了全世界高级语言的霸主,你如果想发明一门语言弯道超车C语言,你会怎么做呢?首先,用C语言写前面提到的转译器,然后,由于C语言支持的平台最多,你的转译器可以随随便便编译到任何平台上。然后编写一个从你的高级语言源代码到中间指令集的编译器,这样,你编译出来的程序就可以通过你的转译器跑在任何平台上,而且只需要编译一次!编译一次就可以拿着文件在各种平台用相应的转译器运行,比C语言不知道高到哪里去了。
这就是Java,我希望易语言的后人们学着点,真的。
后来,这种中间指令集被各路计科大神整理规范,最后形成了一种“语言虚拟机”的概念。就是说这种中间指令集是一种假想的机器的指令集,你的编译器是往这种假想的机器上编译的,而这个假想的机器被你的转译器实现了,只不过你的转译器底层是用其他的计算机实现的这个功能。这种思想后来被LLVM吸收,用来优化C语言的编译过程。
好家伙,套娃终于套起来了。
再后来,由于Unix系统本身的命令行就可以执行一些逻辑,比如判断和循环,但是并不太优雅,所以有人实现了一些其他的交互式命令解析器,来执行一些需要复杂功能的命令。命令太多了,人们就把他保存在文件里,并且解析器可以读取这种文件,来执行文件里面的一套命令,这也算是一种编程了,这被称为脚本编程,Windows的BAT批处理也算是这种编程。
脚本编程用的语言自然也就被称为脚本语言,脚本语言现在也算作是编程语言。因为我们同样可以为脚本语言开发编译器,比如BASIC和Python都是脚本语言出身,但微软为BASIC开发了编译器,并且开发了开发BASIC图形界面程序的工具Visual Basic,而Python现在也是编译到Python虚拟机来执行了。
编程语言的分类
作为学破解的各位朋友,对编程语言的分类可能需要一个和学界不一样的理解。学界从来没有把编程语言分成过三类,要么是“编译型语言/解释型语言”,要么是“动态语言/静态语言”。这些都不适合用来分类以逆向工程观点来看的程序语言。所以我在此提出,按照上文逻辑,将编程语言,或者说可以在计算机上运行的程序分为以下三类:
- 原生语言:编译到平台原生机器指令的语言
- 虚拟机/框架语言:编译到虚拟机指令的语言
- 脚本语言:不编译,直接解释执行的语言
在这个分类下,典型的语言有:
原生语言
汇编、C/C++、Pascal、Go、Rust、VB
虚拟机/框架语言
Java、C#、F#、VB.net,以及一大堆基于JVM的语言,如Kotlin、Scala
脚本语言
Shell脚本、Windows批处理脚本、Python、Ruby、VBScript、JavaScript
当然,同一款语言可能同时存在多种运行方式,但这三种运行方式分别对应三种破解与防破解策略。你的逆向层面越接近源代码编写的层面,你就越容易获得源代码。举个例子:如果你正在使用一个Python脚本,你想要修改他的逻辑,你会傻到打开调试器x64dbg,然后附加到Python解释器进程,然后去修改Python解释器进程的内存吗?直接修改你正在运行的Python脚本就是了。
方法论环节
所以,今天的方法论终于来了,那就是,确定好你的目标程序属于哪一个层面的语言,并且使用对应层面的逆向工具。如果你确定你要破解原生程序,那就请使用IDA和x64dbg,以及CheatEngine。如果你要破解虚拟机程序,比如C#的程序,那就请用dnspy直接从.net虚拟机指令反编译源代码,并且改完以后再编译回去。如果你想破解脚本程序,就请直接修改你所看到的代码,那就是源代码!
想要知道一个exe程序属于原生层面还是虚拟机层面,或者加没加壳,不要使用其他工具,请认准Exe Info PE!这才是现代的万能查壳工具。当你看到.net关键词时,就代表你要破解的exe是个.net虚拟机层面的程序,使用dnspy,而不是IDA!
关于原生程序的壳的问题之后再详述吧,今天的理论就讲到这里,写了一个下午,如果觉得我写得不错,请帮我涨点积分,谢谢大家。如果反响不错,我会继续更新这个系列。