老外给这个CRACKME写的破文
1. Main()
Firing up the executable in OllyDbg (OEP - 0x00401000) raises a couple of eyebrows. A PUSHAD/CALL to a function that increases the stack pointer and rets into a JMP over a bunch of NOPs. It looks like the author had a lot of fun writing this program.
The next thing we see is a dialog box being created through the user32.DialogBoxParamA function with the template name set as "IsDebugPresent" (hmmmm).
The window event processing loop follows.
1.5. Entering in your Name/Key
Enter in any values you want here. Press the RegMe button and place a breakpoint on one of the two addresses listed in the next section.
2. EventProcessing()
None of the information in this function actually matters except for the part where the values from the YourName/YourID are copied into memory.
On lines 0x0040112F and 0x00401145 we see two sequential XXXXXXXXXtDlgItemTextA's. These corresponding to the copying of the name and key into memory.
Following our previous copies is an all too familiar call to XXXXXXXXXXXrtualProtect making the .text section writable. And so begins the morphing code.
3. Anti-Debugging and Morphing Code
The next sections of code, beginning at 0x004011C5 were created to transform sections of the code which appear to be data into actual executable code. I will not discuss every section in detail as most are just simple XORs or additions of hardcoded constants. Instead, I will try to explain the general program flow on a higher level.
0x004011E3 and 0x00401205 are both LOOPD-based loop statements that transform some of the .text section into more meaningful statements.
Past these two loops we will see two calls to XXXXXXXXXXXtLocalTime (0x0040120C & 0x00401230).
At 0x0040124C the milliseconds of the local times are compared. If they are not equal, which implies that we must have been single-step debugging through the section, then the program halts.
Since there is absolutely no reason to single step this section (as it simply and trivially converts more sections of code) then place a breakpoint at 0x0040124C where the compare occurs.
The compare will succeed. A new call will be generated on the spot in the following loop, the program will return to EventProcessing() and then do a modified return into code at 0x00401358.
Put a breakpoint on Call ECX at 0x00401386.
ECX is a call to a far call containing user32.BlockInput which does a "int 2e" to disrupt debugging.
To circumvent this check, trace into Call ECX, trace into the far call, and NOP out the call at line 0x77D9C648 (Windows XP SP2) inside user32. This will force nothing to happen and the code to return from the call like everything went fine.
Returning from our BlockInput call, we will find data translating. Place a breakpoint at 0x00401429 to skip past all this nonsense.
4. Last Tricks
We are now in the final code section.
Ignore the weird compares and the always weird code translations.
We see another Call ECX at the bottom. This was a dangerous part of the code last time, and its a dangerous part of the code this time. Place a breakpoint at this address (0x004014CA) and see what it finally translates to.
OutputDebugStringA(%s%s) the classic OllyDbg 1.10 crash bug. Just NOP out this entire call and continue on your merry way.
5. Name/Key initial checks
After a modified inline strlen() equivalent function, the name and key are both checked for length.
The name length check is at 0x004014F0
The key length check is at 0x00401543
If the name is greater than or equal to 10, it passes.
If the key is greater than or equal to 11, it passes.
Failure of either check causes another call to OutputDebugStringA(%s%s).
6. Final Verification
The final verification appears at 0x00401572. In a high level view you take a 4 DWORDS at different location from the username and add them together into a single DWORD. The same process is performed with the key but from different locations.
The resulting DWORD values, or hashes, are then summed with 2 different constants (which are actually locations in the code). If the 2 hashes match, then you entered a valid combination and a message box is pop'd up.
0x004015DB is the final compare.
Here is the equation that needs to be solved to generate keys for names.
n_1 + (2^8*n_2) + (2^16*n_3) + (2^24*n_4) +
n_2 + (2^8*n_3) + (2^16*n_4) + (2^24*n_5) +
n_6 + (2^8*n_7) + (2^16*n_8) + (2^24*n_9) +
n_3 + (2^8*n_4) + (2^16*n_5) + (2^24*n_6) + c_n
=
k_1 + (2^8*k_2) + (2^16*k_3) + (2^24*k_4) +
k_5 + (2^8*k_6) + (2^16*k_7) + (2^24*k_8) +
k_6 + (2^8*k_7) + (2^16*k_8) + (2^24*k_9) +
k_4 + (2^8*k_5) + (2^16*k_6) + (2^24*k_7) + c_k
Where
- n_i is the ith character in the name
- c_n is the name's constant
- k_i is the ith character in the key
- c_k is the key's constant
All of the n_i's are given by the user entered in his name. Which leaves the k_i's to be solved for. Since there are 9 variables and 1 equation, there is no simple solution for this problem.
Instead one should set the values of all of the key's characters (k_i) to set numbers and solve for the last k_i. Unfortunately, since characters can only express 2^8 different values, we can't simply set all characters to 0, and k_1 to the difference between the name hash and the key hash.
Also note that general equations have many solutions. So the user 'RedStripeBeer' will have many multiples of valid keys associated with it.
7. Algorithm Realizations
By looking at the key equation above, you will first notice that although the key needs to be 11 characters in length, k_0 and k_10 are not used for the calculation. Set these values to whatever you want.
The next thing you should notice is that each character affects a different portion of the hash based upon the position they are pointed inside the DWORD.
For instance, k_1 (since it only appears once and in the lowest byte) can only affect the hash by +255 bits. k_9, on the exact opposite of the spectrum, is being multiplied by 2^24 since it appears only once in the highest byte. This means that k_9 can affect the overall hash by (2^24 * 255).
Also you should see that some characters, such as k_7 appear affect the total hash by (2^24+2^16+2^8 * 255) which is very powerful.
8. My Solution
a. Start with the key X000000000X.
b. Compute the hash of the key and the name.
c. Calculate the difference (name_hash - key_hash).
i. If the difference is negative, we need to make it positive. The way to do this is key adding large values until the DWORD overflows into positive.
d. When the difference becomes positive, divide the difference by the strength of the character.
e. Increase the character by this multiple.
f. Repeat (a) with the new key.
9. Notes/Curiosities/Anger
This program was coded for an Asian language set. This presented many challenges in finding valid characters to use for generating the key. One method I used was to alternate between powerful characters to spread the changes across the key. This prevented single characters from reaching the 254 value and ruining the algorithm.
Also, the MessageBox that pops up in the end appears to look like garbage. Are these poorly translated Asian characters? I have no way of telling.
10. See the README.txt for the solution keygen.
Have fun,
TCM