25/08/2011

NOD or GDI

Introduction

It's my first english article so please be cool.
If you haven't noticed yet, I (only) like old video games (see SNES), today i'm going to play with Red Alert : Tiberian Sun.
This game is protected by an old commercial protection : Safedisc aka C-dilla.

Detection

Safedisc version 1 can be recognized by several files on the CD :

And also the existence of two executables, Game.EXE and Game.ICD.
We can also recognize this protection by her signature :

tibsun_winhex

The signature is "BoG_ *90.0&!! Yy>" followed by 3 unsigned integers : the version, subversion an revision number.

Or simply by using Protection ID :

tibsun_protecid

So in this article we will talk about how to defeat Safedisc version 1.11.0000 using a ring3 debugger.
Yes because all over the internet, i just found article / tutorial using Softice.

Anti Debug

So let's open Game.exe into OllyDbg.
We can found two anti debug tricks related to a ring 3 debugger.
The first anti debug trick is several call to IsDebuggerPresent(), and they also check this manually :

004212C6   .  64:A1 1800000>MOV EAX,DWORD PTR FS:[18]
004212CC   .  8B48 30       MOV ECX,DWORD PTR DS:[EAX+30]
004212CF   .  0FB641 02     MOVZX EAX,BYTE PTR DS:[ECX+2]
004212D3   .  85C0          TEST EAX,EAX

So by putting this code where you want, and change the origin ("New origin here" => Ctrl + Gray *), we can defeat this trick.

004197C5      64:A1 1800000>MOV EAX,DWORD PTR FS:[18]
004197CB      8B40 30       MOV EAX,DWORD PTR DS:[EAX+30]
004197CE      83C0 02       ADD EAX,2
004197D1      C600 00       MOV BYTE PTR DS:[EAX],0
00419E35    ^\E9 5675FFFF   JMP GAME.<ModuleEntryPoint>

The second trick is several call to ZwQueryInformationProcess(), with ProcessInformationClass argument set to ProcessDebugPort for checking if the process is being run under the control of a ring 3 debugger.
I know, it can be simply bypass by using the Phant0m plugin, but i wanted to write a script to defeat it :

var is_ProcessDebugPort
var buffer
var ZwQueryInformationProcess

gpa "ZwQueryInformationProcess", "ntdll.dll"
bphws $RESULT, "x"
mov ZwQueryInformationProcess, $RESULT
add $RESULT, C
bphws $RESULT, "x"
mov is_ProcessDebugPort, 0

cnt:
eob break
run

break:
cmp eip, ZwQueryInformationProcess
je begin_zwqueryinformationprocess

end_zwqueryinformationprocess:
cmp is_ProcessDebugPort, 1
jne cnt
mov is_ProcessDebugPort, 0
mov [buffer], 0
jmp cnt

begin_zwqueryinformationprocess:
cmp [esp + 8], 7
jne cnt
mov buffer, [esp + C]
log buffer
mov is_ProcessDebugPort, 1
jmp cnt

Explanation :

No we can work with our executables without problems.
There are another anti debug tricks, like calls to CreateFileA() with argument : "\.SICE" and "\.NTICE", for checking is softice is running.
Also in this version, they load a driver (secdrv.sys) but i have noticed nothing about anti debug techniques.
I don't know what the purpose of this driver in this version of safedisc (1.11.0000), i tried to do some shit with DeviceIoControl() api but without result.
Apparently, the driver is safe in this version while others are : CVE-2007-5587.

*.ICD

At the beginning of the post, I mentionned the existence of two files Game.EXE and Game.ICD.
Game.EXE executable is only a loader which decrypts and loads the protected game executable in the encrypted ICD file :

tibsun_procexp

tibsun_createproc

Safedisc uses a debug locker technique with buffer overflow to the start the real executable (ICD file).
So let's see the buffer used by WriteProcessMemory() :

tibsun_writeprocess

You can notice the different call eax, LoadLibrary("dplayerx.dll"), GetProcAdress("Ox77F052CC"), then it call this function, for decrypting all sections of the icd file (it uses TEA ).
So if we want to attach a debugger to the icd process, remember my last post about EBFE or CC tricks, and notice the PUSH EBP RET (0x53C3), we can replace these opcode by EBFE, then attach olly to the process.
We can see that if DEP is on, the game will crash (failed?).
So let's replace (0x53C3 by 0xEBFE), continue the program (ResumeThread() will be called) and open a new ollydbg and attach to this process.
Then pause the program ( F12 ), and replace JMP SHORT 0012FFB2 by PUSH EBX RET.
Trace the code you will arrive into kernel32, continue tracing, then at the entry point of the icd file.

tibsun_oep

So we are here and the fun part can begin :] :

