zebian.log

技術系備忘録とか

eBPFに入門して簡単なシステムコールロガーを実装した

セキュキャン2023でSysmonForLinuxを使った経験があり、プログラムの挙動ログを自作ロガーで取りたいなと思ったので、Go+ebpf-goで簡単なシステムコールロガーを実装した。eBPFもGoも初心者なのでコードが汚いのは御愛嬌。

コード全体

コードはここに書いた。記事作成時点のコードであって、最新版ではないので注意。

main.go

package main

import (
    "bytes"
    _ "embed"
    "encoding/binary"
    "encoding/json"
    "fmt"
    "os"
    "os/signal"
    "syscall"

    "github.com/cilium/ebpf"
    "github.com/cilium/ebpf/link"
    "github.com/cilium/ebpf/ringbuf"
    "github.com/cilium/ebpf/rlimit"
)

//go:embed bpf_hook_syscall.o
var bpfBin []byte

type BpfObject struct {
    Events         *ebpf.Map     `ebpf:"events"`
    HookX64SysCall *ebpf.Program `ebpf:"hook_x64_sys_call"`
}

type SyscallEvent struct {
    Timestamp uint64
    SyscallNr uint32
    Pid       uint32
}

func (o *BpfObject) Close() error {
    if err := o.Events.Close(); err != nil {
        return err
    }

    if err := o.HookX64SysCall.Close(); err != nil {
        return err
    }

    return nil
}

func main() {
    spec, err := ebpf.LoadCollectionSpecFromReader(bytes.NewReader(bpfBin))
    if err != nil {
        panic(err)
    }

    if err := rlimit.RemoveMemlock(); err != nil {
        panic(err)
    }

    var o BpfObject
    if err := spec.LoadAndAssign(&o, nil); err != nil {
        panic(err)
    }
    defer o.Close()

    link, err := link.AttachTracing(link.TracingOptions{
        Program: o.HookX64SysCall,
    })
    if err != nil {
        panic(err)
    }
    defer link.Close()

    rd, err := ringbuf.NewReader(o.Events)
    if err != nil {
        panic(err)
    }

    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

    var event SyscallEvent
    var events []SyscallEvent

l:
    for {
        select {
        case <-sigCh:
            // received signal
            break l
        default:
        }

        // read record
        record, err := rd.Read()
        if err != nil {
            if err == ringbuf.ErrClosed {
                panic(err)
            }
            continue
        }

        // parse record
        if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.NativeEndian, &event); err != nil {
            fmt.Printf("Failed to parse syscall event: %s\n", err)
            continue
        }

        events = append(events, event)
    }

    // export json log
    file, err := os.Create("syscall_events.json")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    encoder := json.NewEncoder(file)

    if err := encoder.Encode(events); err != nil {
        panic(err)
    }

    fmt.Printf("Exported syscall events log\n")
}

bpf_hook_syscall.c

// +build ignore

#define __TARGET_ARCH_x86

#include <linux/bpf.h>
#include <linux/version.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

char _license[] SEC("license") = "Dual MIT/GPL";

struct syscall_event
{
    __u64 timestamp;
    __u32 syscall_nr;
    __u32 pid;
};

struct
{
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 1 << 24);
} events SEC(".maps");

// linux/arch/x86/entry/syscall_64.c
// long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
SEC("fentry/x64_sys_call")
int BPF_PROG(hook_x64_sys_call, const struct pt_regs *regs, unsigned int nr)
{
    struct syscall_event *event;
    event = bpf_ringbuf_reserve(&events, sizeof(struct syscall_event), 0);

    // failed to reserve space in ringbuf
    if (!event)
    {
        return 0;
    }

    event->timestamp = bpf_ktime_get_ns();
    event->syscall_nr = nr;
    event->pid = bpf_get_current_pid_tgid() >> 32;

    bpf_ringbuf_submit(event, 0);

    return 0;
}

ビルド

clang -O2 -g -c -target bpf bpf_hook_syscall.c
go build

