Sorry this update took so long. This update is also a bit longer and has a little less guidance as it will simply require you to make use of some of the previous lessons to build your C code.
Let’s get to work on the rest of the key generation algorithm. First off, remember we have to call subChangeQWORD() four times and skip every 9th letter. The disassembly at 0x40111A shows this happening but it’s inlined instead of looped. The programmer wrote this in straight assembly but this may have been written as a loop that was unwrapped by the assembler to improve performance at the cost of a larger binary.
Anywho, here’s our new main() that will operate on the full 35-character serial instead of just the one segment we did previously:
Well, that was quite a diversion. Back in Part I we’d started with the code just before this, labeled GetTextBoxSerial.
We renamed a value to NameSum and then just moved on to see what was going on with the serial. Well, now it’s time to get back to that and figure out what’s going on with the username. Notice that between those two loops, there are seven lines of code and two of them change EAX before saving it off somewhere. These are values being saved for later so they should be renamed so we notice them when they’re used later. Ditto for what IDA has just called “String” here. We know “String” is the username value from the dialog box.
I’m not always consistent on this (and I really should be) but I will often use Hungarian notation for renaming variables in disassembly; it’s just easier to keep track of things. So, NameSum -> szUsernameSummed and dword_40307C -> szUsernameMultiplied. (We’re not renaming “String” here because, if you look further down in the code, you’ll see that the same memory location is re-used for the serial number; it’s just a placeholder for parameters.)
So here’s where IDA’s graph view can sometimes be deceiving. Take a look at the first several lines in the location we’ve labeled GetTextBoxSerial. After more operations on register values that are still related to the username, EAX and EDX are saved off to memory. At first glance, it would have been easy to just gloss over this, assuming it’s part of the serial number operations.
Let’s rename those variables as well, to ensure we know it’s still operating on the username: dword_403080->szUsernameDivided and dword_403084->szUsernameANDed. Also, be aware that szUsernameDivided was modified based on the ROL’d value of EAX in the previous loop: div ecx divides ECX by the value in EAX and stores the value in EDX, which is then saved off to the memory location we just renamed.
Okay, pause for a second. Have you noticed anything about the values derived from our username and serial? Each of them leads to four distinct values saved off into memory. We’ve just renamed the values resulting from the username alterations. Take a look at GetTextBoxSerial and notice that after each call to subChangeQWORD, the result is saved. Time to rename a few more things: I just use szSerialNum_alt[x] where [x] is consecutive 1-4 (alt as in altered).
As is often seen in software serials, the serial is probably derived from the username so we can expect to see each string’s four values involved in a mathematical operation with the other string’s values. Looking a bit further down, at 0x40116B, you’ll notice several calls to some other functions we haven’t looked at yet. There are also several JNZ instructions based on the checking of some register’s value. Look a bit below those jumps to see the call to MessageBoxA for a correct serial. If we were simply interested in cracking this, we could just change those JNZ instructions to NOPs and every username/serial combo would work.
Our goal, however, is to write a keygen. Let’s take a look at sub_4011F1:
So, it’s a function with two parameters that calls two other functions, twice each, consecutively. The first call to each function passes as arguments some of the arguments sub_4011F1* got from *DialogFunc and/or a couple local variables. The second call passes some of those arguments again (though they could have been modified by the called function) as well as parts of our serial.
Essentially, this function doesn’t need to exist as a separate function but let’s build out a skeleton for good measure. We can combine it with main() later if we want to. If you look back at IDA’s graph view, you can see that this could probably just be inlined in a loop. It’s easier to just do one thing at a time:
Looking ahead in the, we can see that the arguments being passed into sub_40128A and sub_401269 are likely ints, which is why I went back and commented out the chars in favor of int pointers. Let’s add function declarations for those other functions.
We’ll also need to adjust the main() function so that subChangeQWORD()’s returned value is saved in one of the szSerialNum_alt variables. I just used a switch based on the iterations variable:
Those other two functions need to be written as well. Notice at the very beginning and end of the function the instructions pusha, and popa. Right off the bat, this tells us that the register values are irrelevant at the end of this function. However, the other catch to look out for is saving data to memory, like the two mov statements that store values in [edi] and [edi+4]. Keep these in mind as they’re essentially serving as pointers for variables.
Now it’s time to get cracking on that sub_40128A(). If you’re a math nerd, this function may bear a resemblence to the formula seen in the strings listing. Since that was covered in another solution, we’ll ignore that and just go about this without the benefit of recognizing the definition of the Multiplication of Complex Numbers in x86 assembly.
Here’s the code from sub_40128A() (sans preamble and cleanup):
This is pertty simple code so let’s just walk through it quickly. Those first three lines are just moving the three parameters into registers. The next two lines tell you that arg_4 and arg_8 are pointers. The next line just multiplies them together.
Next up, we’re getting two more numbers from memory, just offset by +4 from the pointers we already have and multiplying them together too. Then, subtract the result from the previous result and save that final result.
The last few operations are similar. Get some numbers, multiply them together in pairs but add those results this time. The primary thing to take note of, if you can follow which registers are storing what where, is that this second pair of imul operations is operating on the same numbers as before but multiplying them in a different order. Then the last thing before the function cleans up is to save this result right next to the previous one in memory.
The asm code used three parameters since it was operating on pointers, two pointers to cover the four integer values and one to provide a memory location in which to save the resuls. It’ll be easier for us to just use integers and return an array/pointer. However, this function is called from the one at 0x4011F1 so we’ll have to go back and fix the parameters used in those calls. For now, we can just comment them out until we decide whether to keep sub_4011F1() or not.
Here’s the entirety of algorithm.c up to this point. You can uncomment those first few lines in main() to test our new function if you like:
Alrighty, so what’s next? How about that other function doing some math…
Well that was simple. So, we’ve got two functions that require four arguments. Well, if you think back to a little piece in Part II, you’ll see where those areguments are coming from. It’s the username! We didn’t even bother to code that out. Go back to Part II and review the bit of code where we named a value NameSum (around 0x4010B5).
I called this function GetNameValues(). The code below is not commented to follow the assembly as there is nothing particular complicated about it. I do recommend you attempt to create this function yourself and just return here for reference. There are several gotchas you’re more likely to learn about and recognize when you get stuck on them.
If you do try to do this on your own, you can compare your function’s results against mine. For “username” as the char * name parameter, results should look like this:
We’re almost done! Now, all we have to do is tie a couple functions together to get the actual serial number. Looking at the code, you should be able to see which functions we’ve created can just be dropped and which we’re using.
For reference, the serial number generated for “username” is:
Again, I recommend you attempt to find this all on your own by following the code. Immediately after the section that you used to create the above function, you’ll have all the information you need to complete our keygen.
It’s a bit anticlimactic, but that’s it! We’re done!