Registry Crawler 3.0

Written by Sphinx


Introduction


Ok. I've cracked an older version sometime ago through patching only :-) So I thought of a rematch ;-) But the version I got
was already version 3.0, so I don't know if all of the protection schemes used in this version are still valid in older ones, coz
as I've said, I patched :-) Yep, I'll try to know everything there is to know about the protection, which is kinda neat btw :-)


Tools required


SoftICE (I used 3.25)
Turbo Pascal 7.0
RegMon 4.0


Target's URL

http://www.hotfiles.com


Essay


Part 1: Keygeneration algo (a step-by-step attempt)

Ok, snoop! And as you can see, it's not packed / crypted or protected in any other way which makes life a bit easier :-) Anyway, it's just a simple name and serial combo-protected app with a time trial of 30 days. However, we can get around that ;-) So enough and run the thing already.

Now we're presented with it's beautiful nag with the regdlg where we could enter our holy serial. So I entered my nick
and a crappy fake serial. Btw, don't try registering with UNREGISTERED USER as the regname coz the prog will think
you really are :-) Anywho, set a breakpoint on any serial-related APIs you can think of. All seems to fail or maybe I was
just confused / lost in the dark "codewoods" :-) Whatever, I've breakpointed MessageBoxA coz the app makes use of one when the serial's wrong. So do that. In the app, punch the "Unlock" button then break! Now mash F12 once to let the nag draw then click the Ok button on it and hope to see something interesting :-)

But since it's already the error message we've "broke" on, we should backtrace a bit hoping to find a conditional jump :-)
But to know what conditional jump it is, we've to know the address where the START of the drawing of the messagebox occurs (yeah, the pushing of the required params). In this case, it's easy. Anyway, the badboy( ) goes like:

 cs:0040A187  RET                            ; end of another routine (the "good" message!)
 cs:0040A188  PUSH    FF                     ; sound type
 cs:0040A18A  CALL    [USER32!MessageBeep]   ; beep the speaker
 cs:0040A190  TEST    ESI,ESI
 cs:0040A192  JZ      0040A197
 cs:0040A194  MOV     ESI,[ESI+1C]           ; esi = parent handle
 cs:0040A197  PUSH    30                     ; messagebox type
 cs:0040A199  PUSH    0044CF0C               ; box title
 cs:0040A19E  PUSH    0044CDCC               ; box message
 cs:0040A1A3  PUSH    ESI                    ; save the handle
 cs:0040A1A4  CALL    [USER32!MessageBoxA]   ; sure :)
 cs:0040A1AA  POP     ESI
 cs:0040A1AB  POP     EBX
 cs:0040A1AC  ADD     ESP,08
 cs:0040A1AF  RET
So now backtrace and search for a jnz 0040A188 or a jz 0040A188 . It shouldn't take you that long to find this:
 cs:0040A0EF  RET                            ; end of yet another routine (who cares?)
 cs:0040A0D0  CALL    00409C30               ; main keygen and comparison routine :)
 cs:0040A0D5  TEST    EAX,EAX                ; eax = result of above (eax = 0 if not ok)
 cs:0040A0D7  JZ      0040A188               ; 0? jump to the "error" routine (note the addr)
 ...(save reginfos in the registry - yeah, crap)...
Ok. Now what? Yeah, we should delve the call if we want to keygen (or even sniff coz there's no relevant addresses
of the echoes of the serials yet :-) So be sure to trace into that call.
 ...(crap were skipped)...
 cs:00409C48  OR      ECX,-01           ; charge max in ecx (classical "get string length")
 cs:00409C4B  XOR     EAX,EAX           ; until it reads a NULL
 cs:00409C4D  PUSH    ESI               ; save esi first
 cs:00409C4E  PUSH    EDI               ; and edi too :)
 cs:00409C4F  MOV     EDI,00451668      ; edi = pointer to a null-terminated name
 cs:00409C54  REPNZ SCASB               ; scan for byte until it reads a 0
 cs:00409C56  NOT     ECX               ; invert ecx's bits (result is namlength+1)
 cs:00409C58  DEC     ECX               ; so we must decrement it to get the real length
 cs:00409C59  CMP     ECX,08            ; is ecx >= 8?
 cs:00409C5C  JAE     00409C6F          ; yep? jump, else...
 cs:00409C5E  POP     EDI
 cs:00409C5F  POP     ESI
 cs:00409C60  MOV     ECX,[ESP+10]
 cs:00409C64  MOV     FS:[00000000],ECX
 cs:00409C6B  ADD     ESP,1C
 cs:00409C6E  RET                       ; just return with eax = 0 :)