システムコールをフックする方法

eBPFではLinuxカーネルの特定の関数の実行をフックすることができる。調べた限りtracepointkprobefentryの3種類のやり方がある。カーネルがこれらの動作をサポートしている必要があるが、自分の環境ではサポートされているにもかかわらずtracepointとkprobeが動かなかった(もしかしたら実装が悪かっただけかも)。fentryはLinux5.5以降で使える。

フック関数

// linux/arch/x86/entry/syscall_64.c
// long x64_sys_call(const struct pt_regs *regs, unsigned int nr)
SEC("fentry/x64_sys_call")
int BPF_PROG(hook_x64_sys_call, const struct pt_regs *regs, unsigned int nr)
{
    ...
    return 0;
}

SEC("fentry/<関数名>")のようにフックしたい関数を登録する。

Linuxシステムコール関数はinclude/linux/syscalls.hで見ることができるが、sys_*関数を登録して実行してみたところ、関数が見つからないと言われてしまった(理由は謎。関数の実装がCではなくアセンブリだったから?)。

仕方がないので呼び出し元であるarch/x86/entry/syscall_64.cx64_sys_call関数をフックすることにした。

フック関数はマクロを使ってint BPF_PROG(<フック関数名>, <フックしたい関数の引数>, ...)のように実装する。Cのマクロの混沌を実感した。

イベントを記録する

struct syscall_event
{
    __u64 timestamp;
    __u32 syscall_nr;
    __u32 pid;
};

イベントログ用の構造体。上からタイムスタンプ、システムコール番号(x64_sys_call関数の第2引数をそのまま持ってきた)、システムコールを実行したプロセスのpid。

これらのイベントログをeBPFプログラムを呼び出したGo側に送りたい。BPFにはMapsという機能があり、そこでデータを呼び出し元とやり取りすることができる。ハッシュテーブルやリングバッファなど、データ構造も選択可能。今回はリングバッファを利用した。定義は次の通り。

struct
{
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 1 << 24);
} events SEC(".maps");

フック関数内では、次のようにイベントの記録を行う。

... {
    struct syscall_event *event;
    event = bpf_ringbuf_reserve(&events, sizeof(struct syscall_event), 0);

    // failed to reserve space in ringbuf
    if (!event)
    {
        return 0;
    }

    event->timestamp = bpf_ktime_get_ns();
    event->syscall_nr = nr;
    event->pid = bpf_get_current_pid_tgid() >> 32;

    bpf_ringbuf_submit(event, 0);

    return 0;
}

リングバッファ上で記録用の領域を確保し、それぞれ値を書き込む。bpf_get_current_pid_tgid関数は戻り値が64ビットの値で、上位32ビットがpidなので右シフトする。

フック関数の登録とイベントの取得

今度はGo側の実装。

type BpfObject struct {
    Events         *ebpf.Map     `ebpf:"events"`
    HookX64SysCall *ebpf.Program `ebpf:"hook_x64_sys_call"`
}

...

var o BpfObject
if err := spec.LoadAndAssign(&o, nil); err != nil {
    panic(err)
}
defer o.Close()

link, err := link.AttachTracing(link.TracingOptions{
    Program: o.HookX64SysCall,
})
if err != nil {
    panic(err)
}
defer link.Close()

eBPFプログラムをオブジェクトに定義し、フック関数が実行されるように登録する。

type SyscallEvent struct {
    Timestamp uint64
    SyscallNr uint32
    Pid       uint32
}

...

rd, err := ringbuf.NewReader(o.Events)
if err != nil {
    panic(err)
}

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

var event SyscallEvent
var events []SyscallEvent

l:
    for {
        select {
        case <-sigCh:
            // received signal
            break l
        default:
        }

        // read record
        record, err := rd.Read()
        if err != nil {
            if err == ringbuf.ErrClosed {
                panic(err)
            }
            continue
        }

        // parse record
        if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.NativeEndian, &event); err != nil {
            fmt.Printf("Failed to parse syscall event: %s\n", err)
            continue
        }

        events = append(events, event)
    }

