-
-
[原创]针对 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直播授课