肥牛 发表于 2017-6-9 21:46

用实例讲解如何读懂DELPHI程序的反编译

本帖最后由 肥牛 于 2017-6-9 21:53 编辑

最近分析了几个CM以及商品程序,都是DELPHI程序写的。我发出来的文章,有很多新手也问了好多问题。我发现很多都是属于比较基础的,所以我觉得有必要把一些基础性的东西讲出来给新手。这里的新手指的是新学编程的,新学破解的。
用DELPHI写了一个非常简单的程序,两个输入框,输入用户名,输入注册码,点击注册按钮会检验用户名与注册码是否正确,错误的话提示注册失败。

是不是就像一个CM?这就是一个非常简单非常简单的CM。但是这次我们不是讲怎么破解它,我们要讲它反编译以后是什么样子的。
首先,我们先看一下它的DELPHI源码是什么样的吧。因为我们关注的是注册按钮事件,所以只看这个事件的代码:
var
strTemp : string;
begin
if Trim(edtName.Text)='' then
begin
    Application.MessageBox('请输入用户名!', '错误', MB_OK + MB_ICONWARNING);
    edtName.SetFocus;
    Exit;
end;
if Trim(edtCode.Text)='' then
begin
    Application.MessageBox('请输入注册码!', '错误', MB_OK + MB_ICONWARNING);
    edtCode.SetFocus;
    Exit;
end;
strTemp :=Trim(edtName.Text);
if GetCode(strTemp)=Trim(edtCode.Text) then
    Application.MessageBox('注册成功!', '提示', MB_OK + MB_ICONINFORMATION)
else
    Application.MessageBox('注册失败!', '提示', MB_OK + MB_ICONINFORMATION);
有一点编程经验的朋友一定可以读懂这段代码,非常非常简单。即使你没学过编程,猜也能猜出个大概。
因为这里我需要演示一个CALL调用,所以,注册码的验证部分,我单独写了一个函数GetCode,源码如下:
function GetCode(strName : string) : string;
var
i, iLen, iSum : integer;
begin
iLen := Length(strTemp);
iSum :=0;
for i:=1 to iLen do
    iSum :=iSum *10 + Ord(strTemp);
Result := IntToStr(iSum);
end;
GetCode的意思我简单说明一下,就是把输入的字符串,按顺序每个字节取ASCII码(DELPHI语言中取字符的ASCII码用Ord函数,VB里应该是ASC函数),乘以10以后加上下一个字节的ASCII码,和再乘以10再加下一个,以此类推。最后的和转为字符串返回。
OK,到这里,相信大家都能看懂这个程序的意思了。用户名按照上面写的算法计算,得出来的就是注册码,然后与输入的注册码比较,给出相应的提示。
那么我们下面就看反编译的结果。DELPHI程序的反编译,首选是DeDe,它反编译的代码,结构清晰,内部函数都标注出来了。但它的缺点就是不支持高版本的DELPHI。对付高版本的DELPHI,就得用IDR了。IDR反编译的结果和DeDe其实一样,只是对于一些内部的函数及变量,标识的没有DeDe那样清晰。对于熟悉DELPHI的人或者经验比较丰富的,用IDR可能更快捷。对于初学者,还是从DeDe入手为好。
使用DeDe加载这个程序,可以非常容易看到这个程序只有两个事件,那我们要处理的就是注册按钮点击事件。

鼠标双击“btnRegisterClick”,会弹出一个新窗口,这个窗口中的代码,就是我们要分析的反编译后的代码,我把它贴出来。
004537F4   55                     push    ebp
004537F5   8BEC                   mov   ebp, esp
004537F7   B904000000             mov   ecx, $00000004
004537FC   6A00                   push    $00
004537FE   6A00                   push    $00
00453800   49                     dec   ecx
00453801   75F9                   jnz   004537FC
00453803   51                     push    ecx
00453804   53                     push    ebx
00453805   8BD8                   mov   ebx, eax
00453807   33C0                   xor   eax, eax
00453809   55                     push    ebp
0045380A   6871394500             push    $00453971

