SJ W
Posted on May 2, 2024
Introduction
In this post, we are going to delve into something entirely different from what you usually stumble upon in my blog. A few years ago, I was sort of getting into what is known as binary exploitation, a form of hacking that finds vulnerabilities in various binaries, which essentially are executables whose extension starts with .exe in Windows OS for example. My first step into the world of binary exploitation started with going over the book called Hacking: The Art of Exploitation by Jon Erickson, which basically revolves around the topic of the hacking technique called buffer overflow. For the next post right after this, I will go over some of the examples of buffer overflow by going over the CTF challenge that involves the ROP chain, but it basically initiated my fascination with the world of binary exploitation. However, due to some circumstances in life, my interest in it kind of waned eventually, and recently, after reading and watching various contents associated with it kind of rejuvenated my interest in the topic of binary exploitation again. For that exact reason, I decided to get back into it, by actually learning how to write a program in NASM (x86 assembly in Intel syntax), decompiling binaries using tools, such as Ghidra and Radare2, and completing various CTF challenges that have to do with reverse engineering in general.
In this post, we are going to try the CTF challenge called GDB Test Drive on the website called picoCTF, a website that hosts a multitude of CTF challenges for anyone to try for free. I love feeling challenged in general, and feel like there is no better way to learn something other than actually doing and struggling with it.
GDB Test Drive
Okay, so here is how this challenge works: download a binary that must be examined with the tools of your choice, find and submit the flag that is hidden within the binary in order to complete the challenge. In picoCTF, every challenge works differently from another, but the one thing that remains the same is that you have to submit a flag for completing a challenge. Anyway, let's download the binary to see what's going on inside.
1. Examine the binary
Let's use the tool called Ghidra to decompile the binary we downloaded for the challenge.
- The author of this challenge clearly instructs one to run the binary with the GNU Debugger (GDB), but I just decided not to do so, since I felt lazy. I thought that using Ghidra would be enough to complete the challenge.
- Ghidra is showing various options for decompiling a binary, but I decided to go with the default choices, since it seems to be the safest basically.
Here we are! Now, let's get to the task of analyzing the binary and finding the flag hidden within it. To understand how the binary works, I decided to go through a list of functions that exist within the binary, to see if there is anything interesting.
So in C, every program starts with a function called main, and in the list of functions above, right alongside the function main, I have discovered a function called rotate_encrypt. The first thing that came to my mind when I saw it was that it might have something to do with encrypting a string into something illegible to our eyes, basically. Let's check out both functions of main and rotate_encrypt
Inside the function "main"
In the screenshot above, we are basically seeing a decompiled version of the function main, and it is unclear what each variable is for, since every variable is replaced with the name that is prefixed by the word local
. However, as you can see, about midway through the function, that it invokes the function rotate_encrypt with two arguments, to encrypt something that is passed to it, and one of the arguments is of a pointer, which I assume is an address to the first letter of the string. Let's see what gets passed to the function!
- There are total four variables that get assigned with the 8 bytes worth of data each, and we know that they are located right next to one another, for the fact that
local_38
is located 8 bytes beforelocal_30
, which is also placed next tolocal_28
, andlocal_20
.local_38
is located at the address of EBP plus the offset of -0x38 bytes (when the offset is lower, it gets placed higher), and since it occupies 8 bytes starting from the address, it stores the data from the address of -0x38 to -0x31. And belowlocal_38
islocal_30
whose location is the address of EBP plus the offset of -0x30 bytes. Forlocal_30
, it occupies the address of -0x30 to -0x29. You can assume the same for the last two variables:local_28
andlocal_20
. -
local_38 = 0x4c75257240343a41
: The binary assigns the hexadecimal value of0x4c75257240343a41
to a local variable oflocal_38
ofundefined8
(I assume you can store the data of the size of 8 bytes). When a computer stores a string in memory (RAM), it usually stores a string in a series of numbers in succession, and each character is mapped with a certain number. Each cell in memory can hold up to a byte worth of data, and the data may represent anything from an integer of 0 to 255 (0x00-0xff). Since the number (0x4c75257240343a41
) we are seeing above represents eight bytes worth of data, we can split the hexadecimal value into a byte worth of data (0x4c75257240343a41 --> 0x4c, 0x75, 0x25, 0x72, 0x40, 0x34, 0x3a, 0x41
) and convert each to a character according to ASCII encoding, which results in the following string: Lu%r@4:A. In ASCII, 0x4c is mapped with a character L, 0x75 is mapped with a character u, and so on. -
local_30 = 0x4362383846336235
: Let's apply the same logic to this as the example above. Converting the hexadecimal numbers of0x4362383846336235
to ASCII string results in the following string: Cb88F3b5. -
local_28 = 0x6630624760433530
: Converting the hexadecimal numbers of0x6630624760433530
to ASCII string results in the following string: f0bG`C50. -
local_20 = 0x4e64646267353361
: Converting the hexadecimal numbers of0x4e64646267353361
to ASCII string results in the following string: Nddbg53a.
When you concatenate each one of them, it becomes the following string: Lu%r@4:ACb88F3b5f0bG`C50Nddbg53a. So at this point, I would like to assume that the string gets converted to the flag we have to submit for the challenge. Let's see what the function rotate_encrypt does with the string we have discovered.
Inside the function "rotate_encrypt"
Carefully going through the contents of the function, I have noticed the followings:
- It copies the content of the string (Lu%r@4:ACb88F3b5f0bG`C50Nddbg53a) to a new pointer, with the help of the function called
strdup
- In line 11, it stores the length of the copied string.
- In line 12, it iterates over the string using a for-loop.
- In line 13, there is a conditional logic that checks the followings:
(' ' < __s[local_20]) && (__s[local_20] != '\x7f')
. First, it evaluates whether a character in the string is bigger than ' ', which in ASCII evaluates to an integer of 32. Second, it checks whether a character in the string is not equal to\x7f
, which in ASCII evaluates to an integer of 127. If a character evaluates to "true" in both cases, it enters the line 14, else move on to a next character of the string. - In line 14, it adds an integer of 47 to a character, and assigns it to the variable
cVar1
. For example, if I add ')' - in ASCII, it is 41 - with 47, it becomes 88 ('X'). - In line 15, it checks if the variable
cVar1
is smaller than an integer of 127. If it evaluates to "true", then a character is replaced with the content of the variablecVar1
. If not then, it subtracts an integer of 94 fromcVar1
, and replaces a character with it.
Since we pretty much figure out how the program works, we can try to replicate the code with the help of Python!
2. Exploit
Since we know how the program works, we should be able to figure out the flag needed to complete the challenge!
Failure to submit the right flag
Here is my first attempt at writing the python code that replicates the function rotate_encrypt in the binary we analyzed. What it does is that it iterates over the string, converts each character in the string to a number according to ASCII encoding, adds 47 to it, and checks whether it evaluates to being bigger than an integer of 127. If it does, then it subtracts 94 from a number, and lastly, converts the number back to a character, which gets appended to a new string.
Running the script results in the following string: {FTCocipr3ggub3d7_3v1rd_}5538db2. Hmm, looks a bit broken, since the flag is supposed in the format of picoCTF{flagssss}. Anyway, let's try to submit it and see if it is an answer. Surely, it is not an answer! Still though, I find some solace in the fact that reversing the first 8 characters of the string above results in the following: picoCTF{. After learning that very important fact, I come up with a new script to rectify the situation.
Finding the right flag
Since reversing the first 8 characters results in the right result of picoCTF{, which is how a flag should start, we can reverse each data stored in each variable in the following manner:
- Lu%r@4:A --> A:4@r%uL
- Cb88F3b5 --> 5b3F88bC
- f0bG`C50 --> 05C`Gb0f
- Nddbg53a --> a35gbddN
When you concatenate each reversed substring, it results in the string A:4@r%uL5b3F88bC05C`Gb0fa35gbddN. Let's run the script to see what it outputs.
It is still weird. Maybe, I should reverse it? Let's fix the code and output it in reverse.
Seems like the right flag! Let's submit it and see how it works! It is right, which means that I have successfully finished the challenge!
It was a fun challenge overall to ultimately test my knowledge in reverse engineering. From time to time, I will post walkthroughs of different challenges that I come across in picoCTF from time to time. Thank you for reading this post!
Posted on May 2, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.