anonymous1381
13-05-2007, 22:20
Hey guys, I was fooling around with securom 7 for the past 2 days, and now I am kind of tired, kind of stumped, and have other Real Life things to attend to, so I thought I might pass this off to someone here and he could continue my work into gaining some insights about this protection.
Keep in mind that I am a self-acknowledged noob. A lot of the stuff I say here may or may not be correct. Please correct me if I am wrong, its always nice to learn something.
Before I forget, the target is Grand Theft Auto San Andreas 1.1
Oh, and one last thing before I start: I know that I can dump the heaps at the OEP, and fix the GetProcessId check, but there is no challenge in that, is there?
And one last thing. Pirates, fuck off. People who are going to use this to crack the game and distribute it illegally, fuck off. Protection developers, keep reading and pay attention :) Oh and if you want to learn something, keep reading. Needless to say, this is only for educational purposes, for fun, and for a way to pass time, not for any illegal shit.
Ok, so, let me get you up to date on what I have so far. First thing is to find the OEP. Pretty simple task with this protection. Grab your favorite debugger (for the purposes of this post, I used Olly), and start reading.
Securom 7 uses some pretty innovative AntiDebug, but nothing as vast as the Safedisc antidebug arsenal.
First thing it does is get the tick count with GetTickCount, and then it causes a gazillion exceptions. Since your debugger needs to process all these exceptions, it is naturally slowed down. After it causes all those exceptions, it uses GetTickCount to check if the time elapse was large. If it was -> Debugger detected.
To bypass this check you can either put a BP on GetTickCount(Securom doesn't check API's for 0xCC) and change EAX to 0 each time it is called, or just assemble a xor eax, eax; ret; at the beginning of the GetTickCount API
The second trick comes into play right after the large amount of exceptions. Its a simple call to NtQueryInformationProcess (also known as ZwQueryInformationProcess). To bypass it, I put a bp on ZwQueryInformationProcess, and kept running and looking at the top of the stack. This API gets called a lot by kernel32 and other standard dll's, but you will once see it getting called from a place with a much return lower address:
00212128 00EDFE7A /CALL to ZwQueryInformationProcess from gta_sa.00EDFE77
0021212C FFFFFFFF |hProcess = FFFFFFFF
00212130 00000007 |InfoClass = 7
00212134 0021217C |Buffer = 0021217C
00212138 00000004 |Bufsize = 4
0021213C 00000000 \pReqsize = NULL
First of all, note the return buffer(0021217C). Press CTRL+F9 to let olly run until the ret, and then look at the buffer. You will notice that the DWORD there is -1, which means Olly is present. Just change it to 0, and you're fine. I don't know exactly how it uses ZwQueryInformationProcess to detect Olly, but I saw a post on Woodmann concerning it, and I will check it out.
Now, before you run the target, put a Memory Breakpoint on the code section. Now run the target, you will break once or twice, and then finally hit the OEP:
00825330 6A 60 PUSH 60
00825332 68 B8908800 PUSH gta_sa.008890B8
00825337 E8 943C0000 CALL gta_sa.00828FD0
Now, the fun part about Securom is all the anti-dumps. They all revolve around a central routine that is heavily obfuscated and accesses the heaps, does CPUID to check if its running on the correct machine, checks PID of process to make sure its not a dump etc etc...
Now, I'll go through all the antidumps that I have found so far. The first one that is so blatantly easy to spot is all the JMP DWORD PTR's lying around. What securom does during the protection process is cut and paste some code from the code section, put it into a securom(or another location in the code) section, obfuscate it and do all kinds of other, nasty but cool things to it. After it redirects the code to a new spot, it puts a JMP DWORD PTR to the central securom routine at the place where the the original code was. The central securom routine then redirects it to the correct place.
Pretty simple, kind of like IAT redirection, except without the imports, eh? Lets take a look at one of the JMP DWORD PTR's:
0040104A $- FF25 58B04B01 JMP DWORD PTR DS:[14BB058]
00401050 8D604B01 DD gta_sa.014B608D
00401054 D1604B01 DD gta_sa.014B60D1
00401058 15614B01 DD gta_sa.014B6115
0040105C 59614B01 DD gta_sa.014B6159
Its takes us here:
0150DEB0 68 CADE5001 PUSH gta_sa.0150DECA
0150DEB5 68 8E624000 PUSH gta_sa.0040628E
0150DEBA 68 200BF000 PUSH gta_sa.00F00B20
0150DEBF 9C PUSHFD
0150DEC0 816C24 04 50540000 SUB DWORD PTR SS:[ESP+4],5450
0150DEC8 9D POPFD
0150DEC9 C3 RETN
And that takes us here to the central securom routine:
00EFB6D0 60 PUSHAD
00EFB6D1 9C PUSHFD
00EFB6D2 E8 00000000 CALL gta_sa.00EFB6D7
00EFB6D7 F0:FE0D 541C3901 LOCK DEC BYTE PTR DS:[1391C54]
Lovely. Now, I have traced through the securom routine a lot, and here is an interesting thing I noticed: It always exits with a POPFD, POPAD, RET xx. A serious weakness, makes scanning for all the exit points quite simple. The exit point for the EIP redirection can be found here:
00EFE92E 9D POPFD
00EFE92F 61 POPAD
00EFE930 C2 0400 RETN 4
The retn 4 takes you to the correct place. Now, before we begin thinking up some code to fix this, I would just like to point out an interesting feature of the central securom procedure. Please go back to the original JMP DWORD PTR we were looking at:
0040104A $ /FF25 58B04B01 JMP DWORD PTR DS:[14BB058] ; gta_sa.005EE7DA
00401050 |8D604B01 DD gta_sa.014B608D
00401054 |D1604B01 DD gta_sa.014B60D1
00401058 |15614B01 DD gta_sa.014B6115
How interesting. Its now pointing to the correct place. In the interest of speed, Securom overwrites on the fly pointers pointing to its own central routine with pointers to the correct place to go.
Another thing to note is that all JMP DWORD PTR's point to mini routines akin to these:
01510540 68 5A055101 PUSH gta_sa.0151055A
01510545 68 6D7B4000 PUSH gta_sa.00407B6D
0151054A 68 D897F100 PUSH gta_sa.00F197D8
0151054F 9C PUSHFD
01510550 816C24 04 08E10100 SUB DWORD PTR SS:[ESP+4],1E108
01510558 9D POPFD
01510559 C3 RETN
and
0150A360 68 6AA35001 PUSH gta_sa.0150A36A
0150A365 E8 66139FFF CALL gta_sa.00EFB6D0
...all of which lead to the central securom routine. It makes life easier, telling apart Securom JMP DWORD PTR's from legit JMP DWORD PTR's inside the game.
I coded something on the fly to fix it, no comments since I wasn't expecting anyone to use it at the time. MASM to assemble etc etc...
start:
_text equ 00401000h
_textSize equ 00457000h
_securomData equ 01493000h
_securomEnd equ 020DFFF8h
_central equ 00EFB6D0h
mov eax, _text
_find_jmp_dword_loop:
cmp word ptr[eax], 025FFh
je _jmp_dword_found
_find_jmp_dword_loop_ok:
inc eax
cmp eax, (_text+_textSize)
jl _find_jmp_dword_loop
int 3
jmp _part_two
_jmp_dword_found:
mov ebx, dword ptr[eax+2]
cmp ebx, _securomData
jl _find_jmp_dword_loop_ok
cmp ebx, _securomEnd
ja _find_jmp_dword_loop_ok
mov ebx, dword ptr[ebx]
cmp byte ptr[ebx], 068h
jne _check_type2
cmp byte ptr[ebx+5], 068h
jne _check_type2
cmp byte ptr[ebx+0Ah], 068h
jne _check_type2
cmp byte ptr[ebx+0Fh], 09Ch
jne _check_type2
jmp _all_clear
_check_type2:
cmp byte ptr[ebx], 068h
jne _uknown
cmp byte ptr[ebx+5], 0E8h
jne _uknown
mov edx, dword ptr[ebx+6]
add edx, ebx
add edx, 0Ah
cmp edx, _central
jne _uknown
_all_clear:
jmp ebx
_uknown:
int 3
nop
nop
nop
_retPoint:
popfd
popad
add esp, 8
jmp _find_jmp_dword_loop_ok
Assemble this and inject it someplace, I always love the 00's under the header, but you can use VirtualAlloc also... Then replace:
00EFE92E 9D POPFD
00EFE92F 61 POPAD
00EFE930 C2 0400 RETN 4
with a JMP to wherever _retPoint is for you. Run the code (remember to untick INT3 in the exceptions part of Olly's debug options) and when it breaks at the INT 3, all the JMP DWORD PTR's in the code section should be resolved for you. Nice to see the first antidump gone.
The second antidump I noticed in passing. Its your pretty damn standard on-the-fly code decryption. Take a look:
004012E9 .- FF25 2CB14B01 JMP DWORD PTR DS:[14BB12C]
the above jump will lead you to:
014C4459 E8 D2C8A1FF CALL cracked1.00EE0D30
014C445E 51 PUSH ECX
014C445F 010F ADD DWORD PTR DS:[EDI],ECX
Now, put a Hardware BP on 014C4459, and run it, and you will see the code decrypt. Pretty cool, huh?
014C4459 55 PUSH EBP
014C445A FF0D 490B5101 DEC DWORD PTR DS:[1510B49]
014C4460 - 0F84 401711FF JE cracked1.005D5BA6
014C4466 8BEC MOV EBP,ESP
Now, the thing with this protection was when I first saw it, I didn't really have any way on the top of my mind on how to deal with it, so I coded a little something to push onto the stack the locations of all these encrypted code segments, and then I thought I would do them by hand. Heres the code:
_part_two:
mov eax, _text
mov esi, esp
_find_jmp_dword_loop_two:
cmp word ptr[eax], 025FFh
je _jmp_dword_found_two
_find_jmp_dword_loop_ok_two:
inc eax
cmp eax, (_text+_textSize)
jl _find_jmp_dword_loop_two
int 3
jmp _part_three
_jmp_dword_found_two:
mov ebx, dword ptr[eax+2]
cmp ebx, _securomData
jl _find_jmp_dword_loop_ok_two
cmp ebx, _securomEnd
ja _find_jmp_dword_loop_ok_two
mov ebx, dword ptr[ebx]
cmp byte ptr[ebx], 0E8h
jne _find_jmp_dword_loop_ok_two
push ebx
jmp _find_jmp_dword_loop_ok_two
Uhh, ugly code, hurts your eyes even if you never formally learned assembler. But, its gets the job done, that being that it puts on the stack a list containing the offsets of all the encrypted code segments. Of course, I got bored of doing it manually after the 4th routine, so I sat down and started thinking and tracing a little harder, and after a half hour came up with a valid automated solution. You see, the central securom routine always returns to the decrypted code from here:
01391D20 58 POP EAX
01391D21 8BBB 20030000 MOV EDI,DWORD PTR DS:[EBX+320]
01391D27 81C7 04000000 ADD EDI,4
01391D2D 8A47 01 MOV AL,BYTE PTR DS:[EDI+1]
01391D30 8607 XCHG BYTE PTR DS:[EDI],AL
01391D32 81C3 24000000 ADD EBX,24
01391D38 C703 00000000 MOV DWORD PTR DS:[EBX],0
01391D3E 9D POPFD
01391D3F 61 POPAD
However, it always writes that code back right before it jumps to it, so if you modify it in any way, it won't have any effect. Its hard to explain, so just assemble a EB FE in the above code sequence if you have time and watch what happens.
The trick however(there's always a trick) is that it always returns to the above code from this code:
00F00AD5 0FA3FD BT EBP,EDI
00F00AD8 8107 04000000 ADD DWORD PTR DS:[EDI],4
00F00ADE C3 RETN
and that is never overwritten, hence a nice place to redirect to our own injected code. Look at that nice fat 6 byte instruction at 00F00AD8. Man do I have an overpowering urge to overwrite it. Lets do just that. First, inject the following code where-ever you feel like it:
_part_three:
xchg eax, eax
_restore_encrypted_loop:
pop ebx
jmp ebx
_ret_point_three:
nop
nop
nop
add dword ptr[edi], 4
add esp, 4
pop eax
mov edi, dword ptr[ebx+0320h]
add edi, 4
mov al, byte ptr[edi+1]
xchg byte ptr[edi], al
add ebx, 024h
mov dword ptr[ebx], 0
popfd
popad
add esp, 0Ch
cmp esp, esi
jne _restore_encrypted_loop
int 3
Note: I'm assuming that the stack is full of the offsets to the encrypted routines, that was pushed on by the previous code snippet I have you.
Now, replace:
00F00AD8 8107 04000000 ADD DWORD PTR DS:[EDI],4
With a jump to _ret_point_three. Run the code, when it breaks at the int 3 you will have lot of decrypted routines and a cleaner stack, thank god.
Ok, with that done, take a look at the next antidump:
014E72F6 68 2F134000 PUSH fixed_1.0040132F
014E72FB E8 D043A1FF CALL fixed_1.00EFB6D0
extremely simple stuff. Securom replaced CALL xxxxxxxx with push _securomdata; call _centralroutine. Then all it does is fix the EIP to the correct routine. Pretty boring, there is only one catch, and that is if you are scanning for it, a lot of other antidumps are doing CALL fixed_1.00EFB6D0, so you need to take them into account also. Here is some code that I used to fix it, its ugly, but if you want to learn something try to understand it:
mov eax, _securomData
_find_mid_jumps_loop:
cmp byte ptr[eax], 068h
je _mid_jumps_check1
_mid_jumps_ok:
inc eax
cmp eax, _securomEnd
jle _find_mid_jumps_loop
int 3
_mid_jumps_check1:
cmp byte ptr[eax+5], 0E8h
jne _mid_jumps_ok
mov ebx, dword ptr[eax+6]
add ebx, eax
add ebx, 0Ah
cmp ebx, _central
jne _mid_jumps_ok
pushad
jmp eax
nop
nop
nop
_good_return:
mov dword ptr[ebx], 0
popfd
popad
add esp, 8
popad
sub esp, 028h
pop edx
pop esi
add esp, 020h
mov dword ptr[eax], 090909090h
add eax, 4
mov byte ptr[eax], 090h
inc eax
mov byte ptr[eax], 0E8h
mov ebx, eax
sub ebx, edx
add ebx, 5
imul ebx, ebx, -1
mov dword ptr[eax+1], ebx
jmp _mid_jumps_ok
int 3
int 3
int 3
int 3
;type 2 & type 6 exit
_ignore_return:
popfd
popad
add esp, 8
popad
jmp _mid_jumps_ok
To get it to run, you need to replace the exit point of the central securom routine for EIP redirection:
00EFEC7B C703 00000000 MOV DWORD PTR DS:[EBX],0
00EFEC81 9D POPFD
00EFEC82 61 POPAD
00EFEC83 C3 RETN
with a jump to _good_return, and you need to replace the exit point's of the other antidumps (JMP DWORD redirection and instruction emulation) with JMPs to _ignore_return:
JMP dword exit point:
00EFE92E 9D POPFD
00EFE92F 61 POPAD
00EFE930 C2 0400 RETN 4
and instruction emulation exit:
00F00461 9D POPFD
00F00462 61 POPAD
00F00463 C2 0400 RETN 4
Then, run the inject code, it will fix up all the redirected CALLs.
Now, this is the point I have halted at. I don't have the time for the next month or so, so lets see if anyone can continue in my stead. There are two other types of redirections I have noticed. The first (easy) is the import redirection:
006029F4 . 9C PUSHFD
006029F5 . FF0D 20105101 DEC DWORD PTR DS:[1511020]
006029FB .^ 0F84 9197FFFF JE cracked1.005FC192
00602A01 > B8 743F21FE MOV EAX,FE213F74
00602A06 . 35 A489CEFE XOR EAX,FECE89A4
00602A0B . 9D POPFD
00602A0C . FFD0 CALL EAX
014E1ED6 9C PUSHFD
014E1ED7 817424 04 7A2CECD1 XOR DWORD PTR SS:[ESP+4],D1EC2C7A
014E1EDF 9D POPFD
014E1EE0 58 POP EAX
014E1EE1 FFD0 CALL EAX
[/code]
005D1A0B . 9C PUSHFD
005D1A0C . 817424 08 6996F12C XOR DWORD PTR SS:[ESP+8],2CF19669
005D1A14 . 9D POPFD
005D1A15 . 58 POP EAX
005D1A16 . 870424 XCHG DWORD PTR SS:[ESP],EAX
005D1A19 . EB FF JMP SHORT cracked1.005D1A1A
005D1A1B D0 DB D0
[/code]
As you can see, its got a slightly different byte pattern each time, which theoretically makes scanning harder. So, I have decided to blatantly steal Hoodlum's solution to this problem.
You will notice that all the import redirections push a pointer to the beginning of the text section right before they go to the Securom routine. These are pointers to the table of DWORDs at the beginning of the code section(you were probably wondering what they are for, now you know):
00401050 8D604B01 DD cracked1.014B608D
00401054 D1604B01 DD cracked1.014B60D1
00401058 15614B01 DD cracked1.014B6115
0040105C 59614B01 DD cracked1.014B6159
00401060 9D614B01 DD cracked1.014B619D
00401064 E1614B01 DD cracked1.014B61E1
00401068 25624B01 DD cracked1.014B6225
0040106C 69624B01 DD cracked1.014B6269
00401070 AD624B01 DD cracked1.014B62AD
00401074 F1624B01 DD cracked1.014B62F1
00401078 35634B01 DD cracked1.014B6335
0040107C 79634B01 DD cracked1.014B6379
00401080 BD634B01 DD cracked1.014B63BD
00401084 01644B01 DD cracked1.014B6401
00401088 45644B01 DD cracked1.014B6445
Simple brute force. Push each one, call the securom routine, get the correct import from the hooked exit point, replace the DWORD in the table with the pointer to the correct import in the IAT, and when you're finished, recode the securom routine to take the pointer to .text, retrieve the import from it, and then jmp to the import. Pretty simple, but hard to explain. I recommend you give the Hoodlum crack for Grand Theft Auto San Andreas 1.0 a quick glance to see what I mean.
Now, I'm not going to give you the code for the above, as I have yet to do it, still, its pretty simple, you shouldn't have any troubles. If that doesn't work then you can always scan for CALL EAX's perceded with PUSHFD's and PUSH xxxxxx's that push pointers to the beginning of .text. Do whatever you feel like doing.
The last protection has me kind of stumped, and in my opinion is what makes securom so sexy. It replaces single instructions with PUSH/CALLs to the central routine, where they are emulated. I've already figured out that it only emulates certain instructions, and I have a theoretical way of ID'ing each instruction, but its not anything solid enough to warrant me giving you details about. So, this is pretty much what I'm asking you to figure out.
Well, thats it for me. I know a lot of you have better things to do, but this post might help some noobies start on this protection, and the way you guys will criticise me will probably help me improve in some way. I'm pretty sure I'll figure out the single instruction emulation eventually, maybe I'll even update this post when I do. Until then,
you all take care :)
Keep in mind that I am a self-acknowledged noob. A lot of the stuff I say here may or may not be correct. Please correct me if I am wrong, its always nice to learn something.
Before I forget, the target is Grand Theft Auto San Andreas 1.1
Oh, and one last thing before I start: I know that I can dump the heaps at the OEP, and fix the GetProcessId check, but there is no challenge in that, is there?
And one last thing. Pirates, fuck off. People who are going to use this to crack the game and distribute it illegally, fuck off. Protection developers, keep reading and pay attention :) Oh and if you want to learn something, keep reading. Needless to say, this is only for educational purposes, for fun, and for a way to pass time, not for any illegal shit.
Ok, so, let me get you up to date on what I have so far. First thing is to find the OEP. Pretty simple task with this protection. Grab your favorite debugger (for the purposes of this post, I used Olly), and start reading.
Securom 7 uses some pretty innovative AntiDebug, but nothing as vast as the Safedisc antidebug arsenal.
First thing it does is get the tick count with GetTickCount, and then it causes a gazillion exceptions. Since your debugger needs to process all these exceptions, it is naturally slowed down. After it causes all those exceptions, it uses GetTickCount to check if the time elapse was large. If it was -> Debugger detected.
To bypass this check you can either put a BP on GetTickCount(Securom doesn't check API's for 0xCC) and change EAX to 0 each time it is called, or just assemble a xor eax, eax; ret; at the beginning of the GetTickCount API
The second trick comes into play right after the large amount of exceptions. Its a simple call to NtQueryInformationProcess (also known as ZwQueryInformationProcess). To bypass it, I put a bp on ZwQueryInformationProcess, and kept running and looking at the top of the stack. This API gets called a lot by kernel32 and other standard dll's, but you will once see it getting called from a place with a much return lower address:
00212128 00EDFE7A /CALL to ZwQueryInformationProcess from gta_sa.00EDFE77
0021212C FFFFFFFF |hProcess = FFFFFFFF
00212130 00000007 |InfoClass = 7
00212134 0021217C |Buffer = 0021217C
00212138 00000004 |Bufsize = 4
0021213C 00000000 \pReqsize = NULL
First of all, note the return buffer(0021217C). Press CTRL+F9 to let olly run until the ret, and then look at the buffer. You will notice that the DWORD there is -1, which means Olly is present. Just change it to 0, and you're fine. I don't know exactly how it uses ZwQueryInformationProcess to detect Olly, but I saw a post on Woodmann concerning it, and I will check it out.
Now, before you run the target, put a Memory Breakpoint on the code section. Now run the target, you will break once or twice, and then finally hit the OEP:
00825330 6A 60 PUSH 60
00825332 68 B8908800 PUSH gta_sa.008890B8
00825337 E8 943C0000 CALL gta_sa.00828FD0
Now, the fun part about Securom is all the anti-dumps. They all revolve around a central routine that is heavily obfuscated and accesses the heaps, does CPUID to check if its running on the correct machine, checks PID of process to make sure its not a dump etc etc...
Now, I'll go through all the antidumps that I have found so far. The first one that is so blatantly easy to spot is all the JMP DWORD PTR's lying around. What securom does during the protection process is cut and paste some code from the code section, put it into a securom(or another location in the code) section, obfuscate it and do all kinds of other, nasty but cool things to it. After it redirects the code to a new spot, it puts a JMP DWORD PTR to the central securom routine at the place where the the original code was. The central securom routine then redirects it to the correct place.
Pretty simple, kind of like IAT redirection, except without the imports, eh? Lets take a look at one of the JMP DWORD PTR's:
0040104A $- FF25 58B04B01 JMP DWORD PTR DS:[14BB058]
00401050 8D604B01 DD gta_sa.014B608D
00401054 D1604B01 DD gta_sa.014B60D1
00401058 15614B01 DD gta_sa.014B6115
0040105C 59614B01 DD gta_sa.014B6159
Its takes us here:
0150DEB0 68 CADE5001 PUSH gta_sa.0150DECA
0150DEB5 68 8E624000 PUSH gta_sa.0040628E
0150DEBA 68 200BF000 PUSH gta_sa.00F00B20
0150DEBF 9C PUSHFD
0150DEC0 816C24 04 50540000 SUB DWORD PTR SS:[ESP+4],5450
0150DEC8 9D POPFD
0150DEC9 C3 RETN
And that takes us here to the central securom routine:
00EFB6D0 60 PUSHAD
00EFB6D1 9C PUSHFD
00EFB6D2 E8 00000000 CALL gta_sa.00EFB6D7
00EFB6D7 F0:FE0D 541C3901 LOCK DEC BYTE PTR DS:[1391C54]
Lovely. Now, I have traced through the securom routine a lot, and here is an interesting thing I noticed: It always exits with a POPFD, POPAD, RET xx. A serious weakness, makes scanning for all the exit points quite simple. The exit point for the EIP redirection can be found here:
00EFE92E 9D POPFD
00EFE92F 61 POPAD
00EFE930 C2 0400 RETN 4
The retn 4 takes you to the correct place. Now, before we begin thinking up some code to fix this, I would just like to point out an interesting feature of the central securom procedure. Please go back to the original JMP DWORD PTR we were looking at:
0040104A $ /FF25 58B04B01 JMP DWORD PTR DS:[14BB058] ; gta_sa.005EE7DA
00401050 |8D604B01 DD gta_sa.014B608D
00401054 |D1604B01 DD gta_sa.014B60D1
00401058 |15614B01 DD gta_sa.014B6115
How interesting. Its now pointing to the correct place. In the interest of speed, Securom overwrites on the fly pointers pointing to its own central routine with pointers to the correct place to go.
Another thing to note is that all JMP DWORD PTR's point to mini routines akin to these:
01510540 68 5A055101 PUSH gta_sa.0151055A
01510545 68 6D7B4000 PUSH gta_sa.00407B6D
0151054A 68 D897F100 PUSH gta_sa.00F197D8
0151054F 9C PUSHFD
01510550 816C24 04 08E10100 SUB DWORD PTR SS:[ESP+4],1E108
01510558 9D POPFD
01510559 C3 RETN
and
0150A360 68 6AA35001 PUSH gta_sa.0150A36A
0150A365 E8 66139FFF CALL gta_sa.00EFB6D0
...all of which lead to the central securom routine. It makes life easier, telling apart Securom JMP DWORD PTR's from legit JMP DWORD PTR's inside the game.
I coded something on the fly to fix it, no comments since I wasn't expecting anyone to use it at the time. MASM to assemble etc etc...
start:
_text equ 00401000h
_textSize equ 00457000h
_securomData equ 01493000h
_securomEnd equ 020DFFF8h
_central equ 00EFB6D0h
mov eax, _text
_find_jmp_dword_loop:
cmp word ptr[eax], 025FFh
je _jmp_dword_found
_find_jmp_dword_loop_ok:
inc eax
cmp eax, (_text+_textSize)
jl _find_jmp_dword_loop
int 3
jmp _part_two
_jmp_dword_found:
mov ebx, dword ptr[eax+2]
cmp ebx, _securomData
jl _find_jmp_dword_loop_ok
cmp ebx, _securomEnd
ja _find_jmp_dword_loop_ok
mov ebx, dword ptr[ebx]
cmp byte ptr[ebx], 068h
jne _check_type2
cmp byte ptr[ebx+5], 068h
jne _check_type2
cmp byte ptr[ebx+0Ah], 068h
jne _check_type2
cmp byte ptr[ebx+0Fh], 09Ch
jne _check_type2
jmp _all_clear
_check_type2:
cmp byte ptr[ebx], 068h
jne _uknown
cmp byte ptr[ebx+5], 0E8h
jne _uknown
mov edx, dword ptr[ebx+6]
add edx, ebx
add edx, 0Ah
cmp edx, _central
jne _uknown
_all_clear:
jmp ebx
_uknown:
int 3
nop
nop
nop
_retPoint:
popfd
popad
add esp, 8
jmp _find_jmp_dword_loop_ok
Assemble this and inject it someplace, I always love the 00's under the header, but you can use VirtualAlloc also... Then replace:
00EFE92E 9D POPFD
00EFE92F 61 POPAD
00EFE930 C2 0400 RETN 4
with a JMP to wherever _retPoint is for you. Run the code (remember to untick INT3 in the exceptions part of Olly's debug options) and when it breaks at the INT 3, all the JMP DWORD PTR's in the code section should be resolved for you. Nice to see the first antidump gone.
The second antidump I noticed in passing. Its your pretty damn standard on-the-fly code decryption. Take a look:
004012E9 .- FF25 2CB14B01 JMP DWORD PTR DS:[14BB12C]
the above jump will lead you to:
014C4459 E8 D2C8A1FF CALL cracked1.00EE0D30
014C445E 51 PUSH ECX
014C445F 010F ADD DWORD PTR DS:[EDI],ECX
Now, put a Hardware BP on 014C4459, and run it, and you will see the code decrypt. Pretty cool, huh?
014C4459 55 PUSH EBP
014C445A FF0D 490B5101 DEC DWORD PTR DS:[1510B49]
014C4460 - 0F84 401711FF JE cracked1.005D5BA6
014C4466 8BEC MOV EBP,ESP
Now, the thing with this protection was when I first saw it, I didn't really have any way on the top of my mind on how to deal with it, so I coded a little something to push onto the stack the locations of all these encrypted code segments, and then I thought I would do them by hand. Heres the code:
_part_two:
mov eax, _text
mov esi, esp
_find_jmp_dword_loop_two:
cmp word ptr[eax], 025FFh
je _jmp_dword_found_two
_find_jmp_dword_loop_ok_two:
inc eax
cmp eax, (_text+_textSize)
jl _find_jmp_dword_loop_two
int 3
jmp _part_three
_jmp_dword_found_two:
mov ebx, dword ptr[eax+2]
cmp ebx, _securomData
jl _find_jmp_dword_loop_ok_two
cmp ebx, _securomEnd
ja _find_jmp_dword_loop_ok_two
mov ebx, dword ptr[ebx]
cmp byte ptr[ebx], 0E8h
jne _find_jmp_dword_loop_ok_two
push ebx
jmp _find_jmp_dword_loop_ok_two
Uhh, ugly code, hurts your eyes even if you never formally learned assembler. But, its gets the job done, that being that it puts on the stack a list containing the offsets of all the encrypted code segments. Of course, I got bored of doing it manually after the 4th routine, so I sat down and started thinking and tracing a little harder, and after a half hour came up with a valid automated solution. You see, the central securom routine always returns to the decrypted code from here:
01391D20 58 POP EAX
01391D21 8BBB 20030000 MOV EDI,DWORD PTR DS:[EBX+320]
01391D27 81C7 04000000 ADD EDI,4
01391D2D 8A47 01 MOV AL,BYTE PTR DS:[EDI+1]
01391D30 8607 XCHG BYTE PTR DS:[EDI],AL
01391D32 81C3 24000000 ADD EBX,24
01391D38 C703 00000000 MOV DWORD PTR DS:[EBX],0
01391D3E 9D POPFD
01391D3F 61 POPAD
However, it always writes that code back right before it jumps to it, so if you modify it in any way, it won't have any effect. Its hard to explain, so just assemble a EB FE in the above code sequence if you have time and watch what happens.
The trick however(there's always a trick) is that it always returns to the above code from this code:
00F00AD5 0FA3FD BT EBP,EDI
00F00AD8 8107 04000000 ADD DWORD PTR DS:[EDI],4
00F00ADE C3 RETN
and that is never overwritten, hence a nice place to redirect to our own injected code. Look at that nice fat 6 byte instruction at 00F00AD8. Man do I have an overpowering urge to overwrite it. Lets do just that. First, inject the following code where-ever you feel like it:
_part_three:
xchg eax, eax
_restore_encrypted_loop:
pop ebx
jmp ebx
_ret_point_three:
nop
nop
nop
add dword ptr[edi], 4
add esp, 4
pop eax
mov edi, dword ptr[ebx+0320h]
add edi, 4
mov al, byte ptr[edi+1]
xchg byte ptr[edi], al
add ebx, 024h
mov dword ptr[ebx], 0
popfd
popad
add esp, 0Ch
cmp esp, esi
jne _restore_encrypted_loop
int 3
Note: I'm assuming that the stack is full of the offsets to the encrypted routines, that was pushed on by the previous code snippet I have you.
Now, replace:
00F00AD8 8107 04000000 ADD DWORD PTR DS:[EDI],4
With a jump to _ret_point_three. Run the code, when it breaks at the int 3 you will have lot of decrypted routines and a cleaner stack, thank god.
Ok, with that done, take a look at the next antidump:
014E72F6 68 2F134000 PUSH fixed_1.0040132F
014E72FB E8 D043A1FF CALL fixed_1.00EFB6D0
extremely simple stuff. Securom replaced CALL xxxxxxxx with push _securomdata; call _centralroutine. Then all it does is fix the EIP to the correct routine. Pretty boring, there is only one catch, and that is if you are scanning for it, a lot of other antidumps are doing CALL fixed_1.00EFB6D0, so you need to take them into account also. Here is some code that I used to fix it, its ugly, but if you want to learn something try to understand it:
mov eax, _securomData
_find_mid_jumps_loop:
cmp byte ptr[eax], 068h
je _mid_jumps_check1
_mid_jumps_ok:
inc eax
cmp eax, _securomEnd
jle _find_mid_jumps_loop
int 3
_mid_jumps_check1:
cmp byte ptr[eax+5], 0E8h
jne _mid_jumps_ok
mov ebx, dword ptr[eax+6]
add ebx, eax
add ebx, 0Ah
cmp ebx, _central
jne _mid_jumps_ok
pushad
jmp eax
nop
nop
nop
_good_return:
mov dword ptr[ebx], 0
popfd
popad
add esp, 8
popad
sub esp, 028h
pop edx
pop esi
add esp, 020h
mov dword ptr[eax], 090909090h
add eax, 4
mov byte ptr[eax], 090h
inc eax
mov byte ptr[eax], 0E8h
mov ebx, eax
sub ebx, edx
add ebx, 5
imul ebx, ebx, -1
mov dword ptr[eax+1], ebx
jmp _mid_jumps_ok
int 3
int 3
int 3
int 3
;type 2 & type 6 exit
_ignore_return:
popfd
popad
add esp, 8
popad
jmp _mid_jumps_ok
To get it to run, you need to replace the exit point of the central securom routine for EIP redirection:
00EFEC7B C703 00000000 MOV DWORD PTR DS:[EBX],0
00EFEC81 9D POPFD
00EFEC82 61 POPAD
00EFEC83 C3 RETN
with a jump to _good_return, and you need to replace the exit point's of the other antidumps (JMP DWORD redirection and instruction emulation) with JMPs to _ignore_return:
JMP dword exit point:
00EFE92E 9D POPFD
00EFE92F 61 POPAD
00EFE930 C2 0400 RETN 4
and instruction emulation exit:
00F00461 9D POPFD
00F00462 61 POPAD
00F00463 C2 0400 RETN 4
Then, run the inject code, it will fix up all the redirected CALLs.
Now, this is the point I have halted at. I don't have the time for the next month or so, so lets see if anyone can continue in my stead. There are two other types of redirections I have noticed. The first (easy) is the import redirection:
006029F4 . 9C PUSHFD
006029F5 . FF0D 20105101 DEC DWORD PTR DS:[1511020]
006029FB .^ 0F84 9197FFFF JE cracked1.005FC192
00602A01 > B8 743F21FE MOV EAX,FE213F74
00602A06 . 35 A489CEFE XOR EAX,FECE89A4
00602A0B . 9D POPFD
00602A0C . FFD0 CALL EAX
014E1ED6 9C PUSHFD
014E1ED7 817424 04 7A2CECD1 XOR DWORD PTR SS:[ESP+4],D1EC2C7A
014E1EDF 9D POPFD
014E1EE0 58 POP EAX
014E1EE1 FFD0 CALL EAX
[/code]
005D1A0B . 9C PUSHFD
005D1A0C . 817424 08 6996F12C XOR DWORD PTR SS:[ESP+8],2CF19669
005D1A14 . 9D POPFD
005D1A15 . 58 POP EAX
005D1A16 . 870424 XCHG DWORD PTR SS:[ESP],EAX
005D1A19 . EB FF JMP SHORT cracked1.005D1A1A
005D1A1B D0 DB D0
[/code]
As you can see, its got a slightly different byte pattern each time, which theoretically makes scanning harder. So, I have decided to blatantly steal Hoodlum's solution to this problem.
You will notice that all the import redirections push a pointer to the beginning of the text section right before they go to the Securom routine. These are pointers to the table of DWORDs at the beginning of the code section(you were probably wondering what they are for, now you know):
00401050 8D604B01 DD cracked1.014B608D
00401054 D1604B01 DD cracked1.014B60D1
00401058 15614B01 DD cracked1.014B6115
0040105C 59614B01 DD cracked1.014B6159
00401060 9D614B01 DD cracked1.014B619D
00401064 E1614B01 DD cracked1.014B61E1
00401068 25624B01 DD cracked1.014B6225
0040106C 69624B01 DD cracked1.014B6269
00401070 AD624B01 DD cracked1.014B62AD
00401074 F1624B01 DD cracked1.014B62F1
00401078 35634B01 DD cracked1.014B6335
0040107C 79634B01 DD cracked1.014B6379
00401080 BD634B01 DD cracked1.014B63BD
00401084 01644B01 DD cracked1.014B6401
00401088 45644B01 DD cracked1.014B6445
Simple brute force. Push each one, call the securom routine, get the correct import from the hooked exit point, replace the DWORD in the table with the pointer to the correct import in the IAT, and when you're finished, recode the securom routine to take the pointer to .text, retrieve the import from it, and then jmp to the import. Pretty simple, but hard to explain. I recommend you give the Hoodlum crack for Grand Theft Auto San Andreas 1.0 a quick glance to see what I mean.
Now, I'm not going to give you the code for the above, as I have yet to do it, still, its pretty simple, you shouldn't have any troubles. If that doesn't work then you can always scan for CALL EAX's perceded with PUSHFD's and PUSH xxxxxx's that push pointers to the beginning of .text. Do whatever you feel like doing.
The last protection has me kind of stumped, and in my opinion is what makes securom so sexy. It replaces single instructions with PUSH/CALLs to the central routine, where they are emulated. I've already figured out that it only emulates certain instructions, and I have a theoretical way of ID'ing each instruction, but its not anything solid enough to warrant me giving you details about. So, this is pretty much what I'm asking you to figure out.
Well, thats it for me. I know a lot of you have better things to do, but this post might help some noobies start on this protection, and the way you guys will criticise me will probably help me improve in some way. I'm pretty sure I'll figure out the single instruction emulation eventually, maybe I'll even update this post when I do. Until then,
you all take care :)