***** TRY                        //一般来说,DeDe中的代码, TRY这一行以上,以及后面FINALLY以下的,都可以不用看了

//DELPHI程序的特点,在CALL之前,EAX中往往是要处理的数据,EDX中是返回值,知道这个对我们看懂程序有很大帮助
|
0045380F   64FF30               push    dword ptr fs:
00453812   648920               mov   fs:, esp
00453815   8D55F4               lea   edx,       //刚才说了,EDX中是返回值,而LEA的意思就是传送的是一个地址。所以这句话的意思其实就是把返回值放到这个地址中

* Reference to control edtName : TEdit
|
00453818   8B83FC020000         mov   eax,       //中的值放到eax中,很明显,指向的应该是edtName这个输入框

* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
0045381E   E8ADEAFDFF             call    004322D0                //GetText是单参数的,输入是个TControl,输出是TCaption,从edtName输入框中获取值,保存到EDX中
00453823   8B45F4               mov   eax,       //上面这个CALL调用,返回结果也就是edtName中的内容保存到中了,现在再把这个值赋给EAX
00453826   8D55F8               lea   edx,       //同上,意思是返回值放在中

* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
00453829   E8FE46FBFF             call    00407F2C                //Trim是单参数的,输入ANSIString, 输出也是AnsiString。把EAX的字符串做TRIM操作,返回值放到EDX(也就是)中
0045382E   837DF800               cmp   dword ptr , +$00      //比较中是不是空
00453832   752B                   jnz   0045385F
00453834   6A30                   push    $30

* Possible String Reference to: '错误'
|
00453836   B980394500             mov   ecx, $00453980

* Possible String Reference to: '请输入用户名!'
|
0045383B   BA88394500             mov   edx, $00453988
00453840   A16C504500             mov   eax, dword ptr [$0045506C]
00453845   8B00                   mov   eax,

* Reference to: Forms.TApplication.MessageBox(TApplication;PChar;PChar;Longint):Integer;
|
00453847   E8E4E8FFFF             call    00452130                //MessageBox是四个参数,输入EAX是TApplication,ECX是标题,EDX是提示信息,$30是对话框类型。估计返回值也是EDX,只是这里没用上返回值。

* Reference to control edtName : TEdit
|
0045384C   8B83FC020000         mov   eax,
00453852   8B10                   mov   edx,
00453854   FF92C4000000         call    dword ptr       //猜测,这里应该是SetFocus的意思
0045385A   E9C5000000             jmp   00453924
0045385F   8D55EC               lea   edx,       //这里开始处理edtCode了,和上面的分析一样,我就不再写了。

* Reference to control edtCode : TEdit
|
00453862   8B83F8020000         mov   eax,

* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
00453868   E863EAFDFF             call    004322D0
0045386D   8B45EC               mov   eax,
00453870   8D55F0               lea   edx,

* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
00453873   E8B446FBFF             call    00407F2C
00453878   837DF000               cmp   dword ptr , +$00
0045387C   7528                   jnz   004538A6
0045387E   6A30                   push    $30

* Possible String Reference to: '错误'
|
00453880   B980394500             mov   ecx, $00453980

* Possible String Reference to: '请输入注册码!'
|
00453885   BA98394500             mov   edx, $00453998
0045388A   A16C504500             mov   eax, dword ptr [$0045506C]
0045388F   8B00                   mov   eax,

* Reference to: Forms.TApplication.MessageBox(TApplication;PChar;PChar;Longint):Integer;
|
00453891   E89AE8FFFF             call    00452130      

* Reference to control edtCode : TEdit
|
00453896   8B83F8020000         mov   eax,
0045389C   8B10                   mov   edx,
0045389E   FF92C4000000         call    dword ptr
004538A4   EB7E                   jmp   00453924
004538A6   8D55E8               lea   edx,                 //返回值放到

