There are a couple of utilities to toggle whether or not the stack is executable, and ways to set this flag at various stages while compiling assembly files into ELF binaries. Below is an investigation of these options, inspired by the following kernel bug:
commit 07c3ae18cac4dc96bb87ddc7bf9ad93999890146 Author: Jiri Olsa Date: Mon Feb 6 18:54:06 2012 -0200 perf tools: Fix perf stack to non executable on x86_64 BugLink: http://bugs.launchpad.net/bugs/937915 commit 7a0153ee15575a4d07b5da8c96b79e0b0fd41a12 upstream. By adding following objects: bench/mem-memcpy-x86-64-asm.o the x86_64 perf binary ended up with executable stack. The reason was that above object are assembler sourced and is missing the GNU-stack note section. In such case the linker assumes that the final binary should not be restricted at all and mark the stack as RWX. Adding section ".note.GNU-stack" definition to mentioned object, with all flags disabled, thus omiting this object from linker stack flags decision.
1. Toggle noexecstack
with the linker
Here’s a bare-bones assembly file:
jesstess$ cat test.S .text .global _start _start: mov $0x1,%eax int $0x80
Assemble it into an ELF executable by hand, and check if the stack is executable:
jesstess$ as -o test.o test.S jesstess$ ld -s -o test test.o jesstess$ execstack test ? test
execstack
isn’t sure, because it checks for a GNU_STACK
section, which our program doesn’t have:
jesstess$ readelf -Sl test There are 3 section headers, starting at offset 0x90: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000400078 00000078 0000000000000007 0000000000000000 AX 0 0 4 [ 2] .shstrtab STRTAB 0000000000000000 0000007f 0000000000000011 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) Elf file type is EXEC (Executable file) Entry point 0x400078 There are 1 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000007f 0x000000000000007f R E 200000 Section to Segment mapping: Segment Sections... 00 .text
We can ask the linker to add a GNU_STACK
section:
jesstess$ ld -z execstack -s -o test_exec test.o
jesstess$ ld -z noexecstack -s -o test_noexec test.o
jesstess$ readelf -Wl test_noexec | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x8
jesstess$ readelf -Wl test_exec | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x8
jesstess$ diff <(hexdump test_noexec) <(hexdump test_exec)
8c8
< 0000070 0000 0020 0000 0000 e551 6474 0006 0000
---
> 0000070 0000 0020 0000 0000 e551 6474 0007 0000
jesstess$ execstack test_noexec test_exec
- test_noexec
X test_exec
A single SHF_EXECINSTR
bit dictates if the stack is executable.
2. Toggle noexecstack
in the assembly
We can toggle noexecstack
directly in the assembly by adding a .note.GNU-stack
section manually:
jesstess$ cat >> test.S .section .note.GNU-stack,"",%progbits jesstess$ as -o test_note.o test.S jesstess$ readelf -S test_note.o There are 8 section headers, starting at offset 0x88: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000007 0000000000000000 AX 0 0 4 [ 2] .data PROGBITS 0000000000000000 00000048 0000000000000000 0000000000000000 WA 0 0 4 [ 3] .bss NOBITS 0000000000000000 00000048 0000000000000000 0000000000000000 WA 0 0 4 [ 4] .note.GNU-stack PROGBITS 0000000000000000 00000048 0000000000000000 0000000000000000 0 0 1 [ 5] .shstrtab STRTAB 0000000000000000 00000048 000000000000003c 0000000000000000 0 0 1 [ 6] .symtab SYMTAB 0000000000000000 00000288 0000000000000090 0000000000000018 7 5 8 [ 7] .strtab STRTAB 0000000000000000 00000318 0000000000000008 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) jesstess$ ld -s -o test_note test.o jesstess$ execstack test_note - test_note
To specify an executable stack manually, add an "x"
in the .section
description: .section .note.GNU-stack, "x"
.
3. Toggle noexecstack
from the compiler
To get around passing complicated linker options for this toy example, pass -nostdlib
:
jesstess$ gcc -o test_gcc test.s -nostdlib jesstess$ readelf -Sl test_gcc There are 6 section headers, starting at offset 0x110: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .note.gnu.build-i NOTE 00000000004000b0 000000b0 0000000000000024 0000000000000000 A 0 0 4 [ 2] .text PROGBITS 00000000004000d4 000000d4 0000000000000007 0000000000000000 AX 0 0 4 [ 3] .shstrtab STRTAB 0000000000000000 000000db 0000000000000034 0000000000000000 0 0 1 [ 4] .symtab SYMTAB 0000000000000000 00000290 00000000000000a8 0000000000000018 5 3 8 [ 5] .strtab STRTAB 0000000000000000 00000338 0000000000000020 0000000000000000 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings), l (large) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific) Elf file type is EXEC (Executable file) Entry point 0x4000d4 There are 2 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000db 0x00000000000000db R E 200000 NOTE 0x00000000000000b0 0x00000000004000b0 0x00000000004000b0 0x0000000000000024 0x0000000000000024 R 4 Section to Segment mapping: Segment Sections... 00 .note.gnu.build-id .text 01 .note.gnu.build-id
As we can see above, gcc
doesn’t try to set a non-executable stack by default for assembly files, but we can pass a flag to tell gcc
what to do:
jesstess$ execstack -q test_gcc ? test_gcc jesstess$ gcc -z execstack -o test_gcc_exec test.s -nostdlib jesstess$ gcc -z noexecstack -o test_gcc_noexec test.s -nostdlib jesstess$ execstack -q test_gcc_exec test_gcc_noexec X test_gcc_exec - test_gcc_noexec
The situation with C source code is a bit different: gcc
tacks a .note.GNU-stack
section onto the end of C files by default.
We can verify this by stopping gcc
‘s compilation of a bare-bones C file before running the assembler:
jesstess$ cat test.c int main() { return 0; } jesstess$ gcc -o test.i -S test.c jesstess$ cat test.i .file "test.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 movq %rsp, %rbp .cfi_offset 6, -16 .cfi_def_cfa_register 6 movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" .section .note.GNU-stack,"",@progbits
4. Toggle noexecstack
on an executable
We can use execstack
to toggle the behavior of an already-compiled executable:
jesstess$ gcc -o ctest test.c jesstess$ execstack -q ctest - ctest jesstess$ execstack -s ctest; execstack -q ctest X ctest jesstess$ execstack -c ctest; execstack -q ctest - ctest
5. When trampolines make an executable stack necessary
A gcc
extension allows nested functions, which require an executable stack under certain conditions. To quote this blog post: “if you pass a local function (a) as a parameter to another function (b) from an outer calling function (c), then gcc makes that local function a trampoline that’s resolved at runtime, because AFAICS the function is on the stack.”
We can verify this with a short example:
jesstess$ cat trampoline.c int main() { int a = 1; int nested() { return a; } int (*fptr)() = nested; return fptr(); } jesstess$ gcc -o trampoline trampoline.c jesstess$ execstack -q trampoline X trampoline jesstess$ ./trampoline jesstess$ execstack -c trampoline jesstess$ ./trampoline Segmentation fault
6. Finding binaries with an executable stack
scanelf
, part of the pax-utils
package, makes short work of identifying binaries that want an exectuable stack:
jesstess$ scanelf -lpeq RWX --- --- /usr/lib32/libSDL-1.2.so.0.11.3 RWX --- --- /lib/klibc-EBLO2mlo7LXngcucphVUH-0CbT0.so RWX --- --- /usr/bin/grub-editenv RWX --- --- /usr/bin/grub-mkpasswd-pbkdf2 RWX --- --- /usr/bin/grub-mklayout RWX --- --- /usr/bin/grub-menulst2cfg RWX --- --- /usr/bin/grub-mount RWX --- --- /usr/bin/grub-fstest RWX --- --- /usr/bin/grub-mkfont RWX --- --- /usr/bin/grub-mkrelpath RWX --- --- /usr/bin/grub-mkimage RWX --- --- /usr/bin/grub-script-check RWX --- --- /usr/sbin/grub-probe RWX --- --- /usr/sbin/grub-mkdevicemap RWX --- --- /usr/sbin/grub
The flags to scanelf
say to:
-l
: scan all directories listed in/etc/ld.so.conf
-p
: scan all directories in your$PATH
-e
: printGNU_STACK
information-q
: only print data for binaries with ‘bad’ attributes
Let’s pick one reported binary to confirm:
jesstess$ execstack /usr/sbin/grub X /usr/sbin/grub jesstess$ readelf -l /usr/sbin/grub | grep GNU_STACK GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4