Malware.lu HackGyver Challenges
On the 13th of december, 2012, malware.lu
released two challenges.
One for windows,
and one for linux.
In this post we will discuss both challenges, first the windows binary,
then the linux binary.
HackGyver Windows Binary
After loading the binary into IDA Pro, we start off by analyzing the main
function, which is located at sub_401240. The application registers
a regular windows user interface so the user can enter the PIN Number in a
textbox and press on the validate button. When registering a user
interface, the application has to give a handler function. This function
contains all code related to initializing the user interface, handling
button clicks, etc. For this application, the handler is located at
Following the handler function we first see some initializing stuff (some
function calls to create the button etc for the user interface.)
Then we get to the interesting part.
The PIN Number is retrieved from the GUI, using GetWindowTextW.
This unicode string is then converted to ascii, after which it is passed
onto sub_401000. This function checks whether the pin number is
correct or not, and returns 1 on success and 0 on failure.
Function sub_401000 contains routines to extract an encrypted image
from the resource section (i.e. an embedded image.) This image is then
decrypted using the given PIN Number, a number in the range 10000..99999.
Although the code doesn’t necessarily require the pin number to be a
number, the text “Enter your PIN code” is a dead giveaway that this in
fact the case. The length five is hardcoded though, hence the range starts
at the number 10000 and finished at 99999 (the pin number in ascii form is
the key to decrypt the encrypted image data.)
Further analysis show that the sub_401000 function calls two other
functions in order to decrypt the encrypted image. These functions are
standalone, i.e. don’t require any global values or anything else but the
parameters. After the image has been decrypt in-memory, the first four
bytes of some internal state are checked against the magic value
“\xaa\xbb\xcc\xdd”, in order to verify that the decryption key was
The sub_401000 function is, just like the two decrypt functions, a
standalone function, and can be called simply by passing a string
containing the pin code as first parameter.
In order to solve this challenge I decided to go for a bruteforce
approach, i.e. trying the pin numbers 10000 upto and including 99999. As
stated earlier, the sub_401000 function returns 1 on success and 0
on failure. So by checking the return value we can see if the pin number
Finally, we find that the sub_401000 function has the fastcall
calling convention and that the binary is ASLR enabled.
Bruteforcing the PIN Number through IDA Pro
I decided to bruteforce through IDA Pro by using the
AppCall function provided by IDC/IDAPython.
To use AppCall we first have to execute the binary and attach to it
using IDA Pro. This is as simple as pressing F9 after the binary has
loaded up in IDA Pro. Then we have to suspend the process, do this by
pressing the pause button in IDA Pro.
We then have to define our own prototype as outlined in
blogpost by hexblog. As the binary has ASLR enabled, the address of the
sub_401000 function changes every time. Let’s assume, for this example,
that the function is located at 0x12f1000. The prototype looks like
the following (note that you have to type the lines of code into the
IDAPython console within IDA Pro, or do some fancy stuff using
proto = 'int __usercall decrypt<eax>(const char *a<ecx>);' fn = Appcall.proto(0x12f1000, proto)
It’s fairly straightforward; we define a function with parameters given
through registers using __usercall (note that there appears to be
no __fastcall), we call the function decrypt (although this
name has no special value here), the return value is in the eax
register, and finally, the first parameter is in the ecx register.
(The fastcall calling convention places the first parameter in the
ecx register, and the second parameter in the edx register,
although this function doesn’t take a second parameter.)
Now we have defined the prototype, it’s time to bruteforce. There’s not
much to bruteforcing, so here is the script to do it.
for x in xrange(10000, 100000): if fn(str(x)): print x break
After waiting for a few seconds the code has executed, and we see a number
being printed; 13044. Looking into our directory we find
that an image has been written. This is the following image:
That being said, we have solved the challenge
HackGyver Linux Binary
Whereas the Windows Binary was focused on decryption of encrypted data,
the linux binary focusses more on hashing and encoding.
The Linux binary takes one parameter on the command line, the key. The
key has to be atleast nine characters long, or it will be rejected.
The main routine calls two different functions, namely md6 and
RC4_encode, and finally checks two strings with strcmp.
md6 and RC4_encode
The debug symbols were messed up on purpose. The function md6 is
not really using the
md6 hashing algorithm,
instead, it’s a wrapper around the
md5 hashing algorithm. The
md6 function takes a zero-terminated string and returns the
hexadecimal representation of the md5 hash of this string.
Then we have the RC4_encode function which takes only two
parameters, whereas one would expect an
rc4 algorithm to take
atleast an input, an output, and a key parameter. It turns out that the
RC4_encode function simply takes a string and a length (which is
hardcoded to nine) as arguments. Since the length parameter is hardcoded
to nine, it will treat the input string as if it was truncated. It will
then copy the string into a new buffer, append the
encoded version of the string and return this newly created string.
In other words, the RC4_encode function can be represented in
Python as the following.
def RC4_encode(s): # strip the string because the base64 encoding # tends to append a newline character return s + s.encode('base64').strip()
Looking back at the decompiled code of the main function, we see
the strcmp which compares both hashes, and they
have to be equal. So what happens is the following.
Hash1 is calculated by taking the md5 hash of the entire key which
is given on the commandline.
Hash2, however, is the md5 hash of the string generated by the
RC4_encode function. The RC4_encode function is called with
the hardcoded length nine, as mentioned earlier. In other words,
everything after the first nine characters in the key which we give on the
commandline is ignored.
Conclusion: we have to come up with a key on the commandline which equals
the output of the RC4_encode function. To automatically generate
a correct key for any combination of nine characters we have the following
few lines of Python code.
import sys # grab the first 9 chars of the key if len(sys.argv) != 2 or len(sys.argv) != 9: print 'Usage: %s <9-char key>' % sys.argv exit(0) # generate the key print sys.argv + sys.argv.encode('base64').strip()
Generating a key and solving the challenge is as simple as executing the
following two commands.
$ python hackgyver.py 123456789 123456789MTIzNDU2Nzg5 $ ./hackgyverlnx 123456789MTIzNDU2Nzg5 Well, now create your own keygen ; )
That was it for today. Now get yourself curious for the next release of
Cuckoo Sandbox, because a new
version will be released soon!
Pingback: Security News » Malware.lu HackGyver
Hi there, nice writeup.
Regarding the windows crackme, I don’t find a comparison to “\x89\x50\x4e\x47″, just to “\xAA\xBB\xxCC\xDD”. Can you please explain this? Thanks a lot.
Otherwise I understand what the binary is doing….
Oh, as a matter of facts, you’re correct! I just assumed it would compare the first few bytes of the decrypted file data, rather than some internal state of the decryption algorithm. I’ll update my post, thanks.