fix custom mutator C examples

This commit is contained in:
vanhauser-thc
2023-04-15 10:12:20 +02:00
parent 8f6d9d66ef
commit e12acaa203
7 changed files with 73 additions and 499 deletions

View File

@ -1,342 +0,0 @@
#ifndef CUSTOM_MUTATOR_HELPERS
#define CUSTOM_MUTATOR_HELPERS
#include "config.h"
#include "types.h"
#include <stdlib.h>
#define INITIAL_GROWTH_SIZE (64)
#define RAND_BELOW(limit) (rand() % (limit))
/* Use in a struct: creates a name_buf and a name_size variable. */
#define BUF_VAR(type, name) \
type * name##_buf; \
size_t name##_size;
/* this fills in `&structptr->something_buf, &structptr->something_size`. */
#define BUF_PARAMS(struct, name) \
(void **)&struct->name##_buf, &struct->name##_size
typedef struct {
} afl_t;
static void surgical_havoc_mutate(u8 *out_buf, s32 begin, s32 end) {
static s8 interesting_8[] = {INTERESTING_8};
static s16 interesting_16[] = {INTERESTING_8, INTERESTING_16};
static s32 interesting_32[] = {INTERESTING_8, INTERESTING_16, INTERESTING_32};
switch (RAND_BELOW(12)) {
case 0: {
/* Flip a single bit somewhere. Spooky! */
s32 bit_idx = ((RAND_BELOW(end - begin) + begin) << 3) + RAND_BELOW(8);
out_buf[bit_idx >> 3] ^= 128 >> (bit_idx & 7);
break;
}
case 1: {
/* Set byte to interesting value. */
u8 val = interesting_8[RAND_BELOW(sizeof(interesting_8))];
out_buf[(RAND_BELOW(end - begin) + begin)] = val;
break;
}
case 2: {
/* Set word to interesting value, randomly choosing endian. */
if (end - begin < 2) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 1) break;
switch (RAND_BELOW(2)) {
case 0:
*(u16 *)(out_buf + byte_idx) =
interesting_16[RAND_BELOW(sizeof(interesting_16) >> 1)];
break;
case 1:
*(u16 *)(out_buf + byte_idx) =
SWAP16(interesting_16[RAND_BELOW(sizeof(interesting_16) >> 1)]);
break;
}
break;
}
case 3: {
/* Set dword to interesting value, randomly choosing endian. */
if (end - begin < 4) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 3) break;
switch (RAND_BELOW(2)) {
case 0:
*(u32 *)(out_buf + byte_idx) =
interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)];
break;
case 1:
*(u32 *)(out_buf + byte_idx) =
SWAP32(interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)]);
break;
}
break;
}
case 4: {
/* Set qword to interesting value, randomly choosing endian. */
if (end - begin < 8) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 7) break;
switch (RAND_BELOW(2)) {
case 0:
*(u64 *)(out_buf + byte_idx) =
(s64)interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)];
break;
case 1:
*(u64 *)(out_buf + byte_idx) = SWAP64(
(s64)interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)]);
break;
}
break;
}
case 5: {
/* Randomly subtract from byte. */
out_buf[(RAND_BELOW(end - begin) + begin)] -= 1 + RAND_BELOW(ARITH_MAX);
break;
}
case 6: {
/* Randomly add to byte. */
out_buf[(RAND_BELOW(end - begin) + begin)] += 1 + RAND_BELOW(ARITH_MAX);
break;
}
case 7: {
/* Randomly subtract from word, random endian. */
if (end - begin < 2) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 1) break;
if (RAND_BELOW(2)) {
*(u16 *)(out_buf + byte_idx) -= 1 + RAND_BELOW(ARITH_MAX);
} else {
u16 num = 1 + RAND_BELOW(ARITH_MAX);
*(u16 *)(out_buf + byte_idx) =
SWAP16(SWAP16(*(u16 *)(out_buf + byte_idx)) - num);
}
break;
}
case 8: {
/* Randomly add to word, random endian. */
if (end - begin < 2) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 1) break;
if (RAND_BELOW(2)) {
*(u16 *)(out_buf + byte_idx) += 1 + RAND_BELOW(ARITH_MAX);
} else {
u16 num = 1 + RAND_BELOW(ARITH_MAX);
*(u16 *)(out_buf + byte_idx) =
SWAP16(SWAP16(*(u16 *)(out_buf + byte_idx)) + num);
}
break;
}
case 9: {
/* Randomly subtract from dword, random endian. */
if (end - begin < 4) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 3) break;
if (RAND_BELOW(2)) {
*(u32 *)(out_buf + byte_idx) -= 1 + RAND_BELOW(ARITH_MAX);
} else {
u32 num = 1 + RAND_BELOW(ARITH_MAX);
*(u32 *)(out_buf + byte_idx) =
SWAP32(SWAP32(*(u32 *)(out_buf + byte_idx)) - num);
}
break;
}
case 10: {
/* Randomly add to dword, random endian. */
if (end - begin < 4) break;
s32 byte_idx = (RAND_BELOW(end - begin) + begin);
if (byte_idx >= end - 3) break;
if (RAND_BELOW(2)) {
*(u32 *)(out_buf + byte_idx) += 1 + RAND_BELOW(ARITH_MAX);
} else {
u32 num = 1 + RAND_BELOW(ARITH_MAX);
*(u32 *)(out_buf + byte_idx) =
SWAP32(SWAP32(*(u32 *)(out_buf + byte_idx)) + num);
}
break;
}
case 11: {
/* Just set a random byte to a random value. Because,
why not. We use XOR with 1-255 to eliminate the
possibility of a no-op. */
out_buf[(RAND_BELOW(end - begin) + begin)] ^= 1 + RAND_BELOW(255);
break;
}
}
}
/* This function calculates the next power of 2 greater or equal its argument.
@return The rounded up power of 2 (if no overflow) or 0 on overflow.
*/
static inline size_t next_pow2(size_t in) {
if (in == 0 || in > (size_t)-1)
return 0; /* avoid undefined behaviour under-/overflow */
size_t out = in - 1;
out |= out >> 1;
out |= out >> 2;
out |= out >> 4;
out |= out >> 8;
out |= out >> 16;
return out + 1;
}
/* This function makes sure *size is > size_needed after call.
It will realloc *buf otherwise.
*size will grow exponentially as per:
https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially/
Will return NULL and free *buf if size_needed is <1 or realloc failed.
@return For convenience, this function returns *buf.
*/
static inline void *maybe_grow(void **buf, size_t *size, size_t size_needed) {
/* No need to realloc */
if (likely(size_needed && *size >= size_needed)) return *buf;
/* No initial size was set */
if (size_needed < INITIAL_GROWTH_SIZE) size_needed = INITIAL_GROWTH_SIZE;
/* grow exponentially */
size_t next_size = next_pow2(size_needed);
/* handle overflow */
if (!next_size) { next_size = size_needed; }
/* alloc */
*buf = realloc(*buf, next_size);
*size = *buf ? next_size : 0;
return *buf;
}
/* Swaps buf1 ptr and buf2 ptr, as well as their sizes */
static inline void afl_swap_bufs(void **buf1, size_t *size1, void **buf2,
size_t *size2) {
void * scratch_buf = *buf1;
size_t scratch_size = *size1;
*buf1 = *buf2;
*size1 = *size2;
*buf2 = scratch_buf;
*size2 = scratch_size;
}
#undef INITIAL_GROWTH_SIZE
#endif

