【智能设备-深入剖析路由器FOTA固件升级流程:从解包到逆向分析】此文章归类为:智能设备。
Firmware Over-The-Air(FOTA)技术是物联网(IoT)设备维护和管理的关键手段之一。随着智能设备的广泛部署,传统的手动固件升级方式已难以满足大规模、远程管理的需求。FOTA通过无线方式远程更新固件,使设备能够修复漏洞、优化性能、增强安全性并提供新功能,而无需用户手动干预或返回服务中心。其主要优势包括:
DWR-932是一款广泛应用的便携式4G LTE路由器,支持多种网络模式,主要用于移动网络共享。其FOTA升级机制是嵌入式系统中典型的无线固件升级方案,涵盖了:
逆向分析后绘制的FOTA更新流程图:
DWR-932的FOTA机制具有一定的安全设计,如固件校验和分区保护,但仍然存在潜在漏洞,值得深入研究。
本文的主要目标是:
最终,希望本文能为物联网设备FOTA机制的安全性研究提供借鉴,并为嵌入式安全研究者提供实用的分析思路和工具使用技巧。
FOTA的定义:FOTA(Firmware Over-The-Air),即固件无线升级,是一种通过无线通信网络远程更新嵌入式设备固件的软件技术,常用于物联网设备、智能家居设备、汽车电子等领域。
嵌入式系统固件一般包含以下几个部分:
常见固件文件格式:
Unix域套接字(Unix Domain Socket, UDS)是一种本地进程间通信(IPC)机制,类似于TCP/IP套接字,但仅限于本机通信。相比于管道和消息队列,Unix域套接字性能更高,适用于嵌入式系统中的进程交互。
在DWR-932路由器中,FOTA使用了Unix域套接字/var/usock/appmgr.us进行通信:
固件下载地址:e49K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6X3N6s2m8Q4x3X3g2V1L8r3W2F1K9#2)9J5k6h3c8W2i4K6u0r3k6s2N6J5i4K6u0r3k6s2N6J5i4K6u0V1z5e0x3J5i4K6u0r3j5i4u0U0K9r3W2$3k6g2)9J5c8X3c8J5K9i4k6W2M7W2)9#2k6Y4y4G2k6Y4c8%4j5i4u0W2i4K6u0r3c8q4N6d9i4K6u0V1z5e0x3J5i4K6g2X3k6Y4N6Q4y4h3k6J5k6i4k6T1i4K6g2X3x3U0l9J5k6i4g2Q4y4h3k6m8e0p5I4Q4y4h3k6E0N6h3I4@1K9g2)9#2k6U0t1H3x3e0f1H3x3e0p5&6i4K6u0W2P5X3W2H3
固件解压缩:PLC_1earn/1earn/Security/IOT/固件安全/实验/Dlink_DWR-932B路由器固件分析.md at master · dbshow/PLC_1earn
手动解压发现压缩包被加密了:
解压密码是:beUT9Z
由于下载的固件是可以被路由器下载并且解压更新的所以程序中一定会将压缩包的压缩密码进行硬编码,所以直接搜索程序中那个地方使用unzip进行解压的时候使用了密码参数就可以锁定了:
直接将prefota拖入IDA查找字符串就可以锁定!
直接使用交叉引用很快就会发现密码所来自的字段是model_name:
直接字符串搜索就可以锁定目标!
发现文件大部分都是yaffs直接安装工具进行解压:

解包出来非常多的文件系统,每个都需要查看,因为不同的文件系统都放置再不同的分区:
在逆向分析的过程中一开始只查看了2K-mdm-image-mdm9625.yaffs2这个文件系统导致文件不全,浪费了很多时间.
下面对这些以“2K-”开头的文件做个说明,重点解释它们在固件中的作用:
总结来说,所有以“2K-”开头的文件都是固件包的重要组成部分,分别负责启动、运行、恢复以及数据完整性校验。仔细看发现 pre-fota.sh 脚本在用户文件系统映像(2K-mdm9625-usr-image.usrfs.yaffs2)中,而在系统映像(2K-mdm-image-mdm9625.yaffs2)中调用该脚本,正体现了固件设计中将核心系统和用户数据分离管理的思想。
有fota相关知识可知,在加上init里面的shell脚本和字符串upgrade,可以锁定/sbin/fotad程序被用于FOTA固件更新!
start函数与_libc_start_main的调用逻辑。
这个函数是程序的入口点(start),负责初始化程序并调用 main 函数。
分析一下init初始化函数进行的操作:
初始化函数(sub_9840)与性能监控(gmon)的关系。
在程序启动时,根据是否启用性能分析(gmon)动态选择执行性能分析初始化或默认初始化代码。
这里的初始化函数是:loc_D94C
启动FOTA监控线程,用于固件升级状态的实时监控,若线程创建失败则记录错误日志。
实时监控FOTA固件升级状态,从 /proc/fota/info 文件中读取升级进度和版本号,并通过回调函数 Update_FOTA_Status_D74C 上报状态信息。
该函数是FOTA守护进程的主函数,负责初始化配置、监听控制命令,并根据接收到的命令执行固件升级操作。sub_E344:消息处理函数,负责解析命令和执行升级操作,需分析其实现以了解核心升级逻辑。
sub_E344 函数是FOTA(Firmware Over-The-Air,固件无线升级)更新过程中的核心状态管理函数,通过状态机机制(switch 语句)协调和管理固件升级的各个阶段。
继续逆向分析如何才能找到固件下载完成后如何对固件进行操作的函数,可以继续看下这个函数:
该函数的主要功能是记录并上报FOTA(固件无线升级)过程中的状态信息,具体包括工作状态、错误码、下载/上传进度等,并将这些信息写入状态文件后通过消息机制发送给应用管理器。其核心操作可分解为:
我们可以发现程序通过进程通信与其他程序进行交互来完成整个升级过程,可以根据/var/fota/fotad.status文件,或者appmgr.us来寻找目标,找到之后就可以开始逆向分析快进程交互管理模块appmgr:
直接将bin/appmgr拖入IDA中!
直接阅读这整个main函数的代码:
简化以后的伪代码:
我们需要重点关心的模块是模块3:可以去看看具体的伪代码:
这段代码是应用程序管理器(appmgr)的核心初始化部分,负责系统的启动、模块管理和运行准备。
我们可以根据变量去查看一下他具体管理了哪一些程序!
模块列表定义在.data.rel.ro:00082AC4至.data.rel.ro:00082B48,包括以下功能模块:
我们主要是分析mod_fota:固件在线升级模块的功能继续查看!
数据接收(recv)与消息处理(appmgr_ev_process)和modfota_msg_handle函数的FOTA命令处理逻辑。
发现关键部分:
根据前面逆向分析的结果我们可以知道网络数据传输被被绑定到了Unix 域套接字 fd 中:
Unix 域套接字(Unix Domain Socket)连接: 根据代码的上下文,在之前的代码部分,程序通过 socket 和 bind 创建了一个 Unix 域套接字,套接字路径为 /var/usock/appmgr.us。如果套接字没有被占用,它会被绑定并用于进程间通信(IPC)。
n27 = recv(fd, ptr, 0x101Cu, 16448); 这一行从 Unix 域套接字 fd 中接收最多 4140 字节的数据,并将接收到的数据存储到 ptr 中。数据来源于其他进程通过同一套接字发送的消息,用于进程间通信(IPC)。
这里又回到了fotad运行时候会将运行的数据传入appmgr.us文件中,通过该文件实现了进程通信!
现在找到了将appmgr程序发现他是该路由器的一个主控程序,并且找到了appmgr与fotad进程的跨进程通信的交互代码,由此可以继续分析出固件是如何更新的!
根据这个代码我们可以找到会被主动调用的fotad的主要功能:
发现会调用模块表中某一模块的第4个值:
该函数 modfota_msg_handle 主要处理与 FOTA(固件空中升级)相关的消息。根据接收到的消息 ID,它会根据不同的情况执行不同的操作,例如获取固件升级状态、执行固件升级、准备升级、获取磁盘空间等。它还与系统状态进行交互,记录日志,并向其他模块或进程发送回馈。
接下来就可以继续了!
参数解析与模式切换(prepare/start)和调用prefota执行升级准备与触发。
pre-fota.sh脚本的内容:
当脚本以 -a start 参数启动时,do_pre_fota_parser 函数通过 case 语句匹配 -a 选项,并将 fota_mode 设置为 2
调用 do_pre_fota_active 函数,根据 fota_mode=2 执行以下操作:
prefota --start的逆向分析和调用flash_erase和nandwrite完成固件刷写。
将prefota拖入IDA后可以很快得到需要的数据:
该代码是FOTA(固件无线更新)的主控制程序,根据命令行参数(prefota/fota和--prepare/--start)控制固件更新的预升级(Pre-FOTA)和主升级(FOTA)流程.
通过分析上述函数,可还原FOTA更新的核心流程:

更加详细的解析:
该函数 sub_9CAC 是 FOTA升级的核心执行模块,根据全局标志 dword_CB04 区分 预升级(Pre-FOTA) 和 主升级(FOTA) 阶段,完成固件文件的解压、哈希校验与分区写入操作,确保关键组件(如引导程序或恢复镜像)的安全更新。

该函数 sub_9BE0 是 FOTA升级中固件文件写入闪存(MTD设备)的核心模块,根据文件名匹配预定义的分区配置表(结构体),调用底层接口完成 闪存擦除与写入操作,并支持 多分区保护模式(如A/B分区冗余设计)和 日志记录,确保固件正确刷入目标硬件分区。

