Recently I decided to study for the SLAE64 course from Pentester Academy to work on my assembly knowledge, specifically on x86_64. Through the course does focus on Linux I want to apply the knowledge to OpenBSD/amd64 too and thus I installed NASM and looked at what I needed to adjust on my Linux samples to get it working on OpenBSD. Turns out, not that much actually!

Both operating systems use same calling convention, namely the System V AMD64 ABI. Wikipedia has a fairly good article on it, but it basically means that most UNIX-like operating systems use the same method for passing arguments to function and where they expect return values to be stored. For the SLAE64 course the most relevant are the registers in which to pass arguments, which are in order: RDI, RSI, RDX, RCX, R8, R9. Return values up to 64 bits are stored in RAX.

The key difference between OpenBSD and Linux when coding the assembly are the syscalls and their numbers.

So let’s get started and start even simpler than “Hello world”. Let’s just invoke a simple syscall. When it comes to syscalls there’s they don’t come much simpler than exit. Simply put the syscall number in RAX, the return code in the RDI register and make the call.

global _start

section .text

_start:
    mov rax, 0x1
    mov rdi, 0x2a
    syscall

Assemble and link: nasm -f elf64 exit.nasm -o exit.o && ld exit exit.o; that worked, so let’s run it:

$ ./exit
zsh: exec format error: ./exit
$

Turns out we need an extra ELF NOTE section for OpenBSD called .note.openbsd.ident which which we can define with:

%ifdef OpenBSD
section .note.openbsd.ident
        align   2
        dd      8,4,1
        db      "OpenBSD",0
        dd      0
        align   2
%endif

and then assemble with -D OpenBSD. I have taken this defintion from src/lib/csu/os-note-elf.h.

However this still resulted in the exec format error…on an OpenBSD discussion channel it was hinted that I might need -nopie for my static binary; nope still not.

Then I tried it with the GNU linker (ld.bfd) rather than the default linker (ld.lld really) and that worked!

$ nasm -D OpenBSD -f elf64 exit.nasm -o exit.o && \
    ld.bfd -static -e _start exit.o -o exit && \
    ./exit ; echo $?
42
$

But surely lld cannot be broken? I tried the equivalent of the above code with the GNU assembler to determine if the issue is the linker or the assembler:

.section ".note.openbsd.ident", "a"
  .align 2
  .long 8
  .long 4
  .long 1
  .asciz "OpenBSD"
  .long 0
  .align 2

.section .text

.global _start

_start:
  mov $0x1,  %rax
  mov $0x2a, %rdi
  syscall

Assemble and link with: as -o exit_gas.o exit_gas.s && ld -o exit_gas exit_gas.o (and also -static) and that works fine.

It seems there’s something present (or absent) in the object file emitted by NASM that lld isn’t capable of handling.

For now I’ll be able to use NASM with ld.bfd to write my OpenBSD-equivalent code for SLAE64, though I’d like to figure out at some point what’s going wrong with lld, or what I’m missing here.

Update (May 2020)

After digging into this issue again it was pointed out to me by Mark Kettenis that nasm creates an SHT_PROGBITS section for .note.openbsd.ident instead of an SHT_NOTE as evidenced by readelf -S exit_nasm.o:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  000001c0
       000000000000000c  0000000000000000  AX       0     0     16
  [ 2] .note.openbsd.ide NOTE             0000000000000000  000001d0
       0000000000000018  0000000000000000   A       0     0     2
  [ 3] .shstrtab         STRTAB           0000000000000000  000001f0
       0000000000000035  0000000000000000           0     0     1
  [ 4] .symtab           SYMTAB           0000000000000000  00000230
       0000000000000078  0000000000000018           5     4     8
  [ 5] .strtab           STRTAB           0000000000000000  000002b0
       0000000000000012  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

The .section directive in Nasm 2.14 does support passing the type of section as long as its progbits or nobits. With this these commits you can correctly mark the section as a note and link with lld.

Thus with a newer nasm the code becomes:

section .note.openbsd.ident note
        align   2
        dd      8,4,1
        db      "OpenBSD",0
        dd      0
        align   2

resulting in :

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  000001c0
       000000000000000c  0000000000000000  AX       0     0     16
  [ 2] .note.openbsd.ide NOTE             0000000000000000  000001d0
       0000000000000018  0000000000000000   A       0     0     2
  [ 3] .shstrtab         STRTAB           0000000000000000  000001f0
       0000000000000035  0000000000000000           0     0     1
  [ 4] .symtab           SYMTAB           0000000000000000  00000230
       0000000000000078  0000000000000018           5     4     8
  [ 5] .strtab           STRTAB           0000000000000000  000002b0
       0000000000000012  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification. Student ID: SLAE64-1614