* Reference to control edtName : TEdit
|
004538A9   8B83FC020000         mov   eax,                 //还是对edtName输入框操作

* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
004538AF   E81CEAFDFF             call    004322D0                        //取输入框内容
004538B4   8B45E8               mov   eax,                 //取出的内容放到eax中
004538B7   8D55FC               lea   edx,                 //放返回值

* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
004538BA   E86D46FBFF             call    00407F2C                        //做Trim操作。这里大家应该能知道了,Trim后的结果保存到中了
004538BF   8D55E4               lea   edx,                 //这里又放另一个返回值,哪个的返回值呢?
004538C2   8B45FC               mov   eax,                 //Trim后的结果进行操作

|
004538C5   E8B6FEFFFF             call    00453780                        //函数A。函数A去处理Trim(edtName),处理后的结果保存到里
004538CA   8B45E4               mov   eax,                 //结果放到EAX里
004538CD   50                     push    eax                              //压栈,保存起来
004538CE   8D55DC               lea   edx,                 //返回值放

* Reference to control edtCode : TEdit
|
004538D1   8B83F8020000         mov   eax,                 //对edtCode输入框进行处理      

* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
004538D7   E8F4E9FDFF             call    004322D0                        //取edtCode输入框的内容保存到中
004538DC   8B45DC               mov   eax,                 //……,上面都写那么多了,下面应该能读懂了
004538DF   8D55E0               lea   edx,

* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
004538E2   E84546FBFF             call    00407F2C                        
004538E7   8B55E0               mov   edx,                 //OK,到这里,Trim(edtCode)的值保存到edx中了
004538EA   58                     pop   eax                              //出栈,也就是把刚才保存的FunA(Trim(edtName))的值保存到EAX中

* Reference to: System.@LStrCmp;
|
004538EB   E8FC0CFBFF             call    004045EC                        //EAX和EDX的值进行比较
004538F0   751A                   jnz   0045390C                        //OK,后面不写了,综合上面写的,就是判断Trim(edtCode)是不是等于FunA(Trim(edtName)),于是需要去判断FunA是什么
004538F2   6A40                   push    $40

* Possible String Reference to: '提示'
|
004538F4   B9A8394500             mov   ecx, $004539A8

* Possible String Reference to: '注册成功!'
|
004538F9   BAB0394500             mov   edx, $004539B0
004538FE   A16C504500             mov   eax, dword ptr [$0045506C]
00453903   8B00                   mov   eax,

* Reference to: Forms.TApplication.MessageBox(TApplication;PChar;PChar;Longint):Integer;
|
00453905   E826E8FFFF             call    00452130
0045390A   EB18                   jmp   00453924
0045390C   6A40                   push    $40

* Possible String Reference to: '提示'
|
0045390E   B9A8394500             mov   ecx, $004539A8

* Possible String Reference to: '注册失败!'
|
00453913   BABC394500             mov   edx, $004539BC
00453918   A16C504500             mov   eax, dword ptr [$0045506C]
0045391D   8B00                   mov   eax,

* Reference to: Forms.TApplication.MessageBox(TApplication;PChar;PChar;Longint):Integer;
|
0045391F   E80CE8FFFF             call    00452130
00453924   33C0                   xor   eax, eax
00453926   5A                     pop   edx
00453927   59                     pop   ecx
00453928   59                     pop   ecx
00453929   648910               mov   fs:, edx

****** FINALLY
|

* Possible String Reference to: '[嬪]?
|
0045392C   6878394500             push    $00453978
00453931   8D45DC               lea   eax,

* Reference to: System.@LStrClr(void;void);
|
00453934   E8A708FBFF             call    004041E0
00453939   8D45E0               lea   eax,
0045393C   BA02000000             mov   edx, $00000002