A pretty straightforward code isn't it? So we now know that the username should be >= 8 chars to continue. So I changed mine to something more inutitive, Sphinxter (heh!) Now I'll continue where the start of the cals really begin :-)
 cs:00409C6F  PUSH    0044CC4C          ; dump this and you'll get 8267-
 cs:00409C74  LEA     ECX,[ESP+14]      ; ecx = address where it'll save it
 cs:00409C78  CALL    0042637E          ; do it (return with *eax = address)
 cs:00409C7D  MOV     ESI,EAX           ; esi now holds it
 cs:00409C7F  MOV     EDI,00451668      ; edi = pointer to a null-terminated name
 cs:00409C84  OR      ECX,-01           ; for "getting the string length"
 cs:00409C87  XOR     EAX,EAX           ; until it reads a NULL
 cs:00409C89  XOR     EDX,EDX           ; edx = 0 (used below)
 cs:00409C8B  REPNZ SCASB               ; yep, just gets the namelength
 cs:00409C8D  NOT     ECX               ; invert ecx
 cs:00409C8F  DEC     ECX               ; -1 to get the real length
 cs:00409C90  MOV     DWORD PTR [ESP+20],00000000
 cs:00409C98  MOV     EAX,ECX           ; eax = 9 (yeah the namelength)
 cs:00409C9A  MOV     ECX,0000000C      ; ecx = C (divisor)
 cs:00409C9F  DIV     ECX               ; eax = eax / ecx
                                        ;     = 9 / C
                                        ; eax = 0 and
                                        ; edx = 9 (remainder)
 cs:00409CA1  MOV     EAX,[0044C8C0]    ; dump and you'll see YMA19X@24$Z% (datatable!)
 cs:00409CA6  MOV     CL,[EAX+EDX]      ; cl = char pointed from the start of the datatable
                                        ;      with the remainder as the index. Here:
                                        ; cl = [datatable+9], so cl = 24 ("$")
 ...(crap were skipped)...
 cs:00409CC3  MOV     EDI,00451668      ;
 cs:00409CC8  OR      ECX,-01           ;
 cs:00409CCB  XOR     EAX,EAX           ; eax = 0
 cs:00409CCD  XOR     ESI,ESI           ; yeah sure, get the total
 cs:00409CCF  REPNZ SCASB               ; namelength again to ecx,
 cs:00409CD1  NOT     ECX               ; so ecx = 9.
 cs:00409CD3  DEC     ECX               ;
 cs:00409CD4  JZ      00409D72          ; if ecx = 0 (which is not yet) jump, else...
 cs:00409CDA  MOV     EAX,ESI           ; eax = 0 (see above)
 cs:00409CDC  XOR     EDX,EDX           ; edx = 0
 cs:00409CDE  MOV     ECX,00000003      ; ecx = 3 (divisor)
 cs:00409CE3  DIV     ECX               ; eax = eax / ecx
                                        ;     = 0 / 3
                                        ; eax = 0 and
                                        ; edx = 0
 cs:00409CE5  TEST    EDX,EDX           ; so was edx <> 0?
 cs:00409CE7  JNZ     00409D5A          ; sure? jump, else... here, uh uh!
 cs:00409CE9  MOV     AL,[ESI+00451668] ; esi = pointer on [00451668] (username).
                                        ; So al gets the chars. Here, al = 53 ("S")
 cs:00409CEF  CMP     AL,7F             ; is al > 7F?
 cs:00409CF1  JG      00409DF9          ; yes? jump, else... here, no!
 cs:00409CF7  CMP     AL,20             ; is al < 20?
 cs:00409CF9  JL      00409DF9          ; yes? jump, else... here, no! 
Anyway, if the char is > $7F or < $20 then you'll be an immediate badboy! Just follow where that 00409DF9 jumps to :-)
So make sure you'll register with a name within the "valid" range.
 cs:00409CFF  MOVSX   EAX,AL            ; eax = 53
 cs:00409D02  CDQ
 cs:00409D03  SUB     EAX,EDX           ; eax = 53 - 0 (see above), so eax = 53 (duh!)
 cs:00409D05  SAR     EAX,1             ; eax = 53 sar 1, so eax = 29
 cs:00409D07  ADD     AL,22             ; eax = 29 + 22, so eax = 4B 
