Want to learn reverse engineering? Continue ahead. This is a walkthrough of the "keygenme" reversing challenge from NYU's CSAW 2013 CTF competition. I try to be thorough and highlight how to solve both a CTF challenge, and how to use standard and modern reverse engineering tools.

Reversing : keygenme

someone has leaked a binary from an activation server.
can you crack the keygen algorithm for me?

using the ELF provided, reverse the keygeneration algorithm.
The server listening at raxcity.com on port 2000 will ask you for
the passwords of various usernames. If you can provide 10 passwords, you might get a nice flag :-)

*hint*
Rumor has it that the actual keygen runs in a custom vm. I'd start by decoding the instruction format.

Try to make it break

Play with the binary and give it inputs. See if you can draw conclusions, or make it crash.

$ ./keygenme32.elf hellohello 123 123
error: hellohello is not a valid name

$ ./keygenme32.elf hellohellohello1 123 123
:-(

Simply running it gives us the usage details. Trying out different sized usernames leads us to figuring out that the username for the keygen has to be 16+ characters. I couldn't get any crashes, which is reasonable since it's a "reversing" challenge, and not a pwning challenge.

High level picture from disassembly

We enter main. The first fork leads us to either calling printusage() or not.
We probably want to go down the path of not calling printusage().

Next fork is the length check that we already figured out.

And then bam! A gigantic linear procedure.
This looks like a lot of work. Trying to decompile with the tab hotkey doesn't work. We can try to decompile the entire thing (Ctrl+F5), and see if that's any better -- nope, the main() didn't decompile for some reason.

A first pass high-level human decompilation of the huge function, that I've renamed chal_logic(), looks like it's along the lines of:

call strtoul(argv[2])
call strtoul(argv[2])
allocate some std::strings...
get a random huge constant...
dword ptr [esp+4], offset a00004820212900...
some more string stuff...
construct a new cpu::cpu object... # the hint did say the keygen algo runs in a VM
calls cpu::fillmemory()
cpu::execute() is called # the fake VM CPU must take that previous constant and treat it as a series of instructions by loading it into its *fake* memory
call getT6()
call getT7()
call check(int, int, int, int)
either a sadface or happyface is printed

You might've noticed that after the block is another branch. That branch appears to just be some C++ garbage collection, as they both merge to the same point afterwards. It can be ignored.

Digging deeper

If you paid attention, I mentioned a couple paragraphs back that you can use tab to switch to pseudocode. That's huge and nifty. But, IDA has a lot of other small things that makes it useful. If you take a look at the disassembly text that IDA gave us in the chal_logic block, you'll notice a lot of pseudo-variables.

NOTE: Assembly comments beginning with <--- are my own, and others are autogenerated by IDA.

var_28 seems to be C++'s this pointer for the cpu::cpu object. You can see this, because the ebx register is typically used to store the this pointer by C++ compilers.

After GetT6() and GetT7() are called, a call to check(int, int, int, int) is done. Let's see what four ints get passed to check().

In the most common calling convention found on x86, cdecl, arguments get passed to functions off of the stack. In cdecl, the arguments of a function are pushed onto the stack in reverse order -- the function being called expects the top of the stack to be the the first argument to it. It looks like the call to check is check(var_2C, var_30, var_20, var_24).

NOTE: If you're not familiar with the stack, I'm going to be doing a write-up on it soon. For now, it's just a place in memory that acts like a stack data structure (last in, first out) and is used in x86 computer architecture.

From earlier we know that var_2C and var_30 are T6 and T7 respectively. So, what is var_20 and var_24? Let's look back at what IDA disassembled for us. Use the x hotkey on var_20 to get xrefs to it. This will list everywhere that var_20 is cross referenced.

The weird __PAIR__ keyword can basically be ignored. It's just comparing the 1st arguments of each pair, and then the 2nd arguments of each pair. If both comparisons is true, then true is returned. The BYTEn(x) macro is used to mean "the nth byte of x", where BYTE0 is the least significant byte

Okay, we almost have this binary figured out. However, we still need to know how the first argument, the username, is being used. It probably affects what "T6" and "T7" end up being by altering the instructions that get executed on the fake computer.

If var_20 is the the first numeric input, argv[2], then it would stand to reason that argv[1], the username, is var_1C.

It looks like var_1C is compared to 16, so it's actually probably the length of the username, and not the actual username string. Going up a bit from the cmp instruction, it looks like the actual username string is in:

So it would appear that the username is used to create a std::string. Bunch more of std::string family function calls, and we can see that the username string is being concatenated to the random constant string we saw earlier.

What we know so far

So far our analysis of the binary tells us:

The binary takes 3 arguments

Username, a 16+ char string

Two integer arguments (they get converted from c-strings to ints with strtoul())

The binary constructs a cpu::cpu, a fake virtual computer

The binary fills the cpu::cpu, with some data from a long string, "000048202129009.."

The data is also derived from the username argument

The binary then runs cpu::execute to simulate running a computer with the memory/instructions loaded into it

The binary then gets T6 and T7 from that fake CPU after execution is finished.

The binary then runs a check(T6, T7, intarg1, intarg2)

If the check passes, we get a smileyface.

We know the username argument to the keygen is used as a seed to a fake computer to get T6 and T7 values out. We can reverse engineer the fake computer, and figure out how the username affects the instructions ran on it. However, I have a smarter and lazier idea -- it's a a computer, a fake one, but still a computer. That means it's a deterministic finite state machine. The same string input we give to it, will always produce the same output.

Recall the task at hand. We are to provide the correct key for 10 different usernames to a remote server. We have a leaked keygen binary, that takes a username and a key and tells us if they match. We can leverage this leaked keygen binary, and simply feed it the usernames that the remote server asks for. We can insert a debug break point right before the check() call in the binary, and see what arguments to it are. The first two arguments will be the fake computer's T6, and T7. Using those values, we can simply do math for the key that the remote server's check() function is expecting in return. Send it, and get a smileyface :-).