* Reference to: System.@LStrArrayClr(void;void;Integer);
|
00453941   E8BE08FBFF             call    00404204
00453946   8D45E8               lea   eax,
00453949   BA02000000             mov   edx, $00000002

* Reference to: System.@LStrArrayClr(void;void;Integer);
|
0045394E   E8B108FBFF             call    00404204
00453953   8D45F0               lea   eax,

* Reference to: System.@LStrClr(void;void);
|
00453956   E88508FBFF             call    004041E0
0045395B   8D45F4               lea   eax,

* Reference to: System.@LStrClr(void;void);
|
0045395E   E87D08FBFF             call    004041E0
00453963   8D45F8               lea   eax,
00453966   BA02000000             mov   edx, $00000002

* Reference to: System.@LStrArrayClr(void;void;Integer);
|
0045396B   E89408FBFF             call    00404204
00453970   C3                     ret


* Reference to: System.@HandleFinally;
|
00453971   E94602FBFF             jmp   00403BBC
00453976   EBB9                   jmp   00453931

****** END
|
00453978   5B                     pop   ebx
00453979   8BE5                   mov   esp, ebp
0045397B   5D                     pop   ebp
0045397C   C3                     ret

我在这段代码里也写了注释,大家可以结合上面的DELPHI源码,对照看,就更容易理解。这里需要特别强调的是,DELPHI程序的每个动作,其实都是通过CALL实现的,也就是调用WINDOWS的API函数,或者调用DELPHI自己封装好的函数,或者调用自己程序的内部函数。那么CALL函数的参数,基本上就是EAX,返回值也多为EDX(不是绝对啊,这个要看具体情况)。所以在分析DELPHI反编译的代码时就会发现,翻来覆去倒腾EAX,EDX。
而这段代码中,有一个关键的地方就是
004538C5   E8B6FEFFFF             call    00453780                        //函数A。函数A去处理Trim(edtName),处理后的结果保存到里
这个CALL调用的函数A,就是我在DELPHI程序里写的GetCode函数。这个反编译后的代码是这样的:
00453780   55                     push    ebp
00453781   8BEC                   mov   ebp, esp
00453783   51                     push    ecx
00453784   53                     push    ebx
00453785   56                     push    esi
00453786   8BF2                   mov   esi, edx                //一般函数调用的返回值是通过EDX传递的,入口部分先把这个值保存到ESI中
00453788   8945FC               mov   , eax      //传入的参数
0045378B   8B45FC               mov   eax,

* Reference to: System.@LStrAddRef(void;void):Pointer;
|
0045378E   E8FD0EFBFF             call    00404690
00453793   33C0                   xor   eax, eax
00453795   55                     push    ebp
00453796   68E8374500             push    $004537E8

***** TRY
|
0045379B   64FF30               push    dword ptr fs:
0045379E   648920               mov   fs:, esp
004537A1   8B45FC               mov   eax,

* Reference to: System.@LStrLen(String):Integer;
|         or: System.@DynArrayLength;
|         or: System.DynArraySize(Pointer):Integer;
|         or: Variants.DynArraySize(Pointer):Integer;
|
004537A4   E8F70CFBFF             call    004044A0                //取字符串长度,保存到EAX中(按道理应该是保存到EDX中的,可是按照这里代码的理解却是保存到EAX中了,请高手指点)
004537A9   33DB                   xor   ebx, ebx                //求和值EBX=0
004537AB   85C0                   test    eax, eax                //长度为0就跳转
004537AD   7E1A                   jle   004537C9
004537AF   BA01000000             mov   edx, $00000001      //EDX=1,循环初值
004537B4   8B4DFC               mov   ecx,       //ECX=要处理的字符串
004537B7   0FB64C11FF             movzx   ecx, byte ptr       //按顺序取字符串的一个字节
004537BC   03DB                   add   ebx, ebx                //EBX=EBX*2;
004537BE   8D1C9B               lea   ebx,       //EBX=EBX*5。原来的代码写的是EBX=EBX*10,计算机将*10转化为*2*(4+1),因为第一个自身相加,只用一个操作数,处理速度快,*4相当于左移两位。总之,这样处理是为了加快计算速度。
004537C1   03CB                   add   ecx, ebx                //ECX=ECX+EBX
004537C3   8BD9                   mov   ebx, ecx                //EBX=ECX。综上,这段代码的结果就是EBX=EBX*10+ECX
004537C5   42                     inc   edx                        //EDX+1,准备下一个循环
004537C6   48                     dec   eax                        //字符串长度计数
004537C7   75EB                   jnz   004537B4
004537C9   8BD6                   mov   edx, esi                //把函数入口时保存的EDX取出来
004537CB   8BC3                   mov   eax, ebx                //把求和结果EBX赋值给EAX

