├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── callback.c ├── callback.h ├── fuzzotron.c ├── fuzzotron.h ├── generator.c ├── generator.h ├── hash.h ├── monitor.c ├── monitor.h ├── replay.c ├── sender.c ├── sender.h ├── trace.c ├── trace.h └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | fuzzotron 2 | replay 3 | .vscode/ 4 | *.o 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | build-essential \ 5 | git \ 6 | wget \ 7 | curl \ 8 | libssl-dev \ 9 | libpcre3-dev \ 10 | ca-certificates \ 11 | --no-install-recommends 12 | 13 | WORKDIR /src 14 | RUN git clone https://gitlab.com/akihe/radamsa \ 15 | && git clone https://gitlab.com/akihe/blab \ 16 | && git clone https://github.com/denandz/fuzzotron 17 | WORKDIR /src/radamsa 18 | RUN make \ 19 | && make install 20 | WORKDIR /src/blab 21 | RUN make \ 22 | && make install 23 | WORKDIR /src/fuzzotron 24 | RUN make 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, DoI 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BLAB := $(shell command -v blab 2> /dev/null) 2 | RADAMSA := $(shell command -v radamsa 2> /dev/null) 3 | CFLAGS = -W -g -O3 4 | LIBS = -lpcre -lssl -lcrypto -lpthread 5 | 6 | FUZZOTRON = fuzzotron 7 | REPLAY = replay 8 | FUZZOTRON_SRC = fuzzotron.c callback.c generator.c monitor.c sender.c trace.c 9 | REPLAY_SRC = replay.c callback.c sender.c 10 | 11 | FUZZOTRON_OBJ = $(FUZZOTRON_SRC:.c=.o) 12 | REPLAY_OBJ = $(REPLAY_SRC:.c=.o) 13 | 14 | .PHONY: all 15 | all: fuzzotron replay 16 | ifndef RADAMSA 17 | $(error radamsa is not available. Download from https://gitlab.com/akihe/radamsa) 18 | endif 19 | 20 | ifndef BLAB 21 | $(info Blab is not available, attempting to use blab mode will fail. Download from https://gitlab.com/akihe/blab) 22 | endif 23 | 24 | $(FUZZOTRON): $(FUZZOTRON_OBJ) 25 | $(CC) ${LDFLAGS} -o $@ $^ ${LIBS} 26 | 27 | $(REPLAY): $(REPLAY_OBJ) 28 | $(CC) ${LDFLAGS} -o $@ $^ ${LIBS} 29 | 30 | $(SRCS:.c):%.c 31 | $(CC) $(CFLAGS) -MM $< 32 | 33 | .PHONY: clean 34 | clean: 35 | rm -f $(REPLAY_OBJ) $(FUZZOTRON_OBJ) ${FUZZOTRON} ${REPLAY} 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fuzzotron 2 | 3 | Fuzzotron is a simple network fuzzer supporting TCP, UDP and multi-threading. Radamsa and Blab are used for test case generation. Fuzzotron exists as a first-port-of-call network fuzzer, aiming for low setup overhead. 4 | 5 | ## Install 6 | 7 | You need to install some dependencies and at the very least Radamsa (https://gitlab.com/akihe/radamsa). Compiling targets with Address Sanitizer is also useful (https://clang.llvm.org/docs/AddressSanitizer.html) 8 | 9 | ``` 10 | apt install libssl-dev libpcre3-dev 11 | make 12 | ``` 13 | 14 | ## Usage 15 | 16 | ``` 17 | FuzzoTron - A Fuzzing Harness built around OUSPG's Blab and Radamsa. 18 | 19 | Usage (crash-detect mode - blab): ./fuzzotron --blab -g http_request -h 127.0.0.1 -p 80 -P tcp -o output 20 | Usage (crash-detect mode - radamsa): ./fuzzotron --radamsa --directory testcases/ -h 127.0.0.1 -p 80 -P tcp -o output 21 | Usage (crash-detect mode unix socket - radamsa): ./fuzzotron --radamsa --directory testcases/ -h /tmp/something.sock -P unix -o output 22 | Usage (log-monitor mode): ./fuzzotron --blab -g http_request -h 127.0.0.1 -p 80 -P tcp -m /var/log/messages -r 'segfault' -o output 23 | Usage (process-monitor mode): ./fuzzotron --radamsa --directory testcases/ -h 127.0.0.1 -p 80 -P tcp -c 23123 -o output 24 | 25 | General Options: 26 | -k Number of seconds before fuzzing stops 27 | -o Output directory for crashes REQUIRED 28 | -t Number of worker threads 29 | --trace Use AFL style tracing. Single threaded only, see README.md 30 | 31 | Generation Options: 32 | --blab Use Blab for testcase generation 33 | -g Blab grammar to use - eg /usr/share/blab/html.blab 34 | --radamsa Use Radamsa for testcase generation 35 | --directory Directory with original test cases 36 | 37 | Connection Options: 38 | -h IP of host to connect to or path to unix domain socket REQUIRED 39 | -p Port to connect to REQUIRED for TCP and UDP 40 | -P Protocol to use (tcp,udp,unix) REQUIRED 41 | --ssl Use SSL for the connection 42 | --destroy Use TCP_REPAIR mode to immediately destroy the connection, do not send FIN/RST. 43 | 44 | Monitoring Options: 45 | -c PID to check - Fuzzotron will halt if this PID dissapears 46 | -m Logfile to monitor 47 | -r Regex to use with above logfile 48 | -z Check script to execute. Should return 1 on server being okay and anything else otherwise. 49 | ``` 50 | 51 | Basic Fuzzotron usage would look like: 52 | 53 | ``` 54 | ./fuzzotron --radamsa --directory testcases -h 127.0.0.1 -p 8080 -P tcp -c 15634 -o crashes 55 | ``` 56 | 57 | The above will use radamsa to generate test cases based on the files in the `testcases` directory, and fire these test cases at `8080/tcp` on `localhost`. In the event that PID `15634` goes away, fuzzing will stop and the last 100 test cases kept in the `crashes` output directory. This would be used for something like nginx, running with a single worker and the workers PID being specified. Without a PID specified, Fuzzotron will keep running until a connection failure occurs, indicating the port is down. Fuzzotron currently does not automatically re-spawn the target after a crash is detected. The `-o` flag specifies the directory to spool the current test cases out to in the event of a crash. 58 | 59 | When a crash occurs, the test case queues for each thread will be stored in `/-`. The replay utility can be used to send individual test cases. Replay uses the same sender code as Fuzzotron, so anything you've put into `callback.c` will also be triggered by replay. 60 | 61 | ``` 62 | for i in $(find output/ -type f); do replay -h -p -P $i; done 63 | ``` 64 | 65 | Given the nature of daemon fuzzing, running a rolling tcpdump is good insurance. Worse comes to worst, you can carve the test cases out of the PCAP and replay them manually. For example, a tcpdump command that will capture packets into a 10MB file, and capture a maximum of 10 files, would be executed as such (adding your own filter so you only catch fuzzing relevant packets is a good idea): 66 | 67 | ``` 68 | tcpdump -i ens33 -C 10M -W 10 -w out.pcap 69 | ``` 70 | 71 | ### UDP fuzzing 72 | 73 | UDP fuzzing requires some method of determining if the target is down, as the connection should never fail (yay UDP). If you're fuzzing a daemon running on localhost (recommended), then use the `-c` option and specify a PID. If the daemon is remote, Fuzzotron supports the use of an auxiliary check script (`--check` or `-z`). The script needs to output `1` as its first character on success, any anything else on failure. 74 | 75 | An example to fuzz something like, I dunno, a DHCP server running on a router, would be: 76 | 77 | ``` 78 | ./fuzzotron --radamsa --directory ~/testcase-archive/network-services/dhcp-client -h 192.168.1.1 -p 67 -P udp -z ./is-dhcp-up.py -o output 79 | ``` 80 | 81 | ### TCP_REPAIR mode 82 | 83 | Specifying the `--destroy` flag will put the TCP connections into `TCP_REPAIR` mode before closing, meaning no `FIN` packets will get sent. `TCP_REPAIR` requires the `CAP_NET_ADMIN` capability. If `--destroy` ends up stalling, you may have identified a slowloris style DOS condition where the target is blocking waiting for more data. 84 | 85 | ## Testcases 86 | 87 | Testcases are raw packet data. In the TCP example above, assuming we're fuzzing `HTTP/1.1`, the testcase directory would contain a sample set of HTTP requests. The quality of your starting corpus effects the fuzzers performance and good sampleset of various different packets that trigger different functionality are key to robust fuzz testing. 88 | 89 | There are a few ways to assemble your starting testcases. You can use the daemon with an appropriate client, capture the traffic with wireshark/tcpdump then extract the client packet data. This is a good step to do regardless, especially in network protocols with state-machines which might need you to send specific packets before getting to the functionality you'd like to fuzz (see the next section). 90 | 91 | [Wireshark's sample captures](https://wiki.wireshark.org/SampleCaptures) has a good sample set to work from for a bunch of common protocols. You can also see if a similar daemon is implemented in [OSS-Fuzz](https://github.com/google/oss-fuzz) and grab the example testcases from there. 92 | 93 | "Testcase Discovery Mode" is also supported, which lets you use a BLAB grammar and AFL-style tracing to procedurally generate testcases and store any that hit new code paths. More information is available further down in this readme. 94 | 95 | ## Connection Setup and Teardown 96 | 97 | Sometimes some things need to happen with a connection prior to your fuzz testcase being sent. EG, if you need to send a 'hello' packet, and receive a response prior to sending your payload. Sometimes things also need to happen after the testcase is sent, like sending the 'actually-do-the-work-now-FFS' packet. `callback.c` defines two methods that you can modify to achieve this. The `callback_pre_send()` method also allows you to tamper the testcase before its sent, so one can implement things like packet checksumming. 98 | 99 | ## Generators 100 | 101 | Currently, radamsa and blab are supported for testcase generation. Radamsa takes a directory that has valid test cases and performs mutations on the provided test cases. Blab takes a grammar file which is used to programmatically generate the cases. Detailed documentation is available on their respective gitlab pages - (https://gitlab.com/akihe/radamsa) and (https://gitlab.com/akihe/blab) 102 | 103 | Fuzzotron generates test cases in batches of 100 and stores them in `/dev/shm/fuzzotron/-`. Given we don't have granular visibility of exactly what case may have caused a crash (eg, you send 5 cases, case number 3 causes some long running thing to happen that results in a heap overflow (like heap exhaustion or something), server crashes at case number 5 but that's not the one that triggered the issue...), this means more manual triage. Upon detecting a crash, the latest queues for all threads are spooled to the output directory specified via a getopt argument. 104 | 105 | ### Deterministic fuzzing 106 | 107 | Radamsa appears to be a bit overzealous with its mutations. A deterministic step has been introduced when using `--radamsa` mode, which will perform a walking bit mutation prior to moving to radamsa for fuzzing. 108 | 109 | ## AFL style tracing 110 | 111 | Fuzzotron can use the coverage data provided by a target compiled with `afl-gcc` et-al. You need to create the SysV shared memory segment that the application will use and then pass this to both the target application and Fuzzotron. As network services can be rather non-deterministic, each case on a new path is fired multiple times and only saved if it behaves deterministically, otherwise it's jettisoned. Currently tracing is only supported if you're running a single Fuzzotron thread. This is all pretty sketchy and I wouldn't rely on it... 112 | 113 | After your program is compiled, you would need to do the following. I suggest using `afl-clang-fast` (llvm mode...) as it plays nicer with multi-threaded targets. 114 | 115 | ``` 116 | $ ipcmk -M 65536 117 | Shared memory id: 118718481 118 | $ __AFL_SHM_ID=118718481 ./targetd 119 | 120 | # in a separate session 121 | $ ./fuzzotron --radamsa --directory -o -h 127.0.0.1 -p -P tcp --trace 118718481 122 | ``` 123 | 124 | As new solid paths are found, these will be saved in the test-case directory provided. 125 | 126 | ### Attention Deficit Fuzzing 127 | 128 | If a new path is found, then deterministic operations are performed against this path immediately. This is mainly due to Fuzzotron having no concept of an input-test-case-queue at this point. 129 | 130 | ### Testcase Discovery Mode 131 | 132 | Using blab plus AFL style tracing is supported. Fuzzotron will save any generated testcases that trip a new path into the directory specified by `--directory`. This can be useful for generating a set of starting testcases to seed a mutational fuzzer (eg, radamsa mode). 133 | 134 | ## Random Musings 135 | 136 | Something something smart fuzzing seems simple but can get hard fast, instrumentation is important, testcases and mutations and so forth. Fuzzing daemons can be a right pain in the arse, but in general it seems that having your cores spinning away with a dumb fuzzer like Fuzzotron while you're coding your AI based protocol specific monstrosity can net some interesting crashes. Setup is intended to be relatively fast and easy, so why not right? 137 | 138 | ## Linux Tunables 139 | 140 | If you run Fuzzotron, especially against localhost services, you will likely find that Fuzzotron can fire requests and connections faster than linux can clean them up on close(). The following sysctl tunables should be set to minimize the impact of this: 141 | 142 | ``` 143 | sysctl -w net.ipv4.tcp_tw_recycle=1 144 | sysctl -w net.ipv4.tcp_tw_reuse=1 145 | ``` 146 | 147 | If you are using the PID monitor to check for crashes, the fuzzer will automatically resume on a failed connect. This is useful for servers that have bugs or test cases that will lock them up for a while. 148 | 149 | ``` 150 | sysctl -w net.ipv4.tcp_syn_retries=1 151 | ``` 152 | 153 | ## Common Troubleshooting 154 | 155 | * Multiple 'connection refused' errors looping - If you're providing a PID to check, then confirm that the socket is still listening. The monitored PID may still exist but the listening socket could be dead. 156 | * Fuzzotron is stalling! - check the above linux tunables, make sure they're set 157 | * No paths using `--trace` mode - confirm that your target is compiled with `afl-gcc`, and that the SHM number is correct 158 | 159 | ## Acknowledgments 160 | 161 | * lcamtuf - Chunks of code and a number of core concepts from lcamtuf's AFL were used in this code base. 162 | * VT - Testing, contributing, being the OG renegade master. 163 | -------------------------------------------------------------------------------- /callback.c: -------------------------------------------------------------------------------- 1 | /* 2 | * File: callback.c 3 | * Author: DoI 4 | * 5 | * This file defines a set of callback methods that can be used 6 | * to perform custom actions prior to, or after, sending a test 7 | * case. Relies on an -O3 compiler optimization to prune out the 8 | * calls if there is nothing defined in these methods. 9 | */ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "generator.h" 18 | #include "util.h" 19 | 20 | // Called after the socket is connected but before the test case is sent. 21 | void callback_pre_send(int sock, testcase_t * testcase){ 22 | /* 23 | your custom connection setup code goes here! 24 | tip: xxd -i can be used to spit out C arrays 25 | 26 | char packet[] = {0x40, 0x52}; 27 | write(sock, packet, sizeof(packet)); 28 | 29 | or modify the testcase about to be sent somehow 30 | 31 | memset(testcase->data, 0x00, 1); // set first byte to null 32 | 33 | or change something about the socket, such as enabling broadcasting or setting a source port 34 | 35 | int broadcast = 1; 36 | if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)) < 0) 37 | { 38 | fatal("[!] Error: Could not set broadcast: %s\n", strerror(errno)); 39 | } 40 | 41 | struct sockaddr_in client_addr; 42 | memset(&client_addr, 0, sizeof(client_addr)); 43 | client_addr.sin_family = AF_INET; 44 | client_addr.sin_addr.s_addr = htonl(INADDR_ANY); 45 | client_addr.sin_port = htons(68); 46 | 47 | if (bind(sock, (struct sockaddr *) &client_addr, sizeof(client_addr)) < 0) { 48 | fatal("[!] Error: Could not bind socket: %s\n", strerror(errno)); 49 | } 50 | 51 | */ 52 | } 53 | 54 | // Called after the testcase is sent but before the socket is closed. 55 | void callback_post_send(int sock){ 56 | 57 | } 58 | 59 | void callback_ssl_pre_send(SSL * ssl, testcase_t * testcase){ 60 | 61 | } 62 | 63 | void callback_ssl_post_send(SSL * ssl){ 64 | 65 | } -------------------------------------------------------------------------------- /callback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: callback.c 3 | * Author: DoI 4 | */ 5 | 6 | #ifndef CALLBACK_H 7 | #define CALLBACK_H 8 | 9 | #include "generator.h" 10 | 11 | void callback_pre_send(int sock, testcase_t *testcase); 12 | void callback_post_send(int sock); 13 | void callback_ssl_pre_send(SSL * ssl, testcase_t * testcase); 14 | void callback_ssl_post_send(SSL * ssl); 15 | 16 | #endif -------------------------------------------------------------------------------- /fuzzotron.c: -------------------------------------------------------------------------------- 1 | /* 2 | * File: fuzzotron.c 3 | * Author: DoI 4 | * 5 | * Fuzzotron is a simple network socket fuzzer. Connect to a tcp or udp port 6 | * and fire some testcases generated with either blab or radamsa. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "monitor.h" 31 | #include "fuzzotron.h" 32 | #include "sender.h" 33 | #include "generator.h" 34 | #include "trace.h" 35 | #include "util.h" 36 | 37 | // Struct to hold arguments passed to the monitor thread 38 | struct monitor_args mon_args; 39 | 40 | volatile int stop = 0; // the global 'stop fuzzing' variable. When set to 1, all threads will spool 41 | // their cases to disk and exit. 42 | int timeout_stop = 0; // similar to stop, but needed to know if the test cases should be saved. 43 | pthread_mutex_t runlock; 44 | int check_pid = 0; // server pid to check for crash. 45 | int timeout_secs = 0; // time in seconds until fuzzing stops. 46 | struct fuzzer_args fuzz; // Arguments for the fuzzer threads 47 | char * output_dir = NULL; // directory for potential crashes 48 | 49 | static unsigned long cases_sent = 0; 50 | static unsigned long cases_jettisoned = 0; 51 | static unsigned long paths = 0; 52 | 53 | int main(int argc, char** argv) { 54 | 55 | memset(&fuzz, 0x00, sizeof(fuzz)); 56 | // parse arguments 57 | int c, threads = 1; 58 | static int use_blab = 0, use_radamsa = 0; 59 | char * logfile = NULL, * regex = NULL; 60 | fuzz.protocol = 0; fuzz.is_tls = 0; fuzz.destroy = 0; 61 | 62 | static struct option arg_options[] = { 63 | {"alpn", required_argument, 0, 'l'}, 64 | {"blab", no_argument, &use_blab, 1}, 65 | {"radamsa", no_argument, &use_radamsa, 1}, 66 | {"ssl", no_argument, &fuzz.is_tls, 1}, 67 | {"grammar", required_argument, 0, 'g'}, 68 | {"output", required_argument, 0, 'o'}, 69 | {"directory", required_argument, 0, 'd'}, 70 | {"protocol", required_argument, 0, 'p'}, 71 | {"destroy", no_argument, &fuzz.destroy, 1}, 72 | {"checkscript", required_argument, 0, 'z'}, 73 | {"trace", required_argument, 0, 's'}, 74 | {0, 0, 0, 0} 75 | }; 76 | int arg_index; 77 | while((c = getopt_long(argc, argv, "d:c:h:p:g:t:m:c:P:r:w:s:z:o:k:", arg_options, &arg_index)) != -1){ 78 | switch(c){ 79 | case 'c': 80 | // Define PID to check for crash 81 | check_pid = atoi(optarg); 82 | printf("[+] Monitoring PID %d\n", check_pid); 83 | break; 84 | 85 | case 'd': 86 | // define test case directory for blab 87 | fuzz.in_dir = optarg; 88 | if(directory_exists(fuzz.in_dir) < 0){ 89 | fatal("Could not open %s\n", fuzz.in_dir); 90 | } 91 | break; 92 | 93 | case 'g': 94 | // define grammar 95 | fuzz.grammar = optarg; 96 | break; 97 | 98 | case 'h': 99 | // define host 100 | fuzz.host = optarg; 101 | break; 102 | 103 | case 'k': 104 | // set time for fuzzing to run (in seconds) 105 | timeout_secs = atoi(optarg); 106 | break; 107 | 108 | case 'l': 109 | // set ALPN string 110 | fuzz.alpn = optarg; 111 | break; 112 | 113 | case 'm': 114 | // Log file to monitor 115 | logfile = optarg; 116 | break; 117 | 118 | case 'o': 119 | // Output dir for crashes 120 | output_dir = optarg; 121 | 122 | break; 123 | 124 | case 'p': 125 | // define port 126 | fuzz.port = atoi(optarg); 127 | break; 128 | 129 | case 'P': 130 | // define protocol 131 | if((strcmp(optarg,"udp") != 0) && (strcmp(optarg,"tcp") != 0) && (strcmp(optarg,"unix") != 0)){ 132 | fatal("Please specify either 'tcp', 'udp' or 'unix' for -P\n"); 133 | } 134 | if(strcmp(optarg,"tcp") == 0){ 135 | fuzz.protocol = 1; 136 | fuzz.send = send_tcp; 137 | } 138 | else if(strcmp(optarg,"udp") == 0){ 139 | fuzz.protocol = 2; 140 | fuzz.send = send_udp; 141 | } 142 | else if(strcmp(optarg,"unix") == 0){ 143 | fuzz.protocol = 3; 144 | fuzz.send = send_unix; 145 | } 146 | 147 | break; 148 | 149 | case 'r': 150 | // define regex for monitoring 151 | regex = optarg; 152 | break; 153 | 154 | case 't': 155 | // define threads 156 | threads = atoi(optarg); 157 | break; 158 | 159 | case 's': 160 | fuzz.shm_id = atoi(optarg); 161 | break; 162 | 163 | case 'z': 164 | fuzz.check_script = optarg; 165 | break; 166 | 167 | } 168 | } 169 | 170 | // check argument sanity 171 | if((fuzz.host == NULL) || (fuzz.port == 0 && fuzz.protocol != 3) || 172 | (use_blab == 1 && use_radamsa == 1) || 173 | (use_blab == 0 && use_radamsa == 0) || 174 | (use_blab == 1 && fuzz.in_dir && !fuzz.shm_id) || 175 | (use_radamsa == 1 && fuzz.grammar != NULL) || 176 | (fuzz.protocol == 0) || (output_dir == NULL)){ 177 | help(); 178 | return -1; 179 | } 180 | 181 | // if we're using blab, ensure we have a grammar defined 182 | if(use_blab == 1 && fuzz.grammar == NULL){ 183 | fatal("If using blab, -g or --grammar must be specified\n"); 184 | } 185 | // if we're using radamsa, ensure the directory with the example cases is defined 186 | if(use_radamsa == 1 && fuzz.in_dir == NULL){ 187 | fatal("If using radamsa, -d or --directory must be specified\n"); 188 | } 189 | 190 | if(fuzz.shm_id && threads > 1){ 191 | fatal("Tracing only supported single threaded"); 192 | } 193 | if(fuzz.shm_id && fuzz.gen == BLAB && fuzz.in_dir == NULL){ 194 | fatal("Blab and tracing requires --directory"); 195 | } 196 | if(fuzz.shm_id && use_blab == 1 && fuzz.in_dir){ 197 | // Note: hmmm, maybe using blab and instrumentation is a good idea... Save testcases that blab generates 198 | // that hit new paths and use this to seed a mutation-based fuzzer? 199 | puts(GRN "[+] Experimental discovery mode enabled\n" RESET); 200 | } 201 | 202 | if(fuzz.is_tls){ 203 | SSL_library_init(); 204 | OpenSSL_add_all_algorithms(); 205 | SSL_load_error_strings(); 206 | } 207 | 208 | if(logfile != NULL){ 209 | if(regex == NULL){ 210 | printf("[!] No regex specified, falling back to Crash-Detect mode\n"); 211 | } 212 | else { 213 | printf("[+] Monitoring logfile %s\n", logfile); 214 | // Spawn the monitor 215 | int rc; 216 | pthread_t monitor; 217 | 218 | mon_args.file = logfile; 219 | mon_args.regex = regex; 220 | 221 | printf("[+] Spawning monitor\n"); 222 | rc = pthread_create(&monitor, NULL, call_monitor, NULL); 223 | if(rc){ 224 | fatal("Creating pthread failed: %s\n", strerror(errno)); 225 | } 226 | printf("[+] Monitor Spawned!\n"); 227 | } 228 | } 229 | 230 | if(fuzz.check_script){ 231 | if(!file_exists(fuzz.check_script)){ 232 | printf("[!] File %s not found\n", fuzz.check_script); 233 | help(); 234 | return -1; 235 | } 236 | 237 | printf("[+] Using check script: %s\n", fuzz.check_script); 238 | } 239 | 240 | fuzz.tmp_dir = CASE_DIR; 241 | if(directory_exists(fuzz.tmp_dir) < 0){ 242 | if(mkdir(fuzz.tmp_dir, 0755)<0){ 243 | fatal("[!] Could not mkdir %s: %s\n", fuzz.tmp_dir, strerror(errno)); 244 | } 245 | } 246 | 247 | if(directory_exists(output_dir) < 0){ 248 | if(mkdir(output_dir, 0755)<0){ 249 | fatal("[!] Could not mkdir %s: %s\n", output_dir, strerror(errno)); 250 | } 251 | } 252 | 253 | if(use_blab == 1){ 254 | fuzz.gen = BLAB; 255 | } 256 | else if(use_radamsa){ 257 | fuzz.gen = RADAMSA; 258 | } 259 | 260 | if (pthread_mutex_init(&runlock, NULL) != 0){ 261 | fatal("[!] pthread_mutex_init failed"); 262 | } 263 | 264 | signal(SIGPIPE, SIG_IGN); 265 | pthread_t workers[threads]; 266 | int i; 267 | struct worker_args targs[threads]; 268 | for(i = 1; i <= threads; i++){ 269 | 270 | targs[i-1].thread_id = i; 271 | targs[i-1].threads = threads; 272 | 273 | printf("[+] Spawning worker thread %d\n", i); 274 | if(pthread_create(&workers[i-1], NULL, worker, &targs[i-1]) > 0) 275 | fatal("Creating pthread failed: %s\n", strerror(errno)); 276 | 277 | usleep(2000); 278 | } 279 | 280 | pthread_t timeout_monitor; 281 | if(timeout_secs){ 282 | printf("[+] Spawning timeout monitor\n"); 283 | if(pthread_create(&timeout_monitor, NULL, timer_job, NULL) > 0) 284 | fatal("Creating pthread failed: %s\n", strerror(errno)); 285 | printf("[.] Timeout monitor alive and will stop testing in %d seconds\n", timeout_secs); 286 | } 287 | 288 | 289 | char spinner[4] = "|/-\\"; 290 | struct spint { unsigned i:2; } s; 291 | s.i=0; 292 | while(1){ 293 | usleep(50000); 294 | if(stop == 1){ 295 | printf("\n"); 296 | break; 297 | } 298 | 299 | printf("[%c] Sent cases: %lu", spinner[s.i], cases_sent); 300 | if(fuzz.shm_id) 301 | printf(" Paths:%lu Jettisoned: %lu\r", paths, cases_jettisoned); 302 | else 303 | printf("\r"); 304 | 305 | fflush(stdout); 306 | s.i++; 307 | } 308 | 309 | if(timeout_secs){ 310 | pthread_join(timeout_monitor, NULL); 311 | } 312 | for(i = 1; i <= threads; i++){ 313 | pthread_join(workers[i-1], NULL); 314 | } 315 | 316 | pthread_mutex_destroy(&runlock); 317 | printf("[.] Done. Total testcases issued: %lu\n", cases_sent); 318 | 319 | return 1; 320 | } 321 | 322 | void * call_monitor(){ 323 | monitor(mon_args.file, mon_args.regex); 324 | return NULL; 325 | } 326 | 327 | // timeout monitor's flow - checks if the elapsed time has passed the defined timeout, and if so triggers a stop 328 | void * timer_job(void * args __attribute__((unused))){ 329 | time_t start_time; 330 | 331 | time(&start_time); 332 | while(stop == 0 && difftime(time(NULL), start_time) < timeout_secs){ 333 | sleep(1); 334 | } 335 | 336 | if(stop == 0){ 337 | pthread_mutex_lock(&runlock); 338 | printf("[!] Reached timeout\n"); 339 | stop = 1; 340 | timeout_stop = 1; 341 | pthread_mutex_unlock(&runlock); 342 | } 343 | return NULL; 344 | } 345 | 346 | // worker thread, generate cases and sends them 347 | void * worker(void * worker_args){ 348 | struct worker_args *thread_info = (struct worker_args *)worker_args; 349 | printf("[.] Worker %u alive\n", thread_info->thread_id); 350 | 351 | int deterministic = 1; 352 | 353 | // Use the PID as the prefix for generation 354 | char prefix[25]; 355 | sprintf(prefix,"%d",(int)syscall(SYS_gettid)); 356 | 357 | // Testcases 358 | testcase_t * cases = 0x00; 359 | testcase_t * entry = 0x00; 360 | 361 | uint32_t exec_hash; 362 | int r; 363 | 364 | if(fuzz.shm_id > 0){ 365 | printf("[.] Trace enabled\n"); 366 | memset(fuzz.virgin_bits, 255, MAP_SIZE); 367 | fuzz.trace_bits = setup_shm(fuzz.shm_id); 368 | } 369 | 370 | if(fuzz.shm_id > 0 && fuzz.gen == RADAMSA){ 371 | cases = load_testcases(fuzz.in_dir, ""); // load all cases from the provided dir 372 | entry = cases; 373 | 374 | if(fuzz.trace_bits == 0){ 375 | return NULL; 376 | } 377 | 378 | // A server crash in calibration is not handled gracefully, this needs to be tidied up 379 | while(entry){ 380 | memset(fuzz.trace_bits, 0x00, MAP_SIZE); 381 | if(fuzz.send(fuzz.host, fuzz.port, entry) < 0){ 382 | fatal("[!] Failure in calibration\n"); 383 | } 384 | 385 | exec_hash = wait_for_bitmap(fuzz.trace_bits); 386 | if(exec_hash > 0){ 387 | if(has_new_bits(fuzz.virgin_bits, fuzz.trace_bits) > 1){ 388 | r = calibrate_case(entry, fuzz.trace_bits); 389 | if(r == 0) 390 | cases_jettisoned++; 391 | else{ 392 | paths++; 393 | } 394 | } 395 | } 396 | entry = entry->next; 397 | cases_sent++; 398 | } 399 | printf("\n[.] Loaded Paths: %lu Jettisoned: %lu\n", paths, cases_jettisoned); 400 | free_testcases(cases); 401 | } 402 | 403 | while(1){ 404 | // generate the test cases 405 | if(fuzz.gen == BLAB){ 406 | cases = generator_blab(CASE_COUNT, fuzz.grammar, fuzz.tmp_dir, prefix); 407 | } 408 | 409 | else if(fuzz.gen == RADAMSA){ 410 | // Perform some deterministic mutations before going off to radamsa. 411 | // currently limited to the first thread. 412 | if(deterministic == 1 && thread_info->thread_id == 1){ 413 | testcase_t * orig_cases = load_testcases(fuzz.in_dir, ""); // load all cases from the provided dir 414 | testcase_t * orig_entry = orig_cases; 415 | 416 | while(orig_entry){ 417 | if(determ_fuzz(orig_entry->data, orig_entry->len) < 0){ 418 | free_testcases(orig_cases); 419 | goto cleanup; 420 | } 421 | orig_entry = orig_entry->next; 422 | 423 | if(stop < 0){ 424 | break; 425 | } 426 | } 427 | free_testcases(orig_cases); 428 | 429 | deterministic = 0; 430 | if(fuzz.shm_id) 431 | printf("[.] Deterministic mutations completed, sent: %lu paths: %lu\n", cases_sent, paths); 432 | else 433 | printf("[.] Deterministic mutations completed, sent: %lu\n", cases_sent); 434 | 435 | if(stop < 0) // an error or crash occured during the deteministic steps 436 | break; 437 | 438 | continue; 439 | } 440 | 441 | cases = generator_radamsa(CASE_COUNT, fuzz.in_dir, fuzz.tmp_dir, prefix); 442 | } 443 | 444 | if(send_cases(cases) < 0){ 445 | goto cleanup; 446 | } 447 | } 448 | 449 | cleanup: 450 | printf("[!] Thread %d exiting\n", thread_info->thread_id); 451 | return NULL; 452 | } 453 | 454 | // Perform determisistic mutations, id and max paramters for splitting work load across threads 455 | int determ_fuzz(char * data, unsigned long len){ // }, unsigned int id){ 456 | unsigned long max = len << 3; 457 | unsigned long offset = 0; 458 | unsigned long determ_batch_size = strtol(CASE_COUNT, NULL, 10); 459 | 460 | if(determ_batch_size == 0){ 461 | fatal("[!] determ_batch_size strtol returned 0\n"); 462 | } 463 | 464 | testcase_t * cases; 465 | 466 | if(max < determ_batch_size){ 467 | cases = generate_swbitflip(data, len, offset, max); 468 | if(send_cases(cases)<0){ 469 | return -1; 470 | } 471 | } 472 | else{ 473 | unsigned long i = 0; 474 | 475 | while(i < (max/determ_batch_size)){ 476 | cases = generate_swbitflip(data, len, offset, determ_batch_size); 477 | if(send_cases(cases) < 0){ 478 | return -1; 479 | } 480 | offset = offset+determ_batch_size; 481 | i++; 482 | } 483 | // generate remainder 484 | if(max % determ_batch_size > 0){ 485 | cases = generate_swbitflip(data, len, offset, max % determ_batch_size); 486 | if(send_cases(cases) < 0){ 487 | return -1; 488 | } 489 | } 490 | } 491 | 492 | return 0; 493 | } 494 | 495 | // Send all cases in a struct. return -1 if any failure, otherwise 0. Frees the supplied cases struct 496 | // and updates global counters. 497 | int send_cases(void * cases){ 498 | int ret = 0, r = 0; 499 | testcase_t * entry = cases; 500 | uint32_t exec_hash; 501 | 502 | while(entry){ 503 | if(entry->len == 0){ 504 | // no data in test case, go to next one. Radamsa will generate null 505 | // testcases sometimes... 506 | entry = entry->next; 507 | continue; 508 | } 509 | if(fuzz.shm_id){ 510 | memset(fuzz.trace_bits, 0x00, MAP_SIZE); 511 | ret = fuzz.send(fuzz.host, fuzz.port, entry); 512 | if(ret < 0) 513 | break; 514 | 515 | exec_hash = wait_for_bitmap(fuzz.trace_bits); 516 | if(exec_hash > 0){ 517 | if(has_new_bits(fuzz.virgin_bits, fuzz.trace_bits) > 1){ 518 | r = calibrate_case(entry, fuzz.trace_bits); 519 | if(r == -1){ 520 | // crash during calibration? 521 | ret = r; 522 | break; 523 | } 524 | else if(r == 0){ 525 | cases_jettisoned++; 526 | } 527 | else{ 528 | paths++; // new case! save and perform some deterministic fuzzing 529 | save_case(entry->data, entry->len, exec_hash, fuzz.in_dir); 530 | 531 | if(fuzz.gen != BLAB){ 532 | determ_fuzz(entry->data, entry->len); // attention defecit fuzzing 533 | } 534 | } 535 | } 536 | } 537 | } 538 | else { 539 | // no instrumentation 540 | ret = fuzz.send(fuzz.host, fuzz.port, entry); 541 | 542 | if(ret < 0) 543 | break; 544 | } 545 | 546 | entry = entry->next; 547 | cases_sent++; 548 | } 549 | 550 | if(check_stop(cases, ret)<0){ 551 | free_testcases(cases); 552 | return -1; 553 | } 554 | 555 | free_testcases(cases); 556 | return 0; 557 | } 558 | 559 | // checks the return code from send_cases et-al and sets the global stop variable if 560 | // its time to stop fuzzing and saves the cases. 561 | int check_stop(void * cases, int result){ 562 | int ret = result; 563 | 564 | // if global stop, save cases 565 | pthread_mutex_lock(&runlock); 566 | if(stop == 1){ 567 | // save cases 568 | if(!timeout_stop){ 569 | save_testcases(cases, output_dir); 570 | } 571 | pthread_mutex_unlock(&runlock); 572 | return -1; 573 | } 574 | pthread_mutex_unlock(&runlock); 575 | 576 | // If process id is supplied, check it exists and set stop if it doesn't 577 | if(check_pid > 0){ 578 | if((pid_exists(check_pid)) == -1){ 579 | ret = -1; 580 | } 581 | else{ 582 | ret = 0; 583 | } 584 | } 585 | 586 | if(fuzz.check_script){ 587 | int r; 588 | r = run_check(fuzz.check_script); 589 | if( r != 1){ 590 | printf("[!] Check script %s returned %d, stopping\n", fuzz.check_script, r); 591 | ret = -1; 592 | } 593 | else{ 594 | ret = 0; 595 | } 596 | } 597 | 598 | if(ret == -1){ 599 | // We have experienced a crash. set the global stop var 600 | pthread_mutex_lock(&runlock); 601 | stop = 1; 602 | save_testcases(cases, output_dir); 603 | pthread_mutex_unlock(&runlock); 604 | } 605 | 606 | return ret; 607 | } 608 | 609 | /* Calibrate a new testcase. Returns 1 if the testcase behaves deterministically, 0 if it does not 610 | * EG: has variable behaviour. Without this, non deterministic features would cause a bunch of 611 | * tiny, useless test cases. Return -1 on failure. Timeout on waiting for the bitmap to stop changing 612 | * is an immediate 0. 613 | */ 614 | int calibrate_case(testcase_t * testcase, uint8_t * trace_bits){ 615 | uint32_t hash, tmp_hash, i; 616 | 617 | memset(trace_bits, 0x00, MAP_SIZE); 618 | if(fuzz.send(fuzz.host, fuzz.port, testcase) < 0){ 619 | return -1; 620 | } 621 | 622 | hash = wait_for_bitmap(trace_bits); // check null 623 | if(hash == 0 || hash == NULL_HASH) // unstable test case, bitmap still changing after 2 seconds, or no bitmap change 624 | return 0; 625 | 626 | for(i = 0; i < 4; i++){ 627 | memset(trace_bits, 0x00, MAP_SIZE); 628 | if(fuzz.send(fuzz.host, fuzz.port, testcase) < 0){ 629 | return -1; 630 | } 631 | tmp_hash = wait_for_bitmap(trace_bits); 632 | if(tmp_hash != hash){ // non-deterministic testcase 633 | return 0; 634 | } 635 | } 636 | 637 | // timing and case trimming should eventually go here 638 | 639 | return 1; 640 | } 641 | 642 | int pid_exists(int pid){ 643 | struct stat s; 644 | char path[PATH_MAX]; 645 | 646 | sprintf(path, "/proc/%d", pid); 647 | if(stat(path, &s) == -1){ 648 | // PID not found 649 | printf("[!!] PID %d not found. Check for server crash\n", pid); 650 | return -1; 651 | } 652 | 653 | // PID found 654 | return 0; 655 | } 656 | 657 | int run_check(char * script){ 658 | 659 | if(access(script, X_OK) < 0){ 660 | fatal("[!] Error accessing check script %s: %s\n", script, strerror(errno)); 661 | } 662 | 663 | int out_pipe[2]; 664 | int err_pipe[2]; 665 | pid_t pid; 666 | char ret[2]; 667 | memset(ret, 0x00, 2); 668 | 669 | if(pipe(out_pipe) < 0 || pipe(err_pipe) < 0){ 670 | fatal("[!] Error with pipe: %s\n", strerror(errno)); 671 | } 672 | if((pid = fork()) == 0){ 673 | dup2(err_pipe[1], 2); 674 | dup2(out_pipe[1], 1); 675 | close(out_pipe[0]); 676 | close(out_pipe[1]); 677 | close(err_pipe[0]); 678 | close(err_pipe[1]); 679 | 680 | char *args[] = {script, 0}; 681 | execv(args[0], args); 682 | 683 | exit(0); 684 | } 685 | 686 | else if(pid < 0){ 687 | fatal("[!] FORK FAILED!\n"); 688 | } 689 | else{ 690 | close(err_pipe[1]); 691 | close(out_pipe[1]); 692 | waitpid(pid, NULL, 0); 693 | if(read(out_pipe[0], ret, 1) < 0){ 694 | fatal("read() failed"); 695 | }; 696 | close(err_pipe[0]); 697 | close(out_pipe[0]); 698 | return atoi(&ret[0]); 699 | } 700 | 701 | return -1; 702 | } 703 | 704 | int file_exists(char * file){ 705 | struct stat s; 706 | return(stat(file, &s) == 0); 707 | } 708 | 709 | /* 710 | * Check if a directory exists, returns 0 on success or < 0 on failure. 711 | */ 712 | int directory_exists(char * dir){ 713 | DIR * d = opendir(dir); 714 | if(d != NULL){ 715 | closedir(d); 716 | return 0; 717 | } 718 | else{ 719 | return -1; 720 | } 721 | } 722 | 723 | void help(){ 724 | // Print the help and exit 725 | printf("FuzzoTron - A Fuzzing Harness built around OUSPG's Blab and Radamsa.\n\n"); 726 | printf("Usage (crash-detect mode - blab): ./fuzzotron --blab -g http_request -h 127.0.0.1 -p 80 -P tcp -o output\n"); 727 | printf("Usage (crash-detect mode - radamsa): ./fuzzotron --radamsa --directory testcases/ -h 127.0.0.1 -p 80 -P tcp -o output\n"); 728 | printf("Usage (crash-detect mode unix socket - radamsa): ./fuzzotron --radamsa --directory testcases/ -h /tmp/something.sock -P unix -o output\n"); 729 | printf("Usage (log-monitor mode): ./fuzzotron --blab -g http_request -h 127.0.0.1 -p 80 -P tcp -m /var/log/messages -r 'segfault' -o output\n"); 730 | printf("Usage (process-monitor mode): ./fuzzotron --radamsa --directory testcases/ -h 127.0.0.1 -p 80 -P tcp -c 23123 -o output\n\n"); 731 | printf("General Options:\n"); 732 | printf("\t-k\t\tNumber of seconds before fuzzing stops\n"); 733 | printf("\t-o\t\tOutput directory for crashes REQUIRED\n"); 734 | printf("\t-t\t\tNumber of worker threads\n"); 735 | printf("\t--trace\t\tUse AFL style tracing. Single threaded only, see README.md\n\n"); 736 | printf("Generation Options:\n"); 737 | printf("\t--blab\t\tUse Blab for testcase generation\n"); 738 | printf("\t-g\t\tBlab grammar to use - eg /usr/share/blab/html.blab\n"); 739 | printf("\t--radamsa\tUse Radamsa for testcase generation\n"); 740 | printf("\t--directory\tDirectory with original test cases\n\n"); 741 | printf("Connection Options:\n"); 742 | printf("\t-h\t\tIP of host to connect to or path to unix domain socket REQUIRED\n"); 743 | printf("\t-p\t\tPort to connect to REQUIRED for TCP and UDP\n"); 744 | printf("\t-P\t\tProtocol to use (tcp,udp,unix) REQUIRED\n"); 745 | printf("\t--ssl\t\tUse SSL for the connection\n"); 746 | printf("\t--destroy\tUse TCP_REPAIR mode to immediately destroy the connection, do not send FIN/RST.\n\n"); 747 | printf("Monitoring Options:\n"); 748 | printf("\t-c\t\tPID to check - Fuzzotron will halt if this PID dissapears\n"); 749 | printf("\t-m\t\tLogfile to monitor\n"); 750 | printf("\t-r\t\tRegex to use with above logfile\n"); 751 | printf("\t-z\t\tCheck script to execute. Should return 1 on server being okay and anything else otherwise.\n"); 752 | exit(0); 753 | } 754 | -------------------------------------------------------------------------------- /fuzzotron.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: fuzzotron.h 3 | * Author: DoI 4 | */ 5 | 6 | #ifndef FUZZOTRON_H 7 | #define FUZZOTRON_H 8 | 9 | #include 10 | #include "trace.h" 11 | #include "generator.h" 12 | 13 | // Tunables 14 | #define CASE_COUNT "100" 15 | #define CASE_DIR "/dev/shm/fuzzotron" 16 | 17 | #define RADAMSA 0x01 18 | #define BLAB 0x02 19 | 20 | extern volatile int stop; // set to 1 to stop fuzzing 21 | 22 | struct fuzzer_args { 23 | int gen; // generator for the test cases. Blab, radamsa, custom etcetera. 24 | char * in_dir; 25 | char * grammar; 26 | char * tmp_dir; // temporary directory to store test cases 27 | char * host; 28 | char * check_script; // script to check server status. Must return 1 on server-up or anything else on server-down (crashed) 29 | int protocol; // 1 == TCP, 2 == UDP, 3 == UNIX 30 | int destroy; // Use TCP_REPAIR to destroy the connection, do not send a RST after the testcase 31 | int port; 32 | int is_tls; 33 | char * alpn; 34 | 35 | int32_t shm_id; // Shared memory address for AFL style tracing 36 | uint8_t * trace_bits; 37 | uint8_t virgin_bits[MAP_SIZE]; 38 | 39 | int (*send)(char * host, int port, testcase_t * testcase); // pointer to method to send a packet. 40 | }; 41 | 42 | extern struct fuzzer_args fuzz; 43 | 44 | struct monitor_args { 45 | char * file; 46 | char * regex; 47 | }; 48 | 49 | // Worker args struct containing some thread information. Used for divvying up deterministic mutations amongst multiple threads. 50 | struct worker_args { 51 | unsigned int thread_id; // specific thread identifier 52 | unsigned int threads; // total number of threads 53 | }; 54 | 55 | int main(int argc, char** argv); 56 | void * call_monitor(); 57 | void * timer_job(void * args); 58 | void * worker(void * worker_args); 59 | int pid_exists(int pid); 60 | void help(); 61 | int run_check(char * script); 62 | int directory_exists(char * dir); 63 | int file_exists(char * file); 64 | int calibrate_case(testcase_t * testcase, uint8_t * trace_bits); 65 | int determ_fuzz(char * data, unsigned long len); 66 | int send_cases(void * cases); 67 | int check_stop(void * cases, int result); 68 | 69 | #endif -------------------------------------------------------------------------------- /generator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * File: generator.c 3 | * Author: DoI 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "generator.h" 21 | #include "util.h" 22 | 23 | // Executes radamsa and returns a linked list of test cases 24 | // this is pretty inefficient, and running a radamsa server and reading from the socket 25 | // would likely be a far, far smarter idea. 26 | testcase_t * generator_radamsa(char * count, char * testcase_dir, char * path, char * prefix){ 27 | pid_t pid; 28 | int s; 29 | char output[PATH_MAX]; 30 | testcase_t * testcase; 31 | 32 | snprintf(output, PATH_MAX, "%s/%s-%%n", path, prefix); 33 | char * argv[] = { "radamsa", "-n", count, "-r", "-o", output, testcase_dir, 0 }; 34 | 35 | if((pid = fork()) == 0){ 36 | execvp(argv[0], argv); 37 | exit(0); 38 | } 39 | else if(pid < 0){ 40 | fatal("[!] generator_radamsa fork() failed: %s", strerror(errno)); 41 | } 42 | else 43 | waitpid(pid, &s, 0x00); 44 | 45 | testcase = load_testcases(path, prefix); 46 | return testcase; 47 | } 48 | 49 | // Executes blab and returns a linked list of test cases 50 | testcase_t * generator_blab(char * count, char * grammar, char * path, char * prefix){ 51 | pid_t pid; 52 | int s; 53 | char output[PATH_MAX]; 54 | testcase_t * testcase; 55 | 56 | snprintf(output, PATH_MAX, "%s/%s-%%n", path, prefix); 57 | 58 | char * argv[] = { "blab", grammar, "-n", count , "-o", output, 0 }; 59 | 60 | if((pid = fork()) == 0){ 61 | execvp(argv[0], argv); 62 | exit(0); 63 | } 64 | else if(pid < 0){ 65 | printf("[!] generator_blab fork() failed: %s", strerror(errno)); 66 | return 0; 67 | } 68 | else 69 | waitpid(pid, &s, 0x00); 70 | 71 | testcase = load_testcases(path, prefix); 72 | 73 | return testcase; 74 | } 75 | 76 | // single walking bit, returns a linked struct of testcases 77 | testcase_t * generate_swbitflip(char * data, unsigned long in_len, unsigned long offset, unsigned long count){ 78 | unsigned long i = 0; 79 | char * output, * input; 80 | testcase_t * testcase, * entry; 81 | ft_malloc(sizeof(testcase_t),testcase); 82 | entry = testcase; 83 | 84 | ft_malloc(in_len, input); 85 | memset(input, 0x00, in_len); 86 | memcpy(input, data, in_len); 87 | 88 | // If starting from a non-zero offset, retroactively apply the flips 89 | // that should have already happened. 90 | if(offset > 0){ 91 | for(i = 0; i < offset; i++) 92 | FLIP_BIT(input, i); 93 | } 94 | 95 | for(i = 0; i < count; i++){ 96 | ft_malloc(in_len, output); 97 | memset(output, 0x00, in_len); 98 | memcpy(output, input, in_len); 99 | 100 | if(i > 0){ 101 | ft_malloc(sizeof(testcase_t),entry->next); 102 | entry = entry->next; 103 | } 104 | 105 | FLIP_BIT(output, i+offset); 106 | 107 | entry->len = in_len; 108 | entry->data = output; 109 | } 110 | 111 | entry->next = 0; 112 | 113 | free(input); 114 | return testcase; 115 | } 116 | 117 | // load all testcases from dir into a linked list 118 | testcase_t * load_testcases(char * path, char * prefix){ 119 | int i = 0; 120 | testcase_t * testcase, * entry; 121 | 122 | ft_malloc(sizeof(testcase_t), testcase); 123 | entry = testcase; 124 | 125 | DIR * dir; 126 | struct dirent *ents; 127 | if((dir = opendir(path)) == NULL){ 128 | fatal("[!] Error: Could not open directory: %s\n", strerror(errno)); 129 | } 130 | 131 | while ((ents = readdir(dir)) != NULL){ 132 | struct stat s; 133 | 134 | if(strncmp(ents->d_name, prefix, strlen(prefix)) != 0) 135 | continue; 136 | 137 | FILE * fp; 138 | char file_path[PATH_MAX]; 139 | 140 | snprintf(file_path, PATH_MAX, "%s/%s", path, ents->d_name); 141 | if((fp = fopen(file_path, "r"))== NULL){ 142 | fatal("[!] Error: Could not open file %s: %s\n", file_path, strerror(errno)); 143 | } 144 | 145 | if(fstat(fileno(fp), &s) == -1){ 146 | fatal("[!] Error: fstat: %s\n", strerror(errno)); 147 | } 148 | 149 | if(! S_ISREG(s.st_mode)) { 150 | // not a regular file, skip! 151 | fclose(fp); 152 | continue; 153 | } 154 | 155 | if (fseek(fp, 0L, SEEK_END) == 0) { 156 | long bufsize = ftell(fp); 157 | if (bufsize == -1){ 158 | fatal("[!] Error with ftell: %s", strerror(errno)); 159 | } 160 | else if(bufsize == 0){ // handle empty file 161 | fclose(fp); 162 | continue; 163 | } 164 | 165 | if(i>0){ 166 | // not the first entry 167 | ft_malloc(sizeof(testcase_t), entry->next); 168 | entry = entry->next; 169 | } 170 | entry->len = bufsize; 171 | 172 | // Go back to the start of the file. 173 | if (fseek(fp, 0L, SEEK_SET) != 0){ 174 | fatal("[!] Error: could not fseek: %s\n", strerror(errno)); 175 | } 176 | 177 | // Read the entire file into memory. 178 | ft_malloc(entry->len, entry->data); 179 | if(fread(entry->data, sizeof(char), entry->len, fp) != entry->len){ 180 | fatal("[!] Error: fread"); 181 | } 182 | 183 | if ( ferror( fp ) != 0 ){ 184 | fatal("[!] Error: fread: %s\n", strerror(errno)); 185 | } 186 | } 187 | fclose(fp); 188 | i++; 189 | } 190 | 191 | entry->next = 0; // end of the list 192 | closedir(dir); 193 | 194 | if(i == 0){ // no cases found 195 | memset(testcase, 0x00, sizeof(testcase_t)); 196 | fatal("no testcases loaded"); 197 | } 198 | 199 | return testcase; // place holder 200 | } 201 | 202 | // save testcase struct to disk. Returns the number of items saved or <0 on error 203 | int save_testcases(testcase_t * cases, char * path){ 204 | testcase_t * entry; 205 | entry = cases; 206 | int i = 1; 207 | char filename[PATH_MAX]; 208 | 209 | // Use the PID as the prefix for generation 210 | char prefix[25]; 211 | sprintf(prefix,"%d",(int)syscall(SYS_gettid)); 212 | 213 | while(entry){ 214 | snprintf(filename, PATH_MAX, "%s-%d", prefix, i); 215 | save_case_p(entry->data, entry->len, filename, path); 216 | entry = entry->next; 217 | i++; 218 | } 219 | 220 | return i; 221 | } 222 | 223 | void save_case(char * data, unsigned long len, uint32_t hash, char * directory){ 224 | int fd; 225 | ssize_t w; 226 | char path[PATH_MAX]; 227 | 228 | snprintf(path, PATH_MAX, "%s/%u", directory, hash); 229 | fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); 230 | if(fd < 0){ 231 | if(errno == EEXIST){ 232 | // the filename is a hash of the execution hash, so if it exists we already have it! 233 | printf("[!] File %s already exists, ignoring\n", path); 234 | close(fd); 235 | return; 236 | } 237 | else{ 238 | fatal("[!] Could not open file %s: %s", path, strerror(errno)); 239 | } 240 | } 241 | 242 | w = write(fd, data, len); 243 | 244 | if(w < 0){ 245 | fatal("[!] write failed: %s", strerror(errno)); 246 | } 247 | close(fd); 248 | } 249 | 250 | int save_case_p(char * data, unsigned long len, char * prefix, char * directory){ 251 | int fd; 252 | ssize_t w; 253 | char path[PATH_MAX]; 254 | 255 | snprintf(path, PATH_MAX, "%s/%s", directory, prefix); 256 | fd = open(path, O_WRONLY | O_CREAT, 0644); 257 | if(fd < 0){ 258 | fatal("[!] Could not open file %s: %s", path, strerror(errno)); 259 | } 260 | 261 | w = write(fd, data, len); 262 | if(w < 0){ 263 | fatal("[!] write failed: %s", strerror(errno)); 264 | } 265 | close(fd); 266 | 267 | return 0; 268 | } 269 | 270 | void free_testcases(testcase_t * cases){ 271 | testcase_t * entry; 272 | entry = cases; 273 | 274 | while(entry){ 275 | cases = entry->next; 276 | free(entry->data); 277 | free(entry); 278 | entry = cases; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /generator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: generator.h 3 | * Author: DoI 4 | */ 5 | 6 | #ifndef GENERATOR_H 7 | #define GENERATOR_H 8 | 9 | #include 10 | 11 | // Flip a bit, re-used from AFL 12 | #define FLIP_BIT(_ar, _b) do { \ 13 | uint8_t* _arf = (uint8_t*)(_ar); \ 14 | uint32_t _bf = (_b); \ 15 | _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \ 16 | } while (0) 17 | 18 | // linked list 19 | typedef struct { 20 | unsigned long len; 21 | char * data; 22 | void * next; // pointer to the next testcase in the list 23 | } testcase_t; 24 | 25 | testcase_t * generator_blab(char * count, char * grammar, char * path, char * prefix); 26 | testcase_t * generator_radamsa(char * count, char * testcase_dir, char * path, char * prefix); 27 | testcase_t * generate_swbitflip(char * input, unsigned long in_len, unsigned long offset, unsigned long count); 28 | testcase_t * load_testcases(char * path, char * prefix); 29 | int save_testcases(testcase_t * cases, char * path); 30 | void save_case(char * data, unsigned long len, uint32_t hash, char * directory); 31 | int save_case_p(char * data, unsigned long len, char * prefix, char * directory); 32 | void free_testcases(testcase_t * cases); 33 | 34 | #endif -------------------------------------------------------------------------------- /hash.h: -------------------------------------------------------------------------------- 1 | // Shamelessly liberated from AFL. Thanks lcamtuf! 2 | 3 | /* 4 | american fuzzy lop - hashing function 5 | ------------------------------------- 6 | 7 | The hash32() function is a variant of MurmurHash3, a good 8 | non-cryptosafe hashing function developed by Austin Appleby. 9 | 10 | For simplicity, this variant does *NOT* accept buffer lengths 11 | that are not divisible by 8 bytes. The 32-bit version is otherwise 12 | similar to the original; the 64-bit one is a custom hack with 13 | mostly-unproven properties. 14 | 15 | Austin's original code is public domain. 16 | 17 | Other code written and maintained by Michal Zalewski 18 | 19 | Copyright 2016 Google Inc. All rights reserved. 20 | 21 | Licensed under the Apache License, Version 2.0 (the "License"); 22 | you may not use this file except in compliance with the License. 23 | You may obtain a copy of the License at: 24 | 25 | http://www.apache.org/licenses/LICENSE-2.0 26 | 27 | */ 28 | 29 | #ifndef _HAVE_HASH_H 30 | #define _HAVE_HASH_H 31 | 32 | #ifdef __x86_64__ 33 | 34 | #define ROL64(_x, _r) ((((uint64_t)(_x)) << (_r)) | (((uint64_t)(_x)) >> (64 - (_r)))) 35 | 36 | static inline uint32_t hash32(const void* key, uint32_t len, uint32_t seed) { 37 | 38 | const uint64_t* data = (uint64_t*)key; 39 | uint64_t h1 = seed ^ len; 40 | 41 | len >>= 3; 42 | 43 | while (len--) { 44 | 45 | uint64_t k1 = *data++; 46 | 47 | k1 *= 0x87c37b91114253d5ULL; 48 | k1 = ROL64(k1, 31); 49 | k1 *= 0x4cf5ad432745937fULL; 50 | 51 | h1 ^= k1; 52 | h1 = ROL64(h1, 27); 53 | h1 = h1 * 5 + 0x52dce729; 54 | 55 | } 56 | 57 | h1 ^= h1 >> 33; 58 | h1 *= 0xff51afd7ed558ccdULL; 59 | h1 ^= h1 >> 33; 60 | h1 *= 0xc4ceb9fe1a85ec53ULL; 61 | h1 ^= h1 >> 33; 62 | 63 | return h1; 64 | 65 | } 66 | 67 | #else 68 | 69 | #define ROL32(_x, _r) ((((uint32_t)(_x)) << (_r)) | (((uint32_t)(_x)) >> (32 - (_r)))) 70 | 71 | static inline uint32_t hash32(const void* key, uint32_t len, uint32_t seed) { 72 | 73 | const uint32_t* data = (uint32_t*)key; 74 | uint32_t h1 = seed ^ len; 75 | 76 | len >>= 2; 77 | 78 | while (len--) { 79 | 80 | uint32_t k1 = *data++; 81 | 82 | k1 *= 0xcc9e2d51; 83 | k1 = ROL32(k1, 15); 84 | k1 *= 0x1b873593; 85 | 86 | h1 ^= k1; 87 | h1 = ROL32(h1, 13); 88 | h1 = h1 * 5 + 0xe6546b64; 89 | 90 | } 91 | 92 | h1 ^= h1 >> 16; 93 | h1 *= 0x85ebca6b; 94 | h1 ^= h1 >> 13; 95 | h1 *= 0xc2b2ae35; 96 | h1 ^= h1 >> 16; 97 | 98 | return h1; 99 | 100 | } 101 | 102 | #endif /* ^__x86_64__ */ 103 | 104 | #endif /* !_HAVE_HASH_H */ 105 | -------------------------------------------------------------------------------- /monitor.c: -------------------------------------------------------------------------------- 1 | /* 2 | * File: monitor.c 3 | * Author: DoI 4 | * 5 | * Monitors a log file and looks for a specific string 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "monitor.h" 19 | #include "util.h" 20 | #include "fuzzotron.h" 21 | 22 | // Currently does not implement file re-open when the file being monitored is overwritten... 23 | 24 | int monitor(char * file, char * regex){ 25 | char * buff = NULL; 26 | pcre *re; 27 | size_t len = 0; 28 | int check; 29 | 30 | re = compile_regex(regex); 31 | 32 | FILE *fh = fopen(file, "r"); 33 | if(fh == NULL){ 34 | printf("[!] Error opening file %s: %s!\n", file, strerror(errno)); 35 | printf("[!] Falling back to crash-detection only\n"); 36 | return -1; 37 | } 38 | fseek(fh, 0, SEEK_END); 39 | 40 | for(;;){ 41 | ssize_t w; 42 | while((w = getline(&buff, &len, fh)) > 0){ 43 | check = parse_line(buff, re); 44 | if(check == 0){ 45 | printf("[!] REGEX matched! Exiting.\n"); 46 | stop = 1; 47 | goto end; 48 | } 49 | } 50 | 51 | // Once EOF is set, getline will constantly return EOF. 52 | // Call clearerr() to remove the feof flag and read more 53 | // data as it's appended. 54 | clearerr(fh); 55 | usleep(10000); 56 | } 57 | 58 | end: 59 | free(buff); 60 | fclose(fh); 61 | return 0; 62 | } 63 | 64 | int parse_line(char* line, pcre *regex){ 65 | int ovector[30]; 66 | int match = pcre_exec( 67 | regex, 68 | NULL, 69 | line, 70 | (int)strlen(line), 71 | 0, 72 | 0, 73 | ovector, 74 | 30 75 | ); 76 | 77 | if(match < 0){ 78 | return -1; 79 | } 80 | 81 | return 0; 82 | } 83 | 84 | pcre * compile_regex(char* regex){ 85 | pcre *re; 86 | int erroroffset; 87 | const char *error; 88 | 89 | re = pcre_compile( 90 | regex, 91 | 0, 92 | &error, 93 | &erroroffset, 94 | NULL 95 | ); 96 | 97 | if(re == NULL){ 98 | // Compilation failed! 99 | fatal("[!] PCRE compile failed at offset %d error: %s\n", erroroffset, error); 100 | } 101 | return re; 102 | } 103 | -------------------------------------------------------------------------------- /monitor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: monitor.h 3 | * Author: DoI 4 | * 5 | */ 6 | 7 | #ifndef MONITOR_H 8 | #define MONITOR_H 9 | 10 | #include 11 | 12 | int monitor(char * file, char * regex); 13 | int parse_line(char* line, pcre *regex); 14 | pcre * compile_regex(char* regex); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /replay.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | #include "sender.h" 9 | #include "fuzzotron.h" 10 | 11 | struct fuzzer_args fuzz; 12 | 13 | void help(){ 14 | // Print the help and exit 15 | printf("Replay - Send a testcase, the same way Fuzzotron does\n\n"); 16 | printf("Usage: ./replay -h 127.0.0.1 -p 80 -P tcp some_file\n\n"); 17 | printf("\t-h\t\tIP of host to connect to\n"); 18 | printf("\t-p\t\tPort to connect to\n"); 19 | printf("\t-P\t\tProtocol to use (tcp,udp)\n"); 20 | printf("\t--ssl\t\tUse SSL for the connection\n"); 21 | printf("\t--destroy\tUse TCP_REPAIR mode to immediately destroy the connection, do not send FIN/RST.\n"); 22 | exit(0); 23 | } 24 | 25 | int main(int argc, char ** argv){ 26 | int c; 27 | FILE * fp; 28 | char * data; 29 | unsigned long data_len = 0; 30 | 31 | memset(&fuzz, 0x00, sizeof(fuzz)); 32 | 33 | static struct option arg_options[] = { 34 | {"alpn", required_argument, 0, 'l'}, 35 | {"ssl", no_argument, &fuzz.is_tls, 1}, 36 | {"protocol", required_argument, 0, 'p'}, 37 | {"destroy", no_argument, &fuzz.destroy, 1}, 38 | {0, 0, 0, 0} 39 | }; 40 | 41 | int arg_index; 42 | while((c = getopt_long(argc, argv, "h:l:p:P:", arg_options, &arg_index)) != -1){ 43 | switch(c){ 44 | case 'h': 45 | // define host 46 | fuzz.host = optarg; 47 | break; 48 | 49 | case 'l': 50 | // set ALPN string 51 | fuzz.alpn = optarg; 52 | break; 53 | 54 | case 'p': 55 | // define port 56 | fuzz.port = atoi(optarg); 57 | break; 58 | 59 | case 'P': 60 | // define protocol 61 | if((strcmp(optarg,"udp") != 0) && (strcmp(optarg,"tcp") != 0) && (strcmp(optarg,"unix") != 0)){ 62 | fatal("Please specify either 'tcp', 'udp' or 'unix' for -P\n"); 63 | } 64 | if(strcmp(optarg,"tcp") == 0){ 65 | fuzz.protocol = 1; 66 | fuzz.send = send_tcp; 67 | } 68 | else if(strcmp(optarg,"udp") == 0){ 69 | fuzz.protocol = 2; 70 | fuzz.send = send_udp; 71 | } 72 | else if(strcmp(optarg,"unix") == 0){ 73 | fuzz.protocol = 3; 74 | fuzz.send = send_unix; 75 | } 76 | 77 | break; 78 | } 79 | } 80 | 81 | if((fuzz.host == NULL) || (fuzz.port == 0 && fuzz.protocol != 3) || 82 | (fuzz.protocol == 0)){ 83 | help(); 84 | return 0; 85 | } 86 | 87 | char * file = argv[optind]; 88 | if((fp = fopen(file, "r"))== NULL){ 89 | fatal("[!] Error: Could not open file %s\n", strerror(errno)); 90 | } 91 | 92 | if (fseek(fp, 0L, SEEK_END) == 0) { 93 | long bufsize = ftell(fp); 94 | if (bufsize == -1){ 95 | fatal("[!] Error with ftell: %s", strerror(errno)); 96 | } 97 | else if(bufsize == 0){ // handle empty file 98 | fatal("Zero length file"); 99 | } 100 | 101 | // Go back to the start of the file. 102 | if (fseek(fp, 0L, SEEK_SET) != 0){ 103 | fatal("[!] Error: could not fseek: %s\n", strerror(errno)); 104 | } 105 | 106 | // Read the entire file into memory. 107 | data_len = bufsize; 108 | ft_malloc(data_len, data); 109 | if(fread(data, sizeof(char), data_len, fp) != data_len){ 110 | fatal("[!] Error: fread"); 111 | } 112 | 113 | if (ferror( fp ) != 0){ 114 | fatal("[!] Error: fread: %s\n", strerror(errno)); 115 | } 116 | } 117 | fclose(fp); 118 | 119 | if(data_len > 0){ 120 | testcase_t testcase = {data_len, data, 0x00}; 121 | printf("Sending: %s bytes: %lu\n", file, testcase.len); 122 | fuzz.send(fuzz.host, fuzz.port, &testcase); 123 | free(data); 124 | } 125 | 126 | return 1; 127 | } 128 | -------------------------------------------------------------------------------- /sender.c: -------------------------------------------------------------------------------- 1 | /* 2 | * File: sender.c 3 | * Author: DoI 4 | * 5 | * Methods to send buffers down sockets 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include "callback.h" 30 | #include "fuzzotron.h" 31 | #include "generator.h" 32 | #include "sender.h" 33 | #include "util.h" 34 | 35 | extern int errno; 36 | 37 | #define RECV_TIMEOUT 1 // Timeout for SSL connections - default 1 second 38 | 39 | /* 40 | * send a testcase down a 41 | * udp socket 42 | */ 43 | int send_udp(char * host, int port, testcase_t * testcase){ 44 | int sock = 0; 45 | ssize_t r; 46 | struct sockaddr_in serv_addr; 47 | 48 | if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ 49 | fatal("[!] Error: Could not create socket: %s\n", strerror(errno)); 50 | } 51 | 52 | serv_addr.sin_family = AF_INET; 53 | serv_addr.sin_port = htons(port); 54 | inet_pton(AF_INET, host, &serv_addr.sin_addr); 55 | 56 | if(fuzz.is_tls){ // DTLS 57 | SSL_CTX * ctx; 58 | SSL * ssl; 59 | BIO * bio; 60 | int ret; 61 | struct timeval timeout; 62 | 63 | ctx = SSL_CTX_new(DTLS_client_method()); 64 | if(ctx == NULL){ 65 | printf("[!] Error spawning DTLS context\n"); 66 | ERR_print_errors_fp(stdout); 67 | return -1; 68 | } 69 | 70 | ssl = SSL_new(ctx); 71 | bio = BIO_new_dgram(sock, BIO_CLOSE); 72 | timeout.tv_sec = RECV_TIMEOUT; 73 | timeout.tv_usec = 0; 74 | BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_RECV_TIMEOUT, 0, &timeout); 75 | 76 | if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(struct sockaddr_in))) { 77 | fatal("connect"); 78 | } 79 | BIO_ctrl(bio, BIO_CTRL_DGRAM_SET_CONNECTED, 0, &serv_addr); 80 | SSL_set_bio(ssl, bio, bio); 81 | 82 | ret = SSL_connect(ssl); 83 | if (ret < 1){ 84 | printf("[!] Error initiating DTLS session. Error no: %d\n", SSL_get_error(ssl, ret)); 85 | SSL_free(ssl); 86 | close(sock); 87 | SSL_CTX_free(ctx); 88 | return -1; 89 | } 90 | 91 | callback_ssl_pre_send(ssl, testcase); 92 | 93 | ret = SSL_write(ssl, testcase->data, testcase->len); 94 | if (ret < 0){ 95 | printf("[!] Error: SSL_write() error code no: %d\n", SSL_get_error(ssl, ret)); 96 | } 97 | 98 | callback_ssl_post_send(ssl); 99 | 100 | SSL_free(ssl); 101 | close(sock); 102 | SSL_CTX_free(ctx); 103 | 104 | return 0; 105 | } 106 | else { 107 | callback_pre_send(sock, testcase); // user defined callback 108 | 109 | // payload is larger than maximum datagram, send as multiple datagrams 110 | if(testcase->len > 65507){ 111 | const void * position = testcase->data; 112 | unsigned long rem = testcase->len; 113 | 114 | while(rem > 0){ 115 | if(rem > 65507){ 116 | r = sendto(sock,position,65507,0,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); 117 | } 118 | else{ 119 | r = sendto(sock,position,rem,0,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); 120 | } 121 | 122 | if(r < 0){ 123 | printf("[!] Error: in chunked sendto(): %s\n", strerror(errno)); 124 | close(sock); 125 | return -1; 126 | } 127 | 128 | rem -= r; 129 | position += r; 130 | } 131 | } 132 | else{ 133 | r = sendto(sock,testcase->data,testcase->len,0,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); 134 | 135 | if(r < 0){ 136 | printf("[!] Error: in sendto(): %s\n", strerror(errno)); 137 | close(sock); 138 | return -1; 139 | } 140 | } 141 | 142 | callback_post_send(sock); // user defined callback 143 | close(sock); 144 | } 145 | return 0; 146 | } 147 | 148 | /* 149 | * send a testcase down a 150 | * tcp socket. 151 | */ 152 | int send_tcp(char * host, int port, testcase_t * testcase){ 153 | int sock = 0; 154 | struct sockaddr_in serv_addr; 155 | 156 | if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){ 157 | fatal("[!] Error: Could not create socket: %s\n", strerror(errno)); 158 | } 159 | 160 | serv_addr.sin_family = AF_INET; 161 | serv_addr.sin_port = htons(port); 162 | inet_pton(AF_INET, host, &serv_addr.sin_addr); 163 | 164 | int c = connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); 165 | if(c < 0){ 166 | printf("[!] Error: Could not connect: %s errno: %d\n", strerror(errno), errno); 167 | if(errno == ECONNRESET){ 168 | close(sock); 169 | return 0; // just skip this testcase 170 | } 171 | else{ 172 | close(sock); 173 | return -1; 174 | } 175 | } 176 | 177 | if(fuzz.is_tls){ 178 | // Set up the things for TLS 179 | int ret; 180 | SSL *ssl; 181 | SSL_CTX * ctx; 182 | size_t alpn_len; 183 | unsigned char * alpn; 184 | 185 | ctx = SSL_CTX_new(SSLv23_client_method()); 186 | if(ctx == NULL){ 187 | printf("[!] Error spawning TLS context\n"); 188 | ERR_print_errors_fp(stdout); 189 | return -1; 190 | } 191 | 192 | if(fuzz.alpn){ 193 | alpn = next_protos_parse(&alpn_len, fuzz.alpn); 194 | if(!alpn){ 195 | fatal("[!] Error in alpn next_protos_parse"); 196 | } 197 | 198 | if(SSL_CTX_set_alpn_protos(ctx, alpn, alpn_len) != 0){ 199 | free(alpn); 200 | fatal("[!] Error setting ALPN protos: %s\n", ERR_error_string(ERR_get_error(), NULL)); 201 | } 202 | 203 | free(alpn); 204 | } 205 | 206 | ssl = SSL_new(ctx); 207 | SSL_set_fd(ssl,sock); 208 | ret = SSL_connect(ssl); 209 | if (ret < 1){ 210 | printf("[!] Error initiating TLS session. Error no: %d\n", SSL_get_error(ssl, ret)); 211 | SSL_free(ssl); 212 | close(sock); 213 | SSL_CTX_free(ctx); 214 | return -1; 215 | } 216 | 217 | callback_ssl_pre_send(ssl, testcase); // user defined callback 218 | ret = SSL_write(ssl, testcase->data, testcase->len); 219 | if (ret < 0){ 220 | printf("[!] Error: SSL_write() error no: %d\n", SSL_get_error(ssl, ret)); 221 | } 222 | callback_ssl_post_send(ssl); // user defined callback 223 | 224 | SSL_free(ssl); 225 | close(sock); 226 | SSL_CTX_free(ctx); 227 | return 0; 228 | } 229 | else{ 230 | callback_pre_send(sock, testcase); // user defined callback 231 | fcntl(sock, F_SETFL, O_RDONLY|O_NONBLOCK); 232 | if(write(sock, testcase->data, testcase->len) < 0){ 233 | printf("[!] Error: write() error: %s errno: %d\n", strerror(errno), errno); 234 | } 235 | callback_post_send(sock); // user defined callback 236 | } 237 | 238 | 239 | if(fuzz.destroy){ 240 | destroy_socket(sock); 241 | } 242 | else{ 243 | close(sock); 244 | } 245 | 246 | return 0; 247 | } 248 | 249 | // place the connection in TCP_REPAIR mode and call close(). This will 250 | // immediately destroy the socket. 251 | void destroy_socket(int sock){ 252 | int a = 1; 253 | if(setsockopt(sock, SOL_TCP, TCP_REPAIR, &a, sizeof(a)) < 0 ){ 254 | // if EPERM then other side likely closed, if BADF then we already closed it 255 | if(errno == EBADF || errno == EPERM){ 256 | close(sock); 257 | return; 258 | } 259 | printf("[!] destroy_socket: TCP_REPAIR enable failed: %s\n", strerror(errno)); 260 | } 261 | 262 | usleep(100); // there is some weirdness with TCP_REPAIR, need to wait before closing. 263 | close(sock); 264 | } 265 | 266 | /*- 267 | * next_protos_parse parses a comma separated list of strings into a string 268 | * in a format suitable for passing to SSL_CTX_set_next_protos_advertised. 269 | * outlen: (output) set to the length of the resulting buffer on success. 270 | * err: NULL on failure 271 | * in: a NULL terminated string like "abc,def,ghi" 272 | * 273 | * returns: a malloc'd buffer or NULL on failure. 274 | */ 275 | unsigned char * next_protos_parse(size_t * outlen, const char * in){ 276 | size_t len; 277 | unsigned char * out; 278 | size_t i, start = 0; 279 | 280 | len = strlen(in); 281 | if (len >= 65535) 282 | return NULL; 283 | 284 | ft_malloc(strlen(in) + 1, out); 285 | for (i = 0; i <= len; ++i) { 286 | if (i == len || in[i] == ',') { 287 | if (i - start > 255) { 288 | free(out); 289 | return NULL; 290 | } 291 | out[start] = i - start; 292 | start = i + 1; 293 | } else 294 | out[i + 1] = in[i]; 295 | } 296 | 297 | *outlen = len + 1; 298 | return out; 299 | } 300 | 301 | int send_unix(char * path, int port __attribute__((unused)), testcase_t * testcase){ 302 | int sock = 0; 303 | struct sockaddr_un serv_addr; 304 | memset(&serv_addr, 0x00, sizeof(serv_addr)); 305 | 306 | serv_addr.sun_family = AF_LOCAL; 307 | strncpy(serv_addr.sun_path, path, 107); 308 | 309 | if((sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0){ 310 | fatal("[!] Error: Could not create socket: %s\n", strerror(errno)); 311 | } 312 | 313 | if(connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0){ 314 | printf("[!] Error: Could not connect to socket: %s\n", strerror(errno)); 315 | return -1; 316 | } 317 | 318 | callback_pre_send(sock, testcase); // user defined callback 319 | if(write(sock, testcase->data, testcase->len)<0){ 320 | printf("[!] Error: write() error: %s errno: %d\n", strerror(errno), errno); 321 | } 322 | callback_post_send(sock); // user defined callback 323 | 324 | close(sock); 325 | 326 | return 0; 327 | } 328 | -------------------------------------------------------------------------------- /sender.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: sender.h 3 | * Author: DoI 4 | * 5 | */ 6 | 7 | #ifndef SENDER_H 8 | #define SENDER_H 9 | 10 | #include "generator.h" 11 | 12 | void setup_tcp(int sock); 13 | int send_udp(char * host, int port, testcase_t * testcase); 14 | int send_tcp(char * host, int port, testcase_t * testcase); 15 | void destroy_socket(int sock); 16 | unsigned char * next_protos_parse(size_t * outlen, const char * in); 17 | int send_unix(char * path, int port /* not used for UNIX sockets */, testcase_t * testcase); 18 | 19 | #endif -------------------------------------------------------------------------------- /trace.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Various bitmap tracing tools. Mostly liberated from afl with minor tweaking. 3 | * Thanks lcamtuf! http://lcamtuf.coredump.cx/afl/ 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "hash.h" 14 | #include "trace.h" 15 | #include "util.h" 16 | 17 | 18 | uint8_t * setup_shm(int shm_id){ 19 | uint8_t * trace_bits; 20 | trace_bits = shmat(shm_id, NULL, 0); 21 | if (trace_bits == (uint8_t *)-1){ 22 | fatal("[!] shmat() failed: %s\n", strerror(errno)); 23 | } 24 | 25 | return trace_bits; 26 | } 27 | 28 | // will watch the bitmap and loop untill the bitmap stops changing 29 | uint32_t wait_for_bitmap(const void* trace_bits){ 30 | uint32_t checksum; 31 | uint32_t previous_checksum = 0; 32 | long null_count = 0, hash_count = 0; 33 | 34 | while(1){ 35 | checksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); 36 | 37 | if (checksum == NULL_HASH){ 38 | // null ? 39 | null_count++; 40 | if(null_count > 200) // nada after 2 seconds 41 | return NULL_HASH; 42 | else{ 43 | usleep(10000); 44 | continue; 45 | } 46 | } 47 | 48 | if(previous_checksum == checksum) 49 | break; 50 | 51 | previous_checksum = checksum; 52 | hash_count++; 53 | if(hash_count > 200) // still changing after 2 seconds 54 | return 0; 55 | 56 | usleep(50000); 57 | } 58 | 59 | return checksum; 60 | } 61 | 62 | /* Shamelessly liberated from AFL (http://lcamtuf.coredump.cx/afl/) 63 | 64 | Check if the current execution path brings anything new to the table. 65 | Update virgin bits to reflect the finds. Returns 1 if the only change is 66 | the hit-count for a particular tuple; 2 if there are new tuples seen. 67 | Updates the map, so subsequent calls will always return 0. 68 | 69 | This function is called after every exec() on a fairly large buffer, so 70 | it needs to be fast. We do this in 32-bit and 64-bit flavors. */ 71 | 72 | uint8_t has_new_bits(uint8_t * virgin_map, uint8_t * trace_bits) { 73 | 74 | #ifdef __x86_64__ 75 | 76 | uint64_t * current = (uint64_t *)trace_bits; 77 | uint64_t * virgin = (uint64_t *)virgin_map; 78 | 79 | uint32_t i = (MAP_SIZE >> 3); 80 | 81 | #else 82 | 83 | uint32_t * current = (uint32_t *)trace_bits; 84 | uint32_t * virgin = (uint32_t *)virgin_map; 85 | 86 | uint32_t i = (MAP_SIZE >> 2); 87 | 88 | #endif /* ^__x86_64__ */ 89 | 90 | uint8_t ret = 0; 91 | 92 | while (i--) { 93 | 94 | /* Optimize for (*current & *virgin) == 0 - i.e., no bits in current bitmap 95 | that have not been already cleared from the virgin map - since this will 96 | almost always be the case. */ 97 | 98 | if (unlikely(*current) && unlikely(*current & *virgin)) { 99 | 100 | if (likely(ret < 2)) { 101 | 102 | uint8_t * cur = (uint8_t *)current; 103 | uint8_t * vir = (uint8_t *)virgin; 104 | 105 | /* Looks like we have not found any new bytes yet; see if any non-zero 106 | bytes in current[] are pristine in virgin[]. */ 107 | 108 | #ifdef __x86_64__ 109 | 110 | if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) || 111 | (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff) || 112 | (cur[4] && vir[4] == 0xff) || (cur[5] && vir[5] == 0xff) || 113 | (cur[6] && vir[6] == 0xff) || (cur[7] && vir[7] == 0xff)) ret = 2; 114 | else ret = 1; 115 | 116 | #else 117 | 118 | if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) || 119 | (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff)) ret = 2; 120 | else ret = 1; 121 | 122 | #endif /* ^__x86_64__ */ 123 | 124 | } 125 | 126 | *virgin &= ~*current; 127 | 128 | } 129 | 130 | current++; 131 | virgin++; 132 | 133 | } 134 | 135 | return ret; 136 | } 137 | -------------------------------------------------------------------------------- /trace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Various bitmap tracing tools. Mostly liberated from afl-fuzz http://lcamtuf.coredump.cx/afl/ 3 | */ 4 | 5 | #ifndef TRACE_H 6 | #define TRACE_H 7 | 8 | #define MAP_SIZE_POW2 16 9 | #define MAP_SIZE (1 << MAP_SIZE_POW2) 10 | #define HASH_CONST 0xa5b35705 11 | #define NULL_HASH 2982225436 // when an empty bitmap is hashed 12 | 13 | #define likely(_x) __builtin_expect(!!(_x), 1) 14 | #define unlikely(_x) __builtin_expect(!!(_x), 0) 15 | 16 | uint32_t wait_for_bitmap(const void * trace_bits); 17 | uint8_t * setup_shm(int shm_id); 18 | uint8_t has_new_bits(uint8_t * virgin_map, uint8_t * trace_bits); 19 | 20 | #endif -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: util.h 3 | * Author: DoI 4 | */ 5 | 6 | #ifndef UTIL_H 7 | #define UTIL_H 8 | 9 | #include 10 | #include 11 | 12 | #define RED "\x1B[31m" 13 | #define GRN "\x1B[32m" 14 | #define YEL "\x1B[33m" 15 | #define BLU "\x1B[34m" 16 | #define MAG "\x1B[35m" 17 | #define CYN "\x1B[36m" 18 | #define WHT "\x1B[37m" 19 | #define RESET "\x1B[0m" 20 | 21 | #define fatal(x...) \ 22 | do { \ 23 | fprintf(stderr, RED "[!] ERROR: " RESET); \ 24 | fprintf(stderr, x); \ 25 | fprintf(stderr, "\n Location : %s(), %s:%d\n\n", \ 26 | __FUNCTION__, __FILE__, __LINE__); \ 27 | exit(0);\ 28 | } while(0) 29 | 30 | #define ft_malloc(len,ptr) \ 31 | do { \ 32 | if(NULL == (ptr = malloc(len))){\ 33 | fatal("[!] Malloc failed\n"); \ 34 | }\ 35 | } while(0) 36 | 37 | #endif --------------------------------------------------------------------------------