Anyway, we could translate the above in Pascal form like these:
 if ((i-1) mod 3 = 0) then              ; if name_index (i) is divisible by 3 then
  begin                                 ; eax holds the char based on the index prior these
   eax := eax shr 1;                    ; TP7 doesn't support sar but shr will do here
   eax := eax + $22;                    ; add a $22

 cs:00409D09  CMP     AL,5A             ; is al <= 5A?
 cs:00409D0B  MOV     [ESP+0C],AL       ; but save it first
 cs:00409D0F  JLE     00409D1B          ; hmm, yeah? jump, else... here, yes!
 cs:00409D11  CMP     AL,61
 cs:00409D13  JGE     00409D1B
 cs:00409D15  ADD     AL,06
 cs:00409D17  MOV     [ESP+0C],AL
 cs:00409D1B  CMP     AL,39             ; is al <= 39?
 cs:00409D1D  JLE     00409D29          ; yes? jump, else... here, no!
 cs:00409D1F  CMP     AL,41             ; is al >= 41?
 cs:00409D21  JGE     00409D29          ; yes? jump, else... here, jump!
 cs:00409D23  ADD     AL,08
 cs:00409D25  MOV     [ESP+0C],AL 
Anyway, we could translate the above in Pascal form like these:
 if not(eax <= $5A) and not(eax >= $61) then eax := eax + 6;
 if not(eax <= $39) and not(eax >= $41) then eax := eax + 8; 
