[原创]delphi dcu逆向编译漫谈
提起这个话题,主要是从前不久自己的一次实际dcu逆向编译经历而来的。
题记:f-in-box控件是个delphi下进行flash播放的优秀控件,其能从内存中载入ocx,从内存中载入flash等优秀特性。但是很可惜,网上只能找到3.1.2版本的full source版本。之后版本一直到最新版3.5.3之间都只有破解版存在。full source版本苦等不来,后来看f-in-box控件源码结构也比较简单,只有一个pas文件。正好我们都知道dcu中含有不少pas中的原始结构和类型信息,因此尝试在3.1.2源码的基础上对3.5.3进行逆向编译。至于为何在3.1.2的基础上,不在3.1.2的基础上行不行?这个我可以说的是,即使没有3.1.2,也是可行的,3.1.2的存在只是为中间逆向省去了很多麻烦而已。
下面,就对我在逆向f-in-box dcu的过程中的经验做下总结。
一、工具,俗话说欲善其事,必先利其器:
- dede——优秀的delphi反编译工具,并且自带dcu反编译器dcu32int
- dcu2pas——国内作者编写的dcu反编译器,这里既然有了dede,我还要提及他的主要目的是因为dede中的dcu32int在函数参数识别上有很大的不足,例如无法区分var参数和out参数,同时无法区分const参数等等。但是dcu2pas可以。但是dcu2pas无法在反汇编代码中无法用参数名代替偏移量。所以dcu2pas和dede可以互补使用
- IDA与hexrays——这是现在搞逆向破解人的必备工具之一,ida的强大的反汇编以及调试功能和hexrays的强大的反编译功能可以大大的节省逆向的成本
- delphi——这里delphi的作用是在代码初步逆向完成后,用delphi编译后,进行源码级别对比,进行排错时需要用到的。
二、dcu逆向的一般步骤:
- 1、用dede或者dcu2pas反编译dcu文件,取得所有类型声明以及类声明和函数原型,对照这些结果,给出dcu所对应的pas文件的完整类型声明,类声明,函数声明等。
- 2、用delphi编译一个使用此控件的exe,之后用ida反编译。留作将函数逆向为源码时所用
- 3、依次阅读dede给出的dcu反编译结果中每个函数的汇编代码,并采用二进制字串搜索方法在IDA中定位出这个函数,之后相互参照dede的结果和IDA的结果,对函数参数进行命名,这里需要提及的是,dcu中连函数内部参数的参数名和类型信息都有保存,因此对照dede结果可以很方便的对IDA的结果进行信息补充,并最终参照Hexrays插件给出的伪代码,写出大致的函数源码。
- 4、在完成所有函数的汇编代码向源码转换的过程之后,对逆向出来的源码进行编译,注意,编译参数要与原始的dcu完全一致,例如f-in-box控件的demo版用的是debug编译,没有优化,因此我们对源码也采取这种方式进行编译。
- 5、用dede反编译 我们通过逆向dcu得到的pas文件的编译得到的dcu文件,并将结果与原始dcu的反编译结果进行比较,关于比较,这里推荐editplus编辑器,用其去掉反编译结果中的一些注释之后,可以直接用winhex的文件比较结果进行分段比较,快速的定位哪些函数反编译出来的和原始的不一样。之后一一修正这些函数。
- 6、多次重复5步骤,直到最终原始dcu和我们自己编译出来的dcu没有实质上的差别后,就可以进行后续处理了。
- 7、逆向结果的后续处理,众所周知,delphi历史版本很多,仅广泛使用的就有delphi7,delphi2007,delphi2010等版本,并且每个版本的dcu文件格式以及vcl和rtl都有一定差别。因此每个控件实际上在发布的过程中都会发布对应各个版本的dcu。这最后一步就是需要对比各个不同版本delphi使用的控件的dcu文件的不同之处,并用条件编译语句在逆向出来的代码中体现出来。拿f-in-box控件来说,其中最明显的是和tstream相关的函数,delphi5之前tstream函数的size之后integer,也就是4个字节,但是delphi5之后的版本,size是int64,也就是8个字节,因此在控件中delphi5之前的版本和之后的版本,和此相关的函数都有不小的差别,这点对比delphi4或delphi5用的dcu和delphi7用的dcu就能看出来。后续处理的另外一部分内容是尽量还原出控件原始的条件编译指令,这点比较难,也比较麻烦,因为dcu中压根就没这部分信息,那我们怎么办呢?靠猜和结合控件安装包里面定义的预编译指令进行处理。一般逆向编译可以不考虑此部分,如果 有“洁癖”,可以尝试按照此法进行处理。
三、结语:
以上大致给出了逆向编译一个dcu到pas文件的步骤概要以及其所用到的一般工具。这里关于第5步的比较不同并修正的时候,就需要有不少经验的积累了。举个例子,一般的delphi函数中如果用到的string类型的变量作为参数,那么在函数的汇编代码的开始时,肯定有对这个string的引用数进行+1的函数调用,也就是addref,体现在反编译结果中,一般是“call System.@LStrAddRef”或者“call System.@WStrAddRef”,具体是哪个取决于是ansistring还是widestring,如果在某个用到了string类型的参数的delphi函数的汇编代码中没有addref,那么就要考虑,这里的string类型的参数是否用const修饰了。const修饰后的string类型的参数编译后,函数内部一般是不会出现addref调用的。
另外,虽然理论上,任何一个delphi的dcu都能理想成员吗,但这里就涉及到一个逆向成本的问题,我所逆向的f-in-box控件只有一个pas文件,代码集中,所以便于处理,如果是多个pas文件,那么劳动量就要加大了,只能依次处理。
最新评论