如何在没有链接 libc.so 的情况下访问段寄存器?
How to access segment register with out linking libc.so?
我正在尝试在 Ubuntu 20.10 上使用 NASM 版本 2.15.04 在 64 位程序集中编写一个简单的堆栈金丝雀。使用命令 nasm -felf64 canary.asm && ld canary.o
.
进行汇编和链接时,执行以下代码会导致分段错误
global _start
section .text
_start: endbr64
push rbp ; Save base pointer
mov rbp, rsp ; Set the stack pointer
call _func ; Call _func
mov rdi, rax ; Save return value of _func in RDI
mov rax, 0x3c ; Specify exit syscall
syscall ; Exit
_func: endbr64
push rbp ; Save the base pointer
mov rbp, rsp ; Set the stack pointer
sub rsp, 0x8 ; Adjust the stack pointer
mov rax, qword fs:[0x28] ; Get stack canary
mov qword [rbp - 0x8], rax ; Save stack canary on the stack
xor eax, eax ; Clear RAX
mov rax, 0x1 ; Specify write syscall
mov rdi, 0x1 ; Specify stdout
mov rsi, msg ; Char* buffer to print
mov rdx, 0xd ; Length of the buffer
syscall ; Write msg
mov rax, qword [rbp - 0x8] ; Retrieve the stack canary
xor rax, qword fs:[0x28] ; Compare to original value
je _return ; Jump to _return if canary matched original
xor eax, eax ; Clear RAX
mov rax, 0x1 ; Specify write syscall
mov rdi, 0x1 ; Specify stdout
mov rsi, stack_fail ; Char* buffer to print
mov rdx, 0x18 ; Length of the buffer
syscall ; Write stack_fail
mov rax, 0x3c ; Specify exit syscall
mov rax, 0x1 ; Specify error code 1
syscall ; Exit
_return: xor eax, eax ; Set return value to 0
add rsp, 0x8 ; Reset stack pointer
pop rbp ; Get original base pointer
ret ; Return
section .data
msg: db "Hello, World", 0xa, 0x0
stack_fail db "Stack smashing detected", 0xa, 0x0
使用 GDB 调试显示第 16 行发生段错误:mov rax, qword fs:[0x28]
.
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x40101b <_func+4> push rbp
0x40101c <_func+5> mov rbp, rsp
0x40101f <_func+8> sub rsp, 0x8
→ 0x401023 <_func+12> mov rax, QWORD PTR fs:0x28
0x40102c <_func+21> mov QWORD PTR [rbp-0x8], rax
0x401030 <_func+25> xor eax, eax
0x401032 <_func+27> mov eax, 0x1
0x401037 <_func+32> mov edi, 0x1
0x40103c <_func+37> movabs rsi, 0x402000
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "a.out", stopped 0x401023 in _func (), reason: SIGSEGV
然而,通过 nasm -felf64 canary.asm && ld canary.o -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2
与 libc 进行汇编和动态链接会导致执行成功,不再导致分段错误。
使用 Radare2 比较最终的二进制文件显示两个版本将问题指令组装为相同的:
0x00401023 64488b042528. mov rax, qword fs:[0x28]
两种情况下的 GDB 还显示 FS 寄存器在执行该指令时为 0x0000。
因此无论二进制文件是否与 libc 链接并且代码没有使用 libc 的外部符号,指令字节和 FS 寄存器都是相同的。为什么链接libc会导致执行成功,而不链接libc会导致段错误?有没有可能 and/or 我如何在不链接 libc 的情况下实现它?
注意:本例中stack canary的相关性或需求不是问题的重点。
访问段寄存器没有问题,只是mov eax, fs
。但是你想要做的是在 FS 段 base 的一个小偏移处访问线程本地存储,其中 libc init 东西将要求内核设置。
最简单的方法是使用普通的 RIP 相对寻址模式访问您的堆栈金丝雀,而不是相对于 FS 基础,就像 GCC 在针对其他 ISA 时所做的那样。只有当你想让其他一些漏洞利用更难到达金丝雀(并且它的地址可以单独随机化)时,你才需要 TLS。 (或者库代码可以访问它而无需从 GOT 加载指针的间接访问,而不是仅对主要可执行文件中的代码有效。)
如果您想复制 GCC 的 stack-canary 代码,您当然可以进行与 libc 相同的系统调用来设置线程本地存储并使用它。
有趣的事实:sub rax, qword fs:[0x28]
是一种比 XOR 更有效的检查金丝雀的方法——它可以与 JCC 宏融合成一个 uop。这就是当前 GCC 改为使用 sub
的原因。 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90568 - 在 GCC10+ 中修复。
我的 GCC 错误报告实际上包括独立的微基准测试代码(以证明 sub
即使在 FS: 寻址模式下也可以进行宏融合)。
如果静态可执行文件中没有 libc,它会设置 FS 段,因此其基地址是缓冲区的地址,因此 [fs: 0x28]
将起作用。这是 TLS 的基本形式。
global _start
_start:
cookie equ 12345
mov eax, 158 ; __NR_arch_prctl
mov edi, 0x1002 ; ARCH_SET_FS
lea rsi, [buf]
syscall
mov qword [fs: 0x28], cookie
...
section .bss
buf: resb 4096 ; fs.base will point at this buffer
如果内核启用 wrfsbase
供用户 space 使用,您可以使用 wrfsbase rsi
而不是进行系统调用。我认为最新的 Linux 内核 (5.10) 可能已经开始使用 wrfsbase
本身,但我不知道它是否允许用户 space 使用它。
(它可能不会在每次使用时切换 FSGSBASE on/off,因此内核使用意味着用户-space 可以使用它;故障条件 in the manual 不不提特权级,只提CPUID特性位和CR4控制寄存器中的位。而且只在64位模式下;在其他模式包括兼容模式下会#UD。)
我正在尝试在 Ubuntu 20.10 上使用 NASM 版本 2.15.04 在 64 位程序集中编写一个简单的堆栈金丝雀。使用命令 nasm -felf64 canary.asm && ld canary.o
.
global _start
section .text
_start: endbr64
push rbp ; Save base pointer
mov rbp, rsp ; Set the stack pointer
call _func ; Call _func
mov rdi, rax ; Save return value of _func in RDI
mov rax, 0x3c ; Specify exit syscall
syscall ; Exit
_func: endbr64
push rbp ; Save the base pointer
mov rbp, rsp ; Set the stack pointer
sub rsp, 0x8 ; Adjust the stack pointer
mov rax, qword fs:[0x28] ; Get stack canary
mov qword [rbp - 0x8], rax ; Save stack canary on the stack
xor eax, eax ; Clear RAX
mov rax, 0x1 ; Specify write syscall
mov rdi, 0x1 ; Specify stdout
mov rsi, msg ; Char* buffer to print
mov rdx, 0xd ; Length of the buffer
syscall ; Write msg
mov rax, qword [rbp - 0x8] ; Retrieve the stack canary
xor rax, qword fs:[0x28] ; Compare to original value
je _return ; Jump to _return if canary matched original
xor eax, eax ; Clear RAX
mov rax, 0x1 ; Specify write syscall
mov rdi, 0x1 ; Specify stdout
mov rsi, stack_fail ; Char* buffer to print
mov rdx, 0x18 ; Length of the buffer
syscall ; Write stack_fail
mov rax, 0x3c ; Specify exit syscall
mov rax, 0x1 ; Specify error code 1
syscall ; Exit
_return: xor eax, eax ; Set return value to 0
add rsp, 0x8 ; Reset stack pointer
pop rbp ; Get original base pointer
ret ; Return
section .data
msg: db "Hello, World", 0xa, 0x0
stack_fail db "Stack smashing detected", 0xa, 0x0
使用 GDB 调试显示第 16 行发生段错误:mov rax, qword fs:[0x28]
.
─────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x40101b <_func+4> push rbp
0x40101c <_func+5> mov rbp, rsp
0x40101f <_func+8> sub rsp, 0x8
→ 0x401023 <_func+12> mov rax, QWORD PTR fs:0x28
0x40102c <_func+21> mov QWORD PTR [rbp-0x8], rax
0x401030 <_func+25> xor eax, eax
0x401032 <_func+27> mov eax, 0x1
0x401037 <_func+32> mov edi, 0x1
0x40103c <_func+37> movabs rsi, 0x402000
─────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "a.out", stopped 0x401023 in _func (), reason: SIGSEGV
然而,通过 nasm -felf64 canary.asm && ld canary.o -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2
与 libc 进行汇编和动态链接会导致执行成功,不再导致分段错误。
使用 Radare2 比较最终的二进制文件显示两个版本将问题指令组装为相同的:
0x00401023 64488b042528. mov rax, qword fs:[0x28]
两种情况下的 GDB 还显示 FS 寄存器在执行该指令时为 0x0000。
因此无论二进制文件是否与 libc 链接并且代码没有使用 libc 的外部符号,指令字节和 FS 寄存器都是相同的。为什么链接libc会导致执行成功,而不链接libc会导致段错误?有没有可能 and/or 我如何在不链接 libc 的情况下实现它?
注意:本例中stack canary的相关性或需求不是问题的重点。
访问段寄存器没有问题,只是mov eax, fs
。但是你想要做的是在 FS 段 base 的一个小偏移处访问线程本地存储,其中 libc init 东西将要求内核设置。
最简单的方法是使用普通的 RIP 相对寻址模式访问您的堆栈金丝雀,而不是相对于 FS 基础,就像 GCC 在针对其他 ISA 时所做的那样。只有当你想让其他一些漏洞利用更难到达金丝雀(并且它的地址可以单独随机化)时,你才需要 TLS。 (或者库代码可以访问它而无需从 GOT 加载指针的间接访问,而不是仅对主要可执行文件中的代码有效。)
如果您想复制 GCC 的 stack-canary 代码,您当然可以进行与 libc 相同的系统调用来设置线程本地存储并使用它。
有趣的事实:sub rax, qword fs:[0x28]
是一种比 XOR 更有效的检查金丝雀的方法——它可以与 JCC 宏融合成一个 uop。这就是当前 GCC 改为使用 sub
的原因。 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90568 - 在 GCC10+ 中修复。
我的 GCC 错误报告实际上包括独立的微基准测试代码(以证明 sub
即使在 FS: 寻址模式下也可以进行宏融合)。
如果静态可执行文件中没有 libc,它会设置 FS 段,因此其基地址是缓冲区的地址,因此 [fs: 0x28]
将起作用。这是 TLS 的基本形式。
global _start
_start:
cookie equ 12345
mov eax, 158 ; __NR_arch_prctl
mov edi, 0x1002 ; ARCH_SET_FS
lea rsi, [buf]
syscall
mov qword [fs: 0x28], cookie
...
section .bss
buf: resb 4096 ; fs.base will point at this buffer
如果内核启用 wrfsbase
供用户 space 使用,您可以使用 wrfsbase rsi
而不是进行系统调用。我认为最新的 Linux 内核 (5.10) 可能已经开始使用 wrfsbase
本身,但我不知道它是否允许用户 space 使用它。
(它可能不会在每次使用时切换 FSGSBASE on/off,因此内核使用意味着用户-space 可以使用它;故障条件 in the manual 不不提特权级,只提CPUID特性位和CR4控制寄存器中的位。而且只在64位模式下;在其他模式包括兼容模式下会#UD。)