delphi程序反编译

发布时间:2019-05-19编辑:佚名阅读(4938)

最近分析了几个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[i]);
  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:[eax]
00453812   648920                 mov     fs:[eax], esp
00453815   8D55F4                 lea     edx, [ebp-$0C]        //刚才说了,EDX中是返回值,而LEA的意思就是传送的是一个地址。所以这句话的意思其实就是把返回值放到[ebp-$0C]这个地址中
 
* Reference to control edtName : TEdit
|
00453818   8B83FC020000           mov     eax, [ebx+$02FC]        //[ebx+$02FC]中的值放到eax中,很明显,[ebx+$02FC]指向的应该是edtName这个输入框
 
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
0045381E   E8ADEAFDFF             call    004322D0                //GetText是单参数的,输入是个TControl,输出是TCaption,从edtName输入框中获取值,保存到EDX中
00453823   8B45F4                 mov     eax, [ebp-$0C]        //上面这个CALL调用,返回结果也就是edtName中的内容保存到[ebp-$0C]中了,现在再把这个值赋给EAX
00453826   8D55F8                 lea     edx, [ebp-$08]        //同上,意思是返回值放在[ebp-$08]中
 
* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
00453829   E8FE46FBFF             call    00407F2C                //Trim是单参数的,输入ANSIString, 输出也是AnsiString。把EAX的字符串做TRIM操作,返回值放到EDX(也就是[ebp-$08])中
0045382E   837DF800               cmp     dword ptr [ebp-$08], +$00        //比较[ebp-$08]中是不是空
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, [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, [ebx+$02FC]
00453852   8B10                   mov     edx, [eax]
00453854   FF92C4000000           call    dword ptr [edx+$00C4]        //猜测,这里应该是SetFocus的意思
0045385A   E9C5000000             jmp     00453924
0045385F   8D55EC                 lea     edx, [ebp-$14]        //这里开始处理edtCode了,和上面的分析一样,我就不再写了。
 
* Reference to control edtCode : TEdit
|
00453862   8B83F8020000           mov     eax, [ebx+$02F8]
 
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
00453868   E863EAFDFF             call    004322D0
0045386D   8B45EC                 mov     eax, [ebp-$14]
00453870   8D55F0                 lea     edx, [ebp-$10]
 
* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
00453873   E8B446FBFF             call    00407F2C
00453878   837DF000               cmp     dword ptr [ebp-$10], +$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, [eax]
 
* Reference to: Forms.TApplication.MessageBox(TApplication;PChar;PChar;Longint):Integer;
|
00453891   E89AE8FFFF             call    00452130       
 
* Reference to control edtCode : TEdit
|
00453896   8B83F8020000           mov     eax, [ebx+$02F8]
0045389C   8B10                   mov     edx, [eax]
0045389E   FF92C4000000           call    dword ptr [edx+$00C4]
004538A4   EB7E                   jmp     00453924
004538A6   8D55E8                 lea     edx, [ebp-$18]                //返回值放到[ebp-$18]
 
* Reference to control edtName : TEdit
|
004538A9   8B83FC020000           mov     eax, [ebx+$02FC]                //还是对edtName输入框操作
 
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
004538AF   E81CEAFDFF             call    004322D0                        //取输入框内容
004538B4   8B45E8                 mov     eax, [ebp-$18]                //取出的内容放到eax中
004538B7   8D55FC                 lea     edx, [ebp-$04]                //[ebp-$04]放返回值
 
* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
004538BA   E86D46FBFF             call    00407F2C                        //做Trim操作。这里大家应该能知道了,Trim后的结果保存到[ebp-$04]中了
004538BF   8D55E4                 lea     edx, [ebp-$1C]                //[ebp-$1C]这里又放另一个返回值,哪个的返回值呢?
004538C2   8B45FC                 mov     eax, [ebp-$04]                //Trim后的结果进行操作
 
|
004538C5   E8B6FEFFFF             call    00453780                        //函数A。函数A去处理Trim(edtName),处理后的结果保存到[ebp-$1C]里
004538CA   8B45E4                 mov     eax, [ebp-$1C]                //结果放到EAX里
004538CD   50                     push    eax                                //压栈,保存起来
004538CE   8D55DC                 lea     edx, [ebp-$24]                //返回值放[ebp-$24]
 
* Reference to control edtCode : TEdit
|
004538D1   8B83F8020000           mov     eax, [ebx+$02F8]                //对edtCode输入框进行处理       
 
* Reference to: Controls.TControl.GetText(TControl):TCaption;
|
004538D7   E8F4E9FDFF             call    004322D0                        //取edtCode输入框的内容保存到[ebp-$24]中
004538DC   8B45DC                 mov     eax, [ebp-$24]                //……,上面都写那么多了,下面应该能读懂了
004538DF   8D55E0                 lea     edx, [ebp-$20]
 
* Reference to: SysUtils.Trim(AnsiString):AnsiString;overload;
|
004538E2   E84546FBFF             call    00407F2C                       
004538E7   8B55E0                 mov     edx, [ebp-$20]                //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, [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, [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:[eax], edx
 
****** FINALLY
|
 
* Possible String Reference to: '[嬪]?
|
0045392C   6878394500             push    $00453978
00453931   8D45DC                 lea     eax, [ebp-$24]
 
* Reference to: System.@LStrClr(void;void);
|
00453934   E8A708FBFF             call    004041E0
00453939   8D45E0                 lea     eax, [ebp-$20]
0045393C   BA02000000             mov     edx, $00000002
 
* Reference to: System.@LStrArrayClr(void;void;Integer);
|
00453941   E8BE08FBFF             call    00404204
00453946   8D45E8                 lea     eax, [ebp-$18]
00453949   BA02000000             mov     edx, $00000002
 
* Reference to: System.@LStrArrayClr(void;void;Integer);
|
0045394E   E8B108FBFF             call    00404204
00453953   8D45F0                 lea     eax, [ebp-$10]
 
* Reference to: System.@LStrClr(void;void);
|
00453956   E88508FBFF             call    004041E0
0045395B   8D45F4                 lea     eax, [ebp-$0C]
 
* Reference to: System.@LStrClr(void;void);
|
0045395E   E87D08FBFF             call    004041E0
00453963   8D45F8                 lea     eax, [ebp-$08]
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),处理后的结果保存到[ebp-$1C]里

这个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     [ebp-$04], eax        //传入的参数
0045378B   8B45FC                 mov     eax, [ebp-$04]
 
* 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:[eax]
0045379E   648920                 mov     fs:[eax], esp
004537A1   8B45FC                 mov     eax, [ebp-$04]
 
* 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, [ebp-$04]        //ECX=要处理的字符串
004537B7   0FB64C11FF             movzx   ecx, byte ptr [ecx+edx-$01]        //按顺序取字符串的一个字节
004537BC   03DB                   add     ebx, ebx                //EBX=EBX*2;
004537BE   8D1C9B                 lea     ebx, [ebx+ebx*4]        //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:[eax], edx
 
****** FINALLY
|
004537DA   68EF374500             push    $004537EF
004537DF   8D45FC                 lea     eax, [ebp-$04]
 
* 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,与程序是同一个地址段,说明这个是一个用户函数,对于这样的函数,在调试的时候有必要跟进去查看。

    关键字: delphi 程序反编译


鼓掌

3

正能量

0

2

呵呵

0


评论区