-
-
[原创]Go语言模糊测试工具:Go-Fuzz
-
2022-3-11 16:34 16373
-
一、go环境配置
Go 安装包下载地址在:https://golang.org/dl/
1.下载二进制包,本次使用的是 go1.14.4.linux-amd64.tar.gz。
2.将下载的二进制包解压至指定目录,比如 /usr/local目录。
1 | $ tar - C / usr / local - xzf go1. 14.4 .linux - amd64.tar.gz |
3.配置环境变量,进入.bashrc 配置:
1 | $ vim ~ / .bashrc |
4.在最后面添加如下代码:
1 2 3 4 5 6 7 | # GOROOT:go的安装路径 export GOROOT = "/usr/local/go" # GOPATH:go的开发路径(自定义就好) export GOPATH = "/home/xxx/gowork" # GOBIN:go工具程序存放路径 export GOBIN = $GOPATH / bin export PATH = $PATH:${GOPATH / / : / / bin :} / bin : / usr / local / go / bin |
5.保存,退出,使环境变量生效:
1 | $ source ~ / .bashrc |
6.查看环境变量是否生效:
1 | $ go env |
7.接着在开发目录创建文件夹:
1 2 3 4 | cd / home / xxx / gowork mkdir bin # bin是生产目录 mkdir src # src 是开发目录 mkdir pkg # pkg 是包目录 |
完成,之后构建的go项目源代码就放到src下面, 生成的安装包会自动放在bin目录下,生成过程中的中间文件会放在pkg下面。
二、go-fuzz安装
2.1 使用 go get 安装
1 | $ go get github.com / dvyukov / go - fuzz |
2.2 源码安装
创建/home/xxx/gowork/src/github.com/dvyukov/
目录,手动下载 zip 包上传到该目录进行安装,解压文件:
1 | $ unzip go - fuzz - master. zip |
路径是
$GOPATH/src/github.com/dvyukov/go-fuzz
接下来下载 go-fuzz提供的语料库。存放路径是
$GOPATH/src/github.com/dvyukov/go-fuzz-corpus
下载的东西准备好了,执行安装:
1 2 | $ go install $GOPATH / src / github.com / dvyukov / go - fuzz / go - fuzz $ go install $GOPATH / src / github.com / dvyukov / go - fuzz / go - fuzz - build |
三、go-fuzz基本流程
准备待测试程序,将待测试程序下载到本地。并分析待测程序,确定测试对象。
根据待测对象编写Fuzz函数。
go-fuzz规定在执行fuzz时要创建一个开启fuzz的go文件, 这是 go-fuzz 中要求的,并且对内容的格式有规定。函数
func Fuzz(data []byte) int{}
是固定的写法,它是 fuzzer 的入口点;Fuzz 函数的参数 data 是 go-fuzz生成的随机输入;返回值是一个整数,如果输入是有效的,则返回1,否则返回0。该文件需要位于待测文件夹下。作者给出了简单的对于image/png包的Fuzz函数实例
1234567891011package png
import
(
"bytes"
"image/png"
)
func Fuzz(data []byte)
int
{
png.Decode(bytes.NewReader(data))
return
0
}
构建fuzz包
利用
go-fuzz-build
创建 fuzzing zip 文件 ,执行完命令后会在当前文件下生成一个zip文件。构建语料库
新建文件夹为
corpus
,并向中添加种子文件,fuzz引擎会自动从中获取种子。执行fuzz
-bin :指定测试对象的zip
-workdir:指定工作目录
1$GOPATH
/
bin
/
go
-
fuzz
-
bin
=
.
/
mypackage
-
fuzz.
zip
-
workdir
=
.
查看Fuzz进度,fuzz开始后,终端每隔几秒钟就会打印以下形式的stderr:
122015
/
04
/
25
12
:
39
:
53
workers:
500
, corpus:
186
(
42s
ago), crashers:
3
,
restarts:
1
/
8027
, execs:
12009519
(
121224
/
sec), cover:
2746
, uptime:
1m39s
workers
:并行运行的测试数量(-procs参数指定)corpus
:语料库中的有效种子数量, 括号中的时间表示发现最后一个有趣输入的时间crashers
:已发现bug的数量(workdir/crashes 目录下)restarts
:fuzzer重新启动测试过程的速率, 速率应接近 1/10000(即计划的重启速率);如果它大大高于 1/10000,请考虑修复已发现的导致频繁重启的错误execs
:测试执行的总数,括号中的数字是测试执行的平均速度 。cover
:在散列覆盖位图中设置的位数,如果这个数字增长,Fuzzer会发现新的代码行;位图大小为 64K;理想情况下cover
值应小于 5000,否则由于哈希冲突,Fuzzer可能会错过新的有趣输入。uptime
:进程的正常运行时间,该信息也通过 http 提供(见-http
标志)Fuzz结果
Fuzz执行结束后,如果发现crash,会在工作文件夹下出现新的两个文件夹,分为是 suppressions 和 crashers 。 suppressions 中包含崩溃日志。它的作用是让go-fuzz跳过导致相同崩溃的输入 , crashers 文件中放着战利品,每次崩溃都会产生三个文件,文件名为输入的SHA-1哈希,无后缀名的文件为导致崩溃的实际输入、output为 crash dump 、 quoted 中放的是导致崩溃的输入,不过是以字符串形式展示 。
四、go-fuzz项目实战
4.1 ase
项目简介
用于解码和编码 ASE (Adobe Swatch Exchange) 文件的 Golang 包, ASE 包公开了 Decode 和 Encode 方法,只需将 io.Reader 接口传递给 ase.Decode,它将返回解码数据的 ASE 结构 ,我们可以将Decode作为我们的Fuzz目标。
项目地址:https://github.com/arolek/ase
Fuzz 流程
1.首先将下载项目到本地,并重置git到漏洞修复点之前
1 2 3 | $ go get github.com / arolek / ase $ cd `go list - f '{{.Dir}}' github.com / arolek / ase` $ git reset - - hard b1bf7d7a70445821722b29395f07fcd13e940f8c |
2.编写Fuzz.go
1 2 3 4 5 6 7 8 9 | / / + build gofuzz package ase import "bytes" func Fuzz(data []byte) int { if _, err : = Decode(bytes.NewReader(data)); err ! = nil { return 0 } return 1 } |
3.Build
1 | $ go - fuzz - build github.com / arolek / ase |
4.创建工作文件夹,语料库选择作者给出的samples,并开始Fuzz
1 2 3 | $ mkdir - p workdir / corpus $ cp samples / * .ase workdir / corpus $ go - fuzz - bin = ase - fuzz. zip - workdir = workdir |
5.Fuzz执行过程中的显示,下面表明并行运行的数量为2,语料库中有3个有效种子,发现了1个crash 等。
1 | 2022 / 03 / 10 18 : 01 : 40 workers: 2 , corpus: 3 ( 1m15s ago), crashers: 1 , restarts: 1 / 11 , execs: 299360 ( 3991 / sec), cover: 221 , uptime: 1m15s |
Crash 分析
此时workdir文件夹下多了两个目录:crashers
和suppressions
。查看crashers中的内容, 崩溃以输入的 sha1 命名,导致崩溃的实际数据是没有扩展名的文件,如下面的19cd42975df835d9a41f76a1ae4dd2d17916ea9
。 “.quoted”文件是作为字符串常量的数据,因此可以轻松地将其添加到测试文件中。 '.output' 文件是 panic() 或一些 go-fuzz 确定崩溃的输出。
1 2 3 4 5 6 7 | null@ubuntu:~ / gowork / src / github.com / arolek / ase / workdir$ ll crashers / total 20 drwxrwx - - - 2 null null 4096 Mar 10 05 : 33 . / drwxrwxr - x 5 null null 4096 Mar 10 05 : 32 .. / - rw - rw - - - - 1 null null 20 Mar 10 05 : 33 919cd42975df835d9a41f76a1ae4dd2d17916ea9 - rw - rw - - - - 1 null null 885 Mar 10 05 : 33 919cd42975df835d9a41f76a1ae4dd2d17916ea9 .output - rw - rw - - - - 1 null null 42 Mar 10 05 : 33 919cd42975df835d9a41f76a1ae4dd2d17916ea9 .quoted |
查看919cd42975df835d9a41f76a1ae4dd2d17916ea9.output
的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | panic: runtime error: slice bounds out of range [: - 1 ] goroutine 1 [running]: github.com / arolek / ase.( * Color).readName( 0xc000058050 , 0x4e6600 , 0xc00005a150 , 0x59a7c0 , 0x4a8080 ) / home / null / gowork / src / github.com / arolek / ase / color.go: 67 + 0x21f github.com / arolek / ase.( * Color).read( 0xc000058050 , 0x4e6600 , 0xc00005a150 , 0x0 , 0x0 ) / home / null / gowork / src / github.com / arolek / ase / color.go: 33 + 0xfe github.com / arolek / ase.Decode( 0x4e6600 , 0xc00005a150 , 0x44d9c9 , 0x1160437af97c4 , 0x191f107c , 0x191f107c00000000 , 0x6229fe1c , 0xc000074e98 , 0x46c526 , 0x6229fe1c , ...) / home / null / gowork / src / github.com / arolek / ase / ase.go: 56 + 0x69e github.com / arolek / ase.Fuzz( 0x7f5cadcdf000 , 0x14 , 0x14 , 0x4 ) / home / null / gowork / src / github.com / arolek / ase / fuzz.go: 7 + 0xb5 go - fuzz - dep.Main( 0xc000074f70 , 0x1 , 0x1 ) go - fuzz - dep / main.go: 36 + 0x1ad main.main() github.com / arolek / ase / go.fuzz.main / main.go: 15 + 0x52 |
根据堆栈跟踪,查看color.go
内容源码,可以看到如下代码,很明显对name[:len(name)-1])
的切面访问导致了越界错误。
1 | color.Name = string(utf16.Decode(name[: len (name) - 1 ])) |
接着,我们使用 919cd42975df835d9a41f76a1ae4dd2d17916ea9.quoted
中的数据编写测试代码重现该漏洞,编写fuzz_test.go
,内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package ase import ( "strings" "testing" ) func TestFuzzCrashers(t * testing.T) { var crashers = []string{ "ASEF00\x00\x000000\x00\x010000\x00\x00" , } for _, f : = range crashers { Decode(strings.NewReader(f)) } } |
go test 启动测试,很容易发现此时的len(name) = 0
, 因此 len(name)-1
不是有效的切片索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | $ go test - - - FAIL: TestFuzzCrashers ( 0.00s ) panic: runtime error: slice bounds out of range [: - 1 ] [recovered] panic: runtime error: slice bounds out of range [: - 1 ] goroutine 6 [running]: testing.tRunner.func1. 1 ( 0x5354e0 , 0xc000012340 ) / usr / local / go / src / testing / testing.go: 940 + 0x2f5 testing.tRunner.func1( 0xc00008e120 ) / usr / local / go / src / testing / testing.go: 943 + 0x3f9 panic( 0x5354e0 , 0xc000012340 ) / usr / local / go / src / runtime / panic.go: 969 + 0x166 github.com / arolek / ase.( * Color).readName( 0xc0000580a0 , 0x570720 , 0xc00000c0c0 , 0x66d3c8 , 0x511340 ) / home / null / gowork / src / github.com / arolek / ase / color.go: 67 + 0x1ba github.com / arolek / ase.( * Color).read( 0xc0000580a0 , 0x570720 , 0xc00000c0c0 , 0x0 , 0x0 ) / home / null / gowork / src / github.com / arolek / ase / color.go: 33 + 0x9e github.com / arolek / ase.Decode( 0x570720 , 0xc00000c0c0 , 0x4c0b40 , 0x603090 , 0x630840 , 0x0 , 0x1 , 0xc000030748 , 0x451fd9 , 0x16b13735d06 , ...) / home / null / gowork / src / github.com / arolek / ase / ase.go: 56 + 0x4e3 github.com / arolek / ase.TestFuzzCrashers( 0xc00008e120 ) / home / null / gowork / src / github.com / arolek / ase / ase_test.go: 15 + 0xdf testing.tRunner( 0xc00008e120 , 0x550370 ) / usr / local / go / src / testing / testing.go: 991 + 0xdc created by testing.( * T).Run / usr / local / go / src / testing / testing.go: 1042 + 0x357 exit status 2 FAIL github.com / arolek / ase 0.006s |
4.2 iprange
项目简介
iprange是一个库,可用于从nmap格式的字符串中解析IPv4地址 , 它接收一个字符串,并返回一个“Min-Max”格式的列表 , iprange支持以下格式:
1 2 3 4 5 | 10.0 . 0.1 10.0 . 0.0 / 24 10.0 . 0. * 10.0 . 0.1 - 10 10.0 . 0.1 , 10.0 . 0.5 - 10 , 192.168 . 1. * , 192.168 . 10.0 / 24 |
iprange的使用方法示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import ( "log" "github.com/malfunkt/iprange" ) func main() { list , err : = iprange.ParseList( "10.0.0.1, 10.0.0.5-10, 192.168.1.*, 192.168.10.0/24" ) if err ! = nil { log.Printf( "error: %s" , err) } log.Printf( "%+v" , list ) rng : = list .Expand() log.Printf( "%s" , rng) } |
该示例中,调用了 ParseList
函数 ,该函数十分合适作为我们Fuzz的对象。该函数来自于 iprange/y.go
文件中,下为该函数源码,这个函数的功能是:ParseList
接收一个目标规格的列表,并返回一个范围列表。
1 2 3 4 5 6 7 8 9 10 | / / ParseList takes a list of target specifications and returns a list of ranges, / / even if the list contains a single element. func ParseList( in string) (AddressRangeList, error) { lex : = &ipLex{line: []byte( in )} errCode : = ipParse(lex) if errCode ! = 0 || lex.err ! = nil { return nil, errors.Wrap(lex.err, "could not parse target" ) } return lex.output, nil } |
Fuzz 流程
1.下载项目到本地,并执行hard reset
1 2 | $ go get github.com / malfunkt / iprange $ git reset - - hard 3a31f5ed42d2d8a1fc46f1be91fd693bdef2dd52 |
2.准备Fuzz函数,在项目文件夹下创建文件Fuzz.go
1 2 3 4 5 6 7 8 9 | package iprange func Fuzz(data []byte) int { _, err : = ParseList(string(data)) if err ! = nil { return 0 } return 1 } |
3.编译,编译成功后,会在当前文件夹下生成iprange-fuzz.zip
1 | $ go - fuzz - build ~ / gowork / src / github.com / malfunkt / iprange |
4.准备语料库, 为了进行有意义的 fuzz,我们需要尽可能提供格式正确的样本。可以直接从iprange 项目 README文件中复制示例,并创建一下3个文件,放入corpus文件夹。
test1
1 | 10.0 . 0.1 , 10.0 . 0.5 - 10 , 192.168 . 1. * , 192.168 . 10.0 / 24 |
test2
1 2 | 10.0 . 0.1 - 10 , 10.0 . 0.0 / 24 , 10.0 . 0.0 / 24 |
test3
1 | 10.0 . 0. * , 192.168 . 0. * , 192.168 . 1 - 256 |
5.运行Fuzz
1 | $ go - fuzz - bin = . / iprange - fuzz. zip - workdir = . / |
Crash 分析
发现crash后,查看crasher文件夹下出现了3个文件,打开.out
文件查看堆栈跟踪:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | panic: runtime error: index out of range [ 3 ] with length 0 goroutine 1 [running]: encoding / binary.bigEndian.Uint32(...) / usr / local / go / src / encoding / binary / binary.go: 112 github.com / malfunkt / iprange.( * ipParserImpl).Parse( 0xc0000af800 , 0x53db40 , 0xc000064ff0 , 0x0 ) / home / null / gowork / src / github.com / malfunkt / iprange / y.go: 504 + 0x29d7 github.com / malfunkt / iprange.ipParse(...) / home / null / gowork / src / github.com / malfunkt / iprange / y.go: 306 github.com / malfunkt / iprange.ParseList( 0xc000043e78 , 0xa , 0xa , 0xa , 0xc000043e78 , 0xa , 0xc000043e98 ) / home / null / gowork / src / github.com / malfunkt / iprange / y.go: 61 + 0x127 github.com / malfunkt / iprange.Fuzz( 0x7f2a2627b000 , 0xa , 0xa , 0x3 ) / home / null / gowork / src / github.com / malfunkt / iprange / fuzz.go: 4 + 0x7d go - fuzz - dep.Main( 0xc000043f70 , 0x1 , 0x1 ) go - fuzz - dep / main.go: 36 + 0x1ad main.main() github.com / malfunkt / iprange / go.fuzz.main / main.go: 15 + 0x52 |
首先是encoding/binary/binary.bigEndian.Uint32
,它是 go 的标准库,定位到源码 /usr/local/go/src/encoding/binary/binary.go:112
1 2 3 4 | 111 func (bigEndian) Uint32(b []byte) uint32 { 112 _ = b[ 3 ] / / bounds check hint to compiler; see golang.org / issue / 14808 113 return uint32(b[ 3 ]) | uint32(b[ 2 ])<< 8 | uint32(b[ 1 ])<< 16 | uint32(b[ 0 ])<< 24 114 } |
_ = b[3]
这一句很是可疑, 注意到它的注释中提到“给编译器的边界检查提示”,根据它提到的链接,去看看是什么情况 https://github.com/golang/go/issues/14808。在 issues 中讲到了边界检查,这是为了检查输入是否有足够的字节,如果没有,它将在字节被访问时发生 panic 异常。这说明这句可能存在漏洞。
于是构造下面这一小段可以引起 panic 的代码来测试下:
1 2 3 4 5 6 7 8 9 10 | / / Small program to test panic when calling Uint32(nil). package main import ( "encoding/binary" ) func main() { _ = binary.BigEndian.Uint32(nil) } |
发现错误和之前的很相似,那么漏洞位置基本确定了。
1 2 3 4 5 6 7 8 9 | $ go run test.go panic: runtime error: index out of range [ 3 ] with length 0 goroutine 1 [running]: encoding / binary.bigEndian.Uint32(...) / usr / local / go / src / encoding / binary / binary.go: 112 main.main() / home / null / gowork / src / test.go: 9 + 0x1a exit status 2 |
接下来再看看这个漏洞的触发前提,即调用 bigEndian.Uint32
的函数iprange.Parse
。 查看github.com/malfunkt/iprange/y.go:504
附近代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | case 5 : ipDollar = ipS[ippt - 3 : ippt + 1 ] / / line ip.y: 54 { mask : = net.CIDRMask( int (ipDollar[ 3 ].num), 32 ) min : = ipDollar[ 1 ].addrRange. Min .Mask(mask) maxInt : = binary.BigEndian.Uint32([]byte( min )) + 0xffffffff - binary.BigEndian.Uint32([]byte(mask)) maxBytes : = make([]byte, 4 ) binary.BigEndian.PutUint32(maxBytes, maxInt) maxBytes = maxBytes[ len (maxBytes) - 4 :] max : = net.IP(maxBytes) ipVAL.addrRange = AddressRange{ Min : min .To4(), Max : max .To4(), } } |
传入 bigEndian.Uint32
的参数是 min
,min
来自于 mask
,而mask
又来自于 net.CIDRMask
。
查看 net.CIDRMask 的解释,https://golang.org/pkg/net/#CIDRMask
在 go 源码中可以查看到 CIDRMask 的源码:
1 2 3 4 5 6 7 8 9 10 11 12 | / / CIDRMask returns an IPMask consisting of `ones' 1 bits / / followed by 0s up to a total length of `bits' bits. / / For a mask of this form, CIDRMask is the inverse of IPMask.Size. func CIDRMask(ones, bits int ) IPMask { if bits ! = 8 * IPv4len && bits ! = 8 * IPv6len { return nil } if ones < 0 || ones > bits { return nil } / / removed } |
可以发现如果ones
无效,函数将会返回nil
。Mask
为 nil
便是会是我们想要的结果,为了研究如何使 ones
无效,我们可以通过修改iprange包的源码,把 ipDollar[3]
打印出来看看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | case 5 : ipDollar = ipS[ippt - 3 : ippt + 1 ] / / line ip.y: 54 { fmt.Printf( "ipdollar[3]: %v\n" , ipDollar[ 3 ].num) / / print ipdollar[ 3 ] mask : = net.CIDRMask( int (ipDollar[ 3 ].num), 32 ) fmt.Printf( "mask: %v\n" , mask) / / print mask min : = ipDollar[ 1 ].addrRange. Min .Mask(mask) fmt.Printf( "min: %v\n" , min ) / / print min maxInt : = binary.BigEndian.Uint32([]byte( min )) + 0xffffffff - binary.BigEndian.Uint32([]byte(mask)) maxBytes : = make([]byte, 4 ) binary.BigEndian.PutUint32(maxBytes, maxInt) maxBytes = maxBytes[ len (maxBytes) - 4 :] max : = net.IP(maxBytes) ipVAL.addrRange = AddressRange{ Min : min .To4(), Max : max .To4(), } } |
代码修改完毕后,接着用 fuzz时导致 crash 的输入复现一下,代码在之前 fuzz 程序的基础上稍加改动即可,创建文件test2.go,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | / / Small program to investigate a panic in iprange for invalid masks. package main import "github.com/malfunkt/iprange" func main() { _ = Fuzz([]byte( "0.0.0.0/50" )) / / .quoted的内容 } func Fuzz(data []byte) int { _, err : = iprange.ParseList(string(data)) if err ! = nil { return 0 } return 1 } |
运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | $ go run test2.go ipdollar[ 3 ]: 50 mask:<nil> min :<nil> panic: runtime error: index out of range [ 3 ] with length 0 goroutine 1 [running]: encoding / binary.bigEndian.Uint32(...) / usr / local / go / src / encoding / binary / binary.go: 112 github.com / malfunkt / iprange.( * ipParserImpl).Parse( 0xc0000b6000 , 0x515080 , 0xc0000800a0 , 0x0 ) yaccpar: 354 + 0x1e8e github.com / malfunkt / iprange.ipParse(...) yaccpar: 153 github.com / malfunkt / iprange.ParseList( 0xc00003ef58 , 0xa , 0xa , 0xa , 0xc00003ef58 , 0xa , 0x4063df ) ip.y: 93 + 0xdf main.Fuzz(...) / home / null / gowork / src / test2.go: 10 main.main() / home / null / gowork / src / test2.go: 6 + 0x75 exit status 2 |
可以看出,程序中将 50 传递给 net.CIDRMask ,会导致 mask 为 nil ,进而导致 min 也为 nil,这样的 min 再作为参数传入bigEndian.Uint32 便会出现越界索引 。
4.3 gocmpp
项目简介
gocmpp 是一个实现中国移动点对点(cmpp)协议的库 , 可以使用该库来实现任何在客户端和服务器端都使用 cmpp 协议的应用程序、工具或系统。 gocmpp中的每种协议包都实现了Packer接口,其中的Unpack尤其适合模糊测试。
项目地址:https://github.com/bigwhite/gocmpp
Fuzz 流程
- 下载项目到本地, 并在gocmpp下专门建立fuzztest目录,用于存放fuzz test的代码,将各个协议包的fuzz test分到各个子目录中:
1 | $ go get github.com / bigwhite / gocmpp |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | github.com / bigwhite / gocmpp / fuzztest]$tree . ├── fwd │ ├── corpus │ │ └── 0 │ ├── fuzz.go │ └── gen │ └── main.go └── submit ├── corpus │ ├── 0 ├── fuzz.go └── gen └── main.go |
先说说每个fuzz test单元(比如fwd或submit)下的gen/main.go
,这是一个用于生成初始语料的可执行程序,我们以submit/gen/main.go
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import ( "github.com/dvyukov/go-fuzz/gen" ) func main() { data : = []byte{ 0x00 , 0x00 , 0x00 , 0x17 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x01 , 0x01 , 0x01 , 0x74 , 0x65 , 0x73 , 0x74 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x02 , 0x31 , 0x33 , 0x35 , 0x30 , 0x30 , 0x30 , 0x30 , 0x32 , 0x36 , 0x39 , 0x36 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x08 , 0x39 , 0x30 , 0x30 , 0x30 , 0x30 , 0x31 , 0x30 , 0x32 , 0x31 , 0x30 , 0x00 , 0x00 , 0x00 , 0x00 , 0x31 , 0x35 , 0x31 , 0x31 , 0x30 , 0x35 , 0x31 , 0x33 , 0x31 , 0x35 , 0x35 , 0x35 , 0x31 , 0x30 , 0x31 , 0x2b , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x39 , 0x30 , 0x30 , 0x30 , 0x30 , 0x31 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x01 , 0x31 , 0x33 , 0x35 , 0x30 , 0x30 , 0x30 , 0x30 , 0x32 , 0x36 , 0x39 , 0x36 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x1e , 0x6d , 0x4b , 0x8b , 0xd5 , 0x00 , 0x67 , 0x00 , 0x6f , 0x00 , 0x63 , 0x00 , 0x6d , 0x00 , 0x70 , 0x00 , 0x70 , 0x00 , 0x20 , 0x00 , 0x73 , 0x00 , 0x75 , 0x00 , 0x62 , 0x00 , 0x6d , 0x00 , 0x69 , 0x00 , 0x74 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , } gen.Emit(data, nil, true) } |
在这个main.go
中,我们借用submit
包的单元测试中的数据作为fuzz test的初始语料数据,通过go-fuzz提供的gen包将数据输出到文件中:
1 2 3 4 5 6 7 | $cd submit / gen $go run main.go - out .. / corpus / $ll .. / corpus / total 8 drwxr - xr - x 3 tony staff 102 12 7 22 : 00 . / drwxr - xr - x 5 tony staff 170 12 7 21 : 42 .. / - rw - r - - r - - 1 tony staff 181 12 7 22 : 00 0 |
该程序在corpus下生成了一个文件“0”,作为submit fuzz test的初始语料。
接下来我们看看submit/fuzz.go: submit/fuzz.go
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | / / + build gofuzz package cmppfuzz import ( "github.com/bigwhite/gocmpp" ) func Fuzz(data []byte) int { p : = &cmpp.Cmpp2SubmitReqPkt{} if err : = p.Unpack(data); err ! = nil { return 0 } return 1 } |
接下来就是go-fuzz-build和go-fuzz登场
1 2 3 | $cd submit $go - fuzz - build github.com / bigwhite / gocmpp / fuzztest / submit $go - fuzz - bin = . / cmppfuzz - fuzz. zip - workdir = . / |
参考链接
https://github.com/dvyukov/go-fuzz
https://parsiya.net/blog/2018-04-29-learning-go-fuzz-1-iprange/
https://dgryski.medium.com/go-fuzz-github-com-arolek-ase-3c74d5a3150c
https://studygolang.com/articles/5461
http://blog.nsfocus.net/go-fuzz-0806/