Keygen Tutorial 4 Yes123 '99 ---[ WARNING ]-------------------------------------------------------------------------------- This Tutorial is for education purpose only. I wrote it to allow you to understand how are coded some protections schemes in software. I didn't make it to allow you to use the target program without paying the author. If you plan to use these programs regularly, please remeber to send your $ to the authors, don't be a outlaw, and over all, don't be a LAMER !!! ---[ INTRO ]---------------------------------------------------------------------------------- target: CLR Script v1.23 where : http://www.prairienet.org/~clroth/clrscript.html Tools : SoftIce for Win9x v3.24 W32Dasm v8.9 Programming Language (C, Pascal, asm, anyone you want, I'll use our old C) This is my 4th tutorial in english. I hope my bad spelling won't make this text too much hard to understand for you. :) I'll try to teach you how to make a key generator for a program. The way i teach will be based more on reverse engineering (instead of only cracking the program, we try to fully understand the whole key-generating algorithm), some newbies maybe having problem of following this. The protection scheme actually encode your name into certain format, and compare with our serial part by part, there are three part of the serial: first part is constant value, second part encoded from our name, and third part encoded from first and second part.if they all match it will then check again whether we enter a name and serial length is 18 chars. By reading the above paragraph, i think you should know there are many ways to crack this program(byte patching, look at real key directly(a bit tricky), key-generator...), but what i'm teaching here is the key-generator process so we are only interested in building a key-generator.(but i'll still touch a bit on others method :)) If you want to unregister the program, look in the registry: [HKEY_USERS\.Default\Software\CLR\CLR Script\Settings] [HKEY_CURRENT_USER\Software\CLR\CLR Script\Settings] You will find the key "RegistrationName" and "RegistrationNumber" ,just delete them will do. If you look careful at the registry, you will find the key "FirstRunTime"&"ShowExpiredTime", clever cracker will know what they do, yes!they control the expired time, so just set the "ShowExpiredTime" value to a large value will extend the expiration date. I'll assume you know the following: - basic use of SoftIce - asm instructions (at least the ones used for cracking) - knowing which call is important call ---[ TUTORIAL ]------------------------------------------------------------------------------- At first, launch SoftICe (assuming you know the basics, and how to setup this Numega's nice tool). Then launch our target, CLRScrpt.exe! Go to the help menu and select registration number. Enter a name and serial number. Simplily put a breakpoint before you press ok(eg.bpx hmemcpy or others...).You should have break into softice after pressing OK button. Keep pressing F12 until you get back to the traget program code. You will be somewhere in: * Reference To: USER32.GetWindowTextA, Ord:013Fh | :00425375 FF1510854400 Call dword ptr [00448510] :0042537B 6AFF push FFFFFFFF :0042537D 8BCF mov ecx, edi :0042537F E8273B0000 call 00428EAB .... You are inside a call 425333, this call will be call 2 times to get your name and serial. After a return, you will be in: :00406F39 E8F5E30100 call 00425333 ;call to get our name :00406F3E 8D4C2408 lea ecx, dword ptr [esp+08] :00406F42 E8DA190200 call 00428921 :00406F47 8D4C2408 lea ecx, dword ptr [esp+08] :00406F4B C644241801 mov [esp+18], 01 :00406F50 51 push ecx * Possible Reference to Dialog: DialogID_0082, CONTROL_ID:03EC, "" | :00406F51 68EC030000 push 000003EC :00406F56 8BCE mov ecx, esi :00406F58 E8D6E30100 call 00425333 ;call to get our serial :00406F5D E83F5D0300 call 0043CCA1 :00406F62 8B54240C mov edx, dword ptr [esp+0C] :00406F66 8B7804 mov edi, dword ptr [eax+04] :00406F69 52 push edx * Possible StringData Ref from Data Obj ->"RegistrationName" | :00406F6A 6854B64500 push 0045B654 * Possible StringData Ref from Data Obj ->"Settings" | :00406F6F 6804B64500 push 0045B604 :00406F74 8BCF mov ecx, edi :00406F76 E85DA60200 call 004315D8 :00406F7B 8B442408 mov eax, dword ptr [esp+08] :00406F7F 8BCF mov ecx, edi :00406F81 50 push eax * Possible StringData Ref from Data Obj ->"RegistrationNumber" | :00406F82 6840B64500 push 0045B640 * Possible StringData Ref from Data Obj ->"Settings" | :00406F87 6804B64500 push 0045B604 :00406F8C E847A60200 call 004315D8 :00406F91 B958094600 mov ecx, 00460958 After the 2 call of retriving our name and serial, the following calls doesn't interesed us, how do i know?because i trace into it before. So the above are all rubbish call that use to retrive data from registry but we are not interest in it. Trace until we get here: :00406F96 E845F7FFFF call 004066E0 ;this is the *important* call :00406F9B 833D5809460017 cmp dword ptr [00460958], 00000017 ;compare result with 17 :00406FA2 7524 jne 00406FC8 ;if not equal die :00406FA4 8B0D5C094600 mov ecx, dword ptr [0046095C] ;ecx=our name :00406FAA 8B41F8 mov eax, dword ptr [ecx-08] ;eax=length of our name :00406FAD 85C0 test eax, eax :00406FAF 7417 je 00406FC8 ;die if no name enter :00406FB1 8B1560094600 mov edx, dword ptr [00460960] ;edx=our serial :00406FB7 837AF812 cmp dword ptr [edx-08], 00000012;compare serial length with 12 :00406FBB 750B jne 00406FC8 ;die if length not equal to 12 :00406FBD 6A01 push 00000001 :00406FBF 8BCE mov ecx, esi :00406FC1 E8CE160200 call 00428694 ;close window :00406FC6 EB21 jmp 00406FE9 * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:00406FA2(C), :00406FAF(C), :00406FBB(C) | :00406FC8 6A00 push 00000000 :00406FCA 6A30 push 00000030 * Possible StringData Ref from Data Obj ->"Invalid registration number." | :00406FCC 68F0B74500 push 0045B7F0 :00406FD1 E862A30200 call 00431338 ;bad cracker die here! So from the above code we are only interest in call 4066E0, we will try to make [460958]=17. From above code we know that if we want to do a patch, patch it at 406faf & 406fbb, nop this two conditional jumps. But it is not done yet, we still need to patch inside the call 4066E0 so that it always return with [460958]=17. Now we trace into call 4066E0: * Referenced by a CALL at Addresses: |:00406522 , :00406F96 | :004066E0 6AFF push FFFFFFFF :004066E2 6888574400 push 00445788 ....... The above code follows by a lot of rubbish coding(i'll not gonna list them here), basically what it does is just retrieving data from registry, open certain free key in registry, convert all characters in name into capital letter and so on. But all those rubbish doesn't interest us, what we are interested is the following call : :004067A1 51 push ecx :004067A2 52 push edx :004067A3 57 push edi ;edi=our name :004067A4 50 push eax :004067A5 E8E6090000 call 00407190 ;This is a call that generate :004067AA 8B06 mov eax, dword ptr [esi] ;our real serial, the result is :004067AC 83C414 add esp, 00000014 ;stored in [esp+20] :004067AF 8378F812 cmp dword ptr [eax-08], 00000012 :004067B3 0F85FB000000 jne 004068B4 ;die if length of our serial !=12 :004067B9 8D4C2430 lea ecx, dword ptr [esp+30] :004067BD 6A04 push 00000004 :004067BF 51 push ecx :004067C0 8BCE mov ecx, esi :004067C2 E841BF0100 call 00422708 ;this call get first 4 key from :004067C7 8B10 mov edx, dword ptr [eax] ;our serial and return in [eax] :004067C9 C744244002000000 mov [esp+40], 00000002 :004067D1 52 push edx :004067D2 E899B80000 call 00412070 ;call that convert pushed string to hex no :004067D7 8B4C2424 mov ecx, dword ptr [esp+24] ;ecx=first part of real serial :004067DB 83C404 add esp, 00000004 :004067DE 3BC1 cmp eax, ecx ;compare first 4 char of serial :004067E0 0F8597000000 jne 0040687D ;die if not equal :004067E6 6A08 push 00000008 :004067E8 8D442420 lea eax, dword ptr [esp+20] ; :004067EC 6A05 push 00000005 :004067EE 50 push eax :004067EF 8BCE mov ecx, esi :004067F1 E80ABE0100 call 00422600 ;call that get 2nd part of our :004067F6 8B08 mov ecx, dword ptr [eax] ;serial, return as [eax] :004067F8 51 push ecx :004067F9 E872B80000 call 00412070 ;convert 2nd part of our serial :004067FE 8B542428 mov edx, dword ptr [esp+28] ;from string to hex from. :00406802 83C404 add esp, 00000004 ;edx=second part of real serial :00406805 33C9 xor ecx, ecx :00406807 3BC2 cmp eax, edx ;compare second part of serial :00406809 0F94C1 sete cl ;set cl if equal :0040680C 884C2413 mov byte ptr [esp+13], cl ;set marker for later use :00406810 8D4C241C lea ecx, dword ptr [esp+1C] :00406814 E853220200 call 00428A6C :00406819 8A442413 mov al, byte ptr [esp+13] :0040681D 84C0 test al, al :0040681F 745C je 0040687D ;die if marker not set :00406821 8D54242C lea edx, dword ptr [esp+2C] ;edx=third part of real serial :00406825 6A04 push 00000004 :00406827 52 push edx :00406828 8BCE mov ecx, esi :0040682A E85CBE0100 call 0042268B ;get last 4 key of our serial :0040682F 8B00 mov eax, dword ptr [eax] ;eax=third part of our serial :00406831 50 push eax :00406832 E839B80000 call 00412070 ;convert from string to hex :00406837 8B54242C mov edx, dword ptr [esp+2C] ;edx=third part of real serial :0040683B 83C404 add esp, 00000004 :0040683E 33C9 xor ecx, ecx :00406840 3BC2 cmp eax, edx ;compare third part serial :00406842 0F94C1 sete cl :00406845 884C2413 mov byte ptr [esp+13], cl ;set a marker for later used :00406849 8D4C242C lea ecx, dword ptr [esp+2C] :0040684D E81A220200 call 00428A6C :00406852 8A442413 mov al, byte ptr [esp+13] :00406856 84C0 test al, al :00406858 7423 je 0040687D ;die if marker not set :0040685A 8B36 mov esi, dword ptr [esi] :0040685C B12D mov cl, 2D ;cl='-' :0040685E 33C0 xor eax, eax :00406860 384E04 cmp byte ptr [esi+04], cl ;compare 5th char with '-' :00406863 0F94C0 sete al :00406866 84C0 test al, al :00406868 7413 je 0040687D ;die if 5th char is not '-' :0040686A 8A560D mov dl, byte ptr [esi+0D] ;dl=14th char of our serial :0040686D 33C0 xor eax, eax :0040686F 3AD1 cmp dl, cl :00406871 C644241301 mov [esp+13], 01 :00406876 0F94C0 sete al :00406879 84C0 test al, al :0040687B 7505 jne 00406882 ;die if 14th char is not '-' * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:004067E0(C), :0040681F(C), :00406858(C), :00406868(C) | :0040687D C644241300 mov [esp+13], 00 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040687B(C) | :00406882 8D4C2430 lea ecx, dword ptr [esp+30] :00406886 C7442440FFFFFFFF mov [esp+40], FFFFFFFF :0040688E E8D9210200 call 00428A6C :00406893 8A442413 mov al, byte ptr [esp+13] :00406897 84C0 test al, al :00406899 7419 je 004068B4 :0040689B C70317000000 mov dword ptr [ebx], 00000017 ;here will make [460958]=17 :004068A1 8B4C2438 mov ecx, dword ptr [esp+38] :004068A5 64890D00000000 mov dword ptr fs:[00000000], ecx :004068AC 5F pop edi :004068AD 5E pop esi :004068AE 5D pop ebp :004068AF 5B pop ebx :004068B0 83C434 add esp, 00000034 :004068B3 C3 ret *From the above code we know that the serial must be in the form of ????-????????-????. We also observe that it always set [esp+13] to 1 if test correct, this is because it will use [esp+13] in offset 406893 to test whether to make [460958]=17 or not, so we gotta make sure it is not in value '00'. So that we can pass thought offset 40689B sucessfully. *Also, as i mentioned the serial is divide and tested in 3 section, so we can type'd esp+20' to see the real serial. For exmaple by using name=yes123 [esp+20]=48 05 00 00 53 F3 AB 04 F8 14 05 48 =first part of real serial= 1352 in decimal 04 AB F3 53 =second part of real serial= 78377811 in decimal 14 F8 =third part of real serial= 5368 in decimal Remember the data in memory is reverse, so we have to reverse them to get the exact value. And remember also 2nd part must be 8 char and 3rd part must be 4 char. If it is less than the required number add a '0' infront of it. So our serial in above example = 1352-78377811-5368 (with name=yes123) Now we go into the real key-generating routine, *call 00407190* * Referenced by a CALL at Address: |:004067A5 | :00407190 64A100000000 mov eax, dword ptr fs:[00000000] :00407196 6AFF push FFFFFFFF :00407198 68A8584400 push 004458A8 :0040719D 50 push eax :0040719E 64892500000000 mov dword ptr fs:[00000000], esp :004071A5 53 push ebx :004071A6 8B5C2414 mov ebx, dword ptr [esp+14] :004071AA 55 push ebp * Reference To: KERNEL32.lstrlenA, Ord:02A1h | :004071AB 8B2D48824400 mov ebp, dword ptr [00448248] :004071B1 56 push esi :004071B2 8B742424 mov esi, dword ptr [esp+24] ;esi=place of real serial store :004071B6 57 push edi :004071B7 53 push ebx ;ebx='CLR Script' :004071B8 33FF xor edi, edi ;edi as a counter :004071BA C70600000000 mov dword ptr [esi], 00000000 :004071C0 FFD5 call ebp :004071C2 85C0 test eax, eax :004071C4 7E1C jle 004071E2 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004071E0(C) |routine that encode 'CLR Script' :004071C6 8B06 mov eax, dword ptr [esi] ;esi=place of real code stored :004071C8 53 push ebx :004071C9 8D0480 lea eax, dword ptr [eax+4*eax] :004071CC 8D04C0 lea eax, dword ptr [eax+8*eax] :004071CF D1E0 shl eax, 1 :004071D1 8906 mov dword ptr [esi], eax :004071D3 0FBE0C1F movsx ecx, byte ptr [edi+ebx] :004071D7 03C8 add ecx, eax :004071D9 47 inc edi :004071DA 890E mov dword ptr [esi], ecx :004071DC FFD5 call ebp :004071DE 3BF8 cmp edi, eax :004071E0 7CE4 jl 004071C6 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004071C4(C) | :004071E2 8B06 mov eax, dword ptr [esi] :004071E4 33D2 xor edx, edx :004071E6 B910270000 mov ecx, 00002710 :004071EB F7F1 div ecx :004071ED 8D442428 lea eax, dword ptr [esp+28] :004071F1 8916 mov dword ptr [esi], edx *Until here we know that the above code does some encode on 'CLR Script', the outcome is always the same (independent of name entered). in this case [esi]=48 05, which is first part of our real serial, so we now know why it is always constant. But why he want to use constant string 'CLR Script'? because all products of this company uses the same key-generating routine, the only thing different is the first part, which he can use the program name to differ it, so that same routine will produce different key on different programs(products). :004071F3 8B542424 mov edx, dword ptr [esp+24] :004071F7 52 push edx :004071F8 50 push eax :004071F9 E8C2FEFFFF call 004070C0 :004071FE 83C408 add esp, 00000008 :00407201 8D4C2428 lea ecx, dword ptr [esp+28] :00407205 C744241800000000 mov [esp+18], 00000000 :0040720D E8211D0200 call 00428F33 :00407212 8B4C242C mov ecx, dword ptr [esp+2C] ;ecx=place to store result :00407216 8B7C2428 mov edi, dword ptr [esp+28] ;edi=our name :0040721A 33D2 xor edx, edx ;edx as counter :0040721C C70100000000 mov dword ptr [ecx], 00000000 ;clear ecx :00407222 8B47F8 mov eax, dword ptr [edi-08] ;eax=length of name :00407225 85C0 test eax, eax :00407227 7E20 jle 00407249 ;die if no name entered * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407247(C) ;here start the keygen routine | :00407229 8B01 mov eax, dword ptr [ecx] ;eax=code :0040722B 8BD8 mov ebx, eax ;ebx=eax :0040722D C1E303 shl ebx, 03 ;left shift ebx by 3 :00407230 2BD8 sub ebx, eax ;ebx=ebx-eax :00407232 8D0498 lea eax, dword ptr [eax+4*ebx];eax=eax+4*ebx :00407235 D1E0 shl eax, 1 ;left shift eax by 1 :00407237 8901 mov dword ptr [ecx], eax ;store eax :00407239 0FBE1C17 movsx ebx, byte ptr [edi+edx] ;ebx=next char in our name :0040723D 03D8 add ebx, eax ;ebx=ebx+eax :0040723F 42 inc edx ;increase counter :00407240 8919 mov dword ptr [ecx], ebx ;store ebx :00407242 8B47F8 mov eax, dword ptr [edi-08] ;eax=length of name :00407245 3BD0 cmp edx, eax ;compare counter with name length :00407247 7CE0 jl 00407229 ;jump if less than I think the routine is quite straight forward and easy to understand, since i already put remark on it so i wonn't explain them here, but i'll give overall summary bottom part of easy. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00407227(C) | :00407249 8B01 mov eax, dword ptr [ecx] ;eax=code :0040724B 33D2 xor edx, edx ;clear counter :0040724D BF00E1F505 mov edi, 05F5E100 ;edi=100000000 :00407252 C7442418FFFFFFFF mov [esp+18], FFFFFFFF :0040725A F7F7 div edi ;eax=eax/100000000 :0040725C 8911 mov dword ptr [ecx], edx ;ecx=modulus value :0040725E 8B06 mov eax, dword ptr [esi] ;eax=first part serial :00407260 0FAFC2 imul eax, edx ;multiple 1st and 2nd part serial :00407263 33D2 xor edx, edx ;clear edx :00407265 B910270000 mov ecx, 00002710 ;ecx=10000 :0040726A F7F1 div ecx ;eax=eax/10000 :0040726C 8B442430 mov eax, dword ptr [esp+30] :00407270 8D4C2428 lea ecx, dword ptr [esp+28] :00407274 8910 mov dword ptr [eax], edx ;store the modulus value :00407276 E8F1170200 call 00428A6C :0040727B 8B4C2410 mov ecx, dword ptr [esp+10] :0040727F 5F pop edi :00407280 5E pop esi :00407281 5D pop ebp :00407282 64890D00000000 mov dword ptr fs:[00000000], ecx :00407289 5B pop ebx :0040728A 83C40C add esp, 0000000C :0040728D C3 ret ---[ OVERALL CONCLUSION OF KEY GENERATING PROCESS ]-------------------------------------------- The above routine does 4 things: 1.1st part key=1352 2.encode our name and take modulus with 100000000 to get 2nd part key 3.(multiple 1st and 2nd part key)modulus 10000 to get 3rd part key 4.final key = (1st part key)-(2nd part key)-(3rd part key) Eg:name = yes123 serial = 1352-78377811-5368 ---[ C-LANGUAGE CODE ]------------------------------------------------------------------------- #include <stdio.h> #include<conio.h> #include <stdlib.h> int main(void) { unsigned long code=0,temp=0; int count1,count2; char name[25]; clrscr(); textcolor(14); cprintf(" __,__\r\n"); cprintf(" / \\\r\n" ); cprintf(" vvvvvvv /|__/|\r\n"); cprintf(" I /O,O |\r\n"); cprintf(" I /_____ | /|/|\r\n"); cprintf(" J|/^ ^ ^ \\ | /00 | _//|\r\n"); cprintf(" |^ ^ ^ ^ |W| |/^^\\ | /oo |\r\n"); cprintf(" \\m___m__|_| \\m_m_| \\mm_|\r\n"); textcolor(10); cprintf("================================="); textcolor(11); cprintf("\r\nKeyGenerator for CLR Script v1.23"); textcolor(10); cprintf("\r\n================================="); printf("\nCracked by "); textcolor(14); cprintf("%c%c%c",0x10,0x10,0x10); textcolor(12); cprintf("Yes123"); textcolor(14); cprintf("%c%c%c",0x11,0x11,0x11); printf(" - April 1999"); printf("\n\nEnter register name = "); scanf("%[^\n]",name); for(count1=0;name[count1]>0;count1++){ if ((name[count1]>=97) && (name[count1]<=122)) name[count1]=name[count1]-32; } code=name[0]; for(count1=0;name[count1+1]>0;count1++){ temp=code; code=(code<<3)-temp; code=code*4+temp; code=code<<1; code=code+name[count1+1]; } code=code % 100000000; printf("Your registration key = 1352-"); for (temp=10000000;!(code/temp);temp/=10) printf("0"); printf("%ld-%d",code,(code*1352)%10000); return ; } ---[ LAST ]----------------------------------------------------------------------------------- I know my english expressing ability is weak, so in many place i know what's happening but i cann't express well or explain to you clearly. This key-generating algoritherm basically is easy, but the most tricky part is that it has many unknown call that confusing you, so we have to be wise and able to determine which call is important, which call is rubbish call. This has to be train by practising more, this is what so call 'cracker sence'! After cracking this program, the other program from the same company also use the same keygen routine, we only need to change the first part encode string 'CLR Script' will do. Thanks for reading my keygen tutorial. ---[ THAT'S ALL FOLKS ]----------------------------------------------------------------------- __,__ / \ vvvvvvv /|__/| I /O,O | I /_____ | J|/^ ^ ^ \ | |^ ^ ^ ^ |W|Yes123 '99 \m___m__|_|[ghoste@rocketmail.com]vetica"><FONT COLOR="#000000"><B>REMEMBER,</B>I'm