Binary Exploitation Part 2

N0ur5
9 min readJul 31, 2024

--

Hey Again,

If you haven’t read the previous article, you might want to do that before moving forward here. We are going to pick up quickly from where we left off! As a quick refresher — we downloaded the “Jeeves” application from HackTheBox’s Into to Binary Exploitation learning path and used a basic padding brute forcer script, written in bash and Python to overwrite a stack variable. In overwriting the stack variable (a variable we couldn’t control without a buffer overflow vulnerability rooted in a variable above it), we could make its value equal to that value needed to complete the challenge and read the flag.txt file.

So what will this next article be about then you might ask? Well, using a different approach to achieve the same goal is a great way to reinforce information when learning a new concept. So we are going to do exactly that — complete the Jeeves challenge again but this time skip right to solving the challenge fairly quickly since a lot of explanations are out of the way in the article that prefaces this one.

Using a padding brute force technique worked just fine to solve this, but it left something to be desired — more visuals! If we include another tool into our toolkit this time around (Ida), we can view the data as assembly instructions move it around in memory. So first — you will want to install Ida by going here: https://hex-rays.com/ida-free/

This time I’ll skip over the setup of the application — I’m sure Google-foo can get you where you need to be with Ida. Once its installed we are going to want to boot it up and choose these jeeves binary to analyze. The two red lines you will see below are two breakpoints I have placed in the “main” function of the jeeves.

Breakpoints are essentially places in the assembly instructions where we want our Ida debugger to stop the application from executing any further.

The first break point is on the instruction:

lea  rax, [rbp+var_40]

The reason we stop here is because it’s right after:

call  _gets

…which is the vulnerable “gets()” function we discussed in more depth in the previous article. Since the “var_40” variable seems to be part of the next step (instruction), we can assume that the data we entered as our name should be stored in memory at this point.

So first, lets create a basic text file that has the name we would want to enter in. For me, I will put “n0ur5” in a text file.

Just printing the contents of “safeinput.txt” here…

Then we can supply this file as an argument to the jeeves application by clicking on “Debugger” > “Process options…”.

…and then we supply the string:

< safeinput.txt

…which will essentially provide the content we placed in it a moment ago (n0ur5), as the name when the jeeves binary launches and asks for it.

Then finally, we can launch the application, which will stop right after the “gets” function wraps up. Meaning that we should be able to find the hex values for “n0ur5” in memory, and realistically… it shouldn’t be too far above the value we know we eventually overwrite to complete the challenge. But before we look at the data sitting in memory, lets do a quick side-by-side comparison of what the assembly instructions look like for the main function, versus what the decompiled C code for the main function looks like.

If you read the last previous article in this series, you will recall that the challenge we faced, was getting “local_c” to equal 0x1337bab3, but the only data we could enter was stored in “local_48”. In the screenshot above, we can see that local_48 in is called “var_40” in the assembly and local_c is “var_4”. We can also see that the “if” statement in the C code, is done with the “cmp” (compare) assembly instruction — by comparing [rbp+var_4] to 1337BAB3h. And so on…

Back to where we left off, the application is essentially paused in execution on the first break point we set, which was the very next instruction after the “gets” function returned to the main function. We are going to look at the function stack to see if we can locate two things.

  1. Where out input ends up in memory
  2. Where the value we want to overwrite in memory is (we know from the brute force approach we used in the previous article that it isn’t too far below where our input is).

Don’t forget, we are looking for out input — “n0ur5” in hex, not in ascii… so if we look at an ascii to hex chart, we see that the first chunk of hex we should be looking for is “6E” — a lower case “n”

https://www.eso.org/~ndelmott/ascii.html

Looking at the top address viewable in the screenshot of the stack above, we indeed see “6E”. You can go ahead and lookup the rest of the hex chunks if you want, but let’s remember the stack is growing right to left as it flows downwards. So the data will appear as “5ru0n” on the stack.

A little further down in the same screenshot, notice the string hex chunks “DEADC0D3”… this is the value assigned to “local_c” (var_4 in assembly). It is the value that is checked when the “if” statement (cmp in assembly) executes. In order for use to make the if/cmp statement true, we need to make sure the value of local_c/var_4 is 1337bab3. In other words, we can see our valid input is only a few lines away from the value we want to overwrite. Again, we *knew* this from the previous article but we can now actually visualize this. We also know from the previous exercise that if we supplied 60 characters we would overwrite everything in memory right up until the “DEADC0D3”, because the data is accepted with the gets function that fails to limit input to a buffer. To keep the visuals going, lets see how this looks in memory by supplying our 60 “A” characters. In hex, a capital A is “41”, so if our theory is accurate we will see “41” in memory right up until we hit “DEADC0D3”.

echo $(python -c "print('A' * 60)") > buffer.txt

The above command will print 60 of the letter A to a buffer.txt file. Now we can execute the jeeves application again in Ida, but this time we will supply buffer.txt as an argument file, instead of the safeinput.txt file like we did last time.

And again we execute the application which is still configured with a breakpoint just after our data is read into memory.

We were almost right at least haha.

We forgot to account for something here clearly… we filled the buffer up to the “DEADC0D3” but then also managed to overwrite the “D3” with hex “00” — a byte sequence referred to as a “null byte”. Apparently, that gets function, in addition to being vulnerable, will also behave as so…

The gets() function discards any newline character, and the NULL character (\0) is placed immediately after the last byte read.

Source: https://www.ibm.com/docs/en/zos/2.4.0?topic=functions-gets-read-string

Looking at our buffer.txt file in hexeditor…

hexeditor ./buffer.txt

… we can see that in addition to the 60 instances of “41” (the 60 As’), there is a “0A”

This isn’t an issue when we try to align the 1337BAB3 properly in memory though because the “0A”, rewritten as “00” by gets() will come AFTER the 1337BAB3, meaning it won’t effect our 60 character buffer that comes BEFORE 1337BAB3.

Nonetheless… lets wrap up by looking at what happens if we supply the “input.txt” file that we used to solve this challenge in the previous article.

We change the file used as an argument/parameter to the jeeves application one last time annnndd….

..perfect. The 1337BAB3 hex payload aligns perfectly with where “DEADC0D3” previously sat, when the application accepts our input and pauses right after the gets function due to the breakpoint we placed.

Lastly, lets look at what the assembly “cmp” instruction is doing, and why what we did solves the challenge.

This is our next break point — we are paused right before the CPU executes this instruction. From a memory perspective, some things may have changed, but the important stuff for this articles purpose has not. According to Google’s “Gemini AI”:

“The CMP instruction compares two values and sets condition flags based on the result. The values can be in registers or immediate values.”

We won’t dive deep on registers, but they are basically special storage areas in the CPU. One we can see referenced in the CMP instruction is “rbp”. In addition to the stack, we can also use IDA to look at the constantly changing values in the registers. In this case the rbp value contains “00007FFFFFFFDE10”. If this value happens to look familiar, you will notice it’s because it is the line right after the place our buffer+payload land.

So we have rbp+var_4 … we know rbp is memory address “00007FFFFFFFDE10” … what about the “+var_4”? Looking back at the variables that were declared in the main function, we can see that var_4 is declared as:

var_4           = dword ptr -4

This basically means “the 4-bytes prior to” the register referenced (at least in this situation). So if we work backwards, starting from the rbp reference of “00007FFFFFFFDE10”, and we count 4 bytes (each byte being 2 hex characters) we can see we get 1337BAB3… which is what the [rbp+var_4] is being compared to — making the statement true… and moving us to complete the challenge.

If we “step through” individual instructions one at a time, with the “F7” key on the keyboard, we can watch in the graph view of IDA, as we end up on the instruction to read the flag.txt file.

Cool. So using IDA we were able to visualize our input in memory and how we could break out of the intended buffer due to the use of gets() to overwrite another value that was later used in a decision making “cmp” instruction. We also learned that the gets() function will append a null byte when it finishes reading a string literal.

Overall, this is more of a “surgical approach” to solving this challenge, as where the padding brute-force method we used in the previous article was basic and didn’t require us to necessarily view the data in memory or analyze the program from an assembly perspective. As I mentioned initially, sometimes completing the same challenge using multiple techniques is the best way to learn more about how something works and that was certainly the case here!

As always, thanks for reading and/or skimming :)

-N0ur5

--

--

N0ur5
N0ur5

Written by N0ur5

Pentester, bug hunter, red/purple teamer, all that good stuff.

No responses yet