Just study how the boolean operators function and all will be fine :-) Of course, there'll be other ways to do it, but I opted
for these instead :-) Whatever, we continue...
 cs:00409D29  MOV     EDX,[ESP+0C]
 cs:00409D2D  LEA     EAX,[ESP+08]
 cs:00409D31  PUSH    EDX
 cs:00409D32  LEA     ECX,[ESP+18]
 cs:00409D36  PUSH    EAX
 cs:00409D37  PUSH    ECX
 cs:00409D38  CALL    0041FE52           ; after, "d eax" = 8267-$K (yep, they're appended!) 
Anyway, I haven't really traced that above call, I just kept my eyes opened and carefully watched the registers :-) Anyway,
as you can see, it just appended the first 2 obtained chars above to our magic string! So we could translate these now as:
 Realcode := Realcode + chr(eax);
 ...(crap were skipped)...
 cs:00409D5A  MOV     EDI,00451668      ;
 cs:00409D5F  OR      ECX,-01           ; yeah, you know this already
 cs:00409D62  XOR     EAX,EAX           ; right?
 cs:00409D64  INC     ESI               ; so esi = 1
 cs:00409D65  REPNZ SCASB               ;
 cs:00409D67  NOT     ECX               ;
 cs:00409D69  DEC     ECX               ;
 cs:00409D6A  CMP     ESI,ECX           ; so while esi < ecx?                                        
 cs:00409D6C  JB      00409CDA          ; loop!
I guess it's not that very clear but once you trace it on your own, it'll all become clearer :-) Anyway, as you know, it loops
until the "counter" (here, it's esi) is NOT < the namelength! Does the same thing again but ONLY to the chars whose index
are divisible by 3. So we could not tell that it uses a looping scheme like a for..to..do or a repeat.. until loop or whatever.
But I used the first coz it's nicer :-) So to keygen using my approach, you must have a knowledge of coding in Pascal :-)
But we're ain't done yet! We've still got one more thing coming. So let's take it.
 cs:00409D72  MOV     EDX,[ESP+08]      ; edx = address of initial realcode (got from above)
 cs:00409D76  MOV     ECX,0000000C      ; ecx = C (divisor)
 cs:00409D7B  MOV     EAX,[EDX-08]      ; eax = 9 (yep, length of the realcode)
 cs:00409D7E  CDQ
 cs:00409D7F  IDIV    ECX               ; eax = 9 / C
                                          eax = 0 and
                                          edx = 9
 cs:00409D81  MOV     EAX,[0044C8C0]    ; eax = address of YMA19X@24$Z% (datatable again)
 cs:00409D86  MOV     CL,[EAX+EDX]      ; yeah, cl = char pointed by [datatable+9], so
                                        ; cl = 24 ("$")

 ...(crap were skipped)...
 cs:00409DB1  CALL    00426310
 cs:00409DB6  MOV     ECX,[ESP+08]      ; after, dump ecx and you'll see that it's appended!
 cs:00409DBA  PUSH    00451768          ; dump this to get the usercode
 cs:00409DBF  PUSH    ECX               ; dump to see the FINAL realcode (the echo's here!)
 cs:00409DC0  CALL    0040FEB3          ; compare them (seems like calculating? hehe)
 cs:00409DC5  ADD     ESP,08            ; stack cleanup
 cs:00409DC8  LEA     ECX,[ESP+08]
 cs:00409DCC  TEST    EAX,EAX           ; after the call, eax = 0 (same) or eax = -1 (diff)
 cs:00409DCE  SETZ    DL                ; set dl to 1 if eax = 0
 cs:00409DD1  AND     EDX,000000FF
 cs:00409DD7  MOV     DWORD PTR [ESP+20],FFFFFFFF
 cs:00409DDF  MOV     ESI,EDX           ; note the movement
 cs:00409DE1  CALL    00426310
 cs:00409DE6  MOV     EAX,ESI           ; the final result (originally in edx is now in
 cs:00409DE8  POP     EDI                 eax to be used later after the RET below)
 cs:00409DE9  POP     ESI
 cs:00409DEA  MOV     ECX,[ESP+10]
 cs:00409DEE  MOV     FS:[00000000],ECX
 cs:00409DF5  ADD     ESP,1C
 cs:00409DF8  RET 
After this last RET, there'll be a TESTing of eax, which will determine the status. So if you want to patch, try patching that.
But I don't guarantee it'll work coz I haven't tested it :-) For coding practices, aside from just sniffing your serial out, why
not code your own keygen. It's prettier that way ;-) If you have any questions / problems on coding your source (but I
doubt it coz it's easy), mail me and I'd be glad to show you mine :-) That's it.

Part 2: Time Limit "algo"

Ok. Dunno why I'd even bother with this anymore since I'm already registered :-) Maybe because it's kind good, IMHO.
Ok. How the hell does it know how many days it has left? I haven't used RegMon at first so I ended up breakpointing CreateFileA instead. Anyway, after breaking. I noticed that it gets this info by a file called RCW.D00 in the Windows dir. And it's hidden btw. Opening it gave me 5 digits (11521) Btw, I had 29 legal days left ;-) I traced how the prog translates these crap but the whole routine was kinda long (even kinda longer than the keygen routine :-) And I was bored by it ;-)
Yeah, there's nothing special with it. And here's how it determines if it's expired:

  • 11521 converted to hex (ie. 2D01)
  • To be subtracted on 2D02 (ie. 2D02 - 2D01 = 1)
  • If the result above is equal to 1E (30 in dec) then it's expired else continue the trial and nag for registration :-)

    So how would I expire my copy? Ok. I would change the contents of my RCW.D00 to 11551 (ie. 11521 + 30 = 11551).

    Now on my next run, it'll expire!

    But I (still) wanted to know how does it get the value to be subtracted on (ie. my 2D02 from above). Since my head hurt
    from tracing and there seems no use of CreateFileA anymore, I turned to RegMon to monitor the app. And I found out it's
    on HKEY_CLASSES_ROOT\RCW. You'll know it when you see it. Trust me ;-)

    So how to "restore" the whole 30 day-trial? Follow these :-)

  • Goto HKEY_CLASSES_ROOT\RCW and edit the value of "Value" from 11521 to 11522 (I had 29 days left right?)
  • Open RCW.D00 and change the number also to 11522 (if they're NOT equal, it'll expire! Be warned!)

    Now run it and see what happens :-) So that's it! cya!

    Sphinx [sphinxter@edsamail.com.ph]

    Final Notes



    Greets to:

    TRES2000 (nice guys)
    sF (nice CrackMes :-)
    4Developers (nice target ;-)


    When ever there is a door,
    there is an entrance.
    And behind an entrance can no secret hide,
    when a cracker takes his knowledge for a ride
        McCodEMaN



    ObDuh

    The information in this essay is for educational purpose only!
    You are only allow to crack, reverse engineer, modify code and debugg programs that you legaly bought and then for personal use only!!
    To ignore this warning is a criminell act and can result in lawful actions!

    So please note!
    I take no responebility for how you use the information in this essay, i take NO responebility for what might happen to you or your computer!
    You use this information on your own risk!! I mean it!









    Essay written by Sphinx ŠTRES2000. All Rights Reserved.