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 }