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.
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 :
The signature is "BoG_ *90.0&!! Yy>" followed by 3 unsigned integers : the version, subversion an revision number.
Or simply by using Protection ID :
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.
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: 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: 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
- Put an hardware breakpoint at the entry point of the function, and at the end.
- Check if the function was called with ProcessInformationClass argument = ProcessDebugPort ( 0x7 ).
- If it was called with the fact mentionned before, we set the buffer to 0 (The process is not being run under ring 3 debugger).
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.
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 :
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() :
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.
So we are here and the fun part can begin :] :
006854E3 > 55 PUSH EBP 006854E4 8BEC MOV EBP,ESP
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:=01213423 ... 01213423 60 PUSHAD 01213424 68 67000000 PUSH 67 01213429 68 00000000 PUSH 0 0121342E FF15 44342101 CALL DWORD PTR DS: ; 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.
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 :
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 :
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 :
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: ; dplayerx.00D4E9D0 01213DFC 83C4 08 ADD ESP,8 01213DFF 890D 00009500 MOV DWORD PTR DS:,ECX ; save the api address 01213E05 61 POPAD 01213E06 8B0D 00009500 MOV ECX,DWORD PTR DS: 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 :
We need to do the same thing for 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 :
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 ....
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 ;)
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.