binwalk自定义提取器开发:以ubifs格式为例
【免费下载链接】binwalk Firmware Analysis Tool 项目地址: https://gitcode.com/gh_mirrors/bi/binwalk
引言:嵌入式固件分析的痛点与解决方案
在嵌入式系统安全分析中,UBIFS(Unsorted Block Image File System)作为一种流行的闪存文件系统格式,广泛应用于各类物联网设备和嵌入式设备中。然而,当使用binwalk(Firmware Analysis Tool)对包含UBIFS格式的固件进行分析时,许多开发者都会遇到一个共同的痛点:现有的提取器无法完美支持所有UBIFS变体,导致固件提取不完整或失败。
本文将带你一步步实现一个UBIFS格式的自定义提取器,解决这一痛点。读完本文后,你将获得以下技能:
- 理解binwalk提取器的工作原理
- 掌握自定义提取器的开发流程
- 学会UBIFS格式解析的关键技术
- 能够将自定义提取器集成到binwalk中
binwalk提取器架构概述
binwalk的提取器系统采用模块化设计,支持两种类型的提取器:内部提取器(Internal Extractors)和外部提取器(External Extractors)。
提取器类型对比
| 提取器类型 | 实现语言 | 性能 | 安全性 | 可移植性 | 开发难度 |
|---|---|---|---|---|---|
| 内部提取器 | Rust | 高 | 高 | 高 | 中 |
| 外部提取器 | 命令行工具 | 中 | 中 | 低 | 低 |
提取器工作流程
开发准备:环境与工具
开发环境要求
- Rust编译器(1.56.0或更高版本)
- binwalk源代码(从https://gitcode.com/gh_mirrors/bi/binwalk获取)
- UBIFS格式的样本文件(用于测试)
- 代码编辑器(推荐VS Code + Rust插件)
项目结构分析
binwalk源代码中与提取器相关的关键目录结构如下:
src/
├── extractors.rs # 提取器模块入口
├── extractors/ # 各种格式的提取器实现
│ ├── common.rs # 提取器通用功能
│ ├── ubi.rs # UBI/UBIFS提取器(我们将实现这里)
│ ...
├── signatures/ # 文件格式签名定义
│ ├── ubi.rs # UBI/UBIFS签名定义
│ ...
└── structures/ # 文件结构解析
├── ubi.rs # UBI/UBIFS结构定义
...
UBIFS格式解析
UBIFS文件系统结构
UBIFS文件系统由多个逻辑擦除块(LEB)组成,每个LEB包含多个节点(Node)。关键结构包括:
UBIFS签名识别
UBIFS使用特定的魔术字节(Magic Bytes)来标识其结构:
- UBIFS节点魔术:
0x31181006 - UBI擦除块魔术:
UBI#\x01(0x5542492301) - UBI卷魔术:
UBI!\x01(0x5542492101)
这些魔术字节定义在src/signatures/ubi.rs中,用于在固件中识别UBIFS格式。
自定义提取器开发步骤
步骤1:定义提取器结构
首先,我们需要在src/extractors/ubi.rs中定义UBIFS提取器结构。提取器需要实现Extractor trait,该trait定义在src/extractors/common.rs中。
use crate::extractors::common::{Chroot, Extractor, ExtractionResult, ExtractorType};
use crate::structures::ubi::{parse_ubi_superblock_header, UbiSuperBlockHeader};
/// UBIFS提取器实现
pub fn ubifs_extractor() -> Extractor {
Extractor {
utility: ExtractorType::Internal(extract_ubifs),
extension: "ubifs".to_string(),
arguments: vec![],
exit_codes: vec![0],
do_not_recurse: false,
}
}
/// UBIFS内部提取函数
fn extract_ubifs(file_data: &[u8], offset: usize, output_directory: Option<&str>) -> ExtractionResult {
let mut result = ExtractionResult::default();
// 解析UBIFS超级块头部
if let Ok(sb_header) = parse_ubi_superblock_header(&file_data[offset..]) {
result.size = Some(sb_header.leb_size * sb_header.leb_count);
// 如果指定了输出目录,则执行实际提取
if let Some(output_dir) = output_directory {
let chroot = Chroot::new(Some(output_dir));
// 创建输出目录
if !chroot.create_directory("ubifs_root").unwrap_or(false) {
result.success = false;
return result;
}
// 提取UBIFS内容(简化版)
result.success = extract_ubifs_data(
&chroot,
&file_data[offset..],
sb_header
);
} else {
// 干运行模式,仅验证格式不提取内容
result.success = true;
}
}
result
}
步骤2:实现UBIFS解析与数据提取
接下来,实现UBIFS数据提取的核心功能。我们需要解析UBIFS的超级块、节点结构,并将文件系统内容提取到指定目录。
/// 提取UBIFS文件系统内容
fn extract_ubifs_data(chroot: &Chroot, data: &[u8], sb_header: UbiSuperBlockHeader) -> bool {
let leb_size = sb_header.leb_size;
let leb_count = sb_header.leb_count;
// 遍历每个LEB
for leb_idx in 0..leb_count {
let leb_offset = leb_idx * leb_size;
if leb_offset >= data.len() {
break;
}
let leb_data = &data[leb_offset..std::cmp::min(leb_offset + leb_size, data.len())];
// 解析LEB中的节点
if let Ok(nodes) = parse_leb_nodes(leb_data, leb_size) {
// 处理每个节点
for node in nodes {
match node.node_type {
NodeType::Inode => {
if let Ok(inode) = parse_inode_node(&node.data) {
process_inode(chroot, inode, &node.data[inode.header_size..]);
}
}
NodeType::DirEntry => {
// 处理目录项节点
}
// 处理其他类型节点...
_ => {}
}
}
}
}
true
}
/// 解析LEB中的所有节点
fn parse_leb_nodes(leb_data: &[u8], leb_size: usize) -> Result<Vec<Node>, StructureError> {
let mut nodes = Vec::new();
let mut offset = 0;
while offset < leb_size {
if offset + NODE_HEADER_SIZE > leb_data.len() {
break;
}
// 解析节点头部
let node_header = parse_node_header(&leb_data[offset..])?;
let node_size = node_header.size as usize;
// 验证节点CRC
if !validate_node_crc(&leb_data[offset..offset+node_size], &node_header) {
break;
}
// 创建节点对象
let node = Node {
node_type: node_header.node_type,
crc: node_header.crc,
size: node_size,
data: leb_data[offset..offset+node_size].to_vec(),
};
nodes.push(node);
offset += node_size;
}
Ok(nodes)
}
步骤3:实现安全文件操作
binwalk提供了Chroot结构体(在src/extractors/common.rs中),用于安全地进行文件操作,防止路径遍历攻击。我们需要使用这个结构体来创建文件和目录。
/// 处理inode节点,创建文件或目录
fn process_inode(chroot: &Chroot, inode: InodeHeader, data: &[u8]) -> bool {
let path = format!("ubifs_root/{}", inode.name);
match inode.file_type {
FileType::Directory => {
// 创建目录
chroot.create_directory(&path).unwrap_or(false)
}
FileType::RegularFile => {
// 创建文件并写入数据
chroot.create_file(&path, data).unwrap_or(false)
}
FileType::Symlink => {
// 创建符号链接
chroot.create_symlink(&path, inode.symlink_target).unwrap_or(false)
}
// 处理其他文件类型...
_ => false,
}
}
步骤4:注册提取器
在src/extractors.rs中注册我们实现的UBIFS提取器:
pub mod ubi;
// 在extractors数组中添加UBIFS提取器
pub fn extractors() -> Vec<Extractor> {
let mut extractors = Vec::new();
// ...其他提取器注册...
// 添加UBIFS提取器
extractors.push(ubi::ubifs_extractor());
extractors
}
步骤5:定义UBIFS签名
在src/signatures/ubi.rs中定义UBIFS的签名,使binwalk能够识别UBIFS格式:
use crate::signatures::common::{CONFIDENCE_HIGH, SignatureResult};
/// UBIFS签名定义
pub fn ubifs_signatures() -> Vec<Signature> {
vec![
Signature {
name: "ubifs".to_string(),
magic: vec![b"\x31\x18\x10\x06".to_vec()], // UBIFS节点魔术
offset: 0,
description: "UBIFS file system".to_string(),
confidence: CONFIDENCE_HIGH,
parser: Some(ubifs_parser),
extractor: Some("ubifs".to_string()),
..Default::default()
},
]
}
/// UBIFS签名解析器
pub fn ubifs_parser(file_data: &[u8], offset: usize) -> Result<SignatureResult, SignatureError> {
let mut result = SignatureResult {
offset,
description: "UBIFS file system".to_string(),
confidence: CONFIDENCE_HIGH,
..Default::default()
};
// 解析UBIFS超级块以获取大小信息
if let Ok(sb_header) = parse_ubi_superblock_header(&file_data[offset..]) {
result.size = Some(sb_header.leb_size * sb_header.leb_count);
result.description = format!(
"UBIFS file system, {} LEBs of {} bytes each",
sb_header.leb_count, sb_header.leb_size
);
return Ok(result);
}
Err(SignatureError)
}
编译与测试
编译binwalk
# 在项目根目录执行
cargo build --release
测试UBIFS提取器
创建一个测试脚本test_ubifs_extractor.sh:
#!/bin/bash
# 假设我们有一个包含UBIFS的测试固件test_firmware.bin
# 和一个已知正确提取的UBIFS文件系统样本ubifs_sample/
# 使用自定义提取器运行binwalk
./target/release/binwalk -e test_firmware.bin -C extraction_test
# 比较提取结果与样本
diff -r extraction_test/_test_firmware.bin.extracted/ubifs_root ubifs_sample
if [ $? -eq 0 ]; then
echo "UBIFS提取器测试通过!"
else
echo "UBIFS提取器测试失败,存在差异"
fi
调试技巧
- 开启详细日志:
RUST_LOG=debug ./target/release/binwalk -e test_firmware.bin
- 使用GDB调试:
gdb --args ./target/debug/binwalk -e test_firmware.bin
- 单元测试:为关键函数编写单元测试,例如:
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_parse_ubi_superblock() {
let data = fs::read("tests/inputs/ubifs_sb.bin").unwrap();
let sb_header = parse_ubi_superblock_header(&data).unwrap();
assert_eq!(sb_header.leb_size, 0x20000); // 128KB LEB大小
assert_eq!(sb_header.leb_count, 128); // 128个LEB
}
}
高级优化与最佳实践
性能优化
- 并行处理:对于大型UBIFS镜像,可以并行处理多个LEB块:
use rayon::prelude::*;
// 并行处理LEB块
(0..leb_count).into_par_iter().for_each(|leb_idx| {
// 处理每个LEB的代码
});
- 内存优化:避免一次性加载整个固件文件到内存,使用内存映射文件:
use memmap2::Mmap;
let file = File::open("test_firmware.bin").unwrap();
let mmap = unsafe { Mmap::map(&file).unwrap() };
// 将mmap作为&[u8]传递给提取器
错误处理与健壮性
- 完善的错误处理:
// 使用thiserror定义自定义错误类型
use thiserror::Error;
#[derive(Error, Debug)]
pub enum UbifsError {
#[error("Invalid magic bytes: expected {expected:?}, got {actual:?}")]
InvalidMagic { expected: Vec<u8>, actual: Vec<u8> },
#[error("CRC check failed: expected {expected}, got {actual}")]
CrcMismatch { expected: u32, actual: u32 },
#[error("Invalid node type: {0}")]
InvalidNodeType(u8),
// ...其他错误类型
}
- 处理损坏的UBIFS镜像:添加对部分损坏固件的容错处理:
/// 安全解析UBIFS节点,容忍部分损坏
fn safe_parse_node(data: &[u8]) -> Result<Node, UbifsError> {
if data.len() < NODE_HEADER_SIZE {
return Err(UbifsError::InsufficientData);
}
// 解析节点头部
let mut header = [0u8; NODE_HEADER_SIZE];
header.copy_from_slice(&data[0..NODE_HEADER_SIZE]);
// 即使CRC校验失败,也尝试解析节点(用于损坏的固件)
let node_header = match parse_node_header(&header) {
Ok(hdr) => hdr,
Err(_) => {
warn!("节点CRC校验失败,尝试强制解析");
// 创建一个默认头部,尝试继续处理
NodeHeader {
magic: 0,
crc: 0,
size: data.len() as u32,
node_type: NodeType::Unknown,
}
}
};
Ok(Node {
node_type: node_header.node_type,
crc: node_header.crc,
size: node_header.size as usize,
data: data.to_vec(),
})
}
总结与展望
通过本文的步骤,我们成功实现了一个UBIFS格式的binwalk自定义提取器。这个提取器能够解析UBIFS文件系统结构,并将其中的文件和目录提取到指定位置。
关键知识点回顾
- binwalk提取器架构与工作原理
- UBIFS文件系统结构解析
- Rust中的安全文件操作(Chroot)
- 固件分析工具的调试与测试技巧
后续改进方向
- 支持更多UBIFS特性:压缩、加密等
- 提高提取速度和内存效率
- 添加更详细的UBIFS元数据解析
- 实现UBIFS文件系统的可视化分析功能
binwalk作为一款强大的固件分析工具,其自定义提取器机制为开发者提供了扩展其功能的灵活途径。希望本文能够帮助你更好地理解和使用binwalk,为嵌入式系统安全分析工作提供有力支持。
附录:参考资料
- UBIFS官方文档: https://www.kernel.org/doc/html/latest/filesystems/ubifs.html
- binwalk源代码: https://gitcode.com/gh_mirrors/bi/binwalk
- Rust编程语言文档: https://doc.rust-lang.org/
- "Embedded Systems Security" by David Kleidermacher
- "Practical Malware Analysis" by Michael Sikorski & Andrew Honig
【免费下载链接】binwalk Firmware Analysis Tool 项目地址: https://gitcode.com/gh_mirrors/bi/binwalk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



