流量分析
pcap 流量包修复
如果打开流量包出现异常现象,例如:
"The capture file appears to be damaged or corrupt."
,说明流量包损坏,需要修复
在线修复工具:pcapfix - online pcap / pcapng repair service
上述网站修复完毕后,点击 "Get your repaired PCAP-file here."
即可下载流量包
Wireshark 的使用
Wireshark 是用来分析 pcap、pcapng 流量包的网络嗅探器,通常数据包里充满着大量无关的流量信息,因此对流量数据进行分类和过滤十分重要
Wireshark 下载地址:wireshark.org
参考文献:
Wireshark 的使用主要分为数据包筛选、数据包搜索、数据包还原、数据提取四部分
数据包筛选
Wireshark 支持的各类运算符:
运算符 | 意义 |
---|---|
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
and、&& | 与 |
or、|| | 或 |
xor、^^ | 异或 |
not、! | 非 |
IP 地址筛选
- 筛选源 ip 地址
ip.src == 源ip地址
- 筛选目的 ip 地址
ip.dst == 目的ip地址
- 筛选所有源 ip 地址或目的 ip 地址
ip.addr == ip地址
- 筛选除源 ip 地址以外的所有 ip 地址
not ip.src == 源ip地址
MAC 地址筛选
- 筛选源 MAC 地址
eth.dst == 源MAC地址
- 筛选目的 MAC 地址
eth.src == 目的MAC地址
- 筛选所有源 MAC 地址或目的 MAC 地址
eth.addr == MAC地址
协议筛选
- 筛选 tcp 协议流量
tcp
- 筛选 udp 协议流量
udp
- 筛选 http 协议流量
http
- 筛选 arp/icmp/ftp/dns/ip 协议流量,同理
arp/icmp/ftp/dns/ip
- 筛选 http 协议或 icmp 协议流量
http or icmp
端口筛选
- 筛选源端口为 80 端口的 tcp 协议流量
tcp.srcport == 80
- 筛选目的端口为 80 端口的 tcp 协议流量
tcp.dstport == 80
- 筛选源端口为 80 端口的 udp 协议流量
udp.srcport == 80
- 筛选目的端口为 80 端口的 udp 协议流量
udp.dstport == 80
- 筛选源端口或目的端口为 80 端口的所有 tcp 协议流量
tcp.port == 80
- 筛选源端口或目的端口为 80 端口的所有流量
tcp.port == 80 or udp.port == 80
- 筛选端口范围为 1 ~ 80 端口的所有 tcp 协议流量
tcp.port >= 1 and tcp.port <= 80
数据包长度筛选
- 筛选长度为 20 的 tcp 协议流量数据包(不包括 tcp 本身,tcp 下面那块数据包长度)
tcp.length == 20
- 筛选长度大于 20 的 udp 协议流量数据包(udp 本身固定长度 8 与 udp 下面那块数据包长度之和)
udp.length >= 20
- 筛选长度为 20 的整个流量数据包(从 eth 开始到最后)
frame.len == 20
- 筛选长度为 20 的 ip 流量包
ip.len == 20
HTTP 请求筛选
- 筛选 http 请求方法为 get 的流量
http.request.method == "GET"
- 筛选 http 请求方法为 post 的流量
http.request.method == "POST"
- 筛选 http 请求 URL 为
/img/flag.gif
的流量
http.request.uri == "/img/flag.gif"
- 筛选 http 请求内容包含
flag
的流量(contains 仅支持一个)
http contains "flag"
- 筛选 http 请求内容包含
ctf
或flag
的流量(matches 支持多个)
http matches "ctf|flag"
数据包搜索
Wirshark 使用快捷键 “Ctrl + F”可以进行关键字搜索
支持十六进制、字符串和正则表达式等方式,字符串搜索比较常用
搜索栏左侧可以选择分组列表、分组详情和分组字节流,分别对应搜索 Wireshark 界面的三个不同区域
数据包还原
Wireshark 可以通过追踪流将 http 或 tcp 流量集合在一起并还原成原始数据
选中一个流量包,右键追踪流:
数据提取
Wireshark 支持提取通过 http 传输(上传/下载)的文件内容
- 在菜单栏的文件选项中,选择
导出对象 --> HTTP
:
选择想要导出的文件,点击保存即可:
- 在分组详情窗口中,选择要导出的对象,
右键 --> 导出分组字节流
如果是菜刀下载文件的流量,需要删除分组字节流前开头和结尾的
X@Y
字符,否则下载的文件会出错
首先在分组详情窗口中,选择要导出的对象,右键 --> 显示分组字节
然后在弹出的窗口中设置开始和结束的字节(原字节数开头加 3,结尾减 3),导出保存
USB 流量
USB 流量数据存放在
Leftover Capture Data
域中,可在 Wireshark 的分组详情窗口中查看可参考的工具:FzWjScJ/knm: 鼠标键盘流量包取证
键盘流量
键盘流量的数据长度为 8 个字节,集中在
Leftover Capture Data
域的第 3 个字节中
在 Kali Linux 下通过 tshark
命令将键盘数据提取出来
tshark -r usb.pcap -T fields -e usb.capdata > usbdata.txt
tshark -r usb.pcap -T fields -e usb.capdata | sed '/^\s*$/d' > usbdata.txt #提取并去除空行
Kali Linux 自带 tshark,Ubuntu 则需要手动安装:
sudo apt update sudo apt install -y tshark
提取出来的数据有冒号时,[6:8] 代表键盘击键信息;没有冒号时,[4:6] 代表键盘击键信息
加了冒号的数据类似于:00:00:03:00:00:00:00:00
,如果提取出来的数据不带冒号,可以通过脚本添加冒号:
f = open('usbdata.txt', 'r')
fi = open('out.txt', 'w')
while 1:
a = f.readline().strip()
if a:
if len(a) == 16: # 如果是鼠标流量 len 改为 8
out = ''
for i in range(0, len(a), 2):
if i + 2 != len(a):
out += a[i] + a[i + 1] + ":"
else:
out += a[i] + a[i + 1]
fi.write(out)
fi.write('\n')
else:
break
fi.close()
根据加了冒号的键盘数据,使用脚本一和脚本二还原击键信息
脚本一:
mappings = {
0x04: "A", 0x05: "B", 0x06: "C", 0x07: "D", 0x08: "E", 0x09: "F", 0x0A: "G", 0x0B: "H", 0x0C: "I", 0x0D: "J",
0x0E: "K",
0x0F: "L", 0x10: "M", 0x11: "N", 0x12: "O", 0x13: "P", 0x14: "Q", 0x15: "R", 0x16: "S", 0x17: "T", 0x18: "U",
0x19: "V",
0x1A: "W", 0x1B: "X", 0x1C: "Y", 0x1D: "Z", 0x1E: "1", 0x1F: "2", 0x20: "3", 0x21: "4", 0x22: "5", 0x23: "6",
0x24: "7",
0x25: "8", 0x26: "9", 0x27: "0", 0x28: "\n", 0x2a: "[DEL]", 0X2B: " ", 0x2C: " ", 0x2D: "-", 0x2E: "=", 0x2F: "[",
0x30: "]", 0x31: "\\", 0x32: "~", 0x33: ";", 0x34: "'", 0x36: ",", 0x37: "."}
nums = []
keys = open('out.txt')
for line in keys:
if line[0] != '0' or line[1] != '0' or line[3] != '0' or line[4] != '0' or line[9] != '0' or line[10] != '0' or line[12] != '0' or line[13] != '0' or line[15] != '0' or line[16] != '0' or line[18] != '0' or line[19] != '0' or line[21] != '0' or line[22] != '0':
continue
nums.append(int(line[6:8], 16))
keys.close()
output = ""
for n in nums:
if n == 0:
continue
if n in mappings:
output += mappings[n]
else:
output += '[unknown]'
print('output :')
print(output)
脚本二:
normalKeys = {
"04": "a", "05": "b", "06": "c", "07": "d", "08": "e",
"09": "f", "0a": "g", "0b": "h", "0c": "i", "0d": "j",
"0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o",
"13": "p", "14": "q", "15": "r", "16": "s", "17": "t",
"18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y",
"1d": "z", "1e": "1", "1f": "2", "20": "3", "21": "4",
"22": "5", "23": "6", "24": "7", "25": "8", "26": "9",
"27": "0", "28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t",
"2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[", "30": "]", "31": "\\",
"32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".",
"38": "/", "39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>",
"3e": "<F5>", "3f": "<F6>", "40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>",
"44": "<F11>", "45": "<F12>"}
shiftKeys = {
"04": "A", "05": "B", "06": "C", "07": "D", "08": "E",
"09": "F", "0a": "G", "0b": "H", "0c": "I", "0d": "J",
"0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O",
"13": "P", "14": "Q", "15": "R", "16": "S", "17": "T",
"18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y",
"1d": "Z", "1e": "!", "1f": "@", "20": "#", "21": "$",
"22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>",
"2d": "_", "2e": "+", "2f": "{", "30": "}", "31": "|", "32": "<NON>", "33": "\"",
"34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?", "39": "<CAP>", "3a": "<F1>",
"3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>", "40": "<F7>",
"41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
output = []
keys = open('out.txt')
for line in keys:
try:
if line[0] != '0' or (line[1] != '0' and line[1] != '2') or line[3] != '0' or line[4] != '0' or line[
9] != '0' or line[10] != '0' or line[12] != '0' or line[13] != '0' or line[15] != '0' or line[16] != '0' or \
line[18] != '0' or line[19] != '0' or line[21] != '0' or line[22] != '0' or line[6:8] == "00":
continue
if line[6:8] in normalKeys.keys():
output += [[normalKeys[line[6:8]]], [shiftKeys[line[6:8]]]][line[1] == '2']
else:
output += ['[unknown]']
except:
pass
keys.close()
flag = 0
print("".join(output))
for i in range(len(output)):
try:
a = output.index('<DEL>')
del output[a]
del output[a - 1]
except:
pass
for i in range(len(output)):
try:
if output[i] == "<CAP>":
flag += 1
output.pop(i)
if flag == 2:
flag = 0
if flag != 0:
output[i] = output[i].upper()
except:
pass
print('output :' + "".join(output))
鼠标流量
鼠标流量的数据长度为 4 个字节,集中在
Leftover Capture Data
域的第 3 个字节中
- 第 1 个字节代表按键,当取 0x00 时,代表没有按键;为 0x01 时,代表按左键,为 0x02 时,代表按右键
- 第 2 个字节可以看成是一个 signed byte 类型,其最高位为符号位,当这个值为正时,代表鼠标水平右移多少像素,为负时,代表水平左移多少像素
- 第 3 个字节与第 2 字节类似,代表垂直上下移动多少像素
与键盘流量类似,首先使用 tshark
提取鼠标数据:
tshark -r usb2.pcap -T fields -e usb.capdata > usbdata.txt
tshark -r usb2.pcap -T fields -e usb.capdata | sed '/^\s*$/d' > usbdata.txt #提取并去除空行
如果提取出来的数据没有加冒号,使用脚本添加:
f = open('usbdata.txt', 'r')
fi = open('out.txt', 'w')
while 1:
a = f.readline().strip()
if a:
if len(a) == 8: # 如果是键盘流量 len 改为 16
out = ''
for i in range(0, len(a), 2):
if i + 2 != len(a):
out += a[i] + a[i + 1] + ":"
else:
out += a[i] + a[i + 1]
fi.write(out)
fi.write('\n')
else:
break
fi.close()
测试信息隐藏位置,根据脚本中 btn_flag
取何值时能得到一系列坐标来判断信息隐藏在鼠标左键还是右键中:
nums = []
keys = open('out.txt', 'r')
f = open('xy.txt', 'w')
posx = 0
posy = 0
for line in keys:
if len(line) != 12 :
continue
x = int(line[3:5], 16)
y = int(line[6:8], 16)
if x > 127 :
x -= 256
if y > 127 :
y -= 256
posx += x
posy += y
btn_flag = int(line[0:2], 16) # 1 for left , 2 for right , 0 for nothing
if btn_flag == 2 : # 1 代表左键
f.write(str(posx))
f.write(' ')
f.write(str(posy))
f.write('\n')
f.close()
使用 gnuplot
将 xy.txt 里的坐标转化成图像
gnuplot
gnuplot> plot "xy.txt"
如果没有安装 gnuplot
手动安装即可(需要同时安装 gnuplot
和 gnuplot-x11
才能画出图):
sudo apt update && sudo apt upgrade
sudo apt install gnuplot gnuplot-x11
gnuplot -v # 验证安装
Windows 版 gnuplot
下载地址:gnuplot homepage