次にリングバッファから送られるイベントログを取得するために、eBPFプログラムで定義したイベントログ用構造体と同じものを定義する。やり方によっては構造体の定義は自動化できるらしい。

リングバッファとのコネクションを生成し、無限ループでデータが送られてくるのを待機する。signal.Notifyによって外からSIGINTを送ると無限ループを抜けるようにした。

送られてきたイベントログはこんな感じ。

// export json log
file, err := os.Create("syscall_events.json")
if err != nil {
    panic(err)
}
defer file.Close()

encoder := json.NewEncoder(file)

if err := encoder.Encode(events); err != nil {
    panic(err)
}

fmt.Printf("Exported syscall events log\n")

最後にイベントログをJsonにエクスポートして終了。

感想

eBPF、実は思っていたよりも難しくないかもしれない。あと、ちゃんとしたコードを書かないとバイトコードチェックで弾かれる。賢い。

参考

zenn.dev zenn.dev ebpf-go.dev eunomia.dev

UEFI Memory Mapで躓いたポイント

概要

UEFIからブートするタイプのOSでは、ブートローダーで取得したUEFI Memory Mapを受け取り、その情報をもとにメモリアロケーションの初期化を行う。私の自作OSもしかり。

Memory Mapの内容は以下の通り(自作OSのコード)。 github.com

pub const UEFI_PAGE_SIZE: usize = 0x1000;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MemoryType {
    Reserved,
    LoaderCode,
    LoaderData,
    BootServicesCode,
    BootServicesData,
    RuntimeServicesCode,
    RuntimeServicesData,
    Conventional,
    Unusable,
    AcpiReclaim,
    AcpiNonVolatile,
    Mmio,
    MmioPortSpace,
    PalCode,
    PersistentMemory,
    Other(u32),
}

#[derive(Debug, Copy, Clone)]
pub struct MemoryDescriptor {
    pub ty: MemoryType,
    pub phys_start: u64,
    pub virt_start: u64,
    pub page_cnt: u64,
    pub attr: u64,
}

ここで言うpage_cntは1ページがUEFI_PAGE_SIZEバイト=つまり4,096バイトのページの個数である。そして以下がOSが受け取ったMemory Mapの出力。

