Some months ago I was trying to build a very simple JIT compiler in Zig. I eventually gave it up since building a decent one is quite a challenging task, even though I was glad that I learned a bit more about how a JIT really works.

Today I found out this little piece of code for executing a shellcode in Zig: I remember I had to ask how to do it since I couldn't figure it out from the stdlib documentation, so I will share it here in the hope it is useful to others.

// shellcode.zig
const std = @import("std");
const shellcode: []const u8 = &.{
    0x6a, 0x42, 0x58, 0xfe, 0xc4, 0x48, 0x99, 0x52, 0x48, 0xbf,
    0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x57, 0x54,
    0x5e, 0x49, 0x89, 0xd0, 0x49, 0x89, 0xd2, 0x0f, 0x05,
};

pub fn main() !void {
    const aligned_length = std.mem.alignForward(shellcode.len, std.mem.page_size);
    const pcode = try std.heap.page_allocator.allocAdvanced(u8, std.mem.page_size, aligned_length, .exact);
    std.mem.copy(u8, pcode, shellcode);
    try std.os.mprotect(pcode, 7); // 7 means READ + WRITE + EXEC                                                                                                                                                                            
    const ptr = @ptrCast(fn () void, pcode.ptr);
    ptr();
}

You can just try it out by executing

zig run shellcode.zig

Just remember that the shellcode is for linux only (it should be execve /bin/sh).