• 标 题:贴上一个safedisc2的东西,方法奇搓,希望其他兄弟能给出更好的搞定方法。我先谢谢了 (12千字)
  • 作 者:买草帽
  • 时 间:2001-2-9 9:20:42
  • 链 接:http://bbs.pediy.com

红色警报2(red Alert 2)繁体版脱壳解密说明
1概述 :
      红色警报2是WestWood的新版游戏,它在众多游戏中率先采用了c-dilla公司的光盘保护技术safedic
2来对其起正版保护。要让游戏在虚拟光驱的环境下能够运行,首先必须模拟或者跳过对光盘保护特征区域
的调用。根据实际的情况,在red alert 2中并没有把游戏的数据写入保护区域。这样就为我们通过脱去游
戏的光盘识别部分的判别程序。重构一个不具备判断保护的程序提供了可能。以下的做法就是在以脱壳的基
础上实现其在虚拟光驱下执行游戏的一种实现方法的操作说明。

--使用工具
  - TRW V1.23 (safdisc2的保护部分对softice的防护很周到,就是加补丁也搞死。好在还有TRW)
  - 天意II0.46测试版(也是一个很新的动态反编译工具,就是现在不太稳定。支持的命令也不太全)
  - ProcDump 1.6.2  FINAL  VERSION (脱壳工具,用它可以方便的重内存中dump出映像)
  - W32Dasm Version 8.93 (比较直观的静态反汇编工具)
  - UltrEdit32 (或者其他的2进制编辑器用来修改程序)
  - Vitual Driver 2000 v 6.0 虚拟光驱软件(farstone)   
-----具体操作
---脱壳
  首先用TRW 1.23 装入该软件的可执行档ra2.exe.程序载入后 ,首先停留在。stext371这个section中代码
  部分如下:
016F:0041C1FD  55                  PUSH    EBP//装入停留在这里
016F:0041C1FE  8BEC                MOV    EBP,ESP
016F:0041C200  60                  PUSHAD
016F:0041C201  B87BC24100          MOV    EAX,0041C27B
016F:0041C206  2DFDC14100          SUB    EAX,0041C1FD
016F:0041C20B  03057CC24100        ADD    EAX,[0041C27C]
016F:0041C211  C705FDC14100E9000000 MOV    DWORD PTR [0041C1FD],E9
016F:0041C21B  A3FEC14100          MOV    DOWRD PTR [0041C1FE],EAX
016F:0041C220  68C9C04100          PUSH    0041C0C9
016F:0041C225  68BBC04100          PUSH    0041C0BB
016F:0041C22A  6809C04100          PUSH    0041C009
016F:0041C22F  689BC04100          PUSH    0041C09B
016F:0041C234  A021C04100          MOV    AL,[0041C021]
016F:0041C239  3C01                CMP    AL,01
016F:0041C23B  7407                JE      0041C244
016F:0041C23D  B800000000          MOV    EAX,00
016F:0041C242  EB03                JMP    0041C247
016F:0041C244  8B4508              MOV    EAX,[EBP+08]
016F:0041C247  50                  PUSH    EAX
016F:0041C248  E833000000          CALL    0041C280//这里就是safedisc2解密代码的入口,按F10跳过
016F:0041C24D  83C414              ADD    ESP,14
016F:0041C250  83F800              CMP    EAX,00
016F:0041C253  741C                JE      0041C271
当程序过了41c248这个调用后就会弹出一个开始图象,在这个call 41c280中就是safdisc2的关键部分,但现
在不用去研究它。当按F10走过这里时,程序再单步执行几步会跳出.stext371这个section,来到如下的部分
这时看看section的情况,已经是在.text的部分了。这就是说程序已经到了无壳时候的部分。该是游戏程序
的真面目了。
016F:0040787F  55                  PUSH    EBP//停在这里
016F:00407880  8BEC                MOV    EBP,ESP
016F:00407882  6AFF                PUSH    FF
016F:00407884  6878234100          PUSH    00412378
016F:00407889  68E4C54000          PUSH    0040C5E4
016F:0040788E  64A100000000        MOV    EAX,BYTE PTR FS:[00]
016F:00407894  50                  PUSH    EAX
016F:00407895  64892500000000      MOV    FS:[00],ESP
016F:0040789C  83EC58              SUB    ESP,58
016F:0040789F  53                  PUSH    EBX
016F:004078A0  56                  PUSH    ESI
016F:004078A1  57                  PUSH    EDI
016F:004078A2  8965E8              MOV    [EBP+E8],ESP
016F:004078A5  FF15DC104100        CALL    [004110DC]
016F:004078AB  33D2                XOR    EDX,EDX
016F:004078AD  8AD4                MOV    DL,AH
016F:004078AF  891518724100        MOV    [00417218],EDX
016F:004078B5  8BC8                MOV    ECX,EAX
016F:004078B7  81E1FF000000        ADN    ECX,FF
016F:004078BD  890D14724100        MOV    [00417214],ECX
016F:004078C3  C1E108              SHL    ECX,08
016F:004078C6  03CA                ADD    ECX,EDX
016F:004078C8  890D10724100        MOV    [00417210],ECX
当程序在40787f处停下时在TWR1.22中下命令suspend,挂起程序,回到WINDOWS界面,现在我们
要脱掉程序上面的外壳了。在windows下调出ProcDump 1.6.2  FINAL  VERSION把内存中的影象dump
出来,打开procdump后再task对话框中可以找到一个ra2.exe的影象,用鼠标选中它,再点鼠标右键
,在弹出框中选择dump(full)项,然后选一个你指定的文件名保存,我用abd.exe保存,这样就把程
序的主体部分给分离出来了。该是没safedic2调用了。 

