WinExec throws EXCEPTION_ACCESS_VIOLATION

114 Views Asked by At

I tried to modify this shellcode and make it run notepad.exe instead of calc.exe. I'm pushing notepad.exe onto the stack with this code snippet:

mov r9, 0x0000000000657865
push r9
mov r9, 0x2e64617065746f6e
push r9

Upon CALLing the WinExec the RCX register points to the notepad.exe string. enter image description here But then after entering kernel32.dll EXCEPTION_ACCESS_VIOLATION exception is thrown. enter image description here

This is the full shellcode code:

BITS 64
SECTION .text

%define B2W(b1,b2)                      (((b2) << 8) + (b1))
%define W2DW(w1,w2)                     (((w2) << 16) + (w1))
%define DW2QW(dw1,dw2)                  (((dw2) << 32) + (dw1))
%define B2DW(b1,b2,b3,b4)               ((B2W(b3, b4) << 16) + B2W(b1, b2))
%define B2QW(b1,b2,b3,b4,b5,b6,b7,b8)   ((B2DW(b5,b6,b7,b8) << 32) + B2DW(b1,b2,b3,b4))
%define W2QW(w1,w2,w3,w4)               ((W2DW(w3,w4) << 32) + W2DW(w1,w2))


; x64 WinExec *requires* 16 byte stack alignment and four QWORDS of stack space, which may be overwritten.
; http://msdn.microsoft.com/en-us/library/ms235286.aspx
%ifndef PLATFORM_INDEPENDENT
global shellcode
shellcode:
%ifdef FUNC                               ; assumes stack ends with 8 on entry, use STACK_ALIGN if it might not be.
%ifdef CLEAN                              ; 64-bit calling convention considers RAX, RCX, RDX, R8, R9, R10 and R11
    PUSH    RAX                           ; volatile. Use CLEAN if you want to preserve those as well.
    PUSH    RCX
    PUSH    RDX
%endif
    PUSH    RBX
    PUSH    RSI
    PUSH    RDI
    PUSH    RBP                           ; Stack now ends with 8 (!CLEAN) or is 16 byte (CLEAN) aligned
%endif
%ifdef STACK_ALIGN
%ifdef FUNC
    PUSH    RSP
    POP     RAX
%endif
    AND     SP, -16                       ; Align stack to 16 bytes
                                          ; (we can't force it to end with 8 without dummy push and then or)
    PUSH    RAX                           ; Force stack to end with 8 before next push, also saves RSP to restore stack
%elifdef CLEAN
    PUSH    RAX                           ; dummy push to make stack end with 8 before next push
%endif

; Note to SkyLined: instructions on 32-bit registers are automatically sign-extended to 64-bits.
; This means LODSD will set the high DWORD of RAX to 0 if top bit of EAX was 0, or 0xFFFFFFFF if it was 0x80000000.
    PUSH    BYTE 0x60                     ; Stack 
    POP     RDX                           ; RDX = 0x60
%else
%ifdef FUNC
%ifdef CLEAN
    PUSH    RAX                           ; exchanged RDX
    PUSH    RCX
%endif
    PUSH    RBX
    PUSH    RSI
    PUSH    RDI
    PUSH    RBP                           ; Stack now ends with 8 (!CLEAN) or is 16 byte (CLEAN) aligned
%endif
%ifdef CLEAN
%ifndef STACK_ALIGN
    PUSH    RAX                           ; dummy push to make stack end with 8 before next push
%endif
%endif
    MOV     DL, 0x60
%endif
%ifndef USE_COMMON
    ;PUSH    B2DW('c', 'a', 'l', 'c')      ; Stack = "calc\0\0\0\0" (stack alignment changes)
    mov r9, 0x0000000000657865
    push r9
    mov r9, 0x2e64617065746f6e
    push r9
    PUSH    RSP
    POP     RCX                           ; RCX = &("calc")
%endif
    SUB     RSP, RDX                      ; Stack was 16 byte aligned already and there are >4 QWORDS on the stack.
    MOV     RSI, [GS:RDX]                 ; RSI = [TEB + 0x60] = &PEB
    MOV     RSI, [RSI + 0x18]             ; RSI = [PEB + 0x18] = PEB_LDR_DATA
    MOV     RSI, [RSI + 0x10]             ; RSI = [PEB_LDR_DATA + 0x10] = LDR_MODULE InLoadOrder[0] (process)
    LODSQ                                 ; RAX = InLoadOrder[1] (ntdll)
    MOV     RSI, [RAX]                    ; RSI = InLoadOrder[2] (kernel32)
    MOV     RDI, [RSI + 0x30]             ; RDI = [InLoadOrder[2] + 0x30] = kernel32 DllBase
; Found kernel32 base address (RDI)
shellcode_common:
    ADD     EDX, DWORD [RDI + 0x3C]       ; RBX = 0x60 + [kernel32 + 0x3C] = offset(PE header) + 0x60
; PE header (RDI+RDX-0x60) = @0x00 0x04 byte signature
;                            @0x04 0x18 byte COFF header
;                            @0x18      PE32 optional header (= RDI + RDX - 0x60 + 0x18)
    MOV     EBX, DWORD [RDI + RDX - 0x60 + 0x18 + 0x70] ; RBX = [PE32+ optional header + offset(PE32+ export table offset)] = offset(export table)
; Export table (RDI+EBX) = @0x20 Name Pointer RVA
    MOV     ESI, DWORD [RDI + RBX + 0x20] ; RSI = [kernel32 + offset(export table) + 0x20] = offset(names table)
    ADD     RSI, RDI                      ; RSI = kernel32 + offset(names table) = &(names table)
; Found export names table (RSI)
    MOV     EDX, DWORD [RDI + RBX + 0x24] ; EDX = [kernel32 + offset(export table) + 0x24] = offset(ordinals table)
; Found export ordinals table (RDX)
find_winexec_x64:
; speculatively load ordinal (RBP)
    MOVZX   EBP, WORD [RDI + RDX]         ; RBP = [kernel32 + offset(ordinals table) + offset] = function ordinal
    LEA     EDX, [RDX + 2]                ; RDX = offset += 2 (will wrap if > 4Gb, but this should never happen)
    LODSD                                 ; RAX = &(names table[function number]) = offset(function name)
    CMP     DWORD [RDI + RAX], B2DW('W', 'i', 'n', 'E') ; *(DWORD*)(function name) == "WinE" ?
    JNE     find_winexec_x64              ;
    MOV     ESI, DWORD [RDI + RBX + 0x1C] ; RSI = [kernel32 + offset(export table) + 0x1C] = offset(address table)
    ADD     RSI, RDI                      ; RSI = kernel32 + offset(address table) = &(address table)
    MOV     ESI, [RSI + RBP * 4]          ; RSI = &(address table)[WinExec ordinal] = offset(WinExec)
    ADD     RDI, RSI                      ; RDI = kernel32 + offset(WinExec) = WinExec
; Found WinExec (RDI)
    CDQ                                   ; RDX = 0 (assuming EAX < 0x80000000, which should always be true)
    CALL    RDI                           ; WinExec(&("calc"), 0);
%ifdef FUNC
%ifdef CLEAN
%ifdef STACK_ALIGN
    ADD     RSP, 0x68                     ; reset stack to where it was after pushing registers
%else
    ADD     RSP, 0x70                     ; reset stack to where it was after pushing registers
%endif
%else
    ADD     RSP, 0x68                     ; reset stack to where it was after pushing registers
%endif
%ifndef PLATFORM_INDEPENDENT
%ifdef STACK_ALIGN
    POP     RSP
%endif
%endif
    POP     RBP                           ; POP registers
    POP     RDI
    POP     RSI
    POP     RBX
%ifndef PLATFORM_INDEPENDENT
%ifdef CLEAN
    POP     RDX                           ; POP additional registers
    POP     RCX
    POP     RAX
%endif
    RET                                   ; Return
%else
%ifdef CLEAN
    POP     RCX                           ; POP additional registers
    POP     RDX
%endif
%ifdef STACK_ALIGN
    POP     RSP
%endif
%ifdef CLEAN
    POP     RAX
%endif
    RET                                   ; Return
%endif
%endif

The only modification is that I'm pushing notepad.exe instead of calc.exe.
I can't figure out why the exception is thrown and how to fix it.

Shellcode is assembled and linked with:

nasm -fwin64 w64-exec-calc-shellcode.asm -o mybuilds\exe\x64-exec-calc-shellcode.obj
link /MACHINE:x64 /SUBSYSTEM:CONSOLE /OUT:mybuilds\exe\x64-exec-calc-shellcode.exe /NODEFAULTLIB /ENTRY:shellcode kernel32.lib mybuilds\exe\x64-exec-calc-shellcode.obj
1

There are 1 best solutions below

9
Sep Roland On

The only modification is that I'm pushing notepad.exe instead of calc.exe.

Why are you including .exe ?

;PUSH    B2DW('c', 'a', 'l', 'c')      ; Stack = "calc\0\0\0\0" (stack alignment changes)
mov r9, 0x0000000000657865
push r9
mov r9, 0x2e64617065746f6e
push r9
PUSH    RSP
POP     RCX                           ; RCX = &("calc")

I don't see the original version having added .exe to calc, and if that version works fine then do it like this avoiding embedded zero-bytes (thanks @Jester):

mov  rax, B2QW('n', 'o', 't', 'e', 'p', 'a', 'd', '*')
push rax                 ; Stack = "notepad*"
mov  RCX, RSP            ; RCX = &("notepad")
sub  byte [rcx + 7], '*' ; Make zero-terminator: Stack = "notepad\0"