What is SafeCast? Dou you know what is SafeDisc maybe? SafeDisc is well known CD protection from Macrovision, used in many software that comes on CD/DVD (games, etc...) . SafeCast is the same thing except it is made for programs that doesn't came on CD (downloadable programs, trial versions, etc...). In this tutorial we will see how version 2.4 can be unpacked. It is little older one, but I had program protected with it. Target for my tutorial is Adobe Photoshop 8.0, but this should be generic aproach for all 2.4 versions.
Tools that I used for unpacking are usual ; PEiD, OllyDbg, ImpREC, LordPE, and some hex editor. Oh yeah, and Win XP.
I installed program and I scanned installation folder with PEiD. PEiD gave two positive results:
Now, examning protection code is not so easy. SC (SafeCast in the rest of this tutorial) extracts couple files in temp folder that have different purpose. First file that is extracted , with some random name such is ~e5d141.tmp , is executable file. That file is only file that will remain in temp folder after protected program closes. This executable is a cleanup application. It's purpose is to delete all other protection files extracted in temporary folder. Other files are more interesting because one of them is responsable for debugging protection (ei, it holds couple anti debug tricks). This file can be dumped and examned, but it has some encrypted parts of code, plus some obfuscation and there is no need indeed to spend time on that.
First file that I decided to unpack is that AdobeLM.dll. LM probably stands for License Manager. First check that I noticed in protected file is IsDebuggerPresent one. So I placed bp on that api and returned from kernel32. I was in temporary file:
00879A70 /$ 56 PUSH ESI
00879A71 |. 68 C8E18C00 PUSH ~df394b.008CE1C8
00879A76 |. 33F6 XOR ESI,ESI
00879A78 |. E8 B8B70000 CALL ~df394b.00885235
00879A7D |. 50 PUSH EAX
00879A7E |. E8 D6B70000 CALL ~df394b.00885259
00879A83 |. 83C4 08 ADD ESP,8
00879A86 |. 85C0 TEST EAX,EAX
00879A88 |. 74 1C JE SHORT ~df394b.00879AA6
00879A8A |. FFD0 CALL EAX <------------------------------- Here was IsDebuggerPresent call.
00879A8C |. 8BF0 MOV ESI,EAX <---------------------------- I'm Here!!!
00879A8E |. 66:85F6 TEST SI,SI
00879A91 |. 74 13 JE SHORT ~df394b.00879AA6 <-------------- Good jump.
00879A93 |. E8 E277FFFF CALL ~df394b.0087127A <------------------ BadBoy procedure.
00879A98 |. 66:8BF0 MOV SI,AX
00879A9B |. 66:F7DE NEG SI
00879A9E |. 1BF6 SBB ESI,ESI
00879AA0 |. 46 INC ESI
00879AA1 |. 66:85F6 TEST SI,SI
00879AA4 |. 75 13 JNZ SHORT ~df394b.00879AB9
00879AA6 |> 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
00879AAA |. 8B08 MOV ECX,DWORD PTR DS:[EAX]
00879AAC |. 81E1 EA894267 AND ECX,674289EA
00879AB2 |. 8908 MOV DWORD PTR DS:[EAX],ECX
00879AB4 |. 66:8BC6 MOV AX,SI
00879AB7 |. 5E POP ESI ; 0006F46C
00879AB8 |. C3 RETN
00879AB9 |> 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
00879ABD |. 8B08 MOV ECX,DWORD PTR DS:[EAX]
00879ABF |. 81E1 119800EF AND ECX,EF009811
00879AC5 |. 8908 MOV DWORD PTR DS:[EAX],ECX
00879AC7 |. 66:8BC6 MOV AX,SI
00879ACA |. 5E POP ESI ; 0006F46C
00879ACB . C3 RETN
Now, what we can see is that CALL EAX was call to IsDebuggerPresent. If SI=0, jump below it will be executed and that is good jump. If SI=1, debugger is detected. Procedure below jump is executed if debugger is detected. Ofcourse, logic says that we just change SI from 1 to 0 and we can pass this check, but I was wondering how many times that "BadBoy" procedure is used. So I traced in it:
Inside we can see that it finds executable that loaded this dll (which is in this case LOADDLL.EXE from Olly) and then it reads some information from it, etc... But that is not important for us. I checked from how many locations this procedure is called. Olly says:
Local calls from 008796CF, 00879789, 00879865, 00879A93
This procedure can be called 4 times. So we have 4 anti-debug tricks. Let's check those locations.
 First reference to that procedure is from 008796CF:
008796B4 |. 64:A1 18000000 MOV EAX,DWORD PTR FS:
008796BA |. 8B48 20 MOV ECX,DWORD PTR DS:[EAX+20]
008796BD |. 85C9 TEST ECX,ECX
008796BF |. 75 05 JNZ SHORT ~df394b.008796C6
008796C1 |. 33C0 XOR EAX,EAX
008796C3 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
008796C6 |> 61 POPAD
008796C7 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; ~df394b.008713E9
008796CA |. 66:85C0 TEST AX,AX
008796CD |. 74 10 JE SHORT ~df394b.008796DF
008796CF |. E8 A67BFFFF CALL ~df394b.0087127A
008796D4 |. 66:F7D8 NEG AX
008796D7 |. 1BC0 SBB EAX,EAX
008796D9 |. 40 INC EAX
008796DA |. 66:85C0 TEST AX,AX
008796DD |. 75 10 JNZ SHORT ~df394b.008796EF
008796DF |> 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8]
008796E2 |. 8121 E16AFEFE AND DWORD PTR DS:[ECX],FEFE6AE1
008796E8 |. 5F POP EDI ; 0006F46C
008796E9 |. 5E POP ESI ; 0006F46C
008796EA |. 5B POP EBX ; 0006F46C
008796EB |. 8BE5 MOV ESP,EBP
008796ED |. 5D POP EBP ; 0006F46C
008796EE |. C3 RETN
008796EF |> 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8]
008796F2 |. 5F POP EDI ; 0006F46C
008796F3 |. 5E POP ESI ; 0006F46C
008796F4 |. 5B POP EBX ; 0006F46C
008796F5 |. 8121 005CE46A AND DWORD PTR DS:[ECX],6AE45C00
008796FB |. 8BE5 MOV ESP,EBP
008796FD |. 5D POP EBP ; 0006F46C
008796FE . C3 RETN
We can see that it is a custom IsDebuggerPresent check.
 Second reference is at 00879789 and it is the same thing - a custom IsDebuggerPresent check.
 Third reference at 00879865 is ZwQueryInformationProcess check:
 And a final reference is our IsDebuggerPresent check at 00879A93, already explained.