-----IMPORT_TABLE导入表的重构 
------第一种情况(你要做 CALL [********]= 转换=> Call Kernel32.dll(或user32.dll)!具体函数)
  然后执行一下该档碰碰运气,会不会就OK了。可惜程序可没这样简单,马上给报非法错误。。就在4078a5
处的 call[004110dc]非法错误,这个可是大麻烦处。以下的处理全都是为了解决这个问题而采取的。首先
只有回到原来的程序重新调试到4078a5,然后跟进去看看,马上就是调用一个外接的文件了,空间已经到了另
一个程序应该是一个dll文件。看看它都做了些啥,下内存断点bpm esp RW 执行几下后发现程序最后在
kernel32.dll的空间中停下,想想该是有API调用了。继续发现是对函数Kernel32.dll!GetVersion()的调用,
这只有一个可能加壳程序把原程序的导入表(IMPORT_TABLE)中的Kernel32函数破坏了。它通过外接的dll用函
数GetProcAddress来对每个原来的函数导入进行实现,而且把函数的执行也在其中代劳了。
  那我就先在程序中找找导入表的位置把,用procdump打开,点中PE Editor按钮发现IMPORT_TABLE的RVA是
1f000 size 1b8,找到1f000的位置发现导入表的位置全不对!看来程序是把它给隐藏了。而且用W32Dasm反汇
编也找不到。只有手工找找,根据PE文件的结构,我先在abd.exe中查找字符串"kernel32.dll",在131a4这个
RVA找到一个,然后再在程序中查找二进制a4 31 01也就是看IMPORT_TABLE的IMAGE_IMPORT_DESCRIPTOR结构里
有Kernel32.dll这个函数吗,还好,在12ce4处找到。 在我可以先假定IMPROT_TABLE的RVA为12cd8(根据结构
IMAGE_IMPORT_DESCRIPTOR找到其偏移首址),先改改看。
  用procdump打开abd.exe,点中PE Editor按钮,把IMPROT_TABLE的RVA改为12cd8。然后再用W32Dasm 反汇编
