C and C
Introduction
If you are bored of my tutorial on safedisc just go to Bonus section for laughing.
Today we are going to see safedisc version 2 (in this case i worked on v2.05.030).
Detection
No more *.icd file, the loader is now integrated into the main executable.
The signature is the same : "BoG_ *90.0&!! Yy>" followed by 3 unsigned integers : the version, subversion an revision number.
Anti Debug
In this case we are using ring 3 debugger, the tricks are the same than in safedisc version 1 :
- IsDebuggerPresent()
- PEB!IsDebugged
- ZwQueryInformation (ProcessInformationClass = ProcessDebugPort)
You can use Phant0m plugin or follow what i did for safedisc 1.
Find OEP
Like I said in "detection" section, the loader is now integrated in the main executable, so we must find real oep after safedisc decyphering stuff.
I don't know if it's a good way to find it, but i set an hardware breakpoint on GetVersion, and look around for finding where am i. (GetVersion is one of the first called api).
But after watching some disas when i opened my ra2.exe into OllyDbg :
0041C1FD > 55 PUSH EBP
0041C1FE 8BEC MOV EBP,ESP
0041C200 60 PUSHAD
0041C201 B8 7BC24100 MOV EAX,Ra2.0041C27B
0041C206 2D FDC14100 SUB EAX,OFFSET Ra2.<ModuleEntryPoint>
0041C20B 0305 7CC24100 ADD EAX,DWORD PTR DS:[41C27C]
0041C211 C705 FDC14100 E>MOV DWORD PTR DS:[<ModuleEntryPoint>],0E9
0041C21B A3 FEC14100 MOV DWORD PTR DS:[41C1FE],EAX
0041C220 68 C9C04100 PUSH Ra2.0041C0C9 ; ASCII "USER32.dll"
0041C225 68 BBC04100 PUSH Ra2.0041C0BB ; ASCII "KERNEL32.dll"
0041C22A 68 09C04100 PUSH Ra2.0041C009
0041C22F 68 9BC04100 PUSH <&KERNEL32.GetModuleHandleA>
0041C234 A0 21C04100 MOV AL,BYTE PTR DS:[41C021]
0041C239 3C 01 CMP AL,1
0041C23B 74 07 JE SHORT Ra2.0041C244
0041C23D B8 00000000 MOV EAX,0
0041C242 EB 03 JMP SHORT Ra2.0041C247
0041C244 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0041C247 50 PUSH EAX
0041C248 E8 33000000 CALL Ra2.0041C280
0041C24D 83C4 14 ADD ESP,14
0041C250 83F8 00 CMP EAX,0
0041C253 74 1C JE SHORT Ra2.0041C271
0041C255 C705 FDC14100 C>MOV DWORD PTR DS:[<ModuleEntryPoint>],0C2
0041C25F C705 FEC14100 0>MOV DWORD PTR DS:[41C1FE],0C
0041C269 50 PUSH EAX
0041C26A A1 ABC04100 MOV EAX,DWORD PTR DS:[<&KERNEL32.ExitProcess>]
0041C26F FFD0 CALL EAX
0041C271 61 POPAD
0041C272 5D POP EBP
0041C273 EB 06 JMP SHORT Ra2.0041C27B
0041C275 72 16 JB SHORT Ra2.0041C28D
0041C277 61 POPAD
0041C278 1360 0D ADC ESP,DWORD PTR DS:[EAX+D]
0041C27B - E9 FFB5FEFF JMP Ra2.0040787F
Look at 0x0041C271, popad (we restore all our registers), pop ebp, jmp to 0x0041C27B, and again a jump to ... OEP.
After setting an hbp at this address we are here :
0040787F 55 PUSH EBP
00407880 8BEC MOV EBP,ESP
00407882 6A FF PUSH -1
00407884 68 78234100 PUSH Ra2.00412378
00407889 68 E4C54000 PUSH Ra2.0040C5E4
Fix redirect call
In this version of safedisc 2, it's the same difficulty in my opinion, but it's take more time to write call fixer, because there are some funny anti dump tricks.
Let's see the call jsut after oep :

