ARTICLE DETAIL

资讯详情

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

车联网安全逆向实战:从固件解包到密钥提取的完整攻防解析

车联网安全逆向实战:从固件解包到密钥提取的完整攻防解析 1. 从一场CTF赛题看车联网逆向的实战起点最近在复盘“饶派杯XCTF车联网安全挑战赛”的一道逆向题题目名字挺有意思叫“Reverse GotYourKey”。虽然主办方给的原始描述和关键词几乎为零但“车联网安全”和“Reverse”这两个标签一贴再结合“XCTF”这个国内老牌CTF赛事平台这道题的味儿一下就对了。它不像那些纯算法的逆向题更像是模拟了一个车联网场景下某个关键组件比如T-Box、车载娱乐系统或某个ECU控制单元的固件或应用程序让你从中找到那个至关重要的“Key”。这个Key可能是解锁某个功能的凭证也可能是解密一段车载通信数据的密钥总之它是整个安全链条上的关键一环。对于刚接触车联网安全或者想从传统PC逆向转向嵌入式、物联网逆向的朋友来说这类题目是一个绝佳的跳板。它逼着你去理解车联网特有的架构、通信协议以及在这些约束下逆向工程的手法会发生哪些变化。今天我就结合这道“GotYourKey”的解题思路当然我会进行合理的场景重构和细节补充因为原题描述有限来拆解一下车联网逆向中那些你必须掌握的“骚操作”和容易踩的“坑”。2. 车联网逆向环境搭建不只是IDA和虚拟机做传统Windows/Linux逆向你打开IDA Pro配个虚拟机基本就能开干了。但车联网逆向第一步可能就卡住。这道“GotYourKey”的赛题首先给你的可能不是一个标准的ELF或PE文件而是一个bin文件、一个带签名的镜像甚至是一段CAN总线或车载以太网的通信抓包。环境准备是重中之重。2.1 固件提取与初步分析假设题目给的是一个“车载信息娱乐系统IVI”的固件升级包update_package.bin。第一步不是直接扔进IDA而是得把它“拆开”。# 1. 使用binwalk进行初步的固件分析探测内部结构 binwalk update_package.bin # 典型的输出可能如下 # DECIMAL HEXADECIMAL DESCRIPTION # --------------------------------------------------------------------- # 0 0x0 uImage header, header size: 64 bytes, header CRC: 0x8C4D3A21, created: 2023-10-27 03:14:15, image size: 5242880 bytes, Data Address: 0x80008000, Entry Point: 0x80008000, data CRC: 0x1A2B3C4D, OS: Linux, CPU: ARM, image type: OS Kernel Image, compression type: lzma, image name: Linux-5.4.0 # 64 0x40 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 15892480 bytes # 5242944 0x500040 Squashfs filesystem, little endian, version 4.0, compression:lz4, size: 15566336 bytes, 2816 inodes, blocksize: 131072 bytes, created: 2023-10-27 03:15:01从binwalk结果看这个固件包含了一个u-boot镜像头、一个压缩的内核LZMA和一个根文件系统Squashfs。我们的目标“Key”很可能藏在文件系统的某个应用程序或配置文件中。# 2. 使用dd和相应工具解包 # 提取并解压内核如果需要分析内核模块 dd ifupdate_package.bin ofkernel.img bs1 skip64 count5242880 # 可能需要使用lzma或xz命令解压具体看压缩类型 # xz -dc kernel.img kernel.uncompressed # 3. 提取并挂载Squashfs根文件系统 dd ifupdate_package.bin ofrootfs.squashfs bs1 skip5242944 mkdir rootfs sudo mount -t squashfs -o loop rootfs.squashfs ./rootfs现在你就可以像浏览一个Linux系统一样查看rootfs目录了。这里就是寻找线索的主战场。注意车联网设备固件格式千奇百怪除了Squashfs还可能是JFFS2、UBIFS、CRAMFS等。binwalk的签名库可能不完整有时需要根据文件头魔数Magic Number手动判断并用jefferson、unsquashfs等专用工具解包。解包失败是常态要有耐心。2.2 交叉编译工具链与调试环境车联网设备CPU架构以ARMCortex-A/M系列、MIPS、RISC-V为主与你的x86开发机不同。你需要对应的交叉编译工具链如arm-linux-gnueabihf-来编译分析工具或者静态链接你需要的工具如gdbserver放到设备上运行。对于这道赛题如果逆向目标是文件系统里的一个ARM架构的二进制程序/usr/bin/key_manager那么你的IDA Pro或者Ghidra必须加载对应的ARM处理器模块。静态分析时函数调用约定ATPCS/AAPCS、寄存器用途R0-R3传参R0返回值和常见编译器优化模式如Thumb-2指令集都必须门儿清。动态调试则更复杂。理想情况是能有设备的物理调试接口如JTAG/SWD但这在CTF和大多数渗透测试中不现实。更常见的方法是模拟运行使用QEMU用户态模拟运行单个程序。qemu-arm -L ./rootfs ./rootfs/usr/bin/key_manager。但这要求程序依赖的库在rootfs里都全且不涉及特殊硬件驱动。系统级模拟使用QEMU模拟整个系统qemu-system-arm并加载我们提取的内核和根文件系统。这能提供最接近真机的环境但配置复杂驱动支持是个大问题。GDBServer远程调试如果能在设备上或模拟环境中运行一个静态编译的gdbserver然后从主机用gdb-multiarch连接上去那将是最强大的动态分析手段。在CTF中题目有时会“贴心”地已经在固件里留好了gdbserver。对于“GotYourKey”我们假设采用QEMU用户态模拟进行初步动态分析因为它最快捷。3. 逆向目标定位在庞大的文件系统中找到“Key”固件解包后面对成千上万个文件如何快速定位到关键目标这是车联网逆向区别于传统逆向的第一个分水岭——你需要一些“威胁情报”和“场景思维”。3.1 基于场景的敏感路径搜索车联网中与“Key”相关的功能通常集中在几个地方身份认证与密钥管理/usr/bin/,/sbin/下可能存在的auth_xxx,secure_boot,key_mgr,crypto_util等命名的程序。通信加密处理CAN、车载以太网Some/IP, DoIP、4G/5G模块通信的程序。可以搜索can,eth,tcp,ssl,mqtt等关键词。配置与存储/etc/目录下的配置文件可能包含硬编码的密钥或密钥生成种子。/var/或/data/目录下的运行时数据。日志文件/var/log/下的日志可能泄露程序流程或错误信息帮助理解逻辑。我们可以用一些命令快速筛选# 进入挂载的根文件系统 cd rootfs # 查找所有可执行文件 find . -type f -executable -exec file {} \; | grep -E ELF.*(ARM|MIPS) | head -20 # 查找包含“key”、“crypt”、“auth”、“secure”等关键词的文件名或路径 find . -type f \( -name *key* -o -name *crypt* -o -name *auth* -o -name *secure* \) | head -20 # 在文件中搜索可能的密钥模式如base64、hex字符串 # 查找长字符串可能包含硬编码密钥 strings -n 20 ./usr/bin/key_manager | head -50 # 搜索类似AES密钥的hex字符串32字节64个hex字符 grep -r -E “[0-9a-fA-F]{64}” . 2/dev/null | head -10 # 搜索类似硬编码的密码或令牌 grep -r -E “password[[:space:]]*[[:space:]]*[^[:space:]]” ./etc 2/dev/null假设通过搜索我们锁定了目标程序./usr/bin/telematics_keyd一个用于车联网通信密钥管理的守护进程。3.2 静态分析切入点字符串、导入函数与初始化流程用IDA Pro加载telematics_keyd选择ARM Little-endian处理器并留意是否是Thumb模式。加载后先不急着看汇编。查看字符串ShiftF12直接搜索“key”、“success”、“fail”、“error”、“init”、“generate”、“load”等词。你可能会发现诸如“Key generation successful for VIN: %s”、“Loading master key from secure storage...”、“Invalid authentication token”这样的字符串它们直接揭示了程序的关键功能和逻辑分支。查看导入函数CtrlI关注加密相关函数。如果看到libcrypto.so或libssl.so的导入如AES_set_encrypt_key,RSA_public_decrypt,SHA256_Init那么加密逻辑很可能就在这里。如果看到libcurl.so的函数说明可能有网络交互来获取密钥。定位main函数或主要初始化函数对于Linux程序通常从main开始。在ARM架构下main函数通常不是反汇编的第一个函数前面会有一些库的初始化。你可以通过交叉引用Xrefs to上面找到的关键字符串或者查找__libc_start_main的调用它的第一个参数通常是main函数的地址来定位main。在“GotYourKey”的上下文中我们假设在字符串窗口看到了“Final Key: %s\n”交叉引用它我们来到了一个名为generate_final_key的函数附近这显然就是我们的核心目标。4. 核心逻辑逆向从ARM汇编到C伪代码找到了关键函数接下来就是硬核的逆向分析。我们以generate_final_key函数为例。4.1 函数概览与栈帧分析在IDA的图形视图下先看函数整体的控制流图。ARM函数开头通常会有保存寄存器和设置栈帧的指令PUSH {R4-R11, LR} ; 保存寄存器包括返回地址LR ADD R11, SP, #0x20 ; 设置帧指针R11 (FP) SUB SP, SP, #0x50 ; 在栈上分配局部变量空间这告诉我们这个函数使用了R4到R11以及LR栈帧大小是0x50字节。局部变量和临时数据都会在[R11, #-offset]或[SP, #offset]的位置访问。4.2 参数传递与关键算法识别ARM架构下前4个参数通过R0-R3传递更多参数通过栈传递。我们的generate_final_key函数可能接收两个参数一个输入缓冲区指针和一个长度。; 假设R0是input_data指针R1是input_len generate_final_key: PUSH {R4-R11, LR} MOV R4, R0 ; 保存input_data到R4 MOV R5, R1 ; 保存input_len到R5 ...在函数体中你需要密切关注循环、条件分支以及函数调用。特别是对memcpy,strcmp,sprintf等标准库函数以及之前发现的加密函数如AES_encrypt的调用。假设我们在函数中看到了一个明显的循环结构并且内部调用了SHA256_Update那么这部分很可能是在计算输入数据的哈希。紧接着可能看到对某个固定内存地址数据的加载硬编码常量然后调用AES_set_encrypt_key和AES_cbc_encrypt。; 伪代码示意 LOAD R0, hardcoded_iv ; 加载初始化向量 LOAD R1, hardcoded_aes_key ; 加载AES密钥 BL AES_set_encrypt_key MOV R0, SP ; 输出缓冲区可能在栈上 MOV R1, R4 ; 输入数据SHA256结果 MOV R2, #0x20 ; 长度32字节 LOAD R3, hardcoded_iv BL AES_cbc_encrypt这里就是一个关键点hardcoded_aes_key可能是一个中间密钥而不是最终的Flag。最终的Flag可能是用这个密钥加密哈希后的结果也可能是这个密钥本身经过某种变换。你需要继续跟踪这个加密结果的去向。4.3 动态验证与数据流跟踪静态分析到一定程度必须结合动态调试来验证猜想。使用QEMU用户态模拟运行并附加调试器。# 终端1在QEMU中运行程序等待GDB连接 qemu-arm -L ./rootfs -g 1234 ./rootfs/usr/bin/telematics_keyd # 终端2启动GDB并连接 gdb-multiarch ./rootfs/usr/bin/telematics_keyd (gdb) set architecture arm (gdb) target remote localhost:1234 (gdb) break *0x8F01234 # 在generate_final_key函数入口下断点 (gdb) continue当程序断下后你可以检查寄存器和内存。info registers查看当前寄存器值确认参数。x/10x $r0查看R0指向的内存数据输入数据。单步执行si或stepi观察数据流。在调用AES_cbc_encrypt后查看输出缓冲区的内存内容这很可能就是加密后的“Key”。但“GotYourKey”的赛题可能不会这么直接。最终的Key可能需要经过编码Base64、Hex或与某些设备唯一标识符如VIN码组合。这时你需要关注函数末尾的代码看加密结果是否传给了sprintf、base64_encode或某个输出函数。4.4 常见混淆与对抗技术在真实车联网和CTF赛题中程序可能会被混淆控制流扁平化大量的switch-case和跳转表让IDA生成的控制流图变成“意大利面条”。你需要耐心地跟踪每个基本块或者尝试使用Ghidra的Decompiler它有时能更好地还原这种结构。字符串加密所有提示字符串都被加密运行时解密。你需要找到解密函数并在调试时在其执行后dump内存中的字符串。反调试检测调试器如检查ptrace、/proc/self/status中的TracerPid。在QEMU用户态模拟中有些反调试可能失效但系统级模拟或真机环境中就需要patch掉这些检测代码。多线程/进程通信密钥生成可能涉及多个进程间的通信如DBus、Socket。你需要分析进程间通信的数据格式和协议。对于这道题我们假设一种经典套路程序从/etc/vehicle_config.bin读取一个加密的配置块用硬编码的RSA私钥解密后得到一个种子Seed然后用这个种子通过一个自定义的算法生成最终的AES密钥再用这个AES密钥去解密另一段内嵌的数据得到Flag即“Key”。5. 密钥提取与Flag构造最后的临门一脚通过动静态结合分析你终于理解了算法种子Seed是“VIN_ABC123XYZ789”的MD5值的前8字节自定义算法是一个简单的线性同余生成器LCG混合XOR操作生成16字节的AES-128密钥。然后用这个密钥以CBC模式解密文件偏移0x1000处开始的48字节密文解密结果就是Flag。现在你需要写一个提取脚本或Keygen。#!/usr/bin/env python3 import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import struct # 1. 提取种子 vin bVIN_ABC123XYZ789 seed_md5 hashlib.md5(vin).digest()[:8] print(fSeed (first 8 bytes of MD5(VIN)): {seed_md5.hex()}) # 2. 自定义LCG算法根据逆向结果还原 def custom_lcg_keygen(seed_bytes, key_len16): # 假设逆向出的算法 state (state * 0x343FD 0x269EC3) 0xFFFFFFFF # 每个循环生成1字节密钥材料 seed struct.unpack(Q, seed_bytes)[0] # 假设小端序 key bytearray() state seed for i in range(key_len): state (state * 0x343FD 0x269EC3) 0xFFFFFFFF key_byte (state 16) 0xFF key.append(key_byte) return bytes(key) aes_key custom_lcg_keygen(seed_md5) print(fGenerated AES Key: {aes_key.hex()}) # 3. 读取并解密固件中的密文 with open(update_package.bin, rb) as f: f.seek(0x1000) # 密文偏移 ciphertext f.read(48) # 密文长度 # 假设IV是全零根据逆向 iv b\x00 * 16 cipher AES.new(aes_key, AES.MODE_CBC, iv) # 注意密文可能包含Padding需要去除 try: plaintext unpad(cipher.decrypt(ciphertext), AES.block_size) print(fDecrypted Flag: {plaintext.decode(utf-8)}) except ValueError as e: print(fDecryption or unpadding error: {e}) # 可能不需要unpad直接输出 plaintext cipher.decrypt(ciphertext) print(fRaw decrypted: {plaintext})运行这个脚本你得到了FlagXCTF{Car_N3tw0rk_Key_1s_H3r3!}。6. 车联网逆向的独特挑战与实战心得通过这道“GotYourKey”的推演我们可以总结出车联网安全逆向的几个核心特点和个人体会环境复杂性是首要障碍架构多样ARM/MIPS/RISC-V、系统定制化、工具链缺失、调试接口封闭。我的经验是建立一个自己的“物联网逆向工具包”包含常用架构的交叉编译工具链、静态链接的busybox和gdbserver、各种文件系统解包工具binwalk,fmk,jefferson、以及QEMU的各种系统镜像模板。遇到新固件先扔给binwalk和file命令再决定解包策略。攻击面分散需要结合场景密钥可能藏在应用程序、配置文件、通信协议甚至硬件安全模块HSM的交互中。不能只盯着一个二进制。要像侦探一样梳理数据流用户输入 - 哪个进程处理 - 是否读写文件/网络 - 是否调用加密库 - 输出到哪里。日志/var/log/、临时文件/tmp/、进程间通信netstat -anp,dbus-monitor都是重要线索源。静态分析与动态调试必须结合纯静态分析面对混淆和复杂逻辑极易迷失。纯动态调试没有静态分析指引则像无头苍蝇。我常用的流程是静态快速定位关键字符串和函数 - 动态调试验证数据流和算法 - 修改静态分析中的错误理解Ghidra的Decompiler这时很有帮助 - 再次动态跟踪直至完全弄清。对于加密算法重点盯住libcrypto/libssl的函数调用序列和参数。注意“模拟”与“真实”的差异QEMU用户态模拟无法模拟硬件特性如密码学加速引擎、真随机数生成器、特定内核模块或系统调用。这可能导致程序在模拟环境中运行异常或结果不同。如果动态分析结果与静态分析严重不符或程序直接崩溃就要考虑是否是环境差异。有时需要基于静态分析直接写Keygen而不是追求在模拟环境中完美运行。Flag的最终形式多样不一定就是解密的明文。可能是解密后的某个字段可能是多个数据拼接后的MD5也可能是满足特定条件的输入对应的输出。一定要仔细阅读题目描述如果有和程序中的所有输出字符串它们往往暗示了Flag的格式和提交方式。回到“饶派杯XCTF”这道题它更像是一个引子把选手从传统的软件逆向拉进了车联网这个软硬件结合、强调协议和系统性的新战场。解决它你不仅需要逆向技能还需要对嵌入式系统、汽车电子架构有最基本的了解。而这正是车联网安全研究的魅力所在——你永远在拆解一个真实的、在道路上奔跑的复杂系统。
返回列表