我喜欢废话,所以先废话下。
因为今天有重要的事情要做,所以就一直忙到现在了。不过因为一直在干活,头脑还是很兴奋,想了想,最近在delphi应用的分析上面有了一点点的经验,今天和大家分享下,希望可以帮到你。
但是呢,时间有点晚,而且文章也是从我自己逆向时候做的笔记里修改的,可能会有些难理解,甚至有错误的地方,这个万望指正,我肯定改的。
因为是笔记,所以里面也有摘的其他人的东西的,而且也引用了之前自己的一篇文章,希望见到了熟悉的段子,理解下。毕竟是介绍和笔记嘛,也要从低到高的 :)…
只要程序员还在用delphi,我们就还需要熟悉borland delphi的结构。
而且相当有价值的一个部分是,现在有大量的不人道的病毒程序员啊,人家写个病毒也不怕体积大,10KB的程序用delphi的控件来写,成了200KB。坏的是分析人员的事,想简单分析下流程都只能看字符串和导入表了。我们这就开始研究如何定位delphi的函数吧。
//———-
一开始看一个delphi程序,重要的第一要看的肯定是rsrc段,因为windows的API处理资源时候使用了硬编码的.rsrc字符串,所以delphi程序中不管怎么样做加壳,rsrc段肯定是存在的。
而delphi的程序的源代码是分为pascal源代码(.PAS, .DPR, .INC)和资源文件 (.RES, .RC, .DFM, .DCR)二种的,而资源文件在编译之后,就是几乎完全源代码形式的储存在rsrc段中的rcdata类型的资源中。
为什么这个rcdata重要,原因是在delphi里,form中在运行时(进程中)对用户代码的调用,都是通过保存在rcdata中的函数符号名称来进行的。这样,我们不仅仅可以通过rcdata定位特定事件的处理函数,也可以通过任意一个用户事件Handler,向上跟踪找到这个包含字符串的结构,来定位其他自己感兴趣的函数。
通常的delphi小程序,这样的分析就足够了,只是,这还不够浅析咧。
想要分析好目标,我们要从PE的入口点开始分析。
首先delphi的可执行文件无论是exe还是dll类型的,都有一个InitRouterTable,其中保存了在初始化时挨个调用到的routines。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
PFUNTABLE中的初始化函数和结束化函数是成对出现的,在调用这个表中的函数时候,delphi的循环每次是+0×8的偏移,于是Initialization时总会跳过Finalization的函数。
但是一个很沮丧的消息是,在这个初始化,还有完成初始化的列表的函数中,基本是找不到用户自定义的函数的,至少在我遇到的程序中没有。
下面是一个典型的带form的delphi exe入口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | push ebp mov ebp, esp add esp, 0FFFFFFF4h mov eax, offset InitRoutineTable call @@InitExe// mov eax, ds:off_442C20 mov eax, [eax] call unknown_libname_291 mov ecx, ds:off_442AB4 mov eax, ds:off_442C20 mov eax, [eax] mov edx, off_441498 call @TApplication@CreateForm mov eax, ds:off_442C20 mov eax, [eax] call @TApplication@Run call @@Halt0 |
InitExe,unknown_libname_291,TApplication::CreateForm,TApplication::Run 这些都是delphi的库函数,可以在VCL的system.pas中找到。
InitExe会调用一个叫做StartExe的库函数,来从InitRoutineTable读取出所有的FunTable,并且挨个执行Init的Routine。
TApplication::CreateForm则顾名思义,创建程序的主Form,这个就是delphi初始化的主要流程。传递给它的第一个参数是名称为TApplication的类的指针,而edx则是类似于c++的类成员函数的第一个参数,其指向的是
TCustomForm类的TMetaClass结构的指针,会被用来构造程序的CostumForm类型的主form。这个函数,其实也是delphi中一个普通的类为自己重载的ClassCreate构造函数,特别之处只是构造函数的是inline在TApplication的
成员函数中的,只是在入口点,所以被特别命名了。
TApplication::Run中,通常要先用AddExitProc把from的析构函数添加到退出时要执行的流程里,然后就是标准的进入TApplication::HandleMessage delphi的消息循环了。
Halt0则是delphi退出入口点的流程,当没有保存DLL方式进入口点的dwReason,即exe类型时的其值为0,在halt的最后就会调用ExitProcess结束进程的执行。
下面是一个标准的delphi dll入口:
1 2 3 4 5 6 7 8 9 10 11 | push ebp mov ebp,esp add esp,-3C mov eax, InitRouterTable call InitLib // call @@Halt0 |
InitLib是Delphi的一个库函数,从它的参数InitRouterTable 可以定位到DllMain中会被执行的流程的数组。和InitExe的功能是类似的
一般的,如果DLL中也有窗体,需要进行消息循环的话,也就是说,在DLL里也调用了TApplication::CreateForm来创建CostumForm主form的程序中,在入口点会先创建一个新线程,然后,再在新线程中执行
TApplation_CreateClass进行TApplation类的构造,以及执行TApplation::CreateForm和进入TApplication::Run消息循环。以避免DLL载入窗口的过程阻塞了原应用程序的执行。
在Halt0中,是DLL的时候,会判断一个全局结构里保存的进入DllMain的Reason,这个值不为0时,就意味着是DLL的入口点,会调用一个专为DLLMain返回准备的库函数System::_16618,它是以
leavel
retn 0Ch
结尾的。
//———-
上面的就是delphi的默认入口点。相信你也跟我一样不知其所谓,这是因为delphi的实现,全部都是用类来实现的!
而且delphi的入口点,功能其实只是调用类的构造函数,区别只是怎么样的调用类的构造函数。
而具体的程序如何初始化的,完全是由类的构造来决定的。这个类,就是CostumForm,没错,delphi中的form也完全就是一个类。
关于delphi的class逆向的一个实例性质循环渐进的说明,可以看下面的文章
http://www.pediy.com/bbshtml/BBS6/pediy6935.htm
或者同一篇文章在http://bbs.pediy.com/showthread.php?t=5476 #7 英文原版,以及我自己的杨白劳翻译。这篇文章中有一个笔误的地方是TmetaClass被写成ClassInit了。
在这儿,只简单叙述下delphi中的class的构造函数,以方便明白下面介绍的TMetaClass
在delphi中,一个类的构造函数都是单独存在的一个函数,不像c++中总是inline初始化过程到构造类对象的代码位置。同时,除非是自定义的构造函数,这个构造函数的功能一般都很简单。
在里面只有一个功能,以TMetaClass中保存的虚函数表作为参数,调用一个叫做System::ClassCreate的库函数。在这个ClassCreate里面会根据类的大小分配内存空间,以及调用类的初始化对象的函数,此函数的偏移是在虚函数表的 -0xc位置。
对于一个类而言,它在constructor中,首先要调用System::ClassCreate,它其中会使用函数表的 地址- 0xc位置的类特例的InitInstance函数,在其中调用System::TObject::InitInstance来做必备的初始化实例,此时通常会调用System::__linkproc__ GetMem(int)来申请到对象占用的内存。
如你所见,一个类的初始化函数完全没有我们想要的信息。它包含了什么东西,就像c++的初始化函数中不会有这些一样,delphi的也没有。那delphi的窗口、控件、函数是如何组织起来的,就完全要看TMetaClass了。
//———————————————
delphi里定义类的信息的TMetaClass结构,其结构格式是:
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
其中delphi固定使用的结构大小有0x4C,也就是VTBL之前的0x4c字节。
各个元素的简略含义在上面修改自李战大师的结构中已经标明清楚了。
一个TMetaClass的实例
代码:
CODE:00413BF4 THook_ClassInit dd offset FuncTable
CODE:00413BF8 dd 2 dup(0)
CODE:00413C00 dd 5 dup(0)
CODE:00413C14 ClassName_Ptr ; “THook”
CODE:00413C18 dd 18h
CODE:00413C1C dd offset _cls_System_TObject_SubClassInit
CODE:00413C20 System::TObject::SafeCallException(System::TObject *,void *) ;
CODE:00413C24 nullsub
CODE:00413C28 nullsub
CODE:00413C2C System::TObject::Dispatch(void *) ;
CODE:00413C30 nullsub
CODE:00413C34 Comctrls::TTreeNodes::GetFirstNode(void) ;
CODE:00413C38 Destructor_THook
CODE:00413C3C System::TObject::~TObject(void) ;
CODE:00413C40 FuncTable:
CODE:00413C40 ClassName_Ptr: db 5,’THook’
当分析delphi的类的时候,在这个结构中有几个重要的数据是定位目标函数要使用到的。它们的指针位于虚函数表的之前,也就是TMetaClass结构中的数据,摘入下
代码:
+0×10:TypeInfo 指向本类的RTTI信息的指针,
+0×14:FieldTable 指向field表的指针(Published Field)
+0×18:MethodTable 指向publick method表的指针
+0x1c:PrivateMethodTable 指向private表的指针
+0×20:ClassName 指向类名字符串的指针
+0×24:InstanceSize 指向对象实例的大小
+0×28:Parent 指向父类的指针
相对于类实例中0×00000000偏移处储存的类函数表的指针,其偏移分别是
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
这儿的TypeInfo,储存的是这个TMetaClass自身的TTypeInfo,即这个TMetaClass代表的是一个什么类型的类。
它是一个PTTypeInfo类型的指针,在TTypeInfo里面储存的数据,是delphi实现RTTI功能重要数据。
在这个结构中的数据,也用于delphi的控件和组件的实现中,比如窗口之类的组件在delphi程序启动时如何进行初始化,就部分源自TTypeInfo中的property的设定。
PTTypeInfo指向的结构TTypeInfo可以认为是delphi中的最基础的类型,比方integer、float等,包括class的类型,都是由它来定义出来的类型。其结构如下
代码:
1 2 3 4 5 6 7 8 9 10 11 | |
TTypeKind是一个枚举类型,其值决定了后面跟着的那个TTypeData中的数据会被如何组织和使用。注意,枚举的计数是从0开始计数的
在Borland Delphi中,这个枚举的值以及特定的含义为,
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
Name为此TTypeInfo—类型的名称。
在Name其后跟着的那个TypeData,它是一个有着相当复杂的声明的union结构,根据前面的TTypeKind 的值它有不同的结构。
TypeData在delphi中的完整定义列在下面
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
从上面的TTypeInfo中的TypeKind,就可以得到Delphi RTTI动态类型所时候需要判断的类型是什么了。
再从这个结构中的TypeData得到类型特定的信息,就是完整的delphi RTTI功能。
一般而言在逆向时候,在需要处理这个结构的情况中,都是遇到了需用跟踪一下特定的的Delphi控件和组件的类型信息,对于Delphi控件和组件所使用的TTypeInfo,其中的TypeKind都是tkClass。其实就是一个delphi中的类的TTypeInfo
这时候,其后跟着的TypeData结构为
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
各个项的意义在结构中已经说明了,但最后的TPropData,才是分析中class的TTypeInfo里面最重要的地方,
这个PropData也是一个结构,如下
代码:
1 2 3 4 5 6 7 | |
结构其中就包括一个PropCount,还有一个不定长名叫PropList的数组,数组里面是一个个设定class具体到微的TPropInfo结构。
这个保存类型的property的TPropInfo结构的定义为
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
Index是delphi中引用propery可以选用的数字索引,不存在则值为 80000000h
NameIndex可以直接理解作 字符串索引,也即用 ClassName.PropertyName这样常用的格式来使用时,delphi对于这个property内定的索引,因为使用property时,本类型的Property和父类的是在同样的命名空间做符号的匹配的,所以NameIndex的取值范围是考虑了父类的property后才能指定的。
Name则是本property的名称。
这个结构在VCL的实现中,是被名称类似于Get????Prop和Set????Prop的函数所使用的,他们都是TypeInfo类的成员函数,比方method的类型,就有GetMethodProp和SetMethodProp。
如果TPropInfo 中存在有效的GetProc和SetProc的值,这二个函数也会在TypeInfo类中处理它们的函数里被调用到的。
一般而言,Default和SetProc对于我们是最常用的,如果在delphi组件对应的RcData中没有特别设置值的话,初始化给Property的值就是它。而完成特定组件的功能初始化的函数,则是这个SetProc
~~~~~~~~~~~~~~~~~~~~~
-0×38: FieldTable
FieldTable,Field的含义是成员变量,这个表中储存的就是类的成员变量。例如子控件和子组件,在类的声明中定义的integer类型的成员变量,都是储存在这儿。
另外注意,它里面储存的只是published的成员。
FieldTable指向的是一个简单的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Counter,是FiledInfoAry[]数组中元素的个数,也即本类的field的个数。
FiledInfoAry,是一个TFieldInfo结构的数组。TFieldInfo结构中的OffsetFromInstance是一个偏移值,是这个field在内存中,相对于类对象的基地址的偏移;TypeIndex是一个索引,用来在它所在的TFieldTable的FieldTypeAryPtr指向那个TTypeInfo数组中定位到它的TTypeInfo,而这个filed的初始化,则是由对应的TTypeInfo决定的;FieldName为field的名称。
FieldTypeAryPtr,指向的是一个保存TypeInfo数组的结构,它用来保存Filed们的TypeInfo,
struct TFieldTypeAry
{
__int16 Counter;
TTypeInfo * TypeInfoPtrAry[0];//可变长
};
Counter为本结构中的TTypeInfo数量。
TypeInfoPtrAry数组则是一个指向TTypeInfo类型指针的数组。前面TFieldInfo中的TypeIndex对应的是这个数组中的索引。
~~~~~~~~~~~~~~~~~~
-0×34: MethodTable
MethodTable表指向的是类的公开成员函数表。一般的用窗口控件之类组成的delphi程序,可以认为这就是用户自定义函数的咧表。
在这儿要清晰的了解到,对于一个真正的delphi class而言,直接附在TMetaClass后面的那个虚函数表,只是delphi system给予的类型所自带的虚函数表,虽然其中的函数也可能被重载而可能需要分析,但重要的,用户自定义的控件和组件函数,在常见的情况里都是保存在publish method表里面的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
但是,在查找一个delphi class,一个真正的类的函数时候,不应该只从这个publish表中找。这儿都是publish方法,窗口的自定义过程函数是很确定会保存这儿的,但是,一些通过重载来实现的虚函数,还是保存在虚函数表里面的。
分析delphi class的函数,必须published、private、虚函数表三个位置都查找。下面的是private method表的介绍
~~~~~~~~~~~~
-0×30: PrivateMethodTable
delphi中的pravite函数,是单独的储存在private mothed表中的,
因为是pravite方法,所以其中并不保存函数的名称,只保存delphi内部分配给函数的序号,以及函数地址,
例如,delphi中HotKeyDown这个响应WM_HOTKEY消息的函数,在网上流传的代码中通常被写作pravite函数,所以在分析代码时,它通常就是保存在private函数表中的,因为没有保存函数名称,分析时就需要特别的注意下。
PrivateMethodTable指向的结构为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Counter是一个16位的数字,表示本表中有多少个PrivateMethodInfo
Index是一个delphi内部使用的索引.在这儿,对于OnWm_Create之类处理windows窗口消息的pravite函数,delphi非常的取巧,直接用消息宏的具体值,如WM_HOTKEY的0×312来作为索引,在使用时候非常方便。
另外,类似于上面publishd方法中对于要调用的一个函数的查找,当本类中的private表中不存在要调用的动态方法的索引时候,还会进入到继承来的父类中进行private函数的查找,所以这些index是有重载关系的,子类中相同index的函数,在使用时会覆盖掉父类中相同index的函数。
FuncPtr就是private函数的地址。
///—————
上面那么一大长篇的文字,看完后可能觉得没什么用,我用一个实际例子来说明下。
我们现在用ida打开DeDeDark这个dede的修改版(其实我不知道修改了什么 :| 因为里面一点点分析上面的bug还是在的)
如果ida带着delphi 6和7的完整版本的sig的话,分析完成时候,整个程序近一半的代码都会被标示库函数。这儿提一下,因为VCL在每个版本的delphi中都是有源码的,所以可以自己用delphi的lib生产ida的sig :)
然后入口就看到
代码:
1 2 3 | mov eax, offset dword_5A5B30 call @Sysinit@@InitExe$qqrpv ; Sysinit::__linkproc__ InitExe(void *) |
在dword_5A5B30里面则可以发现这儿有0D2h个函数在表里面。这个表的结尾地址和开首地址减去之后,0x005A61C0-0x5A5B38 ==0×688,也就是 0×688包含了Init类和Fina类二种,每个指针4字节,共0xd1* 2* 4字节
代码:
1 2 3 4 5 6 7 8 9 10 11 | CODE:005A5B30 dword_5A5B30 dd 0D2h ; DATA XREF: start+6o CODE:005A5B34 dd offset off_5A5B38 CODE:005A5B38 off_5A5B38 dd offset loc_4072C8 ; DATA XREF: CODE:005A5B34o CODE:005A5B3C dd offset sub_407298 CODE:005A5B40 dd offset @System@initialization$qqrv ; System::initialization(void) CODE:005A5B44 dd offset @System@Finalization$qqrv ; System::Finalization(void) |
…
不过就像前面说的,它没用,不管它。
但接下来的代码就有意思了,dede的入口构造了大量的costumform,我们就从第一个分析一下。这是dede的logo的costumform
其完整的TMetaClass为,
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | off_5A58D4 dd offset _cls_LodoUnit_TLogoForm ; DATA XREF: start+14r CODE:005A58D8 dd 3 dup(0) CODE:005A58E4 dd offset byte_5A5A78 CODE:005A58E8 dd offset word_5A5A10 CODE:005A58EC dd 2 dup(0) CODE:005A58F4 dd offset aTlogoform ; "TLogoForm" CODE:005A58F8 dd 304h CODE:005A58FC dd offset off_466440 CODE:005A5900 dd offset @Classes@TComponent@SafeCallException$qqrp14System@TObjectpv ; Classes::TComponent::SafeCallException(System::TObject *,void *) CODE:005A5904 dd offset @Forms@TCustomForm@AfterConstruction$qqrv ; Forms::TCustomForm::AfterConstruction(void) CODE:005A5908 dd offset @Forms@TCustomForm@BeforeDestruction$qqrv ; Forms::TCustomForm::BeforeDestruction(void) CODE:005A590C dd offset @System@TObject@Dispatch$qqrpv ; System::TObject::Dispatch(void *) CODE:005A5910 dd offset @Forms@TCustomForm@DefaultHandler$qqrpv ; Forms::TCustomForm::DefaultHandler(void *) CODE:005A5914 dd offset @Comctrls@TTreeNodes@GetFirstNode$qqrv ; Comctrls::TTreeNodes::GetFirstNode(void) CODE:005A5918 dd offset sub_403D40 CODE:005A591C dd offset @Forms@TCustomForm@$bdtr$qqrv ; Forms::TCustomForm::~TCustomForm(void) CODE:005A5920 _cls_LodoUnit_TLogoForm dd offset @Controls@TWinControl@AssignTo$qqrp19Classes@TPersistent |
根据上面TMetaClass中几个重要指针的偏移,和对应位置的值,可以看到Logo的Form中没有public函数,也没有private函数。只有类自身的TTypeInfo,还有类的public成员变量表是有的。
现在看看Logo的TTypeInfo
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | CODE:005A5A78 byte_5A5A78 db 7 ; DATA XREF: CODE:005A58E4o CODE:005A5A78 ; CODE:005A5A74o CODE:005A5A78 ; Class CODE:005A5A79 db 9,'TLogoForm' ;类型名称 CODE:005A5A83 dd offset _cls_LodoUnit_TLogoForm ;虚函数表指针 CODE:005A5A87 dd offset off_466584 ; parent 父类的TTypeInfo的指针 CODE:005A5A8B dw 92 CODE:005A5A8D db 8,'LodoUnit' CODE:005A5A96 dw 0 ; PropCount |
很确定的,这儿没有我们感兴趣的信息。因为先看最后面跟着的Property表,这儿的PropCount是0.而它继承的父类是系统的form类,里面的prob和我们无关啊。
回到前面,再到成员变量的列表里找找看,嘿,有够长。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | CODE:005A5A10 word_5A5A10 dw 5 ; DATA XREF: CODE:005A58E8o CODE:005A5A12 dd offset byte_5A5A65 CODE:005A5A16 dw 2F0h CODE:005A5A18 dw 0 CODE:005A5A1A dw 0 CODE:005A5A1C db 6,'Panel1' CODE:005A5A23 dw 2F4h CODE:005A5A25 dw 0 CODE:005A5A27 dw 1 CODE:005A5A29 db 6,'Image1' CODE:005A5A30 dw 2F8h CODE:005A5A32 dw 0 CODE:005A5A34 dw 2 CODE:005A5A36 db 8,'RxLabel1' CODE:005A5A3F dw 2FCh CODE:005A5A41 dw 0 CODE:005A5A43 dw 1 CODE:005A5A45 db 6,'Image3' CODE:005A5A4C dw 300h CODE:005A5A4E dw 0 CODE:005A5A50 dw 2 CODE:005A5A52 db 8,'RxLabel2' |
如果附上成员变量的类型列表里的详细信息,就太长了!那个列表的内容按照含义可以列如下
0 TPanel
1 TImage
2 TRxLabel
可以看到,也全是系统自带的类型。那这儿当然也没有我们需要的用户所自定义的信息了 :)
对于这种在代码里面看着完全静态的东西,根据delphi的资源保存的方式,如果想继续分析它这个logo的话,我们就可以接着到RCData里面检阅下了,这儿我使用的是PEexplorer,它的资源编辑功能比Cffexploer强大不少。
对应Form的名称很容易在RcDta中找到,也可以根据LogoForm中的各个子控件(成员变量)找到它们对应的数据
一看,噢,原来就这么一个玩意儿啊。没用,没用。
:)
简单的实际分析就到此结束,最后,希望这篇文章可以帮到你。