MemoryDescriptor { ty: BootServicesCode, phys_start: 0, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 4096, virt_start: 0, page_cnt: 159, attr: 15 }
MemoryDescriptor { ty: LoaderData, phys_start: 1048576, virt_start: 0, page_cnt: 909, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 4771840, virt_start: 0, page_cnt: 883, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 8388608, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 8421376, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 8433664, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 8437760, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 8454144, virt_start: 0, page_cnt: 240, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 9437184, virt_start: 0, page_cnt: 3328, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 23068672, virt_start: 0, page_cnt: 698677, attr: 15 }
MemoryDescriptor { ty: LoaderData, phys_start: 2884849664, virt_start: 0, page_cnt: 32768, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3019067392, virt_start: 0, page_cnt: 32769, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3153289216, virt_start: 0, page_cnt: 32, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3153420288, virt_start: 0, page_cnt: 9909, attr: 15 }
MemoryDescriptor { ty: LoaderCode, phys_start: 3194007552, virt_start: 0, page_cnt: 56, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3194236928, virt_start: 0, page_cnt: 53, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3194454016, virt_start: 0, page_cnt: 215, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3195334656, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3195346944, virt_start: 0, page_cnt: 6, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3195371520, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3195375616, virt_start: 0, page_cnt: 1501, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3201523712, virt_start: 0, page_cnt: 168, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3202211840, virt_start: 0, page_cnt: 17, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3202281472, virt_start: 0, page_cnt: 6, attr: 15 }
MemoryDescriptor { ty: LoaderData, phys_start: 3202306048, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3202322432, virt_start: 0, page_cnt: 88, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3202682880, virt_start: 0, page_cnt: 25, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3202785280, virt_start: 0, page_cnt: 5, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3202805760, virt_start: 0, page_cnt: 59, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203047424, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203092480, virt_start: 0, page_cnt: 39, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203252224, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203256320, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203268608, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203280896, virt_start: 0, page_cnt: 7, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203309568, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203325952, virt_start: 0, page_cnt: 14, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203383296, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203387392, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203432448, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203440640, virt_start: 0, page_cnt: 13, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203493888, virt_start: 0, page_cnt: 5, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203514368, virt_start: 0, page_cnt: 13, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203567616, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203579904, virt_start: 0, page_cnt: 12, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203629056, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203633152, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203645440, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203653632, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203686400, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203698688, virt_start: 0, page_cnt: 6, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203723264, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203731456, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203776512, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203788800, virt_start: 0, page_cnt: 15, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203850240, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203858432, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203866624, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203874816, virt_start: 0, page_cnt: 20, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203956736, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3203960832, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3203993600, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204001792, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204005888, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204009984, virt_start: 0, page_cnt: 20, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204091904, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204100096, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204132864, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204165632, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204182016, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204198400, virt_start: 0, page_cnt: 16, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204263936, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204272128, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204276224, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204280320, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204292608, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3204304896, virt_start: 0, page_cnt: 35, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3204448256, virt_start: 0, page_cnt: 513, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206549504, virt_start: 0, page_cnt: 12, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206598656, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206631424, virt_start: 0, page_cnt: 11, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206676480, virt_start: 0, page_cnt: 5, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206696960, virt_start: 0, page_cnt: 24, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206795264, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206803456, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206807552, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206815744, virt_start: 0, page_cnt: 9, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206852608, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206860800, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206864896, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206873088, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206877184, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206881280, virt_start: 0, page_cnt: 4, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3206897664, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3206905856, virt_start: 0, page_cnt: 23, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3207000064, virt_start: 0, page_cnt: 1045, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211280384, virt_start: 0, page_cnt: 8, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211313152, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211325440, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211329536, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211333632, virt_start: 0, page_cnt: 10, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211374592, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211382784, virt_start: 0, page_cnt: 2, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211390976, virt_start: 0, page_cnt: 1, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3211395072, virt_start: 0, page_cnt: 3, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3211407360, virt_start: 0, page_cnt: 586, attr: 15 }
MemoryDescriptor { ty: RuntimeServicesData, phys_start: 3213807616, virt_start: 0, page_cnt: 256, attr: 9223372036854775823 }
MemoryDescriptor { ty: RuntimeServicesCode, phys_start: 3214856192, virt_start: 0, page_cnt: 256, attr: 9223372036854775823 }
MemoryDescriptor { ty: Reserved, phys_start: 3215904768, virt_start: 0, page_cnt: 128, attr: 15 }
MemoryDescriptor { ty: AcpiReclaim, phys_start: 3216429056, virt_start: 0, page_cnt: 18, attr: 15 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 3216502784, virt_start: 0, page_cnt: 128, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3217027072, virt_start: 0, page_cnt: 513, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 3219128320, virt_start: 0, page_cnt: 136, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3219685376, virt_start: 0, page_cnt: 32, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3219816448, virt_start: 0, page_cnt: 35, attr: 15 }
MemoryDescriptor { ty: BootServicesData, phys_start: 3219959808, virt_start: 0, page_cnt: 17, attr: 15 }
MemoryDescriptor { ty: BootServicesCode, phys_start: 3220029440, virt_start: 0, page_cnt: 24, attr: 15 }
MemoryDescriptor { ty: RuntimeServicesData, phys_start: 3220127744, virt_start: 0, page_cnt: 132, attr: 9223372036854775823 }
MemoryDescriptor { ty: AcpiNonVolatile, phys_start: 3220668416, virt_start: 0, page_cnt: 136, attr: 15 }
MemoryDescriptor { ty: Conventional, phys_start: 4294967296, virt_start: 0, page_cnt: 262144, attr: 15 }
MemoryDescriptor { ty: Reserved, phys_start: 4278173696, virt_start: 0, page_cnt: 4, attr: 0 }

