阴影空间示例

时间:2015-10-22 05:11:12

标签: windows assembly 64-bit nasm

编辑:

我已经接受了以下答案,并且还添加了我自己的代码的最终修订版。希望它向人们展示影子空间分配的实际例子,而不是更多的单词。

编辑2:我还设法在YouTube视频的注释(所有内容)中找到了一个指向调用约定PDF的链接,该视频在Shadow Space和Linux上的Red Zone上有一些有趣的花絮。可在此处找到:http://www.agner.org/optimize/calling_conventions.pdf

ORIGINAL:

我已经在这里和整个互联网上查看了其他几个问题,但我似乎找不到在64位Windows程序集中调用子程序/ Windows API时分配“影子空间”的正确示例。

我的理解是:

  • 来电者应sub rsp,<bytes here>
  • 之前call callee
  • 如果需要,Callee应该使用它来存储寄存器(或者如果不需要寄存器保存,则使用局部变量)
  • 来电者清理它,例如:add rsp,<bytes here>
  • 分配的金额应与32个字节对​​齐

考虑到这一点,这就是我的尝试:

section .text

start:

    sub rsp,0x20 ; <---- Allocate 32 bytes of "Shadow space"

    mov rcx,msg1
    mov rdx,msg1.len
    call write

    add rsp,0x20

    mov rcx,NULL
    call ExitProcess

    ret

write:

    mov [rsp+0x08],rcx      ; <-- use the Shadow space
    mov [rsp+0x10],rdx      ; <-- and again

    mov rcx,STD_OUTPUT_HANDLE   ; Get handle to StdOut
    call GetStdHandle

    mov rcx,rax         ; hConsoleOutput
    mov rdx,[rsp+0x08]      ; lpBuffer
    mov r8,[rsp+0x10]       ; nNumberOfCharsToWrite
    mov r9,empty        ; lpNumberOfCharsWritten
    push NULL           ; lpReserved
    call WriteConsoleA

    ret

我的两个字符串是“Hello”和“World!\ n”。这设法在崩溃之前打印“Hello”。我怀疑我做得正确......除了我应该以某种方式清理(我不确定如何)。

我做错了什么?我已经尝试了大小的组合,并且在WinAPI调用之前也试过“分配影子空间”(我应该这样做吗?)。

应该注意的是,当我根本不关心影子空间时,它的效果非常好。但是,我正在尝试与ABI兼容,因为我的write函数调用WinAPIs(因此,不是叶子函数)。

2 个答案:

答案 0 :(得分:4)

必须在呼叫之前直接提供影子空间。想象阴影空间是旧stdcall / cdecl约定的遗物:对于WriteFile,你需要五次推动。阴影空间代表最后四次推送(前四个参数)。现在你需要四个寄存器,阴影空间(只是空格,内容无关紧要)和阴影空间(实际上是第一次推送)后的堆栈上的一个值。目前,调用者(start)的返回地址位于WriteFile将用作阴影空间的空间中 - &gt;崩溃。

您可以在函数GetStdHandle内为WinAPI函数(WriteConsoleAwrite)创建一个新的阴影空间:

write:
    push rbp
    mov rbp, rsp
    sub rsp, (8 + 32)       ; 5th argument of WriteConsoleA + Shadow space

    mov [rbp+16],rcx        ; <-- use the Shadow space of `start`
    mov [rbp+24],rdx        ; <-- and again

    mov rcx, -11            ; Get handle to StdOut
    call GetStdHandle

    mov rcx,rax             ; hConsoleOutput
    mov rdx, [rbp+16]       ; lpBuffer
    mov r8, [rbp+24]        ; nNumberOfCharsToWrite
    mov r9,empty            ; lpNumberOfCharsWritten
    mov qword [rsp+32],0    ; lpReserved - 5th argument directly behind the shadow space
    call WriteConsoleA

    leave
    ret

答案 1 :(得分:2)

为了完整起见,我在这里发布,因为这是我最终的结果。这是完美的,并且据我所知,除了Windows上的x64 ASM的UNWIND_INFO /异常处理要求,这几乎是正确的。这些评论也很有希望。

编辑:

现在,Raymonds在下面发表评论后会更新。我删除了rbp的保留,因为它不是必需的,并且使我的堆栈对齐比我想要的更远。

; Windows APIs

; GetStdHandle
; ------------
; HANDLE WINAPI GetStdHandle(
;     _In_ DWORD nStdHandle
; ); 
extern GetStdHandle

; WriteFile
; ------------
; BOOL WINAPI WriteFile(
;   _In_        HANDLE       hFile,
;   _In_        LPCVOID      lpBuffer,
;   _In_        DWORD        nNumberOfBytesToWrite,
;   _Out_opt_   LPDWORD      lpNumberOfBytesWritten,
;   _Inout_opt_ LPOVERLAPPED lpOverlapped
; );
extern WriteFile

; ExitProcess
; -----------
; VOID WINAPI ExitProcess(
;     _In_ UINT uExitCode
; );
extern ExitProcess

global start

section .data

    STD_OUTPUT_HANDLE   equ -11
    NULL                equ 0

    msg1                 db "Hello ", 0
    msg1.len             equ $-msg1

    msg2                 db "World!", 10, 0
    msg2.len             equ $-msg2

section .bss

empty               resd 1

section .text

start:

    sub rsp,0x28    ; Allocate 32 bytes of Shadow Space + align it to 16 bytes (8 byte return address already on stack, so 8 + 40 = 16*3)

    mov rcx,msg1
    mov rdx,msg1.len
    call write

    mov rcx,msg2
    mov rdx,msg2.len
    call write

    mov rcx,NULL
    call ExitProcess

    add rsp,0x28    ; Restore the stack pointer before exiting

    ret

write:

    ; Allocate another 40 bytes of stack space (the return address makes 48 total). Its 32
    ; bytes of Shadow Space for the WinAPI calls + 8 more bytes for the fifth argument
    ; to the WriteFile API call.
    sub rsp,0x28

    mov [rsp+0x30],rcx      ; Argument 1 is 48 bytes back in the stack (40 for Shadow Space above, 8 for return address)
    mov [rsp+0x38],rdx      ; Argument 2 is just after Argument 1

    mov rcx,STD_OUTPUT_HANDLE   ; Get handle to StdOut
    call GetStdHandle

    mov rcx,rax             ; hFile
    mov rdx,[rsp+0x30]      ; lpBuffer
    mov r8,[rsp+0x38]       ; nNumberOfBytesToWrite
    mov r9,empty            ; lpNumberOfBytesWritten

    ; Move the 5th argument directly behind the Shadow Space
   mov qword [rsp+0x20],0   ; lpOverlapped, Argument 5 (just after the Shadow Space 32 bytes back)
    call WriteFile

    add rsp,0x28        ; Restore the stack pointer (remove the Shadow Space)

    ret

导致......:

Finally working!