Monday, October 30 2006 @ 07:32 PM CET Contributed by: haggar Views: 16405
Level : intermediate
SafeDisc 3.20 - 4.00
If you ever played games in your life, then you for sure know what is SafeDisc. SafeDisc is one of the today most used CD protections. In a previous tutorial we met SafeDisc "small brother" - SafeCast. SafeDisc is product of the same company and it is almost indentical as SafeCast. Main difference is that SafeDisc checks for bad sectors on the CD to be sure that program is running from original one. Bad sectors cannot be copied to another CD. Well it shouldn't be, but programs like Alcohol 120% and similar can do that. It also depends of SafeDisc version. I'm not quite sure about that, copying CDs doesn't interest me. Removing protection layer is objective of this tutorial. For this tutorial you need original CD and Windows XP.
Target for this tutorial is "Need For Speed Underground 2", protected probably with some version in range from 3.20 up to 4.00. It is not possible to detect exact version because SafeCast above 3.20 have erased version numbers. PEiD fails to detect protection at all, but Protection_ID v5.1f gives solid information:
Scanning -> C:Need for Speed Underground 2speed2.exe
File Type : Exe, Size : 5987981 (05B5E8Dh) Bytes
-> File has 1166989 (011CE8Dh) bytes of appended data starting at offset 0499000h
[!] Safedisc v3.20 - v4.xx or newer [removed version] detected !
[!] removed version is Safedisc v4.00.000 - v4.00.003
[!] Possible CD/DVD-Check String -> Please insert
- Scan Took : 1.938 Seconds
Protection in this version of SafeDisc consits from:
[1] CD check - bad sectors, we have original CD so this is not problem for unpacking.
[2] Debugger checks - easy to defeat, same as in SafeCast.
[3] Import protection - same as in SafeCast, requires little work but it is not hard.
[4] Emulated opcodes - again, same as in SafeCast.
[5] DebugBlocker and nanomites - the hardest part of protection.
2. OEP, debugger checks and SafeDisc debugger
It is very easy to find whre OEP should be. We open protected file in Olly and we can spot OEP jump:
At address 00933159 is jump that points in first section. That is OEP jump.
Debugger checks are same as in SafeCast. We can place breakpoint on IsDebuggerPresent and then return to code:
6670D9F0 56 PUSH ESI
6670D9F1 68 80A77A66 PUSH 667AA780
6670D9F6 33F6 XOR ESI,ESI
6670D9F8 E8 72C00000 CALL 66719A6F
6670D9FD 50 PUSH EAX
6670D9FE E8 90C00000 CALL 66719A93
6670DA03 83C4 08 ADD ESP,8
6670DA06 85C0 TEST EAX,EAX
6670DA08 74 1C JE SHORT 6670DA26
6670DA0A FFD0 CALL EAX <------------------------ IsDebuggerPresent call.
6670DA0C 8BF0 MOV ESI,EAX
6670DA0E 66:85F6 TEST SI,SI
6670DA11 74 13 JE SHORT 6670DA26
6670DA13 E8 6238FFFF CALL 6670127A <------------------- "BadBoy" procedure!
6670DA18 66:8BF0 MOV SI,AX
6670DA1B 66:F7DE NEG SI
6670DA1E 1BF6 SBB ESI,ESI
6670DA20 46 INC ESI
6670DA21 66:85F6 TEST SI,SI
6670DA24 75 13 JNZ SHORT 6670DA39
6670DA26 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
6670DA2A 8B08 MOV ECX,DWORD PTR DS:[EAX]
6670DA2C 81E1 EA894267 AND ECX,674289EA
6670DA32 8908 MOV DWORD PTR DS:[EAX],ECX
6670DA34 66:8BC6 MOV AX,SI
6670DA37 5E POP ESI ; 0012FB10
6670DA38 C3 RETN
6670DA39 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
6670DA3D 8B08 MOV ECX,DWORD PTR DS:[EAX]
6670DA3F 81E1 119800EF AND ECX,EF009811
6670DA45 8908 MOV DWORD PTR DS:[EAX],ECX
6670DA47 66:8BC6 MOV AX,SI
6670DA4A 5E POP ESI ; 0012FB10
6670DA4B C3 RETN
When we break there, we trace into "BadBoy" procedure. When we enter in it, we just place RETN on first opcode inside and all debugger checks are killed. It is already explained in SafeCast tutorial.
SafeDisc debugger, or self-debugging is something new. Protected program will create couple temporyr files in temp older. One of those files (they all have random names) is executable. Main protected file starts that executable and then waits (WaitForSingleObject) signal that is can continue. Temp executable will attach to protected exe , give it signal and continue to debug it. This prevents debugging with Olly or any RING3 debugger. That can be prevented by not executing CreateProcessA , WaitForSingleObject, and performing couple more small changes to prevent crushing.
Here is how OEP can be found with Olly:
- We place breakpoint on OEP jump:
00933159 -E9 9388E2FF JMP speed2.0075B9F1
- Then we place bp on CreateProcessA and break there:
We don't execute it, instead we set "new origin" at RETN 28. We run and return to main code.
- We must avoid CloseHandle execution after returning from CreateProcessA because process is not created, there is no handle and we would get INVALID_HANDLE exception. So we set new origin below:
00935424 85C0 TEST EAX,EAX
00935426 5D POP EBP
00935427 74 1E JE SHORT speed2.00935447
00935429 8B5424 20 MOV EDX,DWORD PTR SS:[ESP+20]
0093542D 52 PUSH EDX
0093542E FF53 1C CALL DWORD PTR DS:[EBX+1C] ; kernel32.CloseHandle
00935431 8B4424 24 MOV EAX,DWORD PTR SS:[ESP+24]
00935435 50 PUSH EAX
00935436 FF53 1C CALL DWORD PTR DS:[EBX+1C] ; kernel32.CloseHandle
00935439 5F POP EDI
0093543A 5E POP ESI
0093543B 66:B8 0100 MOV AX,1
0093543F 5B POP EBX
- Then we place bp at IsDebuggerPresent and we enter in "BadBoy" procedure where we patch first opcode with RETN.
- After that, we can place bp at the end (end because SafeDisc checks some imports for CC bytes) of SetEvent API. That will bring ous very close to WaitForSingleObject part:
667250A8 |> FF75 FC PUSH DWORD PTR SS:[EBP-4] ; /hEvent = 000000A0 (window)
667250AB |. FF15 64407966 CALL DWORD PTR DS:[<&KERNEL32.SetEvent>] ; SetEvent
667250B1 |. 85C0 TEST EAX,EAX <--------------------------- You should be here!!!
667250B3 |. 75 0C JNZ SHORT ~df394b.667250C1
667250B5 |. FFD3 CALL EBX ; ntdll.RtlGetLastWin32Error
667250B7 |. FF75 FC PUSH DWORD PTR SS:[EBP-4]
667250BA |. FFD6 CALL ESI ; kernel32.CloseHandle
667250BC |. E8 8FC7FEFF CALL ~df394b.66711850
667250C1 |> 6A FF PUSH -1 ; /Timeout = INFINITE
667250C3 |. 57 PUSH EDI ; |hObject = 00000098 (window)
667250C4 |. FF15 90407966 CALL DWORD PTR DS:[<&KERNEL32.WaitForSin>; WaitForSingleObject
667250CA |. FF75 FC PUSH DWORD PTR SS:[EBP-4] <-------------- Set origin here to avoid above API.
667250CD |. 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX
667250D0 |. FFD6 CALL ESI ; kernel32.CloseHandle
667250D2 |. 57 PUSH EDI
667250D3 |. FFD6 CALL ESI ; kernel32.CloseHandle
667250D5 |. 837D F8 00 CMP DWORD PTR SS:[EBP-8],0
667250D9 |. 5F POP EDI
667250DA |. 5E POP ESI ; kernel32.CloseHandle
667250DB |. 74 07 JE SHORT ~df394b.667250E4 <-------------- Execute this jump to avoid error detection.
667250DD |. FFD3 CALL EBX ; ntdll.RtlGetLastWin32Error
667250DF |. E8 6CC7FEFF CALL ~df394b.66711850
667250E4 |> 5B POP EBX ; ntdll.RtlGetLastWin32Error
667250E5 |. C9 LEAVE
667250E6 . C3 RETN
- Run and you'll break at OEP jump. Trace in and you will see OEP:
This feature is completly the same as in SafeCast and it is described in previous tutorial. I will not write whole thing again so please check SafeCast tutorial.
First Type:
Import points to some virtual addres where is code that calls FIND_CORRECT_IMPORT algo. When correct import is found, it jumps there. Using algo against itself we can decrypt all imports.
Second Type:
We have jumps that leads outside of main image. It leads to some code and eventualy it jumps to import.
4. Emulated opcodes
Again, same thing as in SafeCast. This JMP EAX jumps outside of code:
JMP EAX will jump to procedure which will emulate "stolen" opcode, but it will write original back so emulation is performed only one time. Simply by executing those calls we will force SafeDisc to restore stolen code. there can be several this JMP EAX routines.I found two in this example.
5. Nanomites
Nanomites are by far the hardest part. Code section of protected program is full of INT 3 opcodes on unusuall places. For example let's check this procedure:
INT 3 opcode represent one nanomite. When nanmite is executed, it couse exception. SafeDisc debugger takes control, checks what type of exception occured and where, then it emulate that opcode or it writes original opcode there.
To better understand how does this work, we need to debug SafeDisc debugger. With SoftICE it is easy to see what is going on, but if we want to do that with Olly, we need to perform small ritual. OK, so this is how I was able to attach olly to SafeDisc debugger:
- Load main protected file in Olly and break on CreateProcessA. Stop there and wait.
- Go to "C:Documents and SettingsYour_Name_HereLocal SettingsTemp" and open temporary executable in second Olly. Temp exe is hidden with some random name, in my case it is "~e5.0001". Change it's OEP to infinite jump, EB FE. Save changes and close that Olly.
- Execute CreateProcessA (place bp at the end of API). New process is created and it's looping forever. Wait with this olly.
- Open again second Olly and attach to new created process. F9 to run, F12 to pause. Restore oly OEP bytes. Minimize this olly.
- In first Olly, break at IsDebuggerPresent to kill debugger checks by patching "badboy" procedure. After that just run program. Program will wait for second process to attach. It will wait forever (WaitForSingleObject with PUSH -1 parameter) and that is good for us.
- Now here is confusing part: Open THIRD Olly instance, attach to the FIRST one, and detach FIRST OLLY INSTANCE from main proteced executable. Close third olly and that will close first one too, but protected program will be free in memory.
- Now, only one Olly is left and that Olly debugs SafeDisc debugger. From here you can continue on WaitForDebugEvent.
When INT 3 is executed, SD debugger checks where exception occured. Then it emulates that opcode. If same nanomite is executed second time, SD debugger will write (WriteProcessMemory) original opcode to main executable. That is probably to gain on speed. Non-stop emulation would slow down game to the death. Since opcode is second time written, there must be some check for that. Check is very simple, it is one CMP AX,1 and after it JNZ DONT_WRITE. Patching jump forces SD to always write opcode. Now I patched jump and played game within Olly a little. then I minimized, dumped, fixed IAT and code. I started dump and it worked! Game loaded perfect, but it crushed after some time what is expected because most of nanomites are not restored. So how to fix that?
To tell you the truth, I didn't came out with generic solution so I fixed them manually. I attched olly to temp executable and I patched that check CMP AX,1 - JNZ DONT_WRITE. Then I played game a little, tried all kinds of races, tried online game, etc... everything to make sure that I trigered (and by that restored) as much as possible nanomites. Then I dumped code section. After that, I found OEP in instance without SafeDisc debugger and I pasted this dumped code there. Then I fixed imports and stolen opcodes. Now I got second dump from which I could play game. But on exit it would crush. I decide to open dump and check hom many there is nanomites.
But amounth of CC bytes was huge due to reason that executable has tons of CC bytes that are not nanomites, but leftovers from VC++ compiler. For example:
004017C8 . C3 RETN
004017C9 CC INT3
004017CA CC INT3
004017CB CC INT3
004017CC CC INT3
004017CD CC INT3
004017CE 90 NOP
004017CF 90 NOP
005AFD43 . E9 58220000 JMP dumped1.005B1FA0
005AFD48 CC INT3
005AFD49 CC INT3
005AFD4A CC INT3
005AFD4B CC INT3
005AFD4C CC INT3
005AFD4D CC INT3
005AFD4E CC INT3
005AFD4F CC INT3
I wrote script for NOPing those CC bytes, then I found some nanomites and fixed them by examning SafeDisc debugg loop. In SD debugg loop, I would place bp on WaitForDebugEvent, then I would set address in buffer to point on my nanomite. Then in GetThreadContext buffer I would do the same. SafeDisc would decrypt code and I would just copy it. But as said, this is unfinished buisnes. I will have to examne it better or in a updated version of this tutorial, or in a new tutorial with new target (possible higher version of SafeDisc).
I assume that SafeDisc must have some table with all addresses where are nanomites, table with original code, but I didn't had will to trace trough code whole day. I'm already tired.
6. Custom CD check and a final touch
I assume that this is not part of SafeDisc protection. I removed original CD and started dump, but it asked me for CD 2. Breakpoint on GetDriveTypeA got me to CD check procedure:
This one is 3 times called. If this procedure returns EAX=0, CD check fails. This checks wants some files on CD etc. We just make some patch that will return EAX=1 and problem is solved.
EA Games usually have couple logo movies that are shown uppon startup. They are very annoying because they cannot be skiped so I patched them too. But that is not part of protection.
7. The end
And that was it. It was not too hard, but again, my dump is not fully rebuilded. Just as much it needs for playing the game. Some nanomites are stil left unresolved but we learned something about SafeDisc protection features.
The following comments are owned by whomever posted them. This site is not responsible for what they say.
SafeDisc 3.20 - 4.00
Authored by:
caki on
Monday, October 30 2006 @ 10:13 PM CET
Excellent work haggar :) Very nice tutorial, and here are a couple of suggestions to make it even better :)
When getting to the OEP and fixing imports, you make it too complicated to stop the Safedisc debugger from initializing. Simply nop the routine that the WaitForSingleObject is in (or nop the call to it) and you will be able to get to the OEP with the Safedisc debugger not interfering. Also, the nanomites part is a bit incomplete, but a fabulous analysis nonetheless. I wish you luck in finishing up with them :)
When getting to the OEP and fixing imports, you make it too complicated to stop the Safedisc debugger from initializing. Simply nop the routine that the WaitForSingleObject is in (or nop the call to it) and you will be able to get to the OEP with the Safedisc debugger not interfering. Also, the nanomites part is a bit incomplete, but a fabulous analysis nonetheless. I wish you luck in finishing up with them :)
Cheers
-caki