Writing our crack

We can leverage gdb's python scripting capability. Note however, that the python script has to be inside of gdb, after you launch it. We can use my shoe.py script to talk remotely to the server. Recall also that we have to do the check() function in reverse. We will be getting the T7, and we need to figure out what we can give for intarg that will shuffles around and form T7.

This is one of the easier wargames out there. The first ~22 levels have very few gotchas for experienced UNIX developers, and can take 30 minutes to a day. There are a few that I got caught up on though. It's a good way to learn the shell, or get more experienced with it.

Usually each stage will give you a password (and increment the username) to the next stage that you use to login with to get the next password and so on.

Bandit 13 → 14

Bandit 14 → 15

This one's tricky, because it requires you to read the MOTD that says "passwords are stored in /etc/somegame_pass". After that it's just nc localhost 30000 and enter the password of bandit14 from the directory.

Bandit 15 → 16

I had to google how to use nc with SSL for this one. Turns out ncat, another clone of nc, from nmap allows easy ssl.

ncat --ssl localhost 30001

Bandit 16 → 17

For this one, you get an ssh key that you have to feed to a port running on localhost in the range 31000-32000. There's only 5 servers, so just list them all and try them all. You get an ssh key in return, save it to a file and chmod 600 it to use for the next level.

Bandit 18 → 19

Bandit 19 → 20

Bandit 20 → 21

Run a socket listener on one terminal, and use the suconnect binary as a socket client.

nc -l -p 6161 # On one terminal
./suconnect 6262 # On other terminal

Bandit 21 → 22

This one involves some guessing of what they want from you. Basically it's just look in /etc/cron.d and the cronjob_bandit22.sh file. The cronjob runs /usr/bin/cronjob_bandit22.sh which simply creates a tmp file with bandit22's pw. Read that file to get the next password.

