Contribute  :  Web Resources  :  Past Polls  :  Site Statistics  :  Downloads  :  Forum  
    BiW ReversingThe challenge is yours    
 Welcome to BiW Reversing
 Monday, September 26 2022 @ 07:33 AM CEST

Unpacking SafeCast 2.4


TutorialsLevel : intermediate

Unpacking SafeCast 2.4

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.

1. Introduction

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:

C:Program FilesAdobePhotoshop CSAdobeLM.dll SafeDisc 2.41.000 -> Macrovision [Overlay]
C:Program FilesAdobePhotoshop CSTw10122.dat SafeDisc 2.41.000 -> Macrovision [Overlay]

PEiD detects SafeCast as SafeDisc, what is correct because both are the same protection. You can manually check version of protection. Open protected file in Olly and check PE header at the end:

10000FD0 00 00 00 00 42 6F 47 5F 20 2A 39 30 2E 30 26 21 ....BoG_ *90.0&!
10000FE0 21 20 20 59 79 3E 00 00 00 00 00 00 00 00 00 00 ! Yy>..........
10000FF0 00 00 00 00 02 00 00 00 29 00 00 00 00 00 00 00 ........).......

String "BoG_" is unique in SafeDisc versions. Last 3 DWORDS hold version information number. You just need to translate hex numbers to decimal system:

02 00 00 00 -> 2
29 00 00 00 -> 41
00 00 00 00 -> 0

And you get 2.41.0 as version.

2. Anti-debug layer

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]
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]
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:

0087127A /$ 55 PUSH EBP
0087127B |. 8BEC MOV EBP,ESP
0087127D |. 81EC 14010000 SUB ESP,114
00871283 |. 56 PUSH ESI
00871284 |. 33F6 XOR ESI,ESI
00871286 |. E8 DC450100 CALL ~df394b.00885867
0087128B |. 50 PUSH EAX
0087128C |. E8 91000000 CALL ~df394b.00871322
00871291 |. 66:85C0 TEST AX,AX
00871294 |. 59 POP ECX ; ~df394b.00879A98
00871295 |. 0F84 81000000 JE ~df394b.0087131C
008712A1 |. 68 04010000 PUSH 104 ; /BufSize = 104 (260.)
008712A6 |. 50 PUSH EAX ; |PathBuffer = 00000001
008712A7 |. 56 PUSH ESI ; |hModule = 00000001
008712A8 |. FF15 B8118C00 CALL DWORD PTR DS:[<&KERNEL32.GetModuleF>; GetModuleFileNameA
008712AE |. 56 PUSH ESI ; /hTemplateFile = 00000001
008712AF |. 56 PUSH ESI ; |Attributes = READONLY
008712B0 |. 6A 03 PUSH 3 ; |Mode = OPEN_EXISTING
008712B2 |. 56 PUSH ESI ; |pSecurity = 00000001
008712B3 |. 6A 01 PUSH 1 ; |ShareMode = FILE_SHARE_READ
008712B5 |. 8D85 ECFEFFFF LEA EAX,DWORD PTR SS:[EBP-114] ; |
008712BB |. 68 00000080 PUSH 80000000 ; |Access = GENERIC_READ
008712C0 |. 50 PUSH EAX ; |FileName = 00000001 ???
008712C1 |. FF15 BC118C00 CALL DWORD PTR DS:[<&KERNEL32.CreateFile>; CreateFileA
008712C7 |. 83F8 FF CMP EAX,-1
008712CA |. 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
008712CD |. 75 08 JNZ SHORT ~df394b.008712D7
008712CF |. FF15 E0108C00 CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>; [GetLastError
008712D5 |. EB 45 JMP SHORT ~df394b.0087131C
008712D7 |> 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-10]
008712DA |. 6A 65 PUSH 65
008712DC |. 50 PUSH EAX
008712DD |. 8975 F4 MOV DWORD PTR SS:[EBP-C],ESI
008712E0 |. E8 8C490100 CALL ~df394b.00885C71
008712E5 |. 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-10] ; |
008712E8 |. 56 PUSH ESI ; |Arg2 = 00000001
008712E9 |. 50 PUSH EAX ; |Arg1 = 00000001
008712EA |. E8 B5450100 CALL ~df394b.008858A4 ; ~df394b.008858A4
008712EF |. 83C4 10 ADD ESP,10
008712F2 |. 66:85C0 TEST AX,AX
008712F5 |. 74 1C JE SHORT ~df394b.00871313
008712F7 |. 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-10]
008712FA |. 68 B0C18C00 PUSH ~df394b.008CC1B0 ; ASCII "stxt371"
008712FF |. 50 PUSH EAX
00871300 |. E8 46000000 CALL ~df394b.0087134B
00871305 |. 8BF0 MOV ESI,EAX
00871307 |. 8D45 F0 LEA EAX,DWORD PTR SS:[EBP-10]
0087130A |. 50 PUSH EAX
0087130B |. E8 BD460100 CALL ~df394b.008859CD
00871310 |. 83C4 0C ADD ESP,0C
00871313 |> FF75 F0 PUSH DWORD PTR SS:[EBP-10] ; /hObject = 00007B8E
00871316 |. FF15 EC108C00 CALL DWORD PTR DS:[<&KERNEL32.CloseHandl>; CloseHandle
0087131C |> 66:8BC6 MOV AX,SI
0087131F |. 5E POP ESI ; ~df394b.00879A98
00871320 |. C9 LEAVE
00871321 . C3 RETN

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.

[1] First reference to that procedure is from 008796CF:

008796B4 |. 64:A1 18000000 MOV EAX,DWORD PTR FS:[18]
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]
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.

[2] Second reference is at 00879789 and it is the same thing - a custom IsDebuggerPresent check.

[3] Third reference at 00879865 is ZwQueryInformationProcess check:

0087983E |. 6A 00 PUSH 0
00879840 |. 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+10]
00879844 |. 6A 04 PUSH 4
00879846 |. 51 PUSH ECX
00879847 |. 6A 07 PUSH 7 <----------------------------- Parameter for checking debugger presence!
00879849 |. FF15 B4108C00 CALL DWORD PTR DS:[<&KERNEL32.GetCurrentProcess>]
0087984F |. 50 PUSH EAX
00879850 |. FFD7 CALL EDI <--------------------------- ZwQueryInformationProcess call!
00879852 |. 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C]
00879856 |. 85C0 TEST EAX,EAX
00879858 |. 75 02 JNZ SHORT ~df394b.0087985C
0087985A |. 33F6 XOR ESI,ESI
0087985C |> 8BC6 MOV EAX,ESI
0087985E |. 5F POP EDI
0087985F |. 66:85F6 TEST SI,SI
00879862 |. 5E POP ESI
00879863 |. 74 10 JE SHORT ~df394b.00879875
00879865 |. E8 107AFFFF CALL ~df394b.0087127A
0087986A |. 66:F7D8 NEG AX
0087986D |. 1BC0 SBB EAX,EAX
0087986F |. 40 INC EAX
00879870 |. 66:85C0 TEST AX,AX
00879873 |. 75 12 JNZ SHORT ~df394b.00879887
00879875 |> 8B0B MOV ECX,DWORD PTR DS:[EBX]
00879877 |. 81E1 E3EEAB96 AND ECX,96ABEEE3
0087987D |. 890B MOV DWORD PTR DS:[EBX],ECX
0087987F |. 5B POP EBX
00879880 |. 81C4 98000000 ADD ESP,98
00879886 |. C3 RETN
00879887 |> 8B8C24 A000000>MOV ECX,DWORD PTR SS:[ESP+A0]
0087988E |. 5B POP EBX
0087988F |. 8121 524C6AA7 AND DWORD PTR DS:[ECX],A76A4C52
00879895 |. 81C4 98000000 ADD ESP,98
0087989B . C3 RETN

[4] 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:

1005A05E > 55 PUSH EBP <-------------------------------- Entry Point of protected dll.
1005A061 60 PUSHAD
1005A062 BB 5EA00510 MOV EBX,OFFSET AdobeLM.<ModuleEntryPoint>
1005A067 33C9 XOR ECX,ECX
1005A069 8A0D 3DA00510 MOV CL,BYTE PTR DS:[1005A03D]
1005A10B 58 POP EAX
1005A10C FF35 13A10510 PUSH DWORD PTR DS:[1005A113]
1005A112 C3 RETN
1005A113 72 16 JB SHORT AdobeLM.1005A12B
1005A115 61 POPAD
1005A116 1360 0D ADC ESP,DWORD PTR DS:[EAX+D]
1005A119 -E9 A6CEFCFF JMP AdobeLM.10026FC4 <-------------------- Jump to first section, OEP jump.
1005A11E CC INT3
1005A11F CC INT3

