Custom Attacks Part 2: cracking passwords

Elsaid Salem
7 min readJan 12, 2021
Photo by Laurentiu Iordache on Unsplash

Welcome back to the 18th iteration of this blog series. In this series, we’re growing our cybersecurity knowledge starting from the very basics using the overthewire.org challenges as a guide. First, I’d like to thank everyone for their feedback based on the last post! I’ll do my best to implement it and as always, more feedback is always welcome.

In this post, we’ll continue the process of writing our own custom program to crack passwords. We’ll take it in steps and I’ll do my best to explain the choices we’ll make. We’ll also address some issues and discuss some security concepts that relate to programming and password cracking. Let’s get started!

We’ve previously had to write a simple script in bash to crack the password for bandit24. We’ve also discussed BigO notation and how it’s used to calculate program execution speeds and thus efficiency. Let’s take a look at the that old code and break it down:

#! /bin/bashfor i in {1..9999}
do
if [ $i -lt 1000 ]
then
pin="0$i"
elif [ $i -lt 100 ]
then
pin="00$i"
elif [ $i -lt 10 ]
then
pin="000$i"
else
pin=$i
fi
echo "UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ $pin"
done

This code does 2 things: first, it starts to count from 0 to 9999, and second, it sends that count (with the appropriate number of digits) to the terminal through the echo command. In terms of BigO notation, this is a linear iteration on a list of numbers that is 10,000 members long. Since we’ve only used one “for loop”, it still runs as fast as a computer can count to 10,000.

This is a nice program. It’s simple, it does the job well, and it’s easy to scale. If for example, we had to guess a number that was up to 100,000 instead, we’d really just have to add one more “9” and another condition to account for the extra “0” and it would just work! This can be simplified even further by specifying the correct number of “0”s, to begin with, and we can just skip the “if statements” altogether:

#! /bin/bashfor i in {0000..9999}
do
echo "UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ $pin"
done

Now it’s even more scalable! We just have to edit the numbers to account for changes and it’ll just work. But unfortunately, this is only good at guessing numbers and isn’t any good at guessing letters or symbols. That’s what we’ll do today. But today, we’ll do it with python. Why python? Well, because we should know multiple programming languages. Python is also an easy language to code with since it almost reads like plain English.

In fact, once you learn one programming language, it becomes very easy to learn others. The difficulty in programming is much less so about learning the language itself, and more so about learning how to implement the code to perform a task. The difficulty lies in logic. Now high-level languages like python also do a lot of “behind the scenes” work like automatically managing memory. But once you learn “how to code”, you can simply write that code in any language that you know the syntax of. This also means that you can follow along in any language you’d like to write this in.

If you’ve ever wondered how computers can do such complex tasks as running massive virtual worlds, the answer is “chunking”. Chunking is breaking down complex problems into a series of simpler steps. We repeat this process until we come to a problem simple enough to write into code. The combination of these simple solutions eventually builds back into our complex program. Chunking also lends itself to writing “pseudocode” which is code written in plain English. A pseudocode is how you would write a list of instructions that someone can read and follow.

Our first “hurdle” is to find a way of sending commands (to send our password to the binary) to the Linux terminal and then checking the response using python. We can do a quick search on Google, we’ll find a few options such as the sys, subprocess, and os modules. Some more research and we find that the subprocess module has an option to send and command and check the output at the same time. We have our first “code”:

# send password to binary using subprocess

Our final objective is to have a program that guesses passwords that can be made of different characters and not just numbers. To keep it simple, let’s just start with just numbers. It’s easy to do numbers with a “for loop” like we did in the previous bash example but we want to avoid using “for loops”. For one, we want our program to be scalable and part of using a “for loop” is to know how many times a loop will run. Second, “for loops” are terribly inefficient if we nest them and so we want to use them as little as possible. Our second “code”

# iterate over password possibilities without using "for loops"

We’ve previously covered how running attacks can be very “loud” in terms of logs and traffic analyzers. In a best-case scenario, we can exfiltrate the file that needs to be cracked and do our work offline. In our case, we’re going to do the password cracking on the machine itself, so we need a way to make sure that the program we’re writing can be tested before we deploy it. We’d also like our code to be interactive so that we don’t have to edit the code every time we want to change something. What if we don’t know how long a password is? Our code should also be able to keep guessing longer passwords. Our third, fourth, and fifth “codes”:

# be able to test the password guessing and command sending
# make it interactive
# be able to keep guessing longer passwords if original length doesn't work

We now have a pretty good list of tasks in pseudocode to get started. We might come up with more things we want and if we do, we just repeat this process of chunking and pseudocoding.

Our code so far should look like this:

# send password to binary using subprocess
# be able to test the password guessing and command sending
# iterate over password possibilities without using "for loops"
# be able to keep guessing longer passwords if original length doesn't work
# make it interactive

While we work on it, I’ll just keep the section we’re working on “in focus” but I’ll make sure to post our progress at the end of each section. I’ve reordered our pseudocode so that we can tackle the simpler problems first. Working out the simpler issues helps guide the simplicity of our program and they’re also relatively easier to rework if we need to. A note: “#” is a comment in python which means it won’t be executed as part of the program. We should use them to describe our code so if another person will be working on our program, it should make sense and be easy to read. Now, we’ll tackle our first chunk:

# send password to binary using subprocess# we don't need ALL of subprocess, so to keep our program efficient we'll just import the part that we need:
from subprocess import check_output as checkoutput
# this will take a password and the "path" to send to and do the magic
def shellGuesser(guess, path):
attempt = "echo " + guess + " | " + path
return checkoutput(attempt, shell=TRUE)

Great! Our first chunk is complete, we can now send our, yet to exist, password guesses to the terminal. I should note that there are some issues with our current function. One you can find if you read the documentation on check_output carefully. The other is an issue with timing. But we’ll get to these later. Our next chunk:

# be able to test the password guessing and command sendingdef testGuesser(guess):
gotIt = ""
if guess == "testPassword":
gotIt = "Found the password! It's " + guess
else:
gotIt = "Whoops! Try again...."
return gotIt

Simple enough, it takes a password guess that we’ll generate in the future and checks it against our “testPassword” (we can change this to check any combination we want) and lets us know if we got the password.

Our code so far should look like:

# send password to binary using subprocess# we don't need ALL of subprocess, so to keep our program efficient we'll just import the part that we need:
from subprocess import check_output as checkoutput
# this will take a password and the "path" to send to and do the magic
def shellGuesser(guess, path):
attempt = "echo " + guess + " | " + path
return checkoutput(attempt, shell=TRUE)
# be able to test the password guessing and command sendingdef testGuesser(guess):
gotIt = ""
if guess == "testPassword":
gotIt = "Found the password! It's " + guess
else:
gotIt = "Whoops! Try again...."
return gotIt
# iterate over password possibilities without using "for loops"
# be able to keep guessing longer passwords if original length doesn't work
# make it interactive

I hope you were able to follow along with what we’ve coded so far. If this is your first experience with programming, it might look very confusing. Take your time and remember, python is meant to be read like English.

If you have any questions or suggestions please leave it in the comments and I’ll do my best to address them. Next time, we’ll tackle the core of the program: the password guess generator. Until then, a challenge for you: how should we try different combinations of password characters? Hint: think of it like we would try numbers.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

No responses yet

Write a response