元旦好无聊呀。独自一个人在成都。。。
Delphi逆向分析中较困难的就是一些VCL、RTL库函数被IDA识别成unknowlib,还有许多虚函数的调用,例如:call [edx+0xC],此外Pascal的参数传递方式也比较让人不习惯。虽然有DeDe这样强大的逆向工具,但个人在处理以上问题是仍需要较强的手动分析逆向技巧。
Delphi的参数传递:
Eax:第一个参数
Edx:第二个参数
Ecx:第三个参数
[Esp+8]第四,[esp+c]第五。。。(如果有的话)
Meta结构
每个类都有一个Meta结构,在这里我姑且可以把它认为是类的模板。以下是一个Meta结构。
(delphi源码)
type
MyBaseClass=class
Number:integer;
public
constructor Create(num:integer);virtual;
class procedure StaticTest();virtual;
procedure Test();virtual;
end;
MyChildClass=class(MyBaseClass)
newNumber:integer;
public
constructor Create(num:integer);override;
procedure Test();override;
class procedure StaticTest();override;///类方法(静态方法)
end;
MySingleClass=class
Number:integer;
end;
(编译后的Meta结构)
从ida中copy汇编出来,没对齐好,太丑了。这个我就简单写下结构,详细请看project9.idb
MyChildClassMeta dd offset childVtr (指向虚函数表,如果没有虚函数则指向类名称)
dd 7 dup(0) (7个dd 0,不知道有什么用,好像除了继承自TCompont和Exception的类,其他都是7个 dd 0)有谁发现了请告诉我^_^
dd offset aMychildclass ;指向类名称字符串
dd 0ch ;对象大小Sizeof(MyChild)=虚函数表指针大小+2个int=0xC
dd offset MyBaseClassMeta ;指向父类的Meta结构
######################
dd offset @System@TObject@SafeCallException$qqrp14System@TObjectpv ; System::TObject::SafeCallException(System::TObject *,void *)
dd offset nullsub_15
dd offset nullsub_30
dd offset @System@TObject@Dispatch$qqrpv ; System::TObject::Dispatch(void *)
dd offset nullsub_29
dd offset @Comctrls@TTreeNodes@GetFirstNode$qqrv ; Comctrls::TTreeNodes::GetFirstNode(void)
dd offset sub_4036A4
dd offset @System@TObject@$bdtr$qqrv ; System::TObject::~TObject(void)
######################
(以上结构比较固定,好像在所有的类里面都是这样)
childVtr dd offset sub_44CCB8(虚函数表)
dd offset sub_.....
dd offset sub_.....
db 12,'MyChildClass'
Delphi中的构造函数
(source)
constructor MyBaseClass.Create(num:integer);
begin
Number:=num;
end;
constructor MyChildClass.Create(num:integer);
begin
inherited Create(num);
newNumber:=num+1;
end;
汇编:
--------------------------------------------------------
mov ecx, 3
mov dl, 1;;alloc是否分配空间
mov eax, MyChildClassMeta
call ChildCreate ;;;调用
--------------------------------------------------------
; int __fastcall ChildCreate(Meta ChildMeta, bool alloc, int num)
ChildCreate proc near
CODE:0044CD38 push ebx
CODE:0044CD39 push esi
CODE:0044CD3A push edi
CODE:0044CD3B test dl, dl
CODE:0044CD3D jz short loc_44CD47
CODE:0044CD3F add esp, 0FFFFFFF0h
CODE:0044CD42 call @System@@ClassCreate$qqrp17System@TMetaClasso ; System::__linkproc__ ClassCreate(System::TMetaClass *,bool) ;;根据Meta分配对象空间,并初始化默认值
CODE:0044CD47
CODE:0044CD47 loc_44CD47:
CODE:0044CD47 mov esi, ecx
CODE:0044CD49 mov ebx, edx
CODE:0044CD4B mov edi, eax
CODE:0044CD4D mov ecx, esi
CODE:0044CD4F xor edx, edx ;dl=0基类构造函数,不再分配空间
CODE:0044CD51 mov eax, edi
CODE:0044CD53 call BaseCreate ;
CODE:0044CD58 inc esi
CODE:0044CD59 mov [edi+8], esi
CODE:0044CD5C mov eax, edi
CODE:0044CD5E test bl, bl
CODE:0044CD60 jz short loc_44CD71
CODE:0044CD62 call @System@@AfterConstruction$qqrp14System@TObject ; System::__linkproc__ AfterConstruction(System::TObject *)
CODE:0044CD67 pop large dword ptr fs:0
CODE:0044CD6E add esp, 0Ch
CODE:0044CD71
CODE:0044CD71 loc_44CD71:
CODE:0044CD71 mov eax, edi
CODE:0044CD73 pop edi
CODE:0044CD74 pop esi
CODE:0044CD75 pop ebx
CODE:0044CD76 retn
CODE:0044CD76 ChildCreate endp
构造函数经过编译后会自动添加两个参数Meta ChildMeta, bool alloc。
Meta类模板,alloc是否分配空间。从汇编代码中可以看到,传入ChildMeta, 调用System@@ClassCreate为 MyChild对象分配了0xC大小的空间,并初始化默认值(这里的初始化指变量设置为0或nil,并非值构造函数内的初始化),然后再调用基类构造函数,可以看到,在调用基类构造函数时,dl=0;即此时不再分配空间。然后再继续完成Child类的初始化---newNum:=num+1.
有了以上知识我们就可以轻而易举地进行delphi程序的逆向了。
Quick Batch 2.7逆向
Quick Batch是一个将bat转换成exe的工具(加密压缩后保存在exe文件中,2.7版本的保存在append data中,3.0以上版本保存在资源中),该工具还可以捆绑其他文件。因此经常被恶意程序所利用。
事先准备:
1. delphi的VCL、RTL源码(在delphi安装目录下source文件夹);
2. Delphi帮助文档.
Ida载入Quick Batch测试程序test.exe。
1.找到meta定义部分,将所有的meta数据重命名;
2.转到主程序部分
主程序部分
由于delphi从start开始就直接是main函数(不像C,有一段的先导代码然后才跳到main),因此可以将start直接转换成函数,这样ida就能直接标出局部变量。
1. 识别出构造函数,一般在跟在Meta后面的即是该类的构造函数,例如:
CODE:00418EC7 xor ecx, ecx
CODE:00418EC9 mov dl, 1
CODE:00418ECB mov eax, THKStreamsMeta
CODE:00418ED0 call THKstreams@Create
2. 将虚函数重命名;例如:
CODE:00418FCC mov edx, ds:memStreamObj3
CODE:00418FD2 mov eax, ds:strListObj1
CODE:00418FD7 mov ecx, [eax]
CODE:00418FD9 call dword ptr [ecx+6Ch]
此处Ctrl+R,将基址改为TStringListMeta的虚函数表开始,就变成这样了:
call (TstringList@LoadFromStream - _cls_Classes_TStringList)[ecx] ;
3. 查delphi帮助,认清RTL、VCL类的继承关系,识别出各个参数含义
CODE:00418E94 push ebp
CODE:00418E95 mov ebp, esp
CODE:00418E97 mov ecx, 9
CODE:00418E9 。。。。。。略过一些
CODE:00418EC7 xor ecx, ecx
CODE:00418EC9 mov dl, 1
CODE:00418ECB mov eax, THKStreamsMeta
CODE:00418ED0 call THKstreams@Create;;;创建THKS对象
CODE:00418ED5 mov ds:THKstreamsObj, eax
CODE:00418EDA mov eax, ds:THKstreamsObj
CODE:00418EDF mov byte ptr [eax+30h], 1 ; compressed=true
CODE:00418EE3 mov eax, ds:THKstreamsObj
CODE:00418EE8 mov byte ptr [eax+31h], 1 ; EnCrypted=true
CODE:00418EEC mov eax, ds:THKstreamsObj
CODE:00418EF1 add eax, 32h ; key
CODE:00418EF4 mov edx, offset aQuickBatchFile ; "Quick Batch File Compiler" ;;; quick batch as the keyword
。。。。。。。再略过一些
CODE:00418F72 mov eax, [ebp+var_18]
CODE:00418F75 mov edx, ds:MemStreamObj1
CODE:00418F7B call ReadEncrpyData
CODE:00418F80 mov ds:byte_41E960, al
CODE:00418F85 cmp ds:byte_41E960, 0
CODE:00418F8C jz ReadFailedHandler
CODE:00418F92 mov edx, ds:MemStreamObj1 ; ms
CODE:00418F98 mov eax, ds:THKstreamsObj ; HKStream
CODE:00418F9D call HKStream@LoadFromStream;;;;Load进来以后实际上已经解密了。
CODE:00418FA2 mov ecx, ds:memStreamObj2
CODE:00418FA8 mov edx, offset aBat ; "BAT"
CODE:00418FAD mov eax, ds:THKstreamsObj
CODE:00418FB2 call HKS@GetStream;;得到BAT段数据,放入ms2
CODE:00419102 mov ecx, ds:dword_41E968
CODE:00419108 mov edx, ds:TmpPath
。。。。略
CODE:00418FDC mov edx, ds:memStreamObj2
CODE:00418FE2 mov eax, ds:StrListObj2
CODE:00418FE7 mov ecx, [eax]
CODE:00418FE9 call (TstringList@LoadFromStream - _cls_Classes_TStringList)[ecx]
;;;;重新将ms载入到StrList2中
CODE:00419113 mov edx, [ebp+var_34]
CODE:00419116 mov eax, ds:StrListObj2
CODE:0041911B mov ecx, [eax]
CODE:0041911D call (SaveToFile - _cls_Classes_TStringList)[ecx] ; 保存strList2到bat文件
THKStreams结构
THKStreams = class(TComponent)
private
FCompressed,FEncrypted : Boolean;
FKey : String;
FOnAskForKey : TOnAskForKey;
FOnCorrupt : TNotifyEvent;
procedure WriteStr(S: String; Stream: TStream);
function ReadStr(Stream: TStream): string;
Procedure LoadFromStreamNor(ms : TStream);
Procedure SaveToStreamNor(ms : TStream);
function CheckGood(ms: TStream): boolean;
procedure FoundCorrupt;
public
StreamList : TStringList;
Constructor Create(AOWner : TComponent); override;
Destructor Destroy; override;
Procedure LoadFromFile(const Filename : string);
Procedure SaveToFile(const Filename : string);
Procedure AddStream(Const ID : string; Source : TStream);
Procedure RemoveStream(Const ID : String);
Procedure LoadFromStream(ms : TStream);
Procedure SaveToStream(ms : TStream);
Procedure GetStream(Const ID : string; Dest : TStream);
Procedure ClearStreams;
published
property Compressed : boolean read FCompressed write FCompressed;
property Encrypted : boolean read FEncrypted write FEncrypted;
Property Key : string read FKey write FKey;
Property OnAskForKey : TOnAskForKey read FOnAskForKey write FOnAskForKey;
property OnCorrupt : TNotifyEvent read FOnCorrupt write FOnCorrupt;
end;
THKStreams数据保存在streamList结构中,像这样 “BAT”bat data; “Files”FileData …..
完了之后,汇编就非常容易阅读了。可以轻而易举地写出delphi解压源码。(解密代码请见\hks\UnpackQuickBatch.drp)
THKStreams这个类来自第三方,可以从网络上下载到。Quick Batch的加密解密都是用了它。
- 标 题:Delphi逆向分析---quick batch逆向
- 作 者:myskydog
- 时 间:2009-01-09 19:37
- 链 接:http://bbs.pediy.com/showthread.php?t=80205
Delphi逆向分析---quick batch逆向