Exactly what cases does the gcc execstack flag allow and how does it enforce it?
Asked Answered
D

1

11

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.

Disinfectant answered 16/11, 2018 at 22:31 Comment(1)
I'm also really lacking a good tool to visualise memory -- you can use a combination of gdb and /proc/<pid>/maps (to check permissions etc on the sections)Craigcraighead
B
13

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.

Blaney answered 16/11, 2018 at 22:51 Comment(4)
Nice answer. So if I copy the content of what getenv() returns into an executable mmap-ed region, then -zexecstack isn't needed? Because that mmap-ed region isn't on the stack? Thanks.Dampen
Is there any downside of using -zexecstack? Such as performance? If not, I would imagine gcc makes it a default?Dampen
@HCSF: Yes, you can copy data into a page with PROT_EXEC permission, or more simply use mprotect to change permissions on the existing page. The obvious massive downsize to -zexecstack is security: any buffer-overflow vulnerability of a stack buffer allows a code-injection exploit directly without having to find ROP gadgets to "return" to. There's no performance downside. GCC actually does enable if if you take the address of a nested function.Blaney
@HCSF: see How to get c code to execute hex bytecode? for more about executing data as code with GCC.Blaney

© 2022 - 2024 — McMap. All rights reserved.