Skip to content

完整工作流程与原理

🕒 Published at:

完整工作流程与原理 ​

概述 ​

本文档详细记录了将 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 改造目标 ​

  1. 禁用 Android 框架 - 移除不必要的服务和应用
  2. 优化系统资源 - 释放内存和 CPU
  3. 实现网络持久化 - 自动连接 WiFi
  4. 防止配置写入 - 禁用 conf 分区写入
  5. 准备 Linux 迁移 - 为 Alpine Linux chroot 做准备

1.3 技术挑战 ​

  1. 非标准 boot.img - ZTE 特有格式
  2. 内核补丁 - 需要二进制逆向工程
  3. 服务依赖 - 复杂的 Android 服务架构
  4. 持久化问题 - 重启后配置丢失

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.img

3.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 strings

3.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 refs

3.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, epilogue

4. 补丁技术 ​

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 original

4.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 md5

4.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
done

5.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.rc

5.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/surfaceflinger

6. 网络配置 ​

6.1 WiFi 驱动加载 ​

bash
# 加载 WiFi 驱动
insmod /system/lib/modules/8189fs.ko

# 验证驱动加载
lsmod | grep 8189

# 检查网络接口
ifconfig wlan0

6.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.conf

6.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 alpine

7. 验证方法 ​

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_ok

7.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 False

7.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 迁移 ​

步骤:

  1. 下载 Alpine Linux ARM64 rootfs
  2. 推送到设备
  3. 配置 chroot 环境
  4. 迁移系统服务
  5. 测试功能

命令:

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 系统优化 ​

  1. 内核优化 - 调整内核参数
  2. 内存优化 - 优化内存使用
  3. 存储优化 - 优化文件系统
  4. 网络优化 - 优化网络配置

10.3 功能扩展 ​

  1. Docker 支持 - 安装 Docker
  2. Web 服务器 - 部署 Nginx/Apache
  3. 数据库 - 安装 MySQL/PostgreSQL
  4. 监控系统 - 部署监控工具

11. 总结 ​

本文档详细记录了将 ZTE B860AV1.1-T2 机顶盒改造为 Linux 服务器的完整工作流程,包括:

  1. 项目背景 - 设备信息和改造目标
  2. 技术原理 - Android 启动流程和内核结构
  3. 逆向工程 - 内核分析和函数定位
  4. 补丁技术 - 二进制补丁实现
  5. 系统裁剪 - APK 清理和服务禁用
  6. 网络配置 - WiFi 驱动和网络脚本
  7. 验证方法 - 补丁和系统验证
  8. 部署流程 - 完整部署步骤
  9. 故障排除 - 常见问题解决
  10. 后续工作 - Alpine Linux 迁移

通过这些工作,成功将机顶盒改造为专用 Linux 服务器,实现了:

  • ✓ 禁用 Android 框架
  • ✓ 优化系统资源
  • ✓ 实现网络持久化
  • ✓ 防止配置写入
  • ✓ 准备 Linux 迁移

作者: Xiaomi MiMo
日期: 2026-07-26
版本: 1.0