标题:Unpack SecuROM 4.x-5.x with FlyODBG v1.10
目标:秋之回忆:想君简体中文版
声明:纯粹为了速度和节省光驱
作者:无聊的菜鸟
时间:2005年11月20日 01:06
调试环境:WinXP、flyODBG、PEiD、LordPE、ImportREC、WinHex
SecuROM是Sony的产品,介绍就不用了吧,知道它是光盘保护系统就行了。
首先我们需要OEP,这个大家都知道的吧。
0186F5B2 OMOKIMI.<Modu> 55 push ebp <-OD停在了这里
0186F5B3 8BEC mov ebp,esp
0186F5B5 6A FF push -1
0186F5B7 68 B8599401 push OMOKIMI.019459B8
0186F5BC 68 90FA8601 push OMOKIMI.0186FA90
0186F5C1 64:A1 00000000 mov eax,dword ptr fs:[0]
0186F5C7 50 push eax
0186F5C8 64:8925 00000000 mov dword ptr fs:[0],esp
0186F5CF 83EC 58 sub esp,58
0186F5D2 53 push ebx
0186F5D3 56 push esi
0186F5D4 57 push edi
0186F5D5 8965 E8 mov dword ptr ss:[ebp-18],esp
0186F5D8 FF15 10569601 call dword ptr ds:[<&kernel32.GetVersion>] ; kernel32.GetVersion
0186F5DE 33D2 xor edx,edx
我们先来确认一件事情,游戏呢一般都使用VC++6.0写的,而VC++编译出来的程序都好一个把第一个调用的API写成GetVersion,所以呢,如果Sony没有像ASPr一样模拟API的入口的话,我们对这个API下断就一定能到很接近OEP的地方。
现在我们设置OD忽略一切异常(重要!),把FlyODBG的优先级调成低(重要!),He GetVersion,然后运行。
注意堆栈:
第一次:0012FF48 0186F5DE 返回到 OMOKIMI.0186F5DE 来自 kernel32.GetVersion
第二次:0012EF40 100068E8 返回到 CmdLineE.100068E8 来自 kernel32.GetVersion
第三次:0012F688 01816F9C 返回到 OMOKIMI.01816F9C 来自 kernel32.GetVersion
第四次:0012EF50 20002759 返回到 20002759 来自 kernel32.GetVersion
第五次:0012D4C4 01826A59 返回到 OMOKIMI.01826A59 来自 kernel32.GetVersion
第六次:0012F63C 004369E8 返回到 OMOKIMI.004369E8 来自 021B0000 <-这次就是了,因为返回值在第二个段中。
回去看看:
004369BC 55 push ebp
004369BD 8BEC mov ebp,esp
004369BF 6A FF push -1
004369C1 68 D8E34300 push OMOKIMI.0043E3D8
004369C6 68 98AB4300 push OMOKIMI.0043AB98
004369CB 64:A1 00000000 mov eax,dword ptr fs:[0]
004369D1 50 push eax
004369D2 64:8925 00000000 mov dword ptr fs:[0],esp
004369D9 83EC 58 sub esp,58
004369DC 53 push ebx
004369DD 56 push esi
004369DE 57 push edi
004369DF 8965 E8 mov dword ptr ss:[ebp-18],esp
004369E2 FF15 084C8801 call dword ptr ds:[1884C08]
004369E8 33D2 xor edx,edx <-返回这里
与上面对比一下,不用说了吧,传说中的OEP。我们先dump一份下来留着(dump.exe)。
不过我们注意一下返回的地方的前一行,似乎不是call dword ptr ds:[<&kernel32.GetVersion>] ; kernel32.GetVersion
这就是SecuROM的保护了,间接API的调用。
下面我们将为了得到一个能够正确执行的文件而奋斗:
我们先来看看SecuROM都有些什么方法来保护。
1. 首先SecuROM没有加密API,只加密了它的调用,比SD好多了。。。。
2. 修改原始的Call [],使它指向壳中的位置,调用的时候经过一番运算得到应该用的API(比如GetVersion)的正确的存储位置([43E144])后跳去API那里执行。
3. 加密原始的Call [],使它变成Call XXXXXXXX的形式,调用的时候同上。虽然ASPr也喜欢干这种事情,但是Sony做得比较省事一点,在Call的前后加一些无伤大雅的单字节代码(这次我看见了0x27(daa),0x40(inc eax),0x90(nop),0xF5(cmc),0xF8(clc),0xF9(stc))
4. 跳到API去执行的方式也不多。
在这里先说一下SecuROM的修复中最难的是避开他的时间检测,程序运行的时候,API调用得太快或是太慢,它都会丢个不正确的直让你的PatchCode直接异常去。。。
所以我在这里用另一种方法,先修复没有时间校检的调用,然后再Patch有时间校检的地方,让它一边运行一边修复。。。这样的话我们得到的文件就一定能运行。至于剩下多少代码要修复,那就看我们的运气了。。。
现在我们还在OEP处(还记得吧),我用OllyHelper插件分配了一段内存在0x02230000
主动修复代码(修复无时间检测的代码):
60 9C B8 00 10 40 00 33 C9 66 8B 08 66 81 F9 FF 15 74 08 80 F9 E8 74 13 40 EB EC 8A 48 05 80 F9
01 75 F5 8B 48 02 8B 09 40 EB 08 8B 48 01 03 C8 83 C1 05 81 F9 00 20 82 01 72 DD 81 F9 00 30 82
01 77 D5 83 C0 05 50 FF E1 59 33 DB 66 8B 59 FA 66 81 FB FF 15 75 05 89 41 FC EB 2F 8A 19 80 FB
27 74 1A 80 FB 40 74 15 80 FB 90 74 10 80 FB F5 74 0B 80 FB F8 74 06 80 FB F9 74 01 49 83 E9 05
66 C7 01 FF 15 89 41 02 83 C1 06 8B C1 3D 80 DB 43 00 77 02 EB 83 9D 61 C3
02230000 60 pushad ; 保护一下现场
02230001 9C pushfd
02230002 B8 00104000 mov eax,401000 ; 代码基址
02230007 33C9 xor ecx,ecx
02230009 66:8B08 mov cx,word ptr ds:[eax]
0223000C 66:81F9 FF15 cmp cx,15FF ; 搜索Call []
02230011 74 08 je short 0223001B
02230013 80F9 E8 cmp cl,0E8 ; 搜索 Call XXXXXXXX
02230016 74 13 je short 0223002B
02230018 40 inc eax ; 不是的话就继续找
02230019 ^ EB EC jmp short 02230007
0223001B 8A48 05 mov cl,byte ptr ds:[eax+5]
0223001E 80F9 01 cmp cl,1 ; 确定这个Call []真的是我们需要的,因为中还有一些很无辜的东西
02230021 ^ 75 F5 jnz short 02230018
02230023 8B48 02 mov ecx,dword ptr ds:[eax+2] ; 取调用地址
02230026 8B09 mov ecx,dword ptr ds:[ecx]
02230028 40 inc eax
02230029 EB 08 jmp short 02230033
0223002B 8B48 01 mov ecx,dword ptr ds:[eax+1]
0223002E 03C8 add ecx,eax
02230030 83C1 05 add ecx,5
02230033 81F9 00208201 cmp ecx,1822000 ; 确定这个Call是不检测时间的
02230039 ^ 72 DD jb short 02230018
0223003B 81F9 00308201 cmp ecx,1823000
02230041 ^ 77 D5 ja short 02230018
02230043 83C0 05 add eax,5
02230046 50 push eax ; 构造Call的返回地址
02230047 FFE1 jmp ecx ; 去吧
02230049 59 pop ecx ; Patch返回处,获取Call的返回地址
0223004A 33DB xor ebx,ebx
0223004C 66:8B59 FA mov bx,word ptr ds:[ecx-6] ; 准备区分Call []和Call
02230050 66:81FB FF15 cmp bx,15FF
02230055 75 05 jnz short 0223005C
02230057 8941 FC mov dword ptr ds:[ecx-4],eax ; Call []的话就直接写入地址
0223005A EB 2F jmp short 0223008B
0223005C 8A19 mov bl,byte ptr ds:[ecx] ; 确定Call加密的垃圾代码是不是在调用后面
0223005E 80FB 27 cmp bl,27 ; daa
02230061 74 1A je short 0223007D
02230063 80FB 40 cmp bl,40 ; inc eax
02230066 74 15 je short 0223007D
02230068 80FB 90 cmp bl,90 ; nop
0223006B 74 10 je short 0223007D
0223006D 80FB F5 cmp bl,0F5 ; cmc
02230070 74 0B je short 0223007D
02230072 80FB F8 cmp bl,0F8 ; clc
02230075 74 06 je short 0223007D
02230077 80FB F9 cmp bl,0F9 ; stc
0223007A 74 01 je short 0223007D
0223007C 49 dec ecx
0223007D 83E9 05 sub ecx,5
02230080 66:C701 FF15 mov word ptr ds:[ecx],15FF ; 构造Call []
02230085 8941 02 mov dword ptr ds:[ecx+2],eax
02230088 83C1 06 add ecx,6
0223008B 8BC1 mov eax,ecx
0223008D 3D 80DB4300 cmp eax,43DB80 ; 手动找到的最后一个需要修复的地址
02230092 77 02 ja short 02230096
02230094 ^ EB 83 jmp short 02230019
02230096 9D popfd ; 完成后恢复现场
02230097 61 popad
02230098 C3 retn ; 可以在这里下断,但不保证结果
解释一下最后一句,理论上来说修复之后是会停在那个retn的地方,但是不排除会像我一下直接退出。但是这是第一步已完成了,我们用LordPE把0x401000处长度为0x3D000的数据dump下来保存为dump.dmp。
需要Patch的调用过程(我们这里的Patch是为了直接得到API的正确的储存位置而不是API的地址):
01822EC0:
01822F77 FF30 push dword ptr ds:[eax] <-改为JMP 2230049
01822F79 C3 retn
1822D00
01822DD4 8945 04 mov dword ptr ss:[ebp+4],eax <-这里eax改为esi
。。。。
01822DE3 C3 retn <-这里改为pop eax;JMP 2230049
1822AF0
01822BA5 8B00 mov eax,dword ptr ds:[eax] <-改为JMP 2230049
然后我们重新来到入口点(和我一样挂掉的朋友就重新忍受一下异常吧),再用OD载入备份dump.dmp,然后全部选中代码段,右击选择全部取消。这样我们刚刚的努力就有意义了。
被动修复代码(修复有时间接侧的调用):
60 9C 8B 4C 24 24 33 D2 BA 00 E0 43 00 3B 02 74 05 83 C2 04 EB F7 33 DB 89 51 FC 9D 61 FF E0
02230000 60 pushad ; 保护一下现场
02230001 9C pushfd
02230002 8B4C24 24 mov ecx,dword ptr ss:[esp+24] ; 获取调用的返回地址
02230006 33D2 xor edx,edx
02230008 BA 00E04300 mov edx,43E000 ; API的正确存储位置的起点
0223000D 3B02 cmp eax,dword ptr ds:[edx] ; 确定API地址对应的储存位置
0223000F 74 05 je short 02230016
02230011 83C2 04 add edx,4
02230014 ^ EB F7 jmp short 0223000D
02230016 33DB xor ebx,ebx
02230018 8951 FC mov dword ptr ds:[ecx-4],edx ; 由于现在只有Call [],所以就直接写地址
0223001B 9D popfd ; 恢复现场
0223001C 61 popad
0223001D FFE0 jmp eax ; 回去执行API
需要Patch的调用过程(我们这里Patch是为了得到API的地址,方便后面调用):
017C74E9 - FFE0 jmp eax <-我们需要返回修复的地方。改为JMP 2230000
现在我们就要经一切可能使用游戏的一切功能,不过还好,看过KID秋之回忆2代的代码心里很清楚游戏真正有用的代码不会多。
最后我们点退出游戏,OD就会直接挂住它,再用LordPE把0x401000处长度为0x3D000的数据再Dump一份存为dump1.dmp。
现在我们打开WinHex把dump1.dmp写入dump.exe的0x1000位置覆盖后面的数据。然后打开ImportREC填入OEP,IAT起始地址3E000,大小:0x29C,添加一个段修复一下就可以运行了。
现在我们的sony已经很无助了,因为我们现在已经可以正常的玩了,不过,谁知道什么时候会出问题呢?
Need Fpr Speed: Most Wanted(Black Edtion)要下完了,要是硬盘还够的话,那想君就要等上一段时间再来继续修了。
不过现在我们已经可以XX了,其他的暂时就不是那么重要了。
11月21日
无语,NFSMW居然是SD4.60。。。。。而且不知支持我的i855GME。。。。。。
我们还是继续修吧!
由于这次要修得比较少,所以我反而变得更懒了。
我们先搜索一个没修的,然后直接走到它的终点:
017C74E9 - FFE0 jmp eax <-这里
我们把上面那个mov eax,[esi]直接nop掉,然后在这里写下Patch:
mov ecx,[esp]
mov [ecx-4],eax
retn
然后我们就可以回去了,搜一个,新建EIP,按一下F8。Call就被自己SMC了。然后下一个。。。。
最后,我们用LordPE把0x00401000的地方长度为0x3D000的数据dump下来了,用WinHex贴入我们最后那个文件的
0x1000处。
OK全部修完了。
不过,理论上来说不会有问题了,先是会不会和理想有差距那就只能走着瞧了。
如果哪位对那个时间检测很清楚的话,麻烦告诉小弟。谢谢观看。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)