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