[SHEHZADE//ABDULLAH ANSARI]
Red Team Consultant @ Google | MS in Cybersecurity @ NYU
● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0
Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.
$
git clone https://shehzade.io/repos/contraband.git
Copy
Source ZIP (1.93 MB)
263 lines · 6.6 KB
| 1 | package evasion |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "crypto/sha256" |
| 6 | "encoding/hex" |
| 7 | "fmt" |
| 8 | "hash" |
| 9 | "io" |
| 10 | "sort" |
| 11 | "sync" |
| 12 | |
| 13 | "github.com/shehzade/contraband/pkg/common" |
| 14 | ) |
| 15 | |
| 16 | // StreamChunker provides memory-bounded, sequential chunk framing over an io.Reader. |
| 17 | type StreamChunker struct { |
| 18 | reader io.Reader |
| 19 | token common.SessionToken |
| 20 | chunkSize int |
| 21 | totalBytes int64 |
| 22 | totalChunks uint32 |
| 23 | seq uint32 |
| 24 | buf []byte |
| 25 | bufPtr *[]byte |
| 26 | pool *sync.Pool |
| 27 | hasher hash.Hash |
| 28 | } |
| 29 | |
| 30 | var chunkBufferPools sync.Map // map[int]*sync.Pool |
| 31 | |
| 32 | func getChunkBufferPool(size int) *sync.Pool { |
| 33 | if p, ok := chunkBufferPools.Load(size); ok { |
| 34 | return p.(*sync.Pool) |
| 35 | } |
| 36 | p := &sync.Pool{ |
| 37 | New: func() any { |
| 38 | b := make([]byte, size) |
| 39 | return &b |
| 40 | }, |
| 41 | } |
| 42 | actual, _ := chunkBufferPools.LoadOrStore(size, p) |
| 43 | return actual.(*sync.Pool) |
| 44 | } |
| 45 | |
| 46 | // NewStreamChunker creates an iterator that reads up to chunkSize bytes and frames with ChunkHeader. |
| 47 | func NewStreamChunker(reader io.Reader, token common.SessionToken, chunkSize int, totalBytes int64) (*StreamChunker, error) { |
| 48 | if chunkSize <= 0 { |
| 49 | chunkSize = common.DefaultChunkSize |
| 50 | } |
| 51 | if token.IsZero() { |
| 52 | tok, err := common.NewSessionToken() |
| 53 | if err != nil { |
| 54 | return nil, err |
| 55 | } |
| 56 | token = tok |
| 57 | } |
| 58 | |
| 59 | // Auto-detect length if reader is *bytes.Reader or *bytes.Buffer |
| 60 | if totalBytes <= 0 { |
| 61 | if br, ok := reader.(*bytes.Reader); ok { |
| 62 | totalBytes = int64(br.Len()) |
| 63 | } else if bb, ok := reader.(*bytes.Buffer); ok { |
| 64 | totalBytes = int64(bb.Len()) |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | totalChunks := uint32(1) |
| 69 | if totalBytes > 0 { |
| 70 | totalChunks = uint32((totalBytes + int64(chunkSize) - 1) / int64(chunkSize)) |
| 71 | } |
| 72 | |
| 73 | bufSize := common.ChunkHeaderSize + chunkSize |
| 74 | pool := getChunkBufferPool(bufSize) |
| 75 | bufPtr := pool.Get().(*[]byte) |
| 76 | if len(*bufPtr) < bufSize { |
| 77 | *bufPtr = make([]byte, bufSize) |
| 78 | } |
| 79 | |
| 80 | return &StreamChunker{ |
| 81 | reader: reader, |
| 82 | token: token, |
| 83 | chunkSize: chunkSize, |
| 84 | totalBytes: totalBytes, |
| 85 | totalChunks: totalChunks, |
| 86 | buf: (*bufPtr)[:bufSize], |
| 87 | bufPtr: bufPtr, |
| 88 | pool: pool, |
| 89 | hasher: sha256.New(), |
| 90 | }, nil |
| 91 | } |
| 92 | |
| 93 | func (s *StreamChunker) TotalChunks() uint32 { |
| 94 | return s.totalChunks |
| 95 | } |
| 96 | |
| 97 | // PayloadHashHex returns the lowercase hex-encoded SHA-256 hash of all payload bytes streamed so far. |
| 98 | func (s *StreamChunker) PayloadHashHex() string { |
| 99 | return hex.EncodeToString(s.hasher.Sum(nil)) |
| 100 | } |
| 101 | |
| 102 | // Next reads the next chunk slice and returns the framed wire bytes (Header + Payload). |
| 103 | // Returns (chunkFrame, seq, err). At EOF, returns (nil, 0, io.EOF). |
| 104 | func (s *StreamChunker) Next() ([]byte, uint32, error) { |
| 105 | if s.totalBytes > 0 && s.seq >= s.totalChunks { |
| 106 | return nil, 0, io.EOF |
| 107 | } |
| 108 | |
| 109 | payloadBuf := s.buf[common.ChunkHeaderSize : common.ChunkHeaderSize+s.chunkSize] |
| 110 | n, err := io.ReadFull(s.reader, payloadBuf) |
| 111 | if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { |
| 112 | return nil, 0, err |
| 113 | } |
| 114 | if n == 0 { |
| 115 | if s.totalBytes == 0 && s.seq == 0 { |
| 116 | s.seq = 1 |
| 117 | hdr := common.ChunkHeader{ |
| 118 | Magic: common.MagicHeader, |
| 119 | Token: s.token, |
| 120 | Sequence: 1, |
| 121 | TotalChunks: 1, |
| 122 | PayloadLen: 0, |
| 123 | } |
| 124 | hdrBytes, err := hdr.MarshalBinary() |
| 125 | if err != nil { |
| 126 | return nil, 0, err |
| 127 | } |
| 128 | copy(s.buf[:common.ChunkHeaderSize], hdrBytes) |
| 129 | return s.buf[:common.ChunkHeaderSize], 1, nil |
| 130 | } |
| 131 | return nil, 0, io.EOF |
| 132 | } |
| 133 | |
| 134 | s.hasher.Write(payloadBuf[:n]) |
| 135 | s.seq++ |
| 136 | hdr := common.ChunkHeader{ |
| 137 | Magic: common.MagicHeader, |
| 138 | Token: s.token, |
| 139 | Sequence: s.seq, |
| 140 | TotalChunks: s.totalChunks, |
| 141 | PayloadLen: uint32(n), |
| 142 | } |
| 143 | hdrBytes, err := hdr.MarshalBinary() |
| 144 | if err != nil { |
| 145 | return nil, 0, err |
| 146 | } |
| 147 | copy(s.buf[:common.ChunkHeaderSize], hdrBytes) |
| 148 | |
| 149 | return s.buf[:common.ChunkHeaderSize+n], s.seq, nil |
| 150 | } |
| 151 | |
| 152 | // Close releases any allocated buffer back to the sync.Pool. |
| 153 | func (s *StreamChunker) Close() error { |
| 154 | if s.bufPtr != nil && s.pool != nil { |
| 155 | s.pool.Put(s.bufPtr) |
| 156 | s.bufPtr = nil |
| 157 | s.buf = nil |
| 158 | } |
| 159 | return nil |
| 160 | } |
| 161 | |
| 162 | func ChunkPayload(data []byte, token common.SessionToken, chunkSize int) ([][]byte, error) { |
| 163 | if chunkSize <= 0 { |
| 164 | chunkSize = common.DefaultChunkSize |
| 165 | } |
| 166 | totalLen := len(data) |
| 167 | if totalLen == 0 { |
| 168 | totalChunks := uint32(1) |
| 169 | hdr := common.ChunkHeader{ |
| 170 | Magic: common.MagicHeader, |
| 171 | Token: token, |
| 172 | Sequence: 1, |
| 173 | TotalChunks: totalChunks, |
| 174 | PayloadLen: 0, |
| 175 | } |
| 176 | hdrBytes, _ := hdr.MarshalBinary() |
| 177 | return [][]byte{hdrBytes}, nil |
| 178 | } |
| 179 | |
| 180 | totalChunks := uint32((totalLen + chunkSize - 1) / chunkSize) |
| 181 | chunks := make([][]byte, 0, totalChunks) |
| 182 | |
| 183 | for seq := uint32(1); seq <= totalChunks; seq++ { |
| 184 | start := int(seq-1) * chunkSize |
| 185 | end := start + chunkSize |
| 186 | if end > totalLen { |
| 187 | end = totalLen |
| 188 | } |
| 189 | chunkSlice := data[start:end] |
| 190 | |
| 191 | hdr := common.ChunkHeader{ |
| 192 | Magic: common.MagicHeader, |
| 193 | Token: token, |
| 194 | Sequence: seq, |
| 195 | TotalChunks: totalChunks, |
| 196 | PayloadLen: uint32(len(chunkSlice)), |
| 197 | } |
| 198 | hdrBytes, err := hdr.MarshalBinary() |
| 199 | if err != nil { |
| 200 | return nil, err |
| 201 | } |
| 202 | |
| 203 | frame := make([]byte, len(hdrBytes)+len(chunkSlice)) |
| 204 | copy(frame[:len(hdrBytes)], hdrBytes) |
| 205 | copy(frame[len(hdrBytes):], chunkSlice) |
| 206 | chunks = append(chunks, frame) |
| 207 | } |
| 208 | |
| 209 | return chunks, nil |
| 210 | } |
| 211 | |
| 212 | func ReassembleChunks(frames [][]byte) ([]byte, error) { |
| 213 | if len(frames) == 0 { |
| 214 | return nil, fmt.Errorf("no frames provided for reassembly") |
| 215 | } |
| 216 | |
| 217 | type chunkEntry struct { |
| 218 | hdr common.ChunkHeader |
| 219 | payload []byte |
| 220 | } |
| 221 | |
| 222 | entries := make([]chunkEntry, 0, len(frames)) |
| 223 | var expectedTotal uint32 |
| 224 | var expectedToken common.SessionToken |
| 225 | |
| 226 | for i, frame := range frames { |
| 227 | if len(frame) < common.ChunkHeaderSize { |
| 228 | return nil, fmt.Errorf("frame %d shorter than 32-byte header (%d bytes)", i, len(frame)) |
| 229 | } |
| 230 | var hdr common.ChunkHeader |
| 231 | if err := hdr.UnmarshalBinary(frame[:common.ChunkHeaderSize]); err != nil { |
| 232 | return nil, fmt.Errorf("frame %d has invalid header: %w", i, err) |
| 233 | } |
| 234 | if i == 0 { |
| 235 | expectedTotal = hdr.TotalChunks |
| 236 | expectedToken = hdr.Token |
| 237 | } else { |
| 238 | if hdr.TotalChunks != expectedTotal { |
| 239 | return nil, fmt.Errorf("mismatched total chunks count in frame %d", i) |
| 240 | } |
| 241 | if hdr.Token != expectedToken { |
| 242 | return nil, fmt.Errorf("mismatched session token in frame %d", i) |
| 243 | } |
| 244 | } |
| 245 | payload := frame[common.ChunkHeaderSize : common.ChunkHeaderSize+hdr.PayloadLen] |
| 246 | entries = append(entries, chunkEntry{hdr: hdr, payload: payload}) |
| 247 | } |
| 248 | |
| 249 | sort.Slice(entries, func(i, j int) bool { |
| 250 | return entries[i].hdr.Sequence < entries[j].hdr.Sequence |
| 251 | }) |
| 252 | |
| 253 | var buf bytes.Buffer |
| 254 | for expectedSeq := uint32(1); expectedSeq <= expectedTotal; expectedSeq++ { |
| 255 | idx := int(expectedSeq - 1) |
| 256 | if idx >= len(entries) || entries[idx].hdr.Sequence != expectedSeq { |
| 257 | return nil, fmt.Errorf("missing chunk sequence %d of %d", expectedSeq, expectedTotal) |
| 258 | } |
| 259 | buf.Write(entries[idx].payload) |
| 260 | } |
| 261 | |
| 262 | return buf.Bytes(), nil |
| 263 | } |