A Modification to %Esp Cause Sigsegv

A modification to %esp cause SIGSEGV

It's because in x86-64 assembly modifying a 32-bit register such as esp zeroes the highest 32 bits of the corresponding 64-bit register, rsp in this case. In x86-64 the stack pointer rsp is a 64-bit register.

x86-64 Linux always puts the stack pointer near the top of the user-space range of virtual address space, like 0x7fffffffe6d0 for example, so it's always outside the low 32 bits of virtual address space. The high half of RSP is non-zero and ESP != RSP.

So by modifying esp you make rsp point to an address where your program has no access rights, and thus cause a segmentation fault. In x86-64 code you don't normally use esp at all, and you should replace all instances of esp with rsp in your x86-64 code.

Segfault when loading from [esp] in 64-bit code

I'm in x64 mode, and like a bunch of people suggested in the comments using mov ebx, [rsp+8] worked, because esp are just the 4 lower bytes of the register. The stack is outside the low 4 GiB of virtual address space, so ESP != RSP and [esp] will be an unmapped page.

Note that x86-64 calling conventions pass the first few args in register, not on the stack, so you normally don't want to do this at all (unless your function has lots of args).

ESP register dereference segmentation fault, x86

In 64-bit mode, the stack pointer is RSP, not ESP. Using ESP uses the low 32 bits of the register, which isn’t a valid memory address.

Reason for SIGSEGV


But, the destination address of this instruction is a valid address and this memory is mapped as writable.

Not it's not (or the instruction wouldn't have caused a SIGSEGV).

The destination 0x80f8f5c is "covered" by this LOAD segment:

LOAD           0x0b06dc 0x080f86dc 0x080f86dc 0x015f8 0x07730 RW  0x1000

but also by this:

GNU_RELRO      0x0b06dc 0x080f86dc 0x080f86dc 0x00924 0x00924 R   0x1

the GNU_RELRO asks the runtime loader to make this part of address space read-only after the loader has performed the relocation (which is exactly what it did, and what triggered your crash).

Is there a way in gdb to figure out if the address is mapped as readonly or not?

You can ask gdb with info proc map, or just look in /proc/<pid>/maps. Either way you'll discover that the memory is mapped read-only.

what is causing SIGSEV?


long size = 10000000;
struct foo *bar[size];

will create a very big array, which may cause stack overflow, and therefore your program receive the SIGSEV.

You should create this array dynamically:

struct foo *bar = malloc(size * sizeof(struct foo *));

Why does the program work normally if these is not any function call in main()?

The definition of foo will cause main() to have a large stack frame at runtime. If you does not call any function in main(), this large stack frame will not be actually allocated or accessed (the entrance code of main() only make sure that amounts of memory be reserved by manipulating some registers and memory cells); but if you call a function in main(), the calling itself will try to access some addresses in that main() stack frame, because of stack overflow, those addresses may not be valid, this will cause SIGSEV be sent.

If you disassemble and compare the working and not-working versions of this program, this would be obvious. You could also find it out by stepping through the instructions of not-working main() one by one.


Without function call in main():

0x00001ff0 <main+0>:    push   %ebp
0x00001ff1 <main+1>: mov %esp,%eax
0x00001ff3 <main+3>: mov %esp,%ebp
0x00001ff5 <main+5>: sub $0x2625a10,%esp
0x00001ffb <main+11>: mov %eax,%esp
0x00001ffd <main+13>: leave
0x00001ffe <main+14>: ret

Call exit() in main():

0x00001fe0 <main+0>:    push   %ebp
0x00001fe1 <main+1>: mov %esp,%ebp
0x00001fe3 <main+3>: sub $0x2625a28,%esp
0x00001fe9 <main+9>: movl $0x0,(%esp) <==== This causes segfault.
0x00001ff0 <main+16>: call 0x3000 <dyld_stub_exit>

Why segmentation fault doesn't occur with smaller stack boundary?

You're not overwriting the saved eip, it's true. But you are overwriting a pointer that the function is using to find the saved eip. You can actually see this in your i f output; look at "Previous frame's sp" and notice how the two low bytes are 00 35; ASCII 0x35 is 5 and 00 is the terminating null. So although the saved eip is perfectly intact, the machine is fetching its return address from somewhere else, thus the crash.


In more detail:

GCC apparently doesn't trust the startup code to align the stack to 16 bytes, so it takes matters into its own hands (and $0xfffffff0,%esp). But it needs to keep track of the previous stack pointer value, so that it can find its parameters and the return address when needed. This is the lea 0x4(%esp),%ecx, which loads ecx with the address of the dword just above the saved eip on the stack. gdb calls this address "Previous frame's sp", I guess because it was the value of the stack pointer immediately before the caller executed its call main instruction. I will call it P for short.

After aligning the stack, the compiler pushes -0x4(%ecx) which is the argv parameter from the stack, for easy access since it's going to need it later. Then it sets up its stack frame with push %ebp; mov %esp, %ebp. We can keep track of all addresses relative to %ebp from now on, in the way compilers usually do when not optimizing.

The push %ecx a couple lines down stores the address P on the stack at offset -0x8(%ebp). The sub $0x20, %esp makes 32 more bytes of space on the stack (ending at -0x28(%ebp)), but the question is, where in that space does buffer end up being placed? We see it happen after the call to dumb_function, with lea -0x20(%ebp), %eax; push %eax; this is the first argument to strcpy being pushed, which is buffer, so indeed buffer is at -0x20(%ebp), not at -0x28 as you might have guessed. So when you write 24 (=0x18) bytes there, you overwrite two bytes at -0x8(%ebp) which is our stored P pointer.

It's all downhill from here. The corrupted value of P (call it Px) is popped into ecx, and just before the return, we do lea -0x4(%ecx), %esp. Now %esp is garbage and points somewhere bad, so the following ret is sure to lead to trouble. Maybe Px points to unmapped memory and just attempting to fetch the return address from there causes the fault. Maybe it points to readable memory, but the address fetched from that location does not point to executable memory, so the control transfer faults. Maybe the latter does point to executable memory, but the instructions located there are not the ones we want to be executing.


If you take out the call to dumb_function(), the stack layout changes slightly. It's no longer necessary to push ebx around the call to dumb_function(), so the P pointer from ecx now winds up at -4(%ebp), there are 4 bytes of unused space (to maintain alignment), and then buffer is at -0x20(%ebp). So your two-byte overrun goes into space that's not used at all, hence no crash.

And here is the generated assembly with -mpreferred-stack-boundary=2. Now there is no need to re-align the stack, because the compiler does trust the startup code to align the stack to at least 4 bytes (it would be unthinkable for this not to be the case). The stack layout is simpler: push ebp, and subtract 24 more bytes for buffer. Thus your overrun overwrites two bytes of the saved ebp. This is eventually popped from the stack back into ebp, and so main returns to its caller with a value in ebp that is
not the same as on entry. That's naughty, but it so happens that the system startup code doesn't use the value in ebp for anything (indeed in my tests it is set to 0 on entry to main, likely to mark the top of the stack for backtraces), and so nothing bad happens afterwards.

x86_64 assembly %rsp vs %esp

In x86_64, addresses are 64 bits, so how can you expect to do 32-bit math on it and still working fine? Moreover most 32-bit operations on x86_64 zero out the top 32 bits, render the address invalid



Related Topics



Leave a reply



Submit