So we have only IsDebuggerPresent and ZwQueryInformationProcess. Now , weird is that I used OllyAdvanced plugin to hide from these checks , but sometimes photoshop would run within olly and sometimes don't. I don't know why. But there is no need for any plugins. I realized that we can just patch (fill with NOPs) whole "BadBoy" procedure and neither one debugger check will affect our debugging session :)
Oh , I forgot: be carefull where to place breakpoints. SC checks some APIs:
12F794E3 |. 803C03 CC |CMP BYTE PTR DS:[EBX+EAX],0CC
3. Reaching OEP
Finding OEP is not hard after this. If you check Entry Point of protected file (adobelm.dll in my case), you will see obvious OEP jump:
So idea for script was to trace those pointers and obtain imports. That worked and I retrieved all imports. All three thunks was filled with imports. But! Imports was messed. It means that GetCommandLine would be placed on wrong thunk, and many more. I don't know the reason why. I have ome ideas but I'm not sure. After that, I traced trought temporary files, decrypting , removing junk obfuscation, all that in order to se can I find where SC fills imports into IAT. I found it with hardware breakpoints, but I just couldn't find some "magic jump". After some time I noticed that I get good imports if I trace calls and jumps, not the IAT itself.
- Heureka ! ;)
Ok. I can retrieve whole IAT, but imports are mixed in thunks. But, I can save that IAT, then use tracing calls to find good mports, than just redirect those references to new IAT. First I found IAT:
I saved binary in text file. Then I restarted AdobeLM.dll (within main photoshop exe), found OEP of DLL, and I placed IAT in one SC section (Section=stxt774). Then I started to write scripts for redirecting references. We have these possibilities:
If I load DLL in Olly, this call will point to walue in IAT (because image base of dll is 10000000). BUT, that is false call - a junk. If I load DLL within photoshop, it doesn't point to IAT (because it is loaded on different addres due lack of space in memory caused by lot of other dlls loaded)! In first case, it affects my scripts! In second, it is only junk.
Fixing JMP/CALL references was easy, but registers references I fixed manually (huh, I took me long to do that). Ok, after imports are fixed, I checked them one more time to be sure that I didn't missed something.
5. More imports
I runned DLL trough Olly and it crushed with message "LOADDLL.EXE: Unable to load dll". In case that DLL is OK, I should get message in olly status bar "Initialization of dll finished". So I traced again to see where it crushes and I found this jump:
12EEAF9B .-E9 70C90200 JMP dump.12F17910
12EEAFA0 85 DB 85
12EEAFA1 . 834E 04 FF OR DWORD PTR DS:[ESI+4],FFFFFFFF
12EEAFA5 . 6A 01 PUSH 1
12EEAFA7 . 8906 MOV DWORD PTR DS:[ESI],EAX
12EEAFA9 . 58 POP EAX
12EEAFAA . 5E POP ESI
12EEAFAB . C3 RETN
Ah, crap >:( This jump points to section where I placed new IAT. Let's see what is it in original DLL:
And after import is executed, it returns to code below jump and one junky byte. So this is another replace for import call of type CALL DWORD[IMPORT]!?! It seams that it is. But code is little different after every jump. Different or not, I wrote easy script for finding all imports. Now, this was all pretty messy because I didn't expect this, so I won't describe how I fixed these imports. It's up to your skills - or do it manually, or write scripts, or code some plugin.
6. Emulated opcodes
Just when I tought that DLL is finaly unpacked - I stumbled on new problem. I tried to run DLL in Olly, but it stoped on exception. I located problem:
References in AdobeLM:.text to 12EC41C9
Address Disassembly Comment
12EC109B CALL AdobeLM.12EC41C9
12EC1EE1 CALL AdobeLM.12EC41C9
12EC2463 CALL AdobeLM.12EC41C9
lot of calls
12ED439B CALL AdobeLM.12EC41C9
12ED441B CALL AdobeLM.12EC41C9
I executed couple these calls and it semas that this code just emulates some calls, maybe jumps too (judging on stack). No registeres are changed uppon return. But, I was reading tutorial from mr. anonymous. He noticed that these calls emulate stolen opcodes. Also, to speed up execution of protected program (I guess) these opcodes are written to the place of call. I followed that PerformFixup@CJumpRun@@UAEKK@Z function in temp dll:
CMP EAX,4 compares number of execution of one CALL. So after call is used 3 times, 4-th time it will write original opcode instead of CALL. I patched both jumps and then used script to execute all those calls. That restored original opcodes:
If some opcode is not good, I can just undo it. Some opcodes (as first on this list) can be wrong because byte combination that make script think that they are CALL..
All protection of SafeCast is defeated now. But since I deal with protected DLL, relocations should be also fixed in case that DLL will be loaded on different locations (what will happen for sure in this program). That can be done with ImpREC brother tool - ReloX. I didn't fix them because I would had to do all this unpacking again on different base addres. But there are work arounds for that. For example, we pack main executabe (photoshop.exe) with some simple packer and then we inject code that will load our DLL first.
I decided to test my unpacked DLL. Photoshop started with some error, diferent than one for debugger/file_change detection. And after clicking OK, photoshop continued. Problem could be that my dump is "dirty". So second time I created new dump, but this time I didn't deleted two SC sections. I added 2000 Kb to the end of file and expanded last section. SC doesn't check for file size and section sizes! Good. After fixing this new dump (in which I have more faith), I got again error message and this time Photoshop wouldn't start ?!? Ok, dump should be good , so I traced to see usage of AdobeLM.dll.
So, if we patch AdobelLM.dll a little, it will tought that program is in registered state and it will go load next protected dll.
Anti-debug tricks described in this tutorial (IsDebuggerPresent & ZwQueryInformationProcess) are same in all versions of SafeCast/Disc up to 3.20.024 version of protection (Medal of Honor Pacific Assault checked). Also, OEP jump is same on all those versions so script for finding OEP "01.SafeCast2-3_OEP.txt" should work on all targets in that range, even on protected CDs (if you have original CD).
In the archive you will find some scripts that I used while unpacking. They are just here if you are curious to see what they do.
01.SafeCast2-3_OEP.txt - for finding OEP.
02.Decrypt_IAT.txt - to decrypt IAT.
03.Decrypt_CALL-IAT.txt - to decrypt CALL/JMP import references.
04.Decrypt_Jumps.txt - to decrypt jumps that again lead to imports.
05.Decrypt_REG_IAT.txt - just for help to redirect registers references to iat.
06.EmulatedCode.txt - for decrpting emulated opcodes.
07.Dejunk.txt - for removing junk from temporary DLLs.
Unpacking SafeCast was not easy. I tought that anti-debug layer will be hard, but rebuilding dumped target was hardest thing to do. It took me couple days to finish it. SafeCast is good protection, altough I didn't see anything new.
Other resources on SafeCast:
- There is one tutorial on ARTEAM site, writen by anonymous person.
- Also there is unpacker tool on same site that can unpack SafeCast targets. I didn't check it.
And that would be all from me. This tutorial probably has million grammar and spelling mistakes, sorry but that.