flak rss random

dude, where are your syscalls?

The OpenBSD kernel is getting to be really old, like really, really old, mid 40s old, and consequently it doesn’t like surprises, so programs have to tell it where their syscalls are. In today’s edition of the polite programmer, we’ll learn the proper etiquette for doing so.

If you program in C, this is all handled automatically for you, so we’ll be exclusively working in totally not C and avoiding linking with libc.

start

We’ll start with a simple hello program.

void
start()
{
        w("hello\n", 6);
        x();
}

typedef unsigned long size_t;
int
w(void *what, size_t len) {
        __asm(
"       mov x2, x1;"
"       mov x1, x0;"
"       mov w0, #1;"
"       mov x8, #4;"
"       svc #0;"
        );
        return 0;
}
void
x() {
        __asm(
"       mov x8, #1;"
"       svc #0;"
        );
}

In totally not C, we start execution in the start function and print messages with the w function (source included) and then finally exit by calling x because very bad shit will happen if we fall off the bottom of start. This isn’t C!

voodoo

We also need this tiny bit of voodoo to tell the kernel that this is a real program and not some wintermute fever dream.

__asm(" .section \".note.openbsd.ident\", \"a\"\n"
"       .p2align 2\n"
"       .long   8\n"
"       .long   4\n"
"       .long   1\n"
"       .ascii \"OpenBSD\\0\"\n"
"       .long   0\n"
"       .previous\n");

syscalls

Okay, finally for the fun bits. We inform the kernel about our syscall usage with a little table of whats happening. Stringent as the rule may be, compliance is a short bit of work.

struct whats {
        unsigned int offset;
        unsigned int sysno;
} happening[] __attribute__((section(".openbsd.syscalls"))) = {
        { 0x104f4, 4 },
        { 0x10530, 1 },
};

As you will recall from your operating systems lecture, write is syscall #4 and exit is syscall #1. As for the hex offsets, well, this is not the totally not C tutorial, but it’s not terribly difficult to work out. Consult the manual.

Honestly, I’m not sure how to do better for a demo like this, other than compiling once and running objdump. Function pointers aren’t compile time constants, and they wouldn’t give you the elf section offset without further illegal gymnastics. But it’s also the thing that your toolchain can work out pretty easily; it’s only trying to smash it into a one shot demo that adds difficulty. libc uses some more asm section voodoo, but it’s too opaque to fully replicate here.

compilation

All set.

$ cc -c where.c
$ ld -e start --eh-frame-hdr -Bstatic -o where where.o
$ ./where
hello

Hello! Our program and our syscalls, all working in concert.

Posted 05 Mar 2025 09:35 by tedu Updated: 05 Mar 2025 09:35
Tagged: openbsd programming