C ASLR随机化是否会因功能而异?
我有以下代码片段:C ASLR随机化是否会因功能而异?,c,assembly,x86,aslr,C,Assembly,X86,Aslr,我有以下代码片段: #include <inttypes.h> #include <stdio.h> uint64_t esp_func(void) { __asm__("movl %esp, %eax"); } int main() { uint32_t esp = 0; __asm__("\t movl %%esp,%0" : "=r"(esp)); printf("esp:
#include <inttypes.h>
#include <stdio.h>
uint64_t
esp_func(void)
{
__asm__("movl %esp, %eax");
}
int
main()
{
uint32_t esp = 0;
__asm__("\t movl %%esp,%0" : "=r"(esp));
printf("esp: 0x%08x\n", esp);
printf("esp: 0x%08lx\n", esp_func());
return 0;
}
esp_func
显示ASLR是活动的,具有28位的熵,这在我的现代Linux内核上是有意义的
没有意义的是第一个值:为什么它有很大的不同
我看了看装配,它看起来很奇怪
// From main
0x00001150 55 push rbp
0x00001151 4889e5 mov rbp, rsp
0x00001154 4883ec10 sub rsp, 0x10
0x00001158 c745fc000000. mov dword [rbp-0x4], 0
0x0000115f c745f8000000. mov dword [rbp-0x8], 0
0x00001166 89e0 mov eax, esp ; Move esp to eax
0x00001168 8945f8 mov dword [rbp-0x8], eax ; Assign eax to my variable `esp`
0x0000116b 8b75f8 mov esi, dword [rbp-0x8]
0x0000116e 488d3d8f0e00. lea rdi, [0x00002004]
0x00001175 b000 mov al, 0
0x00001177 e8b4feffff call sym.imp.printf ; For whatever reason, the value in [rbp-0x8]
; is assigned here. Why?
// From esp_func
0x00001140 55 push rbp
0x00001141 4889e5 mov rbp, rsp
0x00001144 89e0 mov eax, esp ; Move esp to eax (same instruction as above)
0x00001146 488b45f8 mov rax, qword [rbp-0x8] ; This changes everything. What is this?
0x0000114a 5d pop rbp
0x0000114b c3 ret
0x0000114c 0f1f4000 nop dword [rax]
所以我的问题是,
[rbp-0x8]
中有什么,它是如何达到的,以及为什么这两个值不同?不,堆栈ASLR在程序启动时发生一次。函数之间RSP的相对调整在编译时是固定的,只是为函数的局部变量留出空间的小常量。(C99可变长度数组和alloca对RSP进行运行时变量调整,但不是随机调整。)
您的程序包含未定义的行为,并且实际上没有打印RSP;取而代之的是前一个printf
调用在寄存器中留下的一些堆栈地址(它似乎是堆栈地址,因此其高位确实随ASLR而变化)。它没有告诉您函数之间堆栈指针的差异,只是告诉您如何不使用GNUCinlineASM
第一个值是正确打印当前ESP,但这只是64位RSP的低32位
从非
无效
函数的末尾掉落是不安全的,使用返回值是未定义的行为。任何使用返回值esp_func()
的调用程序都必然会触发UB,因此编译器可以自由地在RAX中保留它想要的任何内容
如果您想写入mov%rsp,%rax
/ret
,则在纯asm中写入该函数,或将mov写入一个“=r”(tmp)
局部变量。使用GNUCinlineASM修改RAX而不告诉编译器它不会改变任何东西;编译器仍然将其视为没有返回值的函数
MSVC内联asm不同:它显然支持使用\u asm{mov eax,123}
或其他东西,然后从非void函数的末尾掉下来,MSVC甚至在内联时也会将其作为函数返回值。GNU C内联asm不需要这样愚蠢的攻击:如果您希望asm与C值交互,请使用带有输出约束的扩展asm,就像您在main
中所做的那样。请记住,GNU C内联asm不是由编译器解析的,只是将模板字符串作为要组装的编译器asm输出的一部分发出
我不知道为什么clang会从堆栈中重新加载一个返回值,但这只是clang内部的一个工件,以及它是如何在禁用优化的情况下生成代码的。但由于未定义的行为,它被允许这样做。它是一个非void函数,因此需要有一个返回值。最简单的方法是只发出一个
ret
,这是一些编译器在启用优化的情况下所做的,但由于过程间优化,即使这样也不能解决问题
在C中,使用未返回值的函数的返回值实际上是未定义的行为。这适用于C级;就编译器而言,使用内联asm修改寄存器而不告诉编译器它不会改变任何事情。因此,您的程序作为一个整体包含UB,因为它将结果传递给printf
。这就是为什么编译器可以这样编译:您的代码已经被破坏了。实际上,它只是从堆栈内存中返回一些垃圾
TL:DR:这不是将mov%rsp,%rax
/ret
作为函数的asm定义发出的有效方法
(C++增强了它的UB,从一端开始,但是在C中只要调用方不使用返回值,它是合法的。如果您用C++编译了与优化相同的源代码,G+甚至在您的内联ASM模板之后甚至不会发出<代码> ReT < /Cult>指令。这可能是为了支持C的默认-<代码> int <代码>如果声明的函数没有返回类型,则返回类型。)
这也是为什么从注释中修改的版本(printf格式字符串已修复)、使用启用优化()编译的版本会打印出“令人惊讶的”不同的“RSP”值:第二个版本根本不使用RSP
#include <inttypes.h>
#include <stdio.h>
uint64_t __attribute__((noinline)) rsp_func(void)
{
__asm__("movq %rsp, %rax");
} // UB if return value used
int main()
{
uint64_t rsp = 0;
__asm__("\t movq %%rsp,%0" : "=r"(rsp));
printf("rsp: 0x%08lx\n", rsp);
printf("rsp: 0x%08lx\n", rsp_func()); // UB here
return 0;
}
GNU C基本asm(无约束)对任何东西都没有用处(除了\uuuu属性(裸)函数体)。
当UB在编译时可见时,不要假设编译器会执行您所期望的操作。(当UB在编译时不可见时,编译器必须生成适用于某些调用者或被调用者的代码,这样您就可以获得您所期望的asm。但是,编译时可见UB意味着所有赌注都没有了。)不,堆栈ASLR在程序启动时发生一次。函数之间RSP的相对调整在编译时是固定的,只是为函数的局部变量留出空间的小常量。(C99可变长度数组和
alloca
对RSP进行运行时变量调整,但不是随机的。)
您的程序包含未定义的行为,并且实际上没有打印RSP;相反,前一次printf
调用在寄存器中留下了一些堆栈地址(它似乎是堆栈地址,因此其高位确实随ASLR而变化)。它没有告诉您函数之间的堆栈指针差异,只是告诉您如何不使用GNU C内联asm
第一个值是正确打印当前ESP,但这只是64位RSP的低32位
从非
void
函数的末尾掉下来是不安全的,使用返回值是未定义的行为。任何使用esp_func()
返回值的调用程序都必然会触发UB,因此编译器可以在RAX中自由地保留它想要的任何内容
如果要编写mov%rsp,%rax
/ret
,请编写该函数
#include <inttypes.h>
#include <stdio.h>
uint64_t __attribute__((noinline)) rsp_func(void)
{
__asm__("movq %rsp, %rax");
} // UB if return value used
int main()
{
uint64_t rsp = 0;
__asm__("\t movq %%rsp,%0" : "=r"(rsp));
printf("rsp: 0x%08lx\n", rsp);
printf("rsp: 0x%08lx\n", rsp_func()); // UB here
return 0;
}
Compiler stderr
<source>:7:1: warning: non-void function does not return a value [-Wreturn-type]
}
^
1 warning generated.
Program returned: 0
Program stdout
rsp: 0x7fff5c472f30
rsp: 0x7f4b811b7170
# from the Godbolt link
rsp_func: # @rsp_func
mov rax, rsp
ret
main: # @main
push rax
mov rsi, rsp
mov edi, offset .L.str
xor eax, eax
call printf
call rsp_func # return value ignored because of UB.
mov edi, offset .L.str
xor eax, eax
call printf # printf("0x%08lx\n", garbage in RSI left from last printf)
xor eax, eax
pop rcx
ret
.L.str:
.asciz "rsp: 0x%08lx\n"