This is an archive of an old whitepaper made by three reverse engineering experts on breaking down SecuROM and its multiple layers of protection.
As mentioned, this is a whitepaper released by ARTeam in 2007/8, revealing how SecuROM 7 can be broken down using reverse engineering techniques. The tutorial is now around 10 years old, so many of the tools used are outdated or hard to find. All of the tools used in this tutorial have been added to the global index, and are stored on this site.
This gives a very interesting look at how the technology was implemented, essentially stitching two executables together, and how to dump and remove the DRM altogether.
Please note that this was written by an external source, ARTeam, and formatted by yours truly for use in a blog, as well as archiving all the images and tools required.
LostFileArchives is not responsible for any repercussions that may
come from using this document, and is simply storing it for archival purposes.
After the publication of our first essay on SecuROM I received a lot of interest and replies on this argument, the first essay was contributed by AnonymouS and was just a bit, an insight into SecuROM, a lot of things were still missing, like fixing other anti-debugging and anti-dump methods, fixing redirections and VM. I then organized with deroko, Human and AnonymouS again to write a more complete walkthrough on SecuROM, this time covering all the required issues to successfully own it.
The result is this new Special Issue collecting the contributions of these authors.
This protector is on the scene since time began, and all the games protected with it have already been cracked then it’s time to make these things clearly public so as anyone can understand them.
I am also distributing the tools and scripts used in the tutorials composing this special issue.
Of course I want to thanks authors here for their time and their tools. SecuROM was a protection which unprotection was kept secret for a long time, now no more.
All code included with this tutorial is free to use and modify; we only ask that you mention where you found it. This tutorial is also free to distribute in its current unaltered form, with all the included supplements.
Archive note: Supplements for this tutorial can be found in The Index’s cold storage.
All the commercial programs used within this document have been used only for the purpose of demonstrating the theories and methods described. No distribution of patched applications has been done under any media or host. The applications used were most of the times already been patched, and cracked versions were available since a lot of time. ARTeam or the authors of the paper cannot be considered responsible for damages to the companies holding rights on those programs. The scope of this tutorial as well as any other ARTeam tutorial is of sharing knowledge and teaching how to patch applications, how to bypass protections and generally speaking how to improve the RCE art and generally the comprehension of what happens under the hood. We are not releasing any cracked application. We are what we know.
Some Insights into SecuROM 7.30.0014 by AnonymouS (3)
- Forewords (3)
- Settings and Target (3)
2.1. Target (3)
2.2. Tool Used (3)
- Defeating the mysterious debug-detection (4)
- Reaching OEP (5)
- Defeating the anti-dumping trick. (7)
- Conclusion (9)
- Final words and greetings (9)
Complete Cracking SecuRom 7.xx by Human (10)
- Foreword and needed tools (10)
- First step: start of journey (10)
- Second step: prepare things (10)
- Third Step: Load the game into Olly and rebase (11)
- Fourth Step: daemons tools and OEP (13)
- Fifth Step: Fixing Anti-dumps (17)
- Sixth Step: fixing the CRCChecks (18)
- Seventh Step: taking care of antidumps (20)
- Conclusions (34)
SecuROM for the masses by deroko (35)
- Forewords (35)
- Tools and Target (35)
- Few words about SecuROM (36)
- Dumping SecuROM (37)
- Anti-Dump fixing (48)
- Conclusion (55)
- References (55)
- Greetings (55)
Well deep Inside SecuRom by AnonymouS (56)
- The funny side of things (56)
- Code morphing (56)
- Basic API redirection (57)
- Code splicing (58)
- Advanced API redirections (59)
- Virtual Machine (60)
Archive Note: All of these tutorials use extremely old tools. The same effect can be achieved with more modern decompilation programs such as AIDA64 and others.
It’s been a while since I did any reversing. It hasn’t been much reversing since the release of the X-Prot v2 unpacker. First of all because I’m lazy, but real life also had a lot going on. Anyway, I have been following the SecuRom thread (http://forums.accessroot.com/index.php?showtopic=4361&st=0) on ARTeam’s forum for a while, decided to look deeper into SecuRom 7.
Initially I wanted to code an unpacker and started coding unpackers for SecuRom 7.10 through 7.12. As I began coding on an unpacker for SecuRom V7.18 I found out that the task was quite demanding so I abandoned 7.18 and moved on to 7.30.0014.
This small tutorial/essay is not about completely reversing SecuRom 7.30.0014. It’s just a help for people on how to reach OEP and on the way defeating the anti-debugger trick that apparently stops a lot of people. I will also show I bypass the anti-dump trick used by SecuRom.
The tutorial/essay is not very explaining as I do think that people reading this will be somehow more than just a newbie reverser. SecuRom is a tough protection and good reversing skills are needed in order to fully reverse this protection.
Resident Evil 4 from Capcom
OllyDbg V1.10 OllyDbg plugin (HideOD) with the following settings:
TaskMngr by drizz (Link to Offline Site)
Okay, let’s go to work… Run Olly, select executable and let Olly loose. We hit to exceptions before get this error message:
In the earlier versions I used to get this whenever the ZwQueryObject trick was launched, but when I patch this I still get the error message, so I tend to think it’s the mysterious debugger detection people are talking about on the ARTeam thread.
How does it detect us?? We’re using HideOD and ZwQueryObject is not the reason, so this must be new debugger detection. Let’s start tracing… I will spare you for the agonizing of tracing through huge amount for checksums with SecuRom and let you straight to the answer.
Notice the CMP BYTE PTR DS:[EAX+4],0 ??? It’s really a part of a much large procedure… Anyway, modify this by setting TRUE back to FALSE (1 to 0).
To be honest, I’m not quite sure what exactly happen, but take a look at this clean code, stripped from obfuscation, trap-flag protection and checksums:
0577F556 FFD5 CALL EBP <-- call RtlAcquirePebLock 056D89E3 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] <-- [EBP+8] == 30h 056D89E6 64:8B00 MOV EAX,DWORD PTR FS:[EAX] <-- Get PEB address 0577FAA2 8B40 0C MOV EAX,DWORD PTR DS:[EAX+C] <-- PPEB_LDR_DATA 0577FDC9 8078 04 00 CMP BYTE PTR DS:[EAX+4],0 <-- TRUE if debugged 0577FDD3 75 12 JNZ SHORT game.0577FDE7 <-- jump bad boy
From Microsoft’s library I came across this:
From what I can see the flag at [EAX+4] is somehow switched on when debugged. I tried coding a little test myself but I always came up with a TRUE result even if I was not running a debugger !?!
Fire up Olly and run through all exceptions, including fixing the anti-debug trick, until you reach:
Then set a conditional BPX on VirtualProtect and run it.
Once Olly breaks clear BPX and suspend all threads except the main one.
Now set a breakpoint on ReleaseMutex and let Olly run until it breaks again. CTRL+F9 will lead you to the RETN in ReleaseMutex. Trace back into user-code and you will end up here:
Now search for the pattern: C9 87 3C 24
Be aware there are more offsets that match this pattern but the first one found should be the “good” one. If this is not the case, you better start tracing ;) Anyway should look something like this:
Place a Hardware breakpoint on the instruction after the LEAVE opcode and run Olly. When Olly breaks go to Memory Map and place a breakpoint code section using F2 and run Olly.
Next time Olly breaks we’re at OEP.
Now we can dump…. Or can we ???
As one can see, reaching the OEP of SecuRom 7.30.0014 is fairly easy. However, the authors did another attempt to slow us down. When we fire up our PE-dumper we get this message:
Hmm… What happens here ?? Our dumper won’t dump ?!? Don’t worry… This is easily defeated … Luckily for you I did all the tracing through SecuRom’s checksum hell. Let’s rewind time a little bit ;)
As I encountered this I decided to find out where exactly made this anti-dump trick possible. I let the executable run until the first exception and tried to dump. Here I also got the error message… So, this means that the anti-dump trick is setup before the first exception. Now I simple started the slow process of tracing through tons of checksums. First I encountered this:
This let me to the thought that SecuRom for some strange reason GUARD PAGE the .securom section. So I set at breakpoint on VirtualProtect and after several hits I ended here:
I then retraced my way back to the user code (CTRL-F9) and ended here:
Now we know what to do when reach the OEP, so let’s fast forward to the OEP and open up the Memory Map and set the access back to FULL ACCESS on the .securom section:
Now we can finally dump the executable without any problems.
As one can see it is fairly easy to reach the OEP of SecuRom 7.30.0014. It’s also possible to dump the executable once we set back the page access to FULL. As for the mysterious debugger detection, we found it and is able to fix the problem. However, reaching OEP and being able to dump the executable is not enough to defeating SecuRom. The dump is filled with VM code, code-splicings etc. etc. I only showed the way to the promised land, it’s now up to you to work your way through it yourself ;)
As stated in the forewords this is not a complete tutorial on how to reverse SecuRom, but merely a quick step-by-step on how to reach the OEP of version 7.30.0014. I must admit that I haven’t looked into the VM and code-splicing of this version. I initially kind of promised that I would code another unpacker but I doubt that I will ever code any again. Basically I haven’t got the time anymore and the credits you get from your many hours of work are minimal. However, if my good friend, Nacho DJ, talk me into it I might do some more tutorials ;)
Last but not least I would like to send out my greetings to:
- My wife, Kristine and my son Frederik
- All ARTeam member (especially Nacho DJ)
- All I forgot
Sincerely, AnonymouS / ARTeam
Hello and Welcome to my tut about cracking securom 7.xx (something around 7.30)
What we need..
- Target: Resident evil 4 or Biohazard 4 ISO
- Resident evil 4 1.1 patch
Resident evil 4 maxi image (included)
Archiver’s Note: Maxi image not included, as it’s against copyright law.
- Cff Explorer
- Ollydbg 1.10
- Ollydump plugin
- My oepfind:) newest ofcourse.
Archiver’s Note: OEPFind is not required, but can be used.
And my scripts:
- Securom 7.x Cpuid Fixer (included into this distribution)
- Securom 7.x CRC Check Fixer (included into this distribution)
Securom 7.x Jump Bridge & Crypted Code Fixer (included into this distribution)
Archiver’s Note: Find the above files in Cold Storage under the name “SecuROM Writeup Required Files”.
Note: this tutorial is more like unpacking tutorial and not a deep analyze why I do things, I spent a lot of time analyzing this, so if you want understand better.
Do it alone, check how secuRom uses those. With this tut it would be easier for you.
Lets start with installing game.
Install patch(if you have biohazard you still can install patch after some steps)
First insert Reg file with path to your biohazard 4
REGEDIT4 [HKEY_LOCAL_MACHINE\SOFTWARE\CAPCOM\resident evil 4] "PATH"="e:\\Games\\biohazard 4\\"
Next lets load patch into ollydbg and patch its complain about no game to update :P
You see 00407E4B change it to JMP SHORT upd_pal1.00407EAB and patch will start patching us to 1.1 and securom protected (well it takes a while)
Lets load game.exe into olly. Press alt+M to see memory.
As you can see d3dx9_30.dll blocks our dump to have linear regions :(
So we have to use rebaser from Dr.Golova to fix it to our needs (dont worry All Works after it) rebase it to 20000000h
Lets reload game.exe and voila All is fine All range after exe till 20000000h is free:)
Lets load maxi image into daemon tools 4.10
Run Yasu to cloak virtual drives
Archiver’s Note: Don’t bother using YASU, just use SecuROM Loader 1.2.
Set break point on
CreateProcessInternalAand run, after break you will see on stack param to use with oepfind, so lets use it.
For me command to run In Total commander is like this:
oep game.exe /Sonydadc /05f0612d /05f0612d /220792E1 /1
Last param is time in ms, antidebug that spawns another instance of exe and kills parent if difference is too high from current GetTickCount. But due we patch GetTickCount to count slower in rate of +1, we use /1 instead of original time
With oepfind we use these settings:
- kill low Alloc so it will not Alloc Any memory under imagebase
- hook virtualalloc to Alloc memory linear not random
GetTickCount +1 to disable spawn of another proper process
Now Press detach to look for OEP. After about 10 seconds we have:
Fine so Press yes or Any key In your language. Run Olly and attach to our game.exe. Remember PID due we will need it later, mine is here 75C
Don’t run just in menu select view/threads and double click that one suspended.
Like you can see we are AT OEP :)
Press OK In oepfind to remove infinite jump. But dont close it yet!!
Again we must Press alt+M and change .securom section axx rights to full so right click on it and do Set Access/Full Access Now it’s time to dump memory regions I know it’s a lot of memory but I’m too lazy to code proper memory manager. As you can see our memory starts at 0x6130000
And ends at 94CF000+1000 so its 94D0000 (last region that doesn’t show Any file name, here last before d3dx9_30.dll). Let put those into oepfind to Fields that are now not grayed out.
Press dump and in your dir you’ll find a file named DUMP_06130000-094D0000 of 54mb, with all the dumped range. Now you can close oepfind.
Now we are ready to dump exe, but before that we must do one thing. Another securom protection is left. Securom uses also EP as antidump. So when ollydump change now EP to OEP we will be screwed. Securom adds return params from many Apis to address. So when for example original PID is 200 and dumped program’s PID is 100, we have 200-100+address gets wrong data and end with a page fault. So it always should be 0, when both are same.
Ok, then back to our EP, what to do? Simply move the header somewhere else. Select all from 400140 till 400300 do binary copy and binary paste it to 4001C0
Then we change 40 to C0 at 40003C to point to a new place.
Now we can dump our exe with unticked rebuild imports.
As you can see exe is 97Mb, biohazard exe without securom is Just 6Mb, that’s how protections hurt customers, not my fault then.
Next step will be to fix imports and add our regions. So let fire up CFF Explorer (I patched mine due I was pissed with asking should I load more than fucked 20MB, what a dumb question today, when minimum memory is 2GB).
Click section headers and right click to do “Add Section (file data)” to add our dumped regions.
Fix virtual address with start address-imagebase 6130000400000=5D30000 and change section rights to E0000020
Do right click, rebuild image size, header and save exe.
Now fix imports, well we don’t need imprec all we need is into the .idata section. Run Winhex with game.exe and press alt+G now go to 66b000 can you see those addresses? So copy those till API names.
Close it and now run Winhex on dump.exe, press alt+G and go to 53E8000, now Ctrl+B to paste our IAT, that now as you can see is not RVA, only 7Cxxxxxx. Save the exe and rebuild from the beginning. After the rebuild we will again paste IAT due from Olly we will dump exe again with API addresses changed from RVA to 7Cxxxxxx.
Till now we dumped the exe, fixed the IAT and the PE header, and added missing file regions. We can start with fixing rest.
We start with fixing CRCchecks, for this we will use my Securom 7.x CRC Check Fixer.txt script (included in goodies folder of this distribution). CRCchecks comes in 2 flavours: memory and register. Memory CRC updates always some address [esp+xx] and later uses it, other type updates one of registers eax,ebx,ecx etc.
So what this script does? It searches for patterns of CRCcheck and sets an HW breakpoint, after calculation loop it replaces everything with nops and paste there just calculated value that will be then hardcoded.
Like you can see 057EE02D ADD DWORD PTR SS:[ESP+14],EDI. This is the place of our fix. (I noticed that, and really don’t know why, odbgscript runs faster when I have some some movie in background, open but stopped).
It takes a lot of time even on my Intel C2D E6700 (with a movie stopped it runs 4x faster :P). Anyway the homework this script does is really huge, it has to fix more than 100.000 locations. The works is done into a temporary memory buffer: CRCchecks calc CRCs using their own native loops, after this the temp buffer is copied back into sections substituting the CrCCheck just executed. Minimizing Olly window also speeds up because Windows doesn’t redraw all.
A result of the script is shown here:
Like you can see in above figure, ESP+14 updates always to 164E, and log shows script took 724983ms so 12 minutes :P
Don’t alter script, it will not be faster, I already optimized it to max. Even direct write of opcodes as bytes is faster than assembling new instruction. CRCcheck pattern is in 2 sections, so both those are In script.
- Seventh Step: taking care of antidumps
Now its time to take care of antidump APIs. In this version securom uses the following:
- GetCurrentProcessId: every process gets PID but to make it work, dump needs to return PID as original process
- GetVersion: every windows have own version, so to make it work on vista but dumped on xp we need to return xp version
- CPUID: every CPU have own ID returned in eax & edx, so to make it work on other CPUs it has to return the ID stored in the dump, ours then.
- ResetEvent: every securom exe creates event or events, dump doesn’t have them so we need to return 1
- GetComputerNameA: every pc has a name, so other pc must match what we store into the dump
- GetUserNameA: every user logged has a name, other user logged and it crashes, so same applies here
- RtlGetLastWin32Error: here again we need to return 1, in case there are errors we tell there aren’t any
- GetSystemInfo: every pc has own 20 bytes System info table returned, again it must match our
Knowing all this we can start patching securom. For this we need a place, I choose 9BFF00. And that’s how it will look:
009BFF00 B8 88030000 MOV EAX,388 009BFF05 034424 04 ADD EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF09 C2 0400 RETN 4 009BFF0C B8 0501280A MOV EAX,0A280105 009BFF11 2B4424 04 SUB EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF15 C2 0400 RETN 4 009BFF18 B8 D6060000 MOV EAX,6D6 009BFF1D 334424 04 XOR EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF21 C2 0400 RETN 4 009BFF24 A1 88BAD705 MOV EAX,DWORD PTR DS:[5D7BA88] 009BFF29 C2 0400 RETN 4 009BFF2C 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF30 8B0D 98BAD705 MOV ECX,DWORD PTR DS:[5D7BA98] 009BFF36 03C1 ADD EAX,ECX 009BFF38 35 A416827C XOR EAX,7C8216A4 009BFF3D C2 0400 RETN 4
009BFF40 A1 F4BAD705 MOV EAX,DWORD PTR DS:[5D7BAF4] 009BFF45 8B0D 04BBD705 MOV ECX,DWORD PTR DS:[5D7BB04] 009BFF4B 03C1 ADD EAX,ECX 009BFF4D 0305 ECBAD705 ADD EAX,DWORD PTR DS:[5D7BAEC] 009BFF53 C2 0400 RETN 4 009BFF56 B8 E0F7CB85 MOV EAX,85CBF7E0 009BFF5B C2 0400 RETN 4 009BFF5E E8 00000000 CALL b.009BFF63 009BFF63 5E POP ESI ; kernel32.7C816FD7 009BFF64 83C6 19 ADD ESI,19 009BFF67 57 PUSH EDI ; ntdll.7C910738 009BFF68 8B7C24 08 MOV EDI,DWORD PTR SS:[ESP+8] 009BFF6C B9 24000000 MOV ECX,24 009BFF71 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 009BFF73 BE 10000000 MOV ESI,10 009BFF78 5F POP EDI ; kernel32.7C816FD7 009BFF79 C2 0400 RETN 4
Here you have binary copy of the patch you can paste in binary format:
B8 74 05 00 00 03 44 24 04 C2 04 00 B8 05 01 28 0A 2B 44 24 04 C2 04 00 B8 D6 06 00 00 33 44 24 04 C2 04 00 A1 88 BA D7 05 C2 04 00 8B 44 24 04 8B 0D 98 BA D7 05 03 C1 35 A4 16 82 7C C2 04 00 A1 F4 BA D7 05 8B 0D 04 BB D7 05 03 C1 03 05 EC BA D7 05 C2 04 00 B8 CF F7 4B 87 C2 04 00 E8 00 00 00 00 5E 83 C6 19 57 8B 7C 24 08 B9 24 00 00 00 F3 A4 BE 10 00 00 00 5F C2 04 00 00 00 00 00 00 10 00 00 00 00 01 00 FF FF FE 7F 03 00 00 00 02 00 00 00 4A 02 00 00 00 00 01 00 06 00 06 0F
Now I will explain why it looks like it. First of all go to address 0x5A61A7B
Lets see what’s inside CALL DWORD PTR DS:[5EA4634]?
05A66760 FF15 7CBAD705 CALL DWORD PTR DS:[5D7BA7C] ; kernel32.GetCurrentProcessId 05A66766 034424 04 ADD EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 05A6676A C2 0400 RETN 4
This code wants our PID, and later adds value pushed on stack to it. That’s why we should have here:
MOV EAX,75C ; is my PID of securom process that I dumped ADD EAX,DWORD PTR SS:[ESP+4] RETN 4
Now get back out of it. Do you see this?
05A61A7A 50 PUSH EAX 05A61A7B FF15 3446EA05 CALL DWORD PTR DS:[5EA4634] ; b.05A66760 05A61A81 35 6067A605 XOR EAX,5A66760 05A61A86 3305 3446EA05 XOR EAX,DWORD PTR DS:[5EA4634] ; b.05A66760
Tricky, it does a XOR on EAX, with the address of this small code part and then does again a XOR with the address of small code part that is held in [5EA4634] so we can’t change address for our patch. But hey just change 2nd XOR to same as 1st one, 2 chained XORs with same value will give us 0 so EAX will not change. Same trick we will do on other below, and under [5EA4634] we will now put 9BFF00 for our patch.
One done few more to go.
What have we in 05A61A92 CALL DWORD PTR DS:[5EA4638] ?
05A66770 FF15 80BAD705 CALL DWORD PTR DS:[5D7BA80] ; kernel32.GetVersion 05A66776 2B4424 04 SUB EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 05A6677A C2 0400 RETN 4
So replacing it with:
009BFF0C B8 0501280A MOV EAX,0A280105 ; my xp version 009BFF11 2B4424 04 SUB EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF15 C2 0400 RETN 4
We will defeat that part too.
05A61AA9 FF15 3C46EA05 CALL DWORD PTR DS:[5EA463C] ; b.05A66780 05A66780 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A66784 892C24 MOV DWORD PTR SS:[ESP],EBP 05A66787 8BEC MOV EBP,ESP 05A66789 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A6678D 890C24 MOV DWORD PTR SS:[ESP],ECX 05A66790 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A66794 891C24 MOV DWORD PTR SS:[ESP],EBX 05A66797 8B0D 2046EA05 MOV ECX,DWORD PTR DS:[5EA4620] 05A6679D 85C9 TEST ECX,ECX 05A6679F 7E 16 JLE SHORT b.05A667B7 05A667A1 A1 B4BAD705 MOV EAX,DWORD PTR DS:[5D7BAB4] 05A667A6 3345 08 XOR EAX,DWORD PTR SS:[EBP+8] ; b.<ModuleEntryPoint> 05A667A9 2B0D 2446EA05 SUB ECX,DWORD PTR DS:[5EA4624] 05A667AF 890D 2046EA05 MOV DWORD PTR DS:[5EA4620],ECX 05A667B5 EB 41 JMP SHORT b.05A667F8 05A667B7 D13D 2446EA05 SAR DWORD PTR DS:[5EA4624],1 05A667BD 53 PUSH EBX 05A667BE 51 PUSH ECX 05A667BF 52 PUSH EDX ; ntdll.KiFastSystemCallRet 05A667C0 B8 01000000 MOV EAX,1 05A667C5 0FA2 CPUID 05A667C7 5A POP EDX ; kernel32.7C816FD7 05A667C8 59 POP ECX ; kernel32.7C816FD7 05A667C9 5B POP EBX ; kernel32.7C816FD7 05A667CA 83E0 DF AND EAX,FFFFFFDF
05A667CD A3 B4BAD705 MOV DWORD PTR DS:[5D7BAB4],EAX 05A667D2 3345 08 XOR EAX,DWORD PTR SS:[EBP+8] ; b.<ModuleEntryPoint> 05A667D5 8945 FC MOV DWORD PTR SS:[EBP-4],EAX 05A667D8 833D 2446EA05 00 CMP DWORD PTR DS:[5EA4624],0 05A667DF 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] 05A667E2 C705 2046EA05 00000100 MOV DWORD PTR DS:[5EA4620],10000 ; UNICODE "=D:=D:\" 05A667EC 75 0A JNZ SHORT b.05A667F8 05A667EE C705 2446EA05 01000000 MOV DWORD PTR DS:[5EA4624],1 05A667F8 5B POP EBX ; kernel32.7C816FD7 05A667F9 C9 LEAVE 05A667FA C2 0400 RETN 4
Oh it’s the CPUID!!!
And as you can see it does on it:
05A667CA 83E0 DF AND EAX,FFFFFFDF 05A667D2 3345 08 XOR EAX,DWORD PTR SS:[EBP+8]
So proper patch for my CPU, a little optimized, is:
009BFF18 B8 D6060000 MOV EAX,6D6 009BFF1D 334424 04 XOR EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF21 C2 0400 RETN 4
Why not esp+8? Why I don’t push and pop ebx?
05A61AC0 FF15 4046EA05 CALL DWORD PTR DS:[5EA4640] ; b.05A66800 05A66800 A1 90BAD705 MOV EAX,DWORD PTR DS:[5D7BA90] 05A66805 3B05 2846EA05 CMP EAX,DWORD PTR DS:[5EA4628] 05A6680B 7C 2C JL SHORT b.05A66839 05A6680D FF35 88BAD705 PUSH DWORD PTR DS:[5D7BA88] 05A66813 FF15 84BAD705 CALL DWORD PTR DS:[5D7BA84] ; kernel32.ResetEvent 05A66819 85C0 TEST EAX,EAX 05A6681B 75 22 JNZ SHORT b.05A6683F 05A6681D 2105 90BAD705 AND DWORD PTR DS:[5D7BA90],EAX 05A66823 813D 2846EA05 00001000 CMP DWORD PTR DS:[5EA4628],100000 05A6682D 7D 06 JGE SHORT b.05A66835 05A6682F D125 2846EA05 SHL DWORD PTR DS:[5EA4628],1 05A66835 33C0 XOR EAX,EAX 05A66837 EB 0B JMP SHORT b.05A66844 05A66839 FF05 90BAD705 INC DWORD PTR DS:[5D7BA90] 05A6683F A1 88BAD705 MOV EAX,DWORD PTR DS:[5D7BA88] 05A66844 C2 0400 RETN 4
And all we need of this is:
009BFF24 A1 88BAD705 MOV EAX,DWORD PTR DS:[5D7BA88] 009BFF29 C2 0400 RETN 4
05A61AE5 FF15 4446EA05 CALL DWORD PTR DS:[5EA4644] ; b.05A66850 05A66850 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A66854 EB 00 JMP SHORT b.05A66856 05A66856 893424 MOV DWORD PTR SS:[ESP],ESI 05A66859 A1 94BAD705 MOV EAX,DWORD PTR DS:[5D7BA94] 05A6685E 3B05 2C46EA05 CMP EAX,DWORD PTR DS:[5EA462C] 05A66864 7C 70 JL SHORT b.05A668D6 05A66866 BE D4BAD705 MOV ESI,b.05D7BAD4 05A6686B 56 PUSH ESI 05A6686C FF15 B8BAD705 CALL DWORD PTR DS:[5D7BAB8] ; ntdll.RtlEnterCriticalSection 05A66872 68 98BAD705 PUSH b.05D7BA98 05A66877 68 C4BAD705 PUSH b.05D7BAC4 ; ASCII "HUMAN" 05A6687C C705 98BAD705 10000000 MOV DWORD PTR DS:[5D7BA98],10 05A66886 FF15 C0BAD705 CALL DWORD PTR DS:[5D7BAC0] ; kernel32.GetComputerNameA 05A6688C 85C0 TEST EAX,EAX 05A6688E 75 24 JNZ SHORT b.05A668B4 05A66890 813D 2C46EA05 00100000 CMP DWORD PTR DS:[5EA462C],1000 05A6689A 7D 06 JGE SHORT b.05A668A2
05A6689C D125 2C46EA05 SHL DWORD PTR DS:[5EA462C],1 05A668A2 8325 94BAD705 00 AND DWORD PTR DS:[5D7BA94],0 05A668A9 56 PUSH ESI 05A668AA FF15 BCBAD705 CALL DWORD PTR DS:[5D7BABC] ; ntdll.RtlLeaveCriticalSection 05A668B0 33C0 XOR EAX,EAX 05A668B2 EB 3A JMP SHORT b.05A668EE 05A668B4 56 PUSH ESI 05A668B5 FF15 BCBAD705 CALL DWORD PTR DS:[5D7BABC] ; ntdll.RtlLeaveCriticalSection 05A668BB 8325 94BAD705 00 AND DWORD PTR DS:[5D7BA94],0 05A668C2 813D 2C46EA05 00100000 CMP DWORD PTR DS:[5EA462C],1000 05A668CC 7D 0E JGE SHORT b.05A668DC 05A668CE D125 2C46EA05 SHL DWORD PTR DS:[5EA462C],1 05A668D4 EB 06 JMP SHORT b.05A668DC 05A668D6 FF05 94BAD705 INC DWORD PTR DS:[5D7BA94] 05A668DC 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8] 05A668E0 8B0D 98BAD705 MOV ECX,DWORD PTR DS:[5D7BA98] 05A668E6 03C1 ADD EAX,ECX 05A668E8 3305 C0BAD705 XOR EAX,DWORD PTR DS:[5D7BAC0] ; kernel32.GetComputerNameA 05A668EE 5E POP ESI ; kernel32.7C816FD7 05A668EF C2 0400 RETN 4
And proper patch of this code will be? Well do you know already?
009BFF2C 8B4424 04 MOV EAX,DWORD PTR SS:[ESP+4] ; ntdll.7C910738 009BFF30 8B0D 98BAD705 MOV ECX,DWORD PTR DS:[5D7BA98] 009BFF36 03C1 ADD EAX,ECX 009BFF38 35 A416827C XOR EAX,7C8216A4 009BFF3D C2 0400 RETN 4
Again no esp+8 no need to push pop esi. Well now you wonder why XOR EAX,7C8216A4
Look closer and you will see it XOR with address of GetComputerNameA, so for me address of this api is 7C8216A4
05A61AF9 FF15 4846EA05 CALL DWORD PTR DS:[5EA4648] ; b.05A66900
Here as you will follow you will see nothing is conditional or memory dependent:
08AF0000 E8 00000000 CALL b.08AF0005 08AF0005 58 POP EAX ; kernel32.7C816FD7 08AF0006 05 AA4EA605 ADD EAX,b.05A64EAA 08AF000B 2D AF4EA605 SUB EAX,b.05A64EAF 08AF0010 C3 RETN
So we leave it as it is
05A61B1E FF15 4C46EA05 CALL DWORD PTR DS:[5EA464C] ; b.05A66910 05A66910 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A66914 892C24 MOV DWORD PTR SS:[ESP],EBP 05A66917 8BEC MOV EBP,ESP 05A66919 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A6691D 890C24 MOV DWORD PTR SS:[ESP],ECX 05A66920 8D6424 FC LEA ESP,DWORD PTR SS:[ESP-4] 05A66924 90 NOP 05A66925 893424 MOV DWORD PTR SS:[ESP],ESI 05A66928 A1 68BAD705 MOV EAX,DWORD PTR DS:[5D7BA68] 05A6692D 3B05 3046EA05 CMP EAX,DWORD PTR DS:[5EA4630] 05A66933 7C 6B JL SHORT b.05A669A0 05A66935 BE F0BBD705 MOV ESI,b.05D7BBF0 05A6693A 56 PUSH ESI 05A6693B FF15 08BCD705 CALL DWORD PTR DS:[5D7BC08] ; ntdll.RtlEnterCriticalSection 05A66941 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4] 05A66944 50 PUSH EAX 05A66945 68 ECBAD705 PUSH b.05D7BAEC ; ASCII "Human" 05A6694A C745 FC 01010000 MOV DWORD PTR SS:[EBP-4],101 05A66951 FF15 6CBAD705 CALL DWORD PTR DS:[5D7BA6C] ; ADVAPI32.GetUserNameA 05A66957 85C0 TEST EAX,EAX 05A66959 75 23 JNZ SHORT b.05A6697E 05A6695B 2105 68BAD705 AND DWORD PTR DS:[5D7BA68],EAX 05A66961 813D 3046EA05 00100000 CMP DWORD PTR DS:[5EA4630],1000
05A6696B 7D 06 JGE SHORT b.05A66973 05A6696D D125 3046EA05 SHL DWORD PTR DS:[5EA4630],1 05A66973 56 PUSH ESI 05A66974 FF15 0CBCD705 CALL DWORD PTR DS:[5D7BC0C] ; ntdll.RtlLeaveCriticalSection 05A6697A 33C0 XOR EAX,EAX 05A6697C EB 3B JMP SHORT b.05A669B9 05A6697E 56 PUSH ESI 05A6697F FF15 0CBCD705 CALL DWORD PTR DS:[5D7BC0C] ; ntdll.RtlLeaveCriticalSection 05A66985 8325 68BAD705 00 AND DWORD PTR DS:[5D7BA68],0 05A6698C 813D 3046EA05 00100000 CMP DWORD PTR DS:[5EA4630],1000 05A66996 7D 0E JGE SHORT b.05A669A6 05A66998 D125 3046EA05 SHL DWORD PTR DS:[5EA4630],1 05A6699E EB 06 JMP SHORT b.05A669A6 05A669A0 FF05 68BAD705 INC DWORD PTR DS:[5D7BA68] 05A669A6 A1 F4BAD705 MOV EAX,DWORD PTR DS:[5D7BAF4] 05A669AB 8B0D 04BBD705 MOV ECX,DWORD PTR DS:[5D7BB04] 05A669B1 03C1 ADD EAX,ECX 05A669B3 0305 ECBAD705 ADD EAX,DWORD PTR DS:[5D7BAEC] 05A669B9 5E POP ESI ; kernel32.7C816FD7 05A669BA C9 LEAVE 05A669BB C2 0400 RETN 4
So proper patch is:
009BFF40 A1 F4BAD705 MOV EAX,DWORD PTR DS:[5D7BAF4] 009BFF45 8B0D 04BBD705 MOV ECX,DWORD PTR DS:[5D7BB04] 009BFF4B 03C1 ADD EAX,ECX 009BFF4D 0305 ECBAD705 ADD EAX,DWORD PTR DS:[5D7BAEC] 009BFF53 C2 0400 RETN 4
And finally last one is
05A61B32 FF15 5046EA05 CALL DWORD PTR DS:[5EA4650] ; b.05A669C0 05A669C0 8B15 74BAD705 MOV EDX,DWORD PTR DS:[5D7BA74] ; ADVAPI32.77DC0000 05A669C6 66:813A 4D5A CMP WORD PTR DS:[EDX],5A4D 05A669CB 75 0C JNZ SHORT b.05A669D9 05A669CD A1 78BAD705 MOV EAX,DWORD PTR DS:[5D7BA78] 05A669D2 66:8138 4D5A CMP WORD PTR DS:[EAX],5A4D 05A669D7 74 04 JE SHORT b.05A669DD 05A669D9 33C0 XOR EAX,EAX 05A669DB EB 38 JMP SHORT b.05A66A15 05A669DD 8B48 3C MOV ECX,DWORD PTR DS:[EAX+3C] 05A669E0 03C8 ADD ECX,EAX 05A669E2 8B42 3C MOV EAX,DWORD PTR DS:[EDX+3C] 05A669E5 03D0 ADD EDX,EAX 05A669E7 8B42 58 MOV EAX,DWORD PTR DS:[EDX+58] 05A669EA 0B42 28 OR EAX,DWORD PTR DS:[EDX+28] 05A669ED 56 PUSH ESI 05A669EE 0B42 08 OR EAX,DWORD PTR DS:[EDX+8] 05A669F1 8B71 58 MOV ESI,DWORD PTR DS:[ECX+58] 05A669F4 0B71 28 OR ESI,DWORD PTR DS:[ECX+28] 05A669F7 0B71 08 OR ESI,DWORD PTR DS:[ECX+8] 05A669FA 03C6 ADD EAX,ESI 05A669FC 0FB772 42 MOVZX ESI,WORD PTR DS:[EDX+42] 05A66A00 0FB752 40 MOVZX EDX,WORD PTR DS:[EDX+40] 05A66A04 03C6 ADD EAX,ESI 05A66A06 03C2 ADD EAX,EDX ; ntdll.KiFastSystemCallRet 05A66A08 0FB751 42 MOVZX EDX,WORD PTR DS:[ECX+42] 05A66A0C 0FB749 40 MOVZX ECX,WORD PTR DS:[ECX+40] 05A66A10 03C2 ADD EAX,EDX ; ntdll.KiFastSystemCallRet 05A66A12 03C1 ADD EAX,ECX 05A66A14 5E POP ESI ; kernel32.7C816FD7 05A66A15 C2 0400 RETN 4
What it does? Well calculates CRC of advapi32.dll, with other dll version, language all will be wrong. So, the proper patch will be just to return the value of EAXx, just before returning. For me it is:
009BFF56 B8 E0F7CB85 MOV EAX,85CBF7E0 009BFF5B C2 0400 RETN 4
Now final move is update address table of antidumps to our patches so at 5EA4634 we will binary paste:
00 FF 9B 00 0C FF 9B 00 18 FF 9B 00 24 FF 9B 00 2C FF 9B 00 00 69 A6 05 40 FF 9B 00 56 FF 9B 00
It’s better to not touch addresses and opcodes due securom also uses those as encryption, when it calculates some value in EAX or other register it likes to do ROL with value of some memory.
For example from
05A61B32 FF15 5046EA05 CALL DWORD PTR DS:[5EA4650]
it can do:
so it does ROL EAX, 50 when we change address in that call then encryption is screwed.
It’s like a mine field or small CRCs, you must really watch out what you change even with CRCchecks fixed.
Next step is:
05ADC7D5 E8 18440000 CALL a.05AE0BF2 05ADC7DA 8B48 14 MOV ECX,DWORD PTR DS:[EAX+14] 05ADC7DD 69C9 FD430300 IMUL ECX,ECX,343FD 05ADC7E3 81C1 C39E2600 ADD ECX,269EC3 05ADC7E9 8948 14 MOV DWORD PTR DS:[EAX+14],ECX
And inside 5AE0BF2 we have:
05AE0BF2 53 PUSH EBX 05AE0BF3 56 PUSH ESI 05AE0BF4 FF15 D8990306 CALL DWORD PTR DS:[60399D8] ; ntdll.RtlGetLastWin32Error 05AE0BFA FF35 7473F405 PUSH DWORD PTR DS:[5F47374] 05AE0C00 8BD8 MOV EBX,EAX 05AE0C02 FF15 2CC3D705 CALL DWORD PTR DS:[5D7C32C] ; kernel32.TlsGetValue 05AE0C08 8BF0 MOV ESI,EAX 05AE0C0A 85F6 TEST ESI,ESI 05AE0C0C 75 49 JNZ SHORT b.05AE0C57 05AE0C0E 68 8C000000 PUSH 8C 05AE0C13 6A 01 PUSH 1 05AE0C15 E8 E7D0FFFF CALL b.05ADDD01 05AE0C1A 8BF0 MOV ESI,EAX 05AE0C1C 85F6 TEST ESI,ESI 05AE0C1E 59 POP ECX ; kernel32.7C816FD7 05AE0C1F 59 POP ECX ; kernel32.7C816FD7 05AE0C20 74 2D JE SHORT b.05AE0C4F 05AE0C22 56 PUSH ESI 05AE0C23 FF35 7473F405 PUSH DWORD PTR DS:[5F47374] 05AE0C29 FF15 30C3D705 CALL DWORD PTR DS:[5D7C330] ; kernel32.TlsSetValue 05AE0C2F 85C0 TEST EAX,EAX 05AE0C31 74 1C JE SHORT b.05AE0C4F 05AE0C33 C746 54 2877F405 MOV DWORD PTR DS:[ESI+54],b.05F47728 05AE0C3A C746 14 01000000 MOV DWORD PTR DS:[ESI+14],1 05AE0C41 FF15 BC9A0306 CALL DWORD PTR DS:[6039ABC] ; kernel32.GetCurrentThreadId 05AE0C47 834E 04 FF OR DWORD PTR DS:[ESI+4],FFFFFFFF 05AE0C4B 8906 MOV DWORD PTR DS:[ESI],EAX 05AE0C4D EB 08 JMP SHORT b.05AE0C57 05AE0C4F 6A 10 PUSH 10 05AE0C51 E8 0DCAFFFF CALL b.05ADD663 05AE0C56 59 POP ECX ; kernel32.7C816FD7 05AE0C57 53 PUSH EBX 05AE0C58 FF15 209B0306 CALL DWORD PTR DS:[6039B20] ; ntdll.RtlSetLastWin32Error 05AE0C5E 8BC6 MOV EAX,ESI 05AE0C60 5E POP ESI ; kernel32.7C816FD7 05AE0C61 5B POP EBX ; kernel32.7C816FD7 05AE0C62 C3 RETN
So as I said we must return 1, assemble just there a MOV EAX,1 and RET and all is fine.
You can now ask why not change call to 05AE0BF2 into MOV EAX,1 it’s also 5 bytes? Well, because I bet that E8 from call is used as ROL in some place. I have already seen that in older securom when I tried to fix E8 call that leads to some winapi to point to my IAT table jump.
Well its not over yet with RtlGetLastWin32Error! Why?
Lets look at:
05ADC7DA 8B48 14 MOV ECX,DWORD PTR DS:[EAX+14] 05ADC7E9 8948 14 MOV DWORD PTR DS:[EAX+14],ECX
When we return 1 and add 14 then we have 15 (lol I’m so good at math :P, but I still don’t know how much is 2*2 :P) and when those two execute then we get page fault :( So only possible way is to NOP those two and all is fine.
Next step is:
05BB8F15 E8 2CB0C7FF CALL a.05833F46 05BB8F1A 83FE 04 CMP ESI,4 05BB8F1D 7C 05 JL SHORT a.05BB8F24
And 5833F46 after many instructions leads to:
05833FA6 68 EC55FD05 PUSH b.05FD55EC ; ASCII "GetSystemInfo" 05833FAB 68 1454FD05 PUSH b.05FD5414 ; ASCII "KERNEL32.dll" 05833FB0 FF15 A09A0306 CALL DWORD PTR DS:[6039AA0] ; kernel32.GetModuleHandleA 05833FB6 50 PUSH EAX 05833FB7 FF15 5C9A0306 CALL DWORD PTR DS:[6039A5C] ; b.05813282 05833FBD A3 E066D505 MOV DWORD PTR DS:[5D566E0],EAX 05833FC2 EB 05 JMP SHORT b.05833FC9
With GetProcAddress and call so only possible solution is to copy those 20 bytes, paste them at 9BFF7A, change that call to point into our patch and it looks like it:
009BFF5E E8 00000000 CALL b.009BFF63 009BFF63 5E POP ESI ; kernel32.7C816FD7 009BFF64 83C6 19 ADD ESI,19 009BFF67 57 PUSH EDI ; ntdll.7C910738 009BFF68 8B7C24 08 MOV EDI,DWORD PTR SS:[ESP+8] 009BFF6C B9 24000000 MOV ECX,24 009BFF71 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 009BFF73 BE 10000000 MOV ESI,10 009BFF78 5F POP EDI ; kernel32.7C816FD7 009BFF79 C2 0400 RETN 4
And finally again at
05BBD15E FF15 A49A0306 CALL DWORD PTR DS:[6039AA4]
This one can be replaced with
MOV EAX, 75C ;my PID
And we are almost home. When you now run exe it will work, but only on your machine, but goal is to make it run on any other.
Now after we patched all antidump apis run this script: Securom 7.x Jump Bridge & Crypted Code Fixer.txt (included into goodies folder).
Do you see it? And where it goes?
What here script does it will set HWBP on write into that DWORD when securom will update it with new address. That’s how it looks now:
Well after write it looks like it:
And destination looks like it:
Well at this point we could finish and go to next jump bridge, but as you can see I have in script now HWBP on execute. It’s not needed here, just to make loop universal for crypted code, instead of code splicing or virtualized code by securom it can lead to crypted code. That we must execute on our machine to decrypt it, due inside there is crypted CPUID check and will not uncrypt on other CPU just crash. So let analyze another jump bridge:
004DDD50 - FF25 54E40606 JMP DWORD PTR DS:[606E454] ; b.06073A10
That leads us to:
You see that call? Its call to decrypted code at and after address of that call, so after execute and HWBP on execute on 4DDD50 we get:
Nice uncrypted code that will stay same after we again dump and run on any other machine, due no more checks there. Run whole script to achive that. Again 54890ms (only asm can make it faster)
And now last part to fix is CPUID checks that can be in spliced code.
Run Securom 7.x Cpuid Fixer.txt to fix all those (included into the goodies folder of this tutorials original package):
What script does is to search for CPUID and checks if there is an instruction like “and eax,FFFFFFDF”, just after the CPUID also there can be EB jump to “and eax,FFFFFFDF”, so we must also handle it. Simplest way to fix it is to replace “and eax,FFFFFFDF” with a “mov eax,6D6” that is my CPUID after and’ing. Script gets your CPUID automatically so you don’t need to modify it.
Finally we are at end and can now dump and again fix IAT with Winhex. It should run now on any pc we like. Of course you can dump just once at end, but I did this few times due its better for me to write this tut and get pictures of code so I could paste them here. I hope you enjoyed it and learned something.
Best regards to: all scene competition, all people that make scene alive, ARTeam, exetools, SND and unpack dot cn.
SecuROM is a famous protection used by many games nowadays. Funny thing is that it’s cracking doesn’t take too much time, only takes time when you are doing it for the first time, after that it goes flowless. There aren’t tutorials about SecuROM in the public as far as I’m aware. First tutorial which deals with SecuROM 7.xx was submitted to ARTeam by AnonymouS author, so I think we should write more about it, just for fun…
Target that we will be using is Command & Conquer : Tiberium Wars v1.0, so you will need to get that DVD to follow this tut.
Archive Note: The method of inspecting the ASS is the same here as for other SecuROM 7.xx games.
Well any SecuROM will work as approach is kinda generic.
- Olly only for kewl screenshots…
- Asm/C compiler
Also make sure that you have original DVD as game developers deserve money for their work…
To run this game you will have to copy cnc3game.dat from RetailExe\1.0 to folder where is located cnc3.exe and simply type:
cnc3game.dat –win –config CNC3_english_1.0.SkuDef
-win is for windowed mode
Of course, you don’t have to do this while you are unpacking protection, it is only required when you are testing your dump, otherwise you will get this message if you run dump without that command line:
Similar output you will get when your dumped file is started without proper command line.
SecuROM protection consists of one .exe attached to original .exe. If you trace a little bit trough SecuROM layer you will see 2nd exe being appended, even standards MSVC initialization routine are held in this upper layer. If you want you may dump at this layer and analyze securom protection, but that is not very important for us atm.
SecuROM uses a lot of buffers to make itself anti-dump which are always allocated on different base addresses which leads us to simple conclusion that some kind of random generator is used to allocate those buffers. Also SecuROM uses several anti-dump tricks such as GetCurrentProcessId, OpenEventA/CloseHandle, ResetEvent, TlsSet/GetValue, entrypoint address, imagesize from PE header etc… which we can patch of course.
It allocates a lots of buffers to execute its code and when dumped those buffers take ~40mb. Luckily when compressed they take around 1MB or less, as all redirections are allocated at 64K boundary but only 1KB is commited. This is result of calling VirtualAlloc a lots of times (try making infinite loop with VirtualAlloc and watch what happens). Thus 64K-1K is padded with zeros in your dump and giving really really good compression ratio. Just for comparison, I have dump of 70MB compressed back to 7MB, well, almost like virgin file Well I’ll also discuss about making this dump smaller : virgin + ~20mb.
Before we even think to fire up SoftICE with SecuROM protection we have to hide it properly, well, my softice is hidden it this way:
- NtCreateFile - Int1/int3/int41 patched - NtQuerySystemInformation - UnhandledExceptionFilter
One more way remains to detect SoftICE and that’s to query it’s service and check if it is active, for this I use hook of OpenServiceA to eliminate NTICE service opening. This hook code and other stuff used to log SecuROM execution may be found in hookdll.c. Also SoftICE activity can be detected with EnumServicesStatusExA which is used by ActiveMARK but that’s another story.
There are a few ways to dump SecuROM at the OEP:
- Use method described by anonymous author [see first chapter of this document]
- Find vm_exit, hook it, and wait when it jumps back to 1st section
- Hook commonly used APIs at MSVC oep and watch when those are called from 1st section
- Just dump .exe, load it in IDA, and apply MSVC signatures, then search for OEP
- Use PAGE_GUARD to find when code section jmp to oep
Well there are several ways as you may see, but it is upto you to find method which fits your needs the best.
OEP in this target is located at: 0x40A1B3
.text:0040A1B3 call ___security_init_cookie .text:0040A1B8 jmp ___tmainCRTStartup
.text:00409EF2 ___tmainCRTStartup: .text:00409EF2 push 58h .text:00409EF4 push offset unk_B95678 .text:00409EF9 call sub_40A250 .text:00409EFE xor ebx, ebx .text:00409F00 mov [ebp-1Ch], ebx .text:00409F03 mov [ebp-4], ebx .text:00409F06 lea eax, [ebp-68h] .text:00409F09 push eax .text:00409F0A call ds:GetStartupInfoA .text:00409F10 mov dword ptr [ebp-4], 0FFFFFFFEh .text:00409F17 mov dword ptr [ebp-4], 1 .text:00409F1E mov eax, large fs:18h .text:00409F24 mov esi, [eax+4] .text:00409F27 mov edi, offset unk_C5DD54
When dumping SecuROM you have to know that it’s PE header in memory is actually PE header of a virgin file (except AddressOfEntryPoint is foobared and used as anti-dump on several places).
As we know this fact we may write dumper for SecuROM and dump virgin file to the disk. You may see sromd.asm for detailed code (nothing special just read PE header from memory and dump image to disk + add extra section for SecuROM sections).
SecuROM uses jmp to execute some stolen procedures which are mixed with SecuROM code. To find such procedure you won’t have to search much, first call in OEP leads us to SecuROM code:
This jmp only first time will take you to SecuROM virtual buffers:
If you keep traceing trough it you will eventually end up here:
Now look, it will take you to stolen and mixed procedure here:
You may see how this procedure is mixed with addresses from .securom section, and some jccs are leading to that section. If we take one step back and look again at the jmp from which we have started to trace, we may see that SecuROM wrote to jmp dword ptr correct address:
Note address in 2nd column, it is same as address of stolen procedure. There are at least 2 reasons why this is done in such way:
- Numerous execution of same procedure would slow down game execution
- Make code anti-dump as you may not simply dump it without fixing those jmps
Those jmp dword ptr can be easily found by using byte search, thus, you will either have to write your own tool, or use olly scripts to do the job for you. Before we even dump file, we have to fix those jmps, and for that I use srom_logger.exe and vmtrace.dll.
Srom_logger.exe is very simple search engine which searches for jmp dword ptr and checks if they are leading us to .securom section. If such jmp is found, loader is injected into remote process which will be responsible for loading vmtrace.dll and calling it’s export vmtrace!tracer.
Let’s analyze those codes a little bit so you may know how and why I’m doing what:
loader: call __delta __delta: pop ebp sub ebp, offset __delta x_push ecx, <vmtrace.dll~> call [ebp+loadlibrarya], esp x_pop x_push ecx, <tracer~> call [ebp+getprocaddress], eax, esp x_pop push 0deadc0deh trace_addr = $-4 call eax mov [ebp+redirection], eax call [ebp+exitthread], 0 loadlibrarya dd ? getprocaddress dd ? exitthread dd ? redirection dd ? size_loader = $-loader
tracer export from vmtrace.dll is very simple and it’s job is to set HWBP on write at address from jmp dword ptr:
tracer proc arg trace_address pusha mov eax, trace_address mov global_trace_address, eax xor eax, eax push offset setdr0 push dword ptr fs:[eax] mov dword ptr fs:[eax], esp mov eax, [eax] pop dword ptr fs:[eax] add esp, 4 call hook_kiuser mov save_esp, esp mov eax, trace_address jmp [eax] __baby: call unhook_kiuser mov eax, destination mov [esp.Pushad_eax], eax popa leave retn 4
Also you may see in vmtrace.asm code responsible for hooking KiUserExceptionDispatcher, which will perform stealth tracing from context of our target.
Now simply execute that address and we are going to nonintrusive tracer:
kiuser_hook: mov ecx, [esp+4] mov ebx, [esp] pusha cmp dword ptr[ebx], EXCEPTION_SINGLE_STEP je __checkdrx xor eax, eax mov [ecx.context_dr6], eax mov [ecx.context_dr0], eax mov [ecx.context_dr1], eax mov destination, eax mov [ecx.context_eip], offset __baby mov eax, save_esp mov [ecx.context_esp], eax jmp __allgood __checkdrx: test [ecx.context_dr6], 4000h ;single steping... jnz __goback test [ecx.context_dr6], 1 jnz __write test [ecx.context_dr6], 2 jz __goback ;dr1 hit... xor eax, eax mov [ecx.context_dr6], eax mov [ecx.context_dr0], eax mov [ecx.context_dr1], eax mov eax, save_esp mov [ecx.context_esp], eax mov eax, offset __baby xchg [ecx.context_eip], eax mov destination, eax jmp __allgood ;dr0 hit... __write: mov esi, ecx call ReadProcessMemory, -1, global_trace_address, o destination, 4, 0 call ReadProcessMemory, -1, destination, o old_data, 4, 0 test eax, eax jz __goback mov eax, destination mov [esi.context_dr1], eax or [esi.context_dr7], 4 __allgood: popa call NtContinue, ecx, 1 nop nop __goback: popa jmp old_kiuser
You would probably wonder why I am using ReadProcessMemory to read address from my own process. Well trick is simple, as I used HWBP on r/w each read from r3 would cause HWBP to generate exception, even when you are reading from exception handler, now as code is rewritten to use HWBP on write this is not needed anymore. Also you may see that I’m setting HWBP on execution on final destination. This is done to avoid wrong identification of destination when SecuROM writes 2 times to this address. Whenever write there occurs we take that address and HWBP on execution on that address. When dr1 is hit (execution HWBP) we know we have good pointer, and we log it.
Also it will occur that procedures are in virtual memory so we dump that too to the disk which will allow us to fix them easily. Also srom_logger.asm will produce log file which will look something like this:
redirection at 0x00401023 to 0x013D34C3 redirection at 0x00401098 to 0x013F11F8 redirection at 0x004010C5 to 0x013F66A5 redirection at 0x00401103 to 0x00434D33 redirection at 0x00401141 to 0x0044D2F1 redirection at 0x0040116F to 0x013EA3CF redirection at 0x004011AD to 0x013F2BBD
There is 2957 those jmps so we need to automate process of fixing. Also regions with procedures are dumped to the disk for later fixing (again in another tool named sfixer.asm). Note that after you run srom_logger.asm you will have fixed jmp dword ptr in your target in memory so this is the point when you dump it to the disk with fixed jmps.
Here is an example of one stolen procedure stored somewhere in virtual memory:
seg000:03A5000B push esi seg000:03A5000C mov esi, [esp+8] seg000:03A50010 jmp short loc_3A5002A seg000:03A50012 seg000:03A50012 loc_3A50012: seg000:03A50012 mov ecx, esi seg000:03A50014 push offset loc_3A50027 seg000:03A50019 push 7FE20Fh seg000:03A5001E retn seg000:03A5001F dd 0DF8A7B4Ah seg000:03A50023 dd 0E5B9FAh seg000:03A50027 seg000:03A50027 loc_3A50027: seg000:03A50027 add esi, 10h seg000:03A5002A seg000:03A5002A loc_3A5002A: seg000:03A5002A cmp esi, [esp+0Ch] seg000:03A5002E jnz short loc_3A50012 seg000:03A50030 pop esi seg000:03A50031 retn
You can’t put it back to its original place as SecuROM uses that space in code section for other anti-dump tricks.
We are almost close to dumping this target.
Next thing we should know is where are all virtual buffers that we have to dump and append. For this purpose I use hookdll.c which can be used with my Ultimate Hooking Engine . Also it is recommended to use hook of rdtsc to avoid randomness in memory allocation.
Oki, inject hookdll.dll into target by typing: hook cnc3game.dat, and after a few seconds you will be greeted with MessageBoxA similar to this one on the picture:
Write down those memory addresses, and press ok. Your target will be in the infinite loop in hook of GetTickCount. Do not attach Olly yet!!! Run srom_logger.exe and you will get output similar to this one :
Oki doki, we have produced 0xXXXXXXXX.dmp files and splices.bin + we have fixed jmp dword ptr in protected program. Now you may attach olly, or simply ctrl+d if you are using SoftICE and you will be here:
What I have done here is to check for return address in GetTickCount, and if it is called from certain location I put target into infinite loop, this giving me possibility to always break at OEP fast. You should nop out this jmp $ but do NOT run target yet, as you will need to get also TlsValue with index 0xF which is used as antidump at one point. Simply assemble this code:
The reason why I’m doing this at this moment is to save you some time, you could easily figure this thing later on, and then you will have to dump all over again. Not letting you know at this point about this trick would be mean. Also you have to write down PID of this process!!!!
Now, you may see that in PE header there is valid virgin header, so you may dump it like that. I use my dumper for SecuROM which dumps that pe header from memory and image as it was before packing, and appends to it other memory occupied by SecuROM.
After running sromd.asm you will have dumped_securom.exe which will look like this:
So far so good. You wrote down addresses displayed in MessageBoxA? If not go all over again.
Now take a closer look at spliced files dumped by srom_logger.exe:
By looking at splices we may see that those are allocated on 0x10000 boundary, but there is one small gap between all those splices located here:
Look closer and you will see that we are missing memory region between 45D0000 and 4960000. So we will have to dump that region too.
So far we have 2 regions which have to be dumped:
1480000 – 37F0000 and 45D0000 – 4960000, dump them, and use CFF explorer by Daniel Pistelli to add those regions to dumped_securom.exe. For me it looks like this:
One more thing left to go over, and that’s to add splices, with sfixer.exe . Before you run sfixer.exe you should save splices.bin to, for example, splices_save.bin as sfixer.exe will modify this file, and if something goes wrong you won’t have this file, also you should know that sfixer.exe assumes that you are fixing file “final.exe” so copy updated dumped_securom.exe to “final.exe”. Note also that this step is NOT required,
you could just dump memory from heap1 to highmem from MessageBoxA but your dump would be + ~20mb. In this way, with splices fixing we are reducing dump size + we are making nicer dump.
After running sfixer.asm we may check some of our virtual memory redirections from log_redirection.exe (produced by srom_logger.asm):
redirection at 0x00410BC1 to 0x03B20001 <--- from log_redirections.txt
And fixed splice:
I’ll also show you two splices which weren’t fixed by my tool correctly, and which I have fixed by hand (from protected game in memory):
From my dump:
And when fixed by hand it should look like:
And second splice:
And when fixed:
As you may see in this picture, splice is fixed, but before fixing this call was causing crash as whole splice was rebased to the new address.
Theoretically speaking sfixer.asm can be rewritten to follow execution flow and rebuild those procedures in better way. We may see 3 patterns used as call:
push <ret_splice_address> push <proc_address> ret push <ret_splice_address> jmp __proc_address push <ret_splice_address> push dword ptr[API_pointer_from_IAT> ret
And those are only patterns which are fixed by sfixer.asm, of course, better engine could be written, but, this is good enough
Voila, dumping is done now. All we have to do is to get rid of anti-dump tricks present in the SecuROM, and we will have dumped and fixed game.
This is part where your head might start hurting a little bit, so my advice is to grab pen and paper and write your observation. Well at least that’s how I do with all protectors.
First anti-dump that you will see is when you enter into SecuROM vm interpreter. I don’t want to trouble you mutch so here is address to be hit first:
.bla:00CBEC00 sub_CBEC00 proc near .bla:00CBEC00 jmp ds:dword_125D9EC .bla:00CBEC00 sub_CBEC00 endp
Depending on your dump address to which this jmp is leading could be different, but for me it is at 3320000:
seg000:03320345 mov eax, large fs:18h seg000:0332034B inc ebp seg000:0332034C db 3Eh seg000:0332034C mov eax, [eax+30h] seg000:03320350 db 3Eh seg000:03320350 mov eax, [eax+8] seg000:03320354 db 3Eh seg000:03320354 mov edx, [eax+3Ch] seg000:03320358 dec ebp seg000:0332035A db 3Eh seg000:0332035A mov edx, [edx+eax+50h]
Here it takes OptionalHeader.SizeOfImage if you dump your target it will have different image size from the one that SecuROM expects to be there. So you will have to fix it, it is, 87A000, well simply dump target from memory with LordPE and you will see correct values.
Ok you need to patch it… simply patch mov edx, [edx+eax+50h] with mod edx, 87A000, and first anti-dump is defeated. Next anti dump is CPUID trick which makes dump only CPU specific eg. It won’t work on other CPUs if you don’t fix it:
seg000:03320305 mov eax, 1 seg000:0332030A push ebx seg000:0332030B add ebp, eax seg000:0332030D cpuid seg000:0332030F and eax, 0FFFFFFDFh
This is relatively simple to fix, what you will do is to search trough dumped vm regions for certain byte patern: mov eax. 1. When you find it, now use lde to check if cpuid is present in next 5 instructions, if you find jmp __ follow it. When you find cpuid you know what to fix. Use again lde from cpuid offset to find and eax, 0FFFFFFDFh and patch it with mov eax, value which is returned on your CPU.
This search’n’replace is easy to write, so you may exercise a little bit.
There are 2 more anti-dumps in VM interpreter. GetCurrentProcessId and OpenEventA/CloseHandle. Trick here is to patch GetCurrentProcessId call to return PID of your dump, and OpenEventA/CloseHandle to return 1 for CloseHandle:
seg000:028400F1 mov edx, [edx] seg000:028400F3 xor edx, 0CC5C4764h seg000:028400F9 call edx <-- GetCurrentProcessId seg000:028400FB and ebp, edx seg000:028400FD mov edx, eax seg000:028400FF sub edx, ecx seg000:02840101 btc ebp, eax seg000:02840104 pop ecx
So it should look something like this when patched:
seg000:028600DB mov edx, [edx] seg000:028600DD xor edx, 0BA6258DBh seg000:028600E3 call sub_286010A seg000:028600E8 a01a3ee67056205 db '01A3EE67056205C94339FAF75B158E1CD',0 seg000:0286010A seg000:0286010A sub_286010A proc near seg000:0286010A seg000:0286010A btc ebp, eax seg000:0286010D push 0 seg000:02860112 mov ebp, 8A9FAD20h seg000:02860117 push 2 seg000:0286011C call edx <--- OpenEvent seg000:0286011E inc ebp seg000:02860120 push eax seg000:02860121 xadd ebp, ebp seg000:02860124 mov edx, [ebx+28h] seg000:02860127 add edx, 14h seg000:0286012D xchg ebp, ebp seg000:0286012F mov edx, [edx] seg000:02860131 xor edx, 0AC86A5A7h seg000:02860137 xor ebp, 0C9C0E01Dh seg000:0286013D call edx <--- CloseHandle
You should patch this part in a smart way, first you have to patch push 2/call edx with add esp, 8 to eliminate 2nd and 3rd arguments passed to OpenEvent, now you will have to patch push eax (event handle with nop) and xor edx, 0AC86A5A7h with mov eax,1 and nop call edx (CloseHandle).
Good, good, VM handlers are now anti-dump patched.
Let’s proceed to another anti-dump in code of SecuROM:
Hmmm what is what here? Lets start by going to each one of them and seeing what is going on:
This is GetCurrentProcessId check, which you will have to patch with mov eax, dump_pid
Check for OS version via GetVersion, so you patch it with mov eax, version_of_your_os
Well pretty much self explanatory, patch it with value which cpuid returns on your machine
Patch this as mov eax, 1, and nop out call to ResetEvent
Hey, there is my computer name, nice. So SecuROM here checks for length of your computer name. Check line 0xFA678F and 2 lines after it is set to 0x10, which is maximum size of buffer passed to GetComputerNameA. Then at line 0xFA67BE it takes length of computer name returned by call to GetComputerName, so your patch here would be to nop out EnterCriticalSection and LeaveCriticalSection, also nop out call to GetComputerNameA and nop passing arguments to it, and assemble there mov [12F5A2C], len_of_your_computer_name , and nop je at 0xFA67AB. Easy…
This buffer will simply return it’s address, so above check you should patch with: mov eax, 4650000
That’s my user name over there: Weeeeeee. Well at line 0xFA6843 it takes 4 chars from name and adds them to EAX, this is very simple to patch so you can do it on your own. Not much stuff going on.
In simple words, if you run dump as different user, it will crash at some point.
And last and 8th check:
Very simple anti-dump check, it loads address of advapi32.dll in edx, and address of kernel32.dll to eax, now it plays a little bit with PE header. Of course on different windows version kernel32.dll and advapi32.dll probably will have different values there. It could happen that during MS updates those two, or at least one of them is updated, and in such PE will be screwed, and wrong value will be returned. One simple way to bypass this anti-dump is to run this procedure and get value returned in eax then simly patch this routine with mov eax, that_value/retn 4
Only 2 more anti-dumps left to go over, only 2 more.
Next anti-dump is related to EntryPoint stored in PE header in memory and is located here:
adc edi,  is actually adding OptionalHeader.AddressOfEntryPoint to edi. Your dump will have different EntryPoint, but in this real target, SecuROM excepts it to be: 4d5b98h . I have located at least 1 more check, but no need to look for all of them, it is more then enough to add extra code to the dump which will overwrite OptionalHead.AddressOfEntryPoint with this value, and also will update OptionalHeader.SizeOfImage with value which SecuROM wants in its VM interpreter, so we will be injection this code into SecuROM:
loader: pusha call __delta __delta: pop ebp sub ebp, offset __delta call getkernelbase xchg eax, ebx gethash <VirtualProtect> call getprocaddress, ebx, hash push esp mov ecx, esp call eax, 400000h, 1000h, PAGE_READWRITE, ecx add esp, 4 mov esi, 400000h add esi, [esi+3ch] mov [esi.pe_addressofentrypoint], 4d5b98h mov [esi.pe_sizeofimage], 87a000h mov eax, [ebp+old_entry_point] mov [esp.Pushad_eax], eax popa jmp eax
And now, do you remember that TLS stuff I mentioned earlier? Well here it goes:
It calls TlsGetValue(0x0F), so you will have to patch it with value you got like this:
And so it has been done, now run your dumped/patched exe, and you will have your game up and running.
That’s all folks…
Well as you may see it is relatively simple to fix SecuROM, still, going for the virgin file would be nice, but it requires more free time as you will have to reverse SecuROM VM to fix it properly.
 Ultimate Hooking Engine, deroko of ARTeam,
(Link to Deroko’s Page)
I wish to thank to all my mates in ARTeam for sharing their knowledge, to 29a for one of the best e-zines, unpack.cn crew (fly, shoooo, heXer, softworm, okododo), AnonymouS tut contributor, he know who he is, and of course, you for reading this document.
С вером у Бога, deroko of ARTeam
The authors of SecuRom cracks me up:
“Time to drink .securom”
And in the redirector proc:
“nobody move, nobody gets hurt”
“Masses Against the Classes”
“yates is still ere.something kinda Ooooh”
“Are there no honor among thieves Mr. Yates ??”
This PUSH 10 and JMP look a bit strange:
Follow jump and you will end up here:
As one can all this does is that it pushes a value onto stack. Anyway, this is not important to us. The dump works fine without patching this ;)
Here is where we first meet a redirection of a call to API:
Let’s trace into 05693644h
As one can see, this is actually a call to GetProcAddress.
If we load our dump into LordPE and take a look at the Directory Table (ImportTable):
Running through the ImportTable (kernel32.dll) we noticed the following API’s gets redirected:
We need to fix these API’s. One can do this either by hand or by using ImpRec or ReVirgin. I prefer nothing it automatically coding a unpacker !!!
The code splicing in SecuRom usually looks like this:
Once we trace into 05EB4000h we will enter the wonderful world of SecuRom redirection. I will spare you the heartbreak for going through all the calculation loops, but what it basically does is redirection you to the code splicing code. Anyway, here is the beginning:
It will eventually end up here:
As one might see this is part of the code splicing. However we can fix this by forcing the jump at 095A859h to jump to 05E6EEE9h. Now here is something interesting… Look above at line 05E6EF70h… What is this ?? Let’s trace… A new chapter begins…
Like with the code splicing the advanced API redirection is trigged by a run through the calculation loops. Most often around 10 times. No big deal… After a little tracing we end up here:
This is a call to GetStartupInfoA !!
As one can see it’s not advanced at all. I only chose to call it this because of the calculation/obfuscation loops.
Often calls to API looks very similar to calls to another SecuRom trick…. The Virtual Machine (VM)… Let’s take a look at this feature…
All materials included here can be found on the index.
(c) ARTeam, 2007/2008.
Reformatted for Archive use by LFATeam, 2018