fuzz一个程序AFL都做了什么

本文介绍著名的模糊测试工具AFL在有源码的非QEMU模式下的总体执行流程,忽略一些参数设置、安全检查等细枝末节的操作和一些实现细节。

插桩编译

afl-gcc

测试有源码的程序时,首先要用afl-gcc代替gcc编译程序。其实afl-gcc只是gcc的封装,其内部最终还是调用了gcc,只是在调用时添加了-B的参数指定afl-as为汇编器。所以先是利用gcc的前端将源码编译为汇编语言,再交给afl-as进行汇编操作。

afl-as

afl-as也是对GNU的汇编器as的封装,它首先在输入的汇编文件中插桩,然后再调用as翻译为机器语言。

所谓插桩,就是在程序中的各个位置插入特定的指令,当这段指令被执行就说明程序运行到了这个位置,据此可以判断执行路径并计算代码覆盖率。在afl-as中,64位的“桩”代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static const u8* trampoline_fmt_64 =

"\n"
"/* --- AFL TRAMPOLINE (64-BIT) --- */\n"
"\n"
".align 4\n"
"\n"
"leaq -(128+24)(%%rsp), %%rsp\n"
"movq %%rdx, 0(%%rsp)\n"
"movq %%rcx, 8(%%rsp)\n"
"movq %%rax, 16(%%rsp)\n"
"movq $0x%08x, %%rcx\n"
"call __afl_maybe_log\n"
"movq 16(%%rsp), %%rax\n"
"movq 8(%%rsp), %%rcx\n"
"movq 0(%%rsp), %%rdx\n"
"leaq (128+24)(%%rsp), %%rsp\n"
"\n"
"/* --- END --- */\n"
"\n";

它是一段格式化字符串,%08x就是要被格式化的内容,在实际插入时它将被替换为一个随机数,作为这个桩的标识。所以这段代码首先保存好各个寄存器,将桩标识存入rcx,调用__afl_maybe_log后再恢复寄存器。

AFL会把桩插在main函数的开头、各个分支的开头和除jmp强制跳转以外所有跳转语句的后面。一个测试程序插桩后再IDA反编译的结果如下

__afl_maybe_log定义在下面的main_payload_64中。

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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
static const u8* main_payload_64 = 

