moonregf

    纯 MoonBit 实现的 Windows 注册表 hive(REGF)读取与写入库,不依赖 Windows API

    windows
    registry
    regf
    hive
    parser
    forensics
    Download zip
    Author
    Version
    0.2.0
    License
    Apache-2.0
    Last updated
    4 hours ago
    Downloads
    9

    #moonregf

    纯 MoonBit 实现的 Windows 注册表 hive 文件(REGF 二进制格式)读取与写入库:不依赖 Windows API、C 库或 FFI。

    Windows 把 NTUSER.DAT、SYSTEM、SOFTWARE 等配置单元以 REGF 格式存盘。本项目在纯 MoonBit 中读写该格式, 让工具链在 Windows 之外(Linux CI、wasm-gc、浏览器)也能读取与生成 hive,并把 .reg 文本导入成 hive。

    #安装

    moon add Xcsa-ee/moonregf

    moon.pkg 中导入:

    import { "Xcsa-ee/moonregf", }

    #读取

    match @moonregf.parse_header(bytes) { Ok(header) => println(header.file_name) Err(error) => println(error.message()) }

    遍历键树、读取键值:

    for entry in @moonregf.walk_key_tree(bytes, header.root_cell_offset, 3) { println(entry.path) } match @moonregf.list_values(bytes, key) { Ok(values) => for value in values { println(value.name + " = " + value.data_type.label()) } Err(error) => println(error.message()) }

    #写入

    let builder = @moonregf.HiveBuilder::new() let software = builder.add_key(0, "Software") builder.set_string(software, "Install", "Hello") builder.set_dword(software, "Count", 42) match builder.build() { Ok(bytes) => println(bytes.length()) Err(error) => println(error.message()) }

    #.reg 导入

    match @moonregf.parse_reg_bytes(raw) { Ok(file) => match file.to_builder() { Ok(builder) => builder.build() Err(error) => println(error.message()) } Err(error) => println(error.message()) }

    命令行:

    moon run cmd/main -- <path-to-NTUSER.DAT> moon run cmd/main -- <path-to-NTUSER.DAT> --values moon run cmd/main -- --build demo.hive moon run cmd/main -- --import-reg backup.reg rebuilt.hive moon run cmd/main -- --export-reg rebuilt.hive out.reg HKEY_CURRENT_USER moon run cmd/main -- --replay-log dirty.hive dirty.hive.LOG1 clean.hive

    #示例

    examples/basic 在内存里构造一个 hive,再读回并打印每个键与值,不需要任何输入文件:

    moon run examples/basic

    #当前进度

    #持续集成

    .github/workflows/ci.yml 在 Ubuntu 上安装 MoonBit 工具链并依次执行格式检查、检查、构建、 wasm-gc 与 js 两个后端的测试,以及 examples/basic。

    #已知限制

    • 写入器只接受 ASCII 键名与值名,非 ASCII 名称返回 UnsupportedName。
    • 单个值最大 16344 字节,超过者返回 ValueTooLarge(尚未写 big data 分段)。
    • .reg 导入不支持 [-key] 删除段,遇到时返回 UnsupportedDelete。
    • 事务日志只解析 Windows 8.1 及更新版本的 HvLE 格式;更早的 512 字节头格式返回 UnsupportedLogVersion。
    • .reg 导出输出 UTF-8 文本;导入端支持 UTF-16LE 与 UTF-8 两种输入。

    #许可

    Apache-2.0,见 LICENSE。

    BuildValue

    type BuildValue

    A value waiting to be written into a hive.

    HiveBuilder

    pub struct HiveBuilder {
    names : Array[String]
    parents : Array[Int]
    children : Array[Array[Int]]
    values : Array[Array[BuildValue]]
    }

    A tree of keys and values that build() turns into a REGF hive.

    Nodes are addressed by index; node 0 is the root key.

    HiveBuilder::add_key

    fn HiveBuilder::add_key(self : HiveBuilder, parent : Int, name : String) -> Int

    Adds a child key under parent and returns its index.

    HiveBuilder::build

    fn HiveBuilder::build(self : HiveBuilder) -> Result[Bytes, RegfError]

    Serializes the tree into a REGF hive file.

    The layout is the one Windows uses: a 4096-byte base block, then one or more hive bins whose cells carry the nk, vk and lf records. Key names must be ASCII and a value payload must fit in one data cell (16344 bytes).

    HiveBuilder::child_index

    fn HiveBuilder::child_index(self : HiveBuilder, parent : Int, name : String) -> Int?

    Index of the child of parent whose name matches exactly, if any.

    HiveBuilder::key_count

    fn HiveBuilder::key_count(self : HiveBuilder) -> Int

    Number of keys in the tree.

    HiveBuilder::new

    Creates a builder holding a single root key called ROOT.

    HiveBuilder::new_named

    fn HiveBuilder::new_named(root_name : String) -> HiveBuilder

    Creates a builder whose root key uses root_name.

    HiveBuilder::set_dword

    fn HiveBuilder::set_dword(self : HiveBuilder, key_index : Int, name : String, value : Int) -> Unit

    Stores a REG_DWORD value.

    HiveBuilder::set_string

    fn HiveBuilder::set_string(self : HiveBuilder, key_index : Int, name : String, text : String) -> Unit

    Stores a REG_SZ value holding text.

    HiveBuilder::set_value

    fn HiveBuilder::set_value(self : HiveBuilder, key_index : Int, name : String, data_type : HiveValueType, data : Bytes) -> Unit

    Stores one value under the key at key_index.

    HiveCell

    pub(all) struct HiveCell {
    offset : UInt
    payload_size : Int
    allocated : Bool
    }

    A cell inside the hive bins region.

    A cell starts with a signed 32-bit size that covers the size field itself: negative sizes mark allocated cells, positive sizes mark free cells.

    HiveFileType

    pub(all) enum HiveFileType {
    Primary
    Log
    External
    Unknown(UInt)
    }

    Kind of file recorded in the base block.

    HiveFileType::label

    fn HiveFileType::label(self : HiveFileType) -> String

    Short label for a hive file type.

    HiveHeader

    pub(all) struct HiveHeader {
    primary_sequence : UInt
    secondary_sequence : UInt
    last_written_high : UInt
    last_written_low : UInt
    major_version : UInt
    minor_version : UInt
    file_type : HiveFileType
    file_format : UInt
    root_cell_offset : UInt
    hive_bins_data_size : UInt
    clustering_factor : UInt
    file_name : String
    checksum : UInt
    }

    Parsed REGF base block (the 4096 bytes at the start of a hive file).

    HiveKeyEntry

    pub(all) struct HiveKeyEntry {
    path : String
    offset : UInt
    subkey_count : Int
    value_count : Int
    }

    One entry of a flattened key tree.

    HiveKeyNode

    pub(all) struct HiveKeyNode {
    offset : UInt
    flags : UInt
    last_written_high : UInt
    last_written_low : UInt
    access_bits : UInt
    parent_offset : UInt
    subkey_count : UInt
    volatile_subkey_count : UInt
    subkey_list_offset : UInt
    volatile_subkey_list_offset : UInt
    value_count : UInt
    value_list_offset : UInt
    security_offset : UInt
    class_name_offset : UInt
    name_length : UInt
    class_name_length : UInt
    name : String
    }

    A key node (nk) record: one registry key and the offsets of its children.

    HiveSubkeyList

    pub(all) struct HiveSubkeyList {
    offset : UInt
    kind : SubkeyListKind
    count : Int
    key_offsets : Array[UInt]
    }

    A subkey list record: the direct children of one key.

    For root index (ri) records the child offsets of the nested lists are flattened, so key_offsets always holds key node offsets in stored order.

    HiveValue

    pub(all) struct HiveValue {
    offset : UInt
    name : String
    data_type : HiveValueType
    data_size : UInt
    data_offset : UInt
    inline_data : Bool
    data : Bytes
    }

    A registry value (vk record) together with its resolved data.

    inline_data is true when the four bytes of data_offset hold the data itself, which Windows does for payloads of at most four bytes.

    HiveValue::as_multi_string

    fn HiveValue::as_multi_string(self : HiveValue) -> Array[String]?

    Splits the data of a REG_MULTI_SZ value into its NUL separated entries.

    HiveValue::as_string

    fn HiveValue::as_string(self : HiveValue) -> String?

    Decodes the data of a string typed value; the trailing NUL is dropped.

    HiveValue::as_u32

    fn HiveValue::as_u32(self : HiveValue) -> UInt?

    Interprets the data as a 32-bit integer when it holds at least four bytes.

    HiveValueType

    pub(all) enum HiveValueType {
    Nil
    Sz
    ExpandSz
    Binary
    Dword
    DwordBigEndian
    Link
    MultiSz
    ResourceList
    FullResourceDescriptor
    ResourceRequirementsList
    Qword
    Unknown(UInt)
    }

    Kind of data stored in a registry value.

    HiveValueType::from_code

    fn HiveValueType::from_code(code : UInt) -> HiveValueType

    Maps the numeric type code stored in a vk record to a value type.

    HiveValueType::label

    fn HiveValueType::label(self : HiveValueType) -> String

    Registry type name such as REG_SZ, as used by regedit and .reg files.

    LogEntry

    pub(all) struct LogEntry {
    sequence : UInt
    bins_data_size : UInt
    size : Int
    pages : Array[LogPageRef]
    data : Bytes
    }

    One transaction log entry: dirty page references plus their bytes.

    LogFile

    pub(all) struct LogFile {
    primary_sequence : UInt
    secondary_sequence : UInt
    entries : Array[LogEntry]
    }

    A parsed transaction log file.

    LogFile::entry_count

    fn LogFile::entry_count(self : LogFile) -> Int

    Number of entries in the log.

    LogFile::page_count

    fn LogFile::page_count(self : LogFile) -> Int

    Total number of dirty pages across every entry.

    LogPageRef

    pub(all) struct LogPageRef {
    offset : UInt
    size : Int
    }

    One dirty page reference inside a log entry.

    LogReplay

    pub(all) struct LogReplay {
    bytes : Bytes
    applied : Int
    final_sequence : UInt
    }

    Outcome of replaying a log onto a hive.

    RegFile

    pub(all) struct RegFile {
    sections : Array[RegSection]
    }

    A parsed .reg document.

    RegFile::section_count

    fn RegFile::section_count(self : RegFile) -> Int

    Number of sections in the document.

    RegFile::to_builder

    fn RegFile::to_builder(self : RegFile) -> Result[HiveBuilder, RegfError]

    Builds a hive from the document.

    The first path segment of each section (the predefined root such as HKEY_CURRENT_USER) is dropped, so the remaining path hangs off the hive root key. Delete sections are reported instead of being dropped silently.

    RegFile::value_count

    fn RegFile::value_count(self : RegFile) -> Int

    Number of values across all sections.

    RegSection

    pub(all) struct RegSection {
    path : String
    delete : Bool
    values : Array[RegValue]
    }

    One [path] section of a .reg file.

    RegValue

    pub(all) struct RegValue {
    name : String
    data_type : HiveValueType
    data : Bytes
    }

    One value read from a .reg file.

    RegfError

    pub(all) enum RegfError {
    TooShort(Int)
    BadSignature(String)
    BadChecksum(UInt, UInt)
    BadCellOffset(Int)
    BadCellSize(Int)
    BadKeyNode(Int)
    BadSubkeyList(Int)
    BadValue(Int)
    BadValueData(Int)
    UnsupportedName(Int)
    ValueTooLarge(Int)
    BadRegFile(String)
    UnsupportedDelete(String)
    NotALogFile(UInt)
    UnsupportedLogVersion(UInt)
    BadLogEntry(Int)
    BadLogPage(Int)
    }

    Errors reported while parsing, building or replaying a REGF hive file.

    RegfError::message

    fn RegfError::message(self : RegfError) -> String

    Human readable message for an error value.

    SubkeyListKind

    pub(all) enum SubkeyListKind {
    FastLeaf
    HashLeaf
    IndexLeaf
    RootIndex
    Unknown(String)
    }

    Kind of subkey list record stored in a hive.

    SubkeyListKind::label

    fn SubkeyListKind::label(self : SubkeyListKind) -> String

    Short label for a subkey list kind.

    apply_log

    fn apply_log(hive : Bytes, log : Bytes) -> Result[Bytes, RegfError]

    Replays a log and returns only the patched hive bytes.

    decode_single_byte

    fn decode_single_byte(bytes : Bytes, offset : Int, count : Int) -> String

    Decodes count single-byte characters starting at byte offset.

    REGF stores compressed key names as one byte per character. Bytes are decoded as UTF-8 with replacement so ASCII names round-trip exactly and stray high bytes stay readable.

    decode_utf16le

    fn decode_utf16le(bytes : Bytes, offset : Int, unit_count : Int) -> String

    Decodes unit_count UTF-16LE code units starting at byte offset.

    Decoding stops at the first NUL code unit, matching how REGF stores the fixed-width base block file name. Unpaired surrogates become U+FFFD so the result is always valid UTF-8.

    encode_utf16le

    fn encode_utf16le(text : String) -> Bytes

    Encodes text as UTF-16LE code units.

    export_reg_text

    fn export_reg_text(bytes : Bytes, root_path : String) -> Result[String, RegfError]

    Renders a hive as .reg text.

    Every section path is root_path followed by the key path below the hive root key, so a hive whose root holds Environment renders as [HKEY_CURRENT_USER\Environment] when root_path is HKEY_CURRENT_USER.

    list_subkeys

    fn list_subkeys(bytes : Bytes, key : HiveKeyNode) -> Result[Array[HiveKeyNode], RegfError]

    Reads the direct children of a key.

    This is the strict view: any unreadable child record fails the whole call.

    list_values

    fn list_values(bytes : Bytes, key : HiveKeyNode) -> Result[Array[HiveValue], RegfError]

    Reads the values of a key in stored order.

    This is the strict view: any unreadable value fails the whole call.

    parse_header

    fn parse_header(bytes : Bytes) -> Result[HiveHeader, RegfError]

    Parses the base block of a hive file.

    parse_key_node

    fn parse_key_node(bytes : Bytes, offset : UInt) -> Result[HiveKeyNode, RegfError]

    Parses the key node stored in the cell at offset.

    parse_log

    fn parse_log(bytes : Bytes) -> Result[LogFile, RegfError]

    Parses a transaction log file.

    Windows 8.1 and newer write a REGF base block with file type 6 followed by HvLE entries on 512-byte boundaries; the parser only accepts that layout and reports older formats as UnsupportedLogVersion instead of guessing.

    parse_reg_bytes

    fn parse_reg_bytes(raw : Bytes) -> Result[RegFile, RegfError]

    Parses .reg text from raw file bytes, honouring UTF-16LE and UTF-8 byte order marks.

    parse_reg_file

    fn parse_reg_file(text : String) -> Result[RegFile, RegfError]

    Parses .reg text into sections and values.

    parse_value

    fn parse_value(bytes : Bytes, offset : UInt) -> Result[HiveValue, RegfError]

    Parses the value record stored in the cell at offset and resolves its data.

    read_cell

    fn read_cell(bytes : Bytes, offset : UInt) -> Result[HiveCell, RegfError]

    Reads the cell header at offset, which is relative to the bins region.

    read_subkey_list

    fn read_subkey_list(bytes : Bytes, offset : UInt) -> Result[HiveSubkeyList, RegfError]

    Reads the subkey list stored at offset, expanding nested ri records.

    read_u16_le

    fn read_u16_le(bytes : Bytes, offset : Int) -> UInt

    Reads a little-endian 16-bit unsigned integer at offset.

    The caller must guarantee that offset + 2 is within bounds.

    read_u32_le

    fn read_u32_le(bytes : Bytes, offset : Int) -> UInt

    Reads a little-endian 32-bit unsigned integer at offset.

    The caller must guarantee that offset + 4 is within bounds.

    read_value_list

    fn read_value_list(bytes : Bytes, offset : UInt) -> Result[Array[UInt], RegfError]

    Reads the value list stored in the cell at offset.

    A value list cell is a plain array of 32-bit vk offsets; it carries no signature of its own.

    replay_log

    fn replay_log(hive : Bytes, log : Bytes) -> Result[LogReplay, RegfError]

    Replays a transaction log onto a copy of a hive.

    Entries are applied in stored order and only when their sequence is newer than the secondary sequence of the hive, so replaying a stale log leaves the hive untouched. Dirty page offsets are relative to the hive bins data, and the base block sequence numbers plus checksum are rewritten after the last applied entry.

    value_offsets

    fn value_offsets(bytes : Bytes, key : HiveKeyNode) -> Result[Array[UInt], RegfError]

    Reads the vk offsets listed by a key, without parsing the values.

    The lenient counterpart of list_values: callers that must survive hives referencing volatile or freed cells can parse each offset on their own.

    walk_key_tree

    fn walk_key_tree(bytes : Bytes, root_offset : UInt, max_depth : Int) -> Array[HiveKeyEntry]

    Walks the key tree rooted at root_offset, descending max_depth levels.

    The lenient view used for offline hives: children whose records cannot be parsed are skipped, because hives can reference volatile or freed cells that are absent from the file on disk.