mirror of https://github.com/infosecn1nja/C3.git
Add backend support for shellcode generation using Donut
parent
08c3087aa7
commit
69fa74bae6
|
@ -0,0 +1,209 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// https://github.com/TheWover/donut/blob/master/lib/donut.h
|
||||
|
||||
namespace MWR.C3.WebController.Comms
|
||||
{
|
||||
public class DonutLibrary
|
||||
{
|
||||
public enum ErrorCode
|
||||
{
|
||||
SUCCESS = 0,
|
||||
FILE_NOT_FOUND = 1,
|
||||
FILE_EMPTY = 2,
|
||||
FILE_ACCESS = 3,
|
||||
FILE_INVALID = 4,
|
||||
NET_PARAMS = 5,
|
||||
NO_MEMORY = 6,
|
||||
INVALID_ARCH = 7,
|
||||
INVALID_URL = 8,
|
||||
URL_LENGTH = 9,
|
||||
INVALID_PARAMETER = 10,
|
||||
RANDOM = 11,
|
||||
DLL_FUNCTION = 12,
|
||||
ARCH_MISMATCH = 13,
|
||||
DLL_PARAM = 14,
|
||||
BYPASS_INVALID = 15,
|
||||
NORELOC = 16,
|
||||
INVALID_ENCODING = 17,
|
||||
INVALID_ENGINE = 18,
|
||||
COMPRESSION = 19,
|
||||
INVALID_ENTROPY = 20,
|
||||
}
|
||||
|
||||
// target architecture
|
||||
public enum Architecture
|
||||
{
|
||||
ANY = -1, // just for vbs,js and xsl files
|
||||
X86 = 1, // x86
|
||||
X64 = 2, // AMD64
|
||||
X84 = 3, // AMD64 + x86
|
||||
}
|
||||
|
||||
// module type
|
||||
public enum ModuleType
|
||||
{
|
||||
NET_DLL = 1, // .NET DLL. Requires class and method
|
||||
NET_EXE = 2, // .NET EXE. Executes Main if no class and method provided
|
||||
DLL = 3, // Unmanaged DLL, function is optional
|
||||
EXE = 4, // Unmanaged EXE
|
||||
VBS = 5, // VBScript
|
||||
JS = 6, // JavaScript or JScript
|
||||
}
|
||||
|
||||
// format type
|
||||
public enum OutputFormat
|
||||
{
|
||||
BINARY = 1,
|
||||
BASE64 = 2,
|
||||
RUBY = 3,
|
||||
C = 4,
|
||||
PYTHON = 5,
|
||||
POWERSHELL = 6,
|
||||
CSHARP = 7,
|
||||
HEX = 8,
|
||||
}
|
||||
|
||||
// compression engine
|
||||
public enum ComperssionEngine
|
||||
{
|
||||
NONE = 1,
|
||||
LZNT1 = 2, // COMPRESSION_FORMAT_LZNT1
|
||||
XPRESS = 3, // COMPRESSION_FORMAT_XPRESS
|
||||
XPRESS_HUFF = 4, // COMPRESSION_FORMAT_XPRESS_HUFF
|
||||
}
|
||||
|
||||
// entropy level
|
||||
public enum EntropyLevel
|
||||
{
|
||||
NONE = 1, // don't use any entropy
|
||||
RANDOM = 2, // use random names
|
||||
DEFAULT = 3, // use random names + symmetric encryption
|
||||
}
|
||||
|
||||
// misc options
|
||||
public enum ExitOpt
|
||||
{
|
||||
EXIT_THREAD = 1, // return to the caller which calls RtlExitUserThread
|
||||
EXIT_PROCESS = 2, // call RtlExitUserProcess to terminate host process
|
||||
}
|
||||
// instance type
|
||||
public enum InstanceType
|
||||
{
|
||||
PIC = 1, // Self-contained
|
||||
URL = 2, // Download from remote server
|
||||
}
|
||||
|
||||
// AMSI/WLDP options
|
||||
public enum AmsiWldpBypass
|
||||
{
|
||||
NONE = 1, // Disables bypassing AMSI/WDLP
|
||||
ABORT = 2, // If bypassing AMSI/WLDP fails, the loader stops running
|
||||
CONTINUE = 3, // If bypassing AMSI/WLDP fails, the loader continues running
|
||||
}
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public const int DONUT_MAX_NAME = 256; // maximum length of string for domain, class, method and parameter names
|
||||
public const int DONUT_MAX_DLL = 8; // maximum number of DLL supported by instance
|
||||
public const int DONUT_MAX_URL = 256;
|
||||
public const int DONUT_MAX_MODNAME = 8;
|
||||
public const int DONUT_SIG_LEN = 8; // 64-bit string to verify decryption ok
|
||||
public const int DONUT_VER_LEN = 32;
|
||||
public const int DONUT_DOMAIN_LEN = 8;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0)]
|
||||
public struct DonutConfig
|
||||
{
|
||||
public int arch;
|
||||
public int bypass;
|
||||
public int compress; // engine to use when compressing file via RtlCompressBuffer
|
||||
public int entropy; // entropy/encryption level
|
||||
public int fork; // fork/create a new thread for the loader
|
||||
public int format; // output format for loader
|
||||
public int exit_opt; // return to caller or invoke RtlExitUserProcess to terminate the host process
|
||||
public int thread; // run entrypoint of unmanaged EXE as a thread. attempts to intercept calls to exit-related API
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string input;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string output;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string runtime;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string domain;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string cls;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string method;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string param;
|
||||
public int ansi;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string url;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.DONUT_MAX_NAME)]
|
||||
public string modname;
|
||||
public int mod_type;
|
||||
public int mod_len;
|
||||
public IntPtr mod; // points to DONUT_MODULE
|
||||
// DONUT_INSTANCE
|
||||
public int inst_type; // DONUT_INSTANCE_PIC or DONUT_INSTANCE_URL
|
||||
public int inst_len; // size of DONUT_INSTANCE
|
||||
public IntPtr inst; // points to DONUT_INSTANCE
|
||||
public int pic_len; // size of loader/shellcode
|
||||
public IntPtr pic; // points to loader/shellcode
|
||||
}
|
||||
|
||||
public static byte[] GenerateShellcode(DonutConfig config)
|
||||
{
|
||||
var buffer = DonutCreate(config);
|
||||
DonutDelete(config);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Use DllImport to import the Win32 MessageBox function.
|
||||
[DllImport("donut")]
|
||||
private static extern int DonutCreate(ref DonutConfig donutConfig);
|
||||
|
||||
public class DonutException : Exception
|
||||
{
|
||||
public DonutException(string message) : base(message) { }
|
||||
}
|
||||
|
||||
[DllImport("donut")]
|
||||
private static extern int DonutDelete(ref DonutConfig donutConfig);
|
||||
|
||||
[DllImport("donut")]
|
||||
private static extern IntPtr DonutError(int errorcode);
|
||||
|
||||
static string GetDonutError(ErrorCode errorcode)
|
||||
{
|
||||
IntPtr ptr = DonutError((int)errorcode);
|
||||
return Marshal.PtrToStringAnsi(ptr);
|
||||
}
|
||||
|
||||
private static byte[] DonutCreate(DonutConfig config)
|
||||
{
|
||||
var createErrorCode = (ErrorCode)DonutCreate(ref config);
|
||||
if (createErrorCode != ErrorCode.SUCCESS)
|
||||
{
|
||||
var errorMessage = GetDonutError(createErrorCode);
|
||||
throw new DonutException($"Donut error: {errorMessage}");
|
||||
}
|
||||
var buffer = new byte[config.pic_len];
|
||||
Marshal.Copy(config.pic, buffer, 0, config.pic_len);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static void DonutDelete(DonutConfig config)
|
||||
{
|
||||
var delErrorCode = (ErrorCode)DonutDelete(ref config);
|
||||
if (delErrorCode != ErrorCode.SUCCESS)
|
||||
{
|
||||
var errorMessage = GetDonutError(delErrorCode);
|
||||
throw new DonutException($"Donut error: {errorMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MWR.C3.WebController.Models;
|
||||
using MWR.C3.WebController.RandomExtentions;
|
||||
|
||||
namespace MWR.C3.WebController.Comms
|
||||
{
|
||||
public class DonutService : IDonutService
|
||||
{
|
||||
public DonutService(IOptions<DonutServiceOptions> options)
|
||||
{
|
||||
m_Options = options.Value;
|
||||
m_TempPath = options.Value.Tempdir ??
|
||||
Environment.GetEnvironmentVariable("TEMPDIR") ??
|
||||
Environment.GetEnvironmentVariable("TEMP") ??
|
||||
".";
|
||||
}
|
||||
|
||||
private DonutServiceOptions m_Options;
|
||||
private string m_TempPath;
|
||||
|
||||
public byte[] GenerateShellcode(byte[] payload, DonutRequest request, Build.Architecture arch, Build.BinaryType binaryType)
|
||||
{
|
||||
if (!m_Options.Enable)
|
||||
throw new Exception("Donut service disabled");
|
||||
|
||||
// donut api requires files
|
||||
var rand = new Random();
|
||||
var tmpFilename = rand.NextString(16);
|
||||
var tmpPayloadFile = Path.Combine(m_TempPath, tmpFilename + ".exe");
|
||||
var tmpDonutFile = Path.Combine(m_TempPath, tmpFilename + ".donut");
|
||||
|
||||
WriteToFile(payload, tmpPayloadFile);
|
||||
|
||||
var config = new DonutLibrary.DonutConfig
|
||||
{
|
||||
arch = (int)(arch == Build.Architecture.X64 ? DonutLibrary.Architecture.X64 : DonutLibrary.Architecture.X86),
|
||||
mod_type = (int)(binaryType == Build.BinaryType.Exe ? DonutLibrary.ModuleType.EXE : DonutLibrary.ModuleType.DLL),
|
||||
format = (int)request.format,
|
||||
compress = (int)request.compress,
|
||||
entropy = (int)request.entropy,
|
||||
inst_type = (int)DonutLibrary.InstanceType.PIC,
|
||||
input = tmpPayloadFile,
|
||||
output = tmpDonutFile,
|
||||
bypass = (int)request.bypass,
|
||||
inst_len = 0
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
DonutLibrary.GenerateShellcode(config);
|
||||
return File.ReadAllBytes(tmpDonutFile);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CleanupFile(tmpPayloadFile);
|
||||
CleanupFile(tmpDonutFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteToFile(byte[] payload, string filename)
|
||||
{
|
||||
using (var binWriter = new BinaryWriter(File.Open(filename, FileMode.Create)))
|
||||
{
|
||||
binWriter.Write(payload);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CleanupFile(string filename)
|
||||
{
|
||||
if (File.Exists(filename))
|
||||
File.Delete(filename);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using MWR.C3.WebController.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MWR.C3.WebController.Comms
|
||||
{
|
||||
public interface IDonutService
|
||||
{
|
||||
byte[] GenerateShellcode(byte[] payload, DonutRequest request, Build.Architecture arch, Build.BinaryType binaryType);
|
||||
}
|
||||
|
||||
public class DonutServiceOptions
|
||||
{
|
||||
public bool Enable { get; set; }
|
||||
public string Tempdir { get; set; }
|
||||
}
|
||||
}
|
|
@ -47,8 +47,11 @@ namespace MWR.C3.WebController.Controllers
|
|||
}
|
||||
|
||||
[HttpPost("customize")]
|
||||
public async Task<IActionResult> Customize([FromBody]RelayBuildRequest request, [FromServices] ICustomizer customizer)
|
||||
public async Task<IActionResult> Customize([FromBody]RelayBuildRequest request, [FromServices] ICustomizer customizer, [FromServices] IDonutService donut)
|
||||
{
|
||||
if (request is null)
|
||||
return BadRequest("Failed to read RelayBuildRequest");
|
||||
|
||||
RelayBuild newBuild = null;
|
||||
System.Action cleanupBuild = () =>
|
||||
{
|
||||
|
@ -73,8 +76,10 @@ namespace MWR.C3.WebController.Controllers
|
|||
context.SaveChanges();
|
||||
}
|
||||
output = await customizer.CustomizeNodeRelay(newBuild);
|
||||
if (request.Donut != null)
|
||||
output = donut.GenerateShellcode(output, request.Donut, request.Architecture, request.Type);
|
||||
var buildName = String.IsNullOrEmpty(newBuild.Name) ? "" : $"_{newBuild.Name}";
|
||||
return File(output, "application/octet-stream", $"Relay_{newBuild.Arch.ToString().ToLower()}_{newBuild.BuildId:x}{buildName}.{newBuild.Type.ToString().ToLower()}");
|
||||
return File(output, "application/octet-stream", $"Relay_{newBuild.Arch.ToString().ToLower()}_{newBuild.BuildId:x}{buildName}.{GetBuildExtention(request)}");
|
||||
}
|
||||
catch (TimeoutException e)
|
||||
{
|
||||
|
@ -95,7 +100,7 @@ namespace MWR.C3.WebController.Controllers
|
|||
catch (Exception e)
|
||||
{
|
||||
cleanupBuild();
|
||||
return StatusCode(StatusCodes.Status500InternalServerError , $"Unknown error. {e.Message}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, $"Unknown error. {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,5 +122,22 @@ namespace MWR.C3.WebController.Controllers
|
|||
ParentGatwayAgentId = gatewayAgentId,
|
||||
};
|
||||
}
|
||||
|
||||
private string GetBuildExtention(RelayBuildRequest request)
|
||||
{
|
||||
switch (request.Donut?.format)
|
||||
{
|
||||
case DonutLibrary.OutputFormat.BINARY: return "bin";
|
||||
case DonutLibrary.OutputFormat.BASE64: return "b64";
|
||||
case DonutLibrary.OutputFormat.RUBY: return "rb";
|
||||
case DonutLibrary.OutputFormat.C: return "c";
|
||||
case DonutLibrary.OutputFormat.PYTHON: return "py";
|
||||
case DonutLibrary.OutputFormat.POWERSHELL: return "ps1";
|
||||
case DonutLibrary.OutputFormat.CSHARP: return "cs";
|
||||
case DonutLibrary.OutputFormat.HEX: return "hex";
|
||||
case null: return request.Type.ToString().ToLower();
|
||||
default: throw new ArgumentException("Unrecognized output format");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace MWR.C3.WebController.RandomExtentions
|
||||
{
|
||||
|
@ -24,5 +25,12 @@ namespace MWR.C3.WebController.RandomExtentions
|
|||
self.NextBytes(buffer);
|
||||
return BitConverter.ToUInt64(buffer);
|
||||
}
|
||||
|
||||
public static string NextString(this Random self, int length)
|
||||
{
|
||||
const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[self.Next(s.Length)]).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,5 +22,34 @@ namespace MWR.C3.WebController.Models
|
|||
public string Name { get; set; }
|
||||
|
||||
public JArray StartupCommands { get; set; }
|
||||
|
||||
public DonutRequest Donut { get; set; }
|
||||
}
|
||||
|
||||
public class DonutRequest
|
||||
{
|
||||
public DonutRequest()
|
||||
{
|
||||
format = DonutLibrary.OutputFormat.BINARY;
|
||||
compress = DonutLibrary.ComperssionEngine.NONE;
|
||||
entropy = DonutLibrary.EntropyLevel.DEFAULT;
|
||||
exitOpt = DonutLibrary.ExitOpt.EXIT_THREAD;
|
||||
bypass = DonutLibrary.AmsiWldpBypass.NONE;
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DonutLibrary.OutputFormat format { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DonutLibrary.ComperssionEngine compress { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DonutLibrary.EntropyLevel entropy { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DonutLibrary.ExitOpt exitOpt { get; set; }
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public DonutLibrary.AmsiWldpBypass bypass { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ namespace MWR.C3.WebController
|
|||
{
|
||||
c.SwaggerDoc("v1", new Info { Title = "C3 API", Version = "v1" });
|
||||
});
|
||||
services.Configure<DonutServiceOptions>(Configuration.GetSection("Donut"));
|
||||
services.AddTransient<IDonutService, DonutService>();
|
||||
services.AddTransient<ICustomizer, Customizer>();
|
||||
services.AddScoped<GatewayResponseProcessor>();
|
||||
services.AddDbContext<C3WebAPIContext>(options => options.UseSqlite("Data Source=C3API.db"));
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DonutRuntimes" Version="0.9.3-7ae213b.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.0" />
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Console": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
"Customizer": {
|
||||
"payloadTemplateDir": "../Bin/"
|
||||
},
|
||||
"ApiBridge": {
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 2323
|
||||
},
|
||||
"Donut": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"Customizer": {
|
||||
"payloadTemplateDir": "../Bin/"
|
||||
},
|
||||
"ApiBridge": {
|
||||
"Host": "127.0.0.1",
|
||||
"Port": 2323
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue