Cracking a Passworded File: A Beginner’s Guide to Security

Bilal Khan
5 min readApr 4, 2016

--

So here we are again, trying to find a password for another file using linux. Keeping under consideration my previous blog posts about the same thing, lets level up a little more. All the files used in this tutorial can be found here on my github.

Lets start with running the file to see what it does:

$ ./crackme2
Access Denied

As in the previous files in my previous blog posts, it used to show “usage” as to where we should write the password to get access; but this time things are a little bit more complicated and we don’t even know where to write the password to gain access. Lets run the file command to check if our file is stripped or not.

$ file crackme2
crackme2: ELF 64-bit LSB executable, x86–64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=e06e0b0229279a69506925702a7f79e36bdc3eb2, not stripped

Luckily, its not stripped. Lets start with doing an ltrace on this file.

ltrace is a program that simply runs the specified command until it exits. It intercepts and records the dynamic library calls which are called by the executed process and the signals which are received by that process. It can also intercept and print the system calls executed by the program.

$ ltrace ./crackme2
__libc_start_main(0x40087d, 1, 0x7ffc72840328, 0x400a70 <unfinished …>
strncmp(“XDG_SESSION_ID=4”, “Passw0rd=”, 9) = 8
strncmp(“TERM=xterm”, “Passw0rd=”, 9) = 4
strncmp(“SHELL=/bin/bash”, “Passw0rd=”, 9) = 3
strncmp(“SSH_CLIENT=10.0.2.2 23512 22”, “Passw0rd=”, 9) = 3
strncmp(“OLDPWD=/home/vagrant/github/holb”…, “Passw0rd=”, 9) = -1
strncmp(“SSH_TTY=/dev/pts/0”, “Passw0rd=”, 9) = 3
strncmp(“USER=vagrant”, “Passw0rd=”, 9) = 5
strncmp(“LS_COLORS=rs=0:di=01;34:ln=01;36”…, “Passw0rd=”, 9) = -4
strncmp(“MAIL=/var/mail/vagrant”, “Passw0rd=”, 9) = -3
strncmp(“PATH=/usr/local/sbin:/usr/local/”…, “Passw0rd=”, 9) = -32
strncmp(“PWD=/home/vagrant/github/holbert”…, “Passw0rd=”, 9) = -10
strncmp(“LANG=en_US.UTF-8”, “Passw0rd=”, 9) = -4
strncmp(“NODE_PATH=/usr/lib/nodejs:/usr/l”…, “Passw0rd=”, 9) = -2
strncmp(“SHLVL=1”, “Passw0rd=”, 9) = 3
strncmp(“HOME=/home/vagrant”, “Passw0rd=”, 9) = -8
strncmp(“passwOrd=bilal”, “Passw0rd=”, 9) = 32
strncmp(“LOGNAME=vagrant”, “Passw0rd=”, 9) = -4
strncmp(“SSH_CONNECTION=10.0.2.2 23512 10”…, “Passw0rd=”, 9) = 3
strncmp(“LESSOPEN=| /usr/bin/lesspipe %s”, “Passw0rd=”, 9) = -4
strncmp(“XDG_RUNTIME_DIR=/run/user/1000”, “Passw0rd=”, 9) = 8
strncmp(“LESSCLOSE=/usr/bin/lesspipe %s %”…, “Passw0rd=”, 9) = -4
strncmp(“_=/usr/bin/ltrace”, “Passw0rd=”, 9) = 15

puts(“Access Denied”Access Denied
) = 14
+++ exited (status 1) +++

From the looks of it, this program is accessing all environment variables such as $HOME, $PWD, and trying to compare first 9 characters with Passw0rd. We can easily recognize that this binary file is trying to search for an environment variable by the name Passw0rd. Lets have one with a random value and do an ltrace again.

$ export Passw0rd=hey$ ltrace ./crackme2
__libc_start_main(0x40087d, 1, 0x7fff93c86f38, 0x400a70 <unfinished …>
strncmp(“XDG_SESSION_ID=4”, “Passw0rd=”, 9) = 8
strncmp(“TERM=xterm”, “Passw0rd=”, 9) = 4
strncmp(“SHELL=/bin/bash”, “Passw0rd=”, 9) = 3
strncmp(“SSH_CLIENT=10.0.2.2 23512 22”, “Passw0rd=”, 9) = 3
strncmp(“OLDPWD=/home/vagrant/github/holb”…, “Passw0rd=”, 9) = -1
strncmp(“SSH_TTY=/dev/pts/0”, “Passw0rd=”, 9) = 3
strncmp(“passw0rd=bilal”, “Passw0rd=”, 9) = 32
strncmp(“USER=vagrant”, “Passw0rd=”, 9) = 5
strncmp(“LS_COLORS=rs=0:di=01;34:ln=01;36”…, “Passw0rd=”, 9) = -4
strncmp(“MAIL=/var/mail/vagrant”, “Passw0rd=”, 9) = -3
strncmp(“PATH=/usr/local/sbin:/usr/local/”…, “Passw0rd=”, 9) = -32
strncmp(“PWD=/home/vagrant/github/holbert”…, “Passw0rd=”, 9) = -10
strncmp(“LANG=en_US.UTF-8”, “Passw0rd=”, 9) = -4
strncmp(“NODE_PATH=/usr/lib/nodejs:/usr/l”…, “Passw0rd=”, 9) = -2
strncmp(“SHLVL=1”, “Passw0rd=”, 9) = 3
strncmp(“HOME=/home/vagrant”, “Passw0rd=”, 9) = -8
strncmp(“Passw0rd=hey”, “Passw0rd=”, 9) = 0
MD5_Init(0x7fff93c86da0, 0x400b06, 9, 6) = 1
strlen(“hey”) = 3
MD5_Update(0x7fff93c86da0, 0x7fff93c88f2c, 3, 0x7fff93c88f2c) = 1
MD5_Final(0x7fff93c86e00, 0x7fff93c86da0, 0x7fff93c86da0, 6) = 1
sprintf(“60”, “%02x”, 0x60) = 2
sprintf(“57”, “%02x”, 0x57) = 2
sprintf(“f1”, “%02x”, 0xf1) = 2
sprintf(“3c”, “%02x”, 0x3c) = 2
sprintf(“49”, “%02x”, 0x49) = 2
sprintf(“6e”, “%02x”, 0x6e) = 2
sprintf(“cf”, “%02x”, 0xcf) = 2
sprintf(“7f”, “%02x”, 0x7f) = 2
sprintf(“d7”, “%02x”, 0xd7) = 2
sprintf(“77”, “%02x”, 0x77) = 2
sprintf(“ce”, “%02x”, 0xce) = 2
sprintf(“b9”, “%02x”, 0xb9) = 2
sprintf(“e7”, “%02x”, 0xe7) = 2
sprintf(“9a”, “%02x”, 0x9a) = 2
sprintf(“e2”, “%02x”, 0xe2) = 2
sprintf(“85”, “%02x”, 0x85) = 2
strcmp("d8578edf8458ce06fbc5bb76a58c5ca4"…, "6057f13c496ecf7fd777ceb9e79ae285"…) = 46
puts(“Access Denied”Access Denied
) = 14
+++ exited (status 1) +++

Woa! This opened a whole new door for us. So after finding out the Passw0rd environment variable, it gets the string length for the value of that environment variable, and from the looks of it calculates an MD5 hash and compares it with a predefined md5 hash in the program.

Lets convert our input password to an MD5 hash to confirm it. Just a simple google would land you with so many md5 hash calculators, you can even do it in bash:

$ printf "hey" | md5sum | cut -d' ' -f1
6057f13c496ecf7fd777ceb9e79ae285

So this MD5 hash is exactly what you can see in the ltrace output for our input password, and it is comparing it with a predefined md5 hash value d8578edf8458ce06fbc5bb76a58c5ca4 using strcmp. Now we need some way to reverse this to a string.

It is not possible to retrieve the original string from a given MD5 hash using only mathematical operations, but there is a way to decrypt a MD5 hash, using a dictionary populated with strings and their MD5 counterpart (or we try brute forcing as well). As most users use very simple passwords (like “123456”, “password”, “abc123”, etc), MD5 dictionaries make them very easy to retrieve. MD5 reverse dictionary containing several millions of entries can be used to convert an MD5 has to a string.

Lets use this website to try converting this hash to a string. It results in an output of “qwerty”. Lets try this as a password.

$ export Passw0rd=qwerty
$ ./crackme2
Access Granted

Tada! We finally have the password and we didn’t even need gdb for it.

Feeling bored? Okay, then lets write a simple ruby script containing only lowercase alphabets to try to brute-force the MD5 string. This script assumes the password string only contains lower-case letters.

#!/usr/bin/ruby
require 'digest/md5'
?a.upto('zzzzzzz') { |string|
md5=Digest::MD5.hexdigest(string)
puts "Trying: #{string}"
if md5 == "d8578edf8458ce06fbc5bb76a58c5ca4"
puts "\nThe string is #{string}\n"
exit
end
}

Run this script, and then go have lunch or watch a movie. After quite a bit of time, 20,30 minutes this script will tell us that the string for that specific MD5 hash is “qwerty”.

P.S. We can also use an equivalent bash script for brute forcing, but as it turns out it was too slow. I have uploaded the bash brute force script in my github repo as well in the link given above.

This article is for education purposes only. Follow me on Github.

--

--