TORN@DO presents: cRACKER's n0TES
Commerical Protection Systems: SecuROM


SecuROM Cracking (Pedro)
First, a simple explanation about SecuROM
SecuROM is a CD-ROM protection that can't be copied when you duplicate a CD. It's used by a lot of games, for the purpose of this tutorial I tried Conflict Freespace (english version), Grim Fandango (italian version) and Might and Magic VI (italian version). This is the first tutorial I write, so forgive me if I'm not clear or if my english is bad. I decided to write this tutorial because the generic SecuROM crack by Laxity doesn't work on the games I tried. I also found that part of SecuROM code is encrypted, and is decrypted by a key read from the CD. My aim is to let the protection decrypt itself, then put decrypted code into the executable and remove SecuROM CD-ROM check.

You need SoftICE, Adump 1.0, ProcDump32 1.1 and the free & great DJGPP32 C++ - Compiler to use this tutorial.


First, let's look at the main executable. Conflict Freespace and Might and Magic VI are not packed, while Grim Fandango is packed with Petite. So the first thing we have to do is to unpack Grim Fandango's executable. Luckily it's quite easy if we use ProcDump32, that has support for unpacking Petite compressed programs.

Ok, let's start with the real cracking ;-)

When we start these games, we see that they load something from disk and they simply exit (because we put a copy, not the original into the reader :-) Well, we can try to breakpoint the usual CALL SendDriverMessage, but nothing happens.

Hmm, it seems SecuROM doesn't use the Windows API to access the CD-ROM. I spare you the effort of finding how the program accesses the CD-ROM: it uses INT 31 with AH=03 (DPMI 0.9+ - SIMULATE REAL MODE INTERRUPT) to call MSCDEX interrupt.

Ok, let's step a bit after the breakpoint, we get into CMS32_95.DLL that is part of the protection, then we get to the main executable. Now if we search for some of the code bytes we see, we can't find them in the main executable file! Parts of the code are crypted, and they are decrypted when the program is run. How can the program modify itself during execution?