* Reference to: SysUtils.IntToStr(Integer):AnsiString;overload;
|
004537CD   E85A48FBFF             call    0040802C                //整型转字符串,结果保存到EDX中
004537D2   33C0                   xor   eax, eax
004537D4   5A                     pop   edx
004537D5   59                     pop   ecx
004537D6   59                     pop   ecx
004537D7   648910               mov   fs:, edx

****** FINALLY
|
004537DA   68EF374500             push    $004537EF
004537DF   8D45FC               lea   eax,

* Reference to: System.@LStrClr(void;void);
|
004537E2   E8F909FBFF             call    004041E0
004537E7   C3                     ret


* Reference to: System.@HandleFinally;
|
004537E8   E9CF03FBFF             jmp   00403BBC
004537ED   EBF0                   jmp   004537DF

****** END
|
004537EF   5E                     pop   esi
004537F0   5B                     pop   ebx
004537F1   59                     pop   ecx
004537F2   5D                     pop   ebp
004537F3   C3                     ret
这段代码也结合着前面的DELPHI代码,对比着看,相信大家很容易就理解了。
这里有个小技巧,我们看反编译的代码的地址段是00453XXX,而程序里的CALL调用,很多都是CALL 0040XXXX,这说明调用的是DELPHI封装的函数,这个知道功能就行了,不需要跟进去。而我刚才说的那个函数,是CALL 00453780,与程序是同一个地址段,说明这个是一个用户函数,对于这样的函数,在调试的时候有必要跟进去查看。
好了,对于这个反编译的讲解就到这里,有什么不明白的欢迎留言。
例子程序也附上,方便大家调试学习。

BY丶显示 发表于 2017-6-9 22:23

基础东西先收藏一下。精彩文档谢谢分享。{:301_1003:}

爱飞的猫 发表于 2017-7-30 05:54

Delphi 的调用约定是 fastcall,前三个参数用 edx、eax 与 ecx 代替,之后的用堆栈。“返回值一般是 edx” 并不准确,应该说是使用 lea 计算地址的指令更可能是储存返回值的内存区域。

gunxsword 发表于 2017-6-9 21:53

不错的讲解,帮顶一下,写这么多,真是辛苦了

玩世不攻 发表于 2017-6-9 22:16

真的很基础,收藏一下 感谢分享

hlh2518 发表于 2017-6-10 06:28

学习了啊

heavy_fire 发表于 2017-6-10 08:19

很强大,拜读一下

yinchuanlong 发表于 2017-6-10 08:41

fgffffffffffffffffffffffff

xiaoxi2011 发表于 2017-6-10 09:58

谢谢分享
好多看前学过点DELPHI,现在全还给老师了

pwp 发表于 2017-6-10 11:28

:wwqwq分析得好透彻,我什么时候才打到你这种境界啊

njzycwc 发表于 2017-6-10 13:39

学习了解!!
页: [1] 2 3 4
查看完整版本: 用实例讲解如何读懂DELPHI程序的反编译