etc~ scripts and random things |
| git clone https://github.com/djnnvx/etc.git |
| log | files | refs | archive |
gen_corpus.sh (6260B)
1 #!/bin/bash 2 # gen_corpus.sh - mint XDR-shaped seeds for each harness. 3 # 4 # libnfs reply bytes are XDR (Sun RPC variant) encodings of nested structs. 5 # Easiest path for real seeds: run rpcinfo/showmount/nfs-ls against an actual 6 # NFS server and capture replies with tcpdump. Without that, this script 7 # writes a handful of minimal-but-valid synthetic encodings so AFL has 8 # something better than /dev/urandom to mutate from. 9 # 10 # Seeds are intentionally tiny - the goal is to stake out the boundaries of 11 # each procedure's accepted-vs-rejected tag and let AFL grow them. 12 set -euo pipefail 13 14 SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)" 15 cd "${SCRIPT_DIR}" 16 17 mkdir -p corpus/nfs3 corpus/nfs4 corpus/mount corpus/pdu 18 19 w32() { printf '%08x' "$1" | xxd -r -p; } # 32-bit big-endian write 20 21 # --- NFSv3 GETATTR3res (NFS3_OK + a small fattr3) --- 22 # First byte of fuzz input picks the decoder; seed 0x0c = GETATTR3res table idx 23 # (counts from the fuzz_nfs3.c order; FSINFO is index 18, READ is 5, etc.) 24 # 25 # fattr3 layout (per RFC1813): ftype(u32) mode(u32) nlink(u32) uid(u32) gid(u32) 26 # size(u64) used(u64) rdev(specdata3=u64) fsid(u64) fileid(u64) 27 # atime(nfstime3=u64) mtime ctime = 80 bytes. 28 # 29 # GETATTR3res = status(u32=0 OK) + fattr3 30 { 31 printf '\x0c' # idx=12 (GETATTR3res) 32 w32 0 # NFS3_OK 33 w32 1 # NF3REG 34 w32 33188 # mode = 0100644 octal 35 w32 1 # nlink 36 w32 1000 # uid 37 w32 1000 # gid 38 printf '\x00\x00\x00\x00\x00\x00\x00\x10' # size=16 39 printf '\x00\x00\x00\x00\x00\x00\x00\x20' # used=32 40 printf '\x00\x00\x00\x00\x00\x00\x00\x00' # rdev 41 printf '\x00\x00\x00\x00\x00\x00\x00\x01' # fsid 42 printf '\x00\x00\x00\x00\x00\x00\x00\x42' # fileid 43 printf '\x00\x00\x00\x00\x00\x00\x00\x00' # atime 44 printf '\x00\x00\x00\x00\x00\x00\x00\x00' # mtime 45 printf '\x00\x00\x00\x00\x00\x00\x00\x00' # ctime 46 } > corpus/nfs3/getattr3_ok.bin 47 48 # NFSv3 READDIR3res failure with post-op-attrs absent 49 { 50 printf '\x0f' # idx=15 READDIR3res 51 w32 5 # NFS3ERR_NOENT 52 w32 0 # post_op_attr present=false 53 } > corpus/nfs3/readdir3_noent.bin 54 55 # NFSv3 FSINFO3res success with the standard 1<<20 properties 56 { 57 printf '\x12' # idx=18 FSINFO3res 58 w32 0 # NFS3_OK 59 w32 0 # post_op_attr present=false 60 w32 1048576 # rtmax 61 w32 1048576 # rtpref 62 w32 4096 # rtmult 63 w32 1048576 # wtmax 64 w32 1048576 # wtpref 65 w32 4096 # wtmult 66 w32 4096 # dtpref 67 printf '\x00\x00\x00\xff\xff\xff\xff\xff' # maxfilesize 68 printf '\x00\x00\x00\x00\x00\x00\x00\x00' # time_delta seconds 69 w32 0 # time_delta nseconds 70 w32 0x1b # properties = LINK|SYMLINK|HOMOGENOUS|CANSETTIME 71 } > corpus/nfs3/fsinfo3.bin 72 73 # --- NFSv4 COMPOUND4res: minimal NFS4_OK status, empty tag, zero ops --- 74 { 75 w32 0 # status NFS4_OK 76 w32 0 # tag length = 0 77 w32 0 # array of resop4: count = 0 78 } > corpus/nfs4/compound4_empty.bin 79 80 # NFSv4 COMPOUND with one PUTROOTFH op 81 { 82 w32 0 # status NFS4_OK 83 w32 0 # tag length = 0 84 w32 1 # resop count = 1 85 w32 24 # OP_PUTROOTFH = 24 86 w32 0 # NFS4_OK 87 } > corpus/nfs4/compound4_putrootfh.bin 88 89 # --- MOUNT3 mountres3: ok + small file handle + 1 auth flavour (AUTH_SYS=1) --- 90 { 91 printf '\x00' # idx=0 mountres3 92 w32 0 # MNT3_OK 93 w32 8 # fhandle3.len = 8 94 printf 'ABCDEFGH' # fhandle bytes 95 w32 1 # auth_flavors.len = 1 96 w32 1 # AUTH_SYS 97 } > corpus/mount/mountres3_ok.bin 98 99 # MOUNT3 exports listing one export with no group restriction 100 { 101 printf '\x01' # idx=1 exports 102 w32 1 # 1 entry (XDR optional: present=1) 103 w32 5 # dirpath length = 5 104 printf '/data' 105 printf '\x00\x00\x00' # padding to 4-byte boundary 106 w32 0 # groups: empty (present=0) 107 w32 0 # next-entry present=0 108 } > corpus/mount/exports_one.bin 109 110 # --- PDU envelope: an accepted REPLY with SUCCESS body --- 111 { 112 printf '\x00' # as_reply (bit 0 = 0) 113 w32 0x12345678 # xid 114 w32 1 # direction = REPLY 115 w32 0 # reply_stat = MSG_ACCEPTED 116 w32 0 # verf flavour = AUTH_NONE 117 w32 0 # verf len = 0 118 w32 0 # accept_stat = SUCCESS 119 } > corpus/pdu/reply_ok.bin 120 121 # A rejected REPLY (RPC_MISMATCH) 122 { 123 printf '\x00' 124 w32 0xdeadbeef 125 w32 1 # REPLY 126 w32 1 # MSG_DENIED 127 w32 0 # RPC_MISMATCH 128 w32 2 ; w32 2 # low=2 high=2 (we speak RPCv2) 129 } > corpus/pdu/reply_denied.bin 130 131 # A CALL frame (server side) - PORTMAP NULL 132 { 133 printf '\x01' # as_call (bit 0 = 1) 134 w32 0xcafebabe 135 w32 0 # direction = CALL 136 w32 2 # rpcvers 137 w32 100000 # program = PORTMAP 138 w32 2 # version 139 w32 0 # procedure NULL 140 w32 0 ; w32 0 # AUTH_NONE cred 141 w32 0 ; w32 0 # AUTH_NONE verf 142 } > corpus/pdu/call_pmap_null.bin 143 144 ls -la corpus/nfs3/ corpus/nfs4/ corpus/mount/ corpus/pdu/ 145 echo "[+] Corpus generated. Drop additional real captures via:" 146 echo " sudo tcpdump -i any -w nfs.pcap port 2049" 147 echo " then split TCP payloads out (see README.md)."