2 * fuzzing proxy - network-level fuzzing injection proxy
4 * Copyright (C) 2016 Andy Green <andy@warmcat.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation:
9 * version 2.1 of the License.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 * fuzxy is designed to go on the client path
24 * [ client <-> fuzxy ] <-> server
26 * you can arrange that with, eg,
28 * http_proxy=localhost:8880
30 * env var before starting the client.
32 * Even though he is on the client side, he is able to see and change traffic
33 * in both directions, and so fuzz both the client and the server.
36 #if defined(_WIN32) && defined(EXTERNAL_POLL)
38 #define _WIN32_WINNT 0x0600
39 #define poll(fdArray, fds, timeout) WSAPoll((LPWSAPOLLFD)(fdArray), (ULONG)(fds), (INT)(timeout))
42 #include "lws_config.h"
53 #include "../lib/libwebsockets.h"
57 #include "gettimeofday.h"
73 enum proxy_parser_states {
80 enum fuzzer_parser_states {
95 enum proxy_parser_states pp;
103 enum fuzzer_parser_states fp;
107 int twin; /* must be fixed up when arrays lose guys */
108 unsigned int outbound:1; /* from local -> remote */
114 static const int ring_size(struct ring *r)
116 return sizeof(r->buf);
118 static const int ring_used(struct ring *r)
120 return (r->head - r->tail) & (ring_size(r) - 1);
122 static const int ring_free(struct ring *r)
124 return (ring_size(r) - 1) - ring_used(r);
126 static const int ring_get_one(struct ring *r)
128 int n = r->buf[r->tail] & 255;
130 if (r->tail == r->head)
134 if (r->tail == ring_size(r))
140 void sighandler(int sig)
145 static struct option options[] = {
146 { "help", no_argument, NULL, 'h' },
147 { "debug", required_argument, NULL, 'd' },
148 { "port", required_argument, NULL, 'p' },
149 { "ssl", no_argument, NULL, 's' },
150 { "allow-non-ssl", no_argument, NULL, 'a' },
151 { "interface", required_argument, NULL, 'i' },
152 { "closetest", no_argument, NULL, 'c' },
153 { "libev", no_argument, NULL, 'e' },
154 #ifndef LWS_NO_DAEMONIZE
155 { "daemonize", no_argument, NULL, 'D' },
157 { "resource_path", required_argument, NULL, 'r' },
161 static struct pollfd pfd[128];
162 static struct state state[128];
165 static void close_and_remove_fd(int index)
169 lwsl_notice("%s: closing index %d\n", __func__, index);
170 close(pfd[index].fd);
173 n = state[index].twin;
175 assert(state[n].twin == index);
177 state[index].type = FZY_S_DEAD;
179 if (index == pfds - 1) {
180 if (state[index].twin)
181 state[state[index].twin].twin = 0;
182 state[index].twin = 0;
186 /* swap the end guy into the deleted guy and trim back one */
188 if (state[pfds - 1].twin) {
189 state[state[pfds - 1].twin].twin = index;
190 if (n && n == pfds - 1)
194 /* swap the last guy into dead guy's place and trim by one */
195 pfd[index] = pfd[pfds - 1];
196 state[index] = state[pfds - 1];
201 close_and_remove_fd(n);
209 static void construct_state(int n, enum types s, int flags)
211 memset(&state[n], 0, sizeof state[n]);
213 pfd[n].events = flags | POLLHUP;
217 fuzxy_listen(const char *interface_name, int port, int *sockfd)
219 struct sockaddr_in serv_addr4;
220 socklen_t len = sizeof(struct sockaddr);
221 struct sockaddr_in sin;
224 *sockfd = socket(AF_INET, SOCK_STREAM, 0);
227 lwsl_err("ERROR opening socket\n");
231 if (setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR,
232 (const void *)&opt, sizeof(opt)) < 0) {
233 lwsl_err("unable to set listen socket options\n");
237 bzero((char *) &serv_addr4, sizeof(serv_addr4));
238 serv_addr4.sin_addr.s_addr = INADDR_ANY;
239 serv_addr4.sin_family = AF_INET;
241 if (interface_name[0] &&
242 lws_interface_to_sa(0, interface_name, (struct sockaddr_in *)
243 (struct sockaddr *)&serv_addr4,
244 sizeof(serv_addr4)) < 0) {
245 lwsl_err("Unable to find interface %s\n", interface_name);
249 serv_addr4.sin_port = htons(port);
251 n = bind(*sockfd, (struct sockaddr *)&serv_addr4,
254 lwsl_err("ERROR on binding to port %d (%d %d)\n",
259 if (getsockname(*sockfd, (struct sockaddr *)&sin, &len) == -1)
260 lwsl_warn("getsockname: %s\n", strerror(errno));
262 port = ntohs(sin.sin_port);
264 listen(*sockfd, SOMAXCONN);
279 struct fuzxy_rule r = {
280 { "Host:", "\x0d", " \x0d" },
285 static int fuzz(int n, char *out, int len)
287 struct state *s = &state[n];
294 c = ring_get_one(&state[s->twin].in);
297 if (c == r.s[0][s->fuzc++]) {
298 if (s->fuzc == r.len[0]) {
300 s->fp = FZY_FP_SEARCH2;
308 c = ring_get_one(&state[s->twin].in);
311 if (c == r.s[1][s->fuzc++]) {
312 if (s->fuzc == r.len[1]) {
313 lwsl_notice("+++++++fuzzer hit...\n");
315 s->fp = FZY_FP_INJECT;
325 out[m++] = r.s[2][s->fuzc++ % r.len[2]];
326 if (s->fuzc == r.inject_len)
327 s->fp = FZY_FP_PENDING;
331 out[m++] = s->pending;
332 s->fp = FZY_FP_SEARCH;
344 struct addrinfo ai, *res, *result;
345 struct sockaddr_in serv_addr4;
346 struct state *s = &state[n];
351 m = ring_get_one(&s->in);
357 if (m != "CONNECT "[s->ppc++]) {
358 lwsl_notice("failed CONNECT match\n");
362 s->pp = FZY_PP_ADDRESS;
368 s->address[s->ppc++] = '\0';
374 s->address[s->ppc++] = '\0';
375 s->pp = FZY_PP_CRLFS;
381 s->address[s->ppc++] = m;
382 if (s->ppc == sizeof(s->address)) {
383 lwsl_notice("Failed on address length\n");
389 s->pp = FZY_PP_CRLFS;
393 if (m >= '0' && m <= '9') {
401 if (m != "\x0d\x0a\x0d\x0a"[s->ppc++])
405 s->type = FZY_S_PROXIED;
407 memset (&ai, 0, sizeof ai);
408 ai.ai_family = PF_UNSPEC;
409 ai.ai_socktype = SOCK_STREAM;
410 ai.ai_flags = AI_CANONNAME;
412 if (getaddrinfo(s->address, NULL, &ai, &result)) {
413 lwsl_notice("failed to lookup %s\n",
420 switch (res->ai_family) {
422 p = &((struct sockaddr_in *)res->
431 lwsl_notice("Failed to get address result %s\n",
433 freeaddrinfo(result);
437 serv_addr4.sin_family = AF_INET;
438 serv_addr4.sin_addr = *((struct in_addr *)p);
439 serv_addr4.sin_port = htons(s->port);
440 bzero(&serv_addr4.sin_zero, 8);
441 freeaddrinfo(result);
443 lwsl_err("Conn %d req '%s' port %d\n", n,
444 s->address, s->port);
445 /* we need to open the associated onward connection */
446 sockfd = socket(AF_INET, SOCK_STREAM, 0);
448 lwsl_err("Could not get socket\n");
452 if (connect(sockfd, (struct sockaddr *)&serv_addr4,
453 sizeof(struct sockaddr)) == -1 ||
456 lwsl_err("proxied onward connection failed\n");
460 construct_state(pfds, FZY_S_ONWARD,
461 POLLOUT | POLLIN | POLLERR);
462 state[pfds].twin = n;
463 lwsl_notice("binding conns %d and %d\n", n, pfds);
464 state[pfds].outbound = s->outbound;
466 pfd[pfds++].fd = sockfd;
468 lwsl_notice("onward connection in progress\n");
469 if (ring_used(&s->in))
470 pfd[s->twin].events |= POLLOUT;
472 "HTTP/1.0 200 \x0d\x0a\x0d\x0a", 17) < 17)
480 static void sigpipe_handler(int x)
485 main(int argc, char **argv)
487 char interface_name[128] = "", interface_name_local[128] = "lo";
488 int port_local = 8880, accept_fd;
489 struct sockaddr_in cli_addr;
498 int syslog_options = LOG_PID | LOG_PERROR;
500 #ifndef LWS_NO_DAEMONIZE
503 signal(SIGPIPE, sigpipe_handler);
506 n = getopt_long(argc, argv, "eci:hsap:d:Dr:", options, NULL);
511 opts |= LWS_SERVER_OPTION_LIBEV;
513 #ifndef LWS_NO_DAEMONIZE
517 syslog_options &= ~LOG_PERROR;
522 debug_level = atoi(optarg);
525 port_local = atoi(optarg);
528 strncpy(interface_name, optarg, sizeof interface_name);
529 interface_name[(sizeof interface_name) - 1] = '\0';
532 fprintf(stderr, "Usage: libwebsockets-test-fuzxy "
533 "[--port=<p>] [--ssl] "
534 "[-d <log bitfield>] "
535 "[--resource_path <path>]\n");
540 #if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
542 * normally lock path would be /var/lock/lwsts or similar, to
543 * simplify getting started without having to take care about
544 * permissions or running as root, set to /tmp/.lwsts-lock
546 if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
547 fprintf(stderr, "Failed to daemonize\n");
552 signal(SIGINT, sighandler);
555 /* we will only try to log things according to our debug_level */
556 setlogmask(LOG_UPTO (LOG_DEBUG));
557 openlog("fuzxy", syslog_options, LOG_DAEMON);
560 /* tell the library what debug level to emit and to send it to syslog */
561 lws_set_log_level(debug_level, lwsl_emit_syslog);
563 lwsl_notice("%s\n(C) Copyright 2016 Andy Green <andy@warmcat.com> - "
564 "licensed under LGPL2.1\n", argv[0]);
566 /* listen on local side */
568 if (fuzxy_listen(interface_name, port_local, &pfd[pfds].fd)) {
569 lwsl_err("Failed to listen on local side\n");
572 construct_state(pfds, FZY_S_LISTENING, POLLIN | POLLERR);
575 lwsl_notice("Local side listening on %s:%u\n",
576 interface_name_local, port_local);
578 while (!force_exit) {
580 m = poll(pfd, pfds, 50);
583 for (n = 0; n < pfds; n++) {
585 if (s->type == FZY_S_LISTENING &&
586 (pfd[n].revents & POLLIN)) {
587 /* first do the accept entry */
589 clilen = sizeof(cli_addr);
590 accept_fd = accept(pfd[0].fd,
591 (struct sockaddr *)&cli_addr, &clilen);
593 if (errno == EAGAIN ||
594 errno == EWOULDBLOCK)
597 lwsl_warn("ERROR on accept: %s\n",
601 construct_state(pfds, FZY_S_ACCEPTED,
603 state[pfds].outbound = n == 0;
604 state[pfds].pp = FZY_PP_CONNECT;
606 pfd[pfds++].fd = accept_fd;
607 lwsl_notice("new connect accepted\n");
610 if (pfd[n].revents & POLLIN) {
611 assert(ring_free(&s->in));
612 m = (ring_size(&s->in) - 1) -
614 if (s->in.head == ring_size(&s->in) - 1 &&
617 m = read(pfd[n].fd, s->in.buf + s->in.head, m);
618 // lwsl_notice("read %d\n", m);
622 if (s->in.head == ring_size(&s->in))
626 case FZY_S_ACCEPTED: /* parse proxy CONNECT */
627 if (handle_accept(n))
632 if (ring_used(&s->in))
633 pfd[s->twin].events |= POLLOUT;
639 if (s->in.head == s->in.tail) {
640 s->in.head = s->in.tail = 0;
641 pfd[n].events |= POLLIN;
643 if (!ring_free(&s->in))
644 pfd[n].events &= ~POLLIN;
646 if (pfd[n].revents & POLLOUT) {
651 * draw down enough of the partner's
652 * in ring to either exhaust it
653 * or fill an output buffer
655 m = fuzz(n, out, sizeof(out));
657 m = write(pfd[n].fd, out, m);
661 pfd[n].events &= ~POLLOUT;
663 if (ring_free(&state[s->twin].in))
664 pfd[s->twin].events |= POLLIN;
674 close_and_remove_fd(n);
675 n--; /* redo this slot */
680 lwsl_notice("%s exited cleanly\n", argv[0]);