hackme: Deconstructing an ELF File

A friend recently asked me to find the password for a little
hard-to-hack program he had written and I agreed to it. The
short journey of a few hours that led me to its password were
extremely interesting and this article describes the process as
well as some of the new techniques learnt along the way.

Few minutes after accepting his challenge, I received a
binary called "hackme" in an E-mail and I got started! Those interested in giving
it a shot can download the binary file
and get back to this article later. Do let me know if you find
anything interesting along the way that I did not think of or
missed! Comments can be sent to manohar dot vanga at gmail dot
com with the title [hackme]. UPDATE: You can post comments to the
Hacker News discussion.

Test Run

I ran the binary and tried a couple of random passwords. As
expected, they failed, providing me with an extremely helpful
message:

$ ./hackme
Password, please? password
Oops..

Interestingly, running the binary under GDB gave a specially
crafted welcome message:

It was stripped. Dead end there. GDB would not be much help with
a stripped binary in terms of deconstructing the logic. I
decided to try looking for a literal string comparison with the
password within the binary just in case:

Just in case, I try all the strings as passwords but they all
fail. Not too surprising. The output however gives me the
message that gets printed on success; "Congratulations!". It
also seems to be containing the string "libc.so.6". Fishy. A
quick ltrace quickly explains what the binary is doing:

While this gives us the same welcoming message, we can see
what is happening here. The libc shared library is being opened
dynamically and the addresses of ptrace, scanf and printf are
being retreived with dlsym! Sneaky trick!

More irritatingly however, the strings output shows that the
binary is using the random() function. However, since it is a
reproducible program, that is the password works every time, the
call to random is probably not being seeded. We will worry about
this later.

The strings output also explains how the binary is detecting
the debugging environment. Calling ptrace while running inside a
ptraced environment (eg. strace, ltrace or gdb) returns -1.

Getting over this debugger hurdle however is quite easy using the
LD_PRELOAD environment variable. The LD_PRELOAD environment
variable can contain a list of custom shared objects which are then
loaded prior to all others when running an executable. It is
an easy route to blocking a process from calling unwanted
functions. I quickly wrote a quick dummy
function in a new file:

Looks like the password buffer is 1024 bytes long. I could
try overflowing that but coupled with stack randomization (which can be turned off if I remember correctly), it
would be hard to do on a lazy Friday. More importantly, my
goal is not the break the program but to get the password!

It is starting to seem like my only option left is to sit and
reverse engineer the code for this binary; something I did not
want to get in to on a Friday afternoon. A geek challenge
however trumps laziness any day of the week so I started with
the task of disassembling the binary.

Disassembly

I start with an objdump output (go ahead and
open it in a new tab if you want to follow along):

The assembly is a mess as expected of a stripped binary. I
quickly need to find the part that pertains to the password
encryption logic. From the test run, I know that the logic is
somewhere between where it prints the "Password, please?"
message and the "Oops.." message. I will start by locating these
strings in the assembly output and backtracing to where they are being used. The "Pa" of the
"Password, please?" string is 50 followed by 61 in
hexadecimal. A quick search locates the string in the assembly
output:

$ grep "50 61" objdumpout.txt
8048798: 00 50 61 add %dl,0x61(%eax)

The address of the string therefore is 0x8048799 (since the
first byte needs to be skipped). Searching the file for this address
leads me to this code:

The loop in the binary runs only 10 times and it has
repeating checks for some offsets of the password. The only
characters of the password that matter are the ones NOT marked
with an 'x' in the output (I make the program set these as it
runs).

Now the sweet part! I run the password against the original
program:

$ ./hackme
Password, please? xxsaxxxpexYoxxxexxx
Congratulations!

HA! That was fun!

Conclusions

So what did I learn?

Know Thy Tools

I knew how to get around many of the hurdles I faced from
past knowledge and experiences with the various tools I used.
The better you know your tools, the more you can think about the
real issues at hand (in this case, reversing the program
logic).

Test the Waters

I knew I would not find an easy way to reverse this program but I
tried all the easy routes anyway. Even though it did not
provide me with much information, I gained confidence after
having eliminated some of the options. It cleared the way
forward.

Know Thy Assembly

The machine instructions were a little hard to decompile and
I found myself referencing the Intel manuals from time to time
to figure out what was going on. More than the assembly itself
though, I really recommend learning the GNU Assembler syntax. I
was familiar with the Intel syntax (eg. NASM) but not completely
proficient with GAS syntax (AT&T syntax). I found this article
and this article
very useful in quickly brushing this up.

Some thoughts on the program itself follow

Checking only few parts of the password was
ineffective although it would not have made life much
more difficult if every character was checked. (Note: The
original author told me he put the 10 in the main loop for debugging
purposes and forgot to change it)

The random number was a nice way to scare me a
little but in the end, it had to be unseeded to be
deterministic and consequently, not very effective. If I
had a different version of libc with a different
random(), the program would probably have failed with
the original password.

The actual password was "SesameOpenYourself!"! I
came up with a few nonsensical variations that worked as
well. eg. "NasaJeeperYouShelby".

All in all, a good Friday afternoon! Again, comments can be
mailed to manohar dot vanga at gmail dot com with a title of
[hackme].