
Registry Crawler 3.0
Written by Sphinx
Introduction |
Tools required |
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 RETSo 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
...(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 :-) 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 = 4BAnyway, 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],ALAnyway, 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
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,
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
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. Final Notes |