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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
open Check
open ELF_parsers
open ELF_types
open Frameworks
open Library
let fuzz_debug = ref false
let string_of_byte = Printf.sprintf "0x%02x"
let full_range_of_byte elfmap byte =
let byte = Int32.of_int byte in
List.find (fun (a, b, _, _) -> a <= byte && byte <= b) elfmap
let range_of_byte elfmap byte =
let (_, _, _, r) = full_range_of_byte elfmap byte in
r
(** [fuzz_check] will print what happened on stderr, and report errors (that is,
when the check went fine) to stdout.
*)
let fuzz_check elfmap bs byte old sdumps =
let is_error = function ERROR(_) -> true | _ -> false in
let (str, _, _) = bs in
let fuzz_description =
string_of_int32 (Int32.of_int byte) ^ " <- " ^
string_of_byte (Char.code str.[byte]) ^ " (was " ^
string_of_byte (Char.code old) ^ ") - " ^
string_of_byte_chunk_desc (range_of_byte elfmap byte)
in
if !fuzz_debug
then print_endline fuzz_description;
try
(* The point here is to go all the way through the checks, and see whether
the checker returns an ERROR or raises an exception. If not, then we
might be missing a bug!
*)
let elf = read_elf_bs bs in
let efw = check_elf_nodump elf sdumps in
if List.exists is_error efw.log
then (* finding an ERROR is the expected behavior *)
begin
if !fuzz_debug
then print_endline (
string_of_log_entry false (List.find is_error efw.log)
)
end
else (* not finding an ERROR is bad thus reported *)
print_endline (fuzz_description ^ " DID NOT CAUSE AN ERROR!")
with
| Assert_failure(s, l, c) ->
if !fuzz_debug
then Printf.printf "fuzz_check failed an assertion at %s (%d, %d)\n" s l c
| Exc.IntOverflow | Exc.Int32Overflow ->
if !fuzz_debug
then Printf.printf "fuzz_check raised an integer overflow exception\n"
| Match_failure(s, l, c) ->
if !fuzz_debug
then Printf.printf "fuzz_check raised a match failure at %s (%d, %d)\n" s l c
| Not_found ->
if !fuzz_debug
then Printf.printf "fuzz_check raised a not found exception\n"
| Invalid_argument(s) ->
if !fuzz_debug
then Printf.printf "fuzz_check raised an invalid argument: %s\n" s
| ELF_parsers.Unknown_endianness ->
if !fuzz_debug
then Printf.printf "fuzz_check raised an unknown endianness exception\n"
(** Tries to prevent some easy-to-catch false positives. Some known false
positives are however hard to predict. For instance, when the virtual
address of a stub is replaced by the virtual address of another exact
same stub.
*)
let ok_fuzz elfmap str byte fuzz =
let (a, b, _, r) = full_range_of_byte elfmap byte in
let a = Safe32.to_int a in
let b = Safe32.to_int b in
let old = Char.code str.[byte] in
let fuz = Char.code fuzz in
match r with
| ELF_header ->
not (List.mem byte
[
0x18; 0x19; 0x1a; 0x1b; (* e_entry *)
0x1c; 0x1d; 0x1e; 0x1f; (* e_phoff *)
0x24; 0x25; 0x26; 0x27; (* e_flags *)
0x2c; 0x2d (* e_phnum *)
]
)
| ELF_progtab -> false
| ELF_shtab -> false
| ELF_section_strtab -> false
| ELF_symbol_strtab -> false
| Symtab_data(_) ->
(* False positive: switching from/to STT_NOTYPE *)
not (byte = a + 12
&& ((old land 0xf = 0) || (fuz land 0xf = 0))
)
| Symtab_function(_) -> true
| Data_symbol(_) ->
(* False positive: 0. becomes -0. *)
not (
(byte + 7 <= b)
&& (fuz = 0x80) (* sign bit *)
&& String.sub str byte 8 = "\000\000\000\000\000\000\000\000"
)
| Function_symbol(_) ->
let opcode = Char.code str.[byte - 3] in
(* False positive: rlwinm with bitmask 0 31 = bitmask n (n - 1) *)
not (0x54 <= opcode && opcode <= 0x57 && old = 0x3E
&& (fuz = 0x40 || fuz = 0x82 || fuz = 0xc4))
| Zero_symbol -> false
| Stub(_) -> true
| Jumptable -> true
| Float_literal(_) ->
(* False positive: 0. becomes -0. *)
not (
(byte = a)
&& (fuz = 0x80) (* sign bit *)
&& String.sub str byte 8 = "\000\000\000\000\000\000\000\000"
)
(* padding is allowed to be non-null, but won't be recognized as padding, but
as unknown, which is not an ERROR *)
| Padding -> false
| Unknown(_) -> false
let fuzz_byte str byte_ndx =
let rand = Char.chr (Random.int 255) in (* [0 - 254] *)
if rand = str.[byte_ndx] (* if we picked a byte equal to the current *)
then Char.chr 255 (* then replace with byte 255 *)
else rand (* else replace with the byte we picked *)
let rec find_byte_to_fuzz elfmap str byterange =
let byte = Random.int byterange in
let fuzz = fuzz_byte str byte in
if ok_fuzz elfmap str byte fuzz
then (byte, fuzz)
else find_byte_to_fuzz elfmap str byterange
let get_elfmap elffilename =
let ic = open_in (elffilename ^ ".elfmap") in
let elfmap = input_value ic in
close_in ic;
elfmap
(** Randomly fuzz bytes forever *)
let fuzz_loop elffilename sdumps =
let elfmap = get_elfmap elffilename in
let (str, ofs, len) = Bitstring.bitstring_of_file elffilename in
let rec fuzz_loop_aux () =
let (byte, fuzz) = find_byte_to_fuzz elfmap str (len/8) in
let str' = String.copy str in
str'.[byte] <- fuzz;
fuzz_check elfmap (str', ofs, len) byte str.[byte] sdumps;
fuzz_loop_aux ()
in fuzz_loop_aux ()
let rec fuzz_every_byte_once_aux elfmap bs sdumps (current: int): unit =
let (str, ofs, len) = bs in
if current = len / 8 (* len is in bits *)
then ()
else (
let fuzz = fuzz_byte str current in
if ok_fuzz elfmap str current fuzz
then (
let str' = String.copy str in
str'.[current] <- fuzz;
fuzz_check elfmap (str', ofs, len) current str.[current] sdumps
);
fuzz_every_byte_once_aux elfmap bs sdumps (current + 1)
)
(** Fuzz each byte of the file once with a random new value *)
let fuzz_every_byte_once elffilename sdumps =
let elfmap = get_elfmap elffilename in
let bs = Bitstring.bitstring_of_file elffilename in
fuzz_every_byte_once_aux elfmap bs sdumps 0
(** Fuzz each byte of the file, then loop *)
let fuzz_every_byte_loop elffilename sdumps =
let elfmap = get_elfmap elffilename in
let bs = Bitstring.bitstring_of_file elffilename in
let rec fuzz_every_byte_loop_aux () =
fuzz_every_byte_once_aux elfmap bs sdumps 0;
fuzz_every_byte_loop_aux ()
in fuzz_every_byte_loop_aux ()
|