That jump is always there, we just needed to decrypt code.

4. Import protection

This is the hardest part. Most of imports are redirected to procedure that will find correct import. Let's see how imports look in unpacked dll.

First we have import call:

10026EF8 |. FF15 C0610310 CALL DWORD PTR DS:[100361C0]

That call points to allocated memory block:

00BBB117 68 8C11EABF PUSH BFEA118C
00BBB11F 68 57B1BB00 PUSH 0BBB157
00BBB124 E8 6C32D0FF CALL ~df394b.008BE395
00BBB129 83C4 08 ADD ESP,8
00BBB12C 6A 00 PUSH 0
00BBB12E 58 POP EAX ; AdobeLM.10026EFE
00BBB131 C3 RETN

Call in this block leads to procedure in one of temporary dlls and that procedure finds correct import and jumps to it with RETN:

008BE504 61 POPAD
008BE505 9D POPFD
008BE506 C3 RETN

I acctually tought that this will be the easiest part in SafeCast unpacking. But I was wrong.

- My failure

My first attempt (and second , and third , and ... untill I gone totaly mad), was to write simple script for finding right imports. And that is very easy since IAT is filled with pointers and imports:

thunk GDI32.DLL
10036034 00BC4A80
10036038 77F15E10 GDI32.CreateCompatibleDC
1003603C 00BC5406
10036040 00BC58C9
10036044 00BC5D8C
10036048 00BC624F
1003604C 00BC6712
10036050 00BC6BD5
10036054 00BC7098
10036058 00BC755B
1003605C 00000000
10036060 00BA0E0F
thunk KERNEL32.DLL
10036208 7C812BE6 kernel32.GetCPInfo
1003620C 00BC0B90
10036210 00BC1053
10036214 00000000
10036218 00BD04BF
thunk USER32.DLL
100362C0 00BDCCBD
100362C4 77D8050B USER32.MessageBoxA
100362C8 00000000
100362CC 00000000

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:

12EF6000 77DFD5BB ADVAPI32.RegCreateKeyA
12EF6018 77DD77B3 ADVAPI32.SetSecurityDescriptorDacl
12EF601C 00000000
12EF6020 5D093439 comctl_1.InitCommonControlsEx
12EF6024 00000000
12EF6028 77F16A3B GDI32.DeleteObject
12EF6058 77F159A0 GDI32.SelectObject
12EF605C 00000000
12EF6060 7C80EFD7 kernel32.FindClose
12EF6064 7C80180E kernel32.ReadFile
12EF6210 7C801E16 kernel32.TerminateProcess
12EF6214 00000000
12EF6218 77D4DB62 USER32.PostMessageA
12EF621C 77D4E2AE USER32.SendMessageA
12EF62C0 77D4C6BC USER32.RedrawWindow
12EF62C4 77D8050B USER32.MessageBoxA

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:

MOV REG, DWORD [IMPORT/POINTER] (reg=eax,ebx,ecx,edx,esi,edi,ebp)

Here is nasty trick (?!?) that also confused me:

12EC26D8 FF15 20600310 CALL DWORD PTR DS:[10036020]

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
12EEAFA5 . 6A 01 PUSH 1

