Как получить доступ к регистру сегментов без ссылки на libc.so?

Я пытаюсь закодировать простую канарейку стека в 64-битной сборке, используя NASM версии 2.15.04 в Ubuntu 20.10. Выполнение приведенного ниже кода приводит к ошибке сегментации при сборке и связывании с помощью команды 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

Однако сборка и динамическое связывание с libc через nasm -felf64 canary.asm && ld canary.o -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2 приводит к успешному выполнению, больше не вызывая ошибки сегментации.

Использование Radare2 для сравнения окончательных двоичных файлов показывает, что обе версии собрали проблемную инструкцию одинаково:

0x00401023 64488b042528. mov rax, qword fs:[0x28]

GDB в обоих случаях также показывает, что регистр FS равен 0x0000 во время выполнения этой инструкции.

Таким образом, байты инструкций и регистр FS идентичны независимо от того, связан ли двоичный файл с libc или нет, и код не использует внешние символы из libc. Почему связывание с libc приводит к успешному выполнению, а отсутствие связывания с libc вызывает ошибку сегментации? Возможно ли это и/или как мне реализовать это без привязки libc?

ПРИМЕЧАНИЕ. Уместность или потребность в канареечном стеке в этом примере не является предметом вопроса.


person Kuma    schedule 27.12.2020    source источник


Ответы (1)


Доступ к сегментному регистру не проблема, просто mov eax, fs. Но то, что вы пытаетесь сделать, это получить доступ к локальному хранилищу потока с небольшим смещением от сегмента FS base, который материал инициализации libc попросит ядро ​​​​настроить.

Проще всего было бы просто получить доступ к вашему канареечному стеку с обычным режимом адресации относительно RIP, а не относительно базы FS, как это делает GCC при нацеливании на другие ISA. TLS нужен только в том случае, если вы хотите затруднить доступ к канарейке для какого-либо другого эксплойта (и чтобы его адрес можно было рандомизировать отдельно).

Конечно, вы можете сделать те же системные вызовы, что и libc, чтобы настроить локальное хранилище потока и использовать его, если вы хотите скопировать стек-канареечный код GCC.


Забавный факт: sub rax, qword fs:[0x28] — более эффективный способ проверки канарейки, чем XOR — он может макросливать с JCC в единую uop. Вот почему текущий GCC изменился на использование sub. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90568 — исправлено в GCC10+.

Мой отчет об ошибках GCC на самом деле включал автономный код микробенчмарка (чтобы доказать, что sub может совмещать макросы даже с режимом адресации FS:) для настройки TLS без libc, поэтому [fs: 0x28] будет работать в статическом исполняемом файле, что то же самое, что вы хотите :

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, но я не знаю, позволяет ли оно использовать его в пользовательском пространстве.

person Peter Cordes    schedule 27.12.2020