first version with unix domain sockets is ready for testing

This commit is contained in:
Maik Betka
2023-04-17 17:09:48 +02:00
parent a3bc8d3440
commit 450dbae8cd
2 changed files with 232 additions and 33 deletions

View File

@ -1,5 +1,4 @@
#include "../../include/afl-fuzz.h" #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -7,10 +6,10 @@
#include <sys/un.h> #include <sys/un.h>
#include <unistd.h> #include <unistd.h>
#define INIT_BUF_SIZE 4096 #define INIT_BUF_SIZE 4096
#define SOCKET_NAME "/tmp/atnwalk.socket" #define SOCKET_NAME "/tmp/atnwalk.socket"
// handshake constants // handshake constants
const uint8_t SERVER_ARE_YOU_ALIVE = 42; const uint8_t SERVER_ARE_YOU_ALIVE = 42;
const uint8_t SERVER_YES_I_AM_ALIVE = 213; const uint8_t SERVER_YES_I_AM_ALIVE = 213;
@ -23,8 +22,10 @@ const uint8_t SERVER_ENCODE_BIT = 0b00001000;
typedef struct atnwalk_mutator { typedef struct atnwalk_mutator {
uint8_t *decoded_buf; uint8_t *fuzz_buf;
size_t decoded_size; size_t fuzz_size;
uint8_t *post_process_buf;
size_t post_process_size;
} atnwalk_mutator_t; } atnwalk_mutator_t;
@ -55,6 +56,32 @@ int write_all(int fd, uint8_t *buf, size_t buf_size) {
return 1; return 1;
} }
void put_uint32(uint8_t *buf, uint32_t val) {
buf[0] = (uint8_t) (val >> 24);
buf[1] = (uint8_t) ((val & 0x00ff0000) >> 16);
buf[2] = (uint8_t) ((val & 0x0000ff00) >> 8);
buf[3] = (uint8_t) (val & 0x000000ff);
}
uint32_t to_uint32(uint8_t *buf) {
uint32_t val = 0;
val |= (((uint32_t) buf[0]) << 24);
val |= (((uint32_t) buf[1]) << 16);
val |= (((uint32_t) buf[2]) << 8);
val |= ((uint32_t) buf[3]);
return val;
}
void put_uint64(uint8_t *buf, uint64_t val) {
buf[0] = (uint8_t) (val >> 56);
buf[1] = (uint8_t) ((val & 0x00ff000000000000) >> 48);
buf[2] = (uint8_t) ((val & 0x0000ff0000000000) >> 40);
buf[3] = (uint8_t) ((val & 0x000000ff00000000) >> 32);
buf[4] = (uint8_t) ((val & 0x00000000ff000000) >> 24);
buf[5] = (uint8_t) ((val & 0x0000000000ff0000) >> 16);
buf[6] = (uint8_t) ((val & 0x000000000000ff00) >> 8);
buf[7] = (uint8_t) (val & 0x00000000000000ff);
}
/** /**
* Initialize this custom mutator * Initialize this custom mutator
@ -67,19 +94,21 @@ int write_all(int fd, uint8_t *buf, size_t buf_size) {
* 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.
*/ */
atnwalk_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) { atnwalk_mutator_t *afl_custom_init(void *afl, unsigned int seed) {
srand(seed); srand(seed);
atnwalk_mutator_t *data = (atnwalk_mutator_t *) malloc(sizeof(atnwalk_mutator_t)); atnwalk_mutator_t *data = (atnwalk_mutator_t *) malloc(sizeof(atnwalk_mutator_t));
if (!data) { if (!data) {
perror("afl_custom_init alloc"); perror("afl_custom_init alloc");
return NULL; return NULL;
} }
data->decoded_buf = (uint8_t *) malloc(INIT_BUF_SIZE); data->fuzz_buf = (uint8_t *) malloc(INIT_BUF_SIZE);
data->decoded_size = INIT_BUF_SIZE; data->fuzz_size = INIT_BUF_SIZE;
data->post_process_buf = (uint8_t *) malloc(INIT_BUF_SIZE);
data->post_process_size = INIT_BUF_SIZE;
return data; return data;
} }
// TODO: implement
/** /**
* Perform custom mutations on a given input * Perform custom mutations on a given input
* *
@ -100,13 +129,12 @@ size_t afl_custom_fuzz(atnwalk_mutator_t *data, uint8_t *buf, size_t buf_size, u
uint8_t *add_buf, size_t add_buf_size, size_t max_size) { uint8_t *add_buf, size_t add_buf_size, size_t max_size) {
struct sockaddr_un addr; struct sockaddr_un addr;
int fd_socket; int fd_socket;
ssize_t n; uint8_t ctrl_buf[8];
uint8_t buffer[5]; uint8_t wanted;
// initialize the socket // initialize the socket
fd_socket = socket(AF_UNIX, SOCK_STREAM, 0); fd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd_socket == -1) { if (fd_socket == -1) {
perror("socket");
*out_buf = NULL; *out_buf = NULL;
return 0; return 0;
} }
@ -114,26 +142,127 @@ size_t afl_custom_fuzz(atnwalk_mutator_t *data, uint8_t *buf, size_t buf_size, u
addr.sun_family = AF_UNIX; addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1); strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
if (connect(fd_socket, (const struct sockaddr *) &addr, sizeof(addr)) == -1) { if (connect(fd_socket, (const struct sockaddr *) &addr, sizeof(addr)) == -1) {
perror("atnwalk server is down"); close(fd_socket);
*out_buf = NULL; *out_buf = NULL;
return 0; return 0;
} }
if (!write_all(fd_socket, buffer, 5)) { // TODO: how to set connection deadline? maybe not required if server already closes the connection?
perror("write to atnwalk server failed");
// TODO: there should be some kind of loop retrying with different seeds and ultimately giving up on that input?
// maybe this is not necessary, because we may also just return a single byte in case of failure?
// ask whether the server is alive
ctrl_buf[0] = SERVER_ARE_YOU_ALIVE;
if (!write_all(fd_socket, ctrl_buf, 1)) {
close(fd_socket);
*out_buf = NULL; *out_buf = NULL;
return 0; return 0;
} }
if (read_all(fd_socket, buffer, 5)) { // see whether the server replies as expected
perror("read to atnwalk server failed"); if (!read_all(fd_socket, ctrl_buf, 1) || ctrl_buf[0] != SERVER_YES_I_AM_ALIVE) {
exit(EXIT_FAILURE); close(fd_socket);
*out_buf = NULL;
return 0;
}
// tell the server what we want to do
wanted = SERVER_MUTATE_BIT | SERVER_ENCODE_BIT;
// 50% chance to perform a crossover if there is an additional buffer available
if ((add_buf_size > 0) && (rand() % 2)) {
wanted |= SERVER_CROSSOVER_BIT;
}
// tell the server what we want and how much data will be sent
ctrl_buf[0] = wanted;
put_uint32(ctrl_buf + 1, (uint32_t) buf_size);
if (!write_all(fd_socket, ctrl_buf, 5)) {
close(fd_socket);
*out_buf = NULL;
return 0;
}
// send the data to mutate and encode
if (!write_all(fd_socket, buf, buf_size)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
if (wanted & SERVER_CROSSOVER_BIT) {
// since we requested crossover, we will first tell how much additional data is to be expected
put_uint32(ctrl_buf, (uint32_t) add_buf_size);
if (!write_all(fd_socket, ctrl_buf, 4)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
// send the additional data for crossover
if (!write_all(fd_socket, add_buf, add_buf_size)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
// lastly, a seed is required for crossover so send one
put_uint64(ctrl_buf, (uint64_t) rand());
if (!write_all(fd_socket, ctrl_buf, 8)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
}
// since we requested mutation, we need to provide a seed for that
put_uint64(ctrl_buf, (uint64_t) rand());
if (!write_all(fd_socket, ctrl_buf, 8)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
// obtain the required buffer size for the data that will be returned
if (!read_all(fd_socket, ctrl_buf, 4)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
size_t new_size = (size_t) to_uint32(ctrl_buf);
// if the data is too large then we ignore this round
if (new_size > max_size) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
if (new_size > buf_size) {
// buf is too small, need to use data->fuzz_buf, let's see whether we need to reallocate
if (new_size > data->fuzz_size) {
data->fuzz_size = new_size << 1;
data->fuzz_buf = (uint8_t *) realloc(data->fuzz_buf, data->fuzz_size);
}
*out_buf = data->fuzz_buf;
} else {
// new_size fits into buf, so re-use it
*out_buf = buf;
}
// obtain the encoded data
if (!read_all(fd_socket, *out_buf, new_size)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
} }
close(fd_socket); close(fd_socket);
return new_size;
} }
// TODO: implement
/** /**
* A post-processing function to use right before AFL writes the test case to * A post-processing function to use right before AFL writes the test case to
* disk in order to execute the target. * disk in order to execute the target.
@ -151,23 +280,90 @@ size_t afl_custom_fuzz(atnwalk_mutator_t *data, uint8_t *buf, size_t buf_size, u
* A return of 0 indicates an error. * A return of 0 indicates an error.
*/ */
size_t afl_custom_post_process(atnwalk_mutator_t *data, uint8_t *buf, size_t buf_size, uint8_t **out_buf) { size_t afl_custom_post_process(atnwalk_mutator_t *data, uint8_t *buf, size_t buf_size, uint8_t **out_buf) {
data->decoded_buf[0] = 'p'; struct sockaddr_un addr;
data->decoded_buf[1] = 'u'; int fd_socket;
data->decoded_buf[2] = 't'; uint8_t ctrl_buf[8];
data->decoded_buf[3] = 's';
data->decoded_buf[4] = ' '; // initialize the socket
data->decoded_buf[5] = ';'; fd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
data->decoded_buf[6] = '\n'; if (fd_socket == -1) {
return 7; *out_buf = NULL;
return 0;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
if (connect(fd_socket, (const struct sockaddr *) &addr, sizeof(addr)) == -1) {
close(fd_socket);
*out_buf = NULL;
return 0;
}
// ask whether the server is alive
ctrl_buf[0] = SERVER_ARE_YOU_ALIVE;
if (!write_all(fd_socket, ctrl_buf, 1)) {
close(fd_socket);
*out_buf = NULL;
return 0;
}
// see whether the server replies as expected
if (!read_all(fd_socket, ctrl_buf, 1) || ctrl_buf[0] != SERVER_YES_I_AM_ALIVE) {
close(fd_socket);
*out_buf = NULL;
return 0;
}
// tell the server what we want and how much data will be sent
ctrl_buf[0] = SERVER_DECODE_BIT;
put_uint32(ctrl_buf + 1, (uint32_t) buf_size);
if (!write_all(fd_socket, ctrl_buf, 5)) {
close(fd_socket);
*out_buf = NULL;
return 0;
}
// send the data to decode
if (!write_all(fd_socket, buf, buf_size)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
// obtain the required buffer size for the data that will be returned
if (!read_all(fd_socket, ctrl_buf, 4)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
size_t new_size = (size_t) to_uint32(ctrl_buf);
// need to use data->post_process_buf, let's see whether we need to reallocate
if (new_size > data->post_process_size) {
data->post_process_size = new_size << 1;
data->post_process_buf = (uint8_t *) realloc(data->post_process_buf, data->post_process_size);
}
*out_buf = data->post_process_buf;
// obtain the decoded data
if (!read_all(fd_socket, *out_buf, new_size)) {
close(fd_socket);
*out_buf = buf;
return buf_size;
}
close(fd_socket);
return new_size;
} }
// TODO: implement
/** /**
* Deinitialize everything * Deinitialize everything
* *
* @param data The data ptr from afl_custom_init * @param data The data ptr from afl_custom_init
*/ */
void afl_custom_deinit(atnwalk_mutator_t *data) { void afl_custom_deinit(atnwalk_mutator_t *data) {
free(data->decoded_buf); free(data->fuzz_buf);
free(data->post_process_buf);
free(data); free(data);
} }

View File

@ -130,12 +130,15 @@ write_to_testcase(afl_state_t *afl, void **mem, u32 len, u32 fix) {
new_size = afl->max_length; new_size = afl->max_length;
} }
// TODO: think about how to enable the change without breaking other implementations
if (new_mem != *mem) { *mem = new_mem; } // if (new_mem != *mem) { *mem = new_mem; }
/* everything as planned. use the potentially new data. */ /* everything as planned. use the potentially new data. */
afl_fsrv_write_to_testcase(&afl->fsrv, *mem, new_size); // TODO: think about how to enable the change without breaking other implementations
len = new_size; afl_fsrv_write_to_testcase(&afl->fsrv, new_mem, new_size);
// TODO: think about how to enable the change without breaking other implementations
// len = new_size;
} else { } else {