心野的小巢

  IT博客 :: 首页 :: 联系 :: 聚合  :: 管理
  36 Posts :: 51 Stories :: 1305 Comments :: 0 Trackbacks
2000 年终最值得纪念的恐怕就是 Cossacks : The European wars --第一个使用Star-Force 保护系统游戏的推出了。Star-Force的出现给程序研究者带来了很多新的问题,现在是给这些问题一个答案的时候了。
  
Star-Force是怎样的保护?
  
这个保护系统最强的主要是以下几点:它的核心是一个伪代码的解释器,伪代码
大大复杂了对Star-Force的研究工作;一部分导入函数(imported function)
的代码是从系统库中拷贝出来并进行修改过的;一部分程序代码只有在执行时候才解密出来。除了这些,保护中还大量的应用了下面这些手段:检测某些内存段的CRC 校验和,经常性地将调试寄存器DRx清零,利用RDTSC指令来控制解码不同块代码的时刻(译者注:这句不是很明白,此句是对照两种不同的英文机器译本硬译出的),最后一块代码的解码甚至是通过截获int0在 ring0 中进行的。关于这个保护的详细情况我大致就介绍这么多,因为我们将另辟蹊径,使这些保护方法中的大多数在我们面前根本起不了作用。

Dump
在这个版本中Dump还是一件很简单的事情,在protect.dll中跳转到原始入口点
之前大致是这样的代码:
push 64h
MOV of eax, [Kernel32!Sleep ]
call eax
ret
因此我们设一个 bpx Sleep,启动游戏,在断点处停下来,跟进 ret 指令,然
后用PEditor之类就可以dump了。
  
恢复import
  
Star-Force所使用的import保护技术是我所见过的保护程序中最强的一种。在传
统的保护中一般最后都是跳转到真正的函数入口处,或者跳转到真正函数入口几条指令之
后的地方。在Star-Force中,所有从kernel32.dll user32.dll advapi32.dll
中import的函数代码都整个被拷贝到保护代码的内存中了(除了push,pop,jxx 指令),拷
贝过来的代码按照下面的规则进行了修改:
1: ?????? 改成 nop 和 jmp short $2 (译者注:这里没有一个软件可以正确
翻译出原来的代码,大家猜猜是什么呢?)
2: push 12345678h 修改成
MOV eax, 12345678h
xchg eax, [esp]
3: push esi 修改成
xchg eax, esi
push eax
xchg eax, esi
4: pop esi 修改成
MOV esi, eax
pop eax
xchg eax, esi
5: 两字节的 jcc 代码被修改成 6 字节的相应代码 (译者注:即 0F 开头的相对跳转代码)
6: 两字节的 push 00h 修改成 5 字节的版本 import 函数入口的长跳转 jmp X 修改成 jmp $+5 ,在首次执行到入口时,X指向的代码进行将windows库函数代码复制和修改的一系列工作,并将 jmp 指令指向修改后的代码。(译者注:原文说得不是很清楚,这一段是按照自己的理解翻译的,可能有误)不过就这个游戏来说,被保护的函数只有11个,因此我决定用手动恢复它们(在我后来对游戏Venom的研究中解决了这个问题):把代码手动转换回原始的形式,用sice在内存中搜索入口前10个字节左右相同的库函数。找出的函数结果按照在保护代码的内存中位置排列如下:
ReadFile, GetUserNameA, FindNextFileA, WriteFile, VirtualFree, GetFileType,VirtualAlloc, CreateFileA, DeleteFileA, FindClose, MessageBoxA
将所有import都手动恢复以后,我们就使用一个我常用的工具 ImportList by Boris来重建 IT。我们把它放进 dump 里面,这部分的工作就完成了。
恢复 _DllDispatch 的代码还没有看晕?那我们进行下一步工作。在我们 dump 出来的代码中大约有20多处(!)调用了 _DllDispatch -- protect.dll中唯一的导出函数。调用它的代码大概看起来是这样的:
push ID
call _DllDispatch
其中 ID 这个参数决定了哪一段代码需要被解码并执行,ID = 0 的情况是将主
程序解码出来。值得注意的是,解码出来的代码仍然包含着对 _DllDispatch 的调用。但是这个 _DllDispatch是用前面提到过的伪代码引擎执行的伪代码。怎么办呢?自己跟踪每一个调用直到跳转到实际的代码?(译者注:自己的理解)你如果愿意试可以自己去试,不过我将提供一种更有趣的方法,一次性自动获得所有被保护的代码。为了做到这一点就需要自己写点程序了。
首先我们知道一旦被执行过一次,需要的代码就以解密的形式存在内存中了,怎么捕捉到这一时刻呢?很简单,每个这样的函数多少都会执行到原来的代码中,因此我们可以截获对_DllDispatch的调用,在将控制权交给protect.dll之前,把原代码段统统清成0CCh,这样从解码出来的代码跳转出来到任何地方都会触发中断,我们截获int 3就可以处理之。更进一步的,我们从ring0的堆栈中找到ring3的堆栈标志,然后就可以很快找到解码出来的代码了。
  