Bandit 22 → 23

Same thing as the previous level, but look into cronjob_bandit23.sh, this one creates a file as well, but it's more obfuscated. You can simply redo what it does and get the filename.

cat `echo I am user bandit23 | md5sum | cut -d ' ' -f 1`

Bandit 23 → 24

Another cronjob challenge. This one uses cronjob_bandit24.sh and executes all scripts in /var/spool/bandit24. Simply create a shell script that writes a file with bandit24's password. Be sure to chmod +x it.

#!/bin/bash
cat /etc/bandit_pass/bandit24 > /tmp/somefile

Bandit 24 → 25

These last two are the most exciting. This one involves bruteforcing all 4 digit pins to a remote server. The slow way is to loop doing echo foo | nc, but that creates a new connection each time. The faster way is to keep the connection open and loop. I used my shoe.py script for easy reading and writing to remote servers.

Black Hat Top 10

OSX loader kills stuff that doesn't match a signature. As such dynamic injection into running Apple signed apps doesn't work. However, can easily remove the signature in the first place, and then it works :).

Dylib hijacking (same as DLL hijacking) is prevalent.

Goes into detail of how to be stealthy and persistent.

Details how to bypass Gatekeeper, Xprotect, OSX Sandbox, Kernel-mode code signing, and that the number of local privilege escalations is absurd and easy to find in OSX.

Some Android permissions are "privileged", and only able to be acquired if OEM (Samsung, LG, etc.) signed them.

Remote control apps are signed by OEMs to work properly, due to needing those privileges.

Talk shows POCs that due to some apps being signed with such high privileges and their poor implementations, backdoors are easy.

Ex: TeamViewer uses a Cert's Serial # to verify communication to it, however Android doesn't use a central CA, self-signing is possible, as such you can create a cert with the same serial # as TeamViewer and MITM it.

GDB is unfortunately not that great for debugging. Especially when you compare it to the Windows world's Visual Studio debugger. It's the best we got though on Linux, and PEDA makes it much more tolerable.

Here's my .gdbinit, and some tips on how to use PEDA to make debugging easier.

# Save in file: ~/.gdbinit
source ~/peda/peda.py
set disassembly-flavor intel
set pagination off
catch exec
# Keep a history of all the commands typed. Search is possible using ctrl-r
set history save on
set history filename ~/.gdb_history
set history size 32768
set history expansion on

NX and ASLR are both enabled. This is very problematic, but let's look back to the hint given to us in the beginning.

The hint for this challenge is:

"However I also made a dumb rookie mistake and made it too easy :(
This is based on real event :) enjoy."

The rookie mistake here is that if you look above, there is no stack program header. So while NX (non-executable stack) is enabled for the machine, NX is not enabled for this binary. That means the solution can leverage arbitrary execution on the stack.

Solution

We need to get our payload onto the stack. We know argc, argv, and envp get pushed onto the stack. So let's put our shellcode into some environment variables. We also need to redirect program flow, recall in the analysis section the four instructions that the program is doing. It pops argc into eax, pops argv into edx, and then starts executing what's pointed by edx.

Normally the first argument in argv is the name of the program, however this is not technically a requirement we can change it to anything we want, evident if you look at the typical structure of an execv call, execv("myprog", {"myprog", "arg1", "arg2"}), notice the repeated "myprog".

ASLR is enabled, so we don't know the exact address that our payload is at on the stack. So we have to spray the stack by creating a bunch of environment variables and nopsleds.

In five steps:
1. The WiFi protocol has something called a deauth frame.
2. The frame can be sent from a station, essentially asking to be released from the network.
3. These frames are usually not encrypted, and only authentication is via a MAC address. An attacker can spoof the MAC address of the victim easily and tell the access point to disconnect it.
4. The attacker then yells very loudly that it's the access point and the victim should connect to it.
5. The victim connects to it 😞.