Welcome to this updated tutorial. Using WinImage as my target I'm going to take you through all of the steps necessary to produce a key generator. The generation routine used here is fairly typical in complexity to that which you'll encounter a lot in reasonable shareware software. The actual comparison itself is not very well concealed and the good code is echoed in EAX but lets not dwell on that, instead we'll start the program and locate *Registering* from the Options menu. Lets immediately start our intrusions with SoftICE, you'll find that >bpx GetDlgItemTextA works well. It might also be advantageous to also have a dissassembly listing of winimage.exe. Allowing the required 2 breaks should gain you a convenient entry point at address 004290A7 and a few F10 steps away lies our protection.
:004290AE CALL 0042CD83 <-- Below this function lies
the protection scheme.
:004290B3 MOV ECX,[0043C800] <-- Set ECX=0.
:004290B9 XOR EDX,EDX <-- Clear EDX.
:004290BB ADD ESP,0C <-- Correct stack.
:004290BE CMP EAX,EDX <-- EAX's value is important here.
:004290C0 MOV [0043C5E8],EAX <-- Flag [0043C5E8] with value of EAX.
:004290C5 JZ 004290CD <-- Jump_bad_code, skipping the flag.
:004290C7 MOV [0043C7C4],ECX <-- Flag [0043C7C4] good but is there any need.
:004290CD CMP [0043C7C4],EDX <-- Pointless check or so it seems.
:004290D3 MOV DWORD PTR [0043CA54], ECX <-- Set [0043CA54] = 0.
:004290D9 JNZ 004290E0 <-- This looks like bad implementation.
:004290DB MOV [0043CA54],EAX <-- Flag [0043CA54] good using value of EAX.
:004290E0 PUSH 01
:004290E2 CMP EAX,EDX <-- The real check.
:004290E4 POP ESI
:004290E5 JNZ 00429113 <-- Jump_good_buyer.
Lets take a good look at this code snippet. After our protection scheme call the value of EAX is very important, a good code sets EAX=1, else it will be 0. Address 004290B3 looks like a compiler fixup (using a flag to zero a register seems inefficient but maybe as I suggest later its not). EDX is then zeroed by the XOR at 004290B9, before being compared with the value of EAX at 004290BE. The JZ 004290C0 acts as the first check, if your code was good then the program flags [0043C7C4] to the value of ECX (which we know at this stage is 0), if not we skip.
Continuing onwards, the JNZ check at 004290E0 looks like somethings gone awry with the protection scheme, why?, well after our protection scheme is called both EDX and our flag [0043C7C4] are always set to 0, so this JNZ can't possibly filter out any bad codes because its always comparing 0 with 0, therefore the code will never jump over 004290DB. In fact there is nothing wrong with this theory, but at this code we appear to have 3 flags (addresses [0043C5E8], [0043C7C4] & [0043C800]) which are either always 0 or when they are set it doesn't seem to matter.
In fact those of you with reversing minds will realise that the most likely culprit is not an inefficient compiler but another location which checks these flags. You could sift the disassembly blindly searching for them, that would work, but investigating the callers of 0042CD83 is the wiser move.
Lets return our mind to the real goal, ascertaining how WinImage transforms our User Name into a Registration Code. Tracing below 0042CD83 is definately the way to go as its the only call that could possibly hide the calculation routines. 1 level down call 0042CC87 gets the length of the user name and uses the API call CharUpperA to uppercase it, after returning we have an elementary length check for 0. Tracing onwards you must also step into call 0042CCB3, below you'll find the main calculation routine.
:0042CD05 PUSH 27 <-- Interesting pushed value.
:0042CD07 POP EDI <-- EDI=27.
:0042CD08 MOVZX EDX, BYTE PTR [ECX+ESI+03] <-- EDX = value of names first letter (upper case).
:0042CD0D LEA EAX,[ECX+03]
:0042CD10 IMUL EDX,EDI <-- EDX = EDX*EDI.
:0042CD13 ADD [EBP-04],EDX <-- Store the result, eventually the good_code_location.
:0042CD16 PUSH 0E <-- And another interesting push.
:0042CD18 CDQ <-- Clear EDX.
:0042CD19 POP EBX <-- EBX=0E (and pop).
:0042CD1A IDIV EBX
:0042CD1C TEST EDX,EDX <-- Test EDX=0.
:0042CD1E JZ 0042CD25 <-- No_jump_on_most_occasions (IMUL EDI,7 if it does).
:0042CD20 LEA EDI,[EDI*2+EDI] <-- EDI = EDI*3.
:0042CD23 JMP 0042CD28 <-- Unconditional.
:0042CD28 INC ECX <-- Loop counter.
:0042CD29 CMP ECX,[EBP+08] <-- Compare_for_end_of_name.
:0042CD2C JL 0042CCF9 <-- Jump_until_name_end.
Although this routine is fairly long, there is nothing particularly complex about its mathematics and we can now set about ripping the required code for our key generator. When the loop completes the good code can be found at 0042CD2E in EAX (note the format), but continue onwards and you'll notice several other interesting things. Before doing the actual compare our good result will first be tested for B8DCDD26, I just wonder what name actually produces that result, its not unheard of for authors to code in names which they don't ever want to register, I tried a few well known ones such as *Phrozen Crew, UCF* but evidently they are acceptable.
A little further you'll see that the 2 strings are actually compared using the function strcmp (exported from crtdll.dll), so this scheme could have been reversed by just loading crtdll exports into SoftICE and >bpx strcmp. There's also some more surprises, after this first compare the program will go on and generate a further 5 codes, of which only some will be valid. In my key generator I only output the first 2, although adding the remaining 3 shouldn't prove any great trouble.
Any programming language (I choose Pascal).
OK, lets begin, this tutor will teach you about keygens. Firstly a few words about this program, it uses multiple registraion codes, 5 to be exact.. however you only need to enter any one of them to register the program. Also this program gets a constant and adds it to the calculations from your name. It also converts your name serial into Hexadecimal, sort of, it will actually switch the 'B' and '8' around. Interesting.
Lets go!, fire UP SoftICE and set a breakpoint on 'hmemcpy', enter your fake name and serial and press enter. After some tracing we will get to this point:
:004346E8 CALL USER32.CharUpperA, Ord:002Fh <-- Capitalizes
all letters in our name.
:004346EE PUSH [ESP+04]
:004346F2 CALL 0041F989
:004346F7 POP ECX
Simple enough, this just capitalizes the user name, nothing big here.
:00434721 CALL KERNEL32.lstrlenA, Ord:0308h <-- Gets length of user name. :0043474B PUSH 00000027 <-- Set EDI = 27h, important in the function below. :0043474D POP EDI
Sure enough after some more tracing we come to this... the utilisation of the name.
:0043474E MOVZX EDX, BYTE PTR [ESI+ECX+03] <-- Get character. :00434753 LEA EDX, DWORD PTR [ECX+03] <-- EAX = position of name+3 meaning the current position in the string. :00434756 IMUL EDX, EDI <-- EDX = ASCII of current character multiplied by value of EDI. :00434759 ADD DWORD PTR [EBP-04], EDX <-- Add EDX to "constant", I will show you how to get the constant later. :0043475C PUSH 0000000E :0043475E CDQ :0043475F POP EBX :00434760 IDIV EBX :00434762 TEST EDX, EDX <-- Do stupid maths. :00434764 JZ 0043476B :00434766 LEA EDI, DWORD PTR [EDI+2*EDI] <-- EDI = EDI*3 :00434769 JMP 0043476E <-- Just follow this. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00434764(C) :0043476B IMUL EDI, 00000007 <-- Ignore this. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00434769(U) | :0043476E INC ECX <-- Increase position. :0043476F CMP ECX, DWORD PTR [EBP+08] <-- Check if end of name. :00434772 JL 0043473F <-- If not... repeat.
Now.. simple.. EDI does NOT get reset to 27h each pass of the loop.. instead, it triples on each consecutive loop.... Now how to get the constant? well if we use a 3 or 4 letter name, and write down what it adds each time then we get the total of the new value and subtract everything that was added to it we get the constant.. pretty simple.. it just repeats the above until it gets to the end of your name!
How to do this? simple.. when you get to the below line.. just type "? edx" and write this value down.
:00434759 ADD DWORD PTR [EBP-04], EDX
For those of you too lazy.. the constant is: 47694Ch
Now, this number generated is your first serial (not the constant, but the result of all the maths), with one exception.. It does its crazy switch with the 'B' and "8" now.
:00434806 LEA EAX, DWORD PTR [EBP+FFFFFF00] <-- String hex value before conversion. :0043480C PUSH EAX :0043480D LEA EAX, DWORD PTR [EBP+FFFFFE00] :00434813 PUSH EDI :00434814 PUSH EAX :00434815 CALL 0043477C <-- Call 'crazy' converter. :0043481A POP ECX :0043481B POP ECX :0043481C PUSH EAX * Reference To: CRTDLL.strcmp, Ord:01CFh <-- Compare strings, like your fake serial and the REAL one.
OK, simple... all it does is take the value of the result of the additions to the string and puts them into a HEX value. Next it uses the 'crazy' call and switches the B and the 8 in hex, see examples:
12345B would become 123458
17A988 would become 17A9BB
I will explain the crazy function in a bit.
Now i will explain the way to get the other serials. Follow below.. It is important to know that EDI starts off as the value of the result of the maths with our name.
:0043482C LEA EAX, DWORD PTR [EBP+FFFFFF00] :00434832 PUSH EAX :00434833 LEA EAX, DWORD PTR [EDI+14051948] <-- Add this constant to EDI. :00434839 PUSH EAX :0043483A LEA EAX, DWORD PTR [EBP+FFFFFE00] :00434840 PUSH EAX :00434841 CALL 0043477C <-- Call the crazy funtion again. :00434846 POP ECX :00434847 POP ECX :00434848 PUSH EAX * Reference To: CRTDLL.strcmp, Ord:01CFh <-- Another string compare.
| OK all it does is add a constant to the result of the maths on our name..it then changes the B and 8 like above by calling the crazy function again. This process repeats for all 5 possible serials.. I wont comment the rest because they are all similar, but I did highlight them for easy access :).
:0043485B LEA EAX, DWORD PTR [EDI+17061954] :00434869 CALL 0043477C * Reference To: CRTDLL.strcmp, Ord:01CFh :00434871 CALL CRTDLL.strcmp :00434883 LEA EAX, DWORD PTR [EDI+10051981] :00434891 CALL 0043477C * Reference To: CRTDLL.strcmp, Ord:01CFh :00434899 CALL CRTDLL.strcmp :004348AB LEA EAX, DWORD PTR [EDI+04011995] :004348B9 CALL 0043477C * Reference To: CRTDLL.strcmp, Ord:01CFh :004348C1 CALL CRTDLL.strcmp
Pretty simple eh?, now the below code is my so called 'Crazy function'. Basically, this function moves the HEX value into a string and compares the characters one by one to B or 8, and if either is found it switches them.
:00434798 MOV AL, BYTE PTR [EBP-10] <-- Load first character into AL. :004347A7 CMP AL, 38 <-- Compare AL to '8'. :004347A9 JNZ 004347AF <-- If not 8 continue. :004347AB ADD AL, 0A <-- If it is 8 convert it to B and follow next jump. :004347AD JMP 004347B5 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004347A9(C) | :004347AF CMP AL, 42 <-- Compare to B. :004347B1 JNZ 004347B5 <-- If it isn't B continue. :004347B3 ADD AL, F6 <-- If it is B convert to 8 by adding F6h. * Referenced by a (U)nconditional or (C)onditional Jump at Addresses: |:004347AD(U), :004347B1(C) | :004347B5 MOV BYTE PTR [ESI], AL <-- Move modified character over old character in string. :004347B7 MOV AL, BYTE PTR [ECX+ESI+01] <-- Get next character. :004347BB INC ESI <-- Increase position marker. :004347BC TEST AL, AL <-- Test for end of string. :004347BE JNZ 004347A7 <-- If more characters, loop.
OK, I hope you can put this all together.. I will also include my source code for my keygen below. I'm a terrible programmer.. but it works! btw, I only generated 3 of the five serials.. can you do the other two?.
I hope to see you again in Flu[X] tutor #9. As always if you like a program buy it! This essay is for educational purposes ONLY! Software authors deserve your support!
http://tuts99.cjb.net - Webpage.