By calling WriteProcessMemory. So we put a breakpoint on this call. We see that all these games call it at least three times, even if we didn't put the CD-ROM into the reader. So this decryption doesn't depend on the data on the original CD-ROM. This is just the code that makes the last decryption. In fact if we put the original CD-ROM into the reader, the API is called once more, and the decrypted code is correct, while if we put a copy into the reader, either WriteProcessMemory is not called the fourth time (bad copy, so the protection realizes it's false) or it is called but the decrypted code is garbage and the program realizes it's wrong and exits.

So the fourth decryption DOES depend on the data read from the original CD-ROM.

If we look at the API reference we see that when the breakpoint is activated on WriteProcessMemory we have:

ESP+08: destination address
ESP+0C: source address
ESP+10: length of area to copy

So, we might dump the code bytes after the decryption and substitute them into the main executable file.

First we make a copy of the main executable called MM6_2.EXE (for this purpose I use Might and Magic VI, but the others work the same way). It's important to use short file names, otherwise supcomp and supwrite won't work. The rule is: we execute the original MM6.EXE to get decrypted code and we patch MM6_2.exe (don't run MM6_2.EXE before it's completely patched or it will hang).

Let's run Adump. With command 'R' I see that the starting memory area for dumping is 0x83651000.

Now let's run MM6.EXE with breakpoint on WriteProcessMemory. Ok, I see that 0x5000 bytes are to be written, the source address is at 0xe80078 while the destination address is at 0x4ae000. So I copy the two areas (source and destination) into two different areas of dump memory:

M 4AE000 L 5000 83651000 (original code)
M E80078 L 5000 83661000 (decrypted code)

I let the process end, I go to the dumper and I write the two areas of memory to two files.

W C:\ORIG1.DAT 5000 83651000
W C:\MODIF1.DAT 5000 83661000

Now I open ORIG1.DAT with a hex editor and I take the first 16 bytes. I search for those bytes into MM6_2.EXE. I find them at offset 0xAD400. Let's see if all 0x5000 bytes are identical. I open a DOS window and I run:

supcomp C:\ORIG1.DAT MM6_2.EXE 0 0xAD400 0x5000

Ok, no differences, so we can patch them.

supwrite C:\MODIF1.DAT MM6_2.EXE 0 0xAD400 0x5000

But now if we run MM6_2.EXE it hangs because it tries to decrypt already decrypted data and gets garbage. So let's run MM6.EXE again, and when we reach the breakpoint we write 'u @esp' and we go up some lines:



       :008CC2FE  8D8D64FEFFFF        LEA     ECX,[EBP-019C] 
       :008CC304  51                  PUSH    ECX 
       :008CC305  8B95C4FEFFFF        MOV     EDX,[EBP-013C] 
       :008CC30B  52                  PUSH    EDX              ; length 
       :008CC30C  8B85E4FEFFFF        MOV     EAX,[EBP-011C] 
       :008CC312  50                  PUSH    EAX              ; source 
       :008CC313  8B8DBCFEFFFF        MOV     ECX,[EBP-0144]  
       :008CC319  2B8DB4FEFFFF        SUB     ECX,[EBP-014C] 
       :008CC31F  51                  PUSH    ECX              ; destination 
       :008CC320  8B15B87D9F00        MOV     EDX,[009F7DB8] 
       :008CC326  52                  PUSH    EDX              ; handle 
       :008CC327  FF15B8839F00        CALL    [KERNEL32!WriteProcessMemory]
    
We must set length to 0 so we change:


       MOV EDX,[EBP-13C] 
       PUSH EDX
    
to:


       XOR EDX,EDX 
       NOP 
       NOP 
       NOP 
       NOP 
       PUSH EDX
    
That is, we search for bytes (in MM6_2.EXE):
8B 95 C4 FE FF FF 52 8B 85 E4 FE FF FF 50 8B 8D BC FE FF FF 2B 8D B4 FE FF FF 51 8B 15 B8 7D 9F 00 52 FF 15 B8 83 9F 00

and we change the first 6 bytes to:

33 D2 90 90 90 90

(it's best to search for many bytes, because there are similar parts of code, and we have to make sure we have found the exact place).

You have to work the same way for the two other breakpoints (change crypted code with decrypted code and set to zero the length for WriteProcessMemory). Someone might wonder why I didn't write a program to do all this automatically.

Well, the problem is, the code is similar but not the same for the games I tried (for example Grim Fandango uses ECX as the register to push the length parameter).

Now we really need the original CD-ROM (you bought it, haven't you? :-) to get correctly decrypted code. We must still repeat the above procedure to decrypt and set length to zero when it reaches the fourth WriteProcessMemory (this time EDX is kindly XORed for us, so we just need to 'NOP' the following instruction that loads EDX). We must also crack the part of code where it checks for the original CD-ROM and exits without executing our patched code (this is in the part of code we decrypted before, so if we hadn't decrypted it, we couldn't find it easily in the executable file).

In fact if we try to execute the program after fourth decryption but before the last crack, it won't work, even with the original CD-ROM in the recorder!

The last part of the crack is a bit trickier because you can't step into the program's code (F8 or F10 won't work). Moreover, if the program detects that you are trying to step into it, next time won't even load until you restart Windows, so you shouldn't put breakpoints except the ones I'll tell you. I'll spare you the time I spent to understand what the program does. First you can put a breakpoint on GetDriveTypeA that is used to find the CD-ROM. With F11 you get back to the program's code. If you scroll the code window some pages down, you'll find a series of POPs and a RET followed by a few INT 03. Put a breakpoint on the RET.

Run the original MM6.EXE with the original CD.

Hmm, it doesn't reach the RET. Remove the CD from the reader and run it again. Ahh, now it reaches the RET with EAX=2. Put the original CD-ROM again into the reader but run MM6_2.EXE (you must have decrypted and patched it ALL four times). It reaches the RET with EAX=7. If you put a copy into the reader you also get EAX=7. So it's easy to understand that EAX contains an error code when something goes wrong, and the RET is never reached when all goes right. Now you must run the modified MM6_2.EXE after putting the original cd into the recorder and setting a breakpoint on GetDriveTypeA and on the RET. Press F11 to get to the code when it reaches GetDriveTypeA. Remember, you should get error code 7 so scroll down till you find:



       TEST EDX,EDX ; error? 
       JNZ ........ ; if it jumps => no error 
       CALL [.....] ; start of error routine 
       PUSH 07 
       CALL ....... 
       MOV EAX,7    ; error code 
       JMP ........ ; jumps to the POPs and RET
 
So the first jnz must be changed to jmp. We search for the following bytes:

75 17 FF 15 B0 83 9F 00 6A 07
Into MM6_2.EXE and we change 75 to EB. Run it. Another error, with return code 8. In the code we find:


       TEST EDX,EDX ; error? 
       JNZ ........ ; if it jumps => no error 
       CALL [.....] ; start of error routine 
       PUSH 08 
       CALL ....... 
       MOV EAX,8    ; error code 
       JMP ........ ; jumps to the POPs and RET
 
Search for:

75 17 FF 15 B0 83 9F 00 6A 08
And change 75 to EB. This time if you run it, you get error code 0. You find this code near the RET:


       JNZ ........ ; jumps if error 
       PUSH 2C      ; all checks passed! 
       CALL ....... 
       JMP ........ ; run, baby, run :-)
 
So you must simply change jnz -> nop nop. The sequence to search for is 75 09 6A 2C and you change 75 09 with 90 90.

At last the modified executable runs again with the original. But what about a copy, I hear you ask? Hooray, it works too because you removed error codes 7, 8 and 0 that also a copy produces! Of course the copy must have the right volume name, otherwise you'll get 'Wrong disc' message box.

Well, I don't know if this is the simplest way to crack SecuROM: I wrote all this especially for didactic purposes. It should work on any SecuROM protected game. So bye, bye SecuROM, we won't miss you :-)



    ****************** Source code for supcomp **************************** 
      
    #include <stdio.h> 
    #include <conio.h> 
    #include <stdlib.h> 
    #include <mem.h> 
    #define AREA 32768 
      
    FILE *f,*g; 
    unsigned char *b1,*b2; 
    char notfound[]="Can't open: %s\n"; 
    char seekerror[]="Seek error: %s\n"; 
    char readerror[]="Read error or end of file: %s\nAborting\n"; 
    long l,cont,ofsrc,ofdest; 
      
    void uscita(void) 
    { 
    if (b1) free(b1); 
    if (b2) free(b2); 
    if (f) fclose(f); 
    if (g) fclose(g); 
    } 
      
    void main(int argc, char *argv[]) 
    { 
    if (atexit(uscita)) 
    { 
    printf("Atexit error\n"); 
    exit(EXIT_FAILURE); 
    } 
    if (argc!=6) 
    { 
    printf("Supcomp v1.0 by Pedro '98\n\n" 
    "Usage: supcomp <src> <dest> <offset src> <offset dest> <length>\n" 
    "Numbers may be in hex if prefixed by 0x\n\n" 
    "The program compares <length> bytes of <src> with the corresponding\n" 
    "bytes in <dest> starting from the specified offsets\n" 
    "Only differences are written to the output\n"); 
    exit(EXIT_FAILURE); 
    } 
    if ((b1=(char *)malloc(AREA))==NULL) exit(EXIT_FAILURE); 
    if ((b2=(char *)malloc(AREA))==NULL) exit(EXIT_FAILURE); 
    if ((f=fopen(argv[1],"rb"))==NULL) 
    { 
    printf(notfound,argv[1]); 
    exit(EXIT_FAILURE); 
    } 
    if ((g=fopen(argv[2],"rb"))==NULL) 
    { 
    printf(notfound,argv[2]); 
    exit(EXIT_FAILURE); 
    } 
    ofsrc=strtol(argv[3],NULL,0); 
    ofdest=strtol(argv[4],NULL,0); 
    if (fseek(f,ofsrc,SEEK_SET)) 
    { 
    printf(seekerror,argv[1]); 
    exit(EXIT_FAILURE); 
    } 
    if (fseek(g,ofdest,SEEK_SET)) 
    { 
    printf(seekerror,argv[2]); 
    exit(EXIT_FAILURE); 
    } 
    if ((l=strtol(argv[5],NULL,0))==0) 
    { 
    printf("Wrong length\n"); 
    exit(EXIT_FAILURE); 
    } 
    while (l) 
    { 
    long letti,i; 
      
    if (l>=AREA) letti=AREA; 
    else letti=l; 
    if (fread(b1,1,letti,f)!=letti) 
    { 
    printf(readerror,argv[1]); 
    exit(EXIT_FAILURE); 
    } 
    if (fread(b2,1,letti,g)!=letti) 
    { 
    printf(readerror,argv[2]); 
    exit(EXIT_FAILURE); 
    } 
    for (i=0;i<letti;i++) 
    { 
    if (b1[i]!=b2[i]) 
    { 
    printf("%.8lx %.2x - %.8lx %.2x\n",cont+ofsrc+i, (unsigned
    int)b1[i],cont+ofdest+i,(unsigned int)b2[i]); 
    } 
    } 
    l-=letti; 
    cont+=letti; 
    } 
    exit(EXIT_SUCCESS); 
    } 
      
      
      
      
    ****************** Source code for supwrite ****************************   
    #include <stdio.h> 
    #include <conio.h> 
    #include <stdlib.h> 
    #include <mem.h> 
    #define AREA 32768 
      
    FILE *f,*g; 
    unsigned char *b1; 
    char notfound[]="Can't open: %s\n"; 
    char seekerror[]="Seek error: %s\n"; 
    char readerror[]="Read error or end of file: %s\nAborting\n"; 
    char writeerror[]="Write error: %s\n"; 
    long l,ofsrc,ofdest; 
      
    void uscita(void) 
    { 
    if (b1) free(b1); 
    if (f) fclose(f); 
    if (g) fclose(g); 
    } 
      
    void main(int argc, char *argv[]) 
    { 
    if (atexit(uscita)) 
    { 
    printf("Atexit error\n"); 
    exit(EXIT_FAILURE); 
    } 
    if (argc!=6) 
    { 
    printf("Supwrite v1.0 by Pedro '98\n\n" 
    "Usage: supwrite <src> <dest> <offset src> <offset dest> <length>\n" 
    "Numbers may be in hex if prefixed by 0x\n\n" 
    "The program writes <length> bytes of <src> to the corresponding\n" 
    "bytes in <dest> starting from the specified offsets\n"); 
    exit(EXIT_FAILURE); 
    } 
    if ((b1=(char *)malloc(AREA))==NULL) exit(EXIT_FAILURE); 
    if ((f=fopen(argv[1],"rb"))==NULL) 
    { 
    printf(notfound,argv[1]); 
    exit(EXIT_FAILURE); 
    } 
    if ((g=fopen(argv[2],"rb+"))==NULL) 
    { 
    printf(notfound,argv[2]); 
    exit(EXIT_FAILURE); 
    } 
    ofsrc=strtol(argv[3],NULL,0); 
    ofdest=strtol(argv[4],NULL,0); 
    if (fseek(f,ofsrc,SEEK_SET)) 
    { 
    printf(seekerror,argv[1]); 
    exit(EXIT_FAILURE); 
    } 
    if (fseek(g,ofdest,SEEK_SET)) 
    { 
    printf(seekerror,argv[2]); 
    exit(EXIT_FAILURE); 
    } 
    if ((l=strtol(argv[5],NULL,0))==0) 
    { 
    printf("Wrong length\n"); 
    exit(EXIT_FAILURE); 
    } 
    while (l) 
    { 
    long letti; 
      
    if (l>=AREA) letti=AREA; 
    else letti=l; 
    if (fread(b1,1,letti,f)!=letti) 
    { 
    printf(readerror,argv[1]); 
    exit(EXIT_FAILURE); 
    } 
    if (fwrite(b1,1,letti,g)!=letti) 
    { 
    printf(writeerror,argv[2]); 
    exit(EXIT_FAILURE); 
    } 
    l-=letti; 
    } 
    exit(EXIT_SUCCESS); 
    }
    




+Xoanon's addendum

 ------------------------------------------------------------------------------
                                    SecuROM
                            Addendum to Pedro's crack
                              by xOANINO [UCF/CLASS]
 ------------------------------------------------------------------------------
I just read the essay by Pedro on SecuROM, very good essay .... But as all italian things ... it's not a complete work. As our amazing politicians, he made easy things more hard than they really are as I'll show you in this little 'addendum' (i can't write a full essay, as always i've girls awaiting me ... you know :))

Anyway, very good work Pedro ... Even if your work is not 100% complete, it works... and it's good to see that also Italy has (indeed!) something worth to show in the cracking scene!

Well ... Here's the problem: As you all can read above in Pedro's essay, he correctly decrypts all the stuff in the .text and .idata section and patches the .exe. Ok, this is how SecuROM must be cracked.

So ... what's wrong in Pedro's essay? Well, try to use his method with (for example) a game which you want to rip and use without the CD. It won't work. Why ? Well, coz the EXE Pedro produces is not 100% rebuilded. It still depends on the SecuROM DLLs, and if it don't find a CD in the drive with the correct name and stuff, the program will not run.

And so here it comes your mega-busy, with girls, study, coding xOANINO to the rescue to produce a full 100% working rebuilded executable :)

Just follow these 6 steps:


1) Follow Pedro essay until the BPX GetdrivetypeA / BPX on the RET some pages down.

2) As you can notice, some lines above the ret (where Pedro patches the last JNZ) there's a JMP EAX. Now ... what is this JMP? It's the jump to the correct program entrypoint, which code is already decrypted following the Pedro essay.

3) Write down the value of EAX, and simply write this value at the entrypoint field of the PE header (PE+28h. should be at offset A8h in every PE file). Consider you must subtract from EAX the imagebase value:

  Example:
  EAX = 418DB4h (Omnia99 entrypoint)

  Imagebase = 400000h
  value to write = 418DB4-400000 = 018DB4h (reversed, of course)

4) Now you can also kill the nomore needed sections... like .CMS_D section and .PETITE section !! (use procdump for this, or do it by hand)

5) Wow, you can also delete the CMSxx.DLL !

6) ri-Wow, the EXE runs with no more problems !!!!


That's all ... its just an 'addendum' :) Pedro made a very good work anyway!




GrimFandango Addendum (Pedro)
Some days ago I found a small change in SecuROM protection that has been made by Sony in a recent game, so I decided to write you again about this. The essay above is still valid, but, as we'll see, there is a bit more work to do in order to crack the new SecuROM. First of all, I'd like to thank +Xoanon for writing his 'addendum'. I had really made things harder than they were. With his simple idea, we no longer need to set WriteProcessMemory length to zero, nor we have to crack error codes before the RET, because we simply skip all SecuROM code and we go directly to the real entry point. I've still got a lot to learn ... :-)

