完整工作流程与原理
概述
本文档详细记录了将 ZTE B860AV1.1-T2 机顶盒改造为 Linux 服务器的完整工作流程,包括背景知识、技术原理、实施步骤和验证方法。
1. 项目背景
1.1 设备信息
设备型号: ZTE B860AV1.1-T2
处理器: ZX296716 (ARM Cortex-A53, 64位)
内存: 990MB RAM
存储: 8GB eMMC
系统: Android 4.4.2
网络: WiFi (RTL8189FS) + 有线以太网1.2 改造目标
- 禁用 Android 框架 - 移除不必要的服务和应用
- 优化系统资源 - 释放内存和 CPU
- 实现网络持久化 - 自动连接 WiFi
- 防止配置写入 - 禁用 conf 分区写入
- 准备 Linux 迁移 - 为 Alpine Linux chroot 做准备
1.3 技术挑战
- 非标准 boot.img - ZTE 特有格式
- 内核补丁 - 需要二进制逆向工程
- 服务依赖 - 复杂的 Android 服务架构
- 持久化问题 - 重启后配置丢失
2. 技术原理
2.1 Android 启动流程
硬件初始化
├── Bootloader
│ ├── 加载 boot.img
│ ├── 解析 ZTE 头部
│ └── 跳转到内核入口
├── Linux 内核
│ ├── 初始化硬件
│ ├── 挂载根文件系统
│ └── 启动 init 进程
├── init 进程
│ ├── 解析 init.rc
│ ├── 启动服务
│ └── 触发事件
└── 用户空间
├── Zygote (应用孵化器)
├── System Server (系统服务)
└── 应用程序2.2 boot.img 格式
ZTE 特有格式:
偏移 0x00-0x5F: ZTE 头部
- 魔数: 0x55667788
- 版本信息
- 保留字段
偏移 0x60-0x7FF: Android 头部
- 魔数: "ANDROID!"
- 内核大小和地址
- Ramdisk 大小和地址
- 第二阶段信息
偏移 0x800-结束: 内核 + Ramdisk + DTB
- ARM64 内核镜像
- 压缩的 Ramdisk
- 设备树二进制 (DTB)2.3 ARM64 内核结构
ARM64 Image 头部 (0x60 字节)
├── 跳转指令 (0x00)
├── 文本偏移 (0x08)
├── 镜像大小 (0x10)
├── 标志位 (0x18)
└── 魔数 "ARM\x64" (0x38)
内核代码
├── 初始化代码
├── 中断处理
├── 进程调度
├── 内存管理
├── 文件系统
└── 设备驱动2.4 conf 分区机制
conf 分区用途:
- 存储设备配置信息
- 记录启动失败次数
- 保存安全模式状态
写入机制:
set_fs_safe_mode()
├── 读取 MonitorFailedNum
├── 递增计数器
├── 写入 conf 分区
└── 检查是否进入安全模式
conf_fs_write()
├── 接收写入请求
├── 格式化数据
├── 写入 conf 分区
└── 返回结果3. 逆向工程方法
3.1 工具准备
bash
# 安装必要的工具
sudo apt-get install binutils-aarch64-linux-gnu
sudo apt-get install python3
sudo apt-get install android-tools-adb
# 下载 OTA 文件
# 从设备提取 boot.img3.2 内核提取
python
import struct
def extract_kernel(boot_img, output_file):
"""从 boot.img 提取内核"""
with open(boot_img, 'rb') as f:
data = f.read()
# 检查 ZTE 魔数
if data[0:4] != b'\x55\x66\x77\x88':
raise ValueError("无效的 ZTE 魔数")
# 检查 Android 魔数
if data[96:104] != b'ANDROID!':
raise ValueError("无效的 Android 魔数")
# 提取内核 (从偏移 0x800 开始)
kernel_offset = 0x800
kernel_data = data[kernel_offset:]
# 保存内核
with open(output_file, 'wb') as f:
f.write(kernel_data)
print(f"内核已提取到: {output_file}")
print(f"内核大小: {len(kernel_data)} 字节")3.3 字符串定位
python
def find_strings(kernel_file, pattern):
"""查找内核中的字符串"""
import subprocess
# 使用 strings 命令
result = subprocess.run(
['strings', '-t', 'x', kernel_file],
capture_output=True,
text=True
)
# 解析结果
strings = []
for line in result.stdout.split('\n'):
if pattern in line:
parts = line.split(' ', 1)
if len(parts) == 2:
offset = int(parts[0], 16)
string = parts[1]
strings.append((offset, string))
return strings3.4 函数定位
python
def find_function(kernel_data, string_offset):
"""根据字符串偏移查找函数"""
# 字符串通常在函数内部通过 ADRP+ADD 引用
# 需要找到引用该字符串的代码
target_page = string_offset & ~0xFFF
target_offset = string_offset & 0xFFF
refs = []
i = 0
while i < len(kernel_data) - 8:
# 查找 ADRP Xn, #target_page
val = struct.unpack('<I', kernel_data[i:i+4])[0]
if (val & 0x9F000000) == 0x90000000:
Rd = val & 0x1F
immhi = (val >> 5) & 0x7FFFF
immlo = (val >> 29) & 0x3
imm = (immhi << 2) | immlo
if imm & 0x100000:
imm -= 0x200000
adrp_page = ((i & ~0xFFF) + (imm << 12)) & 0xFFFFFFFF
if adrp_page == target_page:
# 查找 ADD Xn, Xn, #target_offset
val2 = struct.unpack('<I', kernel_data[i+4:i+8])[0]
if (val2 & 0xFFC00000) == 0x91000000:
imm12 = (val2 >> 10) & 0xFFF
if imm12 == target_offset:
refs.append(i)
i += 4
return refs3.5 函数边界检测
python
def find_function_boundaries(kernel_data, start_offset):
"""查找函数边界"""
# 向前查找函数序言 (STP X29, X30, ...)
prologue = None
i = start_offset
while i > 0:
val = struct.unpack('<I', kernel_data[i:i+4])[0]
# STP X29, X30, [SP, #imm]!
if (val & 0x7FC00000) == 0x29800000:
prologue = i
break
i -= 4
# 向后查找函数结束 (RET)
epilogue = None
i = start_offset
while i < len(kernel_data):
val = struct.unpack('<I', kernel_data[i:i+4])[0]
# RET 指令
if val == 0xD65F03C0:
epilogue = i
break
i += 4
return prologue, epilogue4. 补丁技术
4.1 补丁策略
策略 1: 函数禁用
python
def patch_function_disable(kernel_data, function_offset):
"""禁用函数 - 替换序言为 RET"""
# 保存原始指令
original = struct.unpack('<I', kernel_data[function_offset:function_offset+4])[0]
# 替换为 RET
struct.pack_into('<I', kernel_data, function_offset, 0xD65F03C0)
return original策略 2: 调用禁用
python
def patch_call_disable(kernel_data, call_offset):
"""禁用函数调用 - 替换 BL 为 NOP"""
original = struct.unpack('<I', kernel_data[call_offset:call_offset+4])[0]
# 替换为 NOP
struct.pack_into('<I', kernel_data, call_offset, 0xD503201F)
return original4.2 补丁实现
python
import struct
import hashlib
def apply_patches(input_file, output_file, patches):
"""应用补丁到 boot.img"""
with open(input_file, 'rb') as f:
data = bytearray(f.read())
# 内核起始偏移
kernel_offset = 0x800
print("应用补丁:")
for offset, original, patched, desc in patches:
file_offset = kernel_offset + offset
# 验证原始值
actual = struct.unpack('<I', data[file_offset:file_offset+4])[0]
if actual != original:
print(f" 警告: 0x{offset:06x} 原始值不匹配")
print(f" 期望: 0x{original:08x}")
print(f" 实际: 0x{actual:08x}")
# 应用补丁
struct.pack_into('<I', data, file_offset, patched)
print(f" ✓ 0x{offset:06x}: {desc}")
# 保存补丁后的文件
with open(output_file, 'wb') as f:
f.write(data)
# 计算 MD5
md5 = hashlib.md5(data).hexdigest()
print(f"\n输出文件: {output_file}")
print(f"MD5: {md5}")
return md54.3 补丁列表
python
# 定义补丁
patches = [
# (偏移, 原始值, 补丁值, 描述)
(0x4798E0, 0x900028C0, 0xD65F03C0, "诊断函数 RET"),
(0x479910, 0x97EEF8E2, 0xD503201F, "conf 写入调用 NOP"),
(0x47991C, 0xA9BA7BFD, 0xD65F03C0, "set_fs_safe_mode RET"),
(0x478E70, 0xA9B87BFD, 0xD65F03C0, "conf_fs_write RET"),
]
# 应用补丁
apply_patches(
'/tmp/boot_cma16_patched.bin',
'/tmp/boot_cma16_patched_v3.bin',
patches
)5. 系统裁剪
5.1 APK 清理
bash
#!/system/bin/sh
# 清理无用 APK
# 备份原始 APK
mkdir -p /tmp/apk_backup
# 删除游戏和娱乐应用
for app in /system/app/GameCenter.apk /system/app/VideoPlayer.apk; do
if [ -f "$app" ]; then
cp "$app" /tmp/apk_backup/
rm -f "$app"
echo "删除: $app"
fi
done
# 删除第三方应用
for app in /system/app/BaiduMap.apk /system/app/WeChat.apk; do
if [ -f "$app" ]; then
cp "$app" /tmp/apk_backup/
rm -f "$app"
echo "删除: $app"
fi
done5.2 服务禁用
bash
# 修改 init.rc 禁用服务
# 备份原始 init.rc
cp /system/etc/init.rc /system/etc/init.rc.bak
# 注释掉 zygote 服务
sed -i 's/^service zygote/#service zygote/' /system/etc/init.rc
# 注释掉 media 服务
sed -i 's/^service media/#service media/' /system/etc/init.rc5.3 框架清理
bash
# 删除框架文件
rm -rf /system/framework/*.jar
rm -rf /system/framework/*.vdex
# 删除系统库
rm -rf /system/lib/*.so
rm -rf /system/lib64/*.so
# 删除二进制文件
rm -f /system/bin/app_process
rm -f /system/bin/mediaserver
rm -f /system/bin/surfaceflinger6. 网络配置
6.1 WiFi 驱动加载
bash
# 加载 WiFi 驱动
insmod /system/lib/modules/8189fs.ko
# 验证驱动加载
lsmod | grep 8189
# 检查网络接口
ifconfig wlan06.2 wpa_supplicant 配置
bash
# 创建配置文件
cat > /data/wpa.conf << 'EOF'
ctrl_interface=/var/run/wpa_supplicant
network={
ssid="Xiaomi_F5A3"
psk="123456789"
key_mgmt=WPA-PSK
}
EOF
# 启动 wpa_supplicant
wpa_supplicant -B -iwlan0 -c /data/wpa.conf6.3 网络初始化脚本
bash
#!/system/bin/sh
# eth0.sh - 网络初始化脚本
# 加载 WiFi 驱动
insmod /system/lib/modules/8189fs.ko
sleep 2
# 启动 wpa_supplicant
wpa_supplicant -B -iwlan0 -c /data/wpa.conf
sleep 3
# 获取 IP 地址
dhcpcd wlan0
sleep 2
# 配置 DNS
setprop net.dns1 8.8.8.8
setprop net.dns2 8.8.4.4
# 启用 ADB over TCP
setprop service.adb.tcp.port 5555
stop adbd
start adbd
# 设置主机名
setprop persist.sys.hostname alpine
setprop net.hostname alpine7. 验证方法
7.1 补丁验证
python
def verify_patches(file_path, patches):
"""验证所有补丁"""
with open(file_path, 'rb') as f:
data = f.read()
kernel_offset = 0x800
all_ok = True
print("验证补丁:")
for offset, expected, desc in patches:
file_offset = kernel_offset + offset
actual = struct.unpack('<I', data[file_offset:file_offset+4])[0]
if actual == expected:
print(f" ✓ 0x{offset:06x}: {desc}")
else:
print(f" ✗ 0x{offset:06x}: {desc}")
print(f" 期望: 0x{expected:08x}")
print(f" 实际: 0x{actual:08x}")
all_ok = False
return all_ok7.2 文件完整性验证
python
def verify_file_integrity(file_path, expected_md5):
"""验证文件完整性"""
import hashlib
with open(file_path, 'rb') as f:
data = f.read()
actual_md5 = hashlib.md5(data).hexdigest()
if actual_md5 == expected_md5:
print(f"✓ MD5 匹配: {actual_md5}")
return True
else:
print(f"✗ MD5 不匹配")
print(f" 期望: {expected_md5}")
print(f" 实际: {actual_md5}")
return False7.3 系统状态验证
bash
#!/system/bin/sh
# 验证系统状态
echo "=== 系统状态验证 ==="
# 检查内核补丁
echo "1. 检查内核补丁..."
dd if=/dev/block/boot bs=12959159 count=1 | md5sum
# 检查 conf 分区
echo "2. 检查 conf 分区..."
strings /dev/block/conf | grep MonitorFailedNum
# 检查 WiFi 连接
echo "3. 检查 WiFi 连接..."
ifconfig wlan0
ping -c 1 8.8.8.8
# 检查 ADB 连接
echo "4. 检查 ADB 连接..."
getprop service.adb.tcp.port
# 检查系统资源
echo "5. 检查系统资源..."
free
df -h /data
echo "=== 验证完成 ==="8. 部署流程
8.1 完整部署步骤
bash
# 1. 准备补丁文件
python3 patch_kernel.py
# 2. 推送补丁到设备
adb push boot_cma16_patched_v3.bin /tmp/
# 3. 刷入 boot 分区
adb shell "dd if=/tmp/boot_cma16_patched_v3.bin of=/dev/block/boot bs=4096"
# 4. 验证刷入
adb shell "dd if=/dev/block/boot bs=12959159 count=1 | md5sum"
# 5. 创建网络脚本
adb shell "cat > /system/etc/eth0.sh << 'EOF'
#!/system/bin/sh
insmod /system/lib/modules/8189fs.ko
sleep 2
wpa_supplicant -B -iwlan0 -c /data/wpa.conf
sleep 3
dhcpcd wlan0
EOF"
# 6. 设置脚本权限
adb shell "chmod 755 /system/etc/eth0.sh"
# 7. 更新 install-recovery.sh
adb shell "cat > /system/etc/install-recovery.sh << 'EOF'
#!/system/bin/sh
/system/etc/eth0.sh
EOF"
# 8. 设置脚本权限
adb shell "chmod 755 /system/etc/install-recovery.sh"
# 9. 重启设备
adb reboot
# 10. 验证部署
sleep 60
adb connect 192.168.31.209:5555
adb shell "lsmod | grep 8189"
adb shell "ping -c 1 8.8.8.8"8.2 部署检查清单
[ ] 补丁文件已生成
[ ] boot.img 已刷入
[ ] MD5 验证通过
[ ] 网络脚本已创建
[ ] 脚本权限已设置
[ ] 设备已重启
[ ] WiFi 驱动已加载
[ ] 网络连接正常
[ ] ADB 访问正常
[ ] conf 分区写入已禁用9. 故障排除
9.1 常见问题及解决方案
问题 1: 设备无法启动
- 原因: boot.img 损坏
- 解决: 使用 OTA 恢复
问题 2: WiFi 无法连接
- 原因: 驱动未加载或配置错误
- 解决: 手动加载驱动并检查配置
问题 3: ADB 无法连接
- 原因: ADB over TCP 未启用
- 解决: 通过串口启用 ADB
问题 4: conf 分区仍在写入
- 原因: 补丁未生效
- 解决: 重新刷入 boot.img
9.2 恢复方法
bash
# 方法 1: 使用 OTA 恢复
adb shell "dd if=/cache/upgrade/ota.zip of=/dev/block/boot bs=4096"
# 方法 2: 使用 fastboot
fastboot flash boot boot.img
fastboot flash system system.img
# 方法 3: 使用 TWRP 恢复
# 进入 TWRP 恢复模式
# 选择备份文件恢复10. 后续工作
10.1 Alpine Linux 迁移
步骤:
- 下载 Alpine Linux ARM64 rootfs
- 推送到设备
- 配置 chroot 环境
- 迁移系统服务
- 测试功能
命令:
bash
# 下载 Alpine Linux
wget https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/aarch64/alpine-minirootfs-3.18.0-aarch64.tar.gz
# 推送到设备
adb push alpine-minirootfs-3.18.0-aarch64.tar.gz /data/
# 解压到 /data/alpine
adb shell "mkdir -p /data/alpine"
adb shell "tar xzf /data/alpine-minirootfs-3.18.0-aarch64.tar.gz -C /data/alpine"
# 配置 chroot
adb shell "mount -t proc proc /data/alpine/proc"
adb shell "mount -t sysfs sysfs /data/alpine/sys"
adb shell "mount -o bind /dev /data/alpine/dev"
# 进入 chroot
adb shell "chroot /data/alpine /bin/sh"10.2 系统优化
- 内核优化 - 调整内核参数
- 内存优化 - 优化内存使用
- 存储优化 - 优化文件系统
- 网络优化 - 优化网络配置
10.3 功能扩展
- Docker 支持 - 安装 Docker
- Web 服务器 - 部署 Nginx/Apache
- 数据库 - 安装 MySQL/PostgreSQL
- 监控系统 - 部署监控工具
11. 总结
本文档详细记录了将 ZTE B860AV1.1-T2 机顶盒改造为 Linux 服务器的完整工作流程,包括:
- 项目背景 - 设备信息和改造目标
- 技术原理 - Android 启动流程和内核结构
- 逆向工程 - 内核分析和函数定位
- 补丁技术 - 二进制补丁实现
- 系统裁剪 - APK 清理和服务禁用
- 网络配置 - WiFi 驱动和网络脚本
- 验证方法 - 补丁和系统验证
- 部署流程 - 完整部署步骤
- 故障排除 - 常见问题解决
- 后续工作 - Alpine Linux 迁移
通过这些工作,成功将机顶盒改造为专用 Linux 服务器,实现了:
- ✓ 禁用 Android 框架
- ✓ 优化系统资源
- ✓ 实现网络持久化
- ✓ 防止配置写入
- ✓ 准备 Linux 迁移
作者: Xiaomi MiMo
日期: 2026-07-26
版本: 1.0