I have some example code here which I'm using to understand some C behaviour for a beginner's CTF:
// example.c
#include <stdio.h>
void main() {
void (*print)();
print = getenv("EGG");
print();
}
Compile: gcc -z execstack -g -m32 -o example example.c
Usage: EGG=$(echo -ne '\x90\xc3) ./example
If I compile the code with the execstack
flag, the program will execute the opcodes I've injected above. Without the flag, the program will crash due to a segmentation fault.
Why exactly is this? Is it because getenv
is storing the actual opcodes on the stack, and the execstack flag allows jumps to the stack? Or does getenv
push a pointer onto the stack, and there are some other rules about what sections of memory are executable? I read the manpage, but I couldn't work out exactly what the rules are and how they're enforced.
Another issue is I think I'm also really lacking a good tool to visualise memory whilst debugging, so its hard to figure this out. Any advice would be really appreciated.
execstack Marks the object as requiring executable stack.
Non-executable stack (NX) is a virtual memory protection mechanism to block shell code injection from executing on the stack by restricting a particular memory and implementing the NX bit.
getenv
doesn't store the env var's value on the stack. It's already on the stack from process startup, and getenv
obtains a pointer to it.
See the i386 System V ABI's description of where argv[] and envp[] are located at process startup: above [esp]
.
_start
doesn't copy them before calling main
, just calculates pointers to them to pass as args to main
. (Links to the latest version at https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI, where the official current version is maintained.)
Your code is casting a pointer to stack memory (containing the value of an env var) into a function pointer and calling through it. Look at the compiler-generated asm (e.g. on https://godbolt.org/): it'll be something like call getenv
/ call eax
.
-zexecstack
in your kernel version1 makes all your pages executable, not just the stack. It also applies to .data
, .bss
, and .rodata
sections, and memory allocated with malloc
/ new
.
The exact mechanism on GNU/Linux was a "read-implies-exec" process-wide flag that affects all future allocations, including manual use of mmap
. See Unexpected exec permission from mmap when assembly files included in the project for more about the GNU_STACK
ELF header stuff.
Footnote 1: Linux after 5.4 or so only makes the stack itself executable, not READ_IMPLIES_EXEC: Linux default behavior of executable .data section changed between 5.4 and 5.9?
Fun fact: taking the address of a nested function that accesses its parents local variables gets gcc to enable -zexecstack
. It stores code for an executable "trampoline" onto the stack that passes a "static chain" pointer to the actual nested function, allowing it to reference its parent's stack-frame.
If you wanted to exec data as code without -zexecstack
, you'd use mprotect(PROT_EXEC|PROT_READ|PROT_WRITE)
on the page containing that env var. (It's part of your stack so you shouldn't remove write permission; it could be in the same page as main's stack frame for example.)
Related:
With GNU/Linux ld
from binutils before late 2018 or so, the .rodata
section is linked into the same ELF segment as the .text
section, and thus const char code[] = {0xc3}
or string literals are executable.
Current ld
gives .rodata
its own segment that's mapped read without exec, so finding ROP / Spectre "gadgets" in read-only data is no longer possible, unless you use -zexecstack
. And even that doesn't work on current kernels; char code[] = ...;
as a local inside a function will put data on the stack where it's actually executable. See How to get c code to execute hex machine code? for details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With