But there is something more to say about SecuROM.

Well, in my essay I was a bit imprecise. If you remember I cracked Might and Magic VI as an example, and of course that worked (I read Fravia+'s FAQ and I don't want to write him saying 'My lame crack doesn't work' after he has published my job :-). I also said that Conflict Freespace and Grim Fandango were the same. It's here, that I've been imprecise. In fact I only had a non-working copy of those two games, but I saw there was the same multi-step decryption, so I assumed the entire protection was the same.

Some days later I got the original of Grim Fandango and I was disappointed when I saw that Sony left the multi-step decryption, but added something more to the protection. So I want to behave better than my hated italian politicians and I don't want to let this crack be another unfinished italian thing :-)

First of all, you have to make the usual copy of the decompressed original executable called GRIM2.EXE. Then you must patch the four parts of code as I described in my first essay. You can skip the part where I put zero length for WriteProcessMemory or I crack the error codes, because we'll no longer need that part of SecuROM code. Also, you should put a breakpoint on 'jmp eax' as +Xoanon pointed out, to get the real entry point, and change it into PE header. But if you run it, it doesn't work. Why? Follow me and you'll discover it.

Now let's breakpoint again on WriteProcessMemory and execute the original GrimFandango.exe (with the original copy in the reader). We see there is the same four parts decryption, but after that, WriteProcessMemory is called many more times during game execution, and only 4 bytes are patched every time. When we are inside the breakpoint if we write 'u (@esp+8)-2' to see where the data is going to be written, we always see the same CALL:

CALL [008D6218] (of course it changes from game to game)

By the way, this is the point the SecuROM procedure at [008D6218] was just called from, and it's this procedure that is calling now WriteProcessMemory. If we press F11 we can see what happens. That call has been changed to:

CALL [KERNEL32!GetVersion]

(this is just an example, every call is changed to its original value, i.e. the value that was in the unprotected game before Sony messed with it :-)

We have now understood it. Every time the unprotected game had to call a system routine, or even one of its own routines, Sony saved the address of the CALL into a table, and made the call point to a SecuROM routine. When this routine is executed, it can understand where it was called from by looking at the return value in the stack, then it patches the code so next time the call will be made directly. At last it must give control to the routine that was to be called, and it achieves this by a 'jmp eax' (in our example eax will contain the address of GetVersion). Unfortunately this goes on during the whole execution of the program. But we don't like to keep such a boring neighbour as a SecuROM part of code, we want to kill it completely.

First of all we must see what the real address of this SecuROM routine is:
in location [8D6218] we find address 8CB050.

Now let's take Wdasm and disassemble GRIM2.exe (so the code at 8CB050 is already decrypted). Well you don't really need to disassemble it to make the crack, because I'll explain all steps to make later on, but it's useful to learn, because that's the real reason you are studying reversing and reading this for, not for just copying some silly games, are you? :-)

You can just view the code with SoftICE when you are inside the procedure. Let's go to 8CB050. Here we see many references like these:



       :008CB069 8B0DBCDF8E00            MOV ECX, DWORD PTR [008EDFBC] 
        ...... 
       :008CB072 890DBCDF8E00            MOV DWORD PTR [008EDFBC], ECX 
        ...... 
       :008CB0AA 8A8240FC8E00            MOV AL, BYTE PTR [EDX+008EFC40] 
        ......
  
So there must be the table for decoding all the calls. Note that we don't care about how this table is made. We just need to rebuild the executable the same way we did with the four patches. The routine ends like this:


       :008CB385 FF1518E78E00            CALL DWORD PTR [008EE718] 
                                         ; call to WriteProcessMemory 
      
       :008CB38B 61                      POPAD 
       :008CB38C 8B45F8                  MOV EAX, DWORD PTR [EBP-08] 
       :008CB38F 8BF0                    MOV ESI, EAX 
       :008CB391 8B06                    MOV eax, DWORD PTR [ESI] 
       :008CB393 5F                      POP EDI 
       :008CB394 5E                      POP ESI 
       :008CB395 5B                      POP EBX 
       :008CB396 8BE5                    MOV esp, EBP 
       :008CB398 5D                      POP EBP 
      
       :008CB399 FFE0                    JMP EAX       ; here it makes the original 
                                                       ; game's call 
      
       :008CB39B 5F                      POP EDI 
       :008CB39C 5E                      POP ESI 
       :008CB39D 5B                      POP EBX 
       :008CB39E 8BE5                    MOV ESP, EBP 
       :008CB3A0 5D                      POP EBP 
       :008CB3A1 C3                      RET
 
Hmm, now an interesting idea comes to my mind. First of all the routine doesn't use EBX, so we can use it to get control after calling the routine. Then we could 'simulate' the program calls by using the following small assembly program that can be SoftICE-assembled in an unused area of Adump (poor Adump, we are overloading it :-):
  
       :00000100 B9FA0F4F00              MOV ECX, 004F0FFA 
                                    ; this is (length - 6 bytes) of .text section 
                                    ; of course you'll have to change it with 
                                    ; different games 
      
       :00000105 BA00104000              MOV EDX, 00401000 
                                    ; this is starting address of .text section 
      
       :0000010A 803AFF                  CMP BYTE PTR [EDX], FF 
                                    ; FF 15 18 62 8D 00 is the sequence that 
                                    ; corresponds to call [008d6218], and 
                                    ; you'll have to change it according to 
                                    ; the game you're cracking. 
                                    ; We are searching for that sequence into 
                                    ; the code 
      
       :0000010D 7530                    JNE 0000013F 
                                    ; not found, continue searching 
      
       :0000010F 807A0115                CMP byte ptr [edx+01], 15 
       :00000113 752A                    JNE 0000013F 
       :00000115 807A0218                CMP byte ptr [edx+02], 18 
       :00000119 7524                    JNE 0000013F 
       :0000011B 807A0362                CMP byte ptr [edx+03], 62 
       :0000011F 751E                    JNE 0000013F 
       :00000121 807A048D                CMP byte ptr [edx+04], 8D 
       :00000125 7518                    JNE 0000013F 
       :00000127 807A0500                CPM byte ptr [edx+05], 00 
       :0000012B 7512                    JNE 0000013F 
      
       :0000012D 8D4206                  LEA EAX, DWORD PTR [EDX+06] 
                                    ; found! Now we 'simulate' the call: we put 
                                    ; the return address into the stack like the 
                                    ; call was made by the program 
      
       :00000130 60                      PUSHAD 
                                    ; ...but first we'd better save our precious 
                                    ; registers 
      
       :00000131 50                      PUSH EAX 
                                    ; ahhh, now the correct return address is in 
                                    ; stack 
      
       :00000132 BB3D010000              MOV EBX, 0000013D 
                                    ; we load ebx with the address of the follwing 
                                    ; 'pop eax' instruction, so the we'll regain 
                                    ; control after the patching. Change this value 
                                    ; according to your offset in Adump 
      
       :00000137 FF2518628D00            JMP DWORD PTR [008D6218] 
                                    ; and now, ladies and gentlemen, let's foul 
                                    ; SecuROM (as always, change 008d6218 according 
                                    ; to the game you're cracking) 
      
       :0000013D 58                      POP EAX 
       :0000013E 61                      POPAD 
      
       :0000013F 42                      INC EDX 
      
       :00000140 E2C8                    LOOP 0000010A 
                                    ; the search will go on... 
      
       :00000142 CC                      INT 03 
                                    ; finished, back to SoftICE
   
Let's run GrimFandango.exe with +Xoanon's breakpoint on 'jmp eax'. Now we are about to enter the program (remember we are always running GrimFandango.exe because GRIM2.EXE doesn't work). With 'u 8cb050' we see our mostly hated routine :-).

Scrolling some pages down we see the 'jmp eax' I was telling you about before. This must be changed to 'jmp ebx', to let our tiny (tiny but effective :-) assembly program regain control. Then we write current eip down, we set 'i3here on' and we set eip to the beginning of our assembly code. F5, then we wait a little bit (if I or you have made no mistakes, otherwise you might wait forever for Windows to resurrect :-) Wow, it's finished. So what happened? Well, all 'call [008d6218]' should have been replaced with their correct original counterpart. To tell the truth it's possible (but very unlikely) that a spurious sequence FF 15 18 62 8D 00 that didn't represent the above mentioned call has been incorrectly changed. It's unlikely, though, because 6 bytes are a very specific pattern. So let's just try it first, if everything crashes -or worse- we'll just tediously investigate all the replace locations.

We must now dump from 401000 to the end of the .text section that can be seen with 'map32' (start=401000, length=4f1000 => end=8f2000). The problem now is that some pages are not loaded into memory, so when we write "m" command we get an error. What follows is an idea to dump even if some pages are not loaded into memory: suppose Adump's dumping area starts at 'pippo' (with incredible italian imagination :-). We should assemble this small program with SoftICE near the end of Adump's area, say pippo+4ff000 (ah, remember of course to increase Adump's area, if you need it, like in this case, that should be at least 0x500000 bytes):


    PUSHAD 
    PUSHF 
    CLD 
    REP 
    MOVSB 
    POPF 
    POPAD 
  
Ok, when we need to dump we write down current eip, then we change eip to pippo+4ff000, we step over pushad and pushf, then we manually load ecx with the dump length (in this case 4f1000), esi with starting offset (401000), and edi with destination offset (pippo) and we step over the remaining instructions.

When we've finished dumping we can restore the eip we wrote down before, and GrimFandango will continue peacefully not even knowing we have turned it inside out! :-) Ah, just a note of warning: before dumping or calling my assembly routine, remove ALL breakpoints, otherwise you'll have unwanted 'int 3' in the dump (like I did :-).

The last part is very easy: see where the dumped area starts in the executable and replace it with supwrite (well, my knowledge of PE executables is limited, but I think it's ok to replace it all. Otherwise you may write a small C program to replace only the FF 15 18 62 8D 00 sequences with the correct ones from the dump). If the complete replacing is correct, you don't even need the first four steps: with this single dump and the correct entry point you get it all.

What a nice thing: our non-working GrimFandango is alive again! Not bad for a skeleton :-)).




Outcast Addendum (R!SC)
Tools
* HexEditor (Hacker's View)
* SoftICE 3.2x + Memory Dumper (IceDUMP)
* ProcDump
* RPP 1.2i
* TASM 5.0


My target, LOADER.EXE, compressed with 'Petite' from the game 'Outcast' by Appeal/Infogrames (nice target, as 50% of the time doesnt want to run with the correct CD in anyway).


I have been playing with SecuROM ... I loved Pedros tutorial on it (http://crknotez.cjb.net), and this helped me to crack a few SecuROM games, but, alas, i think Sony have updated SecuROM, so Pedros tutorial don't help much now ... Remember how if it knew you had been debugging it, and it refused to run anymore, until a reboot?? Well, this sucks, and there were a few safe breakpoints you could use before, bpx writeprocessmemory, bpx getdrivetypea, erm, well, it knows about these now, and refuses to run ... alas, we run into a few problems.. :D

well, are you a reverse engineer or a mouse?? hehe, we aint afraid of NO SecuROM ...

Say i wanna bpx on writeprocessmemory, to find out where it decrypts the data in the programs code, i get one break, the first one where it decrypts some SecuROM code, then no more, the program runs in a continous loop, ctrl-alt-del to kill it, then it wont run again ... say i wanna avoid the code decryption part of it, and break on the GetDriveTypea, no no no, if you have any nice breakpoints set, it just doesnt run, and after clearing them, it still doesnt run ...

Stuff i noticed, CMS16.DLL, CMS32_95.DLL & CMS32_NT.DLL are inside the program, and wrote to disk when its executed ... probably to stop people tampering with them.. :D if it knows you have been debugging it, it exits without closing the file handle to CMS16.DLL. Try deleting it, you get a nice error, 'Cannot delete CMS16: The specified file is being used by Windows'.

Well, theres a clue, maybe ... clear all breakpoints, bpx createfilea ... run the SecuROM protected game: first few breaks aren't important ... Windows loading the file, then a short pause, where it gets decompressed, then these are the ones we want, the first opens itself, the second?

CMS16.DLL? the third, CMS16.DLL ... wait, take a look at this code ...

0137:005A1298  50                  PUSH    EAX                      <-- ptr to x:\xx\cms16.dll
0137:005A1299  FF1534685C00        CALL    [KERNEL32!CreateFileA]
0137:005A129F  8945DC              MOV     [EBP-24],EAX
0137:005A12A2  837DDCFF            CMP     DWORD PTR [EBP-24],-01   <-- its already there, and 
0137:005A12A6  753B                JNZ     005A12E3                   - cant be opened again..
0137:005A12A8  C70580645C0000000000MOV     DWORD PTR [005C6480],00000000
0137:005A12B2  6A00                PUSH    00
0137:005A12B4  6A00                PUSH    00
0137:005A12B6  6A03                PUSH    03
0137:005A12B8  6A00                PUSH    00
0137:005A12BA  6A00                PUSH    00
0137:005A12BC  6800000080          PUSH    80000000
0137:005A12C1  8D8D38FFFFFF        LEA     ECX,[EBP-00C8]
0137:005A12C7  51                  PUSH    ECX                      <-- same ptr to cms16.dll
0137:005A12C8  FF1534685C00        CALL    [KERNEL32!CreateFileA]
0137:005A12CE  8945DC              MOV     [EBP-24],EAX
0137:005A12D1  837DDCFF            CMP     DWORD PTR [EBP-24],-01   <-- oh crap, its still -1
0137:005A12D5  750A                JNZ     005A12E1                   - but forcing this jump
0137:005A12D7  6A00                PUSH    00                         - it will run again :D    
0137:005A12D9  E8022C0000          CALL    005A3EE0
0137:005A12DE  83C404              ADD     ESP,04
0137:005A12E1  EB0A                JMP     005A12ED
0137:005A12E3  C70580645C0001000000MOV     DWORD PTR [005C6480],00000001
0137:005A12ED  8B15C4675C00        MOV     EDX,[005C67C4]

Well, see, it tries to create this file, and if it fails, return code FFFFFFFF, it exits ... if we trick it, make it think it could create this file, just by forcing either of these jumps, it runs again :D

So not all is lost.. oops, we still can't bpx writeprocessmemory, or bpx getdrivetypea, so things are trickier, but not impossible ...

What i noticed about the older versions of SecuROM, it decrypts 20 KB of program code, around the original entry point, then checks the disk, and if the correct one is in, it decrypts 200h bytes more of code at the original entry point ... the other code it decrypts wasnt important, as this was SecuROM code ... theory, trace Petite until it has unpacked the program, dump the memory, bpx on the exit point of the SecuROM code, and when you reach it, dump the memory again, and just do a file compare, you should find a nice 20 KB block of decrypted code in the second dump ... arrgh! how to bpx on the exit point of the SecuROM code?? heh, dont ph34r, 'tis easy :D

I expect you to have already fucked up the program, so it wont run anymore, and we have to make it run by changing one of the jumps after the call to CreateFileA, 'CMS16.DLL' ... good ... I used my dodgy process patcher to make a loader that fixed this for me ...

T=10000:
F=loader.exe:
O=securomfix_cc.exe:
P=5A12A6/75/CC: ; 0137:005A12A6 753B JNZ 005A12E3
$

Changing this to a EB makes it run all the time, but i wanted to break here, so i change it to a CC, an INT 03, then in SoftICE, bpint 03, X. run the loader ... hey, when it breaks, don't forget to change the CC to an EB... e eip eb ... okay, when it breaks, and you have changed your INT 03 to a JMP, just make your code window nice and big, and scroll, ctrl-page-down.

This is where we could normally break, but bpx getdrivetypea or just bpx 5a25a2, it knows about them and stops running ... bad, keep scrolling

0137:005A259B  52                  PUSH    EDX
0137:005A259C  FF15E04F5C00        CALL    [KERNEL32!GetDiskFreeSpaceA]
0137:005A25A2  8D8548FCFFFF        LEA     EAX,[EBP-03B8]
0137:005A25A8  50                  PUSH    EAX
0137:005A25A9  FF15004B5C00        CALL    [KERNEL32!GetDriveTypeA]
0137:005A25AF  83F805              CMP     EAX,05

Look, another place we could normally break, but, alas, we cant no more ... keep scrolling ...

0137:005A28E0  8D9548FCFFFF        LEA     EDX,[EBP-03B8]
0137:005A28E6  52                  PUSH    EDX
0137:005A28E7  FF15E04F5C00        CALL    [KERNEL32!GetDiskFreeSpaceA]
0137:005A28ED  8D8548FCFFFF        LEA     EAX,[EBP-03B8]
0137:005A28F3  50                  PUSH    EAX
0137:005A28F4  FF15004B5C00        CALL    [KERNEL32!GetDriveTypeA]
0137:005A28FA  83F805              CMP     EAX,05

Yippee!! this is another place where we would break, many many pages of code have passed before us, and we know, this is where the securom code ends, and jumps to the proper program :D

0137:005A31A8  B8A1535000          MOV     EAX,005053A1
0137:005A31AD  90                  NOP
0137:005A31AE  90                  NOP
0137:005A31AF  50                  PUSH    EAX
0137:005A31B0  EB03                JMP     005A31B5
0137:005A31B2  58                  POP     EAX
0137:005A31B3  FFE0                JMP     EAX

Hmm, good news, we can bpx here 0137:005A31B3 FFE0 JMP EAX, and all is well ... the program still works fine :D yah ... fine!!

heres another lame loader to help us on our way ...

T=10000:
F=loader.exe:
O=securom.cc.jmp.eax.exe:
;P=5A12A6/75/EB: ; heh, my pc crashed for some reason, so this isnt needed yet :)
P=5A31B3/FF/CC: ; i just wanna break on the securom exit point, the jmp eax ...
$

Nice one R!SC, almost time to be destructive :D


Now, using this loader, when softice breaks on the int 03, we can get our decrypted code, what about our dodgy calls?? remember older securom? call dword ptr [securom] for every import? and if you traced over it, the nice securom code replaced [securom] with the actual address of the import in out IAT somewhere in memory? hehe, well, buggered if this works now :( look here ...

This is my original program entry point, see 005053C7.. thats the api call GetVersion, but it calls the securom code, which in time, jmp's to GetVersion, trace into one of the calls, then scroll the code window until a jmp eax ... put a breakpoint on this ... and run it ...

0137:005053A1  55                  PUSH    EBP
0137:005053A2  8BEC                MOV     EBP,ESP
0137:005053A4  6AFF                PUSH    FF
0137:005053A6  6810EB5100          PUSH    0051EB10
0137:005053AB  68C0525000          PUSH    005052C0
0137:005053B0  64A100000000        MOV     EAX,FS:[00000000]
0137:005053B6  50                  PUSH    EAX
0137:005053B7  64892500000000      MOV     FS:[00000000],ESP
0137:005053BE  83EC58              SUB     ESP,58
0137:005053C1  53                  PUSH    EBX
0137:005053C2  56                  PUSH    ESI
0137:005053C3  57                  PUSH    EDI
0137:005053C4  8965E8              MOV     [EBP-18],ESP
0137:005053C7  FF1528C25A00        CALL    [005AC228]       <-- call [securom] ...
0137:005053CD  33D2                XOR     EDX,EDX
0137:005053CF  8AD4                MOV     DL,AH
0137:005053D1  891594585900        MOV     [00595894],EDX
0137:005053D7  8BC8                MOV     ECX,EAX
0137:005053D9  81E1FF000000        AND     ECX,000000FF
0137:005053DF  890D90585900        MOV     [00595890],ECX

my 'jmp eax' was at 59F16F

Break due to BPX #0137:0059F16F (ET=286.97 microseconds)
:?eax
BFF9137C 3220771708 (-1074195588) "��|"
:what eax
The value BFF9137C is (a) KERNEL32!GetVersion <-- ahh

Now, theory, petite unpacks the code, and unpacks a real import address table :D, we just gotta find the real IAT in memory, search for the correct imports for our calls, and fix the calls to call our import table instead of the securom code ...

:s 400000 l ffffffff 7c 13 f9 bf
Pattern found at 0137:0051B110 (0011B110)
:s
Pattern found at 0137:005C6530 (001C6530)
:s
Pattern found at 0137:005CA3E8 (001CA3E8)
:s
Pattern found at 0137:0095067C (0055067C)

Well, we got four choices at the moment, i trace the unpacker code, and stop just before the securom code runs ... then search again ...

:s 400000 l ffffffff 7c 13 f9 bf
Pattern found at 0137:005CA3E8 (001CA3E8)

Yippee, it only finds one ... so if i edit this line ...


0137:005053C7 FF1528C25A00 CALL [005AC228]
to

0137:005053C7 FF15E8A35C00 CALL [KERNEL32!GetVersion] ; CALL [005CA3E8]

Thats one call fixed, only about 300 left to go :) lets code something ...

WHOOPS, i coded something, fixed everything, dumped the memory, copied and pasted it into my previous dump, and it worked ok ... but ... it didn't work on Win95 (I'm working with Win98) ...

Further debugging revealed some code like this ...

015F:00508FE0  55                  PUSH    EBP
015F:00508FE1  8B2D68B15100        MOV     EBP,[KERNEL32!CloseProfileUserMapping]
015F:00508FE7  56                  PUSH    ESI
015F:00508FE8  57                  PUSH    EDI
015F:00508FE9  33DB                XOR     EBX,EBX
015F:00508FEB  33F6                XOR     ESI,ESI
015F:00508FED  33FF                XOR     EDI,EDI
015F:00508FEF  3BC3                CMP     EAX,EBX
015F:00508FF1  7533                JNZ     00509026
015F:00508FF3  FFD5                CALL    EBP

See this line :

015F:00508FE1  8B2D68B15100      MOV     EBP,[KERNEL32!CloseProfileUserMapping]

Its really MOV EBX, DWORD PTR [0051B168], moving an api address from the first IAT we found ... the one thats not their when its unpacked ... securom didnt mess with this, as its not a direct call to the IAT.. well, i unpacked the executable with ProcDump ... ran it with LOADER32, and checked out the memory at 51B168, it contained 72981200 ... obviously, as this is replaced with the linear address of the api function, this was my 'broken' first thunk.. i searched the unpacked exe for 72981200, and found two places, one just before all the imported function names, and the other one i had found before, further studying of the exe with my hex editor, i located the start of the import table.. the list of my image_import_descriptors, 8 of them, followed by 14h null bytes ... the terminating descriptor :D yippee!! just use ProcDumps PE Editor, edit the directory structure, point the import table to the real one ... for this program, it was 528ed8, - imagebase makes 128ed8 ...

Okay, running it with LOADER32 again, checking out address 51b168, yes, its been written over with the linear address of the correct api function ... great ... halfway their ...


Lets code something again ...

;------------------------------------------------------------------------------
; R!SC's dodgy call fixer for 'newer' SecuROM
; (c) august 27th 1999 risc@notme.com
;
; tasm32 /mx /m3 /z /q call_fix
; tlink32 -x /Tpe /aa /c call_fix,call_fix
;
; copy and paste the code into compressed securom executable.. i like the pe header ... 
; break on the jmp eax (in the securom code, jmp orig_entry_point)
; recode the 'jmp eax' in the call [securom] code to jmp ebx
; i3here on, faults on
; copy the code to a empty part of memory.. m 400300 l 60 530000, r eip 530000, run it :0
;------------------------------------------------------------------------------

.486P
.Model Flat

.code
main:
    call    @1  ; please excuse my first attempt at kinda relocatable code
@1:
    pop ebx
    mov esi, ebx
    add ebx, offset here-offset @1  ; return address from jmp [5ac228]
    add esi, offset boring-offset @1
    mov edx, 401000h
    mov ecx, 51b000h-401000h    ; iat begins at 51b000, so code hopefully ends before it
search_loop:
    cmp [edx], 0c22815ffh   ; search pattern for CALL [005AC228]
    jne try_again
    cmp word ptr [edx+4],005ah  ; -
    jne try_again
    
    lea eax, [edx+6]        ; get the address which would be pushed onto the stack
    pushad
    push eax
    ;jmp dword ptr [5ac228h]
    db 0ffh,25h,28h,0c2h,5ah,0  ; jmp blah..

here:                   ; k, we return here from securom code, the api address is in EAX
    mov edx, 51b000h    ; start address of my *real* IAT, first thunk..
search_iat:
    cmp [edx],eax
    jz  got_match
    inc edx
    cmp edx, 51b2a0h
    jne search_iat
    pop eax         ; safty, if it cant find a match for whats in EAX in our IAT
    popad
    int 03          ; match wasnt found, break on the int 03, write down the address in EDX
    jmp try_again   ; and fix it by hand..
got_match:
    mov [esi+4],edx ; save the addr of the import address we got a match for
    pop eax
    popad
    mov eax, [esi+4]    ; retrive the address of the import in our IAT
    mov [edx+2], eax    ; paste it over the 005ac228 in the call [securom]
try_again:
    inc edx
    mov [esi],edx   ; store the last addr EDX was on.. (in case of any problems..)
    dec ecx
    jne search_loop
    int 03
    nop
boring:
end main
;------------------------------------------------------------------------------


Right, compile it, hex edit it and hex edit the compressed SecuROM exe, cut and paste the code from the call fixer into the securom executable somewhere.. i chose file offset 300h, in the pe header ...

Run your loader which breaks on the securom exit point. move the code from the pe header to somewhere else.. m 400300 l 100 530000 , r eip 530000 ...

Put a bpx on JMP [005AC228], and run it. when you get a break, trace into the jmp, and scroll your code window until you see some code like this ...

015F:0059F15E  83C408              ADD     ESP,08
015F:0059F161  61                  POPAD
015F:0059F162  8B45F4              MOV     EAX,[EBP-0C]
015F:0059F165  8BF0                MOV     ESI,EAX
015F:0059F167  8B06                MOV     EAX,[ESI]
015F:0059F169  5F                  POP     EDI
015F:0059F16A  5E                  POP     ESI
015F:0059F16B  5B                  POP     EBX
015F:0059F16C  8BE5                MOV     ESP,EBP
015F:0059F16E  5D                  POP     EBP
015F:0059F16F  FFE0                JMP     EAX

Then change the jmp eax, which would JMP to the API CALL, to JMP EBX ... to jump back to our call fixing routine

:a 59f16f
0137:0059F16F jmp ebx

bc*, i3here on, faults on, cause we wanna trap any errors, and softice to break if it hits either of our int 03's ...

Welp, anyway, run the code, and cross your fingers ... i got a some errors, three calls returned invalid addresses, so i couldnt find them in my IAT, and two calls caused a crash ... with faults on, softice caught the crash, and all i had to do was look in memory location at the end of my code, where i store the address counter, to see what call crashed it ...

502bdd crashed it, and after fixing that, 50ce1c crashed it, so when i run it again, i edit those memory locations, replacing the FF 15 with CC 15, which stopped the code finding the correct byte pattern, thus stopping the crash, and investigate those calls by hand ...

It popped up at this int 03 three times aswell ...

int 03 ; match wasnt found, break on the int 03, write down the address in EDX jmp try_again ; and fix it by hand ...
Just write down the address in edx, and carry on, we can investigate those calls by hand aswell ...

Bad calls were 507c39, 50a324 & 50e0be, so run the program with your break on the securom exit point, edit the eip to point to one of those dodgy call's, and trace, break on the jmp eax ... and search for the api address in your IAT by hand :D, after all the calls are fixed, dump the memory, pagein 400000 11b000 c:\callsfixed.dat .. copy and paste this into the file you unpacked with ProcDump, and fixed the IAT address in the pe-header ...

Cross your fingers, run it :D hey, it works :P

Thats it!! SecuROM is a bit more fun this time around, but if it wasn't for Pedro and +Xoanon, I doubt this tutorial would have been possible. In the three or so days this crack took me, I have learnt quite a lot, and hopefully, you, have learnt something from it aswell ...

R!SC 27th August '99





The cRACKER's n0tES are divided into 10 main parts:
 00. INDEX
 01. Assembly for Crackers (CoRN2)
 02. SoftICE (Boot Menu, Setup, Commands)
 03. Breakpoints & Win API Details
 04. Jump Instructions
 05. SET Instructions
 06. Tips & Tricks for Cracking
 07. Window Messages For Crackers
 08. Identifying Functions, Arguments, and Variables (Rhayader)
 09. Commerical Protection Systems
        1 Armadillo
        2 C-Dilla SafeDISC
        3 SalesAgent
        4 SecuROM
        5 softSENTRY
        6 TimeLOCK
        7 VBox
 10. Bitmanipulation (Cruehead)
 11. General Cracking Theory
 12. FAQ

 +A. How to contact me
 +B. What's New?



The cRACKER's n0TES are Copyright © 1998-2000 by TORN@DO of ID. All Rights Reserved. Archived and Re-hosted by Werdstaff