我们也可以直接进入去查看:
直接根据前面的偏移可以逆向分析出这个结构体的数据,使用idapython打印出来:

lcdd程序的事件队列与固件升级命令和数据结构(byte_CB40)的动态解析。
最后发现固件更新命令还调用了一个程序: system("lcdd fw-upgrade 1 &") 执行并启动固件升级。
继续寻找:
OK接下来就是继续的逆向分析了!
这部分的逆向也比较简单
继续往内部看:
该代码段是嵌入式系统中典型的“命令响应-事件驱动”设计,适用于路由器、IoT 设备等需固件升级的场景。
FOTA(Firmware Over-the-Air)固件升级流程主要涉及从固件下载到刷写的完整链路,整体可分为以下几个阶段:
整个 FOTA 升级过程中,多个核心组件相互协作,共同完成下载、验证、刷写及状态更新:
在进行FOTA(Firmware Over-the-Air)固件升级逆向分析时,以下参考资料可能对您有所帮助:
__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]);
}
}__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]);
}
}appmgr_msg_broadcast_inner(7, 0, 0);appmgr_msg_broadcast_inner(7, 0, 0);if (s[2] == 208) {
if (s[3] == 529) {
::n2 = 2;
}
}if (s[2] == 208) {
if (s[3] == 529) {
::n2 = 2;
}
}fcrackzip -u -v -b fixed.zipfcrackzip -u -v -b fixed.zip┌──(kali㉿kali)-[~/IOT/DWR-932]└─$ grep -r "model_name" ┌──(kali㉿kali)-[~/IOT/DWR-932]└─$ grep -r "model_name" ┌──(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/┌──(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/┌──(kali㉿kali)-[/mnt/hgfs/VMShare/IOT/DWR-932]└─$ ls02.02EU 2K-mdm-image-boot-mdm9625.img 2K-mdm-recovery-image-mdm9625.yaffs2 DWR-932_B1_02.02EU.zip mba.mbn root.hash tz.mbn2K-cksum.txt 2K-mdm-image-mdm9625.yaffs2 appsboot.mbn firmwalker.txt new rpm.mbn wdt.mbn2K-mdm9625-usr-image.usrfs.yaffs2 2K-mdm-recovery-image-boot-mdm9625.img bin fixed.zip qdsp6sw.mbn sbl1.mbn┌──(kali㉿kali)-[/mnt/hgfs/VMShare/IOT/DWR-932]└─$ ls02.02EU 2K-mdm-image-boot-mdm9625.img 2K-mdm-recovery-image-mdm9625.yaffs2 DWR-932_B1_02.02EU.zip mba.mbn root.hash tz.mbn2K-cksum.txt 2K-mdm-image-mdm9625.yaffs2 appsboot.mbn firmwalker.txt new rpm.mbn wdt.mbn2K-mdm9625-usr-image.usrfs.yaffs2 2K-mdm-recovery-image-boot-mdm9625.img bin fixed.zip qdsp6sw.mbn sbl1.mbn// 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();
}// 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();
}void *sub_9840()
{ void *result; // r0
result = &loc_D94C;
if ( &__gmon_start__ )
return _gmon_start__();
return result;
}void *sub_9840()
{ void *result; // r0
result = &loc_D94C;
if ( &__gmon_start__ )
return _gmon_start__();
return result;
}result = pthread_create(&newthread, 0, start_routine, Update_FOTA_Status_D74C);result = pthread_create(&newthread, 0, start_routine, Update_FOTA_Status_D74C);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] == 116;
}
}
sub_E344(); // 消息处理函数(如解析命令、执行升级)
if ( n7 == 7 ) // 退出条件:接收到特定信号(如n7=7)
{
qmi_usock_server_close("fotad.us", __Unix________________);
Log_Message_DF0C("main", 249, "End of fotad!\n");
exit(0);
}
}
}
Log_Message_DF0C("main", 219, "%s: uscok_create fail\n", "main");
return -1;
}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] == 116;
}
}
sub_E344(); // 消息处理函数(如解析命令、执行升级)
if ( n7 == 7 ) // 退出条件:接收到特定信号(如n7=7)
{
qmi_usock_server_close("fotad.us", __Unix________________);
Log_Message_DF0C("main", 249, "End of fotad!\n");
exit(0);
}
}
}
Log_Message_DF0C("main", 219, "%s: uscok_create fail\n", "main");
return -1;
}int sub_E344()
{ // 必要的状态变量声明
int n7; // 当前状态
int n7_0; // 错误代码
int n10; // 重试计数
int n10_0; // 下载重试计数
int n2; // 用户确认状态
char s_[128]; // FOTA状态结构体
char dest[128]; // 目标版本信息
char s[128]; // 下载URL或路径
char s_3[128]; // 文件路径
char s_0[80]; // 恢复下载信息
switch (n7)
{
case 0: // 初始化状态
// 功能:初始化FOTA进程并切换到空闲状态
Log_Message_DF0C("fota_state_process", 267, "FOTA_STATE_INITIAL\n");
n7 = 1; // 切换到空闲状态
update_fota_progress_status(&s_); // 更新进度状态
return n7;
case 1: // 空闲待命状态
// 功能:等待升级指令或检查固件更新
if (n49 == 49) // 检查是否收到升级指令
{
n7 = 4; // 切换到升级状态
Log_Message_DF0C("fota_state_process", 277, "state = FOTA_STATE_UPGRADING\n");
n7_0 = 0; // 错误代码清零
Log_Message_DF0C("fota_state_process", 279, "err_no = FOTA_ERR_NO_ERROR\n");
memset(&s__0, 0, 0x80u); // 清空恢复信息
update_fota_progress_status(&s_);
return n7;
}
else // 检查固件更新
{
... }
case 2: // 固件信息检查状态
... case 3: // 固件下载状态
... case 4: // 固件升级状态
// 功能:执行固件升级操作
Log_Message_DF0C("fota_state_process", 842, "FOTA_STATE_UPGRADING\n");
update_fota_percentage(1); // 更新进度为1%
Log_Message_DF0C("fota_state_process", 847, "FOTA_STATE_UPGRADING[%s]\n", name_0);
update_lcd_and_handle_file(302, 0, 0); // 更新LCD显示
n7 = 5; // 切换到完成状态
Log_Message_DF0C("fota_state_process", 897, "fota_update_process OK state = FOTA_STATE_FINISH\n");
update_fota_progress_status(&s_);
sleep(3); // 等待3秒
return n7;
case 5: // 完成状态
// 功能:升级完成后的处理
Log_Message_DF0C("fota_state_process", 909, "FOTA_STATE_FINISH\n");
if (s_ == 1) // 守护进程模式
{
n7 = 1; // 切换到空闲状态
Log_Message_DF0C("fota_state_process", 918, "FOTA_DAEMON_MODE state = FOTA_STATE_IDLE\n");
if (dword_17704 == 1) // 强制升级完成
{
time(&timer_); // 获取当前时间
update_shared_memory_status(&s_); // 更新共享内存状态
}
}
else // 单次模式
{
n7 = 7; // 切换到退出状态
Log_Message_DF0C("fota_state_process", 931, "FOTA_SINGLE_MODE state = FOTA_STATE_EXIT\n");
}
update_fota_progress_status(&s_);
return n7;
case 6: // 终止状态
... case 8: // 等待网络状态
... case 9: // 配置下载状态
.. case 10: // 配置升级状态
... default:
return n7; // 默认返回当前状态
}
}int sub_E344()
{ // 必要的状态变量声明
int n7; // 当前状态
int n7_0; // 错误代码
int n10; // 重试计数
int n10_0; // 下载重试计数
int n2; // 用户确认状态
char s_[128]; // FOTA状态结构体
char dest[128]; // 目标版本信息
char s[128]; // 下载URL或路径
char s_3[128]; // 文件路径
char s_0[80]; // 恢复下载信息
switch (n7)
{
case 0: // 初始化状态
// 功能:初始化FOTA进程并切换到空闲状态
Log_Message_DF0C("fota_state_process", 267, "FOTA_STATE_INITIAL\n");
n7 = 1; // 切换到空闲状态
update_fota_progress_status(&s_); // 更新进度状态
return n7;
case 1: // 空闲待命状态
// 功能:等待升级指令或检查固件更新
if (n49 == 49) // 检查是否收到升级指令
{
n7 = 4; // 切换到升级状态
Log_Message_DF0C("fota_state_process", 277, "state = FOTA_STATE_UPGRADING\n");
n7_0 = 0; // 错误代码清零
Log_Message_DF0C("fota_state_process", 279, "err_no = FOTA_ERR_NO_ERROR\n");
memset(&s__0, 0, 0x80u); // 清空恢复信息
update_fota_progress_status(&s_);
return n7;
}
else // 检查固件更新
{
... }
case 2: // 固件信息检查状态
... case 3: // 固件下载状态
... case 4: // 固件升级状态
// 功能:执行固件升级操作
Log_Message_DF0C("fota_state_process", 842, "FOTA_STATE_UPGRADING\n");
update_fota_percentage(1); // 更新进度为1%
Log_Message_DF0C("fota_state_process", 847, "FOTA_STATE_UPGRADING[%s]\n", name_0);
update_lcd_and_handle_file(302, 0, 0); // 更新LCD显示
n7 = 5; // 切换到完成状态
Log_Message_DF0C("fota_state_process", 897, "fota_update_process OK state = FOTA_STATE_FINISH\n");
update_fota_progress_status(&s_);
sleep(3); // 等待3秒
return n7;
case 5: // 完成状态
// 功能:升级完成后的处理
Log_Message_DF0C("fota_state_process", 909, "FOTA_STATE_FINISH\n");
if (s_ == 1) // 守护进程模式
{
n7 = 1; // 切换到空闲状态
Log_Message_DF0C("fota_state_process", 918, "FOTA_DAEMON_MODE state = FOTA_STATE_IDLE\n");
if (dword_17704 == 1) // 强制升级完成
{
time(&timer_); // 获取当前时间
update_shared_memory_status(&s_); // 更新共享内存状态
}
}
else // 单次模式
{
n7 = 7; // 切换到退出状态
Log_Message_DF0C("fota_state_process", 931, "FOTA_SINGLE_MODE state = FOTA_STATE_EXIT\n");
}
update_fota_progress_status(&s_);
return n7;
case 6: // 终止状态
... case 8: // 等待网络状态
... case 9: // 配置下载状态
.. case 10: // 配置升级状态
... default:
return n7; // 默认返回当前状态
}
}int __fastcall update_fota_progress_status(const char *s_1)
{... update_shared_memory_status(s_1);
s_3 = fopen("/var/fota/fotad.status", "wb+");
s_2 = s_3;
if ( !s_3 )
return 0;
sub_FA64(s_3, 1);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_WORK_STATUS", *s_1);
n = strlen(s);
fwrite(s, 1u, n, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_STATE", *(s_1 + 2));
n_1 = strlen(s);
fwrite(s, 1u, n_1, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_ERROR", *(s_1 + 107));
n_2 = strlen(s);
fwrite(s, 1u, n_2, s_2);
memset(s, 0, sizeof(s));
n_3 = strlen(off_17168[*(s_1 + 107)]); // "no error."
memcpy((s_1 + 432), off_17168[*(s_1 + 107)], n_3);// "no error."
sprintf(s, "%s=%s\n", "FOTA_PROCESS_ERROR_MSG", s_1 + 432);
n_4 = strlen(s);
fwrite(s, 1u, n_4, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_DW_PERCENT", *(s_1 + 141));
n_5 = strlen(s);
fwrite(s, 1u, n_5, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_UP_PERCENT", *(s_1 + 142));
n_6 = strlen(s);
fwrite(s, 1u, n_6, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_FILE_TYPE", *(s_1 + 145));
... memcpy(dest, src, sizeof(dest));
qmi_usock_msg_sendto(0, 76, v23, 92, 196, "appmgr.us");
return 1;
}int __fastcall update_fota_progress_status(const char *s_1)
{... update_shared_memory_status(s_1);
s_3 = fopen("/var/fota/fotad.status", "wb+");
s_2 = s_3;
if ( !s_3 )
return 0;
sub_FA64(s_3, 1);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_WORK_STATUS", *s_1);
n = strlen(s);
fwrite(s, 1u, n, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_STATE", *(s_1 + 2));
n_1 = strlen(s);
fwrite(s, 1u, n_1, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_ERROR", *(s_1 + 107));
n_2 = strlen(s);
fwrite(s, 1u, n_2, s_2);
memset(s, 0, sizeof(s));
n_3 = strlen(off_17168[*(s_1 + 107)]); // "no error."
memcpy((s_1 + 432), off_17168[*(s_1 + 107)], n_3);// "no error."
sprintf(s, "%s=%s\n", "FOTA_PROCESS_ERROR_MSG", s_1 + 432);
n_4 = strlen(s);
fwrite(s, 1u, n_4, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_DW_PERCENT", *(s_1 + 141));
n_5 = strlen(s);
fwrite(s, 1u, n_5, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_PROCESS_UP_PERCENT", *(s_1 + 142));
n_6 = strlen(s);
fwrite(s, 1u, n_6, s_2);
memset(s, 0, sizeof(s));
sprintf(s, "%s=%i\n", "FOTA_FILE_TYPE", *(s_1 + 145));
... memcpy(dest, src, sizeof(dest));
qmi_usock_msg_sendto(0, 76, v23, 92, 196, "appmgr.us");
return 1;
}int __fastcall main(int argc, const char **argv, const char **envp)
{ // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
sig = 0; // 模块1:初始化和清理
if ( !access("/etc/rc0.d/K80qmuxd", 0) && !access("/etc/qdt_rc0clean.sh", 0) )
{
am_comprintf(1, 0, 0, "APPMGR: RC_CLEAN!!!\n");
system("chmod +x /etc/qdt_rc0clean.sh");
system("/etc/qdt_rc0clean.sh");
}
.... pthread_mutex_init(&mutex_, 0); // 初始化互斥锁
if ( time(0) <= 1293839999 ) // 检查系统时间是否正确,若不正确则重置时间
{
am_comprintf(1, 0, 0, "APPMGR: TimeSkew...DoTimeReset!!!!\n");
system("date -s 2012.01.01-00:00:00");
}
s = fopen("/proc/sys/net/unix/max_dgram_qlen", "wt");
if ( s )
{
fwrite("20", 1u, 2u, s);
fclose(s);
}
// ;0x2 -> SOCK_DGRAM //模块2:套接字和信号处理
fd = socket(1, 2, 0);
if ( fd <= 0 )
{
gAM_usock_fd = -1;
}
else
{
addr[0].sa_family = 1;
sprintf(addr[0].sa_data, "%s/%s", "/var/usock", "appmgr.us");// 创建 unix 域套接字
if ( bind(fd, addr, 0x6Eu) >= 0 )
goto LABEL_20;
if ( connect(fd, addr, 0x6Eu) )
{
if ( unlink(addr[0].sa_data) < 0 )
{
am_comprintf(1, "appmgr_uscok_create", 1628, "Error removing old socket\n");
gAM_usock_fd = -3;
}
else
{
if ( bind(fd, addr, 0x6Eu) >= 0 )
{
... am_comprintf(1, 0, 0, "UDPLOG: init(%d)...\n", fd);
if ( fd < 0 )
{
v12 = am_comprintf(1, "appmgr_udplog_init", 2026, "UDPLOG: sockopen fail\n");
}
else
{
... }
//模块3:模块初始化和启动
appmgr_shm_init(v12); // 初始化共享内存
am_comprintf(1, 0, 0, "APPMGR: Init...\n");
am_comprintf(1, 0, 0, "APPMGR: AMAPI init...\n");
v13 = uiIPC_lib_init("appmgr", 0, 15);// 初始化IPC库
v14 = appmgr_proc_initialize(v13);
cfgs_init(v14);
v15 = &dword_82AC0;
while ( 1 )
{
v17 = v15[1];
++v15;
v16 = v17;
if ( !v17 )
break;
if ( *(v16 + 4) )
{
am_comprintf(1, 0, 0, "APPMGR: init mod[%d]\n", *v16);
(*(v16 + 4))(0);//调用模块初始化函数
}
}
v18 = am_comprintf(1, 0, 0, "APPMGR: Daemon Startup...\n");
appmgr_daemon_startup(v18); // 启动守护进程
am_comprintf(1, 0, 0, "APPMGR: Startup...\n");
v19 = &dword_82AC0;
while ( 1 )
{
v21 = v19[1];
++v19;
v20 = v21;
if ( !v21 )
break;
if ( *(v20 + 8) )
{
am_comprintf(1, 0, 0, "APPMGR: startup mod[%d]\n", *v20);
(*(v20 + 8))(0);//调用模块启动函数
}
}
am_comprintf(1, 0, 0, "APPMGR: Ready...\n");
appmgr_msg_broadcast_inner(7, 0, 0);
am_comprintf(1, 0, 0, "APPMGR: WorkingLoop...\n");// 开始主工作循环
dword_7E180 = 0;
appmgr_msg_broadcast_inner(79, 0, 0);
//模块4:主工作循环
ptr = malloc(0x2000u);
errnum = *_errno_location();
v38 = strerror(errnum);
am_comprintf(1, 0, 0, "APPMGR: wait error(%d,%s)\n", errnum, v38);
}
else
{
sub_116F8();
dword_8413C = time(0);
}
....}int __fastcall main(int argc, const char **argv, const char **envp)
{ // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
sig = 0; // 模块1:初始化和清理
if ( !access("/etc/rc0.d/K80qmuxd", 0) && !access("/etc/qdt_rc0clean.sh", 0) )
{
am_comprintf(1, 0, 0, "APPMGR: RC_CLEAN!!!\n");
system("chmod +x /etc/qdt_rc0clean.sh");
system("/etc/qdt_rc0clean.sh");
}
.... pthread_mutex_init(&mutex_, 0); // 初始化互斥锁
if ( time(0) <= 1293839999 ) // 检查系统时间是否正确,若不正确则重置时间
{
am_comprintf(1, 0, 0, "APPMGR: TimeSkew...DoTimeReset!!!!\n");
system("date -s 2012.01.01-00:00:00");
}
s = fopen("/proc/sys/net/unix/max_dgram_qlen", "wt");
if ( s )
{
fwrite("20", 1u, 2u, s);
fclose(s);
}
// ;0x2 -> SOCK_DGRAM //模块2:套接字和信号处理
fd = socket(1, 2, 0);
if ( fd <= 0 )
{
gAM_usock_fd = -1;
}
else
{
addr[0].sa_family = 1;
sprintf(addr[0].sa_data, "%s/%s", "/var/usock", "appmgr.us");// 创建 unix 域套接字
if ( bind(fd, addr, 0x6Eu) >= 0 )
goto LABEL_20;
if ( connect(fd, addr, 0x6Eu) )
{
if ( unlink(addr[0].sa_data) < 0 )
{
am_comprintf(1, "appmgr_uscok_create", 1628, "Error removing old socket\n");
gAM_usock_fd = -3;
}
else
{
if ( bind(fd, addr, 0x6Eu) >= 0 )
{
... am_comprintf(1, 0, 0, "UDPLOG: init(%d)...\n", fd);
if ( fd < 0 )
{
v12 = am_comprintf(1, "appmgr_udplog_init", 2026, "UDPLOG: sockopen fail\n");
}
else
{
... }
//模块3:模块初始化和启动
appmgr_shm_init(v12); // 初始化共享内存
am_comprintf(1, 0, 0, "APPMGR: Init...\n");
am_comprintf(1, 0, 0, "APPMGR: AMAPI init...\n");
v13 = uiIPC_lib_init("appmgr", 0, 15);// 初始化IPC库
v14 = appmgr_proc_initialize(v13);
cfgs_init(v14);
v15 = &dword_82AC0;
while ( 1 )
{
v17 = v15[1];
++v15;
v16 = v17;
if ( !v17 )
break;
if ( *(v16 + 4) )
{
am_comprintf(1, 0, 0, "APPMGR: init mod[%d]\n", *v16);
(*(v16 + 4))(0);//调用模块初始化函数
}
}
v18 = am_comprintf(1, 0, 0, "APPMGR: Daemon Startup...\n");
appmgr_daemon_startup(v18); // 启动守护进程
am_comprintf(1, 0, 0, "APPMGR: Startup...\n");
v19 = &dword_82AC0;
while ( 1 )
{
v21 = v19[1];
++v19;
v20 = v21;
if ( !v21 )
break;
if ( *(v20 + 8) )
{
am_comprintf(1, 0, 0, "APPMGR: startup mod[%d]\n", *v20);
(*(v20 + 8))(0);//调用模块启动函数
}
}
am_comprintf(1, 0, 0, "APPMGR: Ready...\n");
appmgr_msg_broadcast_inner(7, 0, 0);
am_comprintf(1, 0, 0, "APPMGR: WorkingLoop...\n");// 开始主工作循环
dword_7E180 = 0;
appmgr_msg_broadcast_inner(79, 0, 0);
//模块4:主工作循环
ptr = malloc(0x2000u);
errnum = *_errno_location();
v38 = strerror(errnum);
am_comprintf(1, 0, 0, "APPMGR: wait error(%d,%s)\n", errnum, v38);
}
else
{
sub_116F8();
dword_8413C = time(0);
}
....} ptr = ptr;
n27 = recv(fd, ptr, 0x101Cu, 16448);// 从文件描述符 fd 中接收数据,数据存储到 ptr 指向的内存中,最多接收 0x101C 字节(4140字节),返回实际接收的字节数。
... am_comprintf(2, "main", 959, "APPMGR: MSG-Proc[%hu->%hu] Start[%u]\n", v43, v44, v45);
v46 = ptr[2];
if ( ((v46 - 193) << 16) <= 0x10000 ) // 代码的核心部分是处理传入的消息。首先,它通过 v46 判断消息的类型或ID
{
appmgr_ev_process(ptr); // 然后决定是直接调用事件处理函数 appmgr_ev_process(ptr) 处理该消息
}
else
{
v47 = &dword_82AC0; // 还是通过查找一个函数表来动态选择消息处理函数。
do
{
v49 = v47[1];
++v47;
v48 = v49;
if ( !v49 )
{
am_comprintf(
1,
"appmgr_msg_process",
1437,
"APPMGR: dst(%hd) msg_handle(id=%hu) not found\n",
ptr[1],
v46);
goto LABEL_108;
}
}
while ( !v48[3] || *v48 != ptr[1] );
appmgr_flog("MsgHdl:[%hu->%hu] Op1[%hu] In\n", *ptr, ptr[1], ptr[3]);
(v48[3])(ptr, ptr + 14, ptr[5]); // 执行找到的消息处理函数,传入指针和消息的相关数据。
appmgr_flog("MsgHdl:[%hu->%hu] Op1[%hu] Out\n", *ptr, ptr[1], ptr[3]);
ptr = ptr;
n27 = recv(fd, ptr, 0x101Cu, 16448);// 从文件描述符 fd 中接收数据,数据存储到 ptr 指向的内存中,最多接收 0x101C 字节(4140字节),返回实际接收的字节数。
... am_comprintf(2, "main", 959, "APPMGR: MSG-Proc[%hu->%hu] Start[%u]\n", v43, v44, v45);
v46 = ptr[2];
if ( ((v46 - 193) << 16) <= 0x10000 ) // 代码的核心部分是处理传入的消息。首先,它通过 v46 判断消息的类型或ID
{
appmgr_ev_process(ptr); // 然后决定是直接调用事件处理函数 appmgr_ev_process(ptr) 处理该消息
}
else
{
v47 = &dword_82AC0; // 还是通过查找一个函数表来动态选择消息处理函数。
do
{
v49 = v47[1];
++v47;
v48 = v49;
if ( !v49 )
{
am_comprintf(
1,
"appmgr_msg_process",
1437,
"APPMGR: dst(%hd) msg_handle(id=%hu) not found\n",
ptr[1],
v46);
goto LABEL_108;
}
}
while ( !v48[3] || *v48 != ptr[1] );
appmgr_flog("MsgHdl:[%hu->%hu] Op1[%hu] In\n", *ptr, ptr[1], ptr[3]);
(v48[3])(ptr, ptr + 14, ptr[5]); // 执行找到的消息处理函数,传入指针和消息的相关数据。
appmgr_flog("MsgHdl:[%hu->%hu] Op1[%hu] Out\n", *ptr, ptr[1], ptr[3]);
fd = socket(1, 2, 0); // 创建套接字sprintf(addr[0].sa_data, "%s/%s", "/var/usock", "appmgr.us"); // 设置套接字地址bind(fd, addr, 0x6Eu); // 绑定套接字到指定的路径fd = socket(1, 2, 0); // 创建套接字sprintf(addr[0].sa_data, "%s/%s", "/var/usock", "appmgr.us"); // 设置套接字地址bind(fd, addr, 0x6Eu); // 绑定套接字到指定的路径(v48[3])(ptr, ptr + 14, ptr[5]); // 执行找到的消息处理函数,传入指针和消息的相关数据。(v48[3])(ptr, ptr + 14, ptr[5]); // 执行找到的消息处理函数,传入指针和消息的相关数据。#!/bin/sh # 指定使用 `/bin/sh` 作为解释器# 初始化 FOTA(Firmware Over-The-Air,固件空中升级)模式和结果变量fota_mode=0 # FOTA 模式,默认为 0
fota_result=0 # FOTA 结果,默认为 0
# 解析 FOTA 相关的命令行参数do_pre_fota_parser () { while [ "$1" != "" ]; do # 遍历所有传入的参数
case "$1" in
-a) # 处理 `-a` 选项
shift # 移动到下一个参数
if [ "$1" == "prepare" ]; then # `-a prepare` 表示准备 FOTA
fota_mode=1
echo "fota prepare"
shift # 跳过当前参数
elif [ "$1" == "start" ]; then # `-a start` 表示开始 FOTA
fota_mode=2
echo "fota start"
shift # 跳过当前参数
fi
;;
-e) # 处理 `-e` 选项,执行额外命令
shift
echo "$@ before pre-fota... " # 打印执行的命令
$@ # 运行传入的命令
;;
*) # 忽略其他参数
shift
;;
esac
done
}# 执行 FOTA 相关操作do_pre_fota_active () { if [ $fota_mode -eq 1 ]; then # 如果 FOTA 模式为 1(prepare)
prefota --prepare # 执行 `prefota` 命令,进行 FOTA 预处理
# 检查 `/cache/fota_result` 是否包含 "SUCCESS"
fota_result=`cat /cache/fota_result | grep "SUCCESS" | wc -l`
if [ $fota_result -ge 1 ]; then # 如果成功标志存在
echo "prefota done"
appmgr_cli cmd:fota-dl_done # 通知应用管理器 FOTA 下载完成
else
echo "prefota fail"
appmgr_cli cmd:fota-dl_fail # 通知应用管理器 FOTA 下载失败
fi
elif [ $fota_mode -eq 2 ]; then # 如果 FOTA 模式为 2(start)
prefota --start # 启动 FOTA 更新
else
prefota # 默认执行 `prefota`
fi
}# 解析传入的参数do_pre_fota_parser $@# 执行 FOTA 相关操作do_pre_fota_active#!/bin/sh # 指定使用 `/bin/sh` 作为解释器# 初始化 FOTA(Firmware Over-The-Air,固件空中升级)模式和结果变量fota_mode=0 # FOTA 模式,默认为 0
fota_result=0 # FOTA 结果,默认为 0
# 解析 FOTA 相关的命令行参数do_pre_fota_parser () { while [ "$1" != "" ]; do # 遍历所有传入的参数
case "$1" in
-a) # 处理 `-a` 选项
shift # 移动到下一个参数
if [ "$1" == "prepare" ]; then # `-a prepare` 表示准备 FOTA
fota_mode=1
echo "fota prepare"
shift # 跳过当前参数
elif [ "$1" == "start" ]; then # `-a start` 表示开始 FOTA
fota_mode=2
echo "fota start"
shift # 跳过当前参数
fi
;;
-e) # 处理 `-e` 选项,执行额外命令
shift
echo "$@ before pre-fota... " # 打印执行的命令
$@ # 运行传入的命令
;;
*) # 忽略其他参数
shift
更多【智能设备-深入剖析路由器FOTA固件升级流程:从解包到逆向分析】相关视频教程:www.yxfzedu.com