看见FUCTION 按钮的IMPORT已经出来了,可以看见kernel32.dll,user32.dll,GDI32.dll,ADVAPI32.dll,
Shell32.dll,COMCTL32.dll已经出来了,但可以看见,kernel32.dll,和user32.dll的函数全是空白。这些函
数看来要我去给找出来了。其他的dll调用都正常了。
  下面就是找到程序的加密部分进行分析。把程序走过的API进行重构。还是针对第一个出错的地方,在跟进
的dll空间可以看见以下代码

016F:004078A5  FF15DC104100        CALL    [004110DC] 切入下面的代码中
016F:004078AB  33D2                XOR    EDX,EDX
/*********************************************************************************************/
注:程序在下面用了间接Ret 指令的方式代替call调用返回的方式,把调用下一条指令的IP(004078AB)及
    标志和所有的寄存器保存在堆栈单元中。然后再用改变堆栈的方法切回4078AB,具体实现见下面代码
    010c6CD7  PUSH DWORD BFEA13B4
    010c6CDC  PUSHF//保存标志
    010c6CdD  PUSHA//保存所有的寄存器
    010c6CDE  PUSH ESP       
    010c6CdF  PUSH DOWRD 10C6D167
    010c6CE4  CALL 100195F0  //进入~df394b.tmp,其实是个dll文件,F8跟进可发现
    010c6CE9  ADD ESP BYTE +8//不会到这条指令的,再上一条指令用F10的话就过了!!!!game begin
    010c6CEC  PUSH  BYTE 00
    010c6CEE  POP EAX
    010c6CEF  POPA
    010c6CF0  POPF
    010c6CF1  RET       

走走....到下面........找了好久 在~df394b.tmp中下面的指令中有花指令,静态汇编不可看的到的。

    0100183c8  SETS [EBP-8]
    0100183cc  MOV EAX,[EBP-8]
    0100183cf  AND EAX,0x000000FF
    0100183d4  TEST EAX,EAX
    0100183d6  JNZ  0100183e5 //这里通常是要跳的
    0100183d8  MOV ECX,[0100500E8]
    0100183df  CALL  Kernel32.dll!SetEvent
    0100183e5  JMP  SHORT  0100183ee//再1跳
    0100183e7  MOV EBX,EBX
    0100183e9  JO  0100183F1
    0100183eb  NOP
      0100183ec  JNO  SHORT 0100183f1//再N跳,花指令一直就在跳,这是最后一跳。。
    0100183ee  JMP 0100183e7//再2跳
    ...............................花指令
    0100183f1  MOV ESP,[EBP+0c]//跳到这里水落石出,这里EBP+0c中的就是IP(004078AB)那里压入
                                //的环境数据
    0100183f4  POPA//修改标志为4078a5的的状态
    0100183f5  POPF//修改寄存器4078a5时的状态
    0100183f6  RET //切入Kernel32.dll中的对应函数中,在这条指令下一条时,堆栈已经指向IP 4078AB
                    //当程序重Kernel32模块的对应函数返回时,就返回到4078AB回到主程序执行。
/*********************************************************************************************/
说了半天,结果其实这里就是一个API调用,完全可以通过把函数在004078A5  CALL    [004110DC]处的内容
改为具体的API就可以了,这需要对PE文件的IMPORT_TABLE 的结构了解。你可以发现其实其他的call调用的区
域跟这个一样,都是在IMAGE_IMPORT_DESCRIPTOR结构的 Kernel32.dll或user32.dll的FirstThunk指象的区域
你可以发现现在这里的全是通过上面的外接调用实现的,我们要改它其中的内容为内部直接调用,这就要用手
工了,很麻烦。。首先在程序的.rdata section的末端选择全是00的区域把该函数的hint和名字字符串写入,
可能你还需要手工在procdump修改.rdata section的Vsize 和Psize为能放下你的所有函数hint和名字串的大
小我的这个函数导入表的构造方法如下
第一个断点在4078a5中,其真正的CALL dword ptr [4110dc] ,110dc地址的指针改为1394d,在1394d中有第一
个函数GetVersion的hint及标示字符(这里是自己加的哟)
  RVA 110dc------value------>1394d =>  RVA 1394d----------value------->0800GetVersion
                                                                          ^    ^
                                                                      hint Characteris
                                                                     
