阴影空间示例 [英] Shadow space example

查看:20
本文介绍了阴影空间示例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我接受了下面的答案,并在代码的最终修订版中添加了我自己的答案.希望它向人们展示Shadow Space分配的实际例子,而不是更多的文字.

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

原文:

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

我的理解是这样的:

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

section .text开始:子 rsp,0x20 ;<---- 分配 32 字节的阴影空间"mov rcx,msg1mov rdx,msg1.len调用写添加 rsp,0x20mov rcx,NULL调用 ExitProcess退写:mov [rsp+0x08],rcx;<-- 使用阴影空间mov [rsp+0x10],rdx;<-- 又一次mov rcx,STD_OUTPUT_HANDLE ;获取 StdOut 的句柄调用 GetStdHandlemov rcx,rax ;控制台输出mov rdx,[rsp+0x08];缓冲区mov r8,[rsp+0x10];nNumberOfCharsToWritemov r9,空;lpNumberOfCharsWritten推空;lp保留调用 WriteConsoleA退

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

我做错了什么?在 WinAPI 调用之前,我尝试了多种大小的组合,还尝试了分配阴影空间"(我应该这样做吗?).

应该注意的是,当我根本不关心阴影空间时,这非常有效.但是,我试图符合 ABI,因为我的 write 函数调用了 WinAPI(因此不是叶函数).

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

您可以在 write 函数内为 WinAPI 函数(GetStdHandleWriteConsoleA)创建一个新的影子空间:

写:推RBPmov rbp, rspsub rsp, (16 + 32) ;WriteConsoleA 的第 5 个参数 (8) + 阴影空间 (32);再加上 8 使其成为 16 的倍数(在函数入口后一次推送对齐后保持堆栈对齐)mov [rbp+16],rcx ;<-- 使用我们的阴影空间,由`start`提供mov [rbp+24],rdx ;<-- 再次保存我们传入的参数mov rcx, -11 ;获取 StdOut 的句柄调用 GetStdHandlemov rcx,rax ;控制台输出mov rdx, [rbp+16] ;lp缓冲区;重新加载已保存的寄存器 arg 副本mov r8, [rbp+24] ;nNumberOfCharsToWritemov r9,空;lpNumberOfCharsWrittenmov qword [rsp+32],0;lpReserved - 直接在阴影空间后面的第 5 个参数调用 WriteConsoleA离开退

EDIT:

I have accepted an answer below and also added my own with my final revision of the code. Hopefully it shows people actual examples of Shadow Space allocation rather than more words.

EDIT 2: I also managed to find a link to a calling conventions PDF in the Annotations of a YouTube video (of all things) which has some interesting tidbits on Shadow Space and the Red Zone on Linux. It can be found here: http://www.agner.org/optimize/calling_conventions.pdf

ORIGINAL:

I have looked at a couple of other questions here and all over the internet but I can't seem to find a proper example of allocating "Shadow Space" when calling a subroutine/Windows API in 64 bit Windows assembly.

My understanding is this:

  • Caller should sub rsp,<bytes here> prior to call callee
  • Callee should use it to store registers if need be (or local variables, if register saving isn't required)
  • Caller cleans it up, e.g: add rsp,<bytes here>
  • The amount allocated should be aligned to 32 bytes

With that in mind, this is what I have tried:

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

My two strings are "Hello " and "World! ". This manages to print "Hello " before crashing. I have a suspicion that I am doing it correctly ... except I should be cleaning up somehow (and I'm not sure how).

What am I doing wrong? I have tried a combination of sizes and also tried "allocating Shadow Space" prior to the WinAPI calls too (am I supposed to be doing that?).

It should be noted that this works perfectly fine when I don't care about Shadow Space at all. However, I am trying to be compliant with the ABI since my write function calls WinAPIs (and is therefore, not a leaf function).

解决方案

The shadow space must be provided directly previous to the call. Imagine the shadow space as a relic from the old stdcall/cdecl convention: For WriteFile you needed five pushes. The shadow space stands for the last four pushes (the first four arguments). Now you need four registers, the shadow space (just the space, contents don't matter) and one value on the stack after the shadow space (which is in fact the first push). Currently the return address to the caller (start) is in the space that WriteFile will use as shadow space -> crash.

You can create a new shadow space for the WinAPI functions (GetStdHandle and WriteConsoleA) inside the function write:

write:
    push rbp
    mov rbp, rsp
    sub rsp, (16 + 32)      ; 5th argument of WriteConsoleA (8) + Shadow space (32)
                            ; plus another 8 to make it a multiple of 16 (to keep stack aligned after one push aligned it after function entry)

    mov [rbp+16],rcx        ; <-- use our Shadow space, provided by `start`
    mov [rbp+24],rdx        ; <-- and again, to save our incoming args

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

    mov rcx,rax             ; hConsoleOutput
    mov rdx, [rbp+16]       ; lpBuffer        ; reloaded saved copy of register arg
    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

这篇关于阴影空间示例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