该工具可以从 vmlinux/vmlinuz/bzImage/zImage 内核映像(无论是原始二进制数据块,还是已存在但剥离了符号的 .ELF 文件)生成一个完全可分析的 .ELF 文件,并恢复其中的函数与变量符号。
为此,它会扫描内核中的内核符号表(kallsyms)——这是一种几乎存在于每个内核中、且基本保持原样的压缩符号表。
由于该符号表原本经过压缩,它应当能够恢复原始二进制文件中不可见的字符串。
它会生成一个 .ELF 文件,你可以使用 IDA Pro 和 Ghidra 对其进行分析。因此,该工具对嵌入式系统的逆向工程非常有用。
用法:
# Command line:
vmlinux-to-elf <input_kernel.bin> <output_kernel.elf>
# Command line, list symbol addresses only:
kallsyms-finder <input_kernel.bin> # If installed with uv
vmlinux-to-elf.kallsyms-finder # If installed with snap
# Command line, just decompress the kernel:
vmlinuz-decompressor <input_kernel.bin> <output_kernel.bin> # If installed with uv
vmlinux-to-elf.vmlinuz-decompressor # If installed with snap
# Graphical:
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
安装:
# Install CLI+GUI with Snap (recommended on Ubuntu)
sudo snap install vmlinux-to-elf
# Install CLI+GUI with yay (recommended on Arch, Manjaro)
yay -S vmlinux-to-elf libadwaita python-gobject
# Install CLI+GUI with uv (example with Fedora)
sudo dnf install -y uv glib2-devel libadwaita-devel gtk4-devel \
gobject-introspection-devel python3-gobject-devel \
python3-devel cairo-devel @development-tools
uv tool install vmlinux-to-elf[gui]
vmlinux-to-elf-gui --install-metadata # Install .desktop file
# Install CLI with uv and GUI with Flatpak (recommended on
# distributions with libadwaita < 1.6)
sudo dnf install -y uv flatpak @development-tools
uv tool install vmlinux-to-elf
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak install re.fossplant.vmlinux-to-elf
本地开发环境搭建:
sudo snap install --classic astral-uv
sudo apt install git
git clone [email protected]:marin-m/vmlinux-to-elf.git
# Dependencies for the GTK-4 GUI
sudo apt install libgirepository-2.0-dev libadwaita-1-dev \
gir1.2-adw-1 gir1.2-gtk-4.0 python3-dev blueprint-compiler
cd vmlinux-to-elf
cp -a .github/hooks/* .git/hooks/
# Download Python modules and initialize virtualenv (creates ".venv",
# call "source .venv/bin/activate" to set up)
uv sync --extra gui
# Add vmlinux-to-elf to $PATH, so that the commands are callable
# system-wide (creates a symlink to the source in "~/.local/bin")
uv tool install -e .[gui]
boot.img 文件,这些文件以 ANDROID! 或 UNCOMPRESSED_IMG 魔数开头 [OK]关于“kallsyms”符号表的简要历史可以在“kallsyms.py”文件的开头找到。简而言之,它于 2004 年左右以当前形式引入 Linux 内核,除其他用途外,还用于打印“Kernel oops”消息。
它包含“符号名”、“符号地址”、“符号类型”的元组(符号类型以单个字母表示,方式与 nm 工具类似),这些信息通过一种简单的压缩算法被紧密打包。
下面的架构图展示了这些信息如何序列化到内核中,各个结构的偏移由 vmlinux-to-elf 通过启发式检测来识别:
| 数组名称 | 描述 | 示例内容 |
|---|---|---|
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”中对应符号的一个偏移以 long 类型添加到该表中 | 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 个字,每个字映射到“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 年起有一个补丁,默认移除对 kallsyms 表的压缩(当用户在构建时启用了 kallsyms)。他们这样做是为了在使用 LZMA 重新压缩内核时节省空间。
这意味着 kallsyms_token_table 和 kallsyms_token_address 条目消失,符号名改为使用纯文本 ASCII。本工具同样支持这种情况。
在标准 Linux 6.2 内核中,kallsyms 数组按以下顺序编码:
kallsyms_addresses(或 kallsyms_offsets + kallsyms_relative_base)kallsyms_num_symskallsyms_nameskallsyms_markerskallsyms_seqs_of_names(仅 6.2+)kallsyms_token_tablekallsyms_token_index对于 Linux 6.4+ 内核,该布局改为:
kallsyms_num_symskallsyms_nameskallsyms_markerskallsyms_token_tablekallsyms_token_indexkallsyms_addresses(或 kallsyms_offsets + kallsyms_relative_base)kallsyms_seqs_of_names而 vmlinux-to-elf 的解析算法按以下顺序解析它们:
kallsyms_token_table(倒数第二个结构)kallsyms_token_index(最后一个结构,向前)kallsyms_markers(向后)kallsyms_names(再次向后)kallsyms_num_syms(再次向后)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 的魔数):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