基于Peach的协议Fuzz 以DNS协议为例
Peach Peach是一款基于变异的模糊测试工具,被广泛应用于各种协议的测试。 编译 Gitlab在2020年收购Peach Fuzzer的母公司后,基于Peach Fuzzer Professional v4开发出协议测试工具GitLab Protocol Fuzzer,使用方法变化不大,所以下文都是基于GitLab Protocol Fuzze。 由于官方不提供编译好的二进制文件,需要自己编
Peach
Peach是一款基于变异的模糊测试工具,被广泛应用于各种协议的测试。
编译
Gitlab在2020年收购Peach Fuzzer的母公司后,基于Peach Fuzzer Professional v4开发出协议测试工具GitLab Protocol Fuzzer,使用方法变化不大,所以下文都是基于GitLab Protocol Fuzze。
由于官方不提供编译好的二进制文件,需要自己编译,推荐是在Ubuntu 16.04上编译, Ubuntu 18.04实测也可以。
本文提供了两种编译方式,本地编译以及CI编译,其中本地编译要踩的坑比较多。
本地编译
安装过程主要参考
https://gitlab.com/gitlab-org/security-products/protocol-fuzzer-ce/-/issues/2#note_676566894
安装编译依赖的包
apt-get install -y \
coreutils apt-utils wget curl openssl ca-certificates bash-completion \
joe vim nano \
unzip p7zip \
fping hping3 httping thc-ipv6 gdb \
tcpdump wireshark-common \
locales-all \
git build-essential joe vim strace tcpdump python python-pip \
ruby doxygen libxml2-utils less openjdk-8-jre xsltproc asciidoctor \
nodejs node-typescript wget \
apt-transport-https dirmngr gnupg ca-certificates apt-utils
编译过程中需要用到Paket这个包管理工具下载相关依赖,由于国内网络原因,即使挂代理,也很容易因为网络原因而下载失败,强烈建议在海外主机上编译
这里把代码里面自带的Paket去掉,下载最新版本Paket替代(截止至2021/12/10,最新版本6.2.1)
git clone https://gitlab.com/gitlab-org/security-products/protocol-fuzzer-ce
cd protocol-fuzzer-ce/paket/.paket
rm -f paket.exe
rm -f paket.bootstrapper.exe
rm -f paket.targets
wget https://github.com/fsprojects/Paket/releases/download/6.2.1/paket.bootstrapper.exe
wget https://github.com/fsprojects/Paket/releases/download/6.2.1/paket.targets
wget https://github.com/fsprojects/Paket/releases/download/6.2.1/paket.exe
wget https://github.com/fsprojects/Paket/releases/download/6.2.1/Paket.Restore.targets
cd ../../
下载最新版pintool放在3rdParty/pin/目录下,同时修改build配置
wget https://software.intel.com/sites/landingpage/pintool/downloads/pin-3.21-98484-ge7cd811fd-gcc-linux.tar.gz
mv pin-3.21-98484-ge7cd811fd-gcc-linux.tar.gz 3rdParty/pin/
cd 3rdParty/pin/ && tar -xf pin-3.21-98484-ge7cd811fd-gcc-linux.tar.gz && mv pin-3.21-98484-ge7cd811fd-gcc-linux pin-3.21-98484-gcc-linux
sed -i s/pin-3.19-98425-gcc-linux/pin-3.21-98484-gcc-linux/g build/config/linux.py
cd ../../
下载Mono,官方要求是4.x版本,实测是有问题的,这里我们直接从官方源下载最新版
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | tee /etc/apt/sources.list.d/mono-official-stable.list
apt-get update -y
apt-get install -y mono-devel
对部分代码打Patch,不Patch的话编译过程会报错
参见
https://gitlab.com/gitlab-org/security-products/protocol-fuzzer-ce/-/issues/1
https://gitlab.com/gitlab-org/security-products/protocol-fuzzer-ce/-/merge_requests/7
sed -i '/^int main.*/itemplate<bool b>\nstruct StaticAssert {};\ntemplate <>\nstruct StaticAssert<true>\n{\n static void myassert() {}\n};\n' core/BasicBlocks/bblocks.cpp
sed -i 's/STATIC_ASSERT(sizeof(size_t) == sizeof(ADDRINT))/StaticAssert<sizeof(size_t) == sizeof(ADDRINT)>::myassert()/g' core/BasicBlocks/bblocks.cpp
sed -i 's/var config = new LicenseConfig();/\/\/var config = new LicenseConfig();/g' pro/Core/Runtime/BaseProgram.cs
一切正常的话linux_x86,linux_x86_64都应该是Available状态
./waf configure
如果遇到问题,可以尝试
./waf configure -v
可能会遇到的问题有两类
- 报错如下的是因为Mono版本问题,按上面说的,添加官方源后下载最新版本
linux_x86 is not available: Command ['/usr/bin/mono', '/home/xxxx/protocol-fuzzer-ce/paket/.paket/paket.exe', 'restore'] returned 1 - 报错如下是因为Paket版本问题,按上面说的,下载最新版替换掉原来的
linux_x86 is not available: Command ['/usr/bin/mono', '/home/xxxx/protocol-fuzzer-ce/paket/.paket/paket.bootstrapper.exe'] returned 1
开始编译
./waf build
重点,编译完成后,不要直接执行install,必须要先把mono版本降级到4.x,否则后面会失败
apt purge -y mono* libmono* doxygen
rm /etc/apt/sources.list.d/mono-official-stable.list
apt update -y
apt install -y mono-complete
./waf install
一切顺利的话,output目录下会有编译好的二进制文件
CI 编译
CI配置文件如下,Fork官方repo之后,main分支创建CI/CD流水线即可
image: ubuntu:18.04
stages:
- build
builder:
stage:
build
script:
- export DEBIAN_FRONTEND=noninteractive
- apt-get update -qq
- apt-get install -y -qq gnupg2
- apt-get install -y -qq gcc
- apt-get install -y -qq g++
- apt-get install -y -qq g++-multilib
- apt-get install -y -qq python
- apt-get install -y -qq ruby
- apt-get install -y -qq curl
- apt-get install -y -qq nodejs
- apt-get install -y -qq node-typescript
- apt-get install -y -qq default-jdk
- apt-get install -y -qq doxygen
- apt-get install -y -qq libxml2-utils
- apt-get install -y -qq xsltproc
- apt-get install -y -qq wget
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
- apt install -y apt-transport-https ca-certificates
- echo "deb https://download.mono-project.com/repo/ubuntu stable-bionic main" | tee /etc/apt/sources.list.d/mono-official-stable.list
- apt update -qq -y
- apt-get install -y mono-devel
- cd paket/.paket
- rm -f paket.exe
- rm -f paket.targets
- rm -f paket.bootstrapper.exe
- wget https://github.com/fsprojects/Paket/releases/download/6.2.1/paket.bootstrapper.exe
- wget https://github.com/fsprojects/Paket/releases/download/6.2.1/paket.targets
- wget https://github.com/fsprojects/Paket/releases/download/6.2.1/paket.exe
- wget https://github.com/fsprojects/Paket/releases/download/6.2.1/Paket.Restore.targets
- cd ../../
- wget https://software.intel.com/sites/landingpage/pintool/downloads/pin-3.21-98484-ge7cd811fd-gcc-linux.tar.gz
- mv pin-3.21-98484-ge7cd811fd-gcc-linux.tar.gz 3rdParty/pin/
- cd 3rdParty/pin/ && tar -xf pin-3.21-98484-ge7cd811fd-gcc-linux.tar.gz && mv pin-3.21-98484-ge7cd811fd-gcc-linux pin-3.21-98484-gcc-linux
- cd ../../
- sed -i s/pin-3.19-98425-gcc-linux/pin-3.21-98484-gcc-linux/g build/config/linux.py
- sed -i '/^int main.*/itemplate<bool b>\nstruct StaticAssert {};\ntemplate <>\nstruct StaticAssert<true>\n{\n static void myassert() {}\n};\n' core/BasicBlocks/bblocks.cpp
- sed -i 's/STATIC_ASSERT(sizeof(size_t) == sizeof(ADDRINT))/StaticAssert<sizeof(size_t) == sizeof(ADDRINT)>::myassert()/g' core/BasicBlocks/bblocks.cpp
- sed -i 's/var config = new LicenseConfig();/\/\/var config = new LicenseConfig();/g' pro/Core/Runtime/BaseProgram.cs
- ./waf configure
- ./waf build
- apt purge -y mono* libmono* doxygen
- rm /etc/apt/sources.list.d/mono-official-stable.list
- apt update -y
- apt install -y mono-complete
- apt install -y mono-devel
- ./waf install
artifacts:
untracked: false
expire_in: 30 days
paths:
- output/
基础
使用Peach进行模糊测试可以分为几个步骤
- 创建数据模型
- 创建状态模型
- 添加Publisher
- 添加Monitor
Pit文件
Peach pit文件是一个XML格式的配置文件,它定义了模糊测试所需要的信息,文件里面的内容包括数据模型、状态模型等等。
以下是Peach提供的一个示例
<!-- 版本信息等,一般情况下不做改动 -->
<?xml version="1.0" encoding="utf-8"?>
<Peach xmlns="http://peachfuzzer.com/2012/Peach" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://peachfuzzer.com/2012/Peach ../peach.xsd">
<!-- 定义数据模型 -->
<DataModel name="TheDataModel">
<String value="Hello World!" />
</DataModel>
<!-- 定义状态模型 initialState表示初始状态 -->
<StateModel name="State" initialState="State1" >
<!-- 定义状态 -->
<State name="State1" >
<!-- 具体动作,output表示外发数据 -->
<Action type="output" >
<DataModel ref="TheDataModel"/>
</Action>
</State>
</StateModel>
<!-- 配置要使用的状态模型、数据模型等等 -->
<Test name="Default">
<!-- 指定要使用的状态模型 -->
<StateModel ref="State"/>
<!-- 指定Publisher,用于将生成的数据传输出去,Console表示打印到命令行 -->
<Publisher class="Console" />
</Test>
</Peach>
<!-- end -->
一般pit文件保存在pits目录下,运行
Peach.exe pits/example.xml
DataModel
DataModel描述了数据包中数据的类型、格式、大小等等信息。同时,DataModel还可以被其他DataModel通过ref所引用,类似于编程语言中的继承。
Peach支持非常多类型的数据,下面介绍几种常用的数据类型
Number
Peach支持从1到64比特任意长度的数字,同时可以定制字节序、符号等信息。
常用的属性:
size:比特长度(1-64)
value:默认值
valueType:类型(string/hex)
endian:字节序
signed:有无符号
mutable:是否允许变异,默认为true
<Number name="test" size="32" endian="big" signed="false"/>
String
字符串
常用属性:
length:长度(字节)
type:编码类型,默认utf8
value:默认值
nullTerminated:末尾是否添加NULL截断
<String name="test" value="test"/>
Blob
Blob通常用于表示二进制数据
常用属性:
length:长度(字节)
valueType:格式(hex/string)
<Blob name="test" valueType="hex" value ="1F 22 03"/>
Flags与Flag
Flags是Flag元素的容器,由于旧版本Peach不支持任意比特长度的Number元素,Flag被用来表示只占用一个或几个比特的标志位
<Flags name="flags" size="16">
<Flag name="flag1" position="0" size="1"/>
<Flag name="flag2" position="1" size="5"/>
</Flags>
Block
Block是一个容器元素,与DataModel类型,但区别是Block是DataModel的子元素,同样的,Block也支持被DataModel引用
<Block name="hello">
<Number value="1"/>
<String value="test"/>
</Block>
DNS
RFC1035定义了DNS报文格式
DNS Header