View File

@ -10,21 +10,21 @@
// afl-fuzz -i in -o out -- ./test-instr -f /tmp/foo // afl-fuzz -i in -o out -- ./test-instr -f /tmp/foo
// //
#include "custom_mutator_helpers.h"
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include "afl-fuzz.h"
typedef struct my_mutator { typedef struct my_mutator {
afl_t *afl; afl_state_t *afl;
} my_mutator_t; } my_mutator_t;
my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) { my_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
my_mutator_t *data = calloc(1, sizeof(my_mutator_t)); my_mutator_t *data = calloc(1, sizeof(my_mutator_t));
if (!data) { if (!data) {

View File

@ -7,7 +7,7 @@
*/ */
// You need to use -I/path/to/AFLplusplus/include -I. // You need to use -I/path/to/AFLplusplus/include -I.
#include "custom_mutator_helpers.h" #include "afl-fuzz.h"
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@ -26,19 +26,14 @@ static const char *commands[] = {
typedef struct my_mutator { typedef struct my_mutator {
afl_t *afl; afl_state_t *afl;
// any additional data here! // any additional data here!
size_t trim_size_current; size_t trim_size_current;
int trimmming_steps; int trimmming_steps;
int cur_step; int cur_step;
// Reused buffers: u8 *mutated_out, *post_process_buf, *trim_buf;
BUF_VAR(u8, fuzz);
BUF_VAR(u8, data);
BUF_VAR(u8, havoc);
BUF_VAR(u8, trim);
BUF_VAR(u8, post_process);
} my_mutator_t; } my_mutator_t;
@ -53,7 +48,7 @@ typedef struct my_mutator {
* There may be multiple instances of this mutator in one afl-fuzz run! * There may be multiple instances of this mutator in one afl-fuzz run!
* Return NULL on error. * Return NULL on error.
*/ */
my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) { my_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
srand(seed); // needed also by surgical_havoc_mutate() srand(seed); // needed also by surgical_havoc_mutate()
@ -65,6 +60,27 @@ my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
} }
if ((data->mutated_out = (u8 *)malloc(MAX_FILE)) == NULL) {
perror("afl_custom_init malloc");
return NULL;
}
if ((data->post_process_buf = (u8 *)malloc(MAX_FILE)) == NULL) {
perror("afl_custom_init malloc");
return NULL;
}
if ((data->trim_buf = (u8 *)malloc(MAX_FILE)) == NULL) {
perror("afl_custom_init malloc");
return NULL;
}
data->afl = afl; data->afl = afl;
return data; return data;
@ -96,31 +112,14 @@ size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
// the fuzzer // the fuzzer
size_t mutated_size = DATA_SIZE <= max_size ? DATA_SIZE : max_size; size_t mutated_size = DATA_SIZE <= max_size ? DATA_SIZE : max_size;
// maybe_grow is optimized to be quick for reused buffers. memcpy(data->mutated_out, buf, buf_size);
u8 *mutated_out = maybe_grow(BUF_PARAMS(data, fuzz), mutated_size);
if (!mutated_out) {
*out_buf = NULL;
perror("custom mutator allocation (maybe_grow)");
return 0; /* afl-fuzz will very likely error out after this. */
}
// Randomly select a command string to add as a header to the packet // Randomly select a command string to add as a header to the packet
memcpy(mutated_out, commands[rand() % 3], 3); memcpy(data->mutated_out, commands[rand() % 3], 3);
// Mutate the payload of the packet if (mutated_size > max_size) { mutated_size = max_size; }
int i;
for (i = 0; i < 8; ++i) {
// Randomly perform one of the (no len modification) havoc mutations *out_buf = data->mutated_out;
surgical_havoc_mutate(mutated_out, 3, mutated_size);
}
if (max_size > mutated_size) { mutated_size = max_size; }
*out_buf = mutated_out;
return mutated_size; return mutated_size;
} }
@ -144,24 +143,16 @@ size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
size_t afl_custom_post_process(my_mutator_t *data, uint8_t *buf, size_t afl_custom_post_process(my_mutator_t *data, uint8_t *buf,
size_t buf_size, uint8_t **out_buf) { size_t buf_size, uint8_t **out_buf) {
uint8_t *post_process_buf = if (buf_size + 5 > MAX_FILE) { buf_size = MAX_FILE - 5; }
maybe_grow(BUF_PARAMS(data, post_process), buf_size + 5);
if (!post_process_buf) {
perror("custom mutator realloc failed."); memcpy(data->post_process_buf + 5, buf, buf_size);
*out_buf = NULL; data->post_process_buf[0] = 'A';
return 0; data->post_process_buf[1] = 'F';
data->post_process_buf[2] = 'L';
data->post_process_buf[3] = '+';
data->post_process_buf[4] = '+';
} *out_buf = data->post_process_buf;
memcpy(post_process_buf + 5, buf, buf_size);
post_process_buf[0] = 'A';
post_process_buf[1] = 'F';
post_process_buf[2] = 'L';
post_process_buf[3] = '+';
post_process_buf[4] = '+';
*out_buf = post_process_buf;
return buf_size + 5; return buf_size + 5;
@ -197,13 +188,6 @@ int32_t afl_custom_init_trim(my_mutator_t *data, uint8_t *buf,
data->cur_step = 0; data->cur_step = 0;
if (!maybe_grow(BUF_PARAMS(data, trim), buf_size)) {
perror("init_trim grow");
return -1;
}
memcpy(data->trim_buf, buf, buf_size); memcpy(data->trim_buf, buf, buf_size);
data->trim_size_current = buf_size; data->trim_size_current = buf_size;
@ -284,27 +268,11 @@ int32_t afl_custom_post_trim(my_mutator_t *data, int success) {
size_t afl_custom_havoc_mutation(my_mutator_t *data, u8 *buf, size_t buf_size, size_t afl_custom_havoc_mutation(my_mutator_t *data, u8 *buf, size_t buf_size,
u8 **out_buf, size_t max_size) { u8 **out_buf, size_t max_size) {
if (buf_size == 0) { *out_buf = buf; // in-place mutation
*out_buf = maybe_grow(BUF_PARAMS(data, havoc), 1); if (buf_size <= sizeof(size_t)) { return buf_size; }
if (!*out_buf) {
perror("custom havoc: maybe_grow"); size_t victim = rand() % (buf_size - sizeof(size_t));
return 0;
}
**out_buf = rand() % 256;
buf_size = 1;
} else {
// We reuse buf here. It's legal and faster.
*out_buf = buf;
}
size_t victim = rand() % buf_size;
(*out_buf)[victim] += rand() % 10; (*out_buf)[victim] += rand() % 10;
return buf_size; return buf_size;
@ -371,9 +339,7 @@ uint8_t afl_custom_queue_new_entry(my_mutator_t *data,
void afl_custom_deinit(my_mutator_t *data) { void afl_custom_deinit(my_mutator_t *data) {
free(data->post_process_buf); free(data->post_process_buf);
free(data->havoc_buf); free(data->mutated_out);
free(data->data_buf);
free(data->fuzz_buf);
free(data->trim_buf); free(data->trim_buf);
free(data); free(data);

View File

@ -45,9 +45,8 @@
1) If you don't want to modify the test case, simply set `*out_buf = in_buf` 1) If you don't want to modify the test case, simply set `*out_buf = in_buf`
and return the original `len`. and return the original `len`.
NOTE: the following is currently NOT true, we abort in this case!
2) If you want to skip this test case altogether and have AFL generate a 2) If you want to skip this test case altogether and have AFL generate a
new one, return 0 or set `*out_buf = NULL`. new one, return 0.
Use this sparingly - it's faster than running the target program Use this sparingly - it's faster than running the target program
with patently useless inputs, but still wastes CPU time. with patently useless inputs, but still wastes CPU time.
@ -59,8 +58,6 @@
Note that the buffer will *not* be freed for you. To avoid memory leaks, Note that the buffer will *not* be freed for you. To avoid memory leaks,
you need to free it or reuse it on subsequent calls (as shown below). you need to free it or reuse it on subsequent calls (as shown below).
*** Feel free to reuse the original 'in_buf' BUFFER and return it. ***
Alright. The example below shows a simple postprocessor that tries to make Alright. The example below shows a simple postprocessor that tries to make
sure that all input files start with "GIF89a". sure that all input files start with "GIF89a".
@ -72,7 +69,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "alloc-inl.h" #include "afl-fuzz.h"
/* Header that must be present at the beginning of every test case: */ /* Header that must be present at the beginning of every test case: */
@ -80,8 +77,7 @@
typedef struct post_state { typedef struct post_state {
unsigned char *buf; size_t size;
size_t size;
} post_state_t; } post_state_t;
@ -95,15 +91,6 @@ void *afl_custom_init(void *afl) {
} }
state->buf = calloc(sizeof(unsigned char), 4096);
if (!state->buf) {
free(state);
perror("calloc");
return NULL;
}
return state; return state;
} }
@ -113,6 +100,10 @@ void *afl_custom_init(void *afl) {
size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf, size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
unsigned int len, unsigned char **out_buf) { unsigned int len, unsigned char **out_buf) {
/* we do in-place modification as we do not increase the size */
*out_buf = in_buf;
/* Skip execution altogether for buffers shorter than 6 bytes (just to /* Skip execution altogether for buffers shorter than 6 bytes (just to
show how it's done). We can trust len to be sane. */ show how it's done). We can trust len to be sane. */
@ -120,34 +111,7 @@ size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
/* Do nothing for buffers that already start with the expected header. */ /* Do nothing for buffers that already start with the expected header. */
if (!memcmp(in_buf, HEADER, strlen(HEADER))) { if (!memcmp(in_buf, HEADER, strlen(HEADER))) { return len; }
*out_buf = in_buf;
return len;
}
/* Allocate memory for new buffer, reusing previous allocation if
possible. Note we have to use afl-fuzz's own realloc!
We use afl_realloc because it is effective.
You can also work within in_buf, and assign it to *out_buf. */
*out_buf = afl_realloc(out_buf, len);
/* If we're out of memory, the most graceful thing to do is to return the
original buffer and give up on modifying it. Let AFL handle OOM on its
own later on. */
if (!*out_buf) {
*out_buf = in_buf;
return len;
}
if (len > strlen(HEADER))
memcpy(*out_buf + strlen(HEADER), in_buf + strlen(HEADER),
len - strlen(HEADER));
/* Insert the new header. */ /* Insert the new header. */
@ -162,7 +126,6 @@ size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
/* Gets called afterwards */ /* Gets called afterwards */
void afl_custom_deinit(post_state_t *data) { void afl_custom_deinit(post_state_t *data) {
free(data->buf);
free(data); free(data);
} }

View File

@ -30,7 +30,7 @@
#include <string.h> #include <string.h>
#include <zlib.h> #include <zlib.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include "alloc-inl.h" #include "afl-fuzz.h"
/* A macro to round an integer up to 4 kB. */ /* A macro to round an integer up to 4 kB. */
@ -53,7 +53,7 @@ void *afl_custom_init(void *afl) {
} }
state->buf = calloc(sizeof(unsigned char), 4096); state->buf = calloc(sizeof(unsigned char), MAX_FILE);
if (!state->buf) { if (!state->buf) {
free(state); free(state);
@ -80,21 +80,7 @@ size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
} }
/* This is not a good way to do it, if you do not need to grow the buffer unsigned int pos = 8;
then just work with in_buf instead for speed reasons.
But we want to show how to grow a buffer, so this is how it's done: */
unsigned int pos = 8;
unsigned char *new_buf = afl_realloc(out_buf, UP4K(len));
if (!new_buf) {
*out_buf = in_buf;
return len;
}
memcpy(new_buf, in_buf, len);
/* Minimum size of a zero-length PNG chunk is 12 bytes; if we /* Minimum size of a zero-length PNG chunk is 12 bytes; if we
don't have that, we can bail out. */ don't have that, we can bail out. */
@ -124,7 +110,7 @@ size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
if (real_cksum != file_cksum) { if (real_cksum != file_cksum) {
*(uint32_t *)(new_buf + pos + 8 + chunk_len) = real_cksum; *(uint32_t *)(data->buf + pos + 8 + chunk_len) = real_cksum;
} }
@ -134,7 +120,7 @@ size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
} }
*out_buf = new_buf; *out_buf = data->buf;
return len; return len;
} }