Ah, crap >:( This jump points to section where I placed new IAT. Let's see what is it in original DLL:

12F17910 ? 53 PUSH EBX ; AdobeLM.12EC0000
12F17911 ? E8 B9100000 CALL AdobeLM.12F189CF


12F189CF /$ 870424 XCHG DWORD PTR SS:[ESP],EAX
12F189D2 |. 9C PUSHFD
12F189D3 |. 05 15040000 ADD EAX,415
12F189DA |. 6BDB 21 IMUL EBX,EBX,21 ; AdobeLM.12EC0000
12F189DD |. 0358 04 ADD EBX,DWORD PTR DS:[EAX+4]
12F189E0 |. 9D POPFD
12F189E1 |. 58 POP EAX ; 13298DDA
12F189E2 |. 871C24 XCHG DWORD PTR SS:[ESP],EBX ; AdobeLM.12EC0000
12F189E5 . C3 RETN


13298DDA 68 A1AFEE12 PUSH 12EEAFA1
13298DE4 9C PUSHFD
13298DE5 60 PUSHAD
13298DE6 54 PUSH ESP
13298DE7 68 1A8E2913 PUSH 13298E1A
13298DEC E8 A455CDFF CALL ~df394b.12F6E395
13298DF1 83C4 08 ADD ESP,8
13298DF4 6A 00 PUSH 0
13298DF6 58 POP EAX
13298DF7 61 POPAD
13298DF8 9D POPFD
13298DF9 C3 RETN

This RETN jumps to same import algo that ends with:

12F6E74D 61 POPAD
12F6E74F C3 RETN

Return to 7C809737 (kernel32.GetCurrentThreadId)

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:

12EC34E3 $ B8 7BEFFFFF MOV EAX,-1085
12EC34E8 . 59 POP ECX ; dump.12EC41D0
12EC34EE .-FFE0 JMP EAX <------------------------------ It jumps to nowhere.

In original file, that jump jumps to temporary DLL:

12F31507 . 58 59 68 00 ASCII "XYh",0
12F3150B 00 DB 00
12F3150C 40 DB 40 ; CHAR '@'
12F3150D 00 DB 00
12F3150E . 9C PUSHFD
12F3150F . 60 PUSHAD
12F31510 . 54 PUSH ESP
12F31511 . E8 D2FFFFFF CALL ~df394b.12F314E8
12F31516 . 5C POP ESP
12F31517 . 61 POPAD
12F31518 . 9D POPFD
12F31519 . C3 RETN

12F314F4 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ; ~df394b.12F727F4
12F314F7 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; ~df394b.12F8CD68
12F314FA |. 8B00 MOV EAX,DWORD PTR DS:[EAX] ; ~df394b.12F31277
12F314FC |. 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4] ; ~df394b.12F8CD68
12F314FF |. FF50 08 CALL DWORD PTR DS:[EAX+8] ; ~df394b.?PerformFixup@CJumpRun@@UAEKK@Z
12F31502 |. 8945 08 MOV DWORD PTR SS:[EBP+8],EAX ; ~df394b.12F727F4
12F31505 |. C9 LEAVE
12F31506 . C3 RETN

It countinues inside that DLL and enters in function which has interesting name: PerformFixup@CJumpRun@@UAEKK@Z. After that it returns to some location in main DLL.

That JMP EAX is accessed trugh this small procedure inside of main DLL:

12EC41C9 /$ 51 PUSH ECX
12EC41CA |. 50 PUSH EAX
12EC41CB |. E8 13F3FFFF CALL dump.12EC34E3
12EC41D0 |$ 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4] ; dump.12EC0000
12EC41D4 |. 56 PUSH ESI
12EC41D5 |. 8BF1 MOV ESI,ECX
12EC41DB |. E8 30F6FFFF CALL dump.12EC3810
12EC41E0 |. 8BC6 MOV EAX,ESI
12EC41E2 |. 5E POP ESI ; ntdll.7C9011A7
12EC41E3 . C2 0400 RETN 4

And that procedure is called from these places:

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:

