首页
社区
课程
招聘
4
[原创]针对 Exif 手写一个模糊测试器
发表于: 2024-8-28 23:09 7483

[原创]针对 Exif 手写一个模糊测试器

2024-8-28 23:09
7483

针对 Exif 手写一个模糊测试器

这其实我去年跟着视频做的一个学习笔记,今天刷看雪时看到篇文章才知道,这是来自NYU Fuzzing Talk
我觉得是一个挺不错的fuzz入门教程。

基础知识

虽然模糊测试经常发送随机或半随机数据,但完全随机的输入大多数情况下会被应用程序很早的阶段就判断为无效输入。通过对文件格式有深入的理解,我们可以确保至少部分输入是有效的,或接近有效,从而深入地测试程序的后续处理流程。

很多文件格式,例如PDF、PNG、ELF等,都有所谓的“magic值”或魔数这是文件开头的特定字节序列,用于标识文件的格式。没有正确的 magic 值程序可能会立即拒绝文件,不进一步处理。了解这些 magic 值对于构建有效的模糊输入是关键。
这就是为什么我们需要官方文档的原因

jpg 格式




我们 fuzz 的时候需要先改好头和尾,不然可能程序都不执行

第一步 读取原始种子

读取原始种子用于变异,然后将原始种子和变异种子拿给程序解析,去看有没有crash

这是原图

简易fuzz-读取框架

fuzz 当中首先需要读取种子内容,然后让目标程序去运行解析。首先我们需要有一个读取框架用于将目标文档用作于我们的有效输入样本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys
 
# read bytes from our valid JPEG and return them in a mutable bytearray
def get_bytes(filename):
 
    f = open(filename, "rb").read()
 
    return bytearray(f)
 
if len(sys.argv) < 2:
    print("Usage: JPEGfuzz.py ")
 
else:
    filename = sys.argv[1]
    data = get_bytes(filename)
    counter = 0
    for x in data:
        if counter < 10:
            print(x)
        counter += 1

第二步 样本变异

模糊测试当中一个关键的步骤是进行样本的变异。样本变异简而言之,就是对已知的输入样本(例如一个文件或数据包)进行修改,产生新的、可能触发程序异常的输入。这是模糊测试的核心,因为它的目的就是尝试各种不同的输入,查找程序的潜在问题。

翻转

变异策略也有很多:翻转,插入,删除等

以翻转为例,我们可以选择随机翻转

1
2
3
4
5
6
7
255
0xFF
11111111
随机翻转
11011111
0xDF
233
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import sys
import random
 
# read bytes from our valid JPEG and return them in a mutable bytearray
def get_bytes(filename):
 
    f = open(filename, "rb").read()
 
    return bytearray(f)
 
def bit_flip(data):
 
    num_of_flips = int((len(data) - 4) * 0.01)
 
    indexes = range(4, (len(data) - 4))
 
    chosen_indexes = []
 
    # iterate selecting indexes until we've hit our num_of_flips number
    counter = 0
    while counter < num_of_flips:
        chosen_indexes.append(random.choice(indexes))
        counter += 1
 
    for x in chosen_indexes:
        current = data[x]
        current = (bin(current).replace("0b",""))
        current = "0" * (8 - len(current)) + current
         
        indexes = range(0,8)
 
        picked_index = random.choice(indexes)
 
        new_number = []
 
        # our new_number list now has all the digits, example: ['1', '0', '1', '0', '1', '0', '1', '0']
        for i in current:
            new_number.append(i)
 
        # if the number at our randomly selected index is a 1, make it a 0, and vice versa
        if new_number[picked_index] == "1":
            new_number[picked_index] = "0"
        else:
            new_number[picked_index] = "1"
 
        # create our new binary string of our bit-flipped number
        current = ''
        for i in new_number:
            current += i
 
        # convert that string to an integer
        current = int(current,2)
 
        # change the number in our byte array to our new number we just constructed
        data[x] = current
 
    return data
 
# create new jpg with mutated data
def create_new(data):
 
    f = open("mutated.jpg", "wb+")
    f.write(data)
    f.close()
 
if len(sys.argv) < 2:
    print("Usage: JPEGfuzz.py ")
 
else:
    filename = sys.argv[1]
    data = get_bytes(filename)
    mutated_data = bit_flip(data)
    create_new(mutated_data)

然后我们对之前那个jpg进行变异得到一个新的图片,这就是我们变异样本

然后我们可以对比变异前后两张图片在二进制上的差别

magic nums

magic num 就是指有些数字是特殊的,我们可以选择直接插入magic num 来更有效的进行变异

1
2
3
4
5
6
7
8
9
10
0xFF
0x7F
0x00
0xFFFF
0x0000
0xFFFFFFFF
0x00000000
0x80000000 <-- minimum 32-bit int
0x40000000 <-- just half of that amount
Ox7FFFFFFF <-- max 32-bit int

一些文件格式标志等

  • JPEG 文件: 开头的字节为 FF D8
  • GIF 文件: 开头的字节为 47 49 46 38
  • ELF可执行文件:开头的字节为 7F 45 4C 46

