On December 1, 2022, a message arrived in my Slack inbox simply reading "congratulations!", I was taken aback, unsure of what I had done to deserve such praise at 8 AM. It turned out that I had been declared the winner of the software engineering challenge set up by ALX & The Room. I've since been inundated with inquiries from my colleagues and mentors about my approach. I'm humbled, and I hope that article does its job of satisfying their interest.
On the morning of November 29, 2022, a challenge was released on the ALX SWE Slack announcements channel. This challenge featured a peculiar image that required decoding, and it quickly garnered much attention from the community.
I knew that the data was binary, so I immediately set about storing it in a way that would allow me to perform some sort of computation on it. I decided to use C for this task (though Python or Javascript would have probably been easier), and I created a pointer to an array of type char
to hold the binary data. This allowed me to manipulate and analyze it as needed:
char *binary[] = {
"00111000", "00110100", "00100000", "00111000", "00110111", "00100000", "00110001", "00110000", "00110100", "00100000", "00110001",
"00110010", "00110010", "00100000", "00111001", "00111001", "00100000", "00110001", "00110010", "00110001", "00100000", "00110110",
"00110110", "00100000", "00110001", "00110001", "00110100", "00100000", "00110001", "00110000", "00110000", "00100000", "00110001",
"00110000", "00111001", "00100000", "00111000", "00110010", "00100000", "00110100", "00111001", "00100000", "00110111", "00110011",
"00100000", "00110111", "00110010", "00100000", "00110001", "00110001", "00110010", "00100000", "00110001", "00110001", "00110101",
"00100000", "00111000", "00111001", "00100000", "00110101", "00110000", "00100000", "00110001", "00110010", "00110000", "00100000",
"00110100", "00111001", "00100000", "00110111", "00110011", "00100000", "00110111", "00110001", "00100000", "00110111", "00110000",
"00100000", "00110001", "00110001", "00111001", "00100000", "00110001", "00110000", "00110000", "00100000", "00110111", "00110001",
"00100000", "00110001", "00110010", "00110000", "00100000", "00110101", "00110100", "00100000", "00110111", "00110110", "00100000",
"00110110", "00110111", "00100000", "00110110", "00110110", "00100000", "00110001", "00110001", "00110111", "00100000", "00111001",
"00111000", "00100000", "00110111", "00110001", "00100000", "00110110", "00111001", "00100000", "00110001", "00110000", "00110011",
"00100000", "00111000", "00111001", "00100000", "00110001", "00110001", "00110000", "00100000", "00111001", "00111001", "00100000",
"00110001", "00110000", "00110011", "00100000", "00111001", "00111000", "00100000", "00110111", "00110010", "00100000", "00110110",
"00110110", "00100000", "00110001", "00110001", "00110111", "00100000", "00111001", "00111000", "00100000", "00110101", "00110000",
"00100000", "00110110", "00111001", "00100000", "00110110", "00110001", "00100000", NULL,
}
Once I stored the binary data, I began to explore ways to convert each binary string into its decimal representation. This would allow me to inspect the data more closely and see if there were any discernible patterns. I considered several different approaches, but ultimately I decided to use a recursive function in C that I had written for a previous project. This proved to be an effective solution as it allowed me quickly and efficiently convert the binary strings into their decimal form.
int frbtod(char *s, int i)
{
return (i == strlen(s) - 1) ? s[i] - '0' : ((s[i] - '0') << (strlen(s) - i - 1)) + frbtod(s, i + 1);
}
This generated a series of digits:
5652325655324948523249505032575732495049325454324949523249484832494857325650325257325551325550324949503249495332565732534832495048325257325551325549325548324949573249484832554932495048325352325554325455325454324949553257563255493254573249485132565732494948325757324948513257563255503254543249495532575632534832545732544932
To make the data more manageable, I decided to separate the output with a space for each iteration of the loop:
56 52 32 56 55 32 49 48 52 32 49 50 50 32 57 57 32 49 50 49 32 54 54 32 49 49 52 32 49 48 48 32 49 48 57 32 56 50 32 52 57 32 55 51 32 55 50 32 49 49 50 32 49 49 53 32 56 57 32 53 48 32 49 50 48 32 52 57 32 55 51 32 55 49 32 55 48 32 49 49 57 32 49 48 48 32 55 49 32 49 50 48 32 53 52 32 55 54 32 54 55 32 54 54 32 49 49 55 32 57 56 32 55 49 32 54 57 32 49 48 51 32 56 57 32 49 49 48 32 57 57 32 49 48 51 32 57 56 32 55 50 32 54 54 32 49 49 55 32 57 56 32 53 48 32 54 57 32 54 49 32
My first thought was that this was ASCII. So I printed the output as characters instead of decimals, expecting to be done with the task:
84 87 104 122 99 121 66 114 100 109 82 49 73 72 112 115 89 50 120 49 73 71 70 119 100 71 120 54 76 67 66 117 98 71 69 103 89 110 99 103 98 72 66 117 98 50 69 61
At this stage, I was beginning to suspect that I had made a mistake somewhere along the way. The numbers that I was seeing in the output were not what I had expected, and I was starting to doubt my approach. After taking a break to make some breakfast, two things stood out to me that further confirmed my suspicions. Firstly, the output was shorter than the previous sample, which suggested that I was potentially on the right track, but my assessment was incomplete. Secondly, the output was badly formed (digits instead of characters), which indicated that there was either some sort of error in the data or, again, that my task was incomplete. These observations only strengthened my belief that I needed to revisit my approach and find a better way to solve the challenge.
I decided to try converting this updated output of digits to ASCII. This double conversion proved to be a valuable strategy. Printable character codes – something I missed at the time – are represented between 32-127. This also directly maps to our output above. So, on the off chance that that would reveal the final answer, I wrote a quick test:
int array[] = {
84, 87, 104, 122, 99,
121, 66, 114, 100, 109,
82, 49, 73, 72, 112, 115,
89, 50, 120, 49, 73, 71,
70, 119, 100, 71, 120, 54,
76, 67, 66, 117, 98, 71, 69,
103, 89, 110, 99, 103, 98,
72, 66, 117, 98, 50, 69, 61, -1
};
while (array[count] > 0)
printf("%c", array[count++]);
What I got then was a string:
TWhzcyBrdmR1IHpsY2x1IGFwdGx6LCBubGEgYncgbHBub2E=
It felt like I was closer, though not enough to call this the end. I reasoned that some cryptographic schemes that used the =
sign as a padding byte, so I did some reading and upon further investigation, I found out that this was common with base64 encoding. Excitement mounting, I tested this theory using the base64 shell function:
$ echo TWhzcyBrdmR1IHpsY2x1IGFwdGx6LCBubGEgYncgbHBub2E= | base64 -d
Mhss kvdu zlclu aptlz, nla bw lpnoa
$
While this was an improvement, I still wasn't quite sure what this was. I figured the tokens represented something that could be translated into English so I first tried rotating characters back and forth à la ROT13 and while this was relatively straightforward to accomplish, it didn't produce any results that I felt I could use to make further progress.
I tried a few other combinations – including even Google Translate (see this script) – but nothing I got felt like I was on the right track. I was starting to feel a little frustrated, but I was determined to keep going and find a solution.
I decided to scan through other cryptographic ciphers from the book Hacking Secret Ciphers with Python by Al Sweigart. Flipping through with my mouse, I stumbled upon the Caesar cipher. Legend has it that Julius Caesar used this cipher to communicate secret messages to his generals, and it involves shifting the letters of a message by a certain number of places. There are 25 possible shift integers that we could be dealing with, and this seemed like a promising avenue to explore.
I was excited when I discovered some python code from the book, and I quickly set to work adapting it to fit my input. I made a few changes here and there, and before long I was ready to try it out.
Brilliant! However, the output was both uppercase and lowercase! I was pretty sure that I had messed something up whilst modifying someone else's program, so to confirm my results I ran my input through a second brute force utility program which output:
Fall down seven times, get up eight
Success!
As I worked on this challenge, I learned a number of valuable lessons that I hope will serve the reader.
I discovered the importance of breaking up a problem into manageable parts. This makes it easier to tackle and solve it piecemeal. I also learned the value of analysis and troubleshooting, as this allows you to identify and fix errors or problems before they become too difficult to deal with. To only half pay attention is to miss more than to pay no attention at all.
I discovered the benefits of taking a break to clear my mind and look at things from another perspective. Some of my best thoughts come to me while picking food out of a fridge, taking a walk, or in the shower. This can provide valuable insights and help you find creative solutions to problems that might otherwise seem insurmountable.
And lastly, I learned the need to be flexible and adaptable in the face of challenges. Situational awareness is a great skill to have, but being ambidextrous is probably more important in a fight.
If you read this far, I'm impressed. If this helped you in any way, I'd love to hear about your story.
Thanks to Maarten, Julien, Fred, Rita, Ana and Chee-zaram for your encouragement and support.