好友
阅读权限30
听众
最后登录1970-1-1
|
这个是转帖,我最近几天对delphi程序的逆向超级感兴趣,就找了很多delphi的文章看,把复制人家原文的摘抄们发出来:
http://www.woodmann.com/fravia/dafix_t1.htm
http://bbs.pediy.com/showthread.php?threadid=31939
http://bbs.pediy.com/showthread.php?p=582964
http://www.woodmann.com/fravia/trurlvcl.htm
其他版块都是大牛玩的,看看都是操作系统之类高深的东西不知道发哪儿了,想了想发这儿好点 :P!
delphi编译器编译出来的PE,一般的段可能会有CODE,DATA,BSS,.IDAta,tls,.rdata,.rsrc。
Delphi 程序的源代码经常的,会分为二种文件: pascal源代码(.PAS, .DPR, .INC), 资源文件 (.RES, .RC, .DFM, .DCR),对应的,编译后的也分别储存在二个段里.
.rsrc段比较重要,这里除了一般的资源以外,还有工程信息和DFM资源信息,这一节开始部分是常规的资源表,接着是工程信息,最后就是DFM资源了。
DFM资源包括了所有控件的描述,控件(不论是不是可视)published的属性的初始设置。不同Form的资源在不同的一段里(就像文章的小节一样),每个都是以 #54,#50,#46,#30(TPF0)开始,紧跟其后的就是Form的名字,以pascal串形式存放(第一个字节是长度)。每一个DFM小节都是在连接的时候,由 编译器直接从原始的.dfm文件中拷过来的。
注意DFM资源是以RCDATA类型的资源储存的,win的资源默认都是储存在.rsrc节的资源表中(图标之类储存在其他名称的段里就会发现不对劲了),所以可以用资源编辑软件来编辑,效果就像Borland自己的资源编辑环境一样,虽然用RT_RCDATA Application-defined resource (raw data)的格式看的话,就是一串text字符串。
更有趣的是在DFM资源中到底包含一些什么,答案就是你在Delphi中用对象察看器(左边的 浮动窗口)看到的所有东西。这就是说我们可以仅仅编辑DFM资源数据,来让控件的任何published属性改变。其中就包括了重定位对象的事件处理程序。
另外,Delphi中的VCL控件有一些过程可以允许你读取RCDATA资源,也可以把它们转存成TXT格式。在classes.pas文件中定义了以下过程:
ObjectBinaryToText()
ObjectResourceToText()
ObjectTextToBinary()
ObjectTextToResource()
用它们可以简单把2进制的DFM RCDATA资源转换成其它的表示形式
CODE段,在CODE段的最后储存着dpr的代码,利用其中的Handler字符串名称,可以很容易查找到自己想要的事件响应函数地址。
dpr中对于事件handler的储存格式是一个地址,紧跟着handler的名称。
struct EventHandle{
LPVOID Handler;
struct String
{
BYTE LenStr_B;
char Str_cAry[];
};//pascal格式的
};
在delphi里,form中在运行时(进程中)对用户代码的调用,都是通过字符串名称来进行的,这样我们也可以通过任意一个用户事件Handler,向上跟踪找到这个包含字符串的结构,来跟踪需要的目标。
////////////
首先delphi的可执行文件都有一个InitRouterTable,其中保存了在初始化时挨个调用到的routines。
typedef struct tagFunTable
{
void (* Initialization1)(void);
void (* Finalization1)(void);
...
}FUNTABLE, *PFUNTABLE; //初始化函数和结束化函数成对出现
struct tagInitTable
{
DWORD dwNum; //初始化函数的个数(是初始化,结束化不包括在里面,也就是说只是FunTable元素个素的一半)
PFUNTABLE pFunTable;
} * InitRoutineTable;
delphi exe入口:
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库函数。
InitExe会从.rsrc读取出资源里的drm,然后调用StartExe来从InitRoutineTable读取所有的FunTable,挨个执行对应的Routine。
unknown_libname_291中判断的地方的值总是NULL, 还不清楚是做什么的。只是无关大局。
TApplication::CreateForm顾名思义,创建主Form,是delphi初始化的主要流程。传递给它的第二个参数edx就是指向TCustomForm的虚函数表的指针,用来构造程序的_cls_Forms_TCustomForm主form。
它先调用TObject::NewInstance创建一个基本的TObject类型的对象。
然后调用这个重载了TObject的TCustomForm实例的构造函数,TCustomForm::TCustomForm(Classes::TComponent *)。要注意的是,delphi的用户代码都是在消息循环里才能执行到的,FormLoad也不是这儿执行的。(??这儿不确定...想找个处理FORM_LOAD的程序没找得,也没delphi环境来自己测试。希望大牛稍微指点下)
TApplication::Run中通常先用AddExitProc把from的析构函数添加到退出时要执行的流程里,然后就进入TApplication::HandleMessage delphi的消息循环了。
Halt0是delphi退出的流程,当没有保存进入入口点的dwReason时(为0),最后调用ExitProcess结束。
delphi dll入口:
push ebp
mov ebp,esp
add esp,-3C
mov eax, InitRouterTable
call InitLib //
call @@Halt0
InitLib是Delphi的一个库函数,从这个可以定位到DllMain中会被执行的流程们。
在Halt0中,是DLL的时候,会判断一个全局结构里保存的进入DllMain的Reason,不为0时就调用一个专为DLLMain返回准备的库函数System::_16618它是以leave后再 retn 0Ch结尾的。
////
///
delphi的函数调用,众所周知的delphi是 "fastcall" 或 "Register"方式的调用
就是说,前3个参数的传递是用 eax,edx,ecx ,更多的参数,就按照从左到右的挨个压入堆栈了:
EAX:第一个参数; EDX:第二个参数; ECX:第三个参数; [ESP+8]第四,[ESP+C]第五。(如果有的话)
注意,在类成员函数的调用中,有一个隐藏的第一参数(Self,就像c++一样)- 指向的是执行这个成员函数的对象实例。
这是为了在成员函数中,反向的引用上层的对象实例中的内容们.
这个第一参数指向的对象实例,和C++一样,内容里第一个DWORD是一个类函数表的指针。
逆向时,看到call被mangled的符号,一般都是class函数的
特别的注意,对于在控件、对象中调用delphi Object函数,在调用一个object函数前,寄存器eax会被塞成一个对象指针,这个eax指针是从Self指向的object中一定offset中取得的(指向上层对象,即当前对象所继承来的那个Object),这个offset在每个程序都不一样,编译器生成的。
比方EVENT事件这个object,先是PROCADDR处理例程,接着就是它的parent object的地址了:
struct EVENT
DWORD PROCADDRES
DWORD OBJECTADDRESS
ends
OBJECTADDRESS is an object reference(self,this), the value is transmitted to eax before call to an object's event handler(使用EVENT object元素PROCADDRES的过程).
通常delphi object,比如form的元素和处理过程,它都会在一个全局的结构保存它的处理过程的地址和处理过程的符号名称,以及元素的名称和元素的偏移。定义到一个处理过程时,可以查看引用到它的部分查到这个结构。
delphi的动态链接库叫做Packages.事实上就是DLL,只是通常会静态链接,所以大部分时候都是作为lib的。
//-----------
VCL的消息流程详细的如下:
接到消息,进入StdWndProc (Windows: HWND; Message: Longint; LParam: Longint): Longint; stdcall;
StdWndProc会调用主form的TWinControl.MainWndProc (var Message: TMessage);
这种WndProc在每个需要处理Message的控件里都有一个,在其中的代码是确定是否由自己处理这个Message。类似于MFC的messagemap,被循环查询。
而每个控件中所包含的子控件,在这个父控件的WndProc中,除了自己执行,还会通过DoControlMsg把message分发给子控件的WndProc来查询子控件。这样层层控件加上各负其责,就实现了直接明确到点的消息分发。
一般要跟踪一个什么事件时,都是用户定义的事件了,当然,也是一个自定义的Form。于是MainWndProc会调用
TCustomForm.WndProc (var Message: TMessage);
TCustomForm的WndProc是不处理消息的,它只是又把Message分发给T自己的上层控件,WinControl.WndProc。
TWinControl是一个包装下层具体东西的包装,它里面的WndProc也是空的,只是把Message分发给它的上层控件类型,从TControl.WndProc开始就实质性的分发Message了,它处理很少的几个Message。除此之外的都抛给自己的上层控件TObject了。
从TObject这儿开始来分发消息到对应的子控件上面。
TObject被TControl调用用来分发消息的不是WndProc了,是一个特别的TObject.Dispatch(var Message);
TObject.Dispatch负责所有的事件方法(在delphi IDE 对象属性面版中的所有事件方法)的分派和动态方法的调用,它是对虚拟方法调用的一种内存资源优化.
TObject.Dispatch的参数是一个指针,其指向的第一个WORD就是Dynamic Methods Table 的索引值,用来获得事件handler的流程地址。
除了在TObject.Dispatch中从message指针中取得si,在delphi中调用控件的一个事件,也就是delphi的SendMessage,是这样格式的:
mov si ,FFxx
Call _CallDynaInst;//索引动态函数指针表
这种动态消息分派方法在delphi大量常见,几乎所有的消息事件都是由这种固定模式分派寻找末端的事件方法
从上边的 _CallDynaInst中它内部又调用GetDynaMethod(vmt: TClass; selector: Smallint) : Pointer来获得handle,它调用GetDynaMethod的方法,与TObject.Dispatch(var Message)内部的样式雷同,它们共同约定的寄存器:eax是对象地址,si是动态方法索引值,[eax+vmtDynamicTable]动态方法表,这比C++等其他面向对象多了一个动态函数表格,最终找到事件句柄然后jmp进去.
比如:TButton.Click,查找到一个地址发现EB是它的索引值,那么搜索常量eb就可以得到所有的Tbutton.click调用了。
用这些索引值找特殊事件是个不错的方式.
在这个事件句柄动态表中保存的流程不是直接是对应控件的事件handler,而是一个wapper,会把传来的参数判断下事件指针的高二位(x86上的WORD)是否为0,来确定是否可以调用。当实例中的指针有handler时,接着就会调用对应的控件实例所对应的事件指针了。
当TObject.Dispatch找不到对应的事件handle时,就会调用一个类似于默认消息流程的TControl.DefaultHandler
8B08 mov ecx, [eax]---->对象虚拟表首地址给ecx
FF61 F0 jmp [ecx-10]----------->TControl.DefaultHandler(var Message);
以上消息分派模式是VCL固定不变,变的是继承对象的多态转换...以上分派模式不光是针对WM_COMMAND消息,而且还是WM_Paint等标准Window分派模式,同样也是控件与父窗体通信的消息分派模式例如Win标准控件消息(WM_COMMAND)和公共控件消息(WM_NOTIFY).
按钮事件的WM_Command消息分派流程:
StdWndProc-->TWinControl.MainWndProc--->TCustomForm.WndProc--->TWinControl.WndProc->TControl.WndProc-----
--->TObject.Dispatch--->TCustomForm.WMCommand--->TWinControl.WMCommand-->DoControlMsg(父窗体不理会并把控件通知码发向子控件回调函数)--->TControl.Perform---
--->TButtonControl.WndProc-->TWinControl.WndProc--->TControl.WndProc--->TObject.Dispatch--->TButton.CNCommand---
--->TButton.Click--->TControl.Click--->TButton.CNCommand--->TButton.Click--->TControl.Click;
在Dispatch中调用了TCustomForm.WMCommand以处理收到的WM_COMMAND 消息,在TCustomForm中也可能会有对按钮事件的响应,比方标题栏菜单、右键菜单等存在于主Form自身内部的按钮。
TCustomForm.WMCommand接着调用TWinControl中的WMCommand,这个窗口控件中的WMCommand会通过执行
DoControlMsg(ControlHandle: HWnd; var Message): Boolean;
把消息分发给主form的子控件们。当子控件们没有处理掉这个Message时,它又调用TObject.Dispatch来继续分发了(给其他的form),这儿的递归里面又能调用TObject.Dispatch,是因为TObject.Dispatch中跳转到事件Handle的方法是用jmp,即没有在堆栈上占它的一层。
DoControlMsg分发消息给子控件的方法是,先用FindControl(Handle: HWnd): TWinControl;查找受理message的hwnd所对应的TWinControl实例是哪个,接着调用
TControl.Perform(Msg + CN_BASE, WParam, LParam);CN_BASE=BC00
把这个消息传递给子控件,让它处理。至此,上层控件里对子控件消息的传递完成。可以认为它这一次的消息处理到此结束了。
在TControl.Perform中,它会调用Find到的TWinControl实例的MainWndProc,来让子控件的消息流程处理消息。
它与上边MainWndProc到TCustomForm.WndProc(var Message: TMessage)的分派路线是相似的,但是终点是不同的.这正是继承出来的多态对象改变了终点,二个实例一个TfrmMain对象一个TButton对象,在完全相同的流程中每个函数都传递的对象指针,然后相同的流程因为不同的对象就执行出不同的结果了。
这个区别是在Dispatch时发生的,它在事件handle指向的wapper中调用的是具体实例的事件handle,子控件的handle就会被调用到了。
在TButton.CNCommand中,首先GetParentForm(Self),查找TButton的上层控件,然后设置上层控件的成员变量ModalResult,这是为了在模态对话框中,可以和上层控件进行交互。接着就会调用它的上层控件TControl.Click,以去调用真正的Click。
注意在TControl.Click里还判断了button是否有对应的TAction,通过调用TAction.OnExecute来得到对应的TAction对象地址,这个对象存在时,在Button自己的Click事件Handler调用前,会先调用TActionLink.Execute(Self)。因此按钮事件也可能出现在这儿,用Dede分析TActionLink可以得到相关信息。
最后,一起来膜拜 DEDE源代码吧。delphi完全看不懂,这些笔记也许没什么用,真正需要分析delphi时可能还是得参考dede里的结构看:
http://ftp.twaren.net/cpatch/patchutil/dede/source/
|
|