"\n"
"/* --- AFL MAIN PAYLOAD (64-BIT) --- */\n"
"\n"
".text\n"
".att_syntax\n"
".code64\n"
".align 8\n"
"\n"
"__afl_maybe_log:\n"
"\n"
#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9))
" .byte 0x9f /* lahf */\n"
#else
" lahf\n"
#endif /* ^__OpenBSD__, etc */
" seto %al\n"
"\n"
" /* Check if SHM region is already mapped. */\n"
"\n"
" movq __afl_area_ptr(%rip), %rdx\n"
" testq %rdx, %rdx\n"
" je __afl_setup\n"
"\n"
"__afl_store:\n"
"\n"
" /* Calculate and store hit for the code location specified in rcx. */\n"
"\n"
#ifndef COVERAGE_ONLY
" xorq __afl_prev_loc(%rip), %rcx\n"
" xorq %rcx, __afl_prev_loc(%rip)\n"
" shrq $1, __afl_prev_loc(%rip)\n"
#endif /* ^!COVERAGE_ONLY */
"\n"
#ifdef SKIP_COUNTS
" orb $1, (%rdx, %rcx, 1)\n"
#else
" incb (%rdx, %rcx, 1)\n"
#endif /* ^SKIP_COUNTS */
"\n"
"__afl_return:\n"
"\n"
" addb $127, %al\n"
#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9))
" .byte 0x9e /* sahf */\n"
#else
" sahf\n"
#endif /* ^__OpenBSD__, etc */
" ret\n"
"\n"
".align 8\n"
"\n"
"__afl_setup:\n"
"\n"
" /* Do not retry setup if we had previous failures. */\n"
"\n"
" cmpb $0, __afl_setup_failure(%rip)\n"
" jne __afl_return\n"
"\n"
" /* Check out if we have a global pointer on file. */\n"
"\n"
#ifndef __APPLE__
" movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n"
" movq (%rdx), %rdx\n"
#else
" movq __afl_global_area_ptr(%rip), %rdx\n"
#endif /* !^__APPLE__ */
" testq %rdx, %rdx\n"
" je __afl_setup_first\n"
"\n"
" movq %rdx, __afl_area_ptr(%rip)\n"
" jmp __afl_store\n"
"\n"
"__afl_setup_first:\n"
"\n"
" /* Save everything that is not yet saved and that may be touched by\n"
" getenv() and several other libcalls we'll be relying on. */\n"
"\n"
" leaq -352(%rsp), %rsp\n"
"\n"
" movq %rax, 0(%rsp)\n"
" movq %rcx, 8(%rsp)\n"
" movq %rdi, 16(%rsp)\n"
" movq %rsi, 32(%rsp)\n"
" movq %r8, 40(%rsp)\n"
" movq %r9, 48(%rsp)\n"
" movq %r10, 56(%rsp)\n"
" movq %r11, 64(%rsp)\n"
"\n"
" movq %xmm0, 96(%rsp)\n"
" movq %xmm1, 112(%rsp)\n"
" movq %xmm2, 128(%rsp)\n"
" movq %xmm3, 144(%rsp)\n"
" movq %xmm4, 160(%rsp)\n"
" movq %xmm5, 176(%rsp)\n"
" movq %xmm6, 192(%rsp)\n"
" movq %xmm7, 208(%rsp)\n"
" movq %xmm8, 224(%rsp)\n"
" movq %xmm9, 240(%rsp)\n"
" movq %xmm10, 256(%rsp)\n"
" movq %xmm11, 272(%rsp)\n"
" movq %xmm12, 288(%rsp)\n"
" movq %xmm13, 304(%rsp)\n"
" movq %xmm14, 320(%rsp)\n"
" movq %xmm15, 336(%rsp)\n"
"\n"
" /* Map SHM, jumping to __afl_setup_abort if something goes wrong. */\n"
"\n"
" /* The 64-bit ABI requires 16-byte stack alignment. We'll keep the\n"
" original stack ptr in the callee-saved r12. */\n"
"\n"
" pushq %r12\n"
" movq %rsp, %r12\n"
" subq $16, %rsp\n"
" andq $0xfffffffffffffff0, %rsp\n"
"\n"
" leaq .AFL_SHM_ENV(%rip), %rdi\n"
CALL_L64("getenv")
"\n"
" testq %rax, %rax\n"
" je __afl_setup_abort\n"
"\n"
" movq %rax, %rdi\n"
CALL_L64("atoi")
"\n"
" xorq %rdx, %rdx /* shmat flags */\n"
" xorq %rsi, %rsi /* requested addr */\n"
" movq %rax, %rdi /* SHM ID */\n"
CALL_L64("shmat")
"\n"
" cmpq $-1, %rax\n"
" je __afl_setup_abort\n"
"\n"
" /* Store the address of the SHM region. */\n"
"\n"
" movq %rax, %rdx\n"
" movq %rax, __afl_area_ptr(%rip)\n"
"\n"
#ifdef __APPLE__
" movq %rax, __afl_global_area_ptr(%rip)\n"
#else
" movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n"
" movq %rax, (%rdx)\n"
#endif /* ^__APPLE__ */
" movq %rax, %rdx\n"
"\n"
"__afl_forkserver:\n"
"\n"
" /* Enter the fork server mode to avoid the overhead of execve() calls. We\n"
" push rdx (area ptr) twice to keep stack alignment neat. */\n"
"\n"
" pushq %rdx\n"
" pushq %rdx\n"
"\n"
" /* Phone home and tell the parent that we're OK. (Note that signals with\n"
" no SA_RESTART will mess it up). If this fails, assume that the fd is\n"
" closed because we were execve()d from an instrumented binary, or because\n"
" the parent doesn't want to use the fork server. */\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_temp(%rip), %rsi /* data */\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n"
CALL_L64("write")
"\n"
" cmpq $4, %rax\n"
" jne __afl_fork_resume\n"
"\n"
"__afl_fork_wait_loop:\n"
"\n"
" /* Wait for parent by reading from the pipe. Abort if read fails. */\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_temp(%rip), %rsi /* data */\n"
" movq $" STRINGIFY(FORKSRV_FD) ", %rdi /* file desc */\n"
CALL_L64("read")
" cmpq $4, %rax\n"
" jne __afl_die\n"
"\n"
" /* Once woken up, create a clone of our process. This is an excellent use\n"
" case for syscall(__NR_clone, 0, CLONE_PARENT), but glibc boneheadedly\n"
" caches getpid() results and offers no way to update the value, breaking\n"
" abort(), raise(), and a bunch of other things :-( */\n"
"\n"
CALL_L64("fork")
" cmpq $0, %rax\n"
" jl __afl_die\n"
" je __afl_fork_resume\n"
"\n"
" /* In parent process: write PID to pipe, then wait for child. */\n"
"\n"
" movl %eax, __afl_fork_pid(%rip)\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_fork_pid(%rip), %rsi /* data */\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n"
CALL_L64("write")
"\n"
" movq $0, %rdx /* no flags */\n"
" leaq __afl_temp(%rip), %rsi /* status */\n"
" movq __afl_fork_pid(%rip), %rdi /* PID */\n"
CALL_L64("waitpid")
" cmpq $0, %rax\n"
" jle __afl_die\n"
"\n"
" /* Relay wait status to pipe, then loop back. */\n"
"\n"
" movq $4, %rdx /* length */\n"
" leaq __afl_temp(%rip), %rsi /* data */\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n"
CALL_L64("write")
"\n"
" jmp __afl_fork_wait_loop\n"
"\n"
"__afl_fork_resume:\n"
"\n"
" /* In child process: close fds, resume execution. */\n"
"\n"
" movq $" STRINGIFY(FORKSRV_FD) ", %rdi\n"
CALL_L64("close")
"\n"
" movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi\n"
CALL_L64("close")
"\n"
" popq %rdx\n"
" popq %rdx\n"
"\n"
" movq %r12, %rsp\n"
" popq %r12\n"
"\n"
" movq 0(%rsp), %rax\n"
" movq 8(%rsp), %rcx\n"
" movq 16(%rsp), %rdi\n"
" movq 32(%rsp), %rsi\n"
" movq 40(%rsp), %r8\n"
" movq 48(%rsp), %r9\n"
" movq 56(%rsp), %r10\n"
" movq 64(%rsp), %r11\n"
"\n"
" movq 96(%rsp), %xmm0\n"
" movq 112(%rsp), %xmm1\n"
" movq 128(%rsp), %xmm2\n"
" movq 144(%rsp), %xmm3\n"
" movq 160(%rsp), %xmm4\n"
" movq 176(%rsp), %xmm5\n"
" movq 192(%rsp), %xmm6\n"
" movq 208(%rsp), %xmm7\n"
" movq 224(%rsp), %xmm8\n"
" movq 240(%rsp), %xmm9\n"
" movq 256(%rsp), %xmm10\n"
" movq 272(%rsp), %xmm11\n"
" movq 288(%rsp), %xmm12\n"
" movq 304(%rsp), %xmm13\n"
" movq 320(%rsp), %xmm14\n"
" movq 336(%rsp), %xmm15\n"
"\n"
" leaq 352(%rsp), %rsp\n"
"\n"
" jmp __afl_store\n"
"\n"
"__afl_die:\n"
"\n"
" xorq %rax, %rax\n"
CALL_L64("_exit")
"\n"
"__afl_setup_abort:\n"
"\n"
" /* Record setup failure so that we don't keep calling\n"
" shmget() / shmat() over and over again. */\n"
"\n"
" incb __afl_setup_failure(%rip)\n"
"\n"
" movq %r12, %rsp\n"
" popq %r12\n"
"\n"
" movq 0(%rsp), %rax\n"
" movq 8(%rsp), %rcx\n"
" movq 16(%rsp), %rdi\n"
" movq 32(%rsp), %rsi\n"
" movq 40(%rsp), %r8\n"
" movq 48(%rsp), %r9\n"
" movq 56(%rsp), %r10\n"
" movq 64(%rsp), %r11\n"
"\n"
" movq 96(%rsp), %xmm0\n"
" movq 112(%rsp), %xmm1\n"
" movq 128(%rsp), %xmm2\n"
" movq 144(%rsp), %xmm3\n"
" movq 160(%rsp), %xmm4\n"
" movq 176(%rsp), %xmm5\n"
" movq 192(%rsp), %xmm6\n"
" movq 208(%rsp), %xmm7\n"
" movq 224(%rsp), %xmm8\n"
" movq 240(%rsp), %xmm9\n"
" movq 256(%rsp), %xmm10\n"
" movq 272(%rsp), %xmm11\n"
" movq 288(%rsp), %xmm12\n"
" movq 304(%rsp), %xmm13\n"
" movq 320(%rsp), %xmm14\n"
" movq 336(%rsp), %xmm15\n"
"\n"
" leaq 352(%rsp), %rsp\n"
"\n"
" jmp __afl_return\n"
"\n"
".AFL_VARS:\n"
"\n"