我们这里有一个对magic num 的初步选择

  • 1字节修改
    如果选择的魔数是(1,255),则 data 在选定的索引位置的值被设置为255
    如果是(1,127),则设置为127
    如果是(1,0),则设置为0
  • 2字节修改
    如果选择的魔数是 (2,255),则 data 在选定的索引位置及其后的位置的值被设置为255
    如果是(2,0),则这两个位置都被设置为0
  • 4字节修改:
    (4,255)修改 data 在选定索引位置及其后的三个位置的值为255。
    (4,0) 设置这四个位置的值为0
    (4,128) 设置第一个位置为128,其余为0
    (4,64) 设置第一个位置为64,其余为0
    (4,127) 设置第一个位置为127,其余为255
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import sys
import random
 
# read bytes from our valid JPEG and return them in a mutable bytearray
def get_bytes(filename):
    f = open(filename, "rb").read()
    return bytearray(f)
 
def bit_flip(data):
 
    num_of_flips = int((len(data) - 4) * 0.01)
    indexes = range(4, (len(data) - 4))
    chosen_indexes = []
    # iterate selecting indexes until we've hit our num_of_flips number
    counter = 0
    while counter < num_of_flips:
        chosen_indexes.append(random.choice(indexes))
        counter += 1
 
    for x in chosen_indexes:
        current = data[x]
        current = (bin(current).replace("0b",""))
        current = "0" * (8 - len(current)) + current
         
        indexes = range(0,8)
 
        picked_index = random.choice(indexes)
 
        new_number = []
 
        # our new_number list now has all the digits, example: ['1', '0', '1', '0', '1', '0', '1', '0']
        for i in current:
            new_number.append(i)
 
        # if the number at our randomly selected index is a 1, make it a 0, and vice versa
        if new_number[picked_index] == "1":
            new_number[picked_index] = "0"
        else:
            new_number[picked_index] = "1"
 
        # create our new binary string of our bit-flipped number
        current = ''
        for i in new_number:
            current += i
 
        # convert that string to an integer
        current = int(current,2)
 
        # change the number in our byte array to our new number we just constructed
        data[x] = current
 
    return data
 
def magic(data):
 
    magic_vals = [
    (1, 255),
    (1, 255),
    (1, 127),
    (1, 0),
    (2, 255),
    (2, 0),
    (4, 255),
    (4, 0),
    (4, 128),
    (4, 64),
    (4, 127)
    ]
 
    picked_magic = random.choice(magic_vals)
 
    length = len(data) - 8
    index = range(0, length)
    picked_index = random.choice(index)
 
    # here we are hardcoding all the byte overwrites for all of the tuples that begin (1, )
    if picked_magic[0] == 1:
        if picked_magic[1] == 255:          # 0xFF
            data[picked_index] = 255
        elif picked_magic[1] == 127:            # 0x7F
            data[picked_index] = 127
        elif picked_magic[1] == 0:          # 0x00
            data[picked_index] = 0
 
    # here we are hardcoding all the byte overwrites for all of the tuples that begin (2, )
    elif picked_magic[0] == 2:
        if picked_magic[1] == 255:          # 0xFFFF
            data[picked_index] = 255
            data[picked_index + 1] = 255
        elif picked_magic[1] == 0:          # 0x0000
            data[picked_index] = 0
            data[picked_index + 1] = 0
 
    # here we are hardcoding all of the byte overwrites for all of the tuples that being (4, )
    elif picked_magic[0] == 4:
        if picked_magic[1] == 255:          # 0xFFFFFFFF
            data[picked_index] = 255
            data[picked_index + 1] = 255
            data[picked_index + 2] = 255
            data[picked_index + 3] = 255
        elif picked_magic[1] == 0:          # 0x00000000
            data[picked_index] = 0
            data[picked_index + 1] = 0
            data[picked_index + 2] = 0
            data[picked_index + 3] = 0
        elif picked_magic[1] == 128:            # 0x80000000
            data[picked_index] = 128
            data[picked_index + 1] = 0
            data[picked_index + 2] = 0
            data[picked_index + 3] = 0
        elif picked_magic[1] == 64:         # 0x40000000
            data[picked_index] = 64
            data[picked_index + 1] = 0
            data[picked_index + 2] = 0
            data[picked_index + 3] = 0
        elif picked_magic[1] == 127:            # 0x7FFFFFFF
            data[picked_index] = 127
            data[picked_index + 1] = 255
            data[picked_index + 2] = 255
            data[picked_index + 3] = 255
         
    return data
# create new jpg with mutated data
def create_new(data):
 
    f = open("mutated.jpg", "wb+")
    f.write(data)
    f.close()
 
if len(sys.argv) < 2:
    print("Usage: JPEGfuzz.py ")
 
else:
    filename = sys.argv[1]
    data = get_bytes(filename)
    #mutated_data = bit_flip(data)
    mutated_data = magic(data)
    create_new(mutated_data)

再来看看这个变异的效果

再看看二进制数据上的差别


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-8-29 10:51 被Arahat0编辑 ,原因:
收藏
免费 4
支持
分享
赞赏记录
参与人
雪币
留言
时间
PLEBFE
为你点赞!
2024-12-9 03:43
KEEEY
+1
感谢你的积极参与,期待更多精彩内容!
2024-8-30 12:25
pureGavin
+10
你的分享对大家帮助很大,非常感谢!
2024-8-29 20:07
wx_Dispa1r
+1
你的分享对大家帮助很大,非常感谢!
2024-8-29 16:30
最新回复 (0)
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册