对实现的一些说明
  
为了方便处理类似的游戏,我建议将我们的deprotect.dll 加入到程序的import中,它就可以截获protect.dll的入口了,不过这时程序的代码还没有被解码出来,只有一个指向protect.dll中的入口调用。我们把它修改一下,使得当protect.dll将代码段解码之后将控制权返回给我们的dll, 接下来我们搜索游戏的代码,就可以找到很多对_DllDispatch的调用,以及它们对应的ID,把ID都存到一个文件里面以避免出现重复的。
然后我们挨个用这些ID调用_DllDispatch,在 int 3 的处理函数里面将控制权正确的交给我们的搜索-恢复函数。我们需要在解码出的代码中继续搜寻对 _DllDispatch的调用以得到全部代码。当然还得把调用这一次DllDispatch的代码修改成直接调用解密代码。
因此搜索需要进行两遍,一遍在原来的代码中,另一遍在解密后的代码中。
全部工作做完以后,我们得到了两个文件,一个是text.bin,这是程序代码段中的代码,另一个是code.bin,这是经_DllDispatch解密出的代码,需要手动加到dump中去。整个EXE文件组装完毕后, protect.dll 就没用了,可以删掉。
  
最后的一点修正
  
启动解密后的游戏试验一下,结果很令人失望:金钱狂涨到无限多,任务说明不
显示,TCP/IP联网对战时选择地图后不生成地图。前两个问题好像是文件名的问题,把可执行文件改成 dmcr.exe 就正常了,最后一个错误原因是对某一个_DllDispatch的恢复时出错,大约是这个_DllDispatch中没有跳到其他的代码,就没有被我们的程序捕捉到(译者注:这里大概是一个没有调用任何其他函数的函数),导致最后生成的代码里面有一个错误的调用。这样的函数是我们研究的一个盲点,不过就这个例子来说很好解决,手动跟踪了一下,看出这个函数的地址(译者注:在解码内存区域中的偏移量) 是0A3Ch,手动修改一下调用,把5个nop改成对这里的调用就可以了。
现在所有的问题都解决了,Star-Force完全被搞定了。
  
源程序
  
deprotect.asm (7K) 源程序,使用 TASM5.0 编译成 DLL
deprotect.dll (8K) 编译好的DLL文件