View File

@ -1,6 +1,6 @@
// This simple example just creates random buffer <= 100 filled with 'A' // This simple example just creates random buffer <= 100 filled with 'A'
// needs -I /path/to/AFLplusplus/include // needs -I /path/to/AFLplusplus/include
#include "custom_mutator_helpers.h" #include "afl-fuzz.h"
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@ -13,14 +13,14 @@
typedef struct my_mutator { typedef struct my_mutator {
afl_t *afl; afl_state_t *afl;
// Reused buffers: // Reused buffers:
BUF_VAR(u8, fuzz); u8 *fuzz_buf;
} my_mutator_t; } my_mutator_t;
my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) { my_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
srand(seed); srand(seed);
my_mutator_t *data = calloc(1, sizeof(my_mutator_t)); my_mutator_t *data = calloc(1, sizeof(my_mutator_t));
@ -31,6 +31,14 @@ my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
} }
data->fuzz_buf = (u8 *)malloc(MAX_FILE);
if (!data->fuzz_buf) {
perror("afl_custom_init malloc");
return NULL;
}
data->afl = afl; data->afl = afl;
return data; return data;
@ -44,18 +52,10 @@ size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
int size = (rand() % 100) + 1; int size = (rand() % 100) + 1;
if (size > max_size) size = max_size; if (size > max_size) size = max_size;
u8 *mutated_out = maybe_grow(BUF_PARAMS(data, fuzz), size);
if (!mutated_out) {
*out_buf = NULL; memset(data->fuzz_buf, _FIXED_CHAR, size);
perror("custom mutator allocation (maybe_grow)");
return 0; /* afl-fuzz will very likely error out after this. */
} *out_buf = data->fuzz_buf;
memset(mutated_out, _FIXED_CHAR, size);
*out_buf = mutated_out;
return size; return size;
} }

View File

@ -30,6 +30,7 @@
- unicorn_mode: - unicorn_mode:
- updated and minor issues fixed - updated and minor issues fixed
- new custom module: autotoken, a grammar free fuzzer for text inputs - new custom module: autotoken, a grammar free fuzzer for text inputs
- fixed custom mutator C examples
- better sanitizer default options support for all tools - better sanitizer default options support for all tools
- more minor fixes and cross-platform support - more minor fixes and cross-platform support