12F3141C >/$ 55 PUSH EBP
12F3141D |. 8BEC MOV EBP,ESP
12F3141F |. 81EC D0020000 SUB ESP,2D0
12F31425 |. 53 PUSH EBX ; AdobeLM.<ModuleEntryPoint>
12F31426 |. 8BD9 MOV EBX,ECX
12F31428 |. 56 PUSH ESI
12F31429 |. 57 PUSH EDI
12F3142A |. 8D43 20 LEA EAX,DWORD PTR DS:[EBX+20]
12F3142D |. 50 PUSH EAX ; /pCriticalSection = 0012E68C
12F3142E |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ; |
12F31431 |. FF15 7010F712 CALL DWORD PTR DS:[<&KERNEL32.EnterCriti>; EnterCriticalSection
12F31437 |. 8D85 30FDFFFF LEA EAX,DWORD PTR SS:[EBP-2D0]
12F3143D |. 8BCB MOV ECX,EBX ; AdobeLM.<ModuleEntryPoint>
12F3143F |. 50 PUSH EAX
12F31440 |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; AdobeLM.<ModuleEntryPoint>
12F31443 |. E8 E5FEFFFF CALL ~df394b.12F3132D
12F31448 |. 8B85 E8FDFFFF MOV EAX,DWORD PTR SS:[EBP-218]
12F3144E |. B9 30CDF812 MOV ECX,~df394b.12F8CD30
12F31453 |. 8BF8 MOV EDI,EAX
12F31455 |. 2B43 04 SUB EAX,DWORD PTR DS:[EBX+4]
12F31458 |. 50 PUSH EAX ; /Arg1 = 0012E68C
12F31459 |. E8 5CEC0300 CALL ~df394b.12F700BA ; ~df394b.12F700BA
12F3145E |. 50 PUSH EAX
12F3145F |. E8 F1010000 CALL ~df394b.12F31655
12F31464 |. 8BC8 MOV ECX,EAX
12F31466 |. E8 FC010000 CALL ~df394b.12F31667
12F3146B |. 8BF0 MOV ESI,EAX
12F3146D |. 85F6 TEST ESI,ESI
12F3146F |. 74 3F JE SHORT ~df394b.12F314B0
12F31471 |. 66:837B 08 01 CMP WORD PTR DS:[EBX+8],1
12F31476 |. 75 3D JNZ SHORT ~df394b.12F314B5
12F31478 |. 8D85 30FDFFFF LEA EAX,DWORD PTR SS:[EBP-2D0]
12F3147E |. 8BCE MOV ECX,ESI
12F31480 |. 50 PUSH EAX
12F31481 |. E8 15560100 CALL ~df394b.12F46A9B
12F31486 |. 8BCB MOV ECX,EBX ; AdobeLM.<ModuleEntryPoint>
12F31488 |. E8 8AFEFFFF CALL ~df394b.12F31317
12F3148D |. 83F8 04 CMP EAX,4 <------------------------- Compares number of CALL exceutions.
12F31490 |. 72 14 JB SHORT ~df394b.12F314A6
12F31492 |. 8BCE MOV ECX,ESI
12F31494 |. E8 E8540100 CALL ~df394b.12F46981
12F31499 |. 83F8 04 CMP EAX,4
12F3149C |. 72 08 JB SHORT ~df394b.12F314A6
12F3149E |. 57 PUSH EDI ; /Arg1 = 00000001
12F3149F |. 8BCE MOV ECX,ESI ; |
12F314A1 |. E8 2C550100 CALL ~df394b.12F469D2 ; ~df394b.12F469D2
12F314A6 |> 56 PUSH ESI
12F314A7 |. 8BCB MOV ECX,EBX ; AdobeLM.<ModuleEntryPoint>
12F314A9 |. E8 33FEFFFF CALL ~df394b.12F312E1
12F314AE |. EB 05 JMP SHORT ~df394b.12F314B5
12F314B0 |> 66:8363 08 00 AND WORD PTR DS:[EBX+8],0
12F314B5 |> 81EC CC020000 SUB ESP,2CC
12F314BB |. B9 B3000000 MOV ECX,0B3
12F314C6 |. 8BFC MOV EDI,ESP
12F314CA |. 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8]
12F314CD |. 8BCB MOV ECX,EBX ; AdobeLM.<ModuleEntryPoint>
12F314CF |. 50 PUSH EAX
12F314D0 |. E8 DBFEFFFF CALL ~df394b.12F313B0
12F314D5 |. FF75 FC PUSH DWORD PTR SS:[EBP-4] ; /pCriticalSection = 002456B8
12F314D8 |. FF15 6C10F712 CALL DWORD PTR DS:[<&KERNEL32.LeaveCriti>; LeaveCriticalSection
12F314DE |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] ; AdobeLM.<ModuleEntryPoint>
12F314E1 |. 5F POP EDI ; ntdll.7C9011A7
12F314E2 |. 5E POP ESI ; ntdll.7C9011A7
12F314E3 |. 5B POP EBX ; ntdll.7C9011A7
12F314E4 |. C9 LEAVE
12F314E5 . C2 0400 RETN 4

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:

References in AdobeLM:.text to 12EC41C9
Address Disassembly Comment
12EC109B JE F34910A1
12EC1EE1 CALL AdobeLM.12EE558E
12EC2463 CALL AdobeLM.12EE5B40
12EC2F0B JNZ AdobeLM.12EC343F
12EC41C9 PUSH ECX (Initial CPU selection)
12EC41EB JMP AdobeLM.12EC41C9
12EC476B JE 12EB0571
12EC486E CALL AdobeLM.12EE5B40
12EC4AFA CALL AdobeLM.12EE6624
12EC4ED2 CALL AdobeLM.12EC22A0
12EC4FBB JNZ AdobeLM.12EC4E02
12EC5022 CALL AdobeLM.12EE5B40
12EC5301 CALL AdobeLM.12EE6624
12EC5643 CALL AdobeLM.12EC22A0
12EC57AB CALL AdobeLM.12ECFE40
12EC5BA6 CALL AdobeLM.12EC22A0
12EC5C61 CALL AdobeLM.12EC22A0
12EC6476 CALL AdobeLM.12ECDE40
12EC64E0 JMP AdobeLM.12EC6694
12EC6527 JMP AdobeLM.12EC6694
12EC657E JMP AdobeLM.12EC6694
12EC65C5 JMP AdobeLM.12EC6694
12EC6663 CALL AdobeLM.12ECDE40
12EC66AB JE AdobeLM.12EC6E63
12EC6776 CALL AdobeLM.12EC22A0
12EC67D6 CALL AdobeLM.12EC22A0
12EC6892 CALL AdobeLM.12EC22A0
12EC68E3 CALL AdobeLM.12EC22A0
12EC6978 CALL AdobeLM.12EC22A0
12EC69C7 CALL AdobeLM.12EC22A0
12EC6A6D CALL AdobeLM.12EC22A0
12EC6AA8 JE AdobeLM.12EC7126
12EC6C60 CALL AdobeLM.12EC4770
12EC6E80 JE AdobeLM.12EC6F4B
12EC6F67 CALL AdobeLM.12ED14A0
12EC7394 CALL AdobeLM.12EC22A0
12EC7459 CALL AdobeLM.12EC22A0
12EC7D60 JE AdobeLM.12EC81DE
12EC7F04 CALL AdobeLM.12ECDE40
12EC8007 CALL AdobeLM.12ECDE40
12EC80DA CALL AdobeLM.12ED0DA0
12EC83F7 CALL AdobeLM.12EC22A0
12EC88FF CALL AdobeLM.12EC4CD0
12EC8983 JMP AdobeLM.12EC8AF5
12EC89AE CALL AdobeLM.12ECDE40
12EC8AC4 CALL AdobeLM.12ECDE40
12EC8BE7 CALL AdobeLM.12EC22A0
12EC90F0 CALL AdobeLM.12EC5440
12EC9175 JMP AdobeLM.12EC92E7
12EC91A0 CALL AdobeLM.12ECDE40
12EC92B6 CALL AdobeLM.12ECDE40
12EC941C CALL AdobeLM.12EC22A0
12EC96C0 CALL AdobeLM.12EC22A0
12ECA12B CALL AdobeLM.12EC22A0
12ECA18C CALL AdobeLM.12EE6A30
12ECA77C CALL AdobeLM.12EC22A0
12ECA86F CALL AdobeLM.12EC22A0
12ECAB07 CALL AdobeLM.12ECDE40
12ECB23C CALL AdobeLM.12ECDE40
12ECB3A2 CALL AdobeLM.12EC22A0
12ECB3EB CALL AdobeLM.12EC22A0
12ECB571 CALL AdobeLM.12EC22A0
12ECBE10 CALL AdobeLM.12ECDE40
12ECBE5D JE AdobeLM.12ECC013
12ECBE79 JE AdobeLM.12ECBFF3
12ECBE87 JE AdobeLM.12ECBFF3
12ECC30B CALL AdobeLM.12ECDE40
12ECC423 CALL AdobeLM.12EC22A0
12ECC47D CALL AdobeLM.12EC22A0
12ED3C3B JE AdobeLM.12EE6F41
12ED439B JE AdobeLM.12F058A1
12ED441B JNZ 12E66A21

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..

7. Testing

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.

Photoshop first loads DLL:

00F4723B |. 50 PUSH EAX ; /FileName = "C:...AdobeLM.dll"
00F4723E |. FF15 74D51501 CALL DWORD PTR DS:[<&KERNEL32.LoadLibraryA>] ; LoadLibraryA

Then it calls these functions in DLL:

00F4728A |. FFD0 CALL EAX ; AdobeLM.CreateCAdobeLM_Object

00F472BF |. FF52 04 CALL DWORD PTR DS:[EDX+4] ; AdobeLM.InitOfAdobeLM

And while in this one, it shows error message:

00F46F47 |. FF50 08 CALL DWORD PTR DS:[EAX+8] ; AdobeLM.CheckSigOfAdobeLM

This one returns to EAX=FFFFFFF6. But if EAX=0, it loads second DLL protected with SC:

00F39D8C |> FF75 C8 PUSH DWORD PTR SS:[EBP-38] ; /FileName = "Tw10122.dat"
00F39D8F |. FF15 74D51501 CALL DWORD PTR DS:[<&KERNEL32.LoadLibraryA>] ; LoadLibraryA

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.

8. Summary

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.

Everything is here

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.

haggar 24.10.2006

What's Related

Story Options

Unpacking SafeCast 2.4 | 14 comments | Create New Account
The following comments are owned by whomever posted them. This site is not responsible for what they say.
Unpacking SafeCast 2.4
Authored by: Human on Wednesday, October 25 2006 @ 11:00 PM CEST
hi haggar why havent you used my experience with it? also you cant call it safecast(disc) due this will work with safecast only. adobe is app so safecasted. with safedisc you will encounter few more problems like:
1. script or olly will not reach oep due safedisc spawns ~e5*.exe that attaches to exe and debugs it, with olly it cant attach
2. you will not fix nanomites due it handles ~e5*.exe
3. you will encounter weak calls that are stolen opcode calls that shouldnt be never run, if you try to run any weak call all stolen opcode calls will be screwed
4. games from have 2 versions of safecast, one is picture buttons play or buy this is normal safecast, other is dialogbox based play or buy and this one attaches to exe like safedisc and you cant debug it in olly. my unpacker uses resume/suspend method so it works, but i have to fix it for 2nd type

also its better to resolve all at once like my script does, or unpacker, you have script and unpacker sources over net, also now with my rebuildIAT.
Unpacking SafeCast 2.4
Authored by: haggar on Thursday, October 26 2006 @ 09:06 PM CEST
Hi human. Did you wrote that tutorial for ARTEAM?

This was my first attempt on SafeCast. I sow that all versions (2.2 - 3.8 SafeDisc/Cast, checked on couple CD games) have indentical part with IsDebuggerPresent and ZwQuerySystem information. I do not have (original) protected CDs, so I couldn't spot debug blocker.

I would like to try those self-debugging and nanomites. do you know any (very small) target for download?

I noticed that trick with "false" stolen code calls. In my second attempt on unpacking DLL I fixed only calls that are executed, but it is too long to put it into tutorial. After all, it is for reader to do some work ;)

Btw, do you know how to reset trial on SafeCast? I would like to research it better.

See you around.

Unpacking SafeCast 2.4
Authored by: haggar on Friday, October 27 2006 @ 09:35 AM CEST
So I neet to install SoftICE? Buah. This time I will backup my hard drive first. Last time I had to reinstall Windows :(
Unpacking SafeCast 2.4
Authored by: haggar on Saturday, October 28 2006 @ 01:50 PM CEST
Yep , I'm working on SafeDisc v4 now. Indeed , there are no need for SI. Couple olly instances can solve everything.

Does SafeDisc has two kinds of nanomites?

I sow that it uses WriteProcessMemory to restore original code at CC bytes, but some bytes are not restored ?!? Does it emulate them?

Also, imports are like in SafeCast. I didn't spot any jumps to SC code and then access to import.

I was also searching for JMP EAX, it seams that it uses that feature to ?
Unpacking SafeCast 2.4
Authored by: haggar on Sunday, October 29 2006 @ 01:34 PM CET

In Need for Speed Undeground 2 - SafeDisc4 , nanomites are write after two times. But thanks for hint. I found where is check, then just patched jump. But still, nanomites are hard. I'm traying to find way how to exploit debug loop of SD to fore all decryption.
Unpacking SafeCast 2.4
Authored by: haggar on Sunday, October 29 2006 @ 05:43 PM CET
Protection ID 5.1f recognized it as 4, ofcourse it can be wrong. Btw, is there way to obtain correct version?

Debugging with Olly is not simple, but here it is how:

- You need windows XP.

- Open game in olly, break at CreateProcessA.

- In temp folder is temp executable (with random name) , open it in another olly, set EB FE at it's OEP. Save changes. Close that olly.

- Execute CreateProcessA, stop at end of API.

- Attach with new olly to new created process, restore original OEP bytes.

- In first olly, patch badboy procedure, then run it. WaitForSingleObject waits for something ;)

- Load third Olly, attach to first Olly, DebugActiveProcessStop to detach from game.

- You are left with one instance and you are debugging SafeDisc debugging loop in temporary DLL.

Pain in the ass to perform it every time, but it works perfect. You can play game within Olly.
 Copyright © 2022 BiW Reversing
 All trademarks and copyrights on this page are owned by their respective owners.
Powered By Geeklog 
Created this page in 0.89 seconds