d3d d3d is a computer security researcher and bug bounty hunter, that ❤'s exploit development, writing security tools and helping others learn about Information Security.

Exploit Exercise’s Phoenix x64 VM – Stack Zero

6 min read

Exploit Exercises

In this post, I will be covering the first (of many) stack challenges on the Phoenix VM from exploit education. I will try to beat every challenge without help from other write-ups or papers, but I will be referring to the radare2 documentation for a lot of these challenges.

Virtual Machine

Phoenix introduces basic memory corruption issues such as buffer overflows, format strings and heap exploitation without any form of modern exploit mitigation systems enabled. It has both 32 bit and 64 bit levels available, for both X86 and ARM systems.

In this post I will be focusing on the amd64 version of the stack-zero challenge.

Source Code

The following is the source code for the vulnerable binary stack-zero file.

Assuming you know a little C, you can see by checking the code above that the file is fairly straight forward. Starting at function main (line 23), the first thing that is created is a struct datatype by the name of locals, that contains 2 members, a character array of 64 bytes and a integer with the volatile qualifier prefix, as seen below.

Phoenix Stack Zero
Phoenix Stack Zero

In a struct datatype, the member variables are usually right next to one another in memory when declared as seen in the above image. The image shown below is a visual representation of the struct members in memory.

It is also important to note the volatile keyword in the above integer declaration, as the volatile keyword has special significance here. You might be thinking… what the fuck does “volatile” even mean in this context?!

The volatile keyword is intended to prevent the compiler from applying any optimizations on objects that can change in ways that cannot be determined by the compiler.

Objects declared as volatile are omitted from optimization because their values can be changed by code outside the scope of current code at any time. The system always reads the current value of a volatile object from the memory location rather than keeping its value in temporary register at the point it is requested, even if a previous instruction asked for a value from the same object. So the simple question is, how can value of a variable change in such a way that compiler cannot predict.

https://www.geeksforgeeks.org/understanding-volatile-qualifier-in-c/

I think the “volatile” keyword is basically there to make sure the compiler optimizations don’t move the changeme variable too far outside the gets() overflow stack (since its an overflow).

Below you can see the next section of code starting at line 31, sets the locals.changeme variable to 0, then instantly asks for user input using the gets() function.

The user-input received by gets() will be stored in the locals.buffer variable which contains 64 bytes of space. However, using the gets() function is considered insecure due to its lack of bounds checking when writing data into a fixed sized variable, like locals.buffer for instance, so overwriting the buffer is possible in this scenario. You will also notice the locals struct has the vulnerable buffer, right next to the variable we want to change… see where I am going with this 😀

It looks to me that I should be able to send a large buffer (more than 64 bytes) to the gets() function, and it should overwrite data going past the locals.buffer, and into the locals.changeme address space.

Debugging

Before taking a look at the disassembly of the stack-zero binary, let’s run it from the cli to see what is happening from the client point of view.

The program presents a stdin buffer to send whatever data you want to the program which will be stored in locals.buffer as seen in source code above. This stdin buffer is what I am going to abuse in order to overwrite the locals.changeme variable in memory.

To get more information about the file, I am going to run the rabin2 tool against the stack-zero binary to get a better idea of what features or protections the binary may be using.

The output provided by rabin2 lists detailed information about the binary including any security mechanisms that may be enabled, plus a lot more including byte alignment, endianness, and even the language the binary was written in. Before each challenge it is a good idea to make sure you check what security mechanisms are enabled for the binary.

Another way to check what security mechanisms the binary may be using is the checksec command which outputs useful information about the file’s security as seen below.

As you can see in both of the above results, this specific binary is not using any type of security mitigation and should be a fairly straight forward stack overflow.

Next thing to do is open the binary in radare2 by using the shortcut r2 command, with the -d flag as seen below.

After loading the stack-zero process, r2 dumps us at the prompt which is shown above in yellow. The above output also displays the PID of the process, the starting location in memory, and the assembly bits.

Next, I want to analyze all the referenced code by issuing the aaa command within the r2 prompt as seen below.

The aaa command will analyze all symbols and entry points.

The most common radare2 analysis command sequence is aa, which stands for “analyze all”. That all is referring to all symbols and entry-points. If your binary is stripped you will need to use other commands like aaaaabaaraac or so.

https://radare.gitbooks.io/radare2book/content/analysis/code_analysis.html

To make sure I am starting at the main function, I use the following command which seeks to the location of main() in memory.

As you can see, the prompt address changed from 0x7ffff7dc5d34 to 0x004006dd which is the location of the main() function. To see the visual layout of the program, I use the V command at the prompt, which should open up a visual viewer as seen below.

Note: You can cycle through the different views using p and P.

When using visual mode within r2, you can see the possible logic paths the program can take depending on the conditionals provided. For instance, in the above code, you can see the conditional logic was split into their own boxes, to show you that one segment of code runs if True, the other will run if False. To exit out of visual mode, you can press the q key which should bring you back to the r2 prompt.

To get a better idea of how the C code compares to the assembly, I created a quick code reference using C code with ASM comments.

While reviewing the above code, it is clear to me that to affect the je 40061c instruction, I need to overflow the locals.buffer via gets(). To test this overflow, I will create a rarun2 template using a payload of 65 ‘A’ characters instead of piping data to the binary directly on the command line.

In my solutions directory, I generated the overflow string by echoing python output into a file named stack-zero-payload.txt with the following command.

Then to create the rarun2 template, I created another file in my solutions directory named stack-zero.rr with the following contents.

Note: Ensure to add the rarun2 interpreter instead of bash

The rarun2 template is fairly straight forward, as it lists which program to execute, and what data to send as stdin, but these are only a TINY fraction of what this tool can do. Check out the docs here for more information.

With the template created, I can use the template with r2 by supplying the template location and the -R option as seen below. This will load the program with the stdin ready to go when executed.

Now with everything ready to go, I am going to open up a visual view of the stack, and set a breakpoint right before the gets() function overflow. This will allow us to step through the overflow, and visualize exactly what is happening.

Before jumping to main() to set breakpoints, I need to analyze any symbols and entry points, and load the detected functions with aaa and afl respectfully.

At this point I jumped into the visual viewer by using the V command, then cycling to the window I wanted by using the p key. The following image is the window where I am going to examine the stack while stepping through the program.

In the above visual view, the stack which is outlined in red, only shows 64 bytes by default. Since the buffer we are overwriting is 64 bytes in length, I need to view more of the stack on screen to get a better visual view of the overflow. To do this within r2, you can press the : key to open up a command prompt, then use the e command to set the stack.size variable to a higher number as seen below.

After setting the new stack size, you can now see 128 bytes in the stack instead of the 64, which makes a big difference as seen below.

The next thing to do here, is to set a breakpoint on the 0x00400609 address, which follows the call to gets(). At this point during execution, the buffer should have overwritten 1 byte into the address space of the changeme variable located at [rbp - local_10h]. This is because the buffer of 64 bytes could not contain 65 bytes, letting 1 spill into the neighboring address, which is occupied by the changeme variable.

The db command sets a breakpoint at a specific location, and the dc command continues execution until it hits a breakpoint.

After pressing the s key for stepping into the code after the breakpoint, the instruction loaded the locals.buffer which overwrote the locals.changeme variable from 0 to 0x41 as seen in purple below.

The above image shows 1 byte overflowing into the locals.changeme variable which is shown within the stack outlined by purple. The rax register reflects this by displaying the 0x41 which isn’t 0. Since the next instruction is a test, the test will not be equal and the je instruction will not fire which allows us to pass the challenge. More importantly, you know exactly HOW you passed the challenge.

Allowing the execution to continue, you can see we received the output we wanted to pass the challenge.

This challenge is a good start to learning how assembly works, and how the stack is used for multiple things, including storing struct variable data which can easily overwrite its neighbors memory if insecure functions are being used, such as gets().

d3d d3d is a computer security researcher and bug bounty hunter, that ❤'s exploit development, writing security tools and helping others learn about Information Security.

Leave a Reply

Your email address will not be published. Required fields are marked *

×

*NOTE*

I am currently updating the site, so posts will starting showing up from my previous blog very soon.