首页
社区
课程
招聘
[原创]Go语言模糊测试工具:Go-Fuzz
2022-3-11 16:34 16373

[原创]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基本流程

  1. 准备待测试程序,将待测试程序下载到本地。并分析待测程序,确定测试对象。

  2. 根据待测对象编写Fuzz函数。

    go-fuzz规定在执行fuzz时要创建一个开启fuzz的go文件, 这是 go-fuzz 中要求的,并且对内容的格式有规定。函数 func Fuzz(data []byte) int{} 是固定的写法,它是 fuzzer 的入口点;

    Fuzz 函数的参数 data 是 go-fuzz生成的随机输入;返回值是一个整数,如果输入是有效的,则返回1,否则返回0。该文件需要位于待测文件夹下。作者给出了简单的对于image/png包的Fuzz函数实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package png
     
    import (
        "bytes"
        "image/png"
    )
     
    func Fuzz(data []byte) int {
        png.Decode(bytes.NewReader(data))
        return 0
    }
  3. 构建fuzz包

    利用 go-fuzz-build 创建 fuzzing zip 文件 ,执行完命令后会在当前文件下生成一个zip文件。

  4. 构建语料库

    新建文件夹为corpus,并向中添加种子文件,fuzz引擎会自动从中获取种子。

  5. 执行fuzz

    -bin :指定测试对象的zip

    -workdir:指定工作目录

    1
    $GOPATH/bin/go-fuzz -bin=./mypackage-fuzz.zip -workdir= .
  6. 查看Fuzz进度,fuzz开始后,终端每隔几秒钟就会打印以下形式的stderr:

    1
    2
    2015/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标志)

  7. 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文件夹下多了两个目录:crasherssuppressions。查看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

1646966818526.png

 

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 的参数是 minmin 来自于 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 无效,函数将会返回nilMasknil 便是会是我们想要的结果,为了研究如何使 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 流程

  1. 下载项目到本地, 并在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-3 tony  staff  102 12  7 22:00 ./
drwxr-xr-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/


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

收藏
点赞3
打赏
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/03/31 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回