我们可以先定义出大致的结构,并给与合适的初始值
<DataModel name="Header">
<Number name="ID" size="16"/>
<Number name="QR" size="1" />
<Number name="Opcode" size="4" />
<Number name="AA" size="1" />
<Number name="TC" size="1" />
<Number name="RD" size="1" value="1" />
<Number name="RA" size="1" />
<Number name="Z" size="3" />
<Number name="RCODE" size="4" />
<Number name="QDCOUNT" size="16"/>
<Number name="ANCOUNT" size="16"/>
<Number name="NSCOUNT" size="16"/>
<Number name="ARCOUNT" size="16"/>
</DataModel>
Question
先来看看查询报文


查询报文有三个部分,QTYPE、QCLASS使用Number表示即可,但QNAME部分采用了DNS标准名称表示法,详细可参考RFC1035文档。
简单来说,每个域名(QNAME)是由一系列label构成,每个label第一个字节高两位被用来表示label的类型,一共有两种类型:00表示普通label,11表示压缩label
普通label
第一个字节高两位为00时,表示为普通label,剩下6比特被用来表示label的大小
以 www.baidu.com 为例,可以分为三个label,分别是www、baidu、com,用普通label表示为\x03www\x05baidu\x03com\x00
压缩label
第一个字节高两位为11时,表示为压缩label,剩下6比特与后面的字节共14比特构成一个指针,指针的值为对应字符串的偏移。
由于一个报文内可以同时有多个域名,而这些域名有一些部分是公共的,比如com这类后缀,通过指针可以节省空间。
下面这张图为例子,第二个域名为 www.example.com, 由于example.com在前面出现过,偏移为0xC,所以可以直接用指针

