etc.git


etc

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

gen_corpus.py (16532B)

      1 #!/usr/bin/env python3
      2 """Generate seed corpus for TTYD fuzzing harnesses.
      3 
      4 Creates structured binary seeds in corpus/{websocket_auth,http_parsing}/.
      5 Run from the script directory or pass the output base dir as argv[1].
      6 
      7 Formats:
      8   websocket_auth: [flags:1][cred_len:2 BE][cred][path_len:1][path]
      9                   [origin_len:1][origin][host_len:1][host][ws_message]
     10     flags: bit0=has_cred, bit1=auth_header, bit2=check_origin,
     11            bit3=url_arg, bit4=writable
     12 
     13   http_parsing:   [auth_mode:1][cred_len:2 BE][cred][path_len:2 BE][path][auth_header_value]
     14 """
     15 
     16 import os
     17 import sys
     18 import struct
     19 
     20 base_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(os.path.abspath(__file__))
     21 
     22 
     23 def write_seeds(subdir, seeds):
     24     d = os.path.join(base_dir, "corpus", subdir)
     25     os.makedirs(d, exist_ok=True)
     26     for name, data in seeds.items():
     27         with open(os.path.join(d, name), "wb") as f:
     28             f.write(data)
     29     print(f"[+] Generated {len(seeds)} seeds for {subdir}")
     30 
     31 
     32 # ── helpers ──────────────────────────────────────────────────────────────────
     33 
     34 def ws_seed(flags, cred, path, origin, host, ws_msg):
     35     """websocket_auth format:
     36     [flags:1][cred_len:2 BE][cred][path_len:1][path]
     37     [origin_len:1][origin][host_len:1][host][ws_message]"""
     38     return (struct.pack(">B", flags)
     39             + struct.pack(">H", len(cred)) + cred
     40             + struct.pack(">B", len(path)) + path
     41             + struct.pack(">B", len(origin)) + origin
     42             + struct.pack(">B", len(host)) + host
     43             + ws_msg)
     44 
     45 
     46 def http_seed(mode, cred, path, auth=b""):
     47     """http_parsing format: [mode:1][cred_len:2 BE][cred][path_len:2 BE][path][auth]"""
     48     return (struct.pack(">B", mode)
     49             + struct.pack(">H", len(cred)) + cred
     50             + struct.pack(">H", len(path)) + path
     51             + auth)
     52 
     53 
     54 # ── websocket_auth seeds ─────────────────────────────────────────────────────
     55 # Flags: 0x01=has_cred, 0x02=auth_header, 0x04=check_origin, 0x08=url_arg, 0x10=writable
     56 
     57 WS = b"/ws"
     58 ORIGIN = b"http://localhost:7681"
     59 HOST = b"localhost:7681"
     60 JSON_AUTH = b'{"columns":80,"rows":24,"AuthToken":"admin"}'
     61 JSON_DIMS = b'{"columns":80,"rows":24}'
     62 
     63 ws_seeds = {
     64     # ── No auth, basic JSON_DATA command ──
     65     "noauth_json":          ws_seed(0x10, b"", WS, b"", b"", JSON_DIMS),
     66     "noauth_json_large":    ws_seed(0x10, b"", WS, b"", b"", b'{"columns":200,"rows":50}'),
     67     "noauth_json_empty":    ws_seed(0x10, b"", WS, b"", b"", b"{}"),
     68     "noauth_json_malformed":ws_seed(0x10, b"", WS, b"", b"", b"{invalid json}"),
     69 
     70     # ── With credential — JSON_DATA auth flow ──
     71     "auth_valid":           ws_seed(0x11, b"admin", WS, b"", b"", JSON_AUTH),
     72     "auth_invalid":         ws_seed(0x11, b"admin", WS, b"", b"",
     73                                     b'{"columns":80,"rows":24,"AuthToken":"wrong"}'),
     74     "auth_missing_token":   ws_seed(0x11, b"admin", WS, b"", b"", JSON_DIMS),
     75     "auth_no_dims":         ws_seed(0x11, b"admin", WS, b"", b"", b'{"AuthToken":"admin"}'),
     76     "auth_empty_cred":      ws_seed(0x01, b"", WS, b"", b"", JSON_DIMS),
     77     "auth_token_null":      ws_seed(0x11, b"admin", WS, b"", b"",
     78                                     b'{"columns":80,"rows":24,"AuthToken":null}'),
     79     "auth_token_bool":      ws_seed(0x11, b"admin", WS, b"", b"",
     80                                     b'{"columns":80,"rows":24,"AuthToken":true}'),
     81     "auth_long_cred":       ws_seed(0x11, b"A" * 200, WS, b"", b"",
     82                                     b'{"AuthToken":"' + b"A" * 200 + b'"}'),
     83 
     84     # ── Custom auth header (bit 1) ──
     85     "custom_auth":          ws_seed(0x12, b"someuser", WS, b"", b"", JSON_DIMS),
     86     "custom_auth_empty":    ws_seed(0x02, b"", WS, b"", b"", JSON_DIMS),
     87 
     88     # ── INPUT command ('0' prefix) ──
     89     "input_hello":          ws_seed(0x10, b"", WS, b"", b"", b"0hello world"),
     90     "input_ctrl_c":         ws_seed(0x10, b"", WS, b"", b"", b"0\x03"),
     91     "input_long":           ws_seed(0x10, b"", WS, b"", b"", b"0" + b"A" * 4096),
     92     "input_not_writable":   ws_seed(0x00, b"", WS, b"", b"", b"0hello"),
     93 
     94     # ── RESIZE command ('1' prefix) ──
     95     "resize_normal":        ws_seed(0x10, b"", WS, b"", b"",
     96                                     b'1{"columns":120,"rows":40}'),
     97     "resize_huge":          ws_seed(0x10, b"", WS, b"", b"",
     98                                     b'1{"columns":65535,"rows":65535}'),
     99     "resize_negative":      ws_seed(0x10, b"", WS, b"", b"",
    100                                     b'1{"columns":-1,"rows":-1}'),
    101 
    102     # ── PAUSE/RESUME commands ──
    103     "pause":                ws_seed(0x10, b"", WS, b"", b"", b"2"),
    104     "resume":               ws_seed(0x10, b"", WS, b"", b"", b"3"),
    105 
    106     # ── Unknown command ──
    107     "unknown_cmd":          ws_seed(0x10, b"", WS, b"", b"", b"Xunknown"),
    108 
    109     # ── Origin checking (bit 2) ──
    110     "origin_match":         ws_seed(0x14, b"", WS, ORIGIN, HOST, JSON_DIMS),
    111     "origin_mismatch":      ws_seed(0x14, b"", WS, b"http://evil.com", HOST, JSON_DIMS),
    112     "origin_no_host":       ws_seed(0x14, b"", WS, ORIGIN, b"", JSON_DIMS),
    113     "origin_no_origin":     ws_seed(0x14, b"", WS, b"", HOST, JSON_DIMS),
    114     "origin_long":          ws_seed(0x14, b"", WS,
    115                                     b"http://" + b"A" * 200 + b":9999",
    116                                     b"A" * 200 + b":9999", JSON_DIMS),
    117     "origin_port_80":       ws_seed(0x14, b"", WS,
    118                                     b"http://localhost:80", b"localhost", JSON_DIMS),
    119     "origin_port_443":      ws_seed(0x14, b"", WS,
    120                                     b"https://localhost:443", b"localhost", JSON_DIMS),
    121 
    122     # ── URL args (bit 3) ──
    123     "url_arg_enabled":      ws_seed(0x18, b"", WS, b"", b"", b"0echo test"),
    124     "url_arg_long":         ws_seed(0x18, b"", WS, b"", b"", b"0" + b"X" * 250),
    125 
    126     # ── Path variations ──
    127     "path_wrong":           ws_seed(0x10, b"", b"/wrong", b"", b"", JSON_DIMS),
    128     "path_empty":           ws_seed(0x10, b"", b"", b"", b"", JSON_DIMS),
    129     "path_traversal":       ws_seed(0x10, b"", b"/../ws", b"", b"", JSON_DIMS),
    130     "path_long":            ws_seed(0x10, b"", b"/" + b"A" * 200, b"", b"", JSON_DIMS),
    131     "path_null":            ws_seed(0x10, b"", b"/ws\x00extra", b"", b"", JSON_DIMS),
    132 
    133     # ── Auth + origin combined ──
    134     "auth_origin_valid":    ws_seed(0x15, b"admin", WS, ORIGIN, HOST, JSON_AUTH),
    135     "auth_origin_invalid":  ws_seed(0x15, b"admin", WS, b"http://evil.com", HOST, JSON_AUTH),
    136 
    137     # ── Empty / minimal ──
    138     "empty_msg":            ws_seed(0x10, b"", WS, b"", b"", b""),
    139     "minimal":              ws_seed(0x00, b"", b"", b"", b"", b""),
    140 
    141     # ── JSON edge cases ──
    142     "json_deep_nested":     ws_seed(0x10, b"", WS, b"", b"",
    143                                     b'{"a":' * 50 + b"1" + b"}" * 50),
    144     "json_huge_token":      ws_seed(0x11, b"admin", WS, b"", b"",
    145                                     b'{"columns":80,"rows":24,"AuthToken":"' + b"A" * 5000 + b'"}'),
    146     "json_truncated":       ws_seed(0x10, b"", WS, b"", b"", b'{"columns":'),
    147     "json_extra_ws":        ws_seed(0x10, b"", WS, b"", b"",
    148                                     b'{  "columns" : 80 , "rows" : 24  }'),
    149     "json_uint16_overflow": ws_seed(0x10, b"", WS, b"", b"",
    150                                     b'{"columns":65536,"rows":65536}'),
    151 
    152     # ── Authenticated commands (need JSON_DATA first to auth) ──
    153     # Note: without auth, non-JSON_DATA commands with credential set get rejected
    154     "unauth_input":         ws_seed(0x11, b"admin", WS, b"", b"", b"0hello"),
    155     "unauth_resize":        ws_seed(0x11, b"admin", WS, b"", b"",
    156                                     b'1{"columns":80,"rows":24}'),
    157 }
    158 
    159 
    160 # ── http_parsing seeds ───────────────────────────────────────────────────────
    161 
    162 B64_CRED = b"YWRtaW46cGFzcw=="
    163 
    164 http_seeds = {
    165     # No auth, various paths
    166     "noauth_root":        http_seed(0, b"", b"/"),
    167     "noauth_token":       http_seed(0, b"", b"/token"),
    168     "noauth_ws":          http_seed(0, b"", b"/ws"),
    169     "noauth_parent":      http_seed(0, b"", b"/base-path"),
    170     "noauth_404":         http_seed(0, b"", b"/nonexistent"),
    171     "noauth_empty_path":  http_seed(0, b"", b""),
    172     # Path traversal
    173     "path_traversal":     http_seed(0, b"", b"/../../../etc/passwd"),
    174     "path_double_slash":  http_seed(0, b"", b"//etc/passwd"),
    175     "path_dotdot":        http_seed(0, b"", b"/./../../etc/shadow"),
    176     "path_null":          http_seed(0, b"", b"/\x00hidden"),
    177     "path_encoded":       http_seed(0, b"", b"/%2e%2e/%2e%2e/etc/passwd"),
    178     "path_fmt":           http_seed(0, b"", b"/%s%s%s%n"),
    179     "path_crlf":          http_seed(0, b"", b"/\r\nInjected: header"),
    180     "path_long":          http_seed(0, b"", b"/" + b"A" * 200),
    181     # Basic auth — valid
    182     "basic_valid":        http_seed(1, B64_CRED, b"/", b"Basic " + B64_CRED),
    183     "basic_token_path":   http_seed(1, B64_CRED, b"/token", b"Basic " + B64_CRED),
    184     # Basic auth — invalid
    185     "basic_mismatch":     http_seed(1, B64_CRED, b"/", b"Basic INVALIDBASE64=="),
    186     "basic_empty":        http_seed(1, B64_CRED, b"/", b"Basic "),
    187     "basic_no_header":    http_seed(1, B64_CRED, b"/", b""),
    188     "basic_wrong_scheme": http_seed(1, B64_CRED, b"/", b"Bearer token123"),
    189     "basic_lowercase":    http_seed(1, B64_CRED, b"/", b"basic " + B64_CRED),
    190     "basic_uppercase":    http_seed(1, B64_CRED, b"/", b"BASIC " + B64_CRED),
    191     "basic_dbl_space":    http_seed(1, B64_CRED, b"/", b"Basic  " + B64_CRED),
    192     "basic_leading_sp":   http_seed(1, B64_CRED, b"/", b" Basic " + B64_CRED),
    193     "basic_no_cred":      http_seed(1, b"", b"/", b"Basic " + B64_CRED),
    194     # Boundary (256-byte buf in check_auth)
    195     "basic_long_auth":    http_seed(1, b"x:y", b"/", b"Basic " + b"A" * 300),
    196     "basic_boundary_254": http_seed(1, b"x:y", b"/", b"Basic " + b"A" * 248),
    197     "basic_boundary_256": http_seed(1, b"x:y", b"/", b"Basic " + b"A" * 250),
    198     # Custom header auth
    199     "custom_with_value":  http_seed(2, b"", b"/", b"some-token-value"),
    200     "custom_empty":       http_seed(2, b"", b"/"),
    201     "custom_token_path":  http_seed(2, b"", b"/token", b"admin"),
    202     # Format string in auth
    203     "auth_fmt":           http_seed(1, b"cred", b"/", b"Basic %s%s%s%n%n"),
    204 }
    205 
    206 
    207 # ── ws_fragments seeds ────────────────────────────────────────────────────────
    208 # Format: [num_frags:1][cmd:1][payload...]
    209 
    210 def frags_seed(num_frags, cmd, payload):
    211     return bytes([num_frags & 0x07, cmd]) + payload
    212 
    213 CMD_JSON  = ord('{')
    214 CMD_INPUT = ord('0')
    215 CMD_RESIZE= ord('1')
    216 CMD_PAUSE = ord('2')
    217 
    218 ws_frags_seeds = {
    219     "single_json":     frags_seed(1, CMD_JSON,  b'{"columns":80,"rows":24}'),
    220     "two_json":        frags_seed(2, CMD_JSON,  b'{"columns":80,"rows":24}'),
    221     "four_json":       frags_seed(4, CMD_JSON,  b'{"columns":200,"rows":50}abcdef'),
    222     "single_resize":   frags_seed(1, CMD_RESIZE,b'{"columns":120,"rows":40}'),
    223     "three_resize":    frags_seed(3, CMD_RESIZE,b'{"columns":120,"rows":40}padding'),
    224     "single_input":    frags_seed(1, CMD_INPUT, b'hello world'),
    225     "two_input":       frags_seed(2, CMD_INPUT, b'hello world long payload here'),
    226     "eight_input":     frags_seed(8, CMD_INPUT, b'A' * 64),
    227     "single_pause":    frags_seed(1, CMD_PAUSE, b''),
    228     "large_single":    frags_seed(1, CMD_INPUT, b'X' * 512),
    229     "malformed_json":  frags_seed(2, CMD_JSON,  b'{invalid'),
    230     "empty_payload":   frags_seed(1, CMD_JSON,  b'{}'),
    231 }
    232 
    233 # ── prefs_message seeds ───────────────────────────────────────────────────────
    234 # Format: [prefs_len:2 BE][prefs_json][cmd_len:2 BE][command]
    235 
    236 def prefs_seed(prefs, command):
    237     return (struct.pack(">H", len(prefs)) + prefs +
    238             struct.pack(">H", len(command)) + command)
    239 
    240 prefs_seeds = {
    241     "normal":          prefs_seed(b'{}',                      b'bash'),
    242     "prefs_medium":    prefs_seed(b'{"theme":"light"}',       b'bash'),
    243     "prefs_boundary":  prefs_seed(b'{"x":"' + b'A'*4080 + b'"}', b'bash'),
    244     "prefs_overflow":  prefs_seed(b'A' * 4097,               b'bash'),
    245     "prefs_overflow2": prefs_seed(b'A' * 8192,               b'bash'),
    246     "cmd_medium":      prefs_seed(b'{}',                      b'/bin/bash'),
    247     "cmd_boundary":    prefs_seed(b'{}',                      b'/bin/' + b'A'*3965),
    248     "cmd_overflow":    prefs_seed(b'{}',                      b'A' * 4000),
    249     "both_large":      prefs_seed(b'A' * 3000,               b'B' * 3000),
    250     "empty_prefs":     prefs_seed(b'',                        b'bash'),
    251     "empty_cmd":       prefs_seed(b'{}',                      b''),
    252     "fmt_string":      prefs_seed(b'%s%s%n%n',               b'%s%n'),
    253 }
    254 
    255 # ── json_recv seeds ───────────────────────────────────────────────────────────
    256 # Format: [num_msgs:1][cmd:1][len:2 BE][payload...] × N
    257 
    258 def recv_msg(cmd, payload):
    259     return bytes([cmd]) + struct.pack(">H", len(payload)) + payload
    260 
    261 def recv_seed(*messages):
    262     n = min(len(messages), 4)
    263     return bytes([n & 0x03]) + b''.join(messages[:n])
    264 
    265 JSON_DATA = ord('{')
    266 RESIZE    = ord('1')
    267 INPUT_CMD = ord('0')
    268 PAUSE_CMD = ord('2')
    269 RESUME_CMD= ord('3')
    270 
    271 json_recv_seeds = {
    272     # Single JSON_DATA
    273     "json_only":        recv_seed(recv_msg(JSON_DATA, b'{"columns":80,"rows":24}')),
    274     "json_empty":       recv_seed(recv_msg(JSON_DATA, b'{}')),
    275     "json_malformed":   recv_seed(recv_msg(JSON_DATA, b'{invalid}')),
    276     "json_huge_cols":   recv_seed(recv_msg(JSON_DATA, b'{"columns":65535,"rows":65535}')),
    277     "json_neg":         recv_seed(recv_msg(JSON_DATA, b'{"columns":-1,"rows":-1}')),
    278     "json_overflow16":  recv_seed(recv_msg(JSON_DATA, b'{"columns":65536,"rows":65536}')),
    279     "json_deep":        recv_seed(recv_msg(JSON_DATA, b'{"a":' * 30 + b'1' + b'}' * 30)),
    280     "json_long_str":    recv_seed(recv_msg(JSON_DATA, b'{"columns":80,"rows":24,"x":"' + b'A'*500 + b'"}')),
    281 
    282     # JSON_DATA then RESIZE
    283     "json_then_resize": recv_seed(
    284         recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'),
    285         recv_msg(RESIZE,    b'{"columns":120,"rows":40}'),
    286     ),
    287     "json_then_resize_bad": recv_seed(
    288         recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'),
    289         recv_msg(RESIZE,    b'{bad json'),
    290     ),
    291 
    292     # JSON_DATA then INPUT
    293     "json_then_input":  recv_seed(
    294         recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'),
    295         recv_msg(INPUT_CMD, b'ls -la\n'),
    296     ),
    297 
    298     # JSON_DATA then PAUSE/RESUME
    299     "json_pause_resume":recv_seed(
    300         recv_msg(JSON_DATA,  b'{"columns":80,"rows":24}'),
    301         recv_msg(PAUSE_CMD,  b''),
    302         recv_msg(RESUME_CMD, b''),
    303     ),
    304 
    305     # Four messages
    306     "full_sequence":    recv_seed(
    307         recv_msg(JSON_DATA,  b'{"columns":80,"rows":24}'),
    308         recv_msg(RESIZE,     b'{"columns":160,"rows":50}'),
    309         recv_msg(INPUT_CMD,  b'echo hello\n'),
    310         recv_msg(PAUSE_CMD,  b''),
    311     ),
    312 
    313     # RESIZE before JSON_DATA (pss->process == NULL → no-op in RESIZE)
    314     "resize_first":     recv_seed(recv_msg(RESIZE, b'{"columns":80,"rows":24}')),
    315 
    316     # Unknown command
    317     "unknown_cmd":      recv_seed(recv_msg(ord('Z'), b'some data')),
    318     "unknown_two":      recv_seed(
    319         recv_msg(JSON_DATA, b'{"columns":80,"rows":24}'),
    320         recv_msg(ord('X'),  b'garbage'),
    321     ),
    322 }
    323 
    324 
    325 # ── main ─────────────────────────────────────────────────────────────────────
    326 
    327 if __name__ == "__main__":
    328     write_seeds("websocket_auth",  ws_seeds)
    329     write_seeds("http_parsing",    http_seeds)
    330     write_seeds("ws_fragments",    ws_frags_seeds)
    331     write_seeds("prefs_message",   prefs_seeds)
    332     write_seeds("json_recv",       json_recv_seeds)
    333 
    334     total = len(ws_seeds) + len(http_seeds) + len(ws_frags_seeds) + len(prefs_seeds) + len(json_recv_seeds)
    335     print(f"[+] Total corpus: {total} files")