注意:这个DLL只适用于文中提到的游戏。
(译者注:上面两个程序的URL如下
http://www.reversing.net/articles/122001/deprotect.asm
http://www.reversing.net/articles/122001/deprotect.dll


附deprotect.asp

.586p
.model flat, stdcall

extrn  ExitProcess:PROC, CreateFileA:PROC, WriteFile:PROC, \
CloseHandle:PROC, VirtualAlloc:PROC, GetModuleHandleA:PROC, \
GetProcAddress:PROC, GetCommandLineA:PROC

publicdll _DllPatch

;------------------------------------------------------------------------
.data ; Данные
;------------------------------------------------------------------------

DllName db 'protect.dll',0
FuncName db '_DllDispatch',0
CodePath db 'code.bin',0
TextPath db 'text.bin',0

FirstJump  dd 0
_DllDispatch dd 0
_DllDispatchInExe dd 0
CodeSection dd 0 ; Смещение секции кода

Buf dd 0 ; Буфер под секцию кода
FoundCode dd 0 ; Буфер для найденного кода
IdCalls dd 0 ; Буфер для идентификаторов

_edi dd 0
_ecx dd 0

Base dd 0
Base1 dd 0
SearchSize dd 0
SearchStep db 0
BufSize dd 300000h

NewCodeSectionRVA dd 0 ; Адрес новой секции
IdCallsOffset dd 0 ; Смещение в массиве идентификаторов
CodeOffset dd 0 ; Смещение в найденном коде
EntryPoint dd 0 ; Точка входа

int3 dd 0 ; Адрес INT3

Address dq 0 ; Временные переменные
Temp dd 0


;------------------------------------------------------------------------
.code ; Код
;------------------------------------------------------------------------

start:
cmp dword ptr [esp+8],1
jne DontInit

call VirtualAlloc, 0, BufSize, 1000h, 4 ; Память под буфера
mov Buf,eax

call VirtualAlloc, 0, 10000h, 1000h, 4
mov FoundCode,eax

call VirtualAlloc, 0, 100h, 1000h, 4
mov IdCalls,eax

call GetModuleHandleA, offset DllName ; Настройка констант
call GetProcAddress, eax, offset FuncName

mov _DllDispatch,eax

call GetCommandLineA

inc eax
push eax

mov edi,eax
mov ecx,100h
mov eax,'"'
repnz scasb
dec edi
xor eax,eax
stosd

call GetModuleHandleA
mov NewCodeSectionRVA,eax

mov edi,[eax+3Ch] ; PE-смещение
add edi,eax

mov edx,[edi+50h]
add NewCodeSectionRVA,edx

mov edx,[edi+2Ch]
add edx,eax
mov CodeSection,edx

mov edx,[edi+28h]
add edx,eax
mov EntryPoint,edx

mov eax,[edi+100h]
shr eax,2
inc eax
shl eax,2
mov BufSize,eax

mov esi,offset codestart ; Изменить код на старте
mov edi,edx
mov ecx,[edi+1]
add ecx,edi
add ecx,5
mov FirstJump,ecx
mov ecx,offset codeend
sub ecx,esi
rep movsb

DontInit:
xor eax,eax
inc eax
ret 0Ch

codestart:
push offset Control+5 ; Наш код на старте exe-шника
mov eax,FirstJump
jmp eax
codeend:

;------------------------------------------------------------------------
; Процедура, получающая управление после полной распаковки кода

Control:

mov eax,_DllDispatch ; Настройка констант
mov edi,CodeSection
xor ecx,ecx
dec ecx
_s:
repnz scasd

mov edx,[edi+4]
shr edx,18h
cmp dl,0BFh
jne _s

mov eax,edi
sub eax,4
mov esi,CodeSection
_s1:
inc esi
cmp eax,[esi]
jne _s1

cmp word ptr [esi-2],25FFh
jne _s1

lea eax,[esi-2]
mov _DllDispatchInExe,eax


mov ecx,BufSize ; Скопировать секцию кода
shr ecx,2
mov edx,ecx
mov edi,Buf
mov esi,CodeSection
rep movsd

mov ecx,edx ; Сменить секцию кода на INT3
mov eax,0CCCCCCCCh
mov edi,CodeSection
rep stosd

mov eax,offset Address ; Установить свой обработчик INT3
sidt [eax]
mov edx,[eax+2]

add edx,8*3
mov cx,[edx+6]
shl ecx,10h
mov cx,[edx]

mov int3,ecx

mov ebx,offset Int3Handler
mov [edx],bx
shr ebx,10h
mov [edx+6],bx

mov eax,Buf
mov Base,eax

mov eax,CodeSection
mov Base1,eax

mov eax,BufSize
sub eax,4
mov SearchSize,eax

GFind:
mov ecx,SearchSize ; Искать 'call _DllDispatch'
mov edi,Base
mov _edi,edi
mov _ecx,ecx

Find:
mov edi,_edi
mov ecx,_ecx
mov al,0E8h
repnz scasb

test ecx,ecx
jne ConStep

inc SearchStep ; Обеспечить два прохода
cmp SearchStep,2
je Exit

mov eax,FoundCode
mov Base,eax
mov eax,NewCodeSectionRVA
mov Base1,eax
mov eax,CodeOffset
mov SearchSize,eax

jmp GFind

ConStep:
mov _edi,edi
mov _ecx,ecx

mov ebx,[edi]
sub edi,Base
add edi,Base1
add ebx,edi
add ebx,4

cmp ebx,_DllDispatchInExe
jne Find

mov edi,_edi
mov eax,[edi-5] ; Получить идентификаторы

mov edi,IdCalls ; Не дублироваться
mov ecx,IdCallsOffset
shr ecx,2
test edi,edi
repnz scasd

setz cl
test cl,cl
je Insert

mov edx,[edi]

jmp Begin
Insert:
stosd
mov eax,CodeOffset
stosd

add IdCallsOffset,8
mov edx,CodeOffset

Begin:
mov edi,_edi ; Исправление вызвавшего call'а
sub edi,6
mov eax,90909090h
stosb
mov esi,[edi]
stosd
inc edi
mov eax,NewCodeSectionRVA
add eax,edx
sub eax,Base1
sub eax,edi
add eax,Base
sub eax,4
stosd
sub dword ptr [edi+2],4

test cl,cl
jne Continue

push esi ; Вызвать очередной _DllDispatch
call _DllDispatch

Continue:
jmp Find

Exit:
mov eax,offset Address ; Восстановить IDT
sidt [eax]
mov edx,[eax+2]
add edx,8*3

mov ebx,int3
mov [edx],bx
shr ebx,10h
mov [edx+6],bx

call WriteResults
call ExitProcess, 0

;-----------------------------------------------------
Int3Handler: ; Обработчик INT3       
;-----------------------------------------------------

pushfd
pushad

mov eax,offset Continue ; Вернуться из обработчика к нам
mov [esp+24h],eax

mov ax,8B55h ; Найти очередной call
mov edi,[esp+30h]
mov edi,[edi]
mov ecx,1000h
_loop1:
cmp [edi],ax
je l1
dec edi
loop _loop1

l1:
mov esi,edi

mov ax,0C35Dh
mov ecx,1000h
_loop2:
cmp [edi],ax
je l2
inc edi
loop _loop2
l2:
mov ecx,edi ; Поправить call'ы в найденном коде
sub ecx,esi
add ecx,2
mov edx,ecx

mov edi,FoundCode
add edi,CodeOffset

_jmp:
cmp byte ptr [esi],0E8h
jne _move
mov eax,[esi+1]
add eax,esi
cmp eax,600000h
jg _move
cmp eax,400000h
jl _move

mov eax,[esi+1]
add eax,FoundCode
add eax,esi
sub eax,edi
sub eax,NewCodeSectionRVA
mov [esi+1],eax

_move:

movsb ; Скопировать очередной call
dec ecx
jne _jmp

add CodeOffset,edx

popad
popfd

iretd

;------------------------------------------------------------------------
; Запись результатов

WriteResults:

call CreateFileA, offset CodePath, 40000000h, 0, 0, 2, 80h, 0
call WriteFile, eax, FoundCode, CodeOffset, offset Temp, 0
call CloseHandle, eax

call CreateFileA, offset TextPath, 40000000h, 0, 0, 2, 80h, 0
call WriteFile, eax, Buf, BufSize, offset Temp, 0
call CloseHandle, eax

ret

;------------------------------------------------------------------------
; Экспортируемая функция для удобства добавления библиотеки в EXE

_DllPatch:

ret

;------------------------------------------------------------------------

end start
posted on 2006-08-13 23:31 心野 阅读(1714) 评论(0)  编辑 收藏 引用 所属分类: 软件破解
只有注册用户登录后才能发表评论。