::::::::::::::::::::: m E X / c 4 N T U T O R I A L D I V I S I O N :::::::::::::::::::::::::: Tutor : CoRN2 Editor : Notepad (fullscreen 800x600 wit' wordwrap) Audience : Beginners an' Newbies :) Greets : josephCo, for help when I needed it the most. Target : DirectNet v1.1 Rev Date : 28/03/98 Check out our homepage, http://mex98.home.ml.org/ Hey, lets learn about keygens. Many people seem to think that these things are difficult, and I'm sure there are some real bitches out there, but as a rule, if you can get a valid serial, you can keygen the mf. ;) This target isn't the hardest target in the world... try to learn the method if nothing else :) Please note that this tutorial does NOT cover getting serials as such, so the following instructions are brief, if you need to know more about grabbing serials, try my winzip tute (bah) or any of the others on the mex homepage. 1) Click Register, and type in your details, I used CoRN2 and 111222333444 2) Before you click OK, ctrl-d into SoftIce, lets try our default API calls, GetWindowTextA, and GetDlgItemTextA. 3) Done? F5 to get back to the prog. Click OK. 4) Boom, SoftIce at the start of the GetDlgItemTextA call, F11 to get back to our program. 5) Trace down a few lines, at the CALL ESI line, GetDlgItemTextA pops up again, since ESI now points to it, f11 again to get back. 6) Now we see two pushes, PUSH 407020 and PUSH 407120. The second one is the address of our NAME field, then a CALL, hmm, wonder what that call does. Step over it (f10) NOT trace (f8). Now we see that our CODE is pushed ( PUSH EAX ) and then PUSH 407020 (again). Hmm these are params for lstrcmp - which compares two strings. This smells very much like a serial compare. Look at 407020. Hmmmmmmmmmmm, "9031-79647-2392-654" wonder what that could be... Obviously then, the PUSH 407020 before the CALL sets up a bit of memory to store our serial, combine that with the PUSH of our NAME, smells a bit suspect huh? F5 to get back to the prog, Click OK again, and get back to the offset 40118C. Lets trace into this call this time f8 and make a keygen :) -=- Ok, now we seem to have found our serial generation routine... what now? Well, quite simply, we mimic its operation. We've seen that the serial number is constructed from 4 parts. This *could* mean that there are four parts to our serial generation routine. So lets take a look at the deadlist. * Referenced by a CALL at Addresses: |:004010F7 , :0040118C | :00401230 81EC00010000 sub esp, 00000100 :00401236 A034734000 mov al, byte ptr [00407334] ; Irrelevant Code. :0040123B 88442400 mov byte ptr [esp], al :0040123F 53 push ebx :00401240 56 push esi :00401241 33C0 xor eax, eax :00401243 57 push edi :00401244 B93F000000 mov ecx, 0000003F :00401249 8D7C240D lea edi, dword ptr [esp+0D] :0040124D 55 push ebp :0040124E F3 repz :0040124F AB stosd :00401250 66AB stosw :00401252 BD6B000000 mov ebp, 0000006B ; Note This. :00401257 6834734000 push 00407334 ; Irrelevant Code. :0040125C AA stosb :0040125D 8BB4241C010000 mov esi, dword ptr [esp+0000011C] ; Computed serial addr. :00401264 56 push esi ; push it * Reference To: USER32.wsprintfA, Ord:0262h | :00401265 FF1588934000 Call dword ptr [00409388] ; Call wsprintf :0040126B 8B9C241C010000 mov ebx, dword ptr [esp+0000011C] ; ebx = addr. of NAME :00401272 83C408 add esp, 00000008 ; adjust stack :00401275 8BC3 mov eax, ebx ; eax = addr. of NAME * Reference To: USER32.CharNextA, Ord:0021h | :00401277 8B3D7C934000 mov edi, dword ptr [0040937C] ; edi=addr. of function :0040127D 803B00 cmp byte ptr [ebx], 00 ; check is name char is 0 :00401280 7416 je 00401298 ; if so quit Ok, up until now, we've seen very little of interest. 00401230-401250 clears out a section of memory, and gets ready for the serial generation. We can ignore it all. Then we see the address where the function will store the valid serial it computes ([esp+0000011C]) Now ebx is made to refer to the NAME field, in this case, it points to the first letter of the name which we entered. Then EDI is made to point to the API Call, USER32.CharNextA. So, whenever we see a reference to CALL EDI, we know that CharNextA is the real call. (Note that this is a typical Compiler optimization) Finally the first character of our NAME is checked for NULL (00), if this is empty then the function exits. Ok, lets continue: * Referenced by a (U)nconditional or (C)onditional Jump at Address: ; PART ONE OF SERIAL GEN |:00401296(C) | :00401282 0FBE08 movsx ecx, byte ptr [eax] ; ECX=character of our name :00401285 2BE9 sub ebp, ecx ; subtract letter from EBP :00401287 8BD1 mov edx, ecx ; make a copy of the character :00401289 8D0C49 lea ecx, dword ptr [ecx+2*ecx] ; ECX=char*2+char :0040128C 50 push eax ; push NAME string :0040128D 8D6CCD00 lea ebp, dword ptr [ebp+8*ecx] ; ebp=ebp+ecx :00401291 FFD7 call edi ; CALL CharNextA :00401293 803800 cmp byte ptr [eax], 00 ; have we processed all chars? :00401296 75EA jne 00401282 ; if not go back to 401282 Ok, here were have part one of our serial generation routine. Lets go through it. Firstly, notice that EAX is a pointer to our NAME, and the function CharNextA increments EAX so that it points to the next character of the name. Now we know this we can look at the function. Basically the formula is: ecx = character ebp = ebp-character ecx = ecx * 2 + ecx ebp = ebp + ecx * 8 And thats it, lets look at it with the name 'CoRN2' (note that I'll convert hex2dec ) ecx = 67 ( 67 is the ascii code for 'C' ) ebp = 107 - 67 ( remember at 401252, ebp is set to 6B hex which is 107 decimal ) ecx = (67 * 2) + 67 ebp = 40 + (67 * 8) Then the pointer is incremented by ONE by CharNextA, so the next letter would be 'o', but this time ebp is NOT 107, since its been changed. At the end of this process, the value of EBP is 9031 decimal, then we see this: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00401280(C) | :00401298 8D442410 lea eax, dword ptr [esp+10] ; where to put the string :0040129C 55 push ebp ; push our number * Possible StringData Ref from Data Obj ->"%d-" ; This is a C directive, %d is | ; the number that has been | ; pushed (ebp) | :0040129D 6844734000 push 00407344 :004012A2 50 push eax ; push where to put the string 7 * Reference To: USER32.wsprintfA, Ord:0262h | :004012A3 FF1588934000 Call dword ptr [00409388] ; call wsprintfA :004012A9 8D44241C lea eax, dword ptr [esp+1C] ; eax = pointer to string :004012AD 83C40C add esp, 0000000C ; fix stack :004012B0 50 push eax ; push our string (9031-) :004012B1 56 push esi ; push dest for final string * Reference To: KERNEL32.lstrcatA, Ord:028Dh | :004012B2 FF15B4924000 Call dword ptr [004092B4] ; string concatenate (join) :004012B8 8BC3 mov eax, ebx ; eax = our NAME string :004012BA 803B00 cmp byte ptr [ebx], 00 ; check for NULL character :004012BD 741A je 004012D9 ; if so, exit function Ok, lets explain this. Basically what the above stuff does is to convert the number 9031 into a STRING in memory so we can build the valid serial number. The wsprintfA function does this, and adds a '-' to it. So eax now points to the string "9031-", this is then copied to the destination for the final generated serial to go. We've generated the first part of our serial number. Onward: * Referenced by a (U)nconditional or (C)onditional Jump at Address: ; PART TWO |:004012D7(C) | :004012BF 0FBE08 movsx ecx, byte ptr [eax] ; ecx = character :004012C2 8BD1 mov edx, ecx ; edx = ecx :004012C4 50 push eax ; push name string :004012C5 8D0CC9 lea ecx, dword ptr [ecx+8*ecx] ; ecx = ecx * 8 + ecx :004012C8 8D0C89 lea ecx, dword ptr [ecx+4*ecx] ; ecx = ecx * 4 + ecx :004012CB 8D144A lea edx, dword ptr [edx+2*ecx] ; edx = ecx * 2 + edx :004012CE 8D6C5500 lea ebp, dword ptr [ebp+2*edx] ; ebp = edx * 2 + ebp :004012D2 FFD7 call edi ; CALL NextCharA :004012D4 803800 cmp byte ptr [eax], 00 ; by now we should know :004012D7 75E6 jne 004012BF ; what this is... Ok, same again. Formula: ecx = character edx = character ecx = ecx * 8 + ecx ecx = ecx * 4 + ecx edx = ecx * 2 + edx ebp = edx * 2 + ebp For "CoRN2" : ecx = 67 edx = 67 ecx = 67 * 8 + 67 ecx = 603 * 4 + 603 edx = 3015 * 2 + 67 ebp = 6097 * 2 + 9031 ( note this is the previous number we calculated ) Repeat this for all characters of the name. At this point we should have 21225 in EBP, repeat for all characters. The code that follows this does exactly the same as that above. When we get to this point, we should have in the serial destination: "9031-79647-" Once again: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00401310(C) | :00401300 0FBE08 movsx ecx, byte ptr [eax] :00401303 50 push eax :00401304 8DAC895E080000 lea ebp, dword ptr [ecx+4*ecx+0000085E] :0040130B FFD7 call edi :0040130D 803800 cmp byte ptr [eax], 00 :00401310 75EE jne 00401300 Oh, a nice one, they must be getting bored. Formula: ecx = character ebp = ecx * 4 + ecx + 0000085E WoW. By now you should get the process. At the end of this, we have 2392 in ebp, so after the string mucking around we have, "9031-79647-2392-" Now for the last bit: * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040134C(C) | :00401339 0FBE08 movsx ecx, byte ptr [eax] :0040133C 50 push eax :0040133D 8D1489 lea edx, dword ptr [ecx+4*ecx] :00401340 8D2C559A000000 lea ebp, dword ptr [2*edx+0000009A] :00401347 FFD7 call edi :00401349 803800 cmp byte ptr [eax], 00 :0040134C 75EB jne 00401339 Once again, another NIGHTMARE of code ;) ecx = character edx = ecx * 4 + ecx ebp = edx * 2 + 0000009A In EBP, 654, so the final code turns out to be "9031-79647-2392-654" for the username "CoRN2", check it if you like. :) So now that we know the formula's we need, the next step is to write a program that will enable us to generate our own keys at a whim. To give the most support, I'll supply C, and Pascal sources for this keyen. Lucky you. We'll start with pascal, and get it out of the way ;) * Notice that in parts four and five, although the DirectNet loops through every character, the only one which affects our code is the last character. -------------8<-----------------------------8<-----------------------8<-------------------------- PROGRAM DirectNet_Keygen; VAR Offset : Byte; ECX : LongInt; EBP : LongInt; Name : STRING[20]; PROCEDURE PartOne; BEGIN EBP := $6B; FOR Offset := 1 TO Length( Name ) DO BEGIN ECX := Byte(Name[Offset]); EBP := EBP - ECX; ECX := (ECX * 2)+ECX; EBP := ECX*8+EBP; END; Write( EBP ); END; PROCEDURE PartTwo; VAR EDX : LongInt; BEGIN FOR OffSet := 1 TO Length( Name ) DO BEGIN ECX := Byte( Name[OffSet]); EDX := ECX; ECX := ECX*8+ECX; ECX := ECX*4+ECX; EDX := ECX*2+EDX; EBP := EDX*2+EBP; END; Write( '-',EBP ); END; PROCEDURE PartThree; BEGIN ECX := Byte( Name[Length(Name)]); EBP := ECX*4+ECX+$85E; Write( '-',EBP ); END; PROCEDURE PartFour; VAR EDX : LongInt; BEGIN ECX := Byte( Name[Length(Name)]); EDX := ECX*4+ECX; EBP := EDX*2+$9A; Writeln( '-', EBP ); END; BEGIN Writeln( #13#10'DirectNet v1.1 -- KeyGen' ); Writeln( 'By CoRN2 [mE''98/C4N' ); Writeln( 'http://mex98.home.ml.org' ); Write( #13#10'Name: ' ); Readln( Name ); Write( 'S/N : ' ); PartOne; PartTwo; PartThree; PartFour; END. -------------8<-----------------------------8<-----------------------8<-------------------------- Now for C : -------------8<-----------------------------8<-----------------------8<-------------------------- #include #include long int EBP,ECX; unsigned char Offset; char Name[20]; void PartOne( void ) { EBP = 0x6B; for( Offset = 0 ; Offset <= strlen( Name ) ; Offset ++ ) { ECX = (int)Name[Offset]; EBP -= ECX; ECX = ECX*2+ECX; EBP = ECX*8+EBP; } printf( "%ld",EBP ); } void PartTwo( void ) { long int EDX; for( Offset = 0 ; Offset <= strlen( Name ) ; Offset ++ ) { ECX = (int)Name[Offset]; EDX = ECX; ECX = ECX*8+ECX; ECX = ECX*4+ECX; EDX = ECX*2+EDX; EBP = EDX*2+EBP; } printf( "-%ld",EBP ); } void PartThree( void ) { ECX = (int)Name[strlen(Name)-1]; EBP = ECX*4+ECX+0x85E; printf( "-%ld",EBP ); } void PartFour( void ) { long int EDX; ECX = (int)Name[strlen(Name)-1]; EDX = ECX*4+ECX; EBP = EDX*2+0x9A; printf( "-%ld\n",EBP ); } void main( void ) { printf( "\nDirectNet v1.1 -- KeyGen\nBy CoRN2 [mE'98/C4N]\nhttp://mex98.home.ml.org\n" ); printf( "\nName: " ); gets( Name ); printf( "S/N : " ); PartOne(); PartTwo(); PartThree(); PartFour(); } -------------8<-----------------------------8<-----------------------8<-------------------------- Well, I hope that this tut has been of some use, as you should be able to see, key generators aren't so much hard, as time consuming, but then you learn more about protection routines into the bargain. Practice on your own targets and lemme know how you get on. Good Luck. --CoRN2 ::::::::::::::::::::: m E X / c 4 N T U T O R I A L D I V I S I O N ::::::::::::::::::::::::::