随便写点之前做流量分析总结的经验,不保证有用,欢迎各位补充捏
总的来说咱目前遇到的流量分析可以大致分为3类:渗透流量,工控流量和其它流量(问就是自己瞎起的名字)
渗透流量主要就是指攻击方做渗透的时候防守方抓包得到的流量,经常会看到各种马,做这一类题目需要一定的Web知识,至少得会一点基础的php代码审计,这类就主要和TCP打交道
工控流量主要都是和Modbus协议相关的流量,主要大概是看协议下进行的操作指令
其它流量种类比较多,包括但不限于5G传输协议,USB协议,SMTP协议等,具体情况还得需要自行分析
总之流量分析需要一定的计算机网络知识,不然会挺头大的
工具使用
主要工具自然是Wireshark,不过有的时候得用到tshark,下面会提及
大量数据提取
有的时候流量包过多,直接用手收集需要的数据太麻烦了,这个时候就可以用下面的tshark指令:
1 | tshark -r 流量文件名 -Tfields -Y 过滤器 -e 需要提取的数据 > 输出文件名 |
比如DASCTF 2021八月赛的stealer,里面是DNS流量分析,请求的DNS前面是Base64,我需要提取出所有的Base64,但是数据太多了,那我可以这样做:
1 | tshark -r dump.pcapng -Tfields -Y "dns && ip.src == 172.27.221.13" -e 'dns.qry.name' > data.txt |
当然,提取出来的数据可能还会因为自己的一些疏漏而出现问题,这个时候可以人工拉出来,或者优化一下过滤器
有关过滤器
使用Wireshark要灵活使用过滤器,可以节省大量流量审计时间
如果发现有想检索的参数,但是忘记它的过滤器该怎么写,可以这么办:
比如:我想检索所有TCP Acknowledgement Number为1953的流量包,但是我不知道/忘记怎么写过滤器了:

左下对着对应的参数右键,光标移到”作为过滤器应用“或者”准备作为过滤器“处,右侧选项栏的最上面那栏就会告诉你对应的过滤器参数,非常好用,屡试不爽(笑死了,直接点下面选中就行了,如果过滤器已经有参数了就可以根据情况选“且选中”和“或选中”了)
其实点击选中字段之后,Wireshark左下角就会显示它的过滤器和字段长度,也可以利用一下
然后有一些过滤器参数非常好/常用,这里稍微记录一下,如果还有遇到/想起来其他的还会更新:
ip.addr,ip.dst,ip.src
分别是address,destination,source的意思,可以很方便地过滤特定IP发送/接收的流量
其中ip.addr == ***等价于 ip.src == *** && ip.dst == ***
_ws.expert
常用于提取异常/错误流量包,目前咱比较常用的是_ws.expert.severity和_ws.expert.group,错误包常用_ws.expert.group == “Malformed”;severity过滤视具体情况而定,通常”Note”和”Chat”会忽略
提取异常/错误流量包是因为可能题目是在这类流量包里面隐藏了信息,做题的时候可以稍微关注一下左下出现高亮颜色的参数,那个参数可能就是问题所在
http.request.method,http.response
毕竟有很多渗透流量,所以提取出特定方法的请求&响应的流量还是蛮有用的,就比如我想过滤出POST方法的流量,我就直接http.request.method == POST,就直接出来了,响应就直接http.response,如果是想过滤出返回特定状态码的话就http.response.code == (你需要的状态码) 就行了
一些杂七杂八的要点
除此之外,跟踪流也是非常好用的一个功能,在上面已选中的流量那里右键,光标移到”追踪流“,然后选择想要追踪的流,之后的弹窗就可以显示所有与选中流量同样流下的流量,非常有利于分析流量
还有一件事:那些长度较大的流量需要额外关注下,可能这些流量传输了一些重要的文件,这个时候需要观察相对较近的TCP/UDP流量中是否也在传输大量的数据,因为可能文件太大,需要分段传输
渗透流量
如最开头所说,这类流量常和TCP/IP协议打交道,所以主要还是要看HTTP流,因此除去PHP代码审计能力,还需要一些基础的HTTP/CSS知识,至少得能分清楚哪些是CSS
一般Webshell都会用POST方法传一个马上去,当然也不排除有些题目省去了传马的过程,不过POST方法下的流量还是值得拉出来看看的
目前常用的渗透工具有蚁剑,冰蝎,CobaltStrike,哥斯拉,中国菜刀等,本文先讲解一下前两个工具的流量特征,剩下的之后有空了会再补充的
蚁剑流量分析
这里使用AuroraCTF2022 Webshell2举例
关注到POST了一个shell上去,shell是经过Base64加密过的,这里我们解密一下:

得到的就是蚁剑的混淆,重点关注asenc函数和asoutput函数:
1 | function asenc($out){return @base64_encode($out);}; |
很明显,asenc是把传进来的$out参数进行Base64加密,而asoutput是先获取原先马里的内容,然后再输出”6cc7”和”435d5”,输出经过asenc(Base64)加密过的马,然后输出”d36cfa”和”879e0a”
换言之,就是这样的情况:混淆数据1 + Base64后的马 + 混淆数据2,之后返回的数据就是这样的一个格式,所以删掉混淆数据然后B64解个码就结束了
这就是蚁剑的流量混淆过程,之后基本上就是PHP代码审计了,这方面还请请教那些Web神,咱就一摆烂的Misc,啥都不会
冰蝎流量分析
这里使用忘记哪次DASCTF的题目New Grating举例
看一开始的upload_file.php
1 |
|
Wireshark没法直接输出UTF-8的中文,所以我这里稍微转了一下码
冰蝎最重要的流量特征就是这个AES128和这个$key,iv默认是0x0,但是视情况而定,主要还是看混淆流量里面有没有额外的操作
接下来就是分析一下冰蝎的流量混淆过程:
总体上,冰蝎是这样的一个流程:明文→Base64→AES128→Base64→流量数据,而解码的时候由于冰蝎是先把马B64然后套个eval再进行接下来的操作的,所以解密的时候最好是开着两个窗口,一个负责先B64解密和AES128解密,另一个把eval里面的马B64解密
上面的没搞错应该是冰蝎3.0的默认加密,冰蝎还自带一些其它的加密,比如default_aes,default_xor等,这里先把default_aes的shell.php拉出来:
1 |
|
和上面的其实差不多,就是这个shell是对上传的参数进行一个Decrypt操作,其中的openssl_decrypt里面的参数就告诉了我们参数是如何加密的(逆推一下就行了):
对Base64解密后的$data进行解密后以$key作为key,AES-128-ECB作为加密格式,PKCS1作为Padding进行解密,所以加密就是先AES再Base64,解密就是Base64再AES,和上面其实区别不大,iv也是默认0x0,但是不保证$data里面的东西一定没有Base64,不过各位应该还是能够看出来的吧
default_xor之类的等之后什么时候再补充
天蝎流量分析(Under Construction)
据说有些红队会使用天蝎进行webshell利用,
C2流量简述
线上比赛的时候遇到的流量分析有好一些是C2流量分析,这种流量一般需要对着C2源码去看(这些C2一般是Github开源的),不然确实不知道它是怎么打的、流量是怎么加密的、默认密钥是怎么样的,等等等等
因为这些流量很多是基于TCP传输的(当然也会有一些其他的,这些另说),所以我们需要找到TCP流量里面那些奇怪的流量,举个例子:OrcaC2的HTTP流量在Wireshark里有的时候会误识别为X11协议数据,而且在传输数据之前会传一个.bin
文件
说到这个.bin
,如果能确定是C2传的,那么这个文件很大可能是一个shellcode,如果可能的话最好在沙箱调试,不然就尝试看看能不能把shellcode转换成exe使用IDA逆向分析一下(不过毕竟是shellcode,不太好看的)
工控流量
太久没做了(线上比赛遇到的更多的是C2流量而不是工控流量),就只记得一个Modbus,还有一些读写线圈的操作,之后做到相关的题目再补充一下吧,当然欢迎各位扔给我有关工控流量的题目(进行一个坑的挖)
(目前手上还有个20W+的工控流量,但是没有明确的flag,等之后有空慢慢看吧இ௰இ)
我去怎么有这么多!Orange-Cyberdefense/awesome-industrial-protocols: Security-oriented list of resources about industrial network protocols.,光是刚刚给出来的这篇Repo就记录了65个,受不了了我当场展示原地爆炸
这里还是先写些协议格式好了,实例分析的话我现在有点赶,不好整
Modbus流量分析
本段主要是复制粘贴这篇PDF文档:PI_MBUS_300,很多专业名词我自己瞎JB翻译的,大佬们轻点喷
Modbus协议遵循的是一个Master-Slave
原则,我这里就叫它主从原则吧,一般就是一个控制器组织一条信息的话,它就是主机设备,同时尝试接受一个从机设备(这里包括接下来的很多从
不是那个From
,是Slave
喵)的信息,而当一个控制器接收到一条信息的时候,它就是从机设备,同时会构造一个从响应并返回给主机设备
数据类型
Modbus协议的数据类型和其它的数据类型或多或少有一些不一样,下面稍微写一下:
数据类型 | 访问权限 | 描述 |
---|---|---|
线圈 (Coil) | 可读可写 | 单位输出 (Single bit outputs) |
离散输入 (Discrete Input) | 只读 | 单位输入 (Single bit inputs) |
输入寄存器 (Input Register) | 只读 | 16位输入寄存器 |
持有寄存器 (Holding Register) | 可读可写 | 16位输出寄存器 |
需要注意一下,持有寄存器40001会被记为寄存器
0x0000
,因此持有寄存器40108会被记为寄存器0x006B
,也就是107与此同时,输入寄存器30001也会被记为寄存器
0x0000
,所以具体是哪个寄存器还得根据PDU看
消息结构
对于所有的Modbus协议,都有如下的大框架:
Unit Address | Modbus PDU | Error Check |
---|
然后Modbus一共有4个变种:Modbus ASCII,Modbus RTU,Modbus TCP和Modbus RTU over Modbus TCP,其中ASCII现在是弃用了,比赛遇到的Modbus更多的是后两者
Modbus ASCII
Start | Unit Address | Message | LRC | End |
---|---|---|---|---|
ASCII 58,即: |
2个字符 | N个字符 | 2个字符 | ASCII 13 + ASCII 10,即CRLF换行符 |
看起来很复杂,但是ASCII应该是最好懂的:每个Modbus开头是:
,中间只有0-9A-F,最后结尾是CRLF换行符
一开始的Unit Address
是PLC(Programmable Logic Controller,可编程逻辑控制器)的Hex地址(1~247),其中主机设备可以通过地址0进行广播,中间的Message
则是将一个字节转为2个Hex字符表达,最多支持506个字符(也就是说实际的字节传输上限为253字节),后面的LRC(Longitudinal Redundancy Check,纵向冗余检查)也是2个字符表示,为Unit Address
和Message
的RLC
RLC具体实现比较简单,这里就是将所有数据排列成一行2个字符的矩阵,对所有行数据进行异或操作(第1行$\oplus$第2行$\oplus$…),得到的结果作为RLC
Modbus RTU
相比于ASCII,Modbus RTU更直接:它传输的直接是字节数据
Unit Address | Message | CRC |
---|---|---|
1字节 | N字节 | 2字节 |
和Modbus ASCII完全一致,除了这里的数据全部以字节传输而不是转为Hex字符串再传输,同时校验码从ASCII的RLC换成了CRC,对象还是Unit Address
和Message
Modbus TCP
有的时候Modbus会使用TCP/IP连接进行通信传输,因此出现了Modbus TCP
Transaction ID | Protocol | Length | Unit Address | Message |
---|---|---|---|---|
2字节 | 2字节 | 2字节 | 1字节 | N字节 |
由于一个控制器可能在不同的通信中扮演不同的角色(比如在Txn 1是主机设备,但是在Txn 7是从机设备),因此Modbus TCP引入了Transaction ID
段以分辨不同通信. 至于Protocol
段则被设置为0以表示协议为Modbus协议,接下来的Length
段是后面的两个段的长度,剩下的就和RTU一样了
Modbus RTU over Modbus TCP
这个协议很少用,而且实际上就是Modbus TCP的变种——只是加了一个CRC而已
Transaction ID | Protocol | Length | Unit Address | Message | CRC |
---|---|---|---|---|---|
2字节 | 2字节 | 2字节 | 1字节 | N字节 | 2字节 |
就是缝合怪,此处不做赘述
PDU(协议数据单元)
这个是Modbus协议的核心,也是Message
段传输的内容,一共有24个操作码,其中仅有10个操作码是所有控制器型号通用的(01~07,15~17),其它的或多或少有些不支持的,由于我懒,所以这里就只放出通用的PDU操作,如果哪一天想起来再填一下没写的型号特有的PDU操作
01 Read Coil Status
正如其名,这个操作码是读取线圈内容的,下面是请求体结构,一共5字节
Function Code | Starting Coil Address | Coil Count |
---|---|---|
0x01 | 2字节 | 2字节 |
操作码,起始线圈地址,读取的线圈数量,应该很直观了
响应是这样的:
Function Code | Byte Count | Coil Data |
---|---|---|
0x01 | (Coil Count + 7) / 8 | N字节 |
毕竟一个线圈数据大小为1位,所以Byte Count其实就是告诉你访问的线圈总量打包成字节要多少字节
需要注意一点:返回的线圈数据是按照小端排序,比如我访问线圈22到32,那么它的排列方法是这样的:
Coil 29-22 | 0b 1011 0001 |
---|---|
Coil 32-30 | 0b 0000 0101 |
先传输第22~29个线圈数据,但是第29个线圈作为MSB,第22个线圈为LSB;接下来返回的30~32个线圈也是用小端,但是由于不足8个线圈,因此从MSB开始填充5位0将数据填充到1字节长
考虑到Message
段长度限制,线圈的读取最多可读2000个
02 Read Input Status
类似读取线圈数据的01操作码,这个操作码读取的是离散输入的数据(如果忘记离散输入是什么的建议往上翻一下数据类型),请求体和响应体如下
Function Code | Starting Input Address | Input Count |
---|---|---|
0x02 | 2字节 | 2字节 |
Function Code | Byte Count | Input Data |
---|---|---|
0x02 | (Input Count + 7) / 8 | N字节 |
甚至数据返回大小端那些都完全一致,这里就不再赘述了
03 Read Holding Registers
由题显然可得,这个操作码读取的是持有寄存器内的数据,每个寄存器返回的是2字节的数据,请求体和响应体如下:
Function Code | Starting Register Address | Register Count |
---|---|---|
0x03 | 2字节 | 2字节 |
Function Code | Byte Count | Register Data |
---|---|---|
0x03 | Register Count * 2 | N字节 |
持有寄存器的地址的表示方法也在数据类型里面提到了,返回的数据则是正常大端返回的
04 Read Input Registers
和03类似,这里读取的是输入寄存器的数据
Function Code | Starting Input Address | Register Count |
---|---|---|
0x04 | 2字节 | 2字节 |
Function Code | Byte Count | Register Data |
---|---|---|
0x04 | Register Count * 2 | N字节 |
05 Force Single Coil
感觉用Force
有点奇怪,实际上就是写入单个线圈,不过线圈数据实际上是ON/OFF
,所以用Force
可能是这个原因,同时这个操作会覆盖控制器的内存保护状态和线圈的未启用状态
请求体和响应体如下:
Function Code | Starting Coil Address | Coil Value |
---|---|---|
0x05 | 2字节 | 0x0000 or 0xFF00 |
Function Code | Starting Coil Address | Coil Value |
---|---|---|
0x05 | 2字节 | 0x0000 or 0xFF00 |
其中线圈值只有0x0000
(OFF)和0xFF00
(ON)两种值,也就是所谓的0和1,同时线圈1的地址会被记作地址0x0000
06 Preset Single Register
看起来也不像人话,但是实际上就是写入单个寄存器,请求体和响应体和05也很像
Function Code | Starting Register Address | Register Value |
---|---|---|
0x06 | 2字节 | 2字节 |
Function Code | Starting Register Address | Register Value |
---|---|---|
0x06 | 2字节 | 2字节 |
07 Read Exception Status
这个操作会读取从机设备内置的8个异常状态线圈的内容,其中部分型号中部分线圈是有固定含义的,其它的则是用户自定义的,同时由于每个型号中的异常状态线圈的位置固定,所以不需要添加位置信息
一般的,控制器型号和异常线圈的位置与意义如下表所示:
Controller Model | Coil | Assignment |
---|---|---|
M84, 184/384, 584, 984 | 1-8 | 用户自定义 |
484 | 257 | 电池状态 |
258-264 | 用户自定义 | |
884 | 761 | 电池状态 |
762 | 内存保护状态 | |
763 | 远程IO连接状态 (RIO Health Status) | |
764-768 | 用户自定义 |
请求体和响应体则如下:
Function Code |
---|
0x07 |
Function Code | Coil Data |
---|---|
0x07 | 1字节 |
是的,就是这么简短,不过需要注意一点:返回的Coil Data是从高位线圈到低位线圈排列的,也就是说,假如我们的控制器型号是984,那么返回的线圈数据是从线圈8到线圈1这样排列的,这一点和我们01操作是一致的
15 Force Multiple Coils
你能写入单个线圈,那么为了方便肯定会有写入多个线圈的操作,也就是这个操作,请求体和响应体如下:
Function Code | Starting Address | Coil Count | Byte Count | Coil Value |
---|---|---|---|---|
0x0F | 2字节 | 2字节 | (Coil Count+7) / 8 | N字节 |
Function Code | Starting Address | Coil Count |
---|---|---|
0x0F | 2字节 | 2字节 |
和读取线圈数据一样,写入线圈数据的时候没每一组数据的排列都是用小端序排列的,也就是说如果我要写入线圈1~12,那么Coil Value
第一个字节是线圈8~1,而第二个字节会先在MSB填充4个0,再是线圈12~9的数据
16 Preset Multiple Registers
同理,有写入多个线圈自然有写入多个寄存器的操作,这个操作的请求体和响应体如下:
Function Code | Starting Address | Register Count | Byte Count | Register Value |
---|---|---|---|---|
0x10 | 2字节 | 2字节 | Register Count * 2 | N字节 |
Function Code | Starting Address | Register Count |
---|---|---|
0x10 | 2字节 | 2字节 |
数据具体排列和操作06类似
17 Report Slave ID
这个操作非常重要:它返回的是从机设备的控制器型号的描述、当前从机RUN指示器的当前状态以及其它特定的从机信息,可以说构造Modbus的信息嗅探包必定会使用到这个操作
这个操作的请求体和07一样简单:
Function Code |
---|
0x11 |
但是响应就会变得五花八门,不过大致有个框架:
Function Code | Byte Count | Slave ID | Run Indicator Status | Additional Data |
---|---|---|---|---|
0x11 | 取决于设备 | 取决于设备 | 0x00=OFF, 0xFF=ON | 取决于设备 |
那么Byte Count
我们自然是不关注的,因此我们需要了解的是后面的部分:
Slave ID和控制器型号的对应关系
Slave ID | Controller |
---|---|
0 | Micro 84 (M84) |
1 | 484 |
2 | 184/384 |
3 | 584 |
8 | 884 |
9 | 984 |
M84型号响应体解析
Micro 84型号的控制器的Byte Count
固定为8,其内容如下:
Byte | Contents |
---|---|
1 | Slave ID (0x00) |
2 | RUN指示器状态 |
3 | 当前端口号 |
4 | 内存大小(1=1K,2=2K) |
5~8 | 未使用(全0) |
484型号响应体解析
484型号的控制器的Byte Count
固定为5,其内容如下:
Byte | Contents |
---|---|
1 | Slave ID (0x01) |
2 | RUN指示器状态 |
3 | 系统状态 |
4 | 第一个配置字节 |
5 | 第二个配置字节 |
184/384型号响应体解析
这两个型号的控制器响应体中的Byte Count
要么是4,要么是74(0x4A),这取决于其J347从机接口是否配置完成,同时内部PIB表正常,正常则为74,否则为4
自Byte Count
后开始计算,前4个字节固定返回如下数据:
Byte | Contents |
---|---|
1 | Slave ID (0x02) |
2 | RUN指示器状态 |
3, 4 | 第0位(LSB)为0,第1位为内存保护状态(0为关闭,1为打开),第2、3位表示控制器型号(00表示184型号,10表示384型号),剩下的位未使用 |
剩下的70字节附加数据自然是和J347配置和正常PIB表相关的数据了(字节11~74包含了PIB表,数据仅在控制器运行时有效):
Byte | Contents |
---|---|
5, 6 | PIB表起始地址 |
7, 8 | 控制器序列号 |
9, 10 | 执行ID (Executive ID) |
11, 12 | 输出线圈数量最大值 |
13, 14 | 输出线圈启用表 (Output coil enable table) |
15, 16 | 输入线圈/运行表的地址 |
17, 18 | 输入线圈的数量 |
19, 20 | 输入线圈启用表 (Input coil enable table) |
21, 22 | 锁存器起始位置 (First latch number) (必须是16的倍数) |
23, 24 | 锁存器末尾位置 (Last latch number)(必须是16的倍数) |
25, 26 | 输入寄存器地址 |
27, 28 | 输入寄存器数量 |
29, 30 | 输出和持有寄存器数量 |
31, 32 | 用户逻辑地址 |
33, 34 | 输出线圈RAM表地址 |
35, 36 | 功能抑制掩码 (Function inhibit mask) |
37, 38 | 拓展函数路径地址 |
39, 40 | 数据传输路径地址 |
41, 42 | TCP复用器地址 (Address of traffic cop) |
43, 44 | 未使用 |
45, 46 | 功能抑制掩码 |
47, 48 | ‘A’模式历史表地址 |
49, 50 | DX打印机请求表 (Request table for DX printer) |
51, 52 | 序列组数量 |
53, 54 | 序列镜像表地址 |
55, 56 | 序列RAM地址 |
57, 58 | 50XX寄存器数量 |
59, 60 | 50XX表地址 |
61, 62 | 输出线圈RAM镜像地址 |
63, 64 | 输入RAM镜像地址 |
65, 66 | 延迟输出起始组 |
67, 68 | 延迟输出末尾组 |
69, 70 | 看门狗线 (Watchdog line,我哪知道这是啥) |
71, 72 | 锁存器在RAM上地址 |
73, 74 | 延迟输出组数量 |
有关Byte 41~42,我查了一下,Modbus里面的traffic cop好像就是TCP multiplexer,也就是TCP复用器,是能够允许2个(可能能做到更多?)主机和1个只允许连接1个主机的从机进行连接沟通的东西
584型号响应体解析
584型号的响应体的Byte Count
固定为9,其内容如下:
Byte | Contents |
---|---|
1 | Slave ID (0x03) |
2 | RUN指示器状态 |
3 | 第0页内存中4K区段数量 |
4 | 状态RAM中1K区段数量 |
5 | 用户逻辑区段数量 |
6 | (MSB→LSB) 端口1设置 || 端口2设置 || 端口1地址设置 || 端口2地址设置 || 未分配 || 持续扫描状态 (0=OFF, 1=ON) || 单次扫描状态 (0=OFF, 1=ON) || 24/16位节点(0=24位,1=16位) |
7 | (MSB→LSB) 电源 (必须为1,即ON) || RUN指示器状态 (0=ON, 1=OFF) || 内存保护状态 (0=ON, 1=OFF) || 电池OK (0=OK, 1=Not OK) || (第3~0位)未分配 |
8 | (MSB→LSB) 外部端口停止 (Peripheral port stop) || 未分配 || 模糊意识 (Dim awareness) || 非法外部介入 || 多速率求解表无效 || 起始节点未启动区段 || 状态RAM测试失败 || 未检测到逻辑末尾,或者区段数异常 |
9 | (MSB→LSB) 看门狗计时器过期 || 真实时钟错误 || CPU诊断失败 || 无效TCP复用器类型 || 无效节点类型 || 逻辑校验错误 || 备份校验错误 || 非法配置 |
笑死,Bytes 6~9我完全看不懂,都是瞎翻译的,而且下面的984型号也和584型号类似,所以Bytes 6~9可能还得各位大佬自行查阅官方文档了
884型号响应体解析
884型号的响应体的Byte Count
固定为8,其内容如下:
Byte | Contents |
---|---|
1 | Slave ID (0x08) |
2 | RUN指示器状态 |
3 | 当前端口号 |
4 | 用户逻辑+状态RAM的大小(单位为KB) |
5 | 保留 |
6 | (MSB→LSB) 保留 || 逻辑求解器绕过(1=不执行基础逻辑求解器) || 保留 || 扫描结束测试(1=测试扫描结束hook) || 映射器绕过(1=不执行基础映射器) || (位3~0)保留 |
7, 8 | 保留 |
984型号响应体解析
984型号的响应体的Byte Count
固定为9,其内容如下:
Byte | Contents |
---|---|
1 | Slave ID (0x09) |
2 | RUN指示器状态 |
3 | 第0页内存中4K区段数量 |
4 | 状态RAM中1K区段数量 |
5 | 用户逻辑区段数量 |
6 | (MSB→LSB) 端口1设置 || 端口2设置 || 端口1地址设置 || 端口2地址设置 || 未分配 || 持续扫描状态 (0=OFF, 1=ON) || 单次扫描状态 (0=OFF, 1=ON) || 24/16位节点(0=24位,1=16位) |
7 | (MSB→LSB) 电源 (必须为1,即ON) || RUN指示器状态 (0=ON, 1=OFF) || 内存保护状态 (0=ON, 1=OFF) || 电池OK (0=OK, 1=Not OK) || (第3~1位)未分配 || 内存缩减标志 (0=NO, 1=缩减) |
8 | (MSB→LSB) 外部端口停止 (Peripheral port stop) || 扩展内存奇偶校验失败 (984A, B, X) / 异常TCP复用器 (其它984型号) || 模糊意识 (Dim awareness) || 非法外部介入 || 异常区段计划表 || 起始节点未启动区段 || 状态RAM测试失败 || 未检测到逻辑末尾,或者区段数异常 |
9 | (MSB→LSB) 看门狗计时器过期 || 真实时钟错误 || CPU诊断失败 (984A, B, X) / 异常线圈使用表 (其它984型号) || S908远程IO头失败 || 无效节点类型 || 逻辑校验错误 || 在RUN模式禁用线圈 || 非法配置 |
这里解释一下Byte 7的内存缩减 (Memory downsize)是什么鬼:如果这个标志是1,那么就会计算页0和状态表的大小,计算方法是这样:
1
2 Page 0 size (16-bit words) = (Word 99 * 4096) – (Word 175 low byte * 16)
State table size (16 bit words) = (Word 100 * 1024) – (Word 175 high byte * 16)当然,里面的
word
全都是2字节长度的,如何里面使用的Word 99/100/175
存放的都是内存缩减相关的值
CAN流量分析
UDS流量分析
找的这篇文章:UDS Explained - A Simple Intro (Unified Diagnostic Services) – CSS Electronics
UDS概要
UDS协议,用于汽车的电子控制单元之间的通信,可用于启用诊断、固件更新、常规测试等
CoAP流量分析
这个页面提供了一个非常帅的Cheat Sheet的下载:CoAP — Constrained Application Protocol | Tools,各位可以看这个,更直观;而且这个页面里面有个PPT也是讲解ARM CoAP的,我觉得也很棒
CoAP概要
CoAP协议是为物联网中受约束的节点和网络设计的特化网络传输协议,是一个M2M (machine-to-machine)的传输协议,该协议自RFC 7252提出,默认使用的DTLS参数等价于一个3072位的RSA密钥,安全性有所保证
CoAP建立在UDP/DTLS协议上,而且非常像HTTP,同时代理端可以很容易地将CoAP和HTTP两种协议进行互换
消息结构
CoAP协议的整体消息结构长这样:
Bytes | Description |
---|---|
1 | Version | Type | |
2 | 操作码,指定方法 |
3~4 | 消息ID |
5~(TKL+4),如果有 | Token |
可选项 | |
MQTT流量分析
找了半天,全都在说MQTT的好处什么鬼的…总之官方文档在这里:MQTT Specification,我这里就只写MQTT v5.0的格式
MQTT概要
MQTT的通信是建立在TCP/IP协议上的,传输的时候是明文传输,所以从这一点而言风险很大,但是MQTT代理可以使用TLS协议加密通信
MQTT的通信简单来说是这样的路径:CONNECT -> SUBSCRIBE -> PUBLISH -> UNSUBSCRIBE -> DISCONNECT
:
- 首先是客户端和服务端建立连接
- 然后客户端对特定的话题 (Topic)进行订阅
- 客户端发布消息要使用
PUBLISH
,同时指定发布的话题,如此一来所有订阅了对应话题的客户端都会接收到该消息,然后客户端根据接收到的信息进行自己的一些逻辑操作 - 如果不想获取特定话题的消息了,可以
UNSUBSCRIBE
也就是取消订阅 - 最后断开和服务端的连接
当然一次连接里面客户端可能会订阅多个话题,接收多条信息并进行多种操作
消息结构
MQTT的控制包总是以这样的顺序排列:
固定报文头 (Fixed Header),必定包含 |
---|
可变报文头 (Variable Header),部分控制包包含 |
荷载 (Payload),部分控制包包含 |
固定头
固定头的格式是这样的:
Bytes | Description |
---|---|
1 | (Bits 7~4, MSB) MQTT控制包类型,(Bits 3~0, LSB) MQTT控制包参数 (Flags) |
2 or more… | MQTT控制包剩余长度 |
那么首先解释一下控制包类型有哪些(此处的描述我就保留英文全文了,以便各位理解Name
的意思):
Name | Value | Direction of flow | Description |
---|---|---|---|
Reserved | 0 | 禁止使用 (Forbidden) | 保留 |
CONNECT | 1 | 客户端→服务端(下记为C2S) | Connection request |
CONNACK | 2 | 服务端→客户端(下记为S2C) | Connect acknowledgment |
PUBLISH | 3 | C2S和S2C均可(下记B) | Publish message |
PUBACK | 4 | B | Publish acknowledgment (QoS 1) |
PUBREC | 5 | B | Publish received (QoS 2 delivery part 1) |
PUBREL | 6 | B | Publish release (Qos 2 delivery part 2) |
PUBCOMP | 7 | B | Publish complete (Qos 2 delivery part 3) |
SUBSCRIBE | 8 | C2S | Subscribe request |
SUBACK | 9 | S2C | Subscribe acknowledgment |
UNSUBSCRIBE | 10 | C2S | Unsubscribe request |
UNSUBACK | 11 | S2C | Unsubscribe acknowledgment |
PINGREQ | 12 | C2S | PING request |
PINGRESP | 13 | S2C | PING response |
DISCONNECT | 14 | B | Disconnect notification |
AUTH | 15 | B | Authentication exchange |
考虑到MQTT中所有的消息都是用过
PUBLISH
进行传输的,我们查看消息其实只需要看所有的PUBLISH
控制包,Wireshark里面也就是mqtt.msgtype == 3
这个过滤器,除非MQTT代理启用了TLS我们需要再用证书解一层TLS,否则我们关注的重点主要在Payload
的具体内容
其它流量
这一部分只是因为咱实在懒得分了www
USB流量分析
USB流量分析大多数是有关键盘和鼠标的,主要还是看USB URB下的HID Data
HID有4个字节,但是会由于鼠标的不同有所差异(比如咱惠普的游戏鼠标,就有7个字节),这里给一下常见的鼠标流量,来源CTF中我的USB键盘鼠标流量解密指南和脚本 - FreeBuf网络安全行业门户:
第一个字节代表按键,当取
0x00
时,代表没有按键、为0x01
时,代表按左键,为0x02
时,代表当前按键为右键。第二个字节可以看成是一个 signed byte 类型,其最高位为符号位,当这个值为正(小于127)时,代表鼠标水平右移多少像素,为负(补码负数,大于127小于255)时,代表水平左移多少像素。
第三个字节与第二字节类似,代表垂直上下移动的偏移。
第四个是扩展字节,关于滚轮的操作记录
- 0 - 没有滚轮运动
- 1 - 垂直向上滚动一下
- 0xFF - 垂直向下滚动一下
- 2 - 水平滚动右键一下
- 0xFE - 水平滚动左键单击一下
键盘流量如下,来源同上:
字节下标(我还没发现这个在哪儿)
- 0 : 修改键(组合键)
- 1 : OEM 保留
- 2~7 : 按键码
BYTE1
- bit0: Left Control 是否按下,按下为 1
- bit1: Left Shift 是否按下,按下为 1
- bit2: Left Alt 是否按下,按下为 1
- bit3: Left WIN/GUI 是否按下,按下为 1
- bit4: Right Control 是否按下,按下为 1
- bit5: Right Shift 是否按下,按下为 1
- bit6: Right Alt 是否按下,按下为 1
- bit7: Right WIN/GUI 是否按下,按下为 1
BYTE2 - 暂不清楚,有的地方说是保留位
BYTE3-BYTE8 - 这六个为普通按键
0b10(0x02) 和 0b100000(0x20)都是按下了shift键
这类题主要还是靠脚本做,但是了解一下自然是好的
Net-NTLM流量分析(哈希爆破)
由于SMTP这种邮箱传输真没啥好讲的,这里就跳过吧(bushi)
NTLM是Windows下的一个安全认证协议,原因是Windows内部只存储明文密码的哈希而非明文密码本身,交互流程大致分为协商(Negotiate)→质询(Challenge)→身份认证(Auth)这三步,具体实现可以参考这篇文章
那么我们知道了哈希算法,有一个字典,不就可以用字典哈希爆破了吗(笑)
这里以Net-NTLMv2哈希爆破为例,我们先获取以下信息:
- username
- domain
- ServerChallenge
- NTLMv2response(最开头处16个字节为NTProofstring)
其中除了ServerChallenge在质询流量包中可以找到,其余的信息都在身份认证流量包里面可以找到
然后我们按照这样的格式排列上述信息(假如保存在一个叫crackme.txt文件里)
1 | username::domain:ServerChallenge:NTProofstring:NTLMv2response_without_NTProofstring_at_front |
然后假如我们确认密码在rockyou字典里面,那我们可以用hashcat进行哈希爆破:
1 | hashcat -m 5600 crackme.txt rockyou.txt |
5600指的是Net-NTLMv2,5500是Net-NTLMv1,两个需要的信息不一样,Net-NTLMv1需要的是:
1 | username::domain:ANSI Password:Union Password:Challenge |
感谢爹地qsdz提供的半自动化脚本(此处以geekchallenge2023 窃听风云为例),以后可以不用瞪眼法做这种题咯(这就回去补scapy)
1 | import base64 |
5G传输协议流量分析(以NAS-5GS为例)
(只能说是不懂装懂了www)
只凭借咱浅薄的理解,整个NAS-5GS的流程大致可以分为配置→注册请求→认证→协商→通信这几步,这个协议下的参数巨多,要翻起来真的头都大,就稍微简单讲讲好了
首先是NG Setup,擦除NG-RAN节点和AMF的应用程序级别配置数据,并替换为接收到的数据,同时清楚NG-RAN节点上AMF超载状态信息;如果里面出现错误(如国家与网络运营商参数识别错误),可能导致无法正常进行UE注册请求
然后就是UE注册请求,其中包括了所支持的加密和完保算法,5GS注册类型,NAS key设置等信息,当然如果不支持至少一个加密和完保算法的话,UE会以5GMM Cause: UE security capabilities mismatch拒绝注册请求
注册完成后如果开启UE安全模式,就是进行密钥的协商,即确认最终使用的加密和完保算法
之后InitialContextSetup后就开始通信了
蛮复杂的,协议完全看不懂QAQ,不过大致也能明白个七七八八…吧?
TLS流量分析(Under Construction)
TLS协议一般用于HTTPS通信
蓝牙流量分析
WLAN流量分析
802.1X协议
EAPOL协议
ADS-B协议分析
前段时间强网杯看到的,一坨TCP流量,目前忙着搞期末没时间鼓捣,等放假有空再说(挖坑)
前面的区域,等想起来还有啥再探索吧~
- 本文作者: 9C±Void
- 本文链接: https://cauliweak9.github.io/2023/11/21/FlowAnalysis/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!