ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Wireshark+CAN总线协议分析:从智能车流量包中提取flag

Wireshark+CAN总线协议分析:从智能车流量包中提取flag 网鼎杯青龙组这道叫 teslaaaaa 的 MISC 题名字起得很有意思乍看像是某个人的 ID实际拿到附件就明白了一个 pcap 流量包题材直指智能车通信协议分析。MISC 里凡是遇到“协议分析”四个字考察的核心基本一致——你能不能从一堆看似规律的十六进制报文里识别出哪些字段是正常的控制数据哪些字段是被人为塞进去的隐藏信息。这篇文章我会把拿到题目后从“乱麻一团”到“跑出 flag”的完整思路重新复盘一遍重点讲 CAN 总线报文的识别方法、Wireshark 的过滤技巧、批量提取脚本的写法以及在协议题里容易踩的坑。整个流程不依赖什么冷门工具适合刚入门 MISC、对智能车竞赛通信感兴趣以及准备打校赛省赛的选手。1. 拿到题目先拆需求智能车协议题到底在考什么1.1 从标题和附件推断考察方向很多入门玩家看到 MISC 协议题的第一反应是“找工具解包”但真正高效的做法是先把题目当成一份需求文档来拆解。这个标题里给了三个关键信息misc、智能车、协议分析。再加上 wp 后缀说明这是一道已经有标准解法的题目不是那种需要靠运气蒙的脑洞题。附件是 pcap 格式意味着所有关键数据都已经以网络封包或总线报文的形式固定下来了。pcap 最方便的地方在于里面的每一帧都有时间戳、协议头、payload我们完全可以按照时间线把“整辆车发生了什么事”还原出来。文件名 teslaaaaa 明显在往特斯拉和汽车电子方向引导再结合智能车的主题第一判断就应该是车规级通信总线最常见的就是 CAN 总线。我的解题习惯是先列一个问题清单而不是急着点开流量包看。问题包括这份数据是什么协议封装的数据是周期性上报还是事件触发flag 可能以什么形式存在用什么工具能最快完成初筛这套问题直接决定了后续的路线。比如如果发现是 UDP 数据流那就先过滤端口再按会话重组如果发现是 CAN 报文那就按 ID 分组再分析 payload如果发现是串口透传数据那就先找帧头帧尾再进行协议切分。顺带说一句这类题最忌讳的就是“看到哪算哪”。我以前经常一上来把所有报文全点开眼睛花了几个小时最后还是得退回到统计和分组这一步。协议分析题本质上是在一个高维空间里找异常点如果没有先建立正常基线你根本不知道异常长什么样。1.2 智能车竞赛里那些常见的通信方式聊智能车协议分析得先明白这种竞赛车模上到底跑着哪些通信。全国大学生智能汽车竞赛的组别很多摄像头组、电磁组、信标组、气垫组等等不管哪个组车上的主控芯片、传感器模块和执行机构之间都离不开数据交换。先说板内通信。主控和摄像头之间一般走 DCMI 或 SPI图像数据量大讲究实时吞吐电磁组靠 ADC 采样电感电压通常就是单片机的模拟量采集引脚编码器测速可能用到定时器正交解码。这些通信都属于芯片外设层面的数据通路在 CTF 题目里很少拿来直接出题因为数据格式跟具体芯片绑定太深标准化程度低玩家也没法用一个通用工具打开看。再说板间和整车通信。真正适合出题的是 CAN 总线。智能车竞赛里多块单片机协同工作的场景越来越多一块板负责图像处理一块板负责电机控制一块板负责无线通信如果每一对模块之间都拉独立的信号线整车线束会变成灾难。CAN 总线用双线差分信号多节点共享总线靠报文 ID 决定优先级天然适合这种多主分布式场景。分析比赛日志的时候我们经常在电脑上挂一个 CAN 分析仪把所有报文落到软件里事后再一段一段看哪里出了逻辑问题。这种背景放到 CTF 里就非常顺理成章流量包模拟的是一辆智能车在赛道上跑的过程中记录下来的 CAN 报文隐藏信息可能就混在转向、速度、状态上报这些普通数据里。你不需要知道赛道上车辆具体怎么拐弯但你必须理解协议结构否则 payload 和 ID 在你眼里就是一团没有边界的十六进制数字。1.3 出题人会把 flag 藏在哪些位置协议分析题本质上是在问一件事出题人把 flag 藏在了协议的哪一个维度的哪一个位置。根据我做题的经验常见的藏法大概有五种。第一种最粗暴也最常见直接把 flag 明文塞进某个 CAN ID 的 payload 里。这类题只要找到正确 ID 然后解析 ASCII 就能出结果难点在于怎么从几百上千个周期性帧里定位到那个混入的“非正常帧”。第二种是把 flag 打散到多个报文的 payload 字段中需要按时间戳或者按 ID 顺序重新拼接。这种题多了一个重组过程本质上是在考察你能不能从协议语义中识别出“哪几个字段属于同一段信息流”。第三种藏在 ID 本身比如某些 ID 按帧递增把相邻 ID 的差值收集起来再按二进制转字符就有可能拼出字符串。第四种藏在时间戳里通过帧间隔的微小偏移编码数据。这两种属于隐写范畴稍微隐蔽一些。第五种藏在 CAN 报文 DLC 的填充字节里比如某帧协议只需要 2 字节数据但帧固定发了 8 字节后面 6 个字节如果是 0x00 或者 0xFF 那还正常一旦出现规律变化就要立刻警觉。只要脑子里提前装了这五种可能后面分析起来就会主动得多。这一点很重要不是每条协议题都要把协议完整逆向出来。2. 数据长什么样先学会读 CAN 报文再谈分析2.1 CAN 报文的基本构成先把 CAN 协议的基础结构说清楚。经典 CAN 报文分几个关键部分仲裁字段、控制字段、数据字段、CRC、ACK以及最外层的帧起始和帧结束。我们在 Wireshark 里真正关心的是前三个。仲裁字段里的核心就是 ID。标准帧 ID 是 11 位扩展帧 ID 是 29 位。ID 的名称虽然叫“仲裁”但在实际使用中它同时承担了“数据类型标识”的作用。智能车上0x0C8 可能表示转向指令0x0CA 可能表示车速反馈0x0F1 可能表示整车状态具体含义由协议设计者自己约定。控制字段里最重要的是 DLC即 Data Length Code表示数据域的长度。经典 CAN 的 DLC 范围是 0 到 8CAN FD 的 DLC 编码方式不同可以到 64 字节。数据字段就是真正要分析的内容最长 8 字节。剩下的 CRC、ACK 之类由 CAN 控制器处理在抓包分析里一般不用管。可以这么理解CAN 帧就像快递包裹ID 是门牌号和包裹类型DLC 是箱子大小Data 是箱子里面的货。分析流量包的时候你的任务是找出哪个包裹里藏了违禁品。Wireshark 打开这种 pcap 后一个典型的 CAN 帧在协议列会显示为 can点进去能看到 can.id、can.dlc、can.data 这几个字段。这里的 data 通常以十六进制字节形式显示和实际总线上的原始数据一致。2.2 不确定协议时怎么快速判定通信类型不是所有题目都会好心地告诉你这是 CAN 总线更多时候附件就是一个标注不明的 pcap。这时候需要快速判断协议类型。第一步看 Wireshark 的 Protocol 列。如果能看到很多帧显示为 CAN那基本就定性了。如果是 CAN over UDP 或者 SocketCAN 透传协议列会分别显示为 UDP 和 CAN。如果发现是 UDP比如控制指令固定发往某个端口那就先过滤udp.port 目标端口然后看 payload很多远程遥控劫持类的题都是这种结构。第二步看帧特征。经典 CAN 帧 DLC 固定为 0 到 8如果大量数据帧长度都刚好是 8 字节且 ID 区域变化有规律大概率是 CAN。相反如果在每一帧的开头都能看到 0xAA 0x55 或者 0x5A 0xA5 这种固定帧头结尾有 0x0D 0x0A那就不是标准 CAN而是自定义串口协议。串口协议的处理思路是剥掉帧头帧尾、解析长度字段、取校验码前面的数据区然后同样按分组分析。第三步看时间分布。CAN 总线上的控制帧通常有非常规律的周期发动机转速上报可能是 10ms 一次转向角度可能是 20ms 一次。如果流量包里绝大多数帧的时间间隔稳定在一个值附近那大概率是真实采集或者仿真生成的周期性数据这比自定义事件型协议好分析得多。我遇到过很多朋友在 CAN 和 UART 之间纠结其实只要看 DLC 范围就能排除一大半。自定义串口协议基本不会只发 8 字节长度字段五花八门而 CAN 几乎都是 8 字节定长这种一眼就能看出来。2.3 用 Wireshark 和 tshark 把 CAN 流量提取成表格搞清楚是 CAN 之后下一步是把所有 CAN 帧转成便于处理的表格。GUI 里可以看 Statistics - Protocol Hierarchy 确认各协议占比但这种操作对深度分析帮助不大真正的效率提升来自命令行。推荐直接用 tshark 导出字段命令如下tshark -r tesla.pcap -Y can -T fields -e frame.number -e frame.time_epoch -e can.id -e can.dlc -e can.data -E headery -E separator, can_all.csv这条命令把帧序号、时间戳、CAN ID、DLC、数据全部导成 CSV。为什么要加-E headery因为导出后字段名会保留后面用 pandas 处理时不用再去猜列含义。为什么用frame.time_epoch而不是显示时间因为 epoch 时间可以直接做差值计算用来分析帧间隔非常方便。导出之后我们手头就有一张包含全部 CAN 帧的结构化表。后续所有的统计、分组、拼接操作都围绕这张表展开而不需要反复打开 Wireshark 去肉眼看。这一步看起来不起眼实际上能节省大量时间。真正的协议分析在 CSV 生成之后才算正式开始。3. 核心实操从一堆十六进制里把 flag 挖出来3.1 先做流量画像找出最可疑的 ID拿到 CSV 后的第一件事不是看 payload而是先统计每个 CAN ID 的出现次数、时间跨度和帧间隔。这相当于给流量画一张“心电图”找出哪个节点的行为不正常。可以用 pandas 快速统计import pandas as pd df pd.read_csv(can_all.csv) group df.groupby(can.id).agg( count(can.id, count), start(frame.time_epoch, min), end(frame.time_epoch, max), ) group[span] group[end] - group[start] print(group.sort_values(count, ascendingFalse))正常智能车控制场景下核心控制相关的 ID 会出现成千上万次时间跨度接近整个数据包的时长。如果一个 ID 出现的次数特别少但又有完整的时间跨度那它就很可能是被出题人单独插入的异常帧。我在这道题里的分析过程和这个套路基本一致。把所有 ID 按出现次数排序后绝大多数 ID 都集中在几百上千次唯独有一个 ID 的帧数特别少而且它的 payload 风格跟其他 ID 明显不在一个频道上。这个 ID 不一定就是答案但一定值得优先去看。流量画像还有一个作用就是建立“正常协议基线”。比如某个 ID 的 payload 前两个字节一直是 0x00 0x00突然在某个时间点变成了 0x66 0x6C这种变化就是巨大的异常信号因为 0x66 0x6C 正是 ASCII 编码里 “fl” 的开头。3.2 按 ID 分组提取 payload快速扫描 ASCII协议题里最省力的技巧就是先跑一遍纯 ASCII 扫描。很多出题人并不打算把隐藏信息做得特别复杂经常就是混入几个明文帧你要做的只是把它们挑出来。扫描逻辑很简单把每个 ID 下的所有 payload 按时间排序后拼成一个大字节流然后统计其中可打印字符的占比。正常 CAN 报文数据大多是 00 01 02 FF 这类控制数值可打印字符占比很低。如果某个 ID 拼接后大部分都是可打印字符基本可以断定这里有明文。import pandas as pd from collections import defaultdict df pd.read_csv(can_all.csv) groups defaultdict(list) for _, row in df.iterrows(): can_id row[can.id] data_hex str(row[can.data]).replace(:, ) try: raw bytes.fromhex(data_hex) except ValueError: continue groups[can_id].append((row[frame.time_epoch], raw)) for can_id, items in groups.items(): items.sort(keylambda x: x[0]) stream b.join(raw for _, raw in items) printable sum(1 for b in stream if 32 b 126) ratio printable / len(stream) if stream else 0 if ratio 0.6: print(f{hex(can_id)} printable ratio: {ratio:.2f}) print(stream[:200])这个脚本的关键在于先把每个 ID 的数据帧按时间排序然后再拼接。如果不排序原本有序的 ASCLL 信息会被打乱。对于这道题如果 flag 是以明文形式藏在 payload 里脚本跑完基本就能直接看到结果。不过要小心一种情况flag 可能不是完整的连续字节而是被拆成了多个字段比如前两个字节存在 ID 0x100 里后两个字节存在 ID 0x101 里那就需要先把 ID 按顺序拼接再做 ASCII 转换。3.3 当数据不是明文多字节数值还原与拼图扫描 ASCII 没结果的时候就要怀疑数据是以数值形式编码的。CAN 协议的 payload 里经常有 16 位或 32 位信号比如转向角的原始值可能是 0x0100 到 0x03FF 的一段区间车速可能是带符号的 16 位整数。分析时有一个很直观的判断方法找“会连续变化的字段”。在正常控制数据里转向、车速这类物理量一定是平滑变化的数值不会在相邻几帧之间从 0x0001 直接跳到 0xFFFE。如果你发现某几个字节在连续帧里呈现递增或递减趋势那它就是有语义的数值字段。大小端问题在这里特别关键。同一个字节序列12 34按照大端解析是 0x1234按照小端解析是 0x3412。CAN 协议里两种格式都有具体看协议设计者按什么规范定义。判断技巧是找一个已知的枚举型字段比如挡位信号0x01 表示 D 挡、0x02 表示 N 挡。如果数据显示 0x01 出现在高字节位置而后面一直是同样模式的枚举值大概率就是大端如果同样的值出现在低字节位置那就是小端。如果数据字段本身看起来不像数值也不像 ASCII还有一种常见处理是尝试按文件格式解析。比如把某一 ID 下的 payload 全部拼接后丢进 010 Editor看看文件头是不是 PNG、ZIP 或 RAR 的魔数。这里的思路是payload 本身可以是一段完整的文件内容只是被切碎放进了多个 CAN 帧里。处理这种拼图类题目只需要按时间戳把所有帧的 data 拼起来存成文件再验证格式即可。stream b for _, row in df.sort_values(frame.time_epoch).iterrows(): data_hex str(row[can.data]).replace(:, ) try: stream bytes.fromhex(data_hex) except ValueError: continue with open(merged.bin, wb) as f: f.write(stream)把合并结果存成 bin 文件后用file merged.bin看看是什么格式再决定下一步。这个过程在智能车协议题里很常见因为网络上实时传输的数据本质就是一种流截获后重组就能还原出原始实体文件。3.4 藏在 ID 和时间戳里的双信道信息payload 反复分析无果时就该把注意力转向 ID 本身和时间戳。很多协议题喜欢在同一份数据里开“第二信道”把真正有价值的信息藏在正常协议之外的元数据里。先看 ID 通道。如果某个 ID 的数值随着帧序号递增比如 0x100、0x101、0x102……那么相邻帧 ID 的差值可能就是编码结果。把差值收集起来按二进制转换成 ASCII看能不能组成字符串。有时候出题人会直接让 ID 按照某个字符的 ASCII 码递增那就更直接了。再看时间戳通道。CAN 帧如果周期非常稳定比如每 10ms 一帧那么相邻两帧时间差稳定在 0.01 秒左右。如果某些帧的间隔出现微小的偏移比如 0.0101、0.0100、0.0110这些偏移的 LSB 可能就对应数据位。收集这些 LSB8 个一组转字符也能恢复出信息。检测这种双信道信息时建议写脚本批量计算相邻帧的时间差而不是在 Wireshark 里肉眼观察。肉眼对几十毫秒级别的差异几乎没有感知脚本一行就能把异常帧找出来。df df.sort_values(frame.time_epoch).reset_index(dropTrue) df[delta] df[frame.time_epoch].diff() out df[df[delta] 0.1] # 过滤掉帧间隔明显偏大的异常跳变 bits .join(1 if (round((v % 0.01) * 1000) % 2 1) else 0 for v in out[delta].dropna()) print(len(bits) // 8, bytes)这条思路平时用到的机会不算多但一旦 payload 通道是“死”的它就可能是唯一出路。4. 现场复盘与避坑清单4.1 字节序和位填充是第一个大坑在智能车协议题里字节序带来的麻烦远比你想象中大。CAN 协议里经常见到 Motorola 格式和 Intel 格式两种编码方式。Motorola 格式下16 位信号的高字节存在前面的字节位置Intel 格式则相反低字节在前。很多新手把数据提取出来之后直接用 struct.unpack 以默认的大端方式解结果发现数值完全对不上。判断方法不难第一找枚举型的离散字段比如状态码值总是 0x01、0x02 这种看它落在哪个字节位置第二找连续变化的字段观察它从 0x0100 变到 0x0200 时是高位字节在变还是低位字节在变。位填充则是另一个隐蔽的坑。协议规定 DLC 固定为 8但真实数据可能只有 2 个字节。剩下的 6 个字节如果全为 0x00那是正常的对齐填充如果填了 0xFF那是取反填充如果填充位在某些帧里出现了非固定值就要立刻怀疑是隐写数据。分析时不要一上来就把整段 payload 当作有效信号先要确定哪些字节属于真实信号区哪些属于填充区。4.2 过滤条件不要过滤过头用 tshark 导出数据的时候很多朋友习惯直接加-Y can这个过滤本身没问题但会让一个误区被放大你以为所有 CAN 数据都在这个过滤器里实际上 pcap 里可能还有 CAN FD 帧、CAN over UDP 的封装层或者被嵌在 USB 报文里的 CAN 数据。如果过滤条件写死了can而没有加canfd那些藏在 CAN FD 里的数据就会被完全漏掉。更稳妥的做法是先看 Protocol Hierarchy 统计结果确认所有包含总线数据的协议类型然后再按协议类型逐一导出。另外如果流量包里同时存在多个 ECU 的报文有些功能帧的 ID 是按“基地址 偏移”的方式组织的。比如基地址是 0x100那么 0x101 可能是左前轮速度0x102 可能是右前轮速度这种子 ID 之间的关联性很强。过滤时不能只看单一 ID要通过掩码合并同一基地址的帧再观察合并后的数据流。4.3 导出数据要规范回查才能高效协议分析是典型的“一次分析、反复回查”过程。我第一次跑脚本时导出的 CSV 只有 can.id 和 can.data结果分析到后面想回查某个帧的时间间隔发现列没导全又得从头导一遍。这是完全没有必要的返工。建议从一开始就把 frame.number、frame.time_epoch、can.id、can.dlc、can.data 全部导出哪怕后面用不到至少不会因为缺失字段而中断分析。CSV 的分隔符建议用逗号编码用 UTF-8这样 pandas 读起来不会出问题。如果数据量特别大可以考虑直接用 parquet 格式存储读取速度比 CSV 快很多。还有一个小技巧分析过程中做出的判断和操作步骤及时记录下来。哪怕是“某 ID payload 前 4 字节疑似车速字段”这种不确定的判断都值得写进笔记。协议分析经常会在几小时后推翻几小时前的结论有记录才能把思路拉回正轨。4.4 我做这类题的习惯动手之前先跑盲扫以我自己刷协议题的习惯来说正式进入流程之前会先做一次三件套第一件跑一遍strings第二件看包数和时间跨度第三件用 binwalk 扫一下 pcap 里是否有内嵌文件。这三件事不费多少时间却能提前规避很多无谓的深度分析。strings是最容易出成果的。很多 CTF 出题人在构造 pcap 时会把图省事直接写入一个完整明文 flag或者把文件名和题目提示留在某个包的高层协议里直接跑一遍就能看到。看包数和时间跨度可以帮助判断数据是真实采集还是仿真生成。仿真生成的流量通常时间戳非常均匀包数也整齐这种情况下报文语义往往很简单flag 大概率规整地躺在某一列数据里不需要做复杂的信号分析。binwalk 则用来排除“流量包里面还藏着一个文件”的可能。比如 payload 重组后是一张图片或者一个压缩包binwalk 直接扫描就能识别出文件头特征。这几步做完再进入正式的协议逆向流程效率和心态都会好很多。拿到这题的时候我其实也在 ID 分组这一步卡了一段时间不是因为工具不会用而是总想着是不是有什么高级隐藏手段。后来把心态放平老老实实按“先统计流量画像再按 ID 分组最后做 ASCII 扫描”的流程走下来几分钟就定位到了可疑数据。协议分析题做到最后你会发现真正有用的不是某个神通广大的工具而是你能不能把协议当故事一样读顺。很多看起来复杂的题其实只需要把数据按正确顺序拼起来再回头看一眼结果就自己浮出来了。
返回列表