【文章标题】: Themida的另类破解
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
      大家都知道Themida的强大保护功能,几乎无人能完整的破解它的VM中的handler(最新进展如何?)。我也试图跟踪过它的几个handler,它为一个简单的入栈操作竟有长达数千行的垃圾代码和无穷的转跳,如果要把它168(?)个handler都清理出来,他一定会疯掉!或累死掉?我曾经下决心不碰Themida了,不知为什么鬼使神差的我又跟踪了几个Themida加密软件,竟然还有新的发现!
      附件提供的一个程序是用汇编写的PE信息小工具,用Themida1855加密,加密选择RISC-64 processor,全选加密可选项,并对部分API函数使用VM加密,入口虚拟系数选择15。应该说对该程序用了较强的加密措施。现在介绍用我的脚本,一步步对它破解(绝不暗中使用对原程序已知的信息),居然还原了所有的代码,请看下面详细过程。
  
      一、介绍一个万能脚本
      1.下面的脚本适用于任何用Themida 1.8.5.5版加密的程序,操作十分简单。
  --------------------------------------------------------------
  //本脚本适用于Thmida 1.8.5.5版本加密的任何程序
  
    bphwc
    bc
    jmp Second     //第一次运行后,将本行注释掉后保存一次(本)脚本
  
  data:
    var mem
    var mem1
    var temIAT
    var temESI
    var temAPI
    var APIstr
    var Addr_0
    var Addr_1
    var Addr_2
    var Addr_3
    var Addr_4
    var Addr_5
    var Rva
  Init:
    mov Rva,65b970       //用对话框中的值修改该值,并将第一行的jmp Second注释掉
    mov Addr_0,Rva-997
    mov Addr_1,Rva-809
    mov Addr_2,Rva-ea
    mov Addr_3,Rva-9d
    mov Addr_4,Rva
    mov Addr_5,Rva+74c
    
  start:  
    esto
    esto
    bphws Addr_0,"x" 
    esto  
    bphwc Addr_0
    mov [Addr_0],0062e990  //废除监视断点的代码
    
    bp Addr_1     
    bp Addr_2    
    bp Addr_3    
    bp Addr_4    
    bp Addr_5    
  
  First:
    run
    cmp eip,Addr_1
    jz  A1
    cmp eip,Addr_2        
    jz  A2
    cmp eip,Addr_3
    jz  A3
    cmp eip,Addr_4
    jz  A4
    cmp eip,Addr_5
    jz  A5
    jmp  First
  
  A1:    
    mov temAPI,eax     //eax 是API函数地址
    gn temAPI         //显示函数名
    log $RESULT 
    add APIstr,"  "
    add APIstr,$RESULT_2  //恢复API函数名(INT表)
    jmp First
  A2:    
    mov temIAT,eax
    cmp  edx,10000
    ja  next
    xor  edx,80000000
    mov [eax],edx        //修复IAT表(写入序列号)
    jmp First
  next: 
    mov [eax],temAPI
    jmp First
  
  A3:
    mov mem1,eax          //eax是内存中呼叫API地址
    mov temESI,[esi]      //获取转跳标记
    jmp First
  
  A4:
    cmp temESI,AAAAAAAA
    jnz step3
    mov [mem1],#FF25#      //修复代码中转跳地址
    mov [mem1+2],temIAT   
    jmp First
  
  step3:
    mov [mem1],#FF15#      //修复代码中呼叫地址
    mov [mem1+2],temIAT
    jmp First
  
  A5:
    bc     
    pause
  
  Second:                //查找写IAT地址的一条指令
    var memory
    var tmp
    var temp
    var tmpbp
    var Str
  
    gmi eip,CODEBASE
          mov  memory,$RESULT  
    find memory,#0000000000000000000000000000000000000000000000#
    mov tmp,$RESULT
    bphws tmp,"w"
          esto
    esto
    esto
    bphwc tmp
    sto
          find memory,#909090909090909090909090909090909090#
    mov temp,$RESULT+2
    bphws temp,"w"
    mov  tmpbp,eip
    run
    cmp  eip,tmpbp+2
          jnz  next2
          run
    next2:
    itoa eip
    mov  Str,"请用下列数值:"
          add  Str,$RESULT 
    add  Str," 修改脚本中的Rva."
    bphwc
       msg Str
    pause                       //(脚本完)
  
      本脚本的使用方法是:用OD打开程序后,如果第一次加载本脚本,则会弹出一个对话框,用对话框中的数据修改脚本中的Rva值(第20行,且只有一处),并一定将第3行的jmp Second注释掉,一定将脚本保存一次!这时脚本就是该程序的专用脚本了。重新用OD加载程序后,可以无须任何更改,直接调用本脚本了!
      第2次运行脚本,屏幕快速闪动,一会就出现了神奇的效果了。如果想应用于新的程序,则只须将注释掉的jmp Second恢复即可。
  ----------------------------------------------------------------------------
  
      2.挖掉Themida的一只监视眼,蒙上它的另一只眼
      本脚本的运行原理,请参考我原先的一篇文章《对Themida加密VC++程序的完美脱壳》,当时我初次接触Themida,那个脚本有很多不完善的地方,如不容易进行到底,移植困难等。Themida有很多监视断点的SEH,当时没有采用相应措施,所以被Themida频频发现。它的“监视”方法很多,用得最多的大约有(1)int 1中断,(2)内存访问异常中断,(3)代码扫描等。对于(1)(2)没有任何办法摘除,但脚本的一个循环可与它周旋;对于(3)在我跟踪的代码段中,用脚本将它摘除了。所以脚本运行得非常顺利(使用在其它被Themida加密的程序中也一样)。
      (1)脚本中的 mov [Addr_0],0062e990 就是将该地址的“jb”改成了“jmp”,跳过了代码扫描监视。---相当于挖掉了它的一只眼睛---
      (2)Thmida的另一(多)只眼睛实在难于发现,不管你是什么类型的断点,很快被发现并单步异常后弹出对话框,然后退出运行。请注意,Themida设置的是单步中断,如果让它自行处理这个中断,就是前面的结果。如果将它拦截下来,自已处理就可以绕过了它的SEH。“拦截”方法就是打开OD“选项--调试设置--异常页”只选“非法访问内存”一项,其余全部不能选取。这样除非法访问内存异常让它自行处理外,其余全部交由OD处理。这样当Themida发现断点后产生的单步异常就被OD拦截了。自行处理的方式就是脚本中“First:”标签部分。(用脚本处理OD拦截下来的中断是非常容易的)
       脚本中的First部分(断点循环)就是判断中断地址是不是自己设置的,是则进入相应操作;若不是,则一定是Thmida发现断点后的SEH中断地址(单步中断),处理方式就是用“run“。OD的这个run,就是让程序在eip中断地址上继续运行,绕过了Themida的SEH处理程序(陷阱)。---相当于蒙上了它的另一只眼睛----    
       脚本中的其余部分无须多说,请参考我的前一篇文章。
  
      3.运行脚本后的神奇效果
      用OD打开程序后,加载这个脚本,程序运行得很快。当它暂停后Themida外壳已经被除掉。转到代码段401000,让OD分析代码一次。如果Themida在code段中没有虚拟API函数,则显示在你面前的是一个非常清晰的可读的反汇编程序,各个API函数调用和名称显露无遗。如果你仅仅是想了解原程序的基本结构和思路,这就够了。如果Themida虚拟了部分API,请往下看。如果你一定要dump出来,并去掉垃圾代码,也请往下看。
  
      二、恢复程序的入口代码
      如果想进入它的VM(虚拟机)部分,清理它的handler,还原出原代码,我是绝对无能为力了,下决心不碰它了。
      那么,我是怎样恢复入口代码的呢?跟踪Themida发现,它对解压出来的代码段并不重视,居然没有设置断点监视?你可以任意设断和修改,我巧妙地利用了它这一弱点。
  
      1.设断技巧(以提供的PeInfo_them.exe为例)
      用脚本脱壳后,转到401000代码区,让OD对代码分析一次,来到程序的尾部,API函数都集中在这里(称它为API表)(VC++程序的函数表分散在代码中,相对集中)。
      
  004015FC   $- FF25 5820400>jmp     dword ptr [402058]  ;  USER32.wsprintfA
  00401602   .- FF25 5420400>jmp     dword ptr [402054]  ;  USER32.DialogBoxParamA          <---F2设断
  00401608   $- FF25 5020400>jmp     dword ptr [402050]  ;  USER32.EndDialog
  0040160E   $- FF25 4C20400>jmp     dword ptr [40204C]  ;  USER32.GetDlgItem
  00401614   .- FF25 4820400>jmp     dword ptr [402048]  ;  USER32.GetWindowTextLengthA     <---F2设断
  0040161A   $- FF25 4420400>jmp     dword ptr [402044]  ;  USER32.LoadIconA
  00401620   .- FF25 4020400>jmp     dword ptr [402040]  ;  USER32.MessageBoxA              <---F2设断
  00401626   .- FF25 3C20400>jmp     dword ptr [40203C]  ;  USER32.SendMessageA             <---F2设断
  0040162C   $- FF25 3820400>jmp     dword ptr [402038]  ;  USER32.SetWindowTextA
  00401632   $- FF25 2420400>jmp     dword ptr [402024]  ;  kernel32.CloseHandle
  00401638   $- FF25 2820400>jmp     dword ptr [402028]  ;  kernel32.CreateFileA
  0040163E   $- FF25 2C20400>jmp     dword ptr [40202C]  ;  kernel32.CreateFileMappingA
  00401644   .- FF25 3020400>jmp     dword ptr [402030]  ;  kernel32.ExitProcess            <---F2设断
  0040164A   .- FF25 2020400>jmp     dword ptr [402020]  ;  kernel32.FreeLibrary            <---F2设断
  00401650   $- FF25 1C20400>jmp     dword ptr [40201C]  ;  kernel32.GetFileSize
  00401656   .- FF25 1820400>jmp     dword ptr [402018]  ;  kernel32.GetModuleHandleA       <---F2设断
  0040165C   .- FF25 1420400>jmp     dword ptr [402014]  ;  kernel32.LoadLibraryA           <---F2设断
  00401662   $- FF25 1020400>jmp     dword ptr [402010]  ;  kernel32.MapViewOfFile
  00401668   $- FF25 0C20400>jmp     dword ptr [40200C]  ;  kernel32.UnmapViewOfFile
  0040166E   $- FF25 0820400>jmp     dword ptr [402008]  ;  kernel32.lstrcpyA
  00401674   $- FF25 0020400>jmp     dword ptr [402000]  ;  comdlg32.GetOpenFileNameA
  
      (1)注意到API表中有“$”的地方(如果全都没有,则再分析一次),$表示程序中有call在呼叫,若没有则表示没有关联(VC++编写的程序有很多无关联的垃圾函数)。在这里没有关联的函数肯定是被Themida虚拟了call代码。在没有“$”的地址上设F2中断。
       明眼一看,本例是一个对话框窗口,它只有DialogBoxParamA,没有CreateWindowEx、消息循环等函数。若是Window窗口,则不能在消息循环函数上设断,否则OD将进入假死状态。  
      (2)在解压后代码段中浏览,发现了一段连续的乱码,用OD分析也无用,它一般就是入口代码段了。本例是4015B6--4015FB这一段(被虚拟了)。
      (3)从401000开始寻找跳进006xxxxx段的jmp,这些jmp都是跳到被VM虚拟后的API代码中(代码E9前面都有一个“-”号),本例中跳进VM中的8个jmp地址如下:
  
  0040100D   .- E9 E0532300  jmp     006363F2
  00401029   .- E9 408F2300  jmp     00639F6E
  0040103E   .- E9 95CC2300  jmp     0063DCD8
  00401371   .- E9 4A092400  jmp     00641CC0
  00401385   .- E9 F0422400  jmp     0064567A
  004013C9   .- E9 B2802400  jmp     00649480
  004013DD   .- E9 61B52400  jmp     0064C943
  0040151B   .- E9 39F32400  jmp     00650859
      在以上的每个地址都用F2设断。(放心设断,Themida是不来过问的。)
  
      2.操作技巧(以后的操作基本上都是F9或alt-F9,无须shift-F9)
      (1)当设置完成后,按F9运行一次,程序中断在
  0040165C   .- FF25 1420400>jmp     dword ptr [402014]  ;  kernel32.LoadLibraryA
      且在堆栈中出现
  0013FF90   00654697  /CALL 到 LoadLibraryA
  0013FF94   00402060  \FileName = "RichEd20.dll"
  0013FF98   004001C0  ASCII "   "                <--这是应用程序开始的栈顶,dump堆栈平衡时弹出至此
  0013FF9C   0013FFE0  指向下一个 SEH 记录的指针
  0013FFA0   005E0DB2  SE处理程序
  0013FFA4   7C930738  ntdll.7C930738
      堆栈中0013FF98是进入应用程序后的栈顶,13FF94的(00402060)是压入的参数,0013FF90的(00654697)是VM中的呼叫地址。
      记录下堆栈中的数据,并将4015B6开始的乱码改写为:(它就是解密出来的入口第一组代码)
      push  00402060
      call  0040165C  <---虚拟机中怎样操作不知道,而还原后的代码就是call LoadLibraryA,即call 0040165C。
  
      (2)继续F9一次,中断在
  00401656   .- FF25 1820400>jmp     dword ptr [402018]  ;  kernel32.GetModuleHandleA
  
      堆栈中出现
  0013FF90   006546A3    /CALL 到 GetModuleHandleA
  0013FF94   00000000    \pModule = NULL
      这里记下堆栈,接着前面如下修改乱码;
      push  0
      call  00401656   <---入口的第二组数据
  
      (3)继续一次F9,(记录每一次中断后堆栈中的数据)中断在
  00401602   .- FF25 5420400>jmp     dword ptr [402054]  ;  USER32.DialogBoxParamA
      堆栈是
  0013FF80   006546BB    /CALL 到 DialogBoxParamA
  0013FF84   00400000    |hInst = 00400000            <--GetModuleHandleA的返回值
  0013FF88   000003E8    |pTemplate = 3E8             <--资源ID
  0013FF8C   00000000    |hOwner = NULL
  0013FF90   00401544    |DlgProc = PeInfo_t.00401544 <--窗口程序地址
  0013FF94   00000000    \lParam = NULL
  
  
      因为程序进入了DialogBoxParamA后,不会退出,但初始化窗口时还会调用其它函数,只有关闭窗口后才退出。
      这里将前面3次中断后堆栈中的值,加上必须保存的句柄,将入口处的乱码改写如下:
      push  402060
      call  0040165C  ;LoadLibraryA
      mov   [xxxxx],eax                  <---返回RichEdit20A句柄
      push  0
      call  00401656  ;GetModuleHandleA
      mov   [xxxxx],eax                  <---返回GetModuleHandleA句柄
      push  0
      push  401544
      push  0
      push  3E8
      push  [xxxxx]                      <---它一定是GetModuleHandleA返回的句柄
      call  00401602  ;DialogBoxParamA
  
     打开403000全局变量区,每中断一次就会写入一些数据。你会发现LoadLibraryA句柄存入了[403004],GetModuleHandleA句柄存入了[403000],这样前面的三个[xxxxx]都解决了(需要一些API函数知识):即
  
      push  402060                        <---查402060是字串RichEd20.dll,原来是装载RichEd20库
      call  0040165C       ;call LoadLibraryA
      mov   [403004],eax                  <---返回RichEdit20A句柄
      push  0
      call  00401656       ;call GetModuleHandleA
      mov   [403000],eax                  <---返回GetModuleHandleA句柄
      push  0
      push  401544    <--DialogBoxParamA函数的Proc地址
      push  0
      push  3E8       <--窗口资源ID
      push  [403000]                      <---它是GetModuleHandleA返回的句柄
      call  00401602       ;call DialogBoxParamA
      这就是入口的开局部分。

     提问:如果程序中有如:mov eax,12345678; mov ecx,eax;mov [403040],ecx;add eax,ecx等代码,怎么办?
     应该说,每种语言编写的程序入口都有一种相对固定的格式,在窗口未建立之前一般不会有mov ecx,eax;add eax,ecx等代码,熟习了各种语言的程序入口代码对破解大有帮助。如果在脚本中设置了bpmw [40x000],ff([40x000]是数据区),则对找回如:mov [xxxxx],eax等代码有一定的帮助,但断点在VM中,要防止Themida的监视。退一步说,即使漏掉了一些这样的代码,dump后的程序不能运行,但对程序的结构和思路已经完全掌握了,目的已经达到了。
      
     (4)继续一次F9,中断在地址401371,(窗口初始化调用的函数)
  00401371   .- E9 4A092400  jmp     00641CC0
     这是code中被虚拟了的API函数,将该地址设置的中断F2关闭(因为程序可能反复调用它,以后每当在前面的8个jmp上中断一次,就关闭该断点),再F9一次,中断在API表中的
  00401626   .- FF25 3C20400>jmp     dword ptr [40203C]  ;  USER32.SendMessageA
     堆栈中是
  0013FC14   00641CD7    /CALL 到 SendMessageA
  0013FC18   004600B2    |hWnd = 4600B2          
  0013FC1C   00000080    |Message = WM_SETICON
  0013FC20   00000001    |wParam = 1
  0013FC24   0C1A027D    \lParam = C1A027D
     原来401371加密前应该是call 401625(即call SendMessageA),查看地址401371,参数非常清晰,你不必费力去研究它的参数来源,它是装载图标。
     注意,这个call SendMessageA不是接在开局的call DialogBoxParamA后面,因为程序还没有退出DialogBoxParamA。
     记下00401371地址,将要修改为call 401625(参数修复前不要修改,可能程序会反复调用它)。
     ……………………………………
     这样反复F9,反复记下中断地址和中断函数,(每中断一个jmp的地址,就关闭它的断点(API表中的F2不能关闭)。这样凡是与初始化有关的被虚拟的API都现身了!若发现再F9,老在API表中的SendMessageA(或其它函数)上中断,这是初始化中的调用,运行时总要进入系统领空,可按alt-F9让其返回(在VM中),再F9回到用户领空。窗口未出现前可以先关闭SendMessageA的F2断点,但窗口出现后一定要恢复其断点。
    
    (5)如果F9后,OD没有反应,但状态栏提示读取[FFFFFFFF]异常,不管它,这时,窗口已经出现了(看桌面状态条),程序进入了DialogBoxParamA中的消息循环。
     到此为止,代码段中还有5个断点没有被访问,它们是窗口运行后才要访问的地址。
  
    (6)按普通程序一样运行窗口,让它随便打开一个.exe文件,它立即中断在某jmp虚拟函数上,按前面同样的方法操作,在API函数表中就发现了它调用的API是谁了,这样就一个个地把Themida虚拟的函数清理完了。当代码段中的F2中断地址关闭完后。被VM虚拟的全部函数都现身了。(如何恢复参数,后面再说)
     
    (7)反复运行窗口发现在代码中设置的断点0040151B9(jmp 00650859)始终没有被调用,而API表中的MessageBoxA也没有被调用,可以肯定0040151B9(jmp 00650859)是虚拟了call MessageBoxA。原来它是一个出错对话框,当你打开一个非exe文件时,对话框就出现了。在堆栈中也可以找到它全部参数。
    
    (8)入口的后半部
    当清理完代码段中的所有jmp断点后(被虚拟的API),可以关闭窗口了,按下关闭钮,立即中断在
  0040164A   .- FF25 2020400>jmp     dword ptr [402020]  ;  kernel32.FreeLibrary
    堆栈是
  0013FF90   006546CA    /CALL 到 FreeLibrary
  0013FF94   74D90000    \hLibModule = 74D90000      <--LoadLibraryA的句柄
    即:接着call DialogBoxParamA后面,按下面代码修改
      push  [403004]
      call  40164A       (call FreeLibrary)
    再F9,中断在
  0013FF90   004015FC    (CALL 到 ExitProcess)
    再F9,就退出了。那么,完整的入口代码是:
      push  402060                  
      call  0040165C  
      mov   [403004],eax                  
      push  0
      call  00401656  
      mov   [403000],eax                  
      push  0
      push  00401544    
      push  0
      push  3E8      
      push  [403000]                      
      call  00401602  
      push  [403004]
      call  0040164A
      push  0
      call  004015FC
  
      三、参数的恢复
      API参数的设置,需要一些API知识,以本例为例简要说明,回到前面,401000这里有3个被虚拟的函数。前面的操作知道,先中断在一个jmp上,接着中断在API表中对应的API函数上。这样都清楚了它们是call xxxxxx。

  00401000   $  55            push    ebp
  00401001   .  8BEC          mov     ebp, esp
  00401003   .  83C4 F8       add     esp, -8
  00401006   .  60            pushad
  00401007   .  FF35 0C304000 push    dword ptr [40300C]
->0040100D   .- E9 E0532300   jmp     006363F2            <--应该是call GetWindowTextLengthA
  00401012   .  D3F4          sal     esp, cl
  00401014   .  0D B38ACE8D   or      eax, 8DCE8AB3
  00401019   .  45            inc     ebp
  0040101A   .  F8            clc
  0040101B   .  50            push    eax
  0040101C   .  6A 00         push    0
  0040101E   .  68 37040000   push    437
  00401023   .  FF35 0C304000 push    dword ptr [40300C]
->00401029   .- E9 408F2300   jmp     00639F6E              <--应该是call SendMessageA
  0040102E   .  0B37          or      esi, dword ptr [edi]
  00401030   .  B0 F7         mov     al, 0F7
  00401032   .  F5            cmc
  00401033   .  68 C2000000   push    0C2
  00401038   .  FF35 0C304000 push    dword ptr [40300C]
->0040103E   .- E9 95CC2300   jmp     0063DCD8              <--应该是call SendMessageA
  00401043   .  46            inc     esi
  00401044   .  6F            outs    dx, dword ptr es:[edi]
  00401045   .  C2 0400       retn    4
  
       (1)第一个call GetWindowTextLengthA,参数只有一个push [40300C],前面已知这是RichEdit20A的句柄,显然它是获取编辑软件RichEdit20A中的字符数,结果应该暂存,eax怎样操作?
       (2)第2个是call SendMessageA,消息发往RichEdit20A,堆栈中的表示是:
  0013F798   00639F81  /CALL 到 SendMessageA
  0013F79C   00450292  |hWnd = 450292
  0013F7A0   00000437  |Message = MSG(437)
  0013F7A4   00000000  |wParam = 0
  0013F7A8   0013F7CC  \lParam = 13F7CC  <--移动光标或选择范围的操作是该参数指向一个CHARRANGE结构(8字节)

       这下难了,必须弄清RichEdit20A是干什么的?原来它是一个大型文本编辑软件,操作十分复杂,这里是大才小用,只用它来显示数据,并不对数据编辑。对RichEdit20A的操作主要是靠SendMessageA发送消息。它操作前,一定要确定光标位,确定光标位消息中的lParam参数指向一个CHARRANGE结构(2个dword),这两个值相等,其值是光标位置;若不等则表示选择一个区间。
       清楚了,GetWindowTextLengthA获取屏幕上字符长度,第1个SendMessageA应该将光标定于尾部,好让第2个SendMessageA向它添加字符。那么第1个SendMessageA的参数13F7CC就一定是指向CHARRANGE结构,从ebp的值知13F7CC是指向[ebp-8],[ebp-4]即一个CHARRANGE结构,两个dword值应该相等且就是当前的字串长度。
       这样,显然应该有:mov [ebp-8],eax;mov [ebp-4],eax。所以,代码应该如下:
       call GetWindowTextLengthA
       mov [ebp-8],eax
       mov [ebp-4],eax
       lea eax,[ebp-8]
       push  eax         <---取CHARRANGE结构地址
       push  0
       push  437
       push [40300C]
       call SendMessageA

       第2个SendMessageA一定是将buffer中的字串添加上去。它的参数lParam应该指向一个buffer。堆栈中参数是 
  0013F798   0063DCE9  /CALL 到 SendMessageA
  0013F79C   00E20202  |hWnd = 450292
  0013F7A0   000000C2  |Message = EM_REPLACESEL    <-- EM_REPLACESEL=C2
  0013F7A4   00000000  |wParam = 0
  0013F7A8   0040222F  \Text = "--------------------------------------
      MSG消息EM_REPLACESEL也表示向RichEdit20A添加字符,参数lParam指向40222F,是直接push 40222F还是由调用程序带来?查调用401000的呼叫,知道它有多个地方调用,它的参数一定得由调用程序带来。
      这样,接着的代码是
      push  [ebp+8]    <--唯一的带来参数 
      push  0
      push  0C2        <-- 0C2=EM_REPLACESEL
      push  [40300C]
      call  SendMessageA
  
      (4)SendMessageA没有返回值,最后还有乱码46 6f的原码是什么?观察程序,前面有pushad,后面一定要有popad,又汇编程序是堆栈自动平衡方式,最后一般都有leave,最后这个C2 0400 (retn 4)是否是原码呢?应该说是,Themida一般不去惹call ebx、jmp eax、retn等代码,因为它的转跳是不定的,themida操作起来很复杂。
      至此,这段代码被还原了。有的地方说法可能很勉强(因为有原码参考,所以我说得很肯定),用在其它地方可能就不正确了。不管怎么说,分析起来虽然很困难,但肯定比从VM虚拟代码中还原要容易得多了。
      本方法有个致命的弱点是,一些与被虚拟的函数无关的代码,如add eax,6,mov ecx,edi等是永远找不回来的。好在是已经编译过的.exe文件再用Themida加密,如果不提供原编译文件,则只能对API虚拟了,包含它附近的几个代码。有关API的参数代码相对简单一些,很少有前面说到的那些代码形式,退后一步说,即使有,且被漏掉了,但所有的API都被还原了,关系清楚了,虽然dump后的程序不能运行,但破解的欲望达到了! 
  
      四、可能出现的问题
      1.如果你用OD打开文件后加载脚本,OD象没有反应一样直接就运行到了窗口出现。
        结论:你可能是第1次将脚本用到其它程序上而又忘记了将脚本中的jmp Second恢复过来,或者你忘记了存盘。  
      2.用脚本第1次弹出对话框没问题,第2次按要求修改也存了盘,但就是不出现任何结果。
        结论:Themida版本可能不是1.8.5.5版。或者“调试设置--异常页”的选项选得太多,特别是选择了“忽略特权指令或无效指令”。
      3.脚本不能运行到底,Themida弹出警告框后退出。
        结论:“调试设置--异常页”的选项选得太多,特别是选择了“忽略单步异常”。
  
        我说话很索,谢谢你耐心读完。 
  
 --------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2008年09月05日 10:41:32

上传的附件 temp.rar [附件请到论坛下载:http://bbs.pediy.com/showthread.php?t=72152 ]