uwd 0.3.5

Call Stack Spoofing for Rust
Documentation
;;
;; Code responsible for Call Stack Spoofing Via Synthetic (NASM)
;;
[BITS 64]

;;
;; Export
;;
GLOBAL SpoofSynthetic

[SECTION .data]

;;
;; Configuration structure passed to the spoof ASM routine
;;
STRUC Config
    .RtlUserThreadStartAddr       RESQ 1
    .RtlUserThreadStartFrameSize  RESQ 1
    
    .BaseThreadInitThunkAddr      RESQ 1
    .BaseThreadInitThunkFrameSize RESQ 1

    .FirstFrame                   RESQ 1
    .SecondFrame                  RESQ 1
    .JmpRbxGadget                 RESQ 1
    .AddRspXGadget                RESQ 1

    .FirstFrameSize               RESQ 1
    .SecondFrameSize              RESQ 1
    .JmpRbxGadgetFrameSize        RESQ 1
    .AddRspXGadgetFrameSize       RESQ 1

    .RbpOffset                    RESQ 1

    .SpooFunction                 RESQ 1
    .ReturnAddress                RESQ 1

    .IsSyscall                    RESD 1
    .Ssn                          RESD 1

    .NArgs                        RESQ 1
    .Arg01                        RESQ 1
    .Arg02                        RESQ 1
    .Arg03                        RESQ 1
    .Arg04                        RESQ 1
    .Arg05                        RESQ 1
    .Arg06                        RESQ 1
    .Arg07                        RESQ 1
    .Arg08                        RESQ 1
    .Arg09                        RESQ 1
    .Arg10                        RESQ 1
    .Arg11                        RESQ 1
ENDSTRUC

[SECTION .text]

;;
;; Function responsible for Call Stack Spoofing
;;
SpoofSynthetic:
    ;;
    ;; Saving non-vol registers
    ;;
    push QWORD rbp
    push QWORD rbx
    push QWORD r15

    ;;
    ;; Everything between RSP and RBP is our new stack frame for unwinding 
    ;;
    sub rsp, 210h
    mov rbp, rsp

    ;;
    ;; Creating stack pointer to Restore PROC
    ;;
    lea rax, [rel RestoreSynthetic]
    push rax
    lea rbx, [rsp]

    ;;
    ;; Cutting the call stack. The 0 pushed in this position will be the return address
    ;; of the next frame "RtlUserThreadStart", making it effectively the originating function
    ;;
    xor rax, rax
    push rax
    
    ;;
    ;; RtlUserThreadStart
    ;;
    sub rsp, [rcx + Config.RtlUserThreadStartFrameSize]
    push QWORD [rcx + Config.RtlUserThreadStartAddr]
    add QWORD [rsp], 21h

    ;;
    ;; BaseThreadInitThunk
    ;;
    sub rsp, [rcx + Config.BaseThreadInitThunkFrameSize]
    push QWORD [rcx + Config.BaseThreadInitThunkAddr]                          
    add QWORD [rsp], 14h

    ;;
    ;; Return Adress
    ;;
    mov rax, rsp

    ;;
    ;; First Frame (Fake origin)
    ;;
    push QWORD [rcx + Config.FirstFrame]
    sub rax, [rcx + Config.FirstFrameSize]

    sub rsp, [rcx + Config.SecondFrameSize]
    mov r10, [rcx + Config.RbpOffset]
    mov [rsp + r10], rax
    
    ;;
    ;; ROP Frames
    ;;
    push QWORD [rcx + Config.SecondFrame]

    ;;
    ;; JMP [RBX] Gadget / Stack Pivot (To restore original Control Flow Stack)
    ;;
    sub rsp, [rcx + Config.JmpRbxGadgetFrameSize]
    push QWORD [rcx + Config.JmpRbxGadget]

    sub rsp, [rcx + Config.AddRspXGadgetFrameSize]
    push QWORD [rcx + Config.AddRspXGadget]

    ;;
    ;; Set the pointer to the function to call in R11
    ;;
    mov r11, [rcx + Config.SpooFunction]
    jmp ParametersSynthetic

;;
;; Set the parameters to pass to the target function
;;
ParametersSynthetic:
    mov r12, rcx 
    mov rax, [r12 + Config.NArgs]
    
    ; Arg01 (rcx)
    cmp rax, 1
    jb skip_1
    mov rcx, [r12 + Config.Arg01]

skip_1:
    ; Arg02 (rdx)
    cmp rax, 2
    jb skip_2
    mov rdx, [r12 + Config.Arg02]

skip_2:
    ; Arg03 (r8)
    cmp rax, 3
    jb skip_3
    mov r8, [r12 + Config.Arg03]
    
skip_3:
    ; Arg04 (r9)
    cmp rax, 4
    jb skip_4
    mov r9, [r12 + Config.Arg04]

skip_4:
    ; Stack-based args
    lea r13, [rsp] 

    cmp rax, 5
    jb skip_5
    mov r10, [r12 + Config.Arg05]
    mov [r13 + 28h], r10

skip_5:
    ; Arg06
    cmp rax, 6
    jb skip_6
    mov r10, [r12 + Config.Arg06]
    mov [r13 + 30h], r10

skip_6:
    ; Arg07
    cmp rax, 7
    jb skip_7
    mov r10, [r12 + Config.Arg07]
    mov [r13 + 38h], r10

skip_7:
    ; Arg08
    cmp rax, 8
    jb skip_8
    mov r10, [r12 + Config.Arg08]
    mov [r13 + 40h], r10
    
skip_8:
    ; Arg09
    cmp rax, 9
    jb skip_9
    mov r10, [r12 + Config.Arg09]
    mov [r13 + 48h], r10

skip_9:
    ; Arg10
    cmp rax, 10
    jb skip_10
    mov r10, [r12 + Config.Arg10]
    mov [r13 + 50h], r10

skip_10:
    ; Arg11
    cmp rax, 11
    jb skip_11
    mov r10, [r12 + Config.Arg11]
    mov [r13 + 58h], r10

skip_11:
    cmp BYTE [r12 + Config.IsSyscall], 1
    je ExecuteSyscallSynthetic

    jmp ExecuteSynthetic

;;
;; Restores the original stack frame
;;
RestoreSynthetic:
    mov rsp, rbp
    add QWORD rsp, 210h
    pop QWORD r15
    pop QWORD rbx
    pop QWORD rbp
    ret

;;
;; Executes the target function
;;
ExecuteSynthetic:
    jmp r11

;;
;; Executes a native Windows system call using the spoofed context
;;
ExecuteSyscallSynthetic:
    mov r10, rcx
    mov eax, DWORD [r12 + Config.Ssn]
    jmp r11