这样就构造好了第一个函数Kernel32.dll!GetVersion()
后面的这种调用如法泡制,可能要重构30几个这样的函数。但可能还是有规律的,要是能找到规律或完整的
导入表,就不用这样辛苦了。








------第二钟情况(你要做JMP.stxt774的入口地址==转换=>Call Call Kernel32.dll(或user32.dll)!具体函数)
    第二种情况通过远跳指令跳到.stxt774的相应入口中。
第一条花指令切入是函数GetVersionExA,位置在
409450  jmp 41b12a(跳到。stxt段)  ;这里实际上是  call Kernel32.dll!GetVersionExA,原程序的加
                            ;密处理可以见后继code程序下面的是花指令,静态反汇编会看不见真正代码

它在.stxt774 section中的代码如下

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00409450(U)
|
:0041B12A 53                      push ebx
:0041B12B E800000000              call 0041B130 //这个call的目的只是把IP压栈保存

* Referenced by a CALL at Address:
|:0041B12B 
|
:0041B130 870424                  xchg dword ptr [esp], eax//
:0041B133 9C                      pushfd  //标志保存
:0041B134 05D0FEFFFF              add eax, FFFFFED0
:0041B139 8B18                    mov ebx, dword ptr [eax]
:0041B13B 6BDB0A                  imul ebx, 0000000A //计算切入dll的位置
:0041B13E 035804                  add ebx, dword ptr [eax+04]
:0041B141 9D                      popfd //恢复状态
:0041B142 58                      pop eax
:0041B143 871C24                  xchg dword ptr [esp], ebx//切入~df394b.tmp的位置
:0041B146 C3                      ret//切入

 
 
  然后可以看见下面的类似代码中

    010c6CD7  PUSH ***********//409450的下一条指令,可是花指令哟,这里处理很毒的,不看这里的
                                //话你是不能看见409450的真正的下条指令的情况的。
    010c6CDC  PUSHF//保存标志
    010c6CdD  PUSHA//保存所有的寄存器
    010c6CDE  PUSH ESP       
    010c6CdF  PUSH DOWRD 10C6D167
    010c6CE4  CALL 100195F0  //进入~df394b.tmp,其实是个dll文件,F8跟进可发现
    010c6CE9  ADD ESP BYTE +8//不会到这条指令的,再上一条指令用F10的话就过了!!!!game begin
    010c6CEC  PUSH  BYTE 00
    010c6CEE  POP EAX
    010c6CEF  POPA
    010c6CF0  POPF
    010c6CF1  RET






这种情况的修改方法,在程序的相对偏移地址13bd9中加入 3300GetVersionExA
                                                    ^    ^
                                                    hint Characteris
然后在IMPORT_TABLE区域的空余空间相对偏移地址11184中写入Hint和Characteris的地址首址13bd9即
Address 411184------value------>13bd9 =>  Address 13bd9----------value------->3300GetVersionExA
                                                                                ^    ^
                                                                                hint Characteris
然后再动态执行原程序下断点bpx 409450,用a指令动态修改这里的汇编代码
a
*****:409450  call near [411184]
*****:409456  ***********        ;这样原来的指令就还原了,可以看见真正的指令
记录下程序在该处的16进至数据为 FF1584114100,然后用编辑器在程序中的该位置修改为这几个数据,完成
对GetVersionExA的重构 。。。。 


  用上面的方法一个一个跟踪就可以得到一个完全无壳的可执行文件, 最后在一切搞定后执行的时候一定要把
原程序的执行目录里的ra2.lcf复制为abd.lcf这样才行。也可以让程序执行了,但过程真的好麻烦要是能够生成
这些东西就好了。在制作虚拟光碟时用farstone 的制作虚拟光碟制作vcd档选定智慧算法读取,不要用穷读法,否则过不了safe
disc2的不可读保护,制作完毕后装入就可以不用光盘第顽red  alert 2游戏了。