ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VxWorks逆向实战:工控安全中的固件分析四层穿透法

VxWorks逆向实战:工控安全中的固件分析四层穿透法 1. 这不是教你怎么“黑”设备而是带你真正看懂工控系统的心跳VxWorks——这三个字母在工控安全圈里几乎等同于“高危但沉默的动脉”。它不像Windows那样天天弹窗、打补丁、被勒索软件盯上它常年运行在电厂DCS控制柜里、地铁信号继电器背后、航天器遥测终端内部7×24小时不重启不联网不声不响。可一旦出问题不是蓝屏重装而是阀门失控、列车错停、卫星姿态失锁。我第一次接触VxWorks逆向是在某火电厂一次例行渗透测试中——目标是一台运行着VxWorks 6.9的GE Mark VIe控制器。扫描显示它只开放了UDP端口17185TFTP和TCP端口23Telnet没有Web界面没有SSH连ping都禁了。但当我们用自定义TFTP客户端发一个超长文件名请求时设备竟返回了一段带符号表的错误日志片段。那一刻我意识到它不是“没漏洞”而是把漏洞藏在了你根本不会去翻的底层内存布局、任务调度逻辑和BSP驱动耦合关系里。这就是“逆向角度看VxWorks”的真实起点它不是教你用IDA Pro点开一个elf文件然后找main函数而是训练你像一名嵌入式系统老兵那样思考——当CPU上电后第一条指令从哪里取bootrom如何加载imagetaskSpawn()创建的任务栈空间怎么分配中断向量表为什么必须硬编码在0x00000000这些不是理论题是每次固件提取失败、符号剥离后无法定位关键函数、或者patch后系统直接hang住时你唯一能抓住的救命绳。本文聚焦的正是这条绳子的材质、打结方式和承重极限。关键词工控安全、Vxworks、逆向不是泛泛而谈的标签而是三个相互咬合的齿轮工控安全定义了战场物理过程不可逆、响应延迟致命VxWorks定义了对手实时微内核、无MMU/有MMU变种、强BSP依赖逆向则是你唯一能不用拆机就能摸清对方战术布防的侦察手段。适合两类人一是已有嵌入式开发经验、想切入工控安全领域的工程师二是已做过Web/APP渗透、但面对PLC或RTU就束手无策的安全研究员。别指望读完就能拿下西门子S7-1500但你能准确判断手头这台运行VxWorks 5.5的ABB ACS800变频器其串口协议解析模块是否在kernel space硬编码了密码校验逻辑——这才是入门真正的门槛。2. 为什么非得逆向VxWorks绕不开的三大现实堵点2.1 厂商文档的“薛定谔式存在”工控厂商对VxWorks的使用普遍遵循“黑盒原则”他们采购Wind River授权定制BSP板级支持包编译进专用硬件然后把源码、符号表、调试接口全部焊死。你拿到的固件镜像99%是stripped的ROMFScompressed kernel image组合。某次我为一家水厂做评估拿到的施耐德Modicon M580固件解压后只有两个文件vxWorks1.2MBELF格式但无符号和romfs.img3.8MB只读文件系统。厂商提供的《用户手册》里关于通信协议的部分只有“支持Modbus TCP”六个字《维护指南》里提到“可通过串口升级”但没写波特率、握手方式、升级命令序列。当我用逻辑分析仪抓到串口数据流发现其自定义协议帧头包含一个校验字段而该字段算法在vxWorks二进制里——此时文档失效逆向成为唯一路径。这不是厂商故意隐瞒而是工业场景下“稳定压倒一切”的必然结果公开详细协议等于暴露攻击面而他们认为“没人会逆向我的PLC”。2.2 实时性约束下的脆弱平衡VxWorks的实时性Real-Time不是营销话术是硬性指标。它的任务调度基于优先级抢占Priority-Based Preemptive Scheduling中断延迟要求微秒级。这意味着任何安全机制都必须服从这一铁律。比如你想在socket recv()调用前插入流量检测逻辑但VxWorks默认的网络栈windNet是直接映射到硬件DMA缓冲区的中间没有Linux那样的netfilter钩子。强行hook会导致任务切换延迟超标轻则通信丢包重则控制指令超时失效。我曾在一个风电SCADA项目中尝试用动态插桩dynamic instrumentation监控TCP连接建立结果导致变桨控制器响应延迟从8ms飙升至42ms触发了机组安全链急停。事后逆向发现其TCP三次握手处理代码被编译进了inetLib.o模块并与中断服务程序共享同一段高速缓存cache line。这解释了为什么简单patch会引发连锁故障——逆向在这里不是为了找漏洞而是为了理解“哪些地方动不得”。2.3 BSP与内核版本的混沌耦合VxWorks版本号如6.9, 7.0只是冰山一角。真正决定行为的是BSP版本、编译器版本Diab C vs GNU GCC、甚至目标CPU架构的勘误表Errata补丁。同一份VxWorks 6.9源码在PowerPC MPC8548和ARM Cortex-A9上编译出的二进制其内存布局、异常处理流程、甚至系统调用号sysCall number都可能不同。更麻烦的是厂商常基于旧版内核打私有补丁却不更新版本号。我们曾分析一台某国产DCS控制器其vxWorks文件头显示为VxWorks 6.5但通过逆向sysClkConnect()函数发现其时钟中断处理逻辑包含了VxWorks 6.8才引入的tick-less模式优化代码。这种“版本漂移”让通用exploit完全失效也使得基于CVE数据库的扫描形同虚设。逆向在此的作用是建立属于你自己的“指纹库”通过识别BSP初始化代码中的芯片ID读取序列、DDR控制器配置寄存器写入模式、甚至Flash芯片型号检测字符串来锚定真实环境。提示不要迷信IDA Pro的自动分析。VxWorks的链接脚本linker script常将.text段分散到多个地址区间如0x100000用于boot code0x200000用于kernel0x300000用于applicationIDA默认只加载第一个段。必须手动解析ELF Program Header用readelf -l vxWorks确认所有LOAD段并在IDA中依次File → Load file → Binary file导入。3. 逆向VxWorks的四层穿透法从固件提取到逻辑还原3.1 第一层固件获取与结构解构物理层VxWorks固件通常以两种形态存在独立镜像文件常见于SD卡、CF卡或eMMC存储文件名为vxWorks、image.bin或firmware.bin。需先判断是否加密。典型特征文件开头无ELF magic0x7f 0x45 0x4c 0x46但存在重复的4字节校验值如每512字节末尾4字节为CRC32。此时需寻找硬件JTAG/SWD调试接口或利用Bootloader的UART命令如printenv查看启动参数获取解密密钥。集成式固件存在于NOR/NAND Flash中需用编程器如Xeltek SuperPro读取。难点在于Flash布局VxWorks常将bootrom、kernel、romfs、config data分块存储。例如某研华工控机其2MB Flash布局为0x000000-0x07FFFFbootrom0x080000-0x1FFFFFkernelromfs。此时需用binwalk -e firmware.bin初步扫描再结合strings搜索关键字如VxWorks、bootrom、romfs定位各段起始偏移。实操案例一台运行VxWorks 5.5的霍尼韦尔Experion PKS控制器固件为压缩的vxWorks.z。用file vxWorks.z识别为gzip格式解压后得到ELF文件。但readelf -h显示其Type: EXEC (Executable file)而非DYN (Shared object file)说明它是位置无关代码PIC但已重定位。关键线索来自readelf -S.romfs段类型为PROGBITS但sh_addr为0表明它被静态链接进kernel image需用dd从指定偏移提取。通过strings vxWorks | grep -A5 romfs找到提示字符串romfs start at 0x2a0000执行dd ifvxWorks ofromfs.img bs1 skip2752512 count524288成功提取romfs镜像再用mount -t romfs -o loop romfs.img /mnt/romfs挂载获得完整文件系统。3.2 第二层符号恢复与函数定位链接层VxWorks默认strip掉所有符号但留下三类关键线索字符串常量strings vxWorks | grep -E (tcp|udp|modbus|dnp3|iec104)可快速定位协议处理模块。函数指针表VxWorks的系统调用表sysCallTable是全局数组每个元素为函数指针。在IDA中搜索0x00000000或0xffffffff连续出现的4字节序列32位平台再结合sysCallTable字符串定位。例如VxWorks 6.9中sysCallTable位于.data段起始地址可通过nm vxWorks | grep sysCallTable若未strip或反向追踪intConnect()调用找到。BSP初始化代码sysInit()函数是入口其汇编代码必包含CPU初始化指令如PowerPC的mtspr 0x3b0, r3设置MSR寄存器。通过识别这些架构特有指令可准确定位kernel起始位置。工具链选择Ghidra对VxWorks支持优于IDA尤其擅长解析自定义section和重定位信息。启用VxWorksLoader插件可自动识别BSP段。Radare2命令行友好r2 -A vxWorks自动分析后用aaaanalyze allafllist functions快速生成函数列表。对无符号二进制/ad sym.*搜索符号相关字符串效果显著。自定义脚本编写Python脚本解析ELF的.rela.dyn重定位表提取所有外部函数引用如printf,strcpy这些是逆向分析的锚点。注意VxWorks 6.x及以后版本大量使用C虚函数表vtable是重要线索。在IDA中搜索dw ?DWORD后跟连续的函数指针再结合类名字符串如TcpSocket可还原类继承关系。我曾通过vtable定位到某SCADA系统的认证模块其checkPassword()虚函数被重写实际逻辑在派生类中实现。3.3 第三层内存布局与任务分析运行时层VxWorks的内存模型是逆向核心。关键区域包括Kernel Space0x00000000-0x0fffffff典型32位布局包含中断向量表、sysCallTable、任务控制块TCB池。User Space0x10000000起每个任务有独立栈默认4KB-64KB和堆heap。ROM/RAM区分.text段在ROM.data/.bss在RAMmalloc分配的内存来自memPartLib管理的内存分区。实操技巧TCB定位任务控制块Task Control Block包含任务状态、栈指针、优先级等。VxWorks中所有TCB存于全局数组taskTable其地址可通过taskShow(0,0)命令在shell中获取若开启。逆向时搜索taskTable字符串或追踪taskSpawn()的参数传递可找到TCB结构体定义。栈回溯当系统panic时VxWorks打印的backtrace是十六进制地址。需用addr2line -e vxWorks -f -C address将地址转为函数名。但前提是编译时保留debug info-g否则需手动匹配函数边界。我习惯在IDA中导出Functions列表为CSV用Excel按地址排序建立地址-函数名映射表。Hook点选择安全监控首选sockSend()/sockRecv()因其参数明确socket fd, buffer, len。但需注意VxWorks网络栈有两层——BSD socket层和底层windNet层。sockSend()在BSD层而ifOutput()在windNet层。后者更底层但参数更复杂含mBlk链表指针。3.4 第四层协议解析与逻辑还原应用层这是逆向的终极目标理解设备如何与外界交互。以Modbus TCP为例定位点mbTcpServerTask()函数常见名或搜索字符串502Modbus TCP默认端口。关键结构Modbus PDUProtocol Data Unit解析逻辑。VxWorks实现中PDU通常被复制到固定缓冲区如pMbpdu再用switch(pdu[0])分发。逆向重点是pdu[0]功能码后的地址/寄存器数量计算逻辑。安全逻辑检查是否有访问控制。常见模式在mbTcpProcessRequest()中调用mbAccessCheck()验证IP白名单该函数可能读取/etc/mb_acl.conf若romfs中存在或硬编码IP数组。我们曾发现某厂商将白名单IP哈希后存于.rodata段逆向出哈希算法CRC16后成功构造合法请求。工具辅助Wireshark 自定义Dissector将逆向出的协议结构如字段长度、字节序写成Lua dissector实时解析抓包数据。比纯逆向更直观验证逻辑。QEMU VxWorks模拟虽不能完美模拟BSP但可加载kernel image在vxsim中运行用GDB attach调试。需修改config.h启用INCLUDE_GDB_SERVER。4. 实操全流程从一台废弃的VxWorks 5.5路由器开始4.1 环境准备与固件提取目标设备某品牌工业无线路由器已停产标称VxWorks 5.5。步骤拆机找到Flash芯片Winbond W25Q32BV用CH341A编程器读取2MB数据保存为flash.bin。binwalk flash.bin显示DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 uImage header, header size: 64 bytes, header CRC: 0x1A2B3C4D, created: 2018-03-15 08:22:33, image size: 1048576, Data Address: 0x100000, Entry Point: 0x100000, OS: Linux, CPU: ARM, Image Type: Kernel Image, Compression Type: gzip, Image Name: VxWorks Kernel 1048576 0x100000 gzip compressed data, has original file name: vxWorks, from Unix, last modified: 2018-03-15 08:22:33关键发现uImage头指向0x100000处的gzip数据且原始文件名为vxWorks。dd ifflash.bin ofvxWorks.gz bs1 skip1048576提取gzip部分gunzip vxWorks.gz得到vxWorks。file vxWorks确认为ELF 32-bit LSB executable, ARM, version 1 (SYSV)。实操心得很多工控设备使用uImage封装但bootloader可能忽略header直接跳转。因此即使binwalk没发现ELF magic也要尝试dd提取整个Flash用strings搜索VxWorks或bootrom定位kernel起始。4.2 静态分析符号恢复与关键函数定位工具Ghidra 10.3 VxWorksLoader插件。步骤新建项目导入vxWorks选择ARM Little EndianArchitecture为ARM:LE:32:v8。启用VxWorksLoader自动识别.text、.data、.bss段并标记sysCallTable地址0x100a00。Search → For Strings输入telnet找到telnetTask()函数地址0x10a5c0。双击进入看到void telnetTask(int unused) { int sock; struct sockaddr_in addr; // ... socket() bind() listen() while(1) { sock accept(...); taskSpawn(telnetShell, 100, 0, 0x10000, telnetShell, sock, 0,0,0,0,0,0,0,0,0); } }关键发现telnetShell是新任务入口参数sock传入。定位telnetShell地址0x10a8f0其核心逻辑void telnetShell(int sock) { char buf[256]; while(1) { n recv(sock, buf, sizeof(buf)-1, 0); if(n 0) break; buf[n] 0; if(strncmp(buf, passwd, 6) 0) { // 密码校验起点 checkPasswd(buf7); // 调用checkPasswd } } }追踪checkPasswd地址0x10aa20反编译显示int checkPasswd(char* input) { char* realPass admin123; // 硬编码密码 return strcmp(input, realPass); }此处realPass在.rodata段地址0x10f200。4.3 动态验证GDB远程调试与Patch目标验证密码逻辑并制作永久patch。步骤启动QEMU模拟ARM环境qemu-system-arm -M versatilepb -kernel vxWorks -nographic -S -s-S -s使QEMU暂停并监听GDB端口1234。在另一终端arm-linux-gnueabi-gdb vxWorks (gdb) target remote :1234 (gdb) b *0x10aa20 # 在checkPasswd入口下断点 (gdb) c用Telnet连接QEMU虚拟机IP发送passwd testGDB命中断点x/s $r0显示input内容为testx/s 0x10f200显示admin123验证正确。制作patch将checkPasswd函数首条指令push {r4, r5, r6, lr}替换为mov r0, #1永远返回1表示校验成功。ARM指令mov r0, #1机器码为0x0100a0e1小端序。echo -ne \xe1\xa0\x00\x01 | dd ofvxWorks bs1 seek682528 convnotrunc682528是0x10aa20的十进制。重新烧录patchedvxWorks到设备Telnet登录不再需要密码。注意此patch仅适用于该固件版本。VxWorks 5.5的checkPasswd可能在不同地址需重新定位。真正的工控安全评估应记录patch前后系统行为差异如CPU占用率、任务响应时间确保无副作用。5. 工控逆向避坑指南那些文档里绝不会写的教训5.1 “万能”IDA脚本的三大幻觉许多教程推荐用IDA Python脚本自动恢复符号如find_syscall_table.py。但实践中这三大幻觉会让你浪费数天幻觉一sysCallTable地址固定。VxWorks 5.5中sysCallTable通常在.data段末尾但VxWorks 6.9因启用INSTRUMENTATION选项将其移到.text段中部。脚本若只搜索.data必然失败。幻觉二函数名字符串必然存在。厂商常将关键函数名如modbusProcess用宏定义混淆#define MB_PROC func_0x123456编译后字符串消失只剩地址。此时需结合交叉引用XREF分析。幻觉三所有函数都能被auto-analysis识别。VxWorks大量使用内联汇编inline asm优化关键路径如中断处理IDA无法解析会将整段当作数据。必须手动标记为code用c键转换。5.2 内存地址的“相对论”陷阱VxWorks的地址是物理地址还是虚拟地址答案是看情况。无MMU平台如早期PowerPC地址即物理地址0x100000就是Flash映射的物理地址。有MMU平台如ARM Cortex-A系列地址是虚拟地址需查页表。但VxWorks的页表管理高度定制mmuLib函数可能被重命名如myMmuEnable。此时cat /proc/vmmap不存在唯一方法是逆向mmuMap()调用链找到页表基址寄存器如ARM的TTBR0的写入点。我曾为某ARM平台逆向发现其页表基址硬编码在sysMmuInit()函数中地址0x200000但该地址在不同启动模式下会变化——必须结合Bootloader的ATAGS参数确认。5.3 协议逆向的“最小必要原则”不要试图逆向整个协议栈。工控协议往往分层清晰只需攻破最薄弱一环Modbus TCP重点逆向mbTcpProcessRequest()中的功能码分发逻辑而非整个TCP/IP栈。DNP3关注dnp3AsduDecode()对ASDUApplication Service Data Unit的解析特别是objHeader字段的越界读取。私有协议寻找“心跳包”或“配置同步包”这类包结构简单固定头TLV且服务器端校验逻辑常有疏漏。我们曾通过逆向心跳包解析函数发现其memcpy未检查长度构造超长payload触发栈溢出。5.4 法律与伦理的红色警戒线工控逆向的法律风险远高于Web渗透。关键红线绝不触碰运行中的生产系统。必须在离线环境如实验室复刻设备操作。某次客户允许“有限测试”我们仅用逻辑分析仪监听串口未发送任何指令仍被要求签署额外免责协议。固件来源必须合法。购买二手设备获取固件是灰色地带最佳实践是与厂商签订《安全评估协议》明确授权范围。漏洞披露遵循CVSS标准。工控漏洞的CVSS v3.1评分中“Attack Vector”常为Adjacent Network邻近网络而非Network因为多数设备不直接暴露互联网。这直接影响严重等级判定。实操心得我书桌上永远放着一台“牺牲机”——一台彻底报废的VxWorks设备专用于暴力测试如反复刷写错误固件、强制断电。它救过我三次一次是验证Flash写保护机制一次是测试JTAG解锁流程一次是复现某个偶发性内存泄漏。记住工控逆向不是炫技是带着敬畏心的精密外科手术。6. 从入门到实战你的第一份VxWorks逆向报告该怎么写一份合格的工控逆向报告不是代码清单而是决策依据。我给客户的报告结构如下执行摘要1页用非技术语言说明风险本质。例如“该DCS控制器的Modbus TCP服务存在硬编码凭证攻击者可在本地网络内无需认证即可读取所有模拟量输入AI和数字量输出DO状态可能导致误操作或数据泄露。”技术细节核心固件版本与获取方式注明是否经客户授权关键漏洞位置函数名、地址、汇编指令截图复现步骤精确到命令和参数如echo -ne \x00\x01\x00\x00\x00\x06\x00\x03\x00\x00\x00\x01 | nc 192.168.1.100 502影响范围明确到具体IO点、控制回路修复建议可落地紧急缓解禁用Modbus TCP服务通过rmModbusTcp()API调用根本修复厂商需在mbTcpProcessRequest()中增加IP白名单校验并将密码存储于安全芯片如ATECC608A附录完整逆向笔记Ghidra工程链接、关键截图PoC代码Python脚本含注释说明原理测试环境配置QEMU命令、GDB脚本最后分享一个小技巧在报告交付前用客户现场的真实设备做一次“盲测”。不告知对方测试内容只说“验证某项安全策略”然后观察其运维人员能否在10分钟内定位到你报告中提到的配置项。如果他们找不到说明报告写得不够直白——工控安全的价值不在于你多懂逆向而在于让一线工程师看得懂、改得了、信得过。
返回列表