marin-m/vmlinux-to-elf

GitHub: marin-m/vmlinux-to-elf

一个能从原始或压缩的 Linux 内核镜像中提取 kallsyms 符号表,并将其转换为可被 IDA 和 Ghidra 分析的 ELF 文件的工具。

Stars: 1696 | Forks: 180

# vmlinux-to-elf 该工具允许从 vmlinux/vmlinuz/bzImage/zImage 内核镜像(可以是原始二进制 blob 或已存在但被剥离符号的 .ELF 文件)中获取一个完全可分析的 .ELF 文件,并恢复函数和变量符号。

为此,它会扫描您的内核以查找内核符号表 ([kallsyms](https://github.com/torvalds/linux/blob/master/kernel/kallsyms.c)),这是一种存在于几乎所有内核中的压缩符号表,大部分情况下未被修改。 由于相关的符号表最初是压缩的,因此它应该能恢复原始二进制文件中不可见的字符串。 它生成一个可以使用 IDA Pro 和 Ghidra 分析的 .ELF 文件。因此,该工具对于嵌入式系统逆向工程非常有用。

 

用法: ``` # 命令行: vmlinux-to-elf # 命令行,仅列出符号地址: kallsyms-finder # If installed with uv vmlinux-to-elf.kallsyms-finder # If installed with snap # 图形界面: vmlinux-to-elf-gui # If installed with uv vmlinux-to-elf.gui # If installed with snap flatpak run re.fossplant.vmlinux-to-elf # If installed with flatpak ```

Application main screen Application kernel offsets view

安装: ``` # 使用 Snap 安装 CLI+GUI(Ubuntu 上推荐) sudo snap install vmlinux-to-elf # 使用 uv 安装 CLI+GUI(其他发行版上推荐) sudo apt install gir1.2-adw-1 gir1.2-gtk-4.0 uv tool install vmlinux-to-elf[gui] # 使用 uv 安装 CLI 并使用 Flatpak 安装 GUI(推荐用于 # libadwaita < 1.6 的发行版) sudo snap install --classic astral-uv uv tool install vmlinux-to-elf sudo apt install flatpak flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo flatpak install re.fossplant.vmlinux-to-elf ``` 本地开发环境设置: ``` git clone git@github.com:marin-m/vmlinux-to-elf.git # GTK-4 GUI 的依赖项 sudo apt install libgirepository-2.0-dev libgtk-4-dev libadwaita-1-dev \ gir1.2-adw-1 gir1.2-gtk-4.0 python3-dev glib-compile-resources cd vmlinux-to-elf # 下载 Python 模块并初始化 virtualenv(创建 ".venv", # 调用 "source .venv/bin/activate" 进行设置) uv sync --extra gui # 将 vmlinux-to-elf 添加到 $PATH,以便命令可在 # 全系统范围内调用(在 "~/.local/bin" 中创建指向源代码的符号链接) uv tool install -e .[gui] ``` ## 功能 * 接收原始二进制 blob 或 ELF 内核文件作为输入 [OK] * 自动检测并解压 Linux 内核使用的主要压缩格式 [OK] * 从输入文件中查找并提取嵌入的内核符号表 (kallsyms) [OK] * 推断指令集架构、字节序、位大小,依据包括常见的函数 prologue 签名 [OK] * 从 kallsyms 表中包含的符号推断内核的入口点 [OK] * 提供内核基地址的基本推断 [OK](目前,将其视为二进制文件中低 0xfff 位清零的第一个“TEXT”符号地址 - 似乎工作良好) * 解压特定类型的 Android `boot.img` 文件,以 `ANDROID!` 或 `UNCOMPRESSED_IMG` 魔数开头 [OK] * 生成一个可使用 IDA Pro 或 Ghidra 完全分析的 .ELF 文件作为输出 [OK] ## 它究竟是如何工作的? 关于“kallsyms”符号表的简要历史可以在“[kallsyms.py](vmlinux_to_elf/core/kallsyms.py)”文件的顶部找到。简而言之,它大约在 2004 年以当前形式引入 Linux 内核,除其他外用于打印“Kernel oops”消息。 它包含“符号名称”、“符号地址”、“符号类型”的元组(符号类型用类似于 [`nm`](http://man7.org/linux/man-pages/man1/nm.1p.html) 实用程序的单个字母表示),此信息通过简单的压缩算法紧密打包。 下面的模式展示了此信息如何序列化到内核中,每个各自结构的偏移量由 `vmlinux-to-elf` 通过[启发式算法](vmlinux_to_elf/core/kallsyms.py)检测: | 数组名称 | 描述 | 示例内容 | | ---------- | ----------- | --------------- | | `kallsyms_addresses` (或 `kallsyms_offsets` + `kallsyms_relative_base`) | 每个符号的地址(或在较新内核中相对于基地址的偏移量),作为数组 | `80 82 00 C0 80 82 00 C0 80 82 00 C0 0C 84 00 C0 B4 84 00 C0 5C 85 00 C0 60 85 00 C0 60 85 00 C0` ... | `kallsyms_num_syms` | 符号总数,作为整数(用于检查字节序、对齐、符号表的正确解码) | `54 D4 00 00` | `kallsyms_names` | 压缩的、长度分隔的符号名称本身。压缩符号字符串中的每个字节引用“kallsyms_token_index”数组中的一个索引,该索引本身引用“kallsyms_token_table”数组中字符或字符串片段的偏移量。 | `09 54 64 6F 5F E1 F1 66 F5 25 05 54 F3 74 AB 74 0E 54 FF AB` ... | `kallsyms_markers` | 用于快速查找“kallsyms_names”中压缩符号名称大致位置的查找表:每 256 个符号,指向“kallsyms_names”中相关符号的偏移量将作为一个长整数添加到此表中。 | `00 00 00 00 03 0C 00 00 0C 18 00 00 1B 24 00 00 0F 31 00 00 DA 3D 00 00 CF 4A 00 00` ... | `kallsyms_seqs_of_names` | 此查找表(仅存在于 6.2+ 内核中)包含打包的 3 字节整数的数组序列,其中数组索引匹配给定符号名称的字母数字顺序,数组值匹配 `kallsyms_addresses` 和 `kallsyms_names` 数组中的相应条目索引 | `kallsyms_token_table` | 可能包含在内核符号名称中的以空字符结尾的字符串片段或字符。这最多可以包含 256 个字符串片段或字符。与任何内核符号中实际使用的 ASCII 码点对应的索引将对应于相关的 ASCII 字符,其他位置将包含统计选择的字符串片段。此工具首先尝试在传递的文件中启发式地查找此数组,以便找到 `kallsyms` 符号表。 | `73 69 00 67 70 00 74 74 00 79 6E 00 69 6E 74 5F 00 66 72 00 ` ... | `kallsyms_token_index` | 256 个字(word),每个映射到“kallsyms_token_table”中由其各自索引指定的字符或字符串片段的偏移量。 | `00 00 03 00 06 00 09 00 0C 00 11 00 14 00 1B 00 1E 00 22 00 2C 00 30 00 35 00 38 00` ... 这些字段具有可变的对齐方式和字段大小。字段大小也可能因架构和内核版本而异。因此,`vmlinux-to-elf` 已在各种情况下进行了测试。 OpenWRT [自 2013 年起](https://git.openwrt.org/?p=openwrt/svn-archive/archive.git;a=commit;h=5317e9cb69bb42dee167e0552a5e1f01147ba072)有一个[补丁](https://github.com/openwrt-mirror/openwrt/blob/9b4650b/target/linux/generic/patches-4.4/203-kallsyms_uncompressed.patch),默认情况下移除 `kallsyms` 表上的压缩(当用户启用构建 `kallsyms` 时)。他们这样做是为了在使用 LZMA 对内核进行重新压缩时节省空间。 这意味着 `kallsyms_token_table` 和 `kallsyms_token_address` 条目消失,符号名称改用纯文本 ASCII。这种情况也受支持。 在标准 Linux 6.2 内核中,`kallsyms` 数组按以下顺序编码: 1. `kallsyms_addresses` (或 `kallsyms_offsets` + `kallsyms_relative_base`) 2. `kallsyms_num_syms` 3. `kallsyms_names` 4. `kallsyms_markers` 5. `kallsyms_seqs_of_names` (仅限 6.2+) 6. `kallsyms_token_table` 7. `kallsyms_token_index` 对于 Linux 6.4+ 内核,此布局更改为: 1. `kallsyms_num_syms` 2. `kallsyms_names` 3. `kallsyms_markers` 4. `kallsyms_token_table` 5. `kallsyms_token_index` 6. `kallsyms_addresses` (或 `kallsyms_offsets` + `kallsyms_relative_base`) 7. `kallsyms_seqs_of_names` 而 `vmlinux-to-elf` 的解析算法按以下顺序解析它们: 1. `kallsyms_token_table` (倒数第二个结构) 2. `kallsyms_token_index` (最后一个结构,向前) 3. `kallsyms_markers` (向后) 4. `kallsyms_names` (再次向后) 5. `kallsyms_num_syms` (再次向后) 6. `kallsyms_addresses` (或 `kallsyms_offsets` + `kallsyms_relative_base`) (再次向后) ## 内核支持 它应支持从版本 2.6.10(2004 年 12 月)到当前 6.4(截至 2023 年 8 月)的内核。只有显式配置为不带 `CONFIG_KALLSYMS` 的内核才不受支持。如果在构建时未设置此内核配置变量,您将收到:`KallsymsNotFoundException: No embedded symbol table found in this kernel`。 对于原始内核,可以检测以下架构(使用 [binwalk](https://github.com/ReFirmLabs/binwalk/blob/master/src/binwalk/magic/binarch) 中的魔数):MIPSEL, MIPSEB, ARMEL, ARMEB, PowerPC, SPARC, x86, x86-64, ARM64, MIPS64, SuperH, ARC。 以下内核压缩格式可以自动检测:XZ, LZMA, GZip, BZ2, LZ4, LZO 和 Zstd。 ## 高级用法 您还可以通过使用同样随此工具捆绑的 `kallsyms-finder` 实用程序,获取内核符号名称、地址和类型的纯文本输出。其输出格式将类似于 `/proc/kallsyms` procfs 文件。 一些应由工具自动推断的参数(如指令集或基地址)在出现问题时可以被覆盖。允许执行此操作的参数的完整规范如下所示: ``` $ vmlinux-to-elf -h usage: vmlinux-to-elf [-h] [--e-machine DECIMAL_NUMBER] [--bit-size BIT_SIZE] [--file-offset HEX_NUMBER] [--base-address HEX_NUMBER] [--bss-size BSS_SIZE] [--use-absolute] input_file output_file Turn a raw or compressed kernel binary, or a kernel ELF without symbols, into a fully analyzable ELF whose symbols were extracted from the kernel symbol table positional arguments: input_file Path to the vmlinux/vmlinuz/zImage/bzImage/kernel.bin/kernel.elf file to make into an analyzable .ELF output_file Path to the analyzable .ELF to output options: -h, --help show this help message and exit --e-machine DECIMAL_NUMBER Force overriding the output ELF "e_machine" field with this integer value (rather than auto-detect) --bit-size BIT_SIZE Force overriding the input kernel bit size, providing 32 or 64 bit (rather than auto-detect) --file-offset HEX_NUMBER Consider that the raw kernel starts at this offset of the provided raw file or compressed stream (rather than 0, or the beginning of the ELF sections if an ELF header was present in the input) --base-address HEX_NUMBER Force overriding the output ELF base address field with this integer value (rather than auto-detect) --bss-size BSS_SIZE Size in megabytes of the .bss section in the binary --use-absolute Assume kallsyms offsets are absolute addresses ``` ## 错误修复、改进等 请随时为了任何改进建议[开启一个 issue](https://github.com/marin-m/vmlinux-to-elf/issues/new)。 请优先使用当前的 Github 仓库 issues 和 pull requests 来报告错误、提问等。 或者,如果需要直接联系项目的作者,您可以使用[这个 Matrix 频道](https://matrix.to/#/!ppalJoNacGRgESlXnK:matrix.org),但请将其保留为辅助渠道,例如用于发送内核样本,否则这里的内容更容易丢失。
标签:ELF文件, Ghidra, IDA Pro, kallsyms, Linux内核, Python, vmlinux, 二进制分析, 云安全运维, 云资产清单, 固件分析, 安全渗透, 嵌入式安全, 文件格式转换, 无后门, 符号表提取, 逆向工具, 逆向工程