2

This program overrides the return address of the main function to point to a character array of encoded x86-64 assembly instructions. The instructions simply encode a NOP followed by the syscall exit(1).

char shellcode[] = "\x90\x48\x31\xc0\x48\x89\xc7\x48\xff\xc7\x04\x3c\x0f\x05";
void main() {
      long int *ret;
      ret = (long int *)&ret + 2;
      (*ret) = (long int)shellcode;
}

It is compiled with

gcc program.c -o program.exe -fno-stack-protector -z execstack

on a WSL. (The Ubuntu app from the Microsoft Store)

The -z execstack flag should cause the stack, as well as all other writable portions of the program, to be executable.

Since the global character array resides in the .data of the assembly code it should be executable. This is true when running the program on a Linux VM, but apparently not on the WSL? On WSL, control is still properly redirected to the NOP, but it segfaults if you try to execute that instruction (presumably on code-fetch from a non-executable page). Running gdb shows this after returning from main:

=> 0x8004010 <shellcode>:        nop
   0x8004011 <shellcode+1>:      xor    %rax,%rax
(gdb) si
Program received signal SIGSEGV, Segmentation fault.

What could be causing this inconsistency between WSL and real Linux? Does WSL enforce strict W^X, overriding even -z execstack?

Are other Windows security features at play? I do not know if it is a WSL 1 or 2, but the uname -a output is:

Linux LAPTOP-M91FQN9V 4.4.0-18362-Microsoft #836-Microsoft Mon May 05 16:04:00 PST 2020 x86_64 x86_64 x86_64 GNU/Linux

What could be preventing the instructions from running, when the same does not occur on a Linux VM?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
0_bscure
  • 37
  • 2
  • You could try moving `char shellcode[]` into `main()` so that it is the stack segment. – Weather Vane Jul 27 '20 at 19:38
  • 3
    @WeatherVane Neither data nor stack are normally executable. – fuz Jul 27 '20 at 19:38
  • You can do some reading on the execute disable bit, but basically for security you can't execute code that stored in the data section of your program memory. – Emmanuel Mathi-Amorim Jul 27 '20 at 19:40
  • 1
    Or with `-fno-stack-protector`, it does work for me: it modifies the return address and the shellcode does run without problem for me. **So if the `nop` is faulting, I can only conclude that you forgot `-z execstack`.** (You don't need to disable randomize_va_space; the address calculation is relative to the local var on the stack anyway. BTW, `__builtin_frame_address(0` (https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html) might give you a more reliable anchor to offset from, less dependent on compiler version.) – Peter Cordes Jul 27 '20 at 20:05
  • What Linux distro do you have? 0x8004010 is an unusual address for an x86-64 executable. Usually it's either `0x0000555555558028 in shellcode` in a PIE or `0x404028 ` in a non-PIE. That 0x8004010 looks like a static address from a 32-bit executable, but then GCC wouldn't have disassembled those bytes at `xor %rax, %rax`, the wasted REX prefix would have been a `dec`. Did you possibly use a linker script? Or have your `ld` or `gcc` configured unusually somehow? – Peter Cordes Jul 27 '20 at 20:10
  • @PeterCordes Sorry about the misleading title, this is my first post. Trying my best to not be a n00b. The address does get overridden without the -fno-stack-protector because that would adjust the stack if it were left on, however I promise I compiled it with -z execstack. Ive re-done it a few times now and followed along in gdb to get the same result. It says the next instruction is the NOP and then upon "(gdb) si" it SegFaults – 0_bscure Jul 27 '20 at 20:26
  • @PeterCordes I am running the ubuntu VM from the Microsoft app store. lsb_release -a gives "Ubuntu 20.04 LTS release 20.04 – 0_bscure Jul 27 '20 at 20:29
  • A full VM? Or do you mean WSL? Possibly `-z execstack` is broken on WSL. Is it WSL1 (still using the Windows kernel) or WSL2 (paravirtualized Linux using some hardware-virtualization support)? Put full details in your question and I'll reopen it. Also, probably a good idea to simplify the [mcve] to just calling through a function pointer, instead of overwriting the return address. – Peter Cordes Jul 27 '20 at 20:33
  • @PeterCordes trying to provide as much info as I can. I did not adjust the linker or how gcc behaves. It is stock from the ubuntu distro. If it helps, before hitting the breakpoint for the RET, disassembling main before running it in gdb shows LEA as loading in 0x4010 . Not sure how the address can be different later – 0_bscure Jul 27 '20 at 20:33
  • 1
    And it turns out the title isn't misleading after all, it does reach your shellcode if I compile with the same options as you: `gcc -fno-stack-protector -z execstack shellcode.c -o test`. Perhaps worth testing other ways of running shellcode, e.g. `mmap(PROT_WRITE|PROT_EXEC)`, or a similar `mprotect`. Perhaps Windows is enforcing W^X, and that takes precedence over `execstack`'s read-implies-exec, so writing and then doing mprotect to change it to read-only+exec might be a good idea. – Peter Cordes Jul 27 '20 at 20:36
  • `0x4010` looks like a relative offset in a PIE executable. It's not given a proper address until runtime, when the kernel loads it. To simplify more, use `gcc -no-pie -fno-pie -fno-stack-protector -z execstack shellcode.c -o test`. [32-bit absolute addresses no longer allowed in x86-64 Linux?](https://stackoverflow.com/q/43367427) – Peter Cordes Jul 27 '20 at 20:37
  • @PeterCordes unfortunately I dont know if it is WSL1 or 2 or how to tell. It is the ubuntu app on windows, which I am starting to suspect is the issue. Maybe -z execstack doesnt work there, or its a complicated hardware virtualization issue, so I will load a real linux VM and see if the problem persists. I am not sure of the detail requirements for reopening the question. Thank you for your help! – 0_bscure Jul 27 '20 at 20:39
  • 1
    It will definitely work fine in a real Linux VM; guest page tables work fully normally from the guest's POV, and Ubuntu doesn't default to OpenBSD style strict W^X. I assume there's some way to distinguish WSL1 vs. WSL2, but haven't googled. Posting full `uname -a` output might be sufficient for someone else to tell, if you want to [edit] your question. It would be interesting to know what's going on with WSL here, and useful to future readers with the same problem. – Peter Cordes Jul 27 '20 at 21:08
  • @PeterCordes Certainly! I have edited my question to put forward the crux of the problem more. I am just learning about virtualization and operating systems, so I am glad I ran into limitations with WSL sooner rather than later. It worked perfectly in a VM so I'm considering it problem solved for now! Great help. – 0_bscure Jul 27 '20 at 21:55
  • @0_bscure It works flawlessly on my Ubuntu under WSL2, so it might be WSL1 issue. To get WSL info type `wsl -l -v` under windows console. – Paweł Łukasik Jul 28 '20 at 09:56
  • WSL2 is a full-fledged VM using HyperV while WSL1 is more of a subsystem (the whole picoprocess thing and the syscall hooking). So it might simply be that Microsoft took some shortcuts when parsing the ELF format. You could inspect the ELF segments permissions and then the process workspace to see if they match. If not, WLS1 is not 100% compatible with all the ELF details. – Margaret Bloom Jul 28 '20 at 11:19

0 Answers0