Skip to content

Commit d80063a

Browse files
authored
Begin work on a "structured" mutator mode (#795)
* Begin work on a "structured" mutator mode * mutator: fill in structured mode * worker: re-enable udis86 * README: document the MODE env var
1 parent 067ff92 commit d80063a

File tree

4 files changed

+101
-12
lines changed

4 files changed

+101
-12
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Run the fuzzer for a bit:
4646
* `V=1` enables verbose output on `stderr`
4747
* `D=1` enables the "dummy" mutation mode for debugging purposes
4848
* `M=1` enables the "manual" mutation mode (i.e., read from `stdin`)
49+
* `MODE=mode` can be used to configure the mutation mode in the absence of `D` and `M`
50+
* Valid mutation modes are `sliding` (default), `havoc`, and `structured`
4951

5052
Convert mishegos's raw output into JSONL suitable for analysis:
5153

src/include/mish_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ typedef enum {
102102
typedef enum {
103103
M_HAVOC = 0,
104104
M_SLIDING,
105+
M_STRUCTURED,
105106
M_DUMMY,
106107
M_MANUAL,
107108
} mutator_mode;

src/mishegos/mishegos.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,25 @@ static void mishegos_sem_init() {
181181
}
182182
}
183183

184+
static mutator_mode get_mut_mode() {
185+
const char *mode = getenv("MODE");
186+
187+
/* default to the "sliding" strategy in the mutator. */
188+
if (mode == NULL) {
189+
return M_SLIDING;
190+
}
191+
192+
if (strcmp(mode, "sliding") == 0) {
193+
return M_SLIDING;
194+
} else if (strcmp(mode, "havoc") == 0) {
195+
return M_HAVOC;
196+
} else if (strcmp(mode, "structured") == 0) {
197+
return M_STRUCTURED;
198+
}
199+
200+
errx(1, "unknown mutator mode requested: %s", mode);
201+
}
202+
184203
static void config_init() {
185204
/* TODO(ww): Configurable RNG seed.
186205
*/
@@ -195,7 +214,10 @@ static void config_init() {
195214
} else if (debugging) {
196215
GET_CONFIG()->mut_mode = M_DUMMY;
197216
} else {
198-
GET_CONFIG()->mut_mode = M_SLIDING;
217+
/* If we're not in a manual or debugging mode, try to figure out what
218+
* actual fuzzing mode the user wants from us.
219+
*/
220+
GET_CONFIG()->mut_mode = get_mut_mode();
199221
}
200222
}
201223

src/mishegos/mutator.c

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,72 @@ static bool sliding_candidate(input_slot *slot) {
237237
return true;
238238
}
239239

240+
/* Structured: generate an instruction candidate with the
241+
* "structured" approach.
242+
*/
243+
static bool structured_candidate(input_slot *slot) {
244+
/* We mirror build_sliding_candidate here, but with the constraint that
245+
* we never overapproximate: we constrain ourselves to trying
246+
* to build something that looks like an instruction of no more
247+
* than 15 bytes.
248+
*/
249+
250+
uint8_t len = 0;
251+
252+
/* Up to 4 legacy prefixes. Like sliding, we don't try to enforce group rules.
253+
* Unlike sliding, we allow for the possibility of no legacy prefixes.
254+
* Running max: 4.
255+
*/
256+
uint8_t prefix_count = (rand_byte() % 5);
257+
for (int i = 0; i < prefix_count; ++i) {
258+
slot->raw_insn[i] = legacy_prefixes[rand_byte() % sizeof(legacy_prefixes)];
259+
}
260+
len = prefix_count;
261+
262+
/* One or none REX prefixes.
263+
* Always choose a valid REX prefix if we're inserting one.
264+
* Running max: 5.
265+
*/
266+
if (rand_byte() % 2) {
267+
slot->raw_insn[len] = rex_prefixes[rand_byte() % sizeof(rex_prefixes)];
268+
len++;
269+
}
270+
271+
/* Random (but structured) opcode. Same as sliding.
272+
* Running max: 8
273+
*/
274+
opcode opc;
275+
rand_opcode(&opc);
276+
memcpy(slot->raw_insn + len, opc.op, opc.len);
277+
len += opc.len;
278+
279+
/* One or none ModR/M bytes, and one or none SIB bytes.
280+
* Both of these are just 8-bit LUTs, so they can be fully random.
281+
* Running max: 10.
282+
*/
283+
if (rand_byte() % 2) {
284+
slot->raw_insn[len] = rand_byte();
285+
len++;
286+
}
287+
288+
if (rand_byte() % 2) {
289+
slot->raw_insn[len] = rand_byte();
290+
len++;
291+
}
292+
293+
/* Finally, we have up to 5 bytes to play with for the immediate and
294+
* displacement. Fill some amount of that (maybe not all) with randomness.
295+
*/
296+
uint64_t tail = rand_long();
297+
uint8_t tail_size = rand_byte() % 6;
298+
memcpy(slot->raw_insn + len, &tail, tail_size);
299+
len += tail_size;
300+
301+
slot->len = len;
302+
303+
return true;
304+
}
305+
240306
/* Dummy: Generates a single NOP for debugging purposes.
241307
*/
242308
static bool dummy_candidate(input_slot *slot) {
@@ -285,25 +351,23 @@ void mutator_init() {
285351
* Returns false if the configured mutation mode has been exhausted.
286352
*/
287353
bool candidate(input_slot *slot) {
288-
bool exhausted;
289-
290354
switch (mut_mode) {
291355
case M_HAVOC: {
292-
exhausted = havoc_candidate(slot);
293-
break;
356+
return havoc_candidate(slot);
294357
}
295358
case M_SLIDING: {
296-
exhausted = sliding_candidate(slot);
297-
break;
359+
return sliding_candidate(slot);
360+
}
361+
case M_STRUCTURED: {
362+
return structured_candidate(slot);
298363
}
299364
case M_DUMMY: {
300-
exhausted = dummy_candidate(slot);
301-
break;
365+
return dummy_candidate(slot);
302366
}
303367
case M_MANUAL: {
304-
exhausted = manual_candidate(slot);
305-
break;
368+
return manual_candidate(slot);
306369
}
307370
}
308-
return exhausted;
371+
372+
__builtin_unreachable();
309373
}

0 commit comments

Comments
 (0)