etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

http_mutator.c (10361B)

      1 /*
      2  * AFL++ Custom Mutator for TTYD HTTP fuzzing
      3  *
      4  * Understands the structured input format used by fuzz_http_parsing:
      5  *   [auth_mode:1][cred_len:2 BE][credential][path_len:2 BE][path][auth_header_value]
      6  *
      7  * Instead of random byte flips (which mostly break the format envelope),
      8  * this mutator makes semantically meaningful changes:
      9  *   - Swaps URL paths to known ttyd endpoints or traversal payloads
     10  *   - Mutates auth header values (scheme, base64, truncation)
     11  *   - Toggles auth modes
     12  *   - Varies credential content and length
     13  *
     14  * Build:
     15  *   afl-clang-fast -shared -fPIC -O2 -o http_mutator.so http_mutator.c
     16  *
     17  * Use:
     18  *   AFL_CUSTOM_MUTATOR_LIBRARY=./http_mutator.so afl-fuzz ...
     19  */
     20 
     21 #include <stdint.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 #include <stdio.h>
     25 
     26 /* AFL++ types */
     27 typedef unsigned char u8;
     28 typedef uint32_t u32;
     29 typedef struct afl_state afl_state_t;
     30 
     31 /* ── mutator state ──────────────────────────────────────────────────────── */
     32 
     33 typedef struct {
     34     u8     *out_buf;
     35     size_t  out_buf_size;
     36     u32     seed;
     37 } http_mutator_t;
     38 
     39 /* ── interesting values ─────────────────────────────────────────────────── */
     40 
     41 static const char *interesting_paths[] = {
     42     "/",
     43     "/ws",
     44     "/token",
     45     "/base-path",
     46     "/base-path/",
     47     "/../../../etc/passwd",
     48     "//etc/passwd",
     49     "/./../.././etc/shadow",
     50     "/%00/token",
     51     "/token%00.html",
     52     "/ws/../token",
     53     "/%2e%2e/%2e%2e/etc/passwd",
     54     "/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     55         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     56         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
     57     "",
     58     "/\r\nInjected: header",
     59     "/\x00hidden",
     60     "/%s%s%s%s%n",
     61     "/token?arg=;id",
     62     "/ws?arg=test&arg=another",
     63     "///",
     64 };
     65 #define N_PATHS (sizeof(interesting_paths) / sizeof(interesting_paths[0]))
     66 
     67 static const char *interesting_auth[] = {
     68     "Basic YWRtaW46cGFzcw==",
     69     "Basic ",
     70     "Basic AAAA",
     71     "Basic ====",
     72     "Basic YWRtaW46",
     73     "Bearer token123",
     74     "Digest username=\"admin\"",
     75     "",
     76     "Basic \x00hidden",
     77     "BASIC YWRtaW46cGFzcw==",
     78     "basic YWRtaW46cGFzcw==",
     79     "Basic  YWRtaW46cGFzcw==",
     80     " Basic YWRtaW46cGFzcw==",
     81     "Basic\tYWRtaW46cGFzcw==",
     82     "Basic AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     83         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     84         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     85         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
     86         "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
     87     "Basic %s%s%s%n%n",
     88     "X-Auth-User: admin",
     89 };
     90 #define N_AUTH (sizeof(interesting_auth) / sizeof(interesting_auth[0]))
     91 
     92 static const char *interesting_creds[] = {
     93     "admin:pass",
     94     "YWRtaW46cGFzcw==",
     95     "",
     96     "AAAA",
     97     "a]very:long:credential:with:many:colons",
     98     "admin",
     99     ":nouser",
    100     "user:",
    101     ":",
    102 };
    103 #define N_CREDS (sizeof(interesting_creds) / sizeof(interesting_creds[0]))
    104 
    105 /* ── helpers ────────────────────────────────────────────────────────────── */
    106 
    107 static u32 rand_next(http_mutator_t *m) {
    108     m->seed = m->seed * 1103515245 + 12345;
    109     return (m->seed >> 16) & 0x7fff;
    110 }
    111 
    112 static size_t build_input(u8 *out, size_t max_size,
    113                           u8 auth_mode, const char *cred,
    114                           const char *path, const char *auth_hdr) {
    115     size_t cred_len = cred ? strlen(cred) : 0;
    116     size_t path_len = path ? strlen(path) : 0;
    117     size_t auth_len = auth_hdr ? strlen(auth_hdr) : 0;
    118 
    119     /* Cap lengths to prevent overflow */
    120     if (cred_len > 0xFFFF) cred_len = 0xFFFF;
    121     if (path_len > 0xFFFF) path_len = 0xFFFF;
    122 
    123     size_t total = 1 + 2 + cred_len + 2 + path_len + auth_len;
    124     if (total > max_size) {
    125         /* Trim auth_hdr to fit */
    126         if (1 + 2 + cred_len + 2 + path_len >= max_size) return 0;
    127         auth_len = max_size - 1 - 2 - cred_len - 2 - path_len;
    128         total = max_size;
    129     }
    130 
    131     size_t off = 0;
    132     out[off++] = auth_mode;
    133     out[off++] = (u8)(cred_len >> 8);
    134     out[off++] = (u8)(cred_len & 0xFF);
    135     if (cred_len > 0) { memcpy(out + off, cred, cred_len); off += cred_len; }
    136     out[off++] = (u8)(path_len >> 8);
    137     out[off++] = (u8)(path_len & 0xFF);
    138     if (path_len > 0) { memcpy(out + off, path, path_len); off += path_len; }
    139     if (auth_len > 0) { memcpy(out + off, auth_hdr, auth_len); off += auth_len; }
    140 
    141     return off;
    142 }
    143 
    144 /* Try to parse the structured format. Returns 0 on failure. */
    145 static int parse_input(const u8 *buf, size_t buf_size,
    146                        u8 *auth_mode, const u8 **cred, size_t *cred_len,
    147                        const u8 **path, size_t *path_len,
    148                        const u8 **auth_hdr, size_t *auth_hdr_len) {
    149     if (buf_size < 5) return 0;
    150 
    151     *auth_mode = buf[0];
    152     *cred_len = (size_t)((buf[1] << 8) | buf[2]);
    153     size_t off = 3;
    154 
    155     if (*cred_len > buf_size - off - 2) return 0;
    156     *cred = buf + off;
    157     off += *cred_len;
    158 
    159     if (off + 2 > buf_size) return 0;
    160     *path_len = (size_t)((buf[off] << 8) | buf[off + 1]);
    161     off += 2;
    162 
    163     if (*path_len > buf_size - off) return 0;
    164     *path = buf + off;
    165     off += *path_len;
    166 
    167     *auth_hdr = buf + off;
    168     *auth_hdr_len = buf_size - off;
    169 
    170     return 1;
    171 }
    172 
    173 /* ── AFL++ custom mutator API ───────────────────────────────────────────── */
    174 
    175 void *afl_custom_init(afl_state_t *afl, unsigned int seed) {
    176     http_mutator_t *m = calloc(1, sizeof(http_mutator_t));
    177     if (!m) return NULL;
    178     m->out_buf_size = 1 << 16;
    179     m->out_buf = malloc(m->out_buf_size);
    180     if (!m->out_buf) { free(m); return NULL; }
    181     m->seed = seed;
    182     return m;
    183 }
    184 
    185 size_t afl_custom_fuzz(void *data, u8 *buf, size_t buf_size,
    186                        u8 **out_buf, u8 *add_buf, size_t add_buf_size,
    187                        size_t max_size) {
    188     http_mutator_t *m = (http_mutator_t *)data;
    189 
    190     if (max_size > m->out_buf_size) {
    191         m->out_buf = realloc(m->out_buf, max_size);
    192         if (!m->out_buf) { m->out_buf_size = 0; *out_buf = buf; return buf_size; }
    193         m->out_buf_size = max_size;
    194     }
    195 
    196     /* Parse the existing input */
    197     u8 auth_mode;
    198     const u8 *cred, *path, *auth_hdr;
    199     size_t cred_len, path_len, auth_hdr_len;
    200 
    201     int parsed = parse_input(buf, buf_size, &auth_mode, &cred, &cred_len,
    202                              &path, &path_len, &auth_hdr, &auth_hdr_len);
    203 
    204     /* Copy current fields into mutable buffers */
    205     static char m_cred[4096], m_path[4096], m_auth[4096];
    206     u8 m_mode;
    207 
    208     if (parsed) {
    209         m_mode = auth_mode % 3;
    210         size_t n;
    211         n = cred_len < sizeof(m_cred) - 1 ? cred_len : sizeof(m_cred) - 1;
    212         memcpy(m_cred, cred, n); m_cred[n] = '\0';
    213         n = path_len < sizeof(m_path) - 1 ? path_len : sizeof(m_path) - 1;
    214         memcpy(m_path, path, n); m_path[n] = '\0';
    215         n = auth_hdr_len < sizeof(m_auth) - 1 ? auth_hdr_len : sizeof(m_auth) - 1;
    216         memcpy(m_auth, auth_hdr, n); m_auth[n] = '\0';
    217     } else {
    218         /* Can't parse — generate a fresh valid input */
    219         m_mode = rand_next(m) % 3;
    220         strcpy(m_path, interesting_paths[rand_next(m) % N_PATHS]);
    221         strcpy(m_auth, interesting_auth[rand_next(m) % N_AUTH]);
    222         strcpy(m_cred, interesting_creds[rand_next(m) % N_CREDS]);
    223     }
    224 
    225     /* Pick a mutation strategy */
    226     u32 strategy = rand_next(m) % 8;
    227 
    228     switch (strategy) {
    229     case 0:
    230         /* Replace path with an interesting one */
    231         strcpy(m_path, interesting_paths[rand_next(m) % N_PATHS]);
    232         break;
    233 
    234     case 1:
    235         /* Replace auth header with an interesting one */
    236         strcpy(m_auth, interesting_auth[rand_next(m) % N_AUTH]);
    237         break;
    238 
    239     case 2:
    240         /* Toggle auth mode */
    241         m_mode = rand_next(m) % 3;
    242         if (m_mode == 1 && m_cred[0] == '\0')
    243             strcpy(m_cred, interesting_creds[rand_next(m) % N_CREDS]);
    244         break;
    245 
    246     case 3:
    247         /* Replace credential */
    248         strcpy(m_cred, interesting_creds[rand_next(m) % N_CREDS]);
    249         break;
    250 
    251     case 4: {
    252         /* Byte-level mutation on the path */
    253         size_t plen = strlen(m_path);
    254         if (plen > 0) {
    255             size_t pos = rand_next(m) % plen;
    256             m_path[pos] ^= (1 << (rand_next(m) % 8));
    257         }
    258         break;
    259     }
    260 
    261     case 5: {
    262         /* Byte-level mutation on the auth header */
    263         size_t alen = strlen(m_auth);
    264         if (alen > 0) {
    265             size_t pos = rand_next(m) % alen;
    266             m_auth[pos] ^= (1 << (rand_next(m) % 8));
    267         }
    268         break;
    269     }
    270 
    271     case 6: {
    272         /* Splice path from add_buf if available */
    273         if (add_buf && add_buf_size >= 5) {
    274             u8 am2; const u8 *c2, *p2, *a2;
    275             size_t cl2, pl2, al2;
    276             if (parse_input(add_buf, add_buf_size, &am2, &c2, &cl2,
    277                             &p2, &pl2, &a2, &al2) && pl2 > 0) {
    278                 size_t n = pl2 < sizeof(m_path) - 1 ? pl2 : sizeof(m_path) - 1;
    279                 memcpy(m_path, p2, n);
    280                 m_path[n] = '\0';
    281             }
    282         }
    283         break;
    284     }
    285 
    286     case 7: {
    287         /* Mutate everything at once — max chaos while keeping structure */
    288         m_mode = rand_next(m) % 3;
    289         strcpy(m_path, interesting_paths[rand_next(m) % N_PATHS]);
    290         strcpy(m_auth, interesting_auth[rand_next(m) % N_AUTH]);
    291         if (m_mode == 1)
    292             strcpy(m_cred, interesting_creds[rand_next(m) % N_CREDS]);
    293         break;
    294     }
    295     }
    296 
    297     /* Rebuild the structured output */
    298     size_t out_len = build_input(m->out_buf, max_size,
    299                                  m_mode, m_cred, m_path, m_auth);
    300     if (out_len == 0) {
    301         /* Fallback: pass through original */
    302         *out_buf = buf;
    303         return buf_size;
    304     }
    305 
    306     *out_buf = m->out_buf;
    307     return out_len;
    308 }
    309 
    310 void afl_custom_deinit(void *data) {
    311     http_mutator_t *m = (http_mutator_t *)data;
    312     if (m) {
    313         free(m->out_buf);
    314         free(m);
    315     }
    316 }