006854E3 >  55              PUSH EBP
006854E4    8BEC            MOV EBP,ESP

Call redirection

In Safedisc, all the calls to Kernel32 or User32 api are done through dplayerx.dll.
Let's take an example :

00685509    FF15 54826900   CALL DWORD PTR DS:[<&KERNEL32.CreateDirectoryA>] ; DS:[00698254]=01213423

...

01213423    60              PUSHAD
01213424    68 67000000     PUSH 67
01213429    68 00000000     PUSH 0
0121342E    FF15 44342101   CALL DWORD PTR DS:[1213444]                      ; dplayerx.00D4E9D0
01213434    83C4 08         ADD ESP,8
01213437    61              POPAD
01213438    FF25 3E342101   JMP DWORD PTR DS:[121343E]                       ; Jump to api

You can notice a call to dplayerx.00D4E9D0, with a stack where we pushed 0x67 and 0x0.
What does it mean ?
It simple, 0x0 is for telling the routine that it's a kernel32 api, and 0x67 is the number of the api to call.
This routine will GetProcAddress() for the specified api, then jump to it.
And what about User32 api ?

00D750B7    60              PUSHAD
00D750B8    68 43000000     PUSH 43
00D750BD    68 01000000     PUSH 1
00D750C2    FF15 D850D700   CALL DWORD PTR DS:[D750D8]                                           ; dplayerx.00D4E9D0
00D750C8    83C4 08         ADD ESP,8
00D750CB    61              POPAD
00D750CC  - FF25 D250D700   JMP DWORD PTR DS:[D750D2]

Same thing but we pushed 0x1 instead of 0x0, and jump to the resolved address.

Fix this

The problem is if we dump the executable and try to reconstruct the iat, it will fail, because all calls to kernel32 or user32 api must be solved by dplayerx.dll :

tibsun_import

So why not using their own routine (dplayerx.00D4E9D0) to fix our iat :] ?
Let's take a look at rdata section for all kernel32 redirection :

tibsun_kernel32

It starts at 0x6980b8 and ends at 0x69831b

>>> hex(((0x69831b - 0x6980b8) / 4) + 1)
'0x99'

So we got 0x99 call to fix, like ImportRec said us : NbFunc:99(decimal:153) Valid:NO.
Be careful because the rdata section is not Writable, go fix it :

tibsun_rdata

No choose any memory region wich is executable and code this :

01213DEB    33DB               XOR EBX,EBX				; we start with api 0
01213DED    BA B8806900        MOV EDX,006980b8			; Start of the rdata kernel32 redirection
01213DF2    60                 PUSHAD
01213DF3    53                 PUSH EBX
01213DF4    6A 00              PUSH 0				; Kernel32 api
01213DF6    FF15 61252101      CALL DWORD PTR DS:[1212561]          ; dplayerx.00D4E9D0
01213DFC    83C4 08            ADD ESP,8
01213DFF    890D 00009500      MOV DWORD PTR DS:[950000],ECX	; save the api address
01213E05    61                 POPAD
01213E06    8B0D 00009500      MOV ECX,DWORD PTR DS:[950000]        
01213E0C    890A               MOV DWORD PTR DS:[EDX],ECX		; fix the api address in rdata
01213E0E    43                 INC EBX   	 			; next api
01213E0F    81FB 99000000      CMP EBX,99				; no more api to fix ?
01213E15    74 05              JE SHORT 01213E1C
01213E17    83C2 04            ADD EDX,4				; next api address to solve
01213E1A  ^ EB D6              JMP SHORT 01213DF2
01213E1C    CC                 INT3					; please stop

When we got out of the func dplayerx.00D4E9D0, ecx equals the address of api wich will be called, so we save this in a memory region writable.
And then we fix this in the rdata section for each api for kernel32.
Set a new origin at the begining of your code and run it.
Olly break, and our rdata look pretty nice :

tibsun_fix

We need to do the same thing for user32.

tibsun_user32

>>> hex(((0x6984C4 - 0x698358) / 4) + 1)
'0x5b'

We will use the same code as above but change the start value of edx to 0x698358, PUSH 0 to PUSH 1 and cmp EBX, 99 to cmp EBX, 5C.
Run it and now come back into import rec :

tibsun_ok

Everything is ok, we can dump and fix the iat without problems. Safedisc has been removed correctly :].
BUT ! there is an another problem when we launch the game it ask us for the a cd ....

tibsun_cd

This is not the purpose of the article but you should copy all *.mix files form the cd into the tiberian sun directory and look around 0x004DCBAE ;)

Conclusion

I know over the internet there are several unwrappers for safedisc but with closed source, so i'm actually studying differents versions of safedisc, for writting a similar tools but with source included.

Useful links