1~3行目に着目してもらいたい。

1: MemoryDescriptor { ty: BootServicesCode, phys_start: 0, virt_start: 0, page_cnt: 1, attr: 15 }
2: MemoryDescriptor { ty: Conventional, phys_start: 4096, virt_start: 0, page_cnt: 159, attr: 15 }
3: MemoryDescriptor { ty: LoaderData, phys_start: 1048576, virt_start: 0, page_cnt: 909, attr: 15 }

2番目の領域、Conventionalのpage_cntは159、つまり領域のサイズとしては159 * 4,096 = 651,264バイトになる。開始アドレスが4,096とすると終了アドレスは4,096 + 651,264 = 655,360になる。

ところが、その次の3番目の領域の開始アドレスを見ると1048,576とあり、2番目の終了アドレスから393,216バイト分差がある。ここが躓きポイントであり、私が1年以上気付かなかった自作OSのメモリ破壊バグの原因である(実はみかん本にはこれについての言及が存在する)。

Memory Mapに存在しない領域をどう扱うか

Memory Mapに存在しない領域、先の例では2番目の領域と3番目の領域の間の393,216バイトのことだが、このような領域は空き領域として自由に読み書きできるように扱ってはいけない。

メモリ破壊バグについて

私の自作OSでは、ヒープアロケータ初期化以前のメモリ管理として、4KiBごとの領域をビットマップで管理するビットマップアロケータを実装している。Memory Mapを見るとOSが自由に扱える領域はConventional、BootServicesCode / Data(中身のデータの退避などをしたあとで)であるが、当初のビットマップアロケータは「全てのビットマップを空き領域としてマップしてから、自由に扱えない領域を使用済みとしてマップする」という実装になっていた。つまり、Memory Mapに存在しない領域は空き領域という扱いになっており、そこに何らかのデータが書き込まれた瞬間、メモリ破壊によってOSがクラッシュした。スタック関連のデータが破壊されている感触があったが、詳細は不明。

謎のクラッシュは大抵はメモリ破壊バグ(戒め)

2023年を振り返る

年末なので、今年1年を振り返ってみようと思います。

1月

だそうです...

今もデュアルでBenQを使ってる。BenQはいいぞ。

2月

確かデータサイエンスの授業の期末課題で取り扱ったネタ。徹夜でやってたのを覚えている。内容はしょうもなかったけど一応高評価をもらえた。 マルチスレッド実行が上手く行かなくてシングルでやったので、データの加工にクソほど時間を要した。

この時期特有の電気使い過ぎ問題。

3月

DTM、楽しいけどやる時間がなさすぎて結局趣味にはならなかった。

この頃ずっと自作OSのUSBドライバの実装に取り組んでた。ちなみにノートPCのキーボードは今でも(仮想?)PS/2制御だということを大分後に知った。

久々にお絵かきをしたり。

人生で最も長時間プレイしたゲームがHearts of Iron IVで、そんな感じのゲームを自作してみたくなった。

こういうのがあるので自作OSのデバッグと同時並行してQEMUソースコードを読む会が行われている。

4月

ぼざろの影響で聴き始めた。個人的にはアフターダークが一番好き。

Cのプリプロセッサで遊んでた。

ELFデバッガーを自作してた。

5月

ELFデバッガーの自作でptraceやforkを学んでたりした。

2度目のセキュリティ・キャンプに参加するために応募課題を書いてた。この悩みは結局杞憂だったが、hsjoihs氏に名言を教えてもらった。

この「作業」というやつが運命の分かれ道だった。

