06 Nov

Solving 0x777h’s crackme

Ox777h's crackme was posted on Tuts4You forum in October 2022. Description said that it uses simple anti-debugging and code virtualization, so I decided to take a look.

In this post I'll describe how the protection works and steps I took to defeat it.

However, this is not a full and comprehensive analysis of the protector code or the code virtualization feature. When solving crackmes, I prefer to choose the simplest solution that gets the job done. This is also reason why I did not use ready-made tools like VMAttack, Detours, FRIDA and the like...

Quickly about code virtualization.

Code virtualizers are generally considered one of the hardest software protection methods to defeat. Why is that? Let's see what features a common code virtualization solution offers:

  • Packer. Original program code is packed/encrypted and decoded on runtime.
  • Anti-debug protection. Most protectors use some sort of anti-debugging protection in their code.
  • Code obfuscation. Most protectors add junk code, some use control-flow flattening, constant obfuscation and other techniques.
  • And finally, the actual virtual machine with custom instruction set.

If you have just a single feature, like junk code, it's actually quite easy to reverse. The difficulty comes from the combination of all protector features and also how well they are combined.

There are several ways to defeat code virtualization:

  • completely devirtualize the code. This is the ultimate success, you have recovered the original x86/x64 code, or a close approximation of it;
  • make a disassembler for the particular VM, disassemble PCode and understand how the algorithm works;
  • trace the VM execution, and use trace data to understand how the algorithm works;
  • patch VM handlers and/or PCode;

If you want to learn more about code virtualization in general, I can wholeheartedly recommend Tim Blazytko's blogpost, as well as his Software Deobfuscation training. They are awesome!

With that in mind, let's look at our crackme and see what protections it contains.

Crackme overview.

Encrypted code.

The crackme is an x64 binary that uses a custom protector. If you open crackme.exe in your favorite hex editor, you'll notice that .code section appears to be encrypted. OK, maybe you will not notice that. smile
Just check the entropy of each PE section with a tool like DiE:

So, our first step would be to unpack the file.

Anti-debug protection.

To unpack the crackme properly, we'd need to run it under debugger, find OEP and dump the process memory. However, when you try to run the crackme under x64dbg, you'll see that it throws breakpoint exception and terminates:

You also cannot attach to a running process, as it will terminate immediately.

I spent some time trying different ScyllaHide options but without any success. Debugging the startup code allowed me to note some of the features:

  • It uses a lot of Nt* functions;
  • It manually maps ntdll.dll in memory and (probably) extracts syscall ids;
  • The rest of the protection uses syscalls directly;
  • And the protection code is mostly virtualized!

At this point I decided to try something else. Let's run the crackme without the debugger, dump process memory and try to attack it using static analysis!

Note: if Scylla fails to dump the process, use Process Hacker -> Select crackme.exe process -> Properties -> Memory -> select crackme.exe sections -> Save... and then rebuild PE header.

Junk code.

Dumped file is surprisingly readable in IDA. We can soon find a suspicious part in .code section:

Following the jump, we see a combination of push constant+call followed by data which is very typical for a VM startup:

Following that, we see some code that looks like an obfuscated spaghetti code:

So, it looks like we have located our VM but the code is obfuscated. We'll need to take care of that first.

Deobfuscating junk code.

After spending some time cleaning the junk code, you'll notice it uses several specific patterns for obfuscation.

jmp+junk


The simplest of patterns - it's a short jump and few junk bytes. We can use hex editor and simple regex to replace this with 5 nops.

clc+jnb and stc+jb


First, a carry flag is set to a know value using clc or stc instruction. Then a conditional jump is used to confuse IDA's analysis. Jump distance is usually very short - 2,3 or 4 bytes. Just like before, we can use regex to replace replace clc+jump+junk code with nops.

Big obfuscated do-nothing

Once the simple jumps are replaced, you'll notice a much larger obfuscation pattern:

The pattern begins with pushfq, followed by call and 2 jumps and ends with the popfq. This example uses RAX, but it can be also RDX or some other register.
It's easy to find the end of the pattern just by looking for next popfq instruction.

Even larger do-nothing

And finally, there's a more complicated pattern. It's so large that I had to use graphic editor to stitch it all together for you. Notice that all nops, jumps and junk code are removed from the image!

As with the previous pattern, it's easy to find the end of it, just look for combination of pop rcx, pop eax and popfq.

And we're done with code obfuscation! smile We've identified obfuscation patterns and found a way to deal with them.

Analyzing VM dispatcher

Now we're able to see what is happening on VM startup. First flags and registers are saved:

Then the VM state is prepared and VM dispatcher is reached:

And finally next handler is executed:

Writing VM tracer

For last few years I've written most of my tools in C#. Now I need to hook x64 code and C# is not really suitable for that. So, I dusted off my trusted old copy of Delphi XE2.

Also I needed some injector that would inject my DLL into running crackme.exe process. I randomly chose one the first results from Google search: https://github.com/danielkrupinski/Inflame

I chose to hook VM dispatcher between "add rax, 0F8h" and "mov eax,[eax]" instructions. Since I didn't have any decent x64 hooking library for Delphi XE2, I made my own "hooking" code. It's ugly and you definitely shouldn't do that in production code. But for the crackme it's fine!

And this is the code responsible for logging VM context. Nothing fancy, just get values from memory and log them to console.

Analyzing tracer output

Now, let's run the crackme, inject tracing dll and enter some random serial. We'll get output similar to this:

So far it doesn't look like much, does it? But by examining the instruction pointer PC values, we can see that opcode 0x13 can change PC significantly. So opcode 0x13 is probably a (conditional) jump instruction.

Let's modify our code and log only jumps. Log file is much shorter, and the final few lines are the most interesting.

Specifically, on 2nd line we can see RAX=31. That's ASCII code of "1", the first character of fake serial that I entered. In RCX we see value 0x43. Could it be the correct first character of serial and this is "goodboy/badboy" jump?

Patching VM context and obtaining the correct serial

Let's modify our logger one more time - on our suspected goodboy jump it will set VM flags to a default value, so that the jump is always taken.

We run crackme again, enter a fake serial, and get a good boy message!

Now we can take all logged values from RCX and obtain a correct serial:

TL;DR version

This is how the crackme was defeated:

  • Dumped process memory and used it for static analysis in IDA;
    • Avoids all anti-debug tricks;
  • Used simple regex replaces to defeat junk code;
    • We'll probably break some things but it doesn't matter, as we won't run the broken code;
  • Analyzed VM startup and VM dispatcher;
  • Used DLL injection to get my code running in the crackme process;
    • My code hooks VM dispatcher and dumps VM state before each instruction;
  • Analysis of executed operations allowed me to locate goodboy/badboy jump;
    • My hook code patched flags to always take goodboy jump;
  • We can see correct password in VM registers;

Conclusion

This was a really nice virtual machine, combined with an original anti debugging protection. It's not as difficult as VMProtect or Themida but it was fun to reverse nevertheless.

3 thoughts on “Solving 0x777h’s crackme

Leave a Reply

  • Be nice to me and everyone else.
  • If you are reporting a problem in my tool, please upload the file which causes the problem.
    I can`t help you without seeing the file.
  • Links in comments are visible only to me. Other visitors cannot see them.

Your email address will not be published.

two  ×   =  sixteen