将局部变量与16字节边界(x86 asm)对齐

问题描述 投票:0回答:1

我在分配128位变量,使其在16字节边界(在堆栈上,而不是堆上)上对齐时遇到问题。当我调用函数时,我无法控制堆栈是否对齐,因此我假设它没有对齐。

这里是我的函数(简化):

; start of stackframe
push ebp
mov ebp, esp

; space for our variable
sub esp, 0x10

; the 128-bit variable would be at [ebp - 0x10]
...

; end of stackframe
mov esp, ebp
pop ebp

现在,我可以通过在and esp, 0xFFFF'FFF0之前插入sub esp, 16来使变量对齐,但是由于[ebp - 0x10]将引用旧的,未对齐的堆栈指针,因此我将不再能够用ebp引用它。

考虑到这一点,我认为我需要在mov ebp, esp指令之前对齐堆栈,以便能够手动对齐变量。因此,在此示例中:

; align esp
and esp, 0xFFFF'FFF0

; start of stackframe
push ebp
mov ebp, esp

; padding (because of the push ebp)
sub esp, 0xC

; space for our variable
sub esp, 0x10

; the 128-bit variable would be at [ebp - 0x10]
...

; end of stackframe
mov esp, ebp
pop ebp

问题是,我们不会在堆栈框架的末尾正确清理堆栈(不确定)。这是因为我们在对齐堆栈后执行mov ebp, esp

我真的想不出一种好方法。由于sse对齐要求,我觉得这应该是一个常见的问题,但是我找不到关于该主题的太多信息。还请记住,在调用函数之前我无法控制堆栈,因为这是shellcode。

编辑:我想一种解决方案是将我的堆栈框架包装在另一个堆栈框架中。所以像这样:

push ebp
mov ebp, esp

; align the stack
and esp, 0xFFFF'FFF0

; the "real" stackframe start
push ebp
mov ebp, esp

; padding due to the push ebp prior to this
sub esp, 0xC

; space for our variable
sub esp, 0x10

; our variable is now at [ebp - 0x1C] (i think)
...

; the "real" stackframe end
mov esp, ebp
pop ebp

mov esp, ebp
pop ebp
assembly x86 memory-alignment callstack
1个回答
1
投票

对齐堆栈后,请相对于ESP引用本地。

   push  ebp
   mov   ebp, esp     ; or any register, doesn't really matter

   and  esp, -16      ; round ESP down to a multiple of 16, reserving 0 to 12 bytes
   sub  esp, 32       ; reserve 32 bytes we know are there for sure.

   mov  dword [esp+4], 1234  ; store a local

   xorps  xmm0,xmm0
   movaps [esp+16], xmm0     ; zero 16 bytes of space with an aligned store

   leave            ; mov esp, ebp ; pop ebp
   ret

如果在函数调用之前推送args,请记住这会暂时更改ESP。您可以通过在初始sub中保留足够的空间来简化操作,并像[GCC使用mov

那样简单地将args存储在-faccumulate-outgoing-args中。

如果需要访问堆栈上的传入函数args,则仍可以相对于EBP访问them。

根据您仍然需要访问什么和不需要什么,有很多解决此问题的方法。例如对齐堆栈后,您可以将指针存放在内存中预先对齐的值的某个位置,以释放所有其他7个寄存器。 (在那种情况下,您可以将所有堆栈args装入寄存器before中,以对齐堆栈,因此无需将指针保持在堆栈框架的顶部。)


查看本地输出的clang或GCC8及更高版本,例如,使用alignas(32)编译C或C ++的本地代码时在https://godbolt.org/上。那些编译器(与-O2一起使用)执行了我建议的操作,并在对齐堆栈后引用了相对于ESP的局部变量。

[标准32位Linux调用约定在call推送返回地址之前将ESP对齐16,因此简单的sub始终可以到达已知的alignas(16)边界。根据如何到达Shellcode,即使利用确实具有此保证的代码,您也可能无法利用它。例如如果这是缓冲区溢出的经典代码注入利用,则容易受到攻击的函数末尾的ret将恢复16字节的堆栈对齐,只需使用直接指向代码的指针覆盖返回地址即可。不是ROP攻击的返回地址链。

无论如何,如果要查看编译器如何处理它,则应使用较高的alignas。除MSVC以外,Godbolt上的编译器已安装为目标Linux。许多其他32位ABI仅保证4字节堆栈对齐。


在shellcode中,仅使用movups加载和存储而不用担心堆栈对齐可能更有意义。即使这意味着除非使用AVX版本,否则不能使用内存源操作数。例如如果ESP未与16对齐,则paddd xmm0, [esp+16]可能会出错,但movups xmm1, [esp+16]不能。而且vpaddd xmm0, xmm0, [esp+16]

您必须确定单独的加载指令是否比序言花费了更多的有效负载大小。

此外,[ESP]寻址模式始终需要一个SIB字节,从而使您多花了代码大小的1个字节。所以这是一个缺点。为了节省性能,通常值得使用uops,但对于代码大小,使用push reg / mov reg, esp的3字节设置序列可能值得。


[如果您不需要返回,只需按and esp, -16并忘记它!

例如在您的shellcode的最顶部进行此操作,以进行所需的对齐,然后将其用于有效负载内的所有调用/ rets。漏洞利用的切入点不会是ret(对吗?),而且您通常不在乎其上方的堆栈,因此您无需保留旧值。
© www.soinside.com 2019 - 2024. All rights reserved.