● 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)
Code 174 Commits 1
contraband / pkg / client / evasion / chunker.go
← Back to directory
263 lines · 6.6 KB
Raw
1package evasion
2
3import (
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.
17type 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
30var chunkBufferPools sync.Map // map[int]*sync.Pool
31
32func 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.
47func 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
93func (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.
98func (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).
104func (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.
153func (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
162func 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
212func 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}