具体的に何をやっていたかというと、応募したゼミがZ2の「Rust製Linux向けアンチウイルス実装ゼミ」で、当時は「アンチウイルスに興味はあるが仕組みが全くわからん」という状態で応募しようとしていたため、せっかくなら少しでも実際に手を動かして勉強してみようと思い、リアルタイムマルウェア検知機をChatGPTに質問しながら作り、応募課題にその進捗状況や感想を書く欄もないのに無理矢理ねじ込んだ。 内容はシンプルで、inotifyによるディレクトリ内の変更検知、変更があったファイルの表層解析、ELFバイナリの静的解析を行っている。静的解析に関してはこの短時間で難しいことはできないので、脆弱性のあるscanfコードをバイナリから検出するみたいなことをしていた。

ちなみにこの古いtogetterまとめを見て大いに勇気づけられたので、みんなも応募課題を書くときはぜひ見てほしい。

6月

初めてSECCON Beginners CTFに参加した。Beginnersであってもやっぱり難しかった。

無事に選考通過。よかった~

ついにUSBキーボードの入力にまでこぎつけることができた。ちなみに入力を続けると止まってしまう現象は未だに直ってない。

アセンブラを自作し始めた。結局全然やらずに放置してしまっているので、いつかは再開したい。

7月

やらかし。メインで使ってたLinuxを破壊した。その後も色々あってメインでLinuxを使うのはやめてWindowsに戻した。今の時代WSL2とか便利なものがあるしね。

ちょうど事前学習期間だった気がする。

8月

ついにセキュリティ・キャンプが始まった!

本番でもかなり波乱万丈なことをやっていたが、とても勉強になった。

去年お世話になった内田さんとhikaliumさんと現地で再開できた。

田舎の工場事務所で社内SEのバイトを始めた。通勤に車で1時間かかるのはアレだけど待遇が良いので結構気に入ってる。

9月

成人した。結局タイミングがなくてほぼ飲酒してない。

バイトの初任給でStarfieldを買った。PCのスペックが低くてしょっちゅうフリーズしたり、メインクエストが進行不可になったりしている。悲しい。

10月

Chromebookをポチった。お絵かきに丁度よくて、こういう持ち運べるのが1台あると結構便利。

課題やらバイトやらの開発で10~12月はかなり忙しかった。セキュリティコンテスト、ドローンの自動操縦システムを作るなどをしていた。

11月

研究開発の課題用でFPGAをやることになったので、Tang Primer 20Kを購入した。思いつきでnand2tetrisを実装している。

友達が通っている千葉工大の文化祭に行った。ちぇりーたくあん氏のNAND CPUも実物で見れて感動。色々おしゃべりできたのでとても楽しかった。

12月

ひたすらFPGAをやっていた。nand2tetris自体は完成していて、MMIOを実装して機械語からLEDやディスプレイを制御できるように増築工事中。

総括

今年は色々なことに挑戦するという目標でやっていたので、本音を言うともうちょっと密度が欲しかったところですが、おおむね達成できたのではと思います。特に去年もそうでしたが、今年の2度目のセキュリティ・キャンプに参加したのをきっかけに視野がかなり広まったので、とても良い機会だったなと思います。

授業などが忙しくてセキュリティの勉強が全然できていなかったので、来年はちゃんとやります。

Tello SDKに書かれていない謎仕様

DJI Telloを使ってVideoとStateを取得するプログラムを書いていたときの出来事。

Tello SDK User Guide

www.ryzerobotics.com

Receive Tello State

Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT: 8890

Remark3: Set up a UDP server on PC, Mac or Mobile device and listen the message from IP 0.0.0.0 via UDP PORT 8890. Do Remark2 to start receiving state data if you haven’t.

事前に192.168.10.1:8889に任意の任意のデータを送信しないと、Stateの受信に失敗する。

Receive Tello Video Stream

Tello IP: 192.168.10.1 ->> PC/Mac/Mobile UDP Server: 0.0.0.0 UDP PORT:1111

Remark4: Set up a UDP server on PC, Mac or Mobile device and listen the message from IP 0.0.0.0 via UDP PORT 11111.

Remark5: Do Remark2 if you haven’t. Then send “streamon” command to Tello via UDP PORT 8889 to start the streaming.

事前に192.168.10.1:62512に任意のデータを送信しないと、Videoの受信に失敗する。

Wiresharkを覗いてみた

どうやらStateの送信元ポートが8889、Videoの送信元ポートが62512らしい。つまり送信元ポートを叩いていることになる。

謎仕様と書いたが結局のところ...

PythonOpenCVのVideoCaptureでVideoを受信する際にはポートを叩く必要はなかった。VideoとStateを取得するプログラムはRustで書いているので、言語の問題なのかもしれない。

【Linux】Fallout4にModを導入する

LinuxでFallout4にModを導入することができたので、備忘録として残しておこうと思います。

私のPC環境

LinuxのSteamクライアントでFallout4が正常に動いていれば、特に問題はないと思います。

参考にしたサイト

www.nexusmods.com wiki.fallout4.z49.org

英語版Fallout4を日本語化する

Wikiを参考にしながらFallout4Localizerを使って日本語版のバックアップ、英語版の日本語化を行います。私はバッチファイルの内容を確認しながら、手動でファイルのコピーを行いました。

01Fallout4BackupJPFiles.bat

  • Data/Video, Data/Fallout4 - Voices.ba2, Data/Fallout4 - Vocies_rep.ba2Fallout4Localizer内にコピー

02Fallout4InstallJPFiles.bat

英語音声・日本語字幕の場合

  • 以下共通へ

日本語音声・日本語字幕の場合

  • Fallout4Localizer/BackupEnglishVoice内にFallout4 - Voices.ba2をコピー
  • Fallout4Localizer内の*_ja.*に該当するすべてのファイルを*_en.*に書き換え
  • Fallout4Localizer内の*_en.*に該当するすべてのファイルをData内ににコピー
  • バックアップしたFallout4 - Voices.ba2, Fallout4 - Voices_rep.ba2Data内にコピー

以下共通

  • Fallout4Localizer内のInterface, Strings, VideoDataにコピー

iniの編集

Fallout4.iniは、私の環境では/home/[user_name]/.local/share/Steam/steamapps/compatdata/377160/pfx/drive_c/users/steamuser/Documents/My Games/Fallout4/Fallout4.iniに位置しています。Wikiにもあるように、Fallout4.iniは削除してFallout4Custom.iniに以下を追加します。

[Archive]
bInvalidateOlderFiles=1
SResourceArchiveList=Fallout4 - Voices.ba2, Fallout4 - Meshes.ba2, Fallout4 - MeshesExtra.ba2, Fallout4 - Misc.ba2, Fallout4 - Sounds.ba2, Fallout4 - Materials.ba2, Fallout4 - Voices_rep.ba2

Mod Organizer 2をインストールする

次に、Mod Organizer 2をインストールします。Lutrisでも入手することができますが、このリポジトリからのインストールが推奨されているようです。 github.com

READMEに書かれている通り、必要パッケージを事前にインストールし、install.shを実行します。ポップアップが出現するので、それに従ってインストールを進めます。インストールが完了すると、SteamからFallout4を起動しようとするとMod Organizer 2が起動するようになります。私の場合は、インストールする前にProton6.3に変更して一度起動する必要がありました(指示を促すメッセージが表示されます)。

F4SEをインストールする

f4se.silverlock.org

Modを導入する

Nexus ModsからマニュアルダウンロードでModをダウンロードし、Mod Organizer 2からインストールすることができます。

Linuxカーネル(x86_64)をビルドしてQEMUで実行する

カーネルを取得する

$ git clone https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
$ cd linux
# リポジトリを最新のリリースにチェックアウトする
$ git fetch --tags
$ latestTag=$(git describe --tags `git rev-list --tags --max-count=1`)
$ git checkout $latestTag

カーネルをビルドする

# x86_64の設定ファイルをコピー
$ cp arch/x86/configs/x86_64_defconfig ./.config
# Kernel hackingでいろいろ変更できる
$ make menuconfig
# CPUのコア数で並列処理
$ make -j$(nproc)

ユーザーランドを作る

Linuxカーネルは単体だけでは実行することができないため、BusyBoxを使ってinitramfsを作ります。initramfsはcpioアーカイブで、Linuxカーネルは起動時にファイルシステムをマウントする必要があるのですが、起動時はまだ各デバイスを判別することができないため、一時的なファイルシステムとして使われます。

$ cd ../
$ git clone https://git.busybox.net/busybox.git
$ cd busybox
# Setting -> Build static binaryをチェックする
$ make menuconfig
$ make install
$ find . | cpio -o --format=newc > ../../linux/rootfs.img

QEMUで実行する

bzImageLinuxカーネルの実行ファイルで-kernelオプションによってメモリ上に直接展開されるため、ブートローダーを用意する必要はありません。-initrdオプションでは作成したinitramfsを渡し、-appendオプションのrdinitによって、最初に実行されるプログラムをシェルに設定しています。

$ cd ../../linux
$ qemu-system-x86_64 -kernel ./arch/x86_64/boot/bzImage -initrd ./rootfs.img -append "root=/dev/ram rdinit=/bin/sh"

参考サイト

www.rinsymbol.net nullpo-head.hateblo.jp zenn.dev

live-serverでブラウザの自動リロードが効かない問題

www.npmjs.com

live-serverを使ったファイルの変更の監視で、ファイルを更新しても何故かブラウザが自動リロードしてくれなかった。

ビルドスクリプト

www.npmjs.com

esbuild-create-react-appで生成されたbuilder.tsと内容はほぼ同じ。

import { LiveServerParams, start } from "live-server";
import { build, BuildOptions } from "esbuild";
import cssModulesPlugin from "esbuild-css-modules-plugin";
import { removeSync, copySync } from "fs-extra";
import { watch } from "chokidar";

const isWatch = process.argv.includes("--watch");

const serverParams: LiveServerParams =
{
    port: 8181,
    root: "build",
    open: true
};

const buildParams: BuildOptions =
{
    color: true,
    entryPoints: ["src/index.tsx"],
    loader: { ".ts": "tsx", ".json": "json", ".png": "file", ".jpeg": "file", ".jpg": "file", ".svg": "file" },
    assetNames: "assets/[name]-[hash]",
    outdir: "build",
    minify: !isWatch,
    format: "cjs",
    bundle: true,
    sourcemap: isWatch,
    logLevel: "error",
    incremental: isWatch,
    plugins:
  [
        cssModulesPlugin()
    ]
};

// clean build folder
try
{
    removeSync("build");
}
catch (e)
{
    console.error(e);
}

// copy public folder into build folder
try
{
    copySync("public", "build");
}
catch (e)
{
    console.error(e);
}

if (isWatch)
{
    (async () =>
    {
        // build
        const result = await build(buildParams).catch(() => process.exit(1));

        // start live server
        start(serverParams);

        return watch("src/**/*", { ignored: /(^|[/\\])\../, ignoreInitial: true }).on("all", async (event, path) =>
        {
            if (event === "change")
            {
                console.log(`[esbuild] Rebuilding ${path}`);
                console.time("[esbuild] Done");
                if (result.rebuild) await result.rebuild();
                console.timeEnd("[esbuild] Done");
            }
        });
    })();
}
else
{
    console.log(`[esbuild] Building..`);
    build(buildParams).catch(() => process.exit(1));
}
ts-node scripts/build.ts --watch

esbuildがrebuildしたファイルを基にブラウザを自動リロードしたい。

原因

index.htmlのフォーマットに問題があった。 stackoverflow.com

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <!-- <meta
            http-equiv="Content-Security-Policy"
            content="script-src 'self'"
        /> -->
        <title>Title</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="index.js"></script>
    </body>
</html>

electronで使っていたindex.htmlをそのまま流用していたのが良くなかった。上のコメントアウトしたmetaタグがフォーマット的によろしくなかったらしい。

結論

web全然わからん