随便写点之前做流量分析总结的经验,不保证有用,欢迎各位补充捏
总的来说咱目前遇到的流量分析可以大致分为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(专家信息)
GUI工具栏里面可以通过
分析-专家信息
直接进行查看,常用于提取异常/错误流量包,目前咱比较常用的是_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流量中是否也在传输大量的数据,因为可能文件太大,需要分段传输
有的时候流量中的协议是有问题的,这个时候可能需要我们自己去“解码为(Decode as)”里面将特定的IP和端口与特定的协议进行绑定,然后再进行分析
渗透流量
如最开头所说,这类流量常和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之类的等之后什么时候再补充
Cobalt Strike流量分析
(挖坑未填,To: Be Continued…)
C2流量简述
没啥干货,建议当没看到
线上比赛的时候遇到的流量分析有好一些是C2流量分析,这种流量一般需要对着C2源码去看(这些C2一般是Github开源的),不然确实不知道它是怎么打的、流量是怎么加密的、默认密钥是怎么样的,等等等等
当然有的时候是需要你分析远控木马程序(一般是ELF文件)再对流量进行分析,因此可能需要一点逆向的基础,不然很有可能就卡死了
因为这些流量很多是基于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字节 |
持有寄存器的地址的表示方法也在数据类型里面提到了,返回的数据则是正常大端返回的
对于浮点数(IEEE754)的保存和读取,由于一个寄存器只有16位,因此通常需要多个寄存器进行浮点数的保存,读取的时候自然也需要读取多个寄存器
例如2024年CISCN决赛ICS的“写入寄存器的值”一题,包No.17478 & No.18216中写入的值即为题目所指定的写入的寄存器的值,该值为32位大端保存的IEEE754浮点数
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
存放的都是内存缩减相关的值
23 Read/Write Multiple Registers
一个同时读取和写入寄存器的操作,实际上就是缝合怪(笑),需要注意的是写入操作优先于读取操作执行,因此如果需要读特取定寄存器的数据就需要注意寄存器覆盖相关的可能性了(尤其是使用Modbus Poll的时候)
请求体和响应体如下:
Function Code | Read Starting Address | Read Register Count | Write Starting Address | Write Register Count | Write Byte Count | Write Register Values |
---|---|---|---|---|---|---|
0x17 | 2字节 | 2字节 | 2字节 | 2字节 | 2* Write Register Count | N字节 |
Function Code | Byte Count | Read Registers Value |
---|---|---|
0x17 | Read Register Count*2 | N字节 |
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
的具体内容
TPKT协议
TPKT以TCP作为其传输协议,常用端口号为102,一般用于在TCP和COTP之间建立桥梁
TPKT结构如下:
Bytes | Description |
---|---|
1 | TPKT Version |
2 | Reserved (0x00) |
3~4 | Length of TPKT & Payload |
5~? | Payload |
COTP协议(暂时不会去填完)
本人因为过于懒惰因此会有不准确的地方,还是推荐各位自行阅读官方规范文档:RFC 905: ISO Transport Protocol specification ISO DP 8073
某种程度上可以说是工控的TCP协议,但是两者有一个比较显著的差别:TCP是基于Stream(流)进行数据传输的,而COTP是基于Packet(包)进行数据传输,因此在COTP中数据接收者会收到一个和发送者完全一致的数据边界
消息结构
COTP是以TPDU(Transport Protocol Data Units,传输协议数据单元)为单位进行数据传输的,其整体结构满足如下的框架:
Length | Fixed Part | Variable Part | Data Field |
---|---|---|---|
1字节 | n字节 | m字节 | p字节 |
其中前三者整体被称为Header,对于不同的TPDU(因为本人过懒,TPDU在本部分统一称为PDU)类型,它们的Fixed Part和Variable Part各不相同
Variable Part是定义Parameters的地方,其结构如下:
Parameter Code | Parameter Length | Parameter Value |
---|---|---|
1字节 | 1字节 | Length字节 |
如果一个PDU包内存在Variable Part,则其必定包含1个或多个Parameters
握手包(PDU类型:CC/CR)
Length | PDU Type | Dest. Ref. | Src. Ref. | Options | Variable Part |
---|---|---|---|---|---|
1字节 | 1字节 | 2字节 | 2字节 | 1字节 | Length-7字节 |
- Length:COTP包的长度,长度计算不包括Length字段本身
- Destination Reference:目标参考,用于标识接收方设备的参考值,类似于标识符以唯一标识接收端,在PDU类型为CR的时候该部分置0
- Source Reference:源参考,和目标参考类似,该字段用于唯一标识发送端
- Variable Part:一般的COTP握手包包含3个Parameters:Source TSAP (0xc1),Destination TSAP (0xc2)和TPDU Size (0xc0),其中TPDU Size表示之后的传输过程中的TPDU包的最大长度(默认为128字节)
TSAP:Transport Service Access Point(传输服务访问点),其名字是一个可变长度的二进制数据(一般为一串有意义的字符串),一般会在连接建立的时候进行配对,并在之后的数据传输中被一个2字节长的字段(目标/源参考)代替,作用类似TCP中的端口
Options:包含通信类型、拓展格式和显示流控制三部分选项,通信类型分为0~4共5类:
Class 0:简单类 Class 1:基本错误恢复类 Class 2:复用类 Class 3:错误恢复与复用类 Class 4:错误检测与恢复类 每一类都有一系列可以进行的操作和不能执行的操作,具体看下面的PDU类型
PDU Type:协议数据单元类型,有以下类型:
PDU Type Description 0x01 ED Expedited Data,加急数据 0x02 EA Expedited Data Acknowledgement,加急数据确认 0x04 UD User Data,用户数据 0x05 RJ Reject,拒绝 0x06 AK Data Acknowledgement,数据确认 0x07 ER TPDU Error,TPDU错误 0x08 DR Disconnect Request,断开请求 0x0C DC Disconnect Confirm,断开确认 0x0D CC Connect Confim,连接确认 0x0E CR Connect Request,连接请求 0x0F DT Data,数据传输 当然,对于不同的Classes类型,其支持的上述的PDU类型也是有差别的,这里我直接从RFC文档里面截下图好了,实在懒得搓了
NF:当选项中的
No Explicit Flow Control
选项被选用的时候不可使用该数据类型NRC:当选项中的
Receipt Confirmation
选项被选用的时候不可使用该数据类型UD类型是可选的,但是在Class 0中是不合法的,对于支持的其它Classes该字段的数据域不可超过32个octet(8位的字节)
数据传输包(PDU类型:DT)
对于不同的传输类型,DT包的结构也不一样,
Classes 0, 1:
Length | PDU Type | TPDU Number & EOT | User Data |
---|---|---|---|
1字节 | 1字节 | n字节(取决于PDU类型) | Length字节 |
Classes 2, 3, 4 (Normal Format):
Length | PDU Type | Dest. Ref. | TPDU Number & EOT | Variable Part | User Data |
---|---|---|---|---|---|
1字节 | 1字节 | 2字节 | 1字节 | m字节 | n字节 |
Classes 2, 3, 4 (Extended Format):
Length | PDU Type | Dest. Ref. | TPDU Number & EOT | Variable Part | User Data |
---|---|---|---|---|---|
1字节 | 1字节 | 2字节 | 4字节 | m字节 | n字节 |
PDU Type/Destination Reference:见上
Segment Data:COTP传输的载荷数据
Variable Part:一个Checksum参数,要满足如下的两条算式:(因为太懒,所以用python解释)
1
2
3
4
5
6
7# TPDU_Packet: bytes
sum_0 = 0
sum_1 = 0
for i in range(len(TPDU_Packet)):
sum_0 += TPDU_Packet[i]
sum_1 += TPDU_Packet[i] * i
assert(sum_0 == 0 and sum_1 == 0)TPDU Number:用于标明TPDU包的顺序,其中TPDU Number小的在前,大的在后,长度7bits
EOT:若为当前TPDU传输序列的最后一个包则置1,否则置0,长度1bit
IEC104协议
MMS协议
S7COMM协议
参考文章:The Siemens S7 Communication - Part 1 General Structure – GyM’s Personal Blog,(PDF) You Are What You Attack: Breaking the Cryptographically Protected S7 Protocol
S7COMM协议本身是依赖于ISO传输服务的,其协议数据被TPKT和COTP两个协议所包装,至于这两个协议前面已经基本提到了,因此此处主要注重的还是S7COMM协议
消息结构
S7COMM协议的总体消息结构是非常简单的:
Header | Parameters | Data |
---|---|---|
10~12字节 | m字节 | n字节 |
对于后面的参数长度和数据长度,都会在Header里面标识出来,Header的具体结构如下:
Protocol ID | Message Type | Reserved | PDU Reference | Param Length | Data Length | Error Class | Error Code |
---|---|---|---|---|---|---|---|
1字节(固定0x32 ) |
1字节 | 2字节(固定0x0000 ) |
2字节 | 2字节 | 2字节 | 1字节 | 1字节 |
Message Type:消息类型(有时又称ROSCTR类型),常见的有4种(因为我不知道有没有其它的消息类型):
- Job (0x01):主机发送的请求,包括对内存或区块的读/写操作、设备的启动/停止以及信息传输的建立等
- Ack (0x02):从机发送的没有数据域的简单确认包
- Ack-Data (0x03):与上面的确认有所区分,该类确认包有可选的数据域,一般包含一个Job Request包的响应
- Userdata (0x07):原协议的拓展,参数域包含请求/响应ID,一般用于代码编写/调试、系统状态列表(SZL, System Status List)的读取、安全函数、时间设定、循环读取等
PDU Reference:用于连接响应和对应的请求,由主机进行生成,每次传输都会递增,使用小端序
Parameter Length & Data Length:正如其名,就是声明参数域和数据域的长度,使用大端序
Error Class & Error Code:只会在
Ack-Data
包中出现,相关的常量可以看Appendix A(不如直接看这篇链接:gmiru.com/resources/s7proto/constants.txt)
接下来正式进入Parameter里面的各种情况:
0xF0 Setup Communication
格式很简单:
Function Code | Reserved | Max AMQ Calling | Max AMQ Called | PDU Length |
---|---|---|---|---|
固定0xf0 | 固定0x00 | 2字节 | 2字节 | 2字节 |
PDU Length字段我们见过很多次了,这里就不再赘述;中间的AMQ则是Acknowledgement Message Queue
的意思(大概),人话就是可以并行的Job能有多少个
0x04/0x05 Read/Write Variable
S7进行读写的时候需要制定变量的内存区、地址(偏移)和大小(数据类型),因此首先得先简单引入一下S7的寻址模型,这里简单翻译一下大佬分析的S7内存区的常用部分:
Merker:[M]
Marker
的德语,英文里用Flag
是一个意思,是一系列用于标记状态/信息的值(所以会有Merker bit
的说法)Data Block:[DB] 设备中最常用于存储不同功能所需数据的地方,而数据块都会被编号,且是地址的一部分
Input:[I] 被映射到内存中的数字&模拟输入模块的值
Output:[Q] 和Input类似,也是被映射到内存的输出值
Counter:[C] PLC程序中使用的不同的计数器的值
Timer:[T] PLC程序中使用的不同的计时器的值
变量的长度由其类型决定,其中常用的五类是:
- BIT:[X] 一个位
- WORD:两字节的无符号整型
- DINT:四字节的无符号整型(Double Int)
- REAL:四字节的IEEE浮点数
- COUNTER:PLC程序计数器使用的计数器类型
所以假如一个变量的地址为DB123X 2.1
,那么它访问的是数据块123 (Data Block 123)的第2个字节 (X 2
)的第1位 (.1
)(从0开始计数)
回到协议实现的变量读写操作,S7协议支持在单次信息中请求多个变量的读取,而这种读取又分为3种不同的寻址模式:
- any-type:默认寻址模式,被用于请求任意的变量,对每一个变量都需要所有的参数(即区域、地址和变量类型)
- db-type:一个为了对DB区域的变量进行寻址而设计的特殊模式,比
any-type
的寻找更紧凑 - symbolic-addressing:适用于S7-1200/1500系列的设备,允许使用预定义的符号名称对变量进行寻址
当然,在所有的寻址模式下,参数头的结构是相同的:
- Function Code:1字节
- Item Count:
- Request Item:
Omron FINS协议
JT/T 808协议
CAN协议
参考资料:CAN bus - Wikipedia,CAN Bus - The Ultimate Guide – CSS Electronics
喵的想看ISO标准需要爆金币,小登我可没办法凭空变出2K来
ヽ(*。>Д<)o゜
CAN概要
CAN bus (Controller Area Network)是汽车的“神经系统”,它为汽车中的各个节点(或者称为电子控制单元 (Electronic Control Units, ECUs))提供一个通信的渠道,这样各个ECU之间就可以通过CAN bus进行信息的传递与分享
那么ECU是什么呢?它可以是很多东西:引擎控制单元、安全气囊、音响系统等,这么多东西要是互相连接起来的话肯定绕死个人,所以干脆整一个总线用于通信吧——这就是CAN bus(后文将以CAN总线代替CAN bus)
CAN总线有2条线:CAN low和CAN high(后文将以低线/高线代称),ECU的数据传输是以广播的形式发送的,而总线上的其它ECU都能获取到这些数据,但是是否接收还是忽略就得看各ECU自己的逻辑了
消息结构
感觉叫“CAN帧结构”会更好,嘛,懒得管了www
CAN协议分为CAN 2.0A和CAN 2.0B,两者的差距除了Identifier的长度之外完全一致,下面是CAN 2.0A协议帧的结构:
SOF | ID | RTR | Control | Data | CRC | ACK | EOF |
---|---|---|---|---|---|---|---|
1位 | 11位 | 1位 | 6位 | 0~64位 | 16位 | 2位 | 7位 |
- SOF:Start of Frame,固定为0,表示有一个CAN节点发送了信息
- ID:帧的标识值,其中CAN 2.0A的ID长度为11位,而CAN 2.0B的是29位;越小的标识值有越大的权重,使用小端序
- RTR:Remote Transmission Request,标志着一个节点是发送数据还是向另一个节点请求专用数据
- Control:控制位,包含1位标识是否使用拓展标识的Identifier Extension Bit (IDE)(0为不启用,即使用CAN 2.0A)、1位错误信息位
- Data:
- CRC:
- ACK:
- EOF:
请注意:由于ID使用小端序,RTR和Control
对于拓展的CAN 2.0B协议,它的协议帧结构如下:
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 |
可选项 | |
UDS协议
找的这篇文章:UDS Explained - A Simple Intro (Unified Diagnostic Services) – CSS Electronics
UDS概要
UDS协议,用于汽车的电子控制单元之间的通信,可用于启用诊断、固件更新、常规测试等
LIN协议
FlexRay协议
MOST协议
其它流量
这一部分只是因为咱实在懒得分了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键
这类题主要还是靠脚本做,但是了解一下自然是好的
2025.4.8更新:已经遇到过好几次键鼠以外的USB流量分析了,而且很多是需要自己根据HID协议进行解析的,这里给一下最新的HID v1.6的Spec:HID Usage Tables 1.6 | USB-IF
至于各种HID设备的流量解析这里就不再更新了,除非有好心人愿意捐赠我一些供我研究学习(
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通信
蓝牙流量分析
HCI层
L2CAP协议
RFCOMM协议
OBEX协议
ATT协议
WLAN流量分析
802.1X协议
aircrack-ng
EAPOL协议
ADS-B协议分析
前段时间强网杯看到的,一坨TCP流量,目前忙着搞期末没时间鼓捣,等放假有空再说(挖坑)
Syscall流量分析(我自己起的名字)
在到处翻的时候发现了Wireshark团队出了一个新的软件:Stratoshark,简单看了一下,应该是能以GUI显示Sysdig抓到的scap文件
Sysdig主要是在操作系统层面捕获Linux的系统调用操作,且对容器有原生支持,因此可以将这个安装到云服务器上然后对服务器的系统调用进行监控与查看,个人感觉可以用于应急响应
回到Stratoshark,它支持通过SSH对服务器进行系统调用操作的捕获,同时还有两个Falco的插件,其中cloudtrail适用于AWS服务,用于分析包含cloudtrail事件的日志,并根据日志释放sinsp/scap事件;而gcpaudit则是对应谷歌云服务的日志审计与监控
由于本人也只是简单用过一点点,因此这里也只能简单说一下Stratoshark怎么用,不过总体用法还是和Wireshark差不多,这里拿一个最近随便抓的scap包举例好了

我们可以看到在过滤器那一行最右侧有4个选项栏,里面的是一些预设的过滤器,这些过滤器可以过滤出对应的操作,比如Cmd-Interactive Command Executions
就能过滤出在交互式shell中所执行的命令,里面的过滤器长这个样子:(evt.type == "execve") and evt.dir == "<" and (proc.pname == "bash" or proc.pname == "zsh" or proc.pname == "tcsh" or proc.pname == "ksh" or proc.pname == "fish")
相信还是比较直观的,前面就是事件的类型,然后是事件的方向,最后就是父进程名,结果会长这样:

一切都很好,而且我们还能看到包括环境变量在内的各种信息,不可谓不厉害,但是我们命令执行的结果呢?执行的时候肯定也是会需要进行系统调用的,因此我对上面这个过滤器稍微改动了一下:(evt.type == "execve" or evt.type == "write" or evt.type == "read") and evt.dir == "<" and (proc.pname == "bash" or proc.pname == "zsh" or proc.pname == "tcsh" or proc.pname == "ksh" or proc.pname == "fish")

实际上就是额外过滤出了读和写的事件,事件会多一点,但是能很直观的看到系统执行各种命令操作的时候系统层面的操作是怎么样的,我觉得挺好的
至于剩下的就靠各位自行摸索了ο(=•ω<=)ρ⌒☆
Stratoshark的SSH capture好难用啊…而且还需要目标主机上面有Sysdig或者其它能抓scap的工具,投降喵::>_<::
Appendices
Appendix A — S7COMM的各种Constants
直接复制粘贴这个链接的:gmiru.com/resources/s7proto/constants.txt,当然你们也可以看这个repo:s7commwireshark/src/s7comm/packet-s7comm.c at local-trunk · JuergenKosel/s7commwireshark
1 | ## |
前面的区域,等想起来还有啥再探索吧~
- 本文作者: 9C±Void
- 本文链接: https://cauliweak9.github.io/2023/11/21/FlowAnalysis/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!