It looks like version 1, but they set up new "protection" for fixing it.
I will not get into detail but explain you what they did and how to defeat it.
01306CA7 68 B413EABF PUSH BFEA13B4
01306CAC 9C PUSHFD
01306CAD 60 PUSHAD
01306CAE 54 PUSH ESP
01306CAF 68 E76C3001 PUSH 1306CE7
01306CB4 E8 3729D10E CALL ~df394b.100195F0
01306CB9 83C4 08 ADD ESP,8
01306CBC 6A 00 PUSH 0
01306CBE 58 POP EAX
01306CBF 61 POPAD
01306CC0 9D POPFD
01306CC1 C3 RET
Routine are exactly the same than my previous post about version 1 subersion 41, they compute the addr and then ret to it.
BUT ! now they check on the stack from where you have called this routine, and if it's an unknow address, it will compute a random api address.
So when we will want to fix import, we will have to scan code section find 0xFF15 (call dword [rdata]) and push the addr + 6.
I will spare you from crash, because it's not the only protection ... after making a call fixer, i encoutered a second problem, you can have several call to the same offset to rdata section, and in function of where you called it it will compute different api address :
0040521E CALL DWORD PTR DS:[4110F4] ; Return to 7C91FE01 (ntdll.RtlGetLastWin32Error)
004078F3 CALL DWORD PTR DS:[4110F4] ; Return to 7C812FAD (kernel32.GetCommandLineA)
As you can these 2 calls call the same routine, but ret on different api.
So for our call fixer we will have to create a temporary kernel32 and user32 table, and fix each call dword ptr to call the good index :
- Make a temporary dword table size of nb api, fill with null at start
- Scan code section, compute address api with safedisc routine
- Hook return safedisc routine, and put address into a register
- If computed address is not known find place (null bytes) into temporary table, and fix destination of the call dword ptr with new index into the table.
- If computed adress is already known and index into the table didn't change, do nothing, else fix destination of the call dword ptr.
- When done juste memcpy new table
How to hook safedisc routine ?
100183DF FF15 44800310 CALL DWORD PTR DS:[<&KERNEL32.SetEvent>] ; kernel32.SetEvent
100183E5 /EB 07 JMP SHORT ~df394b.100183EE
100183E7 8BDB MOV EBX,EBX
100183E9 /70 06 JO SHORT ~df394b.100183F1
100183EB |90 NOP
100183EC /71 03 JNO SHORT ~df394b.100183F1
100183EE ^\EB F7 JMP SHORT ~df394b.100183E7
100183F1 8B65 0C MOV ESP,DWORD PTR SS:[EBP+C]
100183F4 61 POPAD
100183F5 9D POPFD
100183F6 C3 RET
We will remplace the JMP SHORT ~df394b.100183EE, to jump to our code (in my case i used a dll, why because dll injection FTW !, no it's a simply way to fuck this anti dump, and not assemble code in ollydbg each time, yes safedisc 2 call fixer is longer than safedisc 1).
So if you are following my tuts and code some shit, your dump will crash... we need to fix 3 more tricks.
The problem is mov REG, [rdata] ... call REG :
References in Ra2:.text to 00411084..00411087, item 3
Address=0040E719
Disassembly=MOV ESI,DWORD PTR DS:[411084]
Comment=DS:[00411084]=01302435
References in Ra2:.text to 004110A4..004110A7, item 0
Address=0040350E
Disassembly=MOV EBP,DWORD PTR DS:[4110A4]
Comment=DS:[004110A4]=01303E8D
References in Ra2:.text to 00411124..00411127, item 0
Address=0040310F
Disassembly=MOV EBX,DWORD PTR DS:[411124]
Comment=DS:[00411124]=0130A7ED
References in Ra2:.text to 00411130..00411133, item 0
Address=00402CF5
Disassembly=MOV EDI,DWORD PTR DS:[411130]
Comment=DS:[00411130]=0130B1CE
So we will need to fix mov edi, [rdata], ebp, ebx, edi, see my call fixer for explanation but same thing like before.
An another tricks is jmp to Stxt774 a section found inside the binary.
0040C488 - E9 98EB0000 JMP Ra2.0041B025
....
0041B025 53 PUSH EBX
0041B026 E8 00000000 CALL Ra2.0041B02B
0041B02B 870424 XCHG DWORD PTR SS:[ESP],EAX
0041B02E 9C PUSHFD
0041B02F 05 D5FFFFFF ADD EAX,-2B
0041B034 8B18 MOV EBX,DWORD PTR DS:[EAX]
0041B036 6BDB 01 IMUL EBX,EBX,1
0041B039 0358 04 ADD EBX,DWORD PTR DS:[EAX+4]
0041B03C 9D POPFD
0041B03D 58 POP EAX
0041B03E 871C24 XCHG DWORD PTR SS:[ESP],EBX
0041B041 C3 RET
This routine will just compute an addr to rdata section, and ret to it, so for fixing this we will have to replace jmp by call [rdata], but wait jmp addr it's only 5 bytes and call [rdata] is equals 6 bytes, but don't worry when we will ret from this routine we will ret at 0x0040C488 + 6 (in the example above), so we have enough place to fix it.
And the last tricks is similar than this one seen below, it is jmp [rdata], and we have enough place too for fixing it too.
Now you have all the pieces to understand my fix import dll :
.386
.model flat,stdcall
option casemap:none
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.const
; user32 addr rdata start
user32_rdata = 004111ACh
; kernel32 addr rdata start
kernel32_rdata = 0041105Ch
code_start = 00401000h
code_end = 00411000h
code_size = 00010000h
; Addr to patch for our hook
addr_to_patch = 100183E5h
start_rdata = 00411000h
size_rdata = 00003000h
stxt_section = 0041B000h
stxt_end = stxt_section + 00001000h
.data?
OldProtect dd ?
addrcall dd 9 dup (?)
nbwrite dd ?
addrapi dd ?
not_real dd ?
kernel32_table dw 051h dup (?,?,?,?)
user32_table dw 01Ah dup (?,?,?,?)
.code
LibMain proc parameter1:DWORD, parameter2:DWORD, parameter3:DWORD
pushad
pushfd
; Set full access for beeing able to fix jmp to call and rdata section
invoke VirtualProtect, addr_to_patch, 5, 40h, addr OldProtect
invoke VirtualProtect, code_start, code_size, 40h, addr OldProtect
invoke VirtualProtect, start_rdata, size_rdata, 40h, addr OldProtect
; Set hook
mov eax, addr_to_patch
mov byte ptr [eax], 0E9h
mov [eax + 1], Hook - addr_to_patch - 5
; Scan section text
SearchCall:
mov eax, code_start
Scantext:
inc eax
cmp eax, code_end
jae end_scan
cmp word ptr[eax], 015FFh ; call [rdata]
je call_type1
cmp word ptr[eax], 025FFh ; Jmp [rdata]
je call_type1
cmp word ptr[eax], 0358Bh ; MOV ESI, ...
je call_type1
cmp word ptr[eax], 02D8Bh ; MOV EBP, ...
je call_type1
cmp word ptr[eax], 01D8Bh ; MOV EBX, ...
je call_type1
cmp word ptr[eax], 03D8Bh ; MOV EDI, ...
je call_type1
cmp byte ptr[eax], 0E9h ; Jmp Stxt774
je jmp_type
jmp Scantext
end_scan:
; copy temporary table to original position
mov ecx, 051h
lea esi, kernel32_table
mov edi, kernel32_rdata
rep movsd
mov ecx, 01Ah
lea esi, user32_table
mov edi, user32_rdata
rep movsd
mov eax, 1
popfd
popad
retn
; Our hook function edx = addr of the resolved api
Hook:
mov [addrapi], edx
mov esp, dword ptr ss:[ebp + 0Ch]
popad
popfd
pop edi
pop edi
retn
; fix jump
jmp_type:
mov edx, [eax + 1]
lea edx, [edx + eax + 5]
.if edx >= stxt_section && edx <= stxt_end
push next_jmp
jmp edx
.endif
jmp Scantext
next_jmp:
xor ecx, ecx
mov ebx, [addrapi]
; is a jump to user32 or kernel32 rdata
.if ebx >= 07E000000h
lea edi, user32_table
mov esi, user32_rdata
.else
lea edi, kernel32_table
mov esi, kernel32_rdata
.endif
.while (dword ptr [edi + ecx * 4] != 0)
.if dword ptr [edi + ecx * 4] == ebx
mov word ptr[eax], 015FFh
mov edx, esi
lea edx, [edx + ecx * 4]
mov dword ptr [eax + 2], edx
jmp @F
.endif
inc ecx
.endw
mov dword ptr [edi + ecx * 4], ebx
mov word ptr[eax], 015FFh
mov edx, esi
lea edx, [edx + ecx * 4]
mov dword ptr [eax + 2], edx
@@:
jmp Scantext
; We will scan if the ptr to rdata section
; is in kernel32 table or go check if it is
; in user32 table
call_type1:
mov edx, [eax + 2]
mov ebx, kernel32_rdata
next_kernel32:
cmp dword ptr [ebx], 0
je user32
cmp ebx, edx
je is_kernel32
add ebx, 4
jmp next_kernel32
; We found it, let's fix this
is_kernel32:
push next_scan
mov ecx, eax
add ecx, 6
push ecx
jmp dword ptr [ebx]
next_scan:
xor ecx, ecx
mov ebx, [addrapi]
.while (dword ptr [kernel32_table + ecx * 4] != 0)
.if dword ptr [kernel32_table + ecx * 4] == ebx
mov edx, kernel32_rdata
lea edx, [edx + ecx * 4]
; fix index
mov dword ptr [eax + 2], edx
jmp @F
.endif
inc ecx
.endw
mov dword ptr [kernel32_table + ecx * 4], ebx
mov edx, kernel32_rdata
lea edx, [edx + ecx * 4]
mov dword ptr [eax + 2], edx
@@:
jmp Scantext
; Same thing like below but for user32
user32:
mov edx, [eax + 2]
mov ebx, user32_rdata
next_user32:
cmp dword ptr [ebx], 0
je Scantext
cmp ebx, edx
je is_user32
add ebx, 4
jmp next_user32
is_user32:
push next_scan_user32
mov ecx, eax
add ecx, 6
push ecx
jmp dword ptr [ebx]
next_scan_user32:
xor ecx, ecx
mov ebx, [addrapi]
.while (dword ptr [user32_table + ecx * 4] != 0)
.if dword ptr [user32_table + ecx * 4] == ebx
mov edx, user32_rdata
lea edx, [edx + ecx * 4]
mov dword ptr [eax + 2], edx
jmp @F
.endif
inc ecx
.endw
mov dword ptr [user32_table + ecx * 4], ebx
mov edx, user32_rdata
lea edx, [edx + ecx * 4]
mov dword ptr [eax + 2], edx
@@:
jmp Scantext
LibMain endp
end LibMain
And the make.bat :
@echo off
if exist "inject.obj" del "inject.obj"
if exist "inject.dll" del "inject.dll"
\masm32\bin\ml /c /coff "inject.asm"
if errorlevel 1 goto end
\masm32\bin\Link /SUBSYSTEM:WINDOWS /DLL "inject.obj"
if errorlevel 1 goto end
:end
pause
For injecting the dll, i used a olly plugin writtent by baboon, big thanks to him :]
Then after injecting dll, dump process, use importrec, and enjoy !
Bonus
And now ! the lulz part :]
In the folder of red alert 2, we can see ra2.exe (protected by safedisc) and game.exe (not protected), ra2.exe will simply launch game.exe. So removing safedisc was a long solution for breaking it.
But they used lame protection to watch if the process was launched by ra2.exe or not.
004916E4 |. 50 PUSH EAX ; /MutexName => "48BC11BD-C4D7-466b-8A31-C6ABBAD47B3E"
004916E5 |. 6A 00 PUSH 0 ; |InitialOwner = FALSE
004916E7 |. 6A 00 PUSH 0 ; |pSecurity = NULL
004916E9 |. FF15 30527800 CALL DWORD PTR DS:[<&KERNEL32.CreateMutexA>] ; \CreateMutexA
004916EF |. 8BF0 MOV ESI,EAX
004916F1 |. FF15 F0517800 CALL DWORD PTR DS:[<&KERNEL32.GetLastError>] ; [GetLastError
004916F7 |. 3D B7000000 CMP EAX,0B7
004916FC |. 0F94C3 SETE BL
004916FF |. 85F6 TEST ESI,ESI
00491701 |. 74 07 JE SHORT game2.0049170A
What !? They are just checking if a mutex has been created or not, ok let's nop this.
A second check is :
0049173A |. 68 20037C00 PUSH game2.007C0320 ; /EventName = "D6E7FC97-64F9-4d28-B52C-754EDF721C6F"
0049173F |. 6A 01 PUSH 1 ; |Inheritable = TRUE
00491741 |. 6A 02 PUSH 2 ; |Access = 2
00491743 |. FF15 28527800 CALL DWORD PTR DS:[<&KERNEL32.OpenEventA>] ; \OpenEventA
00491749 |. 8BF0 MOV ESI,EAX
0049174B |. 85F6 TEST ESI,ESI
0049174D |. 74 22 JE SHORT game2.00491771
Ok a check if OpenEventA worked or not let's nop it too.
Kind of triggers ?
But there is another problem if we fix game.exe, i started a skirmish party for lulz, and after 15 seconds of playing the computer (IA) leave the match and i am victorius. WTF ?!
Ok it's clear the launcher (ra2.exe) send message throw PostThreadMessage() and game.exe set a value if a received well this message :
00491791 |. 8BF1 MOV ESI,ECX
00491793 |. 817E 04 EFBE0>CMP DWORD PTR DS:[ESI+4],0BEEF
0049179A |. 75 4A JNZ SHORT game2.004917E6
0049179C |. 68 A4037C00 PUSH game2.007C03A4
004917A1 |. E8 4A51F7FF CALL game2.004068F0
004917A6 |. 8B46 0C MOV EAX,DWORD PTR DS:[ESI+C]
004917A9 |. 83C4 04 ADD ESP,4
004917AC |. 6A 00 PUSH 0 /BaseAddr = NULL
004917AE |. 6A 00 PUSH 0 |MapSize = 0
004917B0 |. 6A 00 PUSH 0 |OffsetLow = 0
004917B2 |. 6A 00 PUSH 0 |OffsetHigh = 0
004917B4 |. 68 1F000F00 PUSH 0F001F |AccessMode = F001F
004917B9 |. 50 PUSH EAX |hMapObject
004917BA |. FF15 24527800 CALL DWORD PTR DS:[<&KERNEL32.MapViewOfFileEx>] \MapViewOfFileEx
004917C0 |. 85C0 TEST EAX,EAX
004917C2 |. A3 4C9E8300 MOV DWORD PTR DS:[839E4C],EAX
This routine is called for checking the type of message in our case 0xBEEF, and then if the MapViewOfFileEx() is well done, it will set a value in 0x839E4C. By watching reference to this immediate constant, we can see this :
004917FA |. A1 4C9E8300 MOV EAX,DWORD PTR DS:[839E4C]
004917FF |. 83C4 04 ADD ESP,4
00491802 |. 85C0 TEST EAX,EAX
00491804 |. 75 03 JNZ SHORT game2.00491809
00491806 32C0 XOR AL,AL
00491808 |. C3 RET
Just replace xor al, al by mov al, 1 and (trigger?) is fix.
sokie wrote on 15/10/2011 :
Hi,basically i'm working on the same file as you,but i'm having problems with antidebug.
I explain to you what i do:
When i open file i go to FS address,go to FS+30 follow dword and set bit to 00
I have ollyAdvanced so i don't worry about isdebuggerpresent
Then i set BP on ZwQueryInformationProcess,when i break AND is called by my temp file,with 07 argument,i follow buffer in dump,and set value to 00 again.
But then when i try to execute,it says:Unload the debugger and try again :(
What am i missing?
nvm found it:
100016E5 85C9 TEST ECX,ECX
100016E7 74 07 JE SHORT 100016F0
100016E9 B8 00400000 MOV EAX,4000
100016EE EB 05 JMP SHORT 100016F5
100016F0 B8 00000100 MOV EAX,10000
at the end of routine it has to be 10000 in eax,for some reason it failed to be there,je to jmp and all fine
Sorry for the late answer, i wasn't at home this week-end.
Glad to see you solved your problem.
Have fun with safedisc protection :]
Finally something I can use - trying to created a patched version of C&C Generals ZH so I can play the game without CD. Obviously the exe is compressed, but now I see from the signatures I read here that it is probably safedisk security.
Great! Now I have something to do. Will study your pointers and report back later. I have the original game BTW all fair and legal. (sorry bout that lol).
T