所以QNAME一共有三种情况:
1.全部由普通label表示,最后以NULL结尾
2.只有一个压缩label
3.前面由普通label表示,最后一个压缩label
普通label中,size的取值取决于后面的长度,这里用到Relation这个属性,取后面的长度赋值给size
<Block name="Label1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="www" nullTerminated="false"/>
</Block>
压缩label就比较简单
<Block>
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
第一种情况全部由普通label表示,最后以NULL结尾
<Block>
<Block occurs="1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="www" nullTerminated="false"/>
</Block>
<Block occurs="1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="google" nullTerminated="false"/>
</Block>
<Block occurs="1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="com" nullTerminated="false"/>
</Block>
<Blob name="NULL" length="1" valueType="hex" mutable="false" value="00"/>
</Block>
第二种情况 只有一个压缩label
<Block>
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
第三种情况前面由普通label表示,最后一个压缩label
<Block>
<Block occurs="1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="www" nullTerminated="false"/>
</Block>
<Block occurs="1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="google" nullTerminated="false"/>
</Block>
<Block occurs="1">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" value="com" nullTerminated="false"/>
</Block>
<Block>
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
</Block>
将三种情况通过Choice组合在一起,Fuzz过程中,Peach会随机在Choice中挑选一个来执行,类似于C语言中的switch
<DataModel name="Label">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" nullTerminated="false"/>
</DataModel>
<DataModel name="Labels">
<Block ref="Label" occurs="1">
<String name="id" value="www" nullTerminated="false"/>
</Block>
<Block ref="Label" occurs="1">
<String name="id" value="google" nullTerminated="false"/>
</Block>
<Block ref="Label" occurs="1">
<String name="id" value="com" nullTerminated="false"/>
</Block>
</DataModel>
<DataModel name="QNAME">
<Choice>
<Block ref="Labels">
<Blob name="NULL" length="1" valueType="hex" mutable="false" value="00"/>
</Block>
<Block ref="Labels">
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
<Block>
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
</Choice>
</DataModel>
Header中的QDCOUNT取决于Question的数量,同样用Relation处理
<DataModel name="DNS" ref="Header">
<Number name="QDCOUNT" size="16" mutable="true">
<Relation type="count" of="Question"/>
</Number>
<Block name="Question" ref="QNAME" maxOccurs="10">
<Number name="QTYPE" size="16" valueType="hex" mutable="false" value="00 01"/>
<Number name="QCLASS" size="16" valueType="hex" mutable="false" value="00 01"/>
</Block>
</DataModel>
到此数据建模部分基本完结,有些地方处理的方式比较粗糙,希望多多指教
StateModel
DNS协议通信比较简单,分别定义一个output跟input的action,这里我们不关心返回的结果,直接用Blob去匹配就行
<StateModel name="State" initialState="State1" >
<!-- 定义状态 -->
<State name="State1" >
<!-- 具体动作,output表示外发数据 -->
<Action type="output" >
<DataModel ref="DNS"/>
</Action>
<Action type="input" >
<DataModel>
<Blob />
</DataModel>
</Action>
</State>
</StateModel>
Publisher
Publisher是Peach Fuzzer的IO接口,所有的数据都是经过Publisher来处理,Peach支持相当多种类的Publisher,如Udp、Tcp、RawSocket、CAN、File等等。DNS协议只要涉及到Tcp与Udp这两个,具体支持的参数可查阅官方文档 https://gitlab.com/gitlab-org/security-products/protocol-fuzzer-ce/-/tree/main/docs/src.pro/Common/Monitors
<Publisher class="TcpClient">
<Param name="Host" value="8.8.8.8"/>
<Param name="Port" value="53" />
</Publisher>
<Publisher class="Udp">
<Param name="Host" value="8.8.8.8"/>
<Param name="Port" value="53" />
</Publisher>
Monitor
Monitor是Peach用来观察Fuzz对象状态的模块,Peach支持许多种类的Monitor,常用的有Ping Monitor、Socket Monitor、TcpPort Monitor、Run Command等等
TCP比较好处理,在每轮循环结束的时候,检测目标端口,当为Closed的时候,生成Fault
<Monitor class="TcpPort">
<Param name="Host" value="8.8.8.8" />
<Param name="Port" value="53" />
<Param name="Action" value="Fault" />
<Param name="State" value="Closed" />
<Param name="When" value="OnIterationEnd" />
</Monitor>
由于不好检测Udp端口是否开放,因此可以采用Run Command来检测,每次循环结束,执行自定义脚本,确定dns服务器是否正常
monitor.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-"
import dns.resolver
myResolver = dns.resolver.Resolver()
myResolver.nameservers = ['8.8.8.8']
try:
myAnswers = myResolver.query("www.baidu.com", "A",lifetime=2)
except:
exit(-1)
当自定义脚本返回值非0时,就认为产生错误
<Monitor class="RunCommand">
<Param name="Command" value="python3"/>
<Param name="Arguments" value="./pits/monitor.py" />
<Param name="FaultOnNonZeroExit" value="true" />
<Param name="When" value="OnIterationEnd" />
</Monitor>
优化
Peach支持许多种类的变异器(Mutator),但并不是所有的变异器都是我们想要使用的,可通过exclude去除掉不需要的变异器,相反地,也可以通过include指定变异器
<Mutators mode="exclude">
<Mutator class="DataElementDuplicate"/>
<Mutator class="DataElementRemove"/>
<Mutator class="DataElementSwapNearNodes"/>
<Mutator class="DataElementSwapNear"/>
</Mutators>
测试
将dns.xml保存至pits目录下,开始执行Peach.exe pits/dns.xml
dns.xml
<?xml version="1.0" encoding="utf-8"?>
<Peach xmlns="http://peachfuzzer.com/2012/Peach" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://peachfuzzer.com/2012/Peach ../peach.xsd">
<!-- 默认配置 -->
<Defaults>
<Number endian="big" />
</Defaults>
<!-- 定义数据模型 -->
<DataModel name="Header">
<Number name="ID" size="16" value="1234"/>
<Number name="QR" size="1" mutable="false"/>
<Number name="Opcode" size="4" mutable="false"/>
<Number name="AA" size="1" mutable="false"/>
<Number name="TC" size="1" mutable="false"/>
<Number name="RD" size="1" value="1" mutable="false"/>
<Number name="RA" size="1" mutable="false"/>
<Number name="Z" size="3" mutable="false"/>
<Number name="RCODE" size="4" mutable="false"/>
<Number name="QDCOUNT" size="16" mutable="false"/>
<Number name="ANCOUNT" size="16" mutable="false"/>
<Number name="NSCOUNT" size="16" mutable="false"/>
<Number name="ARCOUNT" size="16" mutable="false" />
</DataModel>
<DataModel name="Label">
<Number name="flag" size="2" value="0" mutable="false"/>
<Number name="size" size="6">
<Relation type="size" of="id"/>
</Number>
<String name="id" nullTerminated="false"/>
</DataModel>
<DataModel name="Labels">
<Block ref="Label" occurs="1">
<String name="id" value="www" nullTerminated="false"/>
</Block>
<Block ref="Label" occurs="1">
<String name="id" value="google" nullTerminated="false"/>
</Block>
<Block ref="Label" occurs="1">
<String name="id" value="com" nullTerminated="false"/>
</Block>
</DataModel>
<DataModel name="QNAME">
<Choice>
<Block ref="Labels">
<Blob name="NULL" length="1" valueType="hex" mutable="false" value="00"/>
</Block>
<Block ref="Labels">
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
<Block>
<Number name="flag" value="3" size="2" mutable="false"/>
<Number size="14"/>
</Block>
</Choice>
</DataModel>
<DataModel name="DNS" ref="Header">
<Number name="QDCOUNT" size="16" mutable="true">
<Relation type="count" of="Query"/>
</Number>
<Block name="Query" ref="QNAME" maxOccurs="10">
<Number name="QTYPE" size="16" valueType="hex" mutable="false" value="00 01"/>
<Number name="QCLASS" size="16" valueType="hex" mutable="false" value="00 01"/>
</Block>
</DataModel>
<!-- 定义状态模型 initialState表示初始状态 -->
<StateModel name="State" initialState="State1" >
<!-- 定义状态 -->
<State name="State1" >
<!-- 具体动作,output表示外发数据 -->
<Action type="output" >
<DataModel ref="DNS"/>
</Action>
</State>
</StateModel>
<!-- Monitor -->
<Agent name="TheAgent">
<Monitor class="RunCommand">
<Param name="Command" value="python3"/>
<Param name="Arguments" value="./pits/monitor.py" />
<Param name="FaultOnNonZeroExit" value="true" />
<Param name="When" value="OnIterationEnd" />
</Monitor>
</Agent>
<!-- 设置maxOutputSize以防止产生过大数据包 -->
<Test name="Default" maxOutputSize="100000" >
<!-- 变异策略 -->
<Strategy class="Random">
<Param name="MaxFieldsToMutate" value="6" />
<Param name="SwitchCount" value="200" />
</Strategy>
<Mutators mode="exclude">
<Mutator class="DataElementDuplicate"/>
<Mutator class="DataElementRemove"/>
<Mutator class="DataElementSwapNearNodes"/>
<Mutator class="DataElementSwapNear"/>
</Mutators>
<!-- 指定要使用的状态模型 -->
<StateModel ref="State"/>
<!-- 指定Publisher -->
<Publisher class="Udp">
<Param name="Host" value="8.8.8.8"/>
<Param name="Port" value="53" />
</Publisher>
<Agent ref="TheAgent"/>
</Test>
</Peach>

Wireshark抓包观察到畸形报文

完结
如果有不正确的地方,请各位大佬多多指点