Protostar – Stack1

Official description here.

This exercise demonstrates how to set a specific value to a neighbor variable thanks to a buffer overflow. It will also briefly describe how the content of a variable is laid out in memory.

Here is the source code of the executable we will analyse, /opt/protostar/bin/stack1:

Unlike the previous exercise, the variable buffer is populated thanks to the argument sent when the binary is called. Let’s quickly review the source:

At line 8 and line 9, the variable modified and buffer are declared. Then (at line 11), the if statement checks if the amount of arguments ( argc) is equal to 1. If it is the case, it prints the string “please specify an argument”, then exit.

At line 15, the variable modified is set to 0, then (line 16) the first argument used when the user called the binary ( argv[1]) is then copied ( strcpy()) to the array buffer.

So for instance, if the user runs the binary with the following command:

The value “Hello” will by copied in the array buffer.

Finally, at line 17, the if statement compares the value of the variable modified  (previously set to 0 at line 8) with the hexadecimal value 0x61626364. If it matches, the string “you have correctly got the variable to the right value” is printed, if not, the string “Try again, you got ox########” is printed, where the hashtags represents the value stored in the variable modified in hexadecimal (see format rules for printf).

Let’s look at the assembly code with objdump:

From 0x8048464 to 0x804846a, you should remember from stack0, it is the prologue followed by the memory alignment and the memory allocation in the stack for the variables modified and buffer.

At 0x804846d we can find the instruction that checks if argc is equal to 1. If not ( jne), it jumps to 0x8048487, otherwise, it just moves to the next instruction where 0x80485a0 is copied somewhere in the stack, then the value 1 is copied at the top of the stack and finally, the function errx() is called.

As expected, 0x80485a0 pointing to the string “please specify an argument“:

This it the typical process whenever a function is called with arguments. For instance, with the following function: errx(1, "a string"). Typically, in ASM, it we will first put the first argument on top of the stack, the second just after and so on.

errx() will then exit the program, now let see what will happen argc is different than 1, when we jump to 0x8048487. At this instruction, the value 0 is copied in the variable pointed at [esp+0x5c]. If you remember the C code, we can assume that  [esp+0x5c] is actually the variable modified.

In my last post, I mentioned that variables referenced based on ESP are usually the local variables (as just seen previously with errx()), while variables referenced based on EBP are usually arguments sent to the function. So at 0x804848f, we can see that the value pointed by [ebp+0xc] is copied in EAX, then (at 0x8048492), we skip 4 bytes to finally replace EAX with the content to which it is pointed to:

As shown in the previous gdb capture,  [ebp+0xc] points to an array that contains all the arguments typed when the binary has been called. Here in this case, “/opt/protostar/bin/stack1” and “Hello“.

So now (at 0x8048495), EAX points to the string “Hello”. The pointer is then (at 0x8048497) saved in the stack.

At 0x804849b, we then have the stack address [esp+0x1c] copied in EAX, then (at 0x804849f) EAX is copied in the stack. Basically, those three instructions are the equivalent of the following:

So if we look at the stack, we have the following:

The next instruction (at 0x80484a2) call the function strcpy(). This functions takes 2 arguments, a string pointer destination and a string pointer source. As explained earlier, the parameters are sent via the stacks. The top of the stack points to the destination, and the address just after point to the source.

So, instructions from 0x804848f to 0x80484a2 prepare the stack to execute the function strcpy() that will copy the string entered when executing the binary (in this example “Hello”) to the array buffer.

At 0x80484a7, we then have [esp+0x5c], identified as the variable modified, moved into EAX. Then at 0x80484ab EAX is compared with the value 0x61626364.

Then (at 0x80484b0) if EAX is not equal to 0x61626364, it jumps ( jne) to 0x80484c0, otherwise it moved to the next instruction (at 0x80484b2) where the pointer 0x80485bc is copied on top of the stack and puts is then called (at 0x80484b9).

Puts take a string pointer as argument then print it in stdout. Here in this case, the pointer points to the following string:

Finally at 0x80484be we have an unconditional jump ( jmp) to the end of the function with the instruction leave (at 0x80484d0) and ret (at 0x80484d6).

leave is used to set the the stack pointer back to the base pointer, then pop the base pointer to set it back to its initial value.

ret is used to redirect the instruction flow right after caller, so here in the case, we will end up right after the call of the function main.

Now let see what happen if modified is not equal to 0x61626364, when the conditional jump at 0x80484b0 is taken. At 0x80484c0, the content of [esp+0x5c] (variable modified) is moved to EDX. Then at 0x80484c4, the pointer 0x80485f3 is moved into EAX. Here is the content of 0x80485f3:

At 0x80484c9, EDX is moved to the stack, then (at 0x80484cd) EAX is moved at the top of the stack. So the top of the stack looks like this:

Where 0x080485f3 is the pointer to the string “Try again, you…” and 0x00000000 is the value of variable modified.

Finally, the function printf is called. This function prints a formatted output to the standard output stream. Here in this case, will be printed in stdout the following message: “Try again, you got 0x” concatenated with the hexadecimal value of the next variable in the stack, which means 0x00000000.

We now have reviewed the entire assembly code for the function main. As we can see, modified and buffer are organised in the stack the same way as in stack0. The variables are even located at the same reference, i.e. [esp+0x5c] and [esp+0x1c]. Therefore, the overflow will work with the exact same format: We write 60 characters in order to fill in the array buffer, then the additional characters after will overflow in the variable modified.

We know that modified must contains the value 0x61626364 (see at 0x80484ab). According to the ASCII table, 0x61 is the character a, 0x62 is the character b, 0x63 is c and 0x64 is d. So let’s try to call the binary with 60 character followed by “abcd”:

As you can see, modified does not contained 0x61626364 as expected, but 0x64636261. The reason is due to the endianness, briefly mentioned in stack0. I would highly recommend you to read this article “Understanding Big and Little Endian Byte Order” if you don’t understand yet the concept.

To make it short, the hexadecimal representation of the integer variable modified that has been overflow by a long string will display the four characters starting from right to left. So this means that if we want to have the value 0x61626364 in modified, we need to send the characters backward, i.e. “dcba”:

Ant it works! Here, we demonstrate that it is possible to change the content of a neighbor variable to a specific value thanks to a buffer overflow.

Protostar write-ups: stack0stack1stack2

Leave a Reply

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