从这个帖子可以看出这FS:[0x28]
是一个堆栈金丝雀。我在这个函数上使用 GCC 生成相同的代码,
void foo () {
char a[500] = {};
printf("%s", a);
}
具体来说,我正在组装这个程序集..
0x000006b5 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=0x1978 ; '(' ; "x\x19"
0x000006be 488945f8 mov qword [local_8h], rax
...stuff...
0x00000700 488b45f8 mov rax, qword [local_8h]
0x00000704 644833042528. xor rax, qword fs:[0x28]
0x0000070d 7405 je 0x714
0x0000070f e85cfeffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
; CODE XREF from 0x0000070d (sym.foo)
0x00000714 c9 leave
0x00000715 c3 ret
设置 的值是什么fs:[0x28]
?内核,还是 GCC 抛出的代码?你能显示内核中的代码,或者编译成设置的二进制文件fs:[0x28]
吗?金丝雀是否在启动时或进程生成时重新生成?这是在哪里记录的?
答案1
跟踪此初始化很容易,因为(几乎)每个进程strace
在进程运行的一开始都显示一个非常可疑的系统调用:
arch_prctl(ARCH_SET_FS, 0x7fc189ed0740) = 0
就是这样man 2 arch_prctl
说的:
ARCH_SET_FS
Set the 64-bit base for the FS register to addr.
是的,看起来这就是我们所需要的。为了找到谁调用了arch_prctl
,让我们寻找回溯:
(gdb) catch syscall arch_prctl
Catchpoint 1 (syscall 'arch_prctl' [158])
(gdb) r
Starting program: <program path>
Catchpoint 1 (call to syscall arch_prctl), 0x00007ffff7dd9cad in init_tls () from /lib64/ld-linux-x86-64.so.2
(gdb) bt
#0 0x00007ffff7dd9cad in init_tls () from /lib64/ld-linux-x86-64.so.2
#1 0x00007ffff7ddd3e3 in dl_main () from /lib64/ld-linux-x86-64.so.2
#2 0x00007ffff7df04c0 in _dl_sysdep_start () from /lib64/ld-linux-x86-64.so.2
#3 0x00007ffff7dda028 in _dl_start () from /lib64/ld-linux-x86-64.so.2
#4 0x00007ffff7dd8fb8 in _start () from /lib64/ld-linux-x86-64.so.2
#5 0x0000000000000001 in ?? ()
#6 0x00007fffffffecef in ?? ()
#7 0x0000000000000000 in ?? ()
因此,FS 段基数是在程序加载期间由 设定的ld-linux
,它是 的一部分glibc
(如果程序是静态链接的,则此代码将嵌入到二进制文件中)。这就是一切发生的地方。
在启动过程中,加载程序初始化 TLS。这包括内存分配和设置 FS 基值以指向 TLS 开头。这是通过以下方式完成的arch_prctl
系统调用。 TLS 初始化后security_init
功能被调用,它生成堆栈保护的值并将其写入内存位置,该位置fs:[0x28]
指向:
并且是位于 TLS 开头的结构体中字段0x28
的偏移量。stack_guard
答案2
你所看到的(在 GCC 中)被称为堆栈粉碎保护器 (SSP),这是一种形式缓冲区溢出保护由编译器生成。该值是程序在启动时生成的随机数,正如维基百科文章提到的那样,放置在线程本地存储 (TLS)。其他编译器可能使用不同的策略来实现这种类型的保护。
为什么将值存储在 TLS 中?由于该值位于此处,因此 CS、DS 和 SS 寄存器无法访问其地址,因此如果您试图通过恶意代码更改堆栈,则很难猜测存储的值。