I am attempting to code a simple stack canary in 64bit assembly using NASM version 2.15.04 on Ubuntu 20.10. Executing the code below results in a segmentation fault when assembling and linking with the command 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
Debugging with GDB shows that the segmentation fault happens on line 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
However assembling and dynamically linking with libc via nasm -felf64 canary.asm && ld canary.o -lc -dynamic-linker /usr/lib64/ld-linux-x86-64.so.2
results in execution succeeding, no longer causing a segmentation fault.
Using Radare2 to compare the final binaries shows that both versions assembled the problem instruction identically as:
0x00401023 64488b042528. mov rax, qword fs:[0x28]
GDB in both cases also shows that the FS register is 0x0000 at execution time for that instruction.
So the instruction bytes and the FS register are identical whether or not the binary is linked with libc and the code has no use of external symbols from libc. Why is it that linking with libc causes execution to succeed while not linking libc causes a segmentation fault? Is it possible and/or how would I implement this without linking libc?
NOTE: The relevance or need of a stack canary in this example is not the focus of the question.
See Question&Answers more detail:
os