-2

Practically I'm trying to execute a shellcode created from a small program in Asm x64, the problem is that it always gives me a segmentation fault error even if my shellcode is clean and I have compiled the program in C in the correct way.

Assembly code:

global _start

section .text

_start:
    jmp code
    string: db "Hello world", 0xa

code:
    add al, 1
    xor rdi, rdi
    add rdi, 1
    lea rsi, [rel string]
    xor rdx, rdx
    add rdx, 12
    syscall

    xor rax, rax
    add rax, 60
    xor rdi, rdi
    syscall

Shellcode from assembly:

\xeb\x0c\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a\x04\x01\x48\x31\xff\x48\x83\xc7\x01\x48\x8d\x35\xe4\xff\xff\xff\x48\x31\xd2\x48\x83\xc2\x0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05

C code:

#include<stdio.h>
#include<string.h>

unsigned char code[] = "\xeb\x0c\x48\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a\x04\x01\x48\x31\xff\x48\x83\xc7\x01\x48\x8d\x35\xe4\xff\xff\xff\x48\x31\xd2\x48\x83\xc2\x0c\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05";

int main() {
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

C compilation command:

gcc -fno-stack-protector -z execstack execSC.c -o execSC

I have the same problem even if i try to do something simple like "\x90"

If I try to run the same shellcode with Python it work and doesn't give me the segmentation fault error

import ctypes, mmap, sys

# Check Python version
if sys.version_info >= (3, 0):
    def b(string, charset='latin-1'):
        if isinstance(string, bytes) and not isinstance(string, str):
            return (string)
        else:
            return bytes(string, charset)
else:
    def b(string):
        return bytes(string)

def create_shellcode_function (shellcode_str):
    shellcode_bytes = b(shellcode_str)

# Allocate memory with a RWX private anonymous mmap
exec_mem = mmap.mmap(-1, len(shellcode_bytes),
                     prot = mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC,
                     flags = mmap.MAP_ANONYMOUS | mmap.MAP_PRIVATE)

# Copy shellcode from bytes object to executable memory
exec_mem.write(shellcode_bytes)

# Cast the memory to a C function object
ctypes_buffer = ctypes.c_int.from_buffer(exec_mem)
function = ctypes.CFUNCTYPE( ctypes.c_int64 )(ctypes.addressof(ctypes_buffer))
function._avoid_gc_for_mmap = exec_mem

# Return pointer to shell code function in executable memory
return function

shellcode = "shellcode"
create_shellcode_function(shellcode)()
  • Did you not read the duplicate links on your previous question? `-z execstack` does *not* make global `unsigned char code[]` executable on modern kernels. Tutorials that assume that became obsolete somewhere around Linux kernel version 5.8, in case that's why you think you have the "correct command". – Peter Cordes Jul 17 '21 at 17:25

1 Answers1

0

Your Python code specifies mmap.PROT_EXEC, which makes the memory where you write the shell code region executable. However, unsigned char code[] = "..."; is written straight into the executable and later loaded in non-executable memory, which prevents you from executing the shell code.

Now, you specified -fno-stack-protector -z execstack, but the shell code isn't on the stack, so these options don't really matter.

As you can see on Compiler Explorer, the string literal with the shell code is placed in the .data section, which is not marked as executable.

C  : int (*ret)() = (int(*)())code;
ASM: mov    QWORD PTR [rbp-0x8],0x404060

So we know that code is at address 0x404060, which belongs to the .data section.

Output from readelf -S:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [14] .text             PROGBITS         0000000000401050  00001050
       00000000000001a5  0000000000000000  AX       0     0     16
  [23] .got.plt          PROGBITS         0000000000404000  00003000
       0000000000000028  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000404040  00003040
       0000000000000054  0000000000000000  WA       0     0     32
  [25] .bss              NOBITS           0000000000404094  00003094
       0000000000000004  0000000000000000  WA       0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), ...

So, the flags of .text, which is where the executable code is stored, are AX (SHF_ALLOC | SHF_EXECINSTR), so execution is allowed, but the flags of .data, where your shell code is stored, are WA, no X - so execution is not allowed.


However if you place unsigned char code[] = "..."; in the main function, everything will run fine because now the shell code will be on the stack:

ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
Hello world

However, this still crashes when compiled with -O3, which can be fixed by declaring code as volatile (https://godbolt.org/z/TG8aGe6c4).

Now this code:

int (*ret)() = (int(*)())code;
ret();

Compiles to:

main:
        push    rbx
        sub     rsp, 64
        ...
        # Move the stack pointer to `rbx`
        mov     rbx, rsp
        ... # rbx is never assigned to
        # Print the shell code length
        call    printf

        xor     eax, eax
        # Now start executing
        # data on the stack, since
        # rbx == rsp
        call    rbx

        add     rsp, 64
        xor     eax, eax
        pop     rbx
        ret
ForceBru
  • 43,482
  • 10
  • 63
  • 98
  • `-z execstack` *used to* result in setting the READ_IMPLIES_EXEC process "personality" flag in Linux, and thus did used to work for examples constructed like in the question, with non-const global variables. This changed recently, [Linux default behavior of executable .data section changed between 5.4 and 5.9?](https://stackoverflow.com/q/64833715), – Peter Cordes Jul 17 '21 at 17:29
  • ... so the possible answers to [How to get c code to execute hex machine code?](https://stackoverflow.com/a/55893781) are more complicated than they used to be, probably invalidating some tutorials. (This is of course intentional to reduce the attack surface for code injection and ROP attacks.) Anyway, if the querent was following some tutorial (hence the claim of having "the correct commands"), this history would help to explain why a tutorial would say that, and that it's now wrong while this answer is right. – Peter Cordes Jul 17 '21 at 17:29