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

参考文献:

  1. CTF流量分析之wireshark使用 - FreeBuf网络安全行业门户
  2. 奇安信攻防社区-CTF流量分析

Wireshark 的使用主要分为数据包筛选数据包搜索数据包还原数据提取四部分

数据包筛选

Wireshark 支持的各类运算符:

运算符意义
==等于
!=不等于
>大于
<小于
>=大于等于
<=小于等于
and、&&
or、||
xor、^^异或
not、!

IP 地址筛选

  1. 筛选源 ip 地址
ip.src == 源ip地址
  1. 筛选目的 ip 地址
ip.dst == 目的ip地址
  1. 筛选所有源 ip 地址或目的 ip 地址
ip.addr == ip地址
  1. 筛选除源 ip 地址以外的所有 ip 地址
not ip.src == 源ip地址

MAC 地址筛选

  1. 筛选源 MAC 地址
eth.dst == 源MAC地址
  1. 筛选目的 MAC 地址
eth.src == 目的MAC地址
  1. 筛选所有源 MAC 地址或目的 MAC 地址
eth.addr == MAC地址

协议筛选

  1. 筛选 tcp 协议流量
tcp
  1. 筛选 udp 协议流量
udp
  1. 筛选 http 协议流量
http
  1. 筛选 arp/icmp/ftp/dns/ip 协议流量,同理
arp/icmp/ftp/dns/ip
  1. 筛选 http 协议或 icmp 协议流量
http or icmp

端口筛选

  1. 筛选源端口为 80 端口的 tcp 协议流量
tcp.srcport == 80
  1. 筛选目的端口为 80 端口的 tcp 协议流量
tcp.dstport == 80
  1. 筛选源端口为 80 端口的 udp 协议流量
udp.srcport == 80
  1. 筛选目的端口为 80 端口的 udp 协议流量
udp.dstport == 80
  1. 筛选源端口或目的端口为 80 端口的所有 tcp 协议流量
tcp.port == 80
  1. 筛选源端口或目的端口为 80 端口的所有流量
tcp.port == 80 or udp.port == 80
  1. 筛选端口范围为 1 ~ 80 端口的所有 tcp 协议流量
tcp.port >= 1 and tcp.port <= 80

数据包长度筛选

  1. 筛选长度为 20 的 tcp 协议流量数据包(不包括 tcp 本身,tcp 下面那块数据包长度)
tcp.length == 20
  1. 筛选长度大于 20 的 udp 协议流量数据包(udp 本身固定长度 8 与 udp 下面那块数据包长度之和)
udp.length >= 20
  1. 筛选长度为 20 的整个流量数据包(从 eth 开始到最后)
frame.len == 20
  1. 筛选长度为 20 的 ip 流量包
ip.len == 20

HTTP 请求筛选

  1. 筛选 http 请求方法为 get 的流量
http.request.method == "GET"
  1. 筛选 http 请求方法为 post 的流量
http.request.method == "POST"
  1. 筛选 http 请求 URL 为 /img/flag.gif 的流量
http.request.uri == "/img/flag.gif"
  1. 筛选 http 请求内容包含 flag 的流量(contains 仅支持一个)
http contains "flag"
  1. 筛选 http 请求内容包含 ctfflag 的流量(matches 支持多个)
http matches "ctf|flag"

数据包搜索

Wirshark 使用快捷键 “Ctrl + F”可以进行关键字搜索

支持十六进制、字符串和正则表达式等方式,字符串搜索比较常用

CTF-Misc_流量分析1.png

搜索栏左侧可以选择分组列表、分组详情和分组字节流,分别对应搜索 Wireshark 界面的三个不同区域

CTF-Misc_流量分析2.png

CTF-Misc_流量分析3.png


数据包还原

Wireshark 可以通过追踪流将 http 或 tcp 流量集合在一起并还原成原始数据

选中一个流量包,右键追踪流:

CTF-Misc_流量分析4.png

CTF-Misc_流量分析7.png


数据提取

Wireshark 支持提取通过 http 传输(上传/下载)的文件内容

  1. 在菜单栏的文件选项中,选择 导出对象 --> HTTP

CTF-Misc_流量分析5.png

选择想要导出的文件,点击保存即可:

CTF-Misc_流量分析6.png

  1. 在分组详情窗口中,选择要导出的对象,右键 --> 导出分组字节流

CTF-Misc_流量分析8.png

如果是菜刀下载文件的流量,需要删除分组字节流前开头和结尾的 X@Y 字符,否则下载的文件会出错

首先在分组详情窗口中,选择要导出的对象,右键 --> 显示分组字节

然后在弹出的窗口中设置开始和结束的字节(原字节数开头加 3,结尾减 3),导出保存

CTF-Misc_流量分析9.png


USB 流量

USB 流量数据存放在 Leftover Capture Data 域中,可在 Wireshark 的分组详情窗口中查看

可参考的工具:FzWjScJ/knm: 鼠标键盘流量包取证

参考文献:CTF流量分析常见题型(二)-USB流量-腾讯云开发者社区-腾讯云

键盘流量

键盘流量的数据长度为 8 个字节,集中在 Leftover Capture Data 域的第 3 个字节中

CTF-Misc_流量分析10.png

在 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. 第 1 个字节代表按键,当取 0x00 时,代表没有按键;为 0x01 时,代表按左键,为 0x02 时,代表按右键
  2. 第 2 个字节可以看成是一个 signed byte 类型,其最高位为符号位,当这个值为正时,代表鼠标水平右移多少像素,为负时,代表水平左移多少像素
  3. 第 3 个字节与第 2 字节类似,代表垂直上下移动多少像素

CTF-Misc_流量分析11.png

与键盘流量类似,首先使用 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 手动安装即可(需要同时安装 gnuplotgnuplot-x11 才能画出图):

sudo apt update && sudo apt upgrade
sudo apt install gnuplot gnuplot-x11
gnuplot -v   # 验证安装

Windows 版 gnuplot 下载地址:gnuplot homepage