Duelist Crackme #1 Tutorial

Duelist Crackme 1 Tutorial
Original Site
http://www.woodmann.com/RCE-CD-SITES/Quantico/crackme%27.htm
Supplemental Reading
http://www.swansontec.com/sregisters.html
http://www.intel.com/products/processor/manuals/
Chapters 1-4 of Hacker Disassembly Uncovered
http://en.wikipedia.org/wiki/Microsoft_Windows_library_files
http://en.wikipedia.org/wiki/Native_API
Required Knowledge
How to use IDA Pro
Basic knowledge of IA32
My Methodology
**It may help to run this through a debugger such as Immunity or Olly while reading through the code in
IDA. That way you can see the process as it happens.
1.) The first thing I did was run this through IDA so I could get a better grasp of what I was looking at.
2.) If you run the program you see that it is a simple keygen box that accepts a key and varifies that the
key is correct. It is this verification we want to circumvent so our first goal is to find the section of code
responsible for verification.
3.) My starting point was to look up the imports, on the imports tab of IDA Pro, and find a function that
would grab user input. If you read the wiki article that talked about user32.dll you might conjecture that
the input will be there since we are looking at a standard window. Sure enough, a little ways down you
will find the function GetDlgItemTextA. (The A means ASCII. There is support for unicode as well but it is
much slower and rarely used.)
4.) Use the xrefs function to find the one call to GetDlgItemTextA in the program. Go ahead and look the
function up on MSDN if you haven't already. We immediately see that the call accepts a string of length
24h and data is placed in a buffer called string.
5.) Directly after the call EAX is 0ed out (Tip: EAX usually contains the return value of a function. That
doesn't matter here because the function put the user's string in the string buffer.) What follows is what
we can identify as a loop. After the xor is a comparison of our user input string with an offset of EAX
(which we just 0ed out) and 0. This is directly followed by a jz. If you look on ahead we can determine
that if the jump is taken our loop ends. What we're probably looking at here is a loop that is going to
iterate over our string and terminate once it has covered the entire string. Essentially, what it's with that
compare followed by a jmp is test for a null byte. In a higher language it might look like this:
int x = 0;
while(string[x] != '\0') {
string[x] ^= 0x43;
string[x] ^= 0x1E;
string[x] ^= 0x55;
x++;
}
6.) This is followed by a series of XORs. Let's say we entered the string test so let's see by hand how the
XORs transforms our string. test in binary is: 01110100011001010111001101110100. And we are xoring
that with 01000011 (zero extended). We expect to see something like 110111 or 37h. In this case that is
7 ascii. Let's go ahead and repeat that process for the other xors. You should come up with |m{|.
7.) The test for our null byte returns true and the loop is taken. This is directly followed by another
comparison of EAX and 0. (Look up the compare operator in the Intel manuals if you don't already know
how it works.) If you were watching closely you'll notice that EAX should have the length of our string.
Essentially, this is just making sure that the user didn't enter nothing.
8.) Assuming we entered test let's go to the next block. We see a function that pushes 24h, an unknown
piece of data in some buffer, and our modified user string. We don't know what the 24h is until we see it
used but we can probably infer from the other data that this is going to be some sort of check as to
whether we entered the proper string. If we go ahead and take a look at the unknown string we can see
it contains: {aexdm&kzikcem&<&fm [7Fh this character isn't printable] jam{&jq&l}mda{|0. Chances are
this is the string we need to match.
9.) We know that at this point three arguments should have been pushed onto the stack. Arg3 = 24h,
Arg2 = offeset_to_checkstring, Arg1 = offset_to_string. (Remember it is c/c++ convension to push
arguments onto the stack from right to left. That way the first argument is on top.
10.) Next let's see what is in our function. Enter is called with arguments 0 and 0 so we can assume no
dynamic storage is needed for our stack.
11.) The next part is a little tricky and requires that you were either paying very close attention to what
was happening or you need to know the conventions for how a function is called (which you should
definitely learn if you don't know them already). Hacker Disassembling Uncovered is a great resource for
that. 1 gets moved into EAX. EBP (the base pointer) + arg_0 (which contains 8) is moved into EDI
(Destination index). Well, what's on ourstack at this point? If you know your function proceedures you'll
remember that at this point a pointer to the saved stack pointer and the return address come before
EBP+8 which is our first argument (in this case the user string). ESI (the source pointer) will be loaded
with a pointer to the value of our comparison string and ECX (the register for counting variables) will
have the value 24h. Again, if you haven't already, go reference the intel developer's manual mentioned
in the introduction post if you don't know how repe cmpsb instruction works. (That's two seperate
instructions on the same line to clarify.)
12.) Basically, what is happening here is the REPE CMPSB is going to compare every byte in our string
against the correct string. If every byte matches then at the end CX should be 0. If CX is 0 then then the
JCXZ is taken and the function returns with a value of 1. Otherwise the function returns with a value of
0.
13.) If you've followed up to this point you probably understand what happens next. If you entered the
correct serial than the program displays a congradulatory message otherwise it displays a message
telling you to enter the serial again. The only problem is now reversing the three xors to create a correct
serial key.
14.) The process is relatively simple; we xor every byte of the correct key in the opposite order that the
user entered key was originally xored. Now, we could do this by hand however, we're hackers. A simple
script should do the job for us.
---------------------------------------------------------#!/usr/bin/ruby
string = "{aexdm&kzikcem&<&fm" + "7Fh".hex.chr + "jam{&jq&l}mda{|0"
string.each_byte do |char|
char = char ^ "0x55".hex ^ "1E".hex ^ "43".hex
print char.chr
end
puts
---------------------------------------------------------15.) We run our simple script and the results: simple.crackme.4.newbies.by.duelist8
16.) If you want it isn't hard to take the project a step further and write a cracked.exe that will always
register as true. You simply have to find a way to get the jmp to never be taken. Without getting into too
much detail you would need to open up the executable with a hexeditor the code in such a way that the
conditional jmp is never taken so the congradulatory message is always displayed.