#ifdef __APPLE__

" .comm __afl_area_ptr, 8\n"
#ifndef COVERAGE_ONLY
" .comm __afl_prev_loc, 8\n"
#endif /* !COVERAGE_ONLY */
" .comm __afl_fork_pid, 4\n"
" .comm __afl_temp, 4\n"
" .comm __afl_setup_failure, 1\n"

#else

" .lcomm __afl_area_ptr, 8\n"
#ifndef COVERAGE_ONLY
" .lcomm __afl_prev_loc, 8\n"
#endif /* !COVERAGE_ONLY */
" .lcomm __afl_fork_pid, 4\n"
" .lcomm __afl_temp, 4\n"
" .lcomm __afl_setup_failure, 1\n"

#endif /* ^__APPLE__ */

" .comm __afl_global_area_ptr, 8, 8\n"
"\n"
".AFL_SHM_ENV:\n"
" .asciz \"" SHM_ENV_VAR "\"\n"
"\n"
"/* --- END --- */\n"
"\n";

#endif /* !_HAVE_AFL_AS_H */

这段代码将在插完所有的桩后插入程序的最后,所以它只是相当于定义了__afl_maybe_log函数。因为是汇编写的,比较晦涩,所以简单写了个伪代码,便于理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __afl_maybe_log() {
if (SHM was not mapped) {
initialize SHM;
while(1) {
read(198, tmp, 4);
child_pid = fork();
if (!child_pid) goto __afl_store;
write(198 + 1, &child_pid, 4);
waitpid(child_pid, &status, 0);
write(198 + 1, &status, 4);
}
}
__afl_store:
cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
}

AFL使用了共享内存来记录每次测试的执行路径。如果执行__afl_maybe_log时共享内存还没有被映射过来,说明这是第一次运行目标程序并且是main函数开头的__afl_maybe_log,那么就将这个进程作为forkserver,用来与fuzzer通信。当fuzzer发出指令时就调用fork函数克隆自身并将信息传回去,其中198和199分别是fuzzer和forkserver通信的控制管道和状态管道。因为forkserver是在main函数的开头陷入死循环,所以fork出来的子进程能够完整地运行目标程序。此时共享内存已经被映射,所以之后执行__afl_maybe_log函数时就会执行__afl_store,在共享内存中记录当前程序的执行路径,完成“桩”的功能。

模糊测试常规的思路是用execve()来运行目标程序,但是execve()的执行非常浪费时间,因为需要等待链接器和各种链接库的初始化。AFL通过对目标程序的改造,使它在第一次执行时变成forkserver,这样在后续需要运行目标程序时只需要复制自己即可,这种机制大大地提高了模糊测试的效率。

模糊测试

正片从这里才开始,相关的代码在afl-fuzz.c中,有八千多行。总体的流程就是参数检查–>读取测试用例–>测试用例变异–>测试,细节太多,这里就讲最关键的测试用例变异部分和测试部分。

测试用例变异

SIMPLE BITFLIP (+dictionary construction)

顾名思义就是按位翻转,它对当前需要变异的测试用例按顺序执行以下六种变换。

bitflip 1/1

每次翻转1个bit,按照每1个bit的步长从头开始。同时检查是否有类似于PNG文件中的IHDR这样token存在,因为这样的token破坏之后就破坏了原来的文件格式,比如一个要求PNG文件作为输入的程序,当输入非标准PNG文件时执行路径一定是相同的,所以就不需要再相同的执行路径上浪费时间。

bitflip 2/1

每次翻转相邻的2个bit,按照每1个bit的步长从头开始

bitflip 4/1

每次翻转相邻的4个bit,按照每1个bit的步长从头开始

bitflip 8/8

每次翻转相邻的8个bit,按照每8个bit的步长从头开始,即依次对每个byte做翻转。同时生成effector map,这个以8个字节为一组,表示该位置上的8个字节对执行路径是否有影响,即该组字节是否有效。如果该组字节修改前后执行路径不变说明是无效字节,后续的变异中会跳过这些无效字节,从而提高效率。

bitflip 16/8

每次翻转相邻的16个bit,按照每8个bit的步长从头开始,即依次对每个word做翻转

bitflip 32/8

每次翻转相邻的32个bit,按照每8个bit的步长从头开始,即依次对每个dword做翻转

ARITHMETIC INC/DEC

分别以1、2、4个字节为一组进行1到35的加减运算,步长为1个字节,在bitflip中出现过的变异结果将不会出现,在effector map被标记为无效的字节也将跳过。在2、4个字节的情况下还同时考虑了大小端两种情况。

INTERESTING VALUES

分别以1、2、4个字节为一组进行特殊值替换,步长为1个字节,同样,之前出现过的测试用例和在effector map被标记为无效的字节将跳过。特殊值的定义如下

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
#define INTERESTING_8 \
-128, /* Overflow signed 8-bit when decremented */ \
-1, /* */ \
0, /* */ \
1, /* */ \
16, /* One-off with common buffer size */ \
32, /* One-off with common buffer size */ \
64, /* One-off with common buffer size */ \
100, /* One-off with common buffer size */ \
127 /* Overflow signed 8-bit when incremented */

#define INTERESTING_16 \
-32768, /* Overflow signed 16-bit when decremented */ \
-129, /* Overflow signed 8-bit */ \
128, /* Overflow signed 8-bit */ \
255, /* Overflow unsig 8-bit when incremented */ \
256, /* Overflow unsig 8-bit */ \
512, /* One-off with common buffer size */ \
1000, /* One-off with common buffer size */ \
1024, /* One-off with common buffer size */ \
4096, /* One-off with common buffer size */ \
32767 /* Overflow signed 16-bit when incremented */

#define INTERESTING_32 \
-2147483648LL, /* Overflow signed 32-bit when decremented */ \
-100663046, /* Large negative number (endian-agnostic) */ \
-32769, /* Overflow signed 16-bit */ \
32768, /* Overflow signed 16-bit */ \
65535, /* Overflow unsig 16-bit when incremented */ \
65536, /* Overflow unsig 16 bit */ \
100663045, /* Large positive number (endian-agnostic) */ \
2147483647 /* Overflow signed 32-bit when incremented */

DICTIONARY STUFF

将用户定义的token和之前bitflip找到的token分别替换、插入到测试用例的每个位置,其中bitflip找到的token没用做插入,同样在effector map被标记为无效的字节将跳过。

RANDOM HAVOC

对测试用例做随机次数的以下操作再传入测试

  • 随机选取某个bit进行翻转
  • 随机选取某个byte,将其设置为随机的interesting value
  • 随机选取某个word,并随机选取大、小端序,将其设置为随机的interesting value
  • 随机选取某个dword,并随机选取大、小端序,将其设置为随机的interesting value
  • 随机选取某个byte,对其减去一个随机数
  • 随机选取某个byte,对其加上一个随机数
  • 随机选取某个word,并随机选取大、小端序,对其减去一个随机数
  • 随机选取某个word,并随机选取大、小端序,对其加上一个随机数
  • 随机选取某个dword,并随机选取大、小端序,对其减去一个随机数
  • 随机选取某个dword,并随机选取大、小端序,对其加上一个随机数
  • 随机选取某个byte,将其设置为随机数
  • 随机删除一段bytes
  • 随机选取一个位置,插入一段随机长度的内容,其中75%的概率是插入原文中随机位置的内容,25%的概率是插入一段随机选取的数
  • 随机选取一个位置,替换为一段随机长度的内容,其中75%的概率是替换成原文中随机位置的内容,25%的概率是替换成一段随机选取的数
  • 随机选取一个位置,用随机选取的token(用户提供的或自动生成的)替换
  • 随机选取一个位置,用随机选取的token(用户提供的或自动生成的)插入

SPLICING

从测试用例队列中随机选取一个与当前测试用例差异明显的测试用例在随机位置进行拼接,在进入到RANDOM HAVOC环节进行随机变化

测试

上面说到了AFL如何启动一个目标程序和如何对测试用例进行变异,那怎么将变异后的测试用例传给目标程序当做输入呢?其实很简单,AFL在初始化forkserver的时候就用dup2()函数将测试用例的文件描述符复制为了标准输入的文件描述符0,这样之后往测试用例的文件描述符里写的测试用例就变成了标准输入,从而成为目标程序的输入。

AFL会对用户提供的所有测试用例进行一遍上述变异,这样才完成一个cycle,AFL运行过程中一直在做这样的cycle。当然,在后面的cycle中只做RANDOM HAVOC和SPLICING两个部分,因为其他部分的变异结果都是固定的。

参考资料

AFL(American Fuzzy Lop)实现细节与文件变异

AFL源码阅读笔记之gcc与fuzz部分

AFL编译插桩部分源码分析

Fuzzing random programs without execve()

初探AFL-Fuzz

Linux 的进程间通信:管道

文章目录
  1. 1. 插桩编译
    1. 1.1. afl-gcc
    2. 1.2. afl-as
  2. 2. 模糊测试
    1. 2.1. 测试用例变异
      1. 2.1.1. SIMPLE BITFLIP (+dictionary construction)
        1. 2.1.1.1. bitflip 1/1
        2. 2.1.1.2. bitflip 2/1
        3. 2.1.1.3. bitflip 4/1
        4. 2.1.1.4. bitflip 8/8
        5. 2.1.1.5. bitflip 16/8
        6. 2.1.1.6. bitflip 32/8
      2. 2.1.2. ARITHMETIC INC/DEC
      3. 2.1.3. INTERESTING VALUES
      4. 2.1.4. DICTIONARY STUFF
      5. 2.1.5. RANDOM HAVOC
      6. 2.1.6. SPLICING
    2. 2.2. 测试
  3. 3. 参考资料
|