深入剖析路由器FOTA固件升级流程:从解包到逆向分析

一、引言

FOTA技术在物联网设备中的重要性

Firmware Over-The-Air(FOTA)技术是物联网(IoT)设备维护和管理的关键手段之一。随着智能设备的广泛部署,传统的手动固件升级方式已难以满足大规模、远程管理的需求。FOTA通过无线方式远程更新固件,使设备能够修复漏洞、优化性能、增强安全性并提供新功能,而无需用户手动干预或返回服务中心。其主要优势包括:

  • 远程管理:厂商可批量推送升级,降低维护成本。
  • 安全性:修补安全漏洞,防止设备受到攻击。
  • 稳定性:修复Bug、优化系统,提升设备运行效率。
  • 节省成本:减少人工维护成本,提升设备生命周期。
    FOTA技术在智能家居、工业物联网(IIoT)、路由器、嵌入式系统等领域得到了广泛应用。特别是在路由器等网络设备中,FOTA不仅能修复软件缺陷,还能提高网络协议的兼容性,增强安全性,防止网络攻击。

DWR-932路由器及其固件升级机制

DWR-932是一款广泛应用的便携式4G LTE路由器,支持多种网络模式,主要用于移动网络共享。其FOTA升级机制是嵌入式系统中典型的无线固件升级方案,涵盖了:

  1. 固件包获取:通过FTP或HTTP下载固件压缩包。
  2. 解压与校验:使用加密校验机制验证固件完整性。
  3. 进程管理:由fotad守护进程控制FOTA升级,并与appmgr进程交互。
  4. 固件刷写:利用prefota程序调用MTD工具flash_erasenandwrite完成固件写入。
  5. 重启与验证:升级完成后触发设备重启,并通过Bootloader检查固件状态。

逆向分析后绘制的FOTA更新流程图:

DWR-932的FOTA机制具有一定的安全设计,如固件校验和分区保护,但仍然存在潜在漏洞,值得深入研究。

研究目标

本文的主要目标是:

  • 揭示FOTA固件升级的完整流程:通过逆向分析,探索从固件下载、解包、校验到刷写的详细过程。
  • 研究进程间通信机制:分析fotadappmgrprefota等关键组件如何协同工作。
  • 分析固件升级的安全性:查找可能的安全风险,如未加密通信、硬编码密码、权限控制问题等,详见另一篇文章《IoT安全透视:D-Link DWR-932B固件全面逆向分析》
  • 积累嵌入式系统逆向经验:通过实战学习IDA Prounyaffs等工具的使用,提升对路由器固件的分析能力。

最终,希望本文能为物联网设备FOTA机制的安全性研究提供借鉴,并为嵌入式安全研究者提供实用的分析思路和工具使用技巧。

二、基础知识

2.1 FOTA技术概述

FOTA的定义:FOTA(Firmware Over-The-Air),即固件无线升级,是一种通过无线通信网络远程更新嵌入式设备固件的软件技术,常用于物联网设备、智能家居设备、汽车电子等领域。

2.2 嵌入式固件结构

嵌入式系统固件一般包含以下几个部分:

  1. Bootloader(引导程序)
    • 负责设备启动和固件加载,如U-Boot、LK(Little Kernel)。
    • 在FOTA升级中,Bootloader也负责检测和加载新固件。
  2. 系统映像(Kernel + RootFS)
    • Kernel(内核):操作系统核心,如Linux内核。
    • RootFS(根文件系统):包括应用程序、驱动程序、配置文件等。
  3. 用户文件系统(User FS)
    • 存放用户数据和应用配置文件,通常不会在FOTA升级时被覆盖。
    • 常见格式:ext4、YAFFS2、UBIFS。

常见固件文件格式:

  • YAFFS2(Yet Another Flash File System 2)
    • 主要用于NAND Flash设备,支持掉电保护和坏块管理。
    • FOTA升级时,通常解包YAFFS2格式的固件进行分析。
  • UBI/UBIFS
    • 适用于大容量NAND Flash,提供更好的磨损均衡。
    • 许多现代嵌入式设备采用UBIFS作为用户文件系统。
  • SquashFS
    • 只读压缩文件系统,节省存储空间,常用于嵌入式Linux系统。

2.4 Unix域套接字与进程间通信(IPC)

Unix域套接字的基本概念

Unix域套接字(Unix Domain Socket, UDS)是一种本地进程间通信(IPC)机制,类似于TCP/IP套接字,但仅限于本机通信。相比于管道和消息队列,Unix域套接字性能更高,适用于嵌入式系统中的进程交互。

Unix域套接字的常见用法

  • 数据传输:FOTA进程可以通过Unix套接字传递下载进度和升级状态。
  • 控制指令:appmgr可通过Unix套接字向fotad发送启动/停止升级的命令。

FOTA中的典型应用

在DWR-932路由器中,FOTA使用了Unix域套接字/var/usock/appmgr.us进行通信:

  1. fotad** 监听 **appmgr.us
    __Unix________________ = qmi_usock_server_open("fotad.us", 0);
    while (1) {
   
        memset(s, 0, sizeof(s));
        if (qmi_usock_server_recv(__Unix________________, 1, s, 4124) > 0) {
   
            Log_Message_DF0C("_fota_msg_handle", "msg_id:%d", s[2]);
        }
    }
  1. appmgr** 发送指令**
    appmgr_msg_broadcast_inner(7, 0, 0);
  1. fotad** 解析命令并执行**
    if (s[2] == 208) {
   
        if (s[3] == 529) {
   
            ::n2 = 2;
        }
    }

三、固件解包与初步分析

3.1 固件获取与解包

固件下载地址https://ftp.dlink.de/dwr/dwr-932/archive/driver_software/DWR-932_fw_revb_202eu_ALL_multi_20150119.zip
固件解压缩PLC_1earn/1earn/Security/IOT/固件安全/实验/Dlink_DWR-932B路由器固件分析.md at master · dbshow/PLC_1earn

手动解压发现压缩包被加密了:

方法一;通过fcrackzip进行压缩包密码爆破:

 fcrackzip  -u -v -b  fixed.zip

解压密码是:beUT9Z

方法二:通过逆向分析锁定解压密码:

由于下载的固件是可以被路由器下载并且解压更新的所以程序中一定会将压缩包的压缩密码进行硬编码,所以直接搜索程序中那个地方使用unzip进行解压的时候使用了密码参数就可以锁定了:

直接将prefota拖入IDA查找字符串就可以锁定!

直接使用交叉引用很快就会发现密码所来自的字段是model_name:

┌──(kali㉿kali)-[~/IOT/DWR-932]
└─$ grep -r "model_name"   

直接字符串搜索就可以锁定目标!

发现文件大部分都是yaffs直接安装工具进行解压:

┌──(kali㉿kali)-[/mnt/hgfs/VMShare/IOT/DWR-932]
└─$ sudo apt install unyaffs
┌──(kali㉿kali)-[/mnt/hgfs/VMShare/IOT/DWR-932]
└─$ sudo unyaffs 2K-mdm-image-mdm9625.yaffs2 ~/IOT/DWR-932/yaffs2-root/

3.2 文件系统结构概览

解包出来非常多的文件系统,每个都需要查看,因为不同的文件系统都放置再不同的分区:
在逆向分析的过程中一开始只查看了2K-mdm-image-mdm9625.yaffs2这个文件系统导致文件不全,浪费了很多时间.

┌──(kali㉿kali)-[/mnt/hgfs/VMShare/IOT/DWR-932]
└─$ ls
02.02EU                            2K-mdm-image-boot-mdm9625.img           2K-mdm-recovery-image-mdm9625.yaffs2  DWR-932_B1_02.02EU.zip  mba.mbn      root.hash  tz.mbn
2K-cksum.txt                       2K-mdm-image-mdm9625.yaffs2             appsboot.mbn                          firmwalker.txt          new          rpm.mbn    wdt.mbn
2K-mdm9625-usr-image.usrfs.yaffs2  2K-mdm-recovery-image-boot-mdm9625.img  bin                                   fixed.zip               qdsp6sw.mbn  sbl1.mbn

下面对这些以“2K-”开头的文件做个说明,重点解释它们在固件中的作用:

  • 2K-mdm-image-mdm9625.yaffs2
    这是主固件映像文件,通常包含了调制解调器(modem)系统的核心文件和程序。在逆向 fotad 时发现它调用了 pre-fota.sh 脚本,但该脚本实际上不在这个映像里,而是在用户文件系统映像中。
  • 2K-mdm9625-usr-image.usrfs.yaffs2
    这是用户文件系统映像,主要存放运行时的配置、脚本和额外的工具(比如 pre-fota.sh 脚本)。这种划分通常是为了区分系统核心和用户/配置数据,使固件升级或恢复时能有不同的处理方式。
  • 2K-mdm-image-boot-mdm9625.img 与 2K-mdm-recovery-image-boot-mdm9625.img
    这两个文件分别为正常启动和恢复模式下的 bootloader 映像,它们负责在设备开机时加载相应的固件映像。
  • 2K-mdm-recovery-image-mdm9625.yaffs2
    这是专门用于恢复模式的固件映像,提供一套备用的系统环境,以便在主系统出现问题时进行修复或恢复操作。
  • 2K-cksum.txt
    这个文件一般用于记录上述固件映像文件的校验和,方便在升级或恢复过程中验证文件完整性,确保固件数据未被破坏或篡改。

总结来说,所有以“2K-”开头的文件都是固件包的重要组成部分,分别负责启动、运行、恢复以及数据完整性校验。仔细看发现 pre-fota.sh 脚本在用户文件系统映像(2K-mdm9625-usr-image.usrfs.yaffs2)中,而在系统映像(2K-mdm-image-mdm9625.yaffs2)中调用该脚本,正体现了固件设计中将核心系统和用户数据分离管理的思想。

四、FOTA流程逆向分析

有fota相关知识可知,在加上init里面的shell脚本和字符串upgrade,可以锁定/sbin/fotad程序被用于FOTA固件更新!

4.1 程序入口点分析

start函数与_libc_start_main的调用逻辑。

// positive sp value has been detected, the output may be wrong!
void __noreturn start(void (*rtld_fini)(void), int a2, int a3, int a4, ...)
{
   
  int argc; // [sp-4h] [bp-4h]
  va_list va; // [sp+0h] [bp+0h] BYREF

  va_start(va, a4);
  _libc_start_main(main, argc, va, init, nullsub_1, rtld_fini, va);
  abort();
}

这个函数是程序的入口点(start),负责初始化程序并调用 main 函数。

  • rtld_fini:指向运行时链接器(RTLD)清理函数的指针,在程序退出时调用。
  • _libc_start_main:C库的入口函数,负责初始化程序并调用 main 函数。其参数包括:
    • main:程序的主函数。
    • argc:命令行参数的数量。
    • va:命令行参数的值(通过 va_list 传递)。
    • init:程序初始化函数(在 main 之前调用)。
    • nullsub_1:可能是一个空函数或占位符。
    • rtld_fini:运行时链接器(RTLD)的清理函数(在程序退出时调用)。
    • va:其他参数。

分析一下init初始化函数进行的操作:

初始化函数(sub_9840)与性能监控(gmon)的关系。

void *sub_9840()
{
   
  void *result; // r0

  result = &loc_D94C;
  if ( &__gmon_start__ )
    return _gmon_start__();
  return result;
}

在程序启动时,根据是否启用性能分析(gmon)动态选择执行性能分析初始化或默认初始化代码

这里的初始化函数是:loc_D94C

启动FOTA监控线程,用于固件升级状态的实时监控,若线程创建失败则记录错误日志

result = pthread_create(&newthread, 0, start_routine, Update_FOTA_Status_D74C);

实时监控FOTA固件升级状态,从 /proc/fota/info 文件中读取升级进度和版本号,并通过回调函数 Update_FOTA_Status_D74C 上报状态信息

4.2 主函数(main)逻辑剖析

int __fastcall main(int n2, char **a2, char **a3)
{
   
  const char *_var_fota_fotad.conf; // r1
  int v6; // r3
  _WORD s[2062]; // [sp+Ch] [bp-1074h] BYREF
  char s_1[88]; // [sp+1028h] [bp-58h] BYREF

  memset(s_1, 0, 0x40u);
  if ( n2 == 2 )                                // 处理命令行参数:若传入参数为2,使用自定义配置文件路径
    _var_fota_fotad.conf = a2[1];
  else
    _var_fota_fotad.conf = "/var/fota/fotad.conf";
  strncpy(s_1, _var_fota_fotad.conf, 0x3Fu);
  puts("FOTA client generic version 1.0");
  Log_Message_DF0C("main", 141, "FOTA daemon version:%s\n", "0.0.2");
  if ( chdir("/") < 0 )
  {
   
    Log_Message_DF0C("main", 176, "EXIT_FAILURE 3\n");
    exit(1);
  }
  close(0);
  Log_Message_DF0C("main", 186, "FOTA daemon version:%s\n", "0.0.2");
  memset(&s_, 0, 0x258u);
  if ( sub_E038(s_1) == -1 )
  {
   
    Log_Message_DF0C("main", 192, "FOTA daemon: no config file\n");
    exit(0);
  }
  if ( signal(10, handler) == -1 )
    Log_Message_DF0C("main", 197, "ERROR! set callback of SIGUSR1\n");
  if ( signal(15, sub_C620) == -1 )
    Log_Message_DF0C("main", 200, "ERROR! set callback of SIGUSR1\n");
  if ( access("/var/fota_user", 0) )
  {
   
    v6 = 1;
    dword_177B4 = 1;
  }
  else
  {
   
    dword_177B4 = 0;
    unlink("/var/fota_user");
    v6 = dword_177B4;
  }
  Log_Message_DF0C("main", 212, "conf_flag=%d\n", v6);
  __Unix________________ = qmi_usock_server_open("fotad.us", 0);//  创建Unix域套接字服务器,用于接收控制命令
  if ( __Unix________________ >= 0 )
  {
   
    while ( 1 )
    {
   
      memset(s, 0, sizeof(s));
      if ( qmi_usock_server_recv(__Unix________________, 1, s, 4124) > 0 )
      {
   
        Log_Message_DF0C("_fota_msg_handle", 42, "msg_id:%d, opt1:%d\n", s[2], s[3]);
        if ( s[2] == 208 && (n7 - 9) <= 1 && !::n2 )
        {
   
          if ( s[3] == 529 )
            ::n2 = 2;
          else
            ::n2 = s[3] == 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值