Reversing - lcm6.exe

:: cracking, reverse engineering

As an entertaining start to the new year, this post will cover the basics of reverse engineering a program and breaking its password protection. Of course, I do not advocate software piracy in any way, and the program in question is called a crackme which is a program designed to broken, with various measures to make it harder for you. They come in different levels of difficulty, the one under the microsope today is a relatively easy one - however, breaking even the simplest program requires a fairly deep understanding of computers, and the process might be quite interesting if you don’t know how it’s done. Let’s have a look at it:


A typical flow for this kind of operation is as follows;

  • Learn stuff about the program - clues about how it was compiled, what libraries it is using and so forth
  • Attempt to locate the code that checks the password
  • For fun, simply patch it, skipping the protection to make sure everything is understood
  • Identify the actual password checking algorithm and work out what it’s doing
  • With this new knowledge, construct and enter a correct password
  • Write a program that can generate passwords

For this post, the only tool used will be the excellent x64dbg which is an open-source windows debugger, a spiritual successor to the legendary OllyDbg.

Learn stuff

Since the program already tells us it was written in Pascal, and this is a really simple crackme - there’s not much to investigate, but lets look at it anyway. Loading the program in the debugger and viewing the Memory Map screen will show us how the operating system has laid out the program along with all the additional libraries it has loaded.

Here we can see how the Pascal compiler has laid out the memory, the CODE section is where the actual progam is, and the other sections are for static global data, and other stuff.

Moving over to the symbols view, we can see what functions the program exports to the world, and all the exported function locations for all the linked libraries. Looking at the linked libraries can yield important information about what the program might do.

We can see here the program exports just the one function, its main entry point, which the debugger has kindly put a breakpoint on for us already.

Locating the code

Finding the code in question is the first challenge. You can’t step through millions of lines of assembly code, which includes all the linked libraries and runtime systems from the programming language in question. It can often be hard to see the wood through the trees! What we need is some kind of hook to look for. Since this is a console program, and it writes a bunch of stuff to the console, we could try to find some of the strings it writes and then locate where they are referenced from to get us in the right area. To do this we can search, or maybe look at the DATA sections to see if we can spot any of the strings. Infact, it so happens x64dbg has a function that looks for referenced strings, so we can just run that.

Hmm. It did indeed find a ton of referenced strings, but the only ones with “password” in it are in some system library that is not relevant to our interests. Something is clearly amiss here!

Let’s let the program run until it prompts the user to enter the password, then break into the debugger and try again

Aha! This is what we were looking for. It would seem something is happening that causes the code to appear later. Let’s look at the area of code it is pointing at

Great! We can see here it is printing the text, then the following calls are probably getting input from the command line, then it is going to test the password somehow. Before we look at that though, let’s investigate why the string references are not found when the program is started. Let’s start the program again and look at the same address where this code lives. (0x00408498)

This code is completely different! And, looking at it, it is nonsensical assembly code - rubbish or data. It would seem the program is somehow unpacked into this location at runtime. VERY INTERESTING!

Let’s have a look at the actual entry point of the program to see what’s going on. The entry point is at 0x00408887, which is somewhat higher in memory than the rubbish code area we have discovered.

Well well, what do we have here! This is a strange looking piece of code. The ecx register is used with the loop instruction here to create a loop that iterates 0x549 times, counting down. (when loop is executed, it decrements ecx and jumps if ecx is not zero). It then grabs the 32 bits present at memory location ecx + 0x40833C, XOR’s it with 0x35, and then writes it back again to the same memory address! What exactly is this mysterious address? 549 + 40833C = 408885, which is the memory address of the bytes just above the entry point itself!

So, it would seem the program does a basic decryption of itself by XORing each 32bit address with 0x35, thus revealing the progam. Stepping through the loop a few times shows this is the case as you can see the real program magically appearing.


Patch it!

Now let’s look at how the password is checked, and see if we can bypass it.

This isn’t all of the code, but it’s most of it. You can see here it’s doing a bunch of checks and then jumping to 40855c if it fails. Looking at that area, you can see it prints the failure message

You can also see here, the instruction above the jump target which is the final check, jumps to 4085AF where it prints the success message. Therefore, it should be a simple job to make the first check always jump to 4085AF. We could do this in multiple ways. For now, we’ll simply change the first check to jump to the success location if the test fails.

Wiht a little digging, it turns out this first check is ensuring that the password is 0xA characters long. With the in-memory patch, we should be able to put in any other length string and skip the other checks.

However, we cannot write this change to the executable since it decrypts itself upon load. What we need to do instead is change the specific encrypted byte so that when it gets XOR’d with 35, it produces the correct new value. Looking at the assembled change to the jump target, it is the value at address 4084D4 that changes from 84 to D7. Now, let’s look at the same area of memory before it is decrypted

Here we can see a value of B0, which when XOR’d with 35 gives us 84, proving it is the correct value. Now we simply change that value to (D7 ^ 35) = E2 and save the binary. Now, when the program starts, it decrypts itself as normal except it now has our new jump target for the first check, thus bypassing the protection.

The algorithm

Examining the assembly code and re-writing it in a higher level reveals something like this

  if(pwd.length == 10
     && pwd[9] == (pwd[0] ^ 0xB)
     && pwd[2] + pwd[3] == pwd[1]
     && pwd[4] == (0x1B ^ 0x36)
     && pwd[5] + pwd[6] + pwd[7] + pwd[8] == 0xFB)
      wl("You cracked it!");

We can see here a bunch of different checks on the string, where some values are connected with each other in various ways. Clearly, there are many valid passwords that would satisfy the check, one restriction is that since it is adding up the ascii characters directly, all the numbers must fall within the range that the user can input as ascii characters on the command line.

Let’s write a simple rough’n’ready key generator that will create valid passwords, this time in F# (for no good reason)

// silly brute force keygen
let keygen() = 
    let getAscii = 
        let chaos = System.Random()
        fun () -> chaos.Next(32,126)
    let inRange n = n >= 32 && n <= 126
    let pwd = [|for x in 0 .. 9 -> 'a'|]
    pwd.[4] <- char 0x2D  // always x2D  '-'
    let rec aux1() =
        let v = getAscii()
        let v = v ^^^ 0xB
        if inRange v then v else aux1()
    pwd.[0] <- char(aux1())
    pwd.[9] <- char (int pwd.[0] ^^^ 0xB)
    let rec aux2() =
        let v1, v2 = getAscii(), getAscii()
        if inRange (v1 + v2) then v1,v2 else aux2()
    let v1, v2 = aux2()
    pwd.[2] <- char v1
    pwd.[3] <- char v2
    pwd.[1] <- char (v1 + v2)

    //finally 4 in range values that add up to 0xFB (251)
    //each character must be at least 32!
    let rec aux3() =
        let v1,v2,v3 = getAscii(), getAscii(), getAscii()
        if v1 + v2 + v3 < 0xFB then 
            pwd.[5] <- char v1
            pwd.[6] <- char v2
            pwd.[7] <- char v3
            pwd.[8] <- char (0xFB - v3 - v2 - v1)
        else aux3()

This function generates lovely passwords such as “RlF&-sL&Y” that successfully pass the check.

I hope you enjoyed this little foray into the world of reverse engineering, and happy new year!