【问题标题】:How to access segment register with out linking libc.so?如何在不链接 libc.so 的情况下访问段寄存器?
【发布时间】:2021-12-05 01:12:03
【问题描述】:

我正在尝试在 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 &amp;&amp; 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 会导致分段错误?是否有可能和/或如何在不链接 libc 的情况下实现它?

注意:此示例中堆栈金丝雀的相关性或需求不是问题的重点。

【问题讨论】:

    标签: segmentation-fault x86-64 nasm thread-local-storage memory-segmentation


    【解决方案1】:

    访问段寄存器没问题,只需mov eax, fs。但是您尝试做的是在距 FS 段 base 的一个小偏移量处访问线程本地存储,libc init 内容将要求内核进行设置。

    最简单的方法是使用正常的 RIP 相对寻址模式访问您的堆栈金丝雀,而不是相对于 FS 基础,就像 GCC 在针对其他 ISA 时所做的那样。只有当你想让其他一些漏洞更难到达金丝雀(并且它的地址可以单独随机化)时,你才需要 TLS。 (或者这样库代码可以访问它,而无需从 GOT 间接加载指针,而不是仅对主可执行文件中的代码有效。)

    如果您想复制 GCC 的堆栈金丝雀代码,您当然可以进行与 libc 相同的系统调用来设置线程本地存储并使用它。


    有趣的事实:sub rax, qword fs:[0x28] 是一种比 XOR 更有效的检查金丝雀的方法 - 它可以将 JCC 宏融合到单个微指令中。这就是为什么当前的 GCC 改为使用 subhttps://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 供用户空间使用,您可以使用wrfsbase rsi 而不是进行系统调用。我认为最新的 Linux 内核(5.10)可能已经开始使用 wrfsbase 本身,但我不知道它是否允许用户空间使用它。

    (它可能不会在每次使用它时打开/关闭 FSGSBASE,因此内核使用意味着用户空间可以使用它;故障条件in the manual 没有提到特权级别,只有 CPUID 功能位和CR4控制寄存器中的一个位。并且仅在64位模式下;在包括兼容模式在内的其他模式下会#UD。)

    【讨论】:

    • Linux 内核在 5.9 版中启用了 FSGSBASE 的用户空间使用。
    猜你喜欢
    • 2012-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-29
    • 1970-01-01
    • 2010-09-27
    相关资源
    最近更新 更多