10 Commits
28 changed files with 1322 additions and 144 deletions
+63
View File
@@ -0,0 +1,63 @@
name: Create Release
on:
workflow_dispatch:
push:
tags:
- "v*.*.*"
env:
SOLUTION_FILE_PATH: .
BUILD_CONFIGURATION: Release
BUILD_OUTPUT_PATH: Build
permissions:
contents: write
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.CODECEPTICON_TOKEN }}
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v2
- name: Restore NuGet packages
working-directory: ${{env.GITHUB_WORKSPACE}}
run: nuget restore ${{env.SOLUTION_FILE_PATH}}
- name: Build
working-directory: ${{env.GITHUB_WORKSPACE}}
run: msbuild /m /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:OutputPath=../${{env.BUILD_OUTPUT_PATH}} ${{env.SOLUTION_FILE_PATH}}
- name: Delete .config file
run: Remove-Item -Path ${{env.BUILD_OUTPUT_PATH}}/Codecepticon.exe.config
- name: Set release filename
run: echo "RELEASE_FILENAME=Codecepticon-${{ github.ref_name }}.zip" >> $env:GITHUB_ENV
- name: Compress release
run: Compress-Archive -Path ${{env.BUILD_OUTPUT_PATH}}/* -Destination ${{ env.RELEASE_FILENAME }}
- name: Get file hash
run: |
$hash = Get-FileHash -Algorithm SHA256 ${{ env.RELEASE_FILENAME }} | select -exp Hash
echo "FILE_HASH=$hash" >> $env:GITHUB_ENV
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: ${{ env.RELEASE_FILENAME }}
name: Codecepticon-${{ github.ref_name }}
body: |
Archive SHA256: `${{ env.FILE_HASH }}`
For more details see `CHANGELOG.md`.
token: ${{ secrets.CODECEPTICON_TOKEN }}
+16
View File
@@ -1,5 +1,21 @@
# Codecepticon Changelog
## v1.2.3
* `[Update]` Update copyright & links, added workflows to repo.
## v1.2.2
* `[Update]` Removed `BouncyCastle` dependency, now certificates are generated using native .NET functionality.
## v1.2.1
* `[New]` C#: Added support for renaming Structs.
## v1.2.0
* `[Update]` Removed the `signtool.exe` dependency and are now natively signing executables. The code was taken & customised from https://github.com/Danielku15/SigningServer, under MIT License - original author is Danielku15.
## v1.1.0
* `[New]` Module: Implement the `sign` module, to enable creating self-signed certificates and using any given certificate to sign an executable. This functionality is using `signtool.exe`.
+12 -12
View File
@@ -4,42 +4,43 @@
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<SatelliteResourceLanguages>none</SatelliteResourceLanguages>
<PlatformTarget>x86</PlatformTarget>
<PlatformTarget>x64</PlatformTarget>
<Platforms>AnyCPU;x86;x64</Platforms>
<LangVersion>9.0</LangVersion>
<PackageId>Codecepticon</PackageId>
<Title>Codecepticon</Title>
<Version>1.1.0</Version>
<Version>1.2.3</Version>
<Authors>Pavel Tsakalidis</Authors>
<Company>Accenture Security</Company>
<Company></Company>
<Product>Codecepticon</Product>
<Description>Offensive Security Code Obfuscator</Description>
<PackageProjectUrl>https://github.com/Accenture/Codecepticon</PackageProjectUrl>
<PackageProjectUrl>https://github.com/sadreck/Codecepticon</PackageProjectUrl>
<AssemblyVersion></AssemblyVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugType>full</DebugType>
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<DebugType>full</DebugType>
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>full</DebugType>
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DebugType>full</DebugType>
<DebugType>none</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<DebugType>full</DebugType>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
@@ -56,7 +57,6 @@
<ItemGroup>
<PackageReference Include="Antlr4.Runtime.Standard" Version="4.9.2" />
<PackageReference Include="BouncyCastle" Version="1.8.9" />
<PackageReference Include="Microsoft.Build" Version="17.3.1" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.5.3" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
@@ -64,7 +64,7 @@
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.9.0" />
<PackageReference Include="Microsoft.PowerShell.5.1.ReferenceAssemblies" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="System.Collections.Immutable" Version="6.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
+4
View File
@@ -8,8 +8,11 @@ Usage: Codecepticon.exe --module [csharp|cs] [OPTIONS]...
--build Whether to build the project upon completion.
--build-path [path] Path to a directory where the solution will be compiled to. Only works with --build.
--profile [name] Name of an application-specific profile to use. Supported profiles are:
- certify
- rubeus
- seatbelt
- sharpchrome
- sharpdpapi
- sharphound
- sharpview
@@ -22,6 +25,7 @@ Usage: Codecepticon.exe --module [csharp|cs] [OPTIONS]...
- p Properties
- a Parameters
- v Variables
- s Structs
- o Command Line
%%_SHARED_%%
+2 -2
View File
@@ -1,2 +1,2 @@
Codecepticon v%%_VERSION_%% [ Accenture Security ]
- For more information visit https://github.com/Accenture/Codecepticon
Codecepticon v%%_VERSION_%% [ Pavel Tsakalidis ]
- For more information visit https://github.com/sadreck/Codecepticon
+5 -3
View File
@@ -16,6 +16,8 @@ Usage: Codecepticon.exe --module sign [OPTIONS]...
--overwrite When used with '--action cert' this will indicate whether to rewrite the target file
if it already exists.
--password Password for the pfx file (either to save or load, depending on the --action)
--signtool Only used with '--action sign' to indicate the location of signtool.exe on the system.
If this argument is not passed, Codecepticon will try to find it automatically.
--path [executable] Location of the executable file to be signed.
--path [executable] Location of the executable file to be signed.
--algorithm When used with '--action sign', this argument will specify the signature algorithm.
This can be one of: MD5, SHA1, SHA256, SHA384, SHA512.
--timestamp When used with '--action sign', this is where the Timestamp Server is specified.
For example 'http://timestamp.sectigo.com' or 'http://timestamp.digicert.com'.
@@ -112,6 +112,7 @@ namespace Codecepticon.Modules.CSharp
Logger.Verbose($"\tProperties:\t{DataCollector.AllProperties.Count}");
Logger.Verbose($"\tParameters:\t{DataCollector.AllParameters.Count}");
Logger.Verbose($"\tVariables:\t{DataCollector.AllVariables.Count}");
Logger.Verbose($"\tStructs:\t{DataCollector.AllStructs.Count}");
Logger.Info("Generating mappings...");
if (await GenerateMappings() == false)
@@ -190,6 +191,7 @@ namespace Codecepticon.Modules.CSharp
await DataCollector.CollectProperties(solution, project.Name, document.Name);
await DataCollector.CollectVariables(solution, project.Name, document.Name);
await DataCollector.CollectParameters(solution, project.Name, document.Name);
await DataCollector.CollectStructs(solution, project.Name, document.Name);
}
}
@@ -320,6 +322,21 @@ namespace Codecepticon.Modules.CSharp
}
}
if (CommandLineData.CSharp.Rename.Structs)
{
Logger.Verbose("Creating mappings for structs");
foreach (string name in DataCollector.AllStructs)
{
newName = await GenerateName(CommandLineData.Global.NameGenerator, DataCollector.IsMappingUnique);
if (newName.Length == 0)
{
return false;
}
DataCollector.Mapping.Structs.Add(name, newName);
}
}
return true;
}
@@ -587,6 +604,28 @@ namespace Codecepticon.Modules.CSharp
}
}
if (CommandLineData.CSharp.Rename.Structs)
{
Logger.Info($"Renaming {DataCollector.Mapping.Structs.Count} structs...", CommandLineData.Global.Project.Debug);
c = 0;
foreach (Document document in project.Documents)
{
if (CommandLineData.Global.Project.Debug)
{
Logger.Debug($"Renaming structs in document {document.FilePath}");
}
else if (++c % step == 0)
{
Logger.Verbose(".", false, false);
}
solution = await dataRenamer.RenameStructs(solution, project.Name, document.Name);
}
if (!CommandLineData.Global.Project.Debug)
{
Logger.Info("", true, false);
}
}
Logger.Debug($"Setting ProjectGuid to {{{CommandLineData.Global.Project.Guid.ToString().ToUpper()}}}");
VisualStudioManager.SetProjectConfiguration(solution, new Dictionary<string, string>
{
@@ -129,6 +129,9 @@ namespace Codecepticon.Modules.CSharp.CommandLine
case "o":
CommandLineData.CSharp.Rename.CommandLine = true;
break;
case "s":
CommandLineData.CSharp.Rename.Structs = true;
break;
}
}
break;
@@ -145,6 +148,7 @@ namespace Codecepticon.Modules.CSharp.CommandLine
CommandLineData.CSharp.Rename.Variables = value;
CommandLineData.CSharp.Rename.Parameters = value;
CommandLineData.CSharp.Rename.CommandLine = value;
CommandLineData.CSharp.Rename.Structs = value;
}
}
}
+28 -1
View File
@@ -23,6 +23,7 @@ namespace Codecepticon.Modules.CSharp
public static List<string> AllProperties = new List<string>();
public static List<string> AllVariables = new List<string>();
public static List<string> AllParameters = new List<string>();
public static List<string> AllStructs = new List<string>();
public struct CommandLine
{
@@ -41,6 +42,7 @@ namespace Codecepticon.Modules.CSharp
public Dictionary<string, string> Properties;
public Dictionary<string, string> Variables;
public Dictionary<string, string> Parameters;
public Dictionary<string, string> Structs;
public Dictionary<string, CommandLine> CommandLine;
}
@@ -52,7 +54,8 @@ namespace Codecepticon.Modules.CSharp
Enums = new Dictionary<string, string>(),
Properties = new Dictionary<string, string>(),
Variables = new Dictionary<string, string>(),
Parameters = new Dictionary<string, string>()
Parameters = new Dictionary<string, string>(),
Structs = new Dictionary<string, string>(),
};
public static bool IsMappingUnique(string name)
@@ -93,6 +96,12 @@ namespace Codecepticon.Modules.CSharp
return false;
}
item = Mapping.Structs.FirstOrDefault(s => s.Value == name).Key;
if (item != null)
{
return false;
}
item = Mapping.Parameters.FirstOrDefault(s => s.Value == name).Key;
return item == null;
}
@@ -195,6 +204,24 @@ namespace Codecepticon.Modules.CSharp
}
}
public static async Task CollectStructs(Solution solution, string projectName, string documentName)
{
Document document = VisualStudioManager.GetDocumentByName(solution, projectName, documentName);
SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
var structs = syntaxTree.GetRoot().DescendantNodes().OfType<StructDeclarationSyntax>();
foreach (var s in structs)
{
string name = s.Identifier.ToString();
if (AllStructs.Contains(name))
{
continue;
}
AllStructs.Add(name);
}
}
public static async Task CollectProperties(Solution solution, string projectName, string documentName)
{
Document document = VisualStudioManager.GetDocumentByName(solution, projectName, documentName);
@@ -190,5 +190,26 @@ namespace Codecepticon.Modules.CSharp
return solution;
}
public async Task<Solution> RenameStructs(Solution solution, string projectName, string documentName)
{
Document document = VisualStudioManager.GetDocumentByName(solution, projectName, documentName);
SyntaxTree syntaxTree = await document.GetSyntaxTreeAsync();
var structs = syntaxTree.GetRoot().DescendantNodes().OfType<StructDeclarationSyntax>();
foreach (var s in structs)
{
string name = s.Identifier.ToString();
if (!DataCollector.Mapping.Structs.ContainsKey(name))
{
Logger.Debug($"Struct does not exist in mapping: {name}");
continue;
}
solution = await RenameCode<StructDeclarationSyntax>(solution, projectName, documentName, name, DataCollector.Mapping.Structs[name]);
}
return solution;
}
}
}
+6
View File
@@ -23,6 +23,7 @@ namespace Codecepticon.Modules.CSharp
["properties"] = "",
["variables"] = "",
["parameters"] = "",
["structs"] = "",
["cmdline"] = "",
};
@@ -61,6 +62,11 @@ namespace Codecepticon.Modules.CSharp
data["parameters"] = ConcatData(DataCollector.Mapping.Parameters);
}
if (CommandLineData.CSharp.Rename.Structs)
{
data["structs"] = ConcatData(DataCollector.Mapping.Structs);
}
if (CommandLineData.CSharp.Rename.CommandLine)
{
data["cmdline"] = DataCollector.ConcatCommandLineData(DataCollector.Mapping.CommandLine);
+3 -1
View File
@@ -67,6 +67,7 @@ namespace Codecepticon.CommandLine
public bool Variables;
public bool Parameters;
public bool CommandLine;
public bool Structs;
}
public struct RewriteTemplateStruct
@@ -155,7 +156,8 @@ namespace Codecepticon.CommandLine
public struct SignSettings
{
public SignNewCertificate NewCertificate;
public string SignTool;
public string TimestampServer;
public string SignatureAlgorithm;
}
public struct RenameGeneratorStruct
+35 -67
View File
@@ -1,101 +1,69 @@
using Codecepticon.Utils;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace Codecepticon.Modules.Sign
{
class CertificateManager
{
private const string SignatureAlgorithm = "SHA256WithRSA";
private const int KeyLength = 2048;
public bool GenerateCertificate(string Subject, string Issuer, DateTime NotBefore, DateTime NotAfter, string Password, string PfxOutput)
{
// https://mcse.cloud/create-a-self-signed-certificate-with-bouncy-castle-and-c/
Logger.Debug("Initialising random generators and certificate generators...");
SecureRandom secureRandom = new SecureRandom(new CryptoApiRandomGenerator());
// https://stackoverflow.com/a/48210587/2445959
RSA keyPair = RSA.Create(KeyLength);
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
Logger.Verbose("Generating issuer certificate...");
Logger.Verbose("Issuer is " + Issuer);
X509Certificate2 certificateIssuer = GenerateIssuerCertificate(Issuer, NotBefore, NotAfter);
// Create and set serial number.
Logger.Verbose("Setting up certificate properties...");
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber);
Logger.Info("Generating signing certificate...");
Logger.Verbose("Subject is " + Subject);
CertificateRequest certRequest = new(Subject, keyPair, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
//certRequest.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.8") }, true));
certRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(certRequest.PublicKey, false));
X509Certificate2 cert = certRequest.Create(certificateIssuer, NotBefore, NotAfter, new byte[] { 1, 2, 3, 4 });
Logger.Debug("Creating Subject: " + Subject);
X509Name subjectDN = new X509Name(true, Subject);
certificateGenerator.SetSubjectDN(subjectDN);
// Add the private key back to the certificate.
X509Certificate2 certificate = cert.CopyWithPrivateKey(keyPair);
Logger.Debug("Creating Issuer: " + Issuer);
X509Name issuerDN = new X509Name(true, Issuer);
certificateGenerator.SetIssuerDN(issuerDN);
Logger.Debug("Setting NotBefore: " + NotBefore);
certificateGenerator.SetNotBefore(NotBefore);
Logger.Debug("Setting NotAfter: " + NotAfter);
certificateGenerator.SetNotAfter(NotAfter);
KeyGenerationParameters keyGeneration = new KeyGenerationParameters(secureRandom, KeyLength);
// Create RSA key.
Logger.Verbose("Generating RSA keypair...");
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGeneration);
AsymmetricCipherKeyPair keyPair = keyPairGenerator.GenerateKeyPair();
// Add the public/private keys to the certificate generator.
Logger.Debug("Setting public key...");
certificateGenerator.SetPublicKey(keyPair.Public);
ISignatureFactory signatureFactory = new Asn1SignatureFactory(SignatureAlgorithm, keyPair.Private, secureRandom);
X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
Logger.Debug("Creating keystore...");
Pkcs12Store keyStore = new Pkcs12Store();
X509CertificateEntry certificateEntry = new X509CertificateEntry(certificate);
keyStore.SetCertificateEntry(certificate.SubjectDN.ToString(), certificateEntry);
keyStore.SetKeyEntry(certificate.SubjectDN.ToString(), new AsymmetricKeyEntry(keyPair.Private), new[] { certificateEntry });
// Convert to .NET Certificate.
Logger.Debug("Converting to a .NET certificate...");
MemoryStream stream = new MemoryStream();
keyStore.Save(stream, Password.ToCharArray(), secureRandom);
System.Security.Cryptography.X509Certificates.X509Certificate2 netCertificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(stream.ToArray(), Password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.PersistKeySet | System.Security.Cryptography.X509Certificates.X509KeyStorageFlags.Exportable);
Logger.Verbose("Writing certificate to " + PfxOutput);
File.WriteAllBytes(PfxOutput, netCertificate.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pfx, Password));
Logger.Info("Exporting certificate to file...");
File.WriteAllBytes(PfxOutput, certificate.Export(X509ContentType.Pfx, Password));
return true;
}
private X509Certificate2 GenerateIssuerCertificate(string Issuer, DateTime NotBefore, DateTime NotAfter)
{
RSA keyPair = RSA.Create(KeyLength);
CertificateRequest issuerRequest = new(Issuer, keyPair, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
issuerRequest.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
issuerRequest.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(issuerRequest.PublicKey, false));
return issuerRequest.CreateSelfSigned(NotBefore, NotAfter);
}
public bool CheckPfxPassword(string pfxFile, string password)
{
try
{
Pkcs12Store keyStore = new Pkcs12Store(File.OpenRead(pfxFile), password.ToCharArray());
} catch (Exception e)
X509Certificate2 certificate = new(pfxFile, password);
}
catch (Exception e)
{
return false;
}
return true;
}
public System.Security.Cryptography.X509Certificates.X509Certificate GetCertificateFromFile(string signedFile)
public X509Certificate GetCertificateFromFile(string signedFile)
{
return System.Security.Cryptography.X509Certificates.X509Certificate2.CreateFromSignedFile(signedFile);
return X509Certificate.CreateFromSignedFile(signedFile);
}
}
}
@@ -21,7 +21,8 @@ namespace Codecepticon.Modules.Sign.CommandLine
{ "password", "" },
{ "pfx-file", "" },
{ "overwrite", "switch" },
{ "signtool", "" }
{ "algorithm", "" },
{ "timestamp", "" }
};
MergeArguments();
}
@@ -67,10 +68,10 @@ namespace Codecepticon.Modules.Sign.CommandLine
}
break;
case "password":
CommandLineData.Sign.NewCertificate.Password= argument.Value;
CommandLineData.Sign.NewCertificate.Password = argument.Value;
break;
case "pfx-file":
CommandLineData.Sign.NewCertificate.PfxFile= argument.Value;
CommandLineData.Sign.NewCertificate.PfxFile = argument.Value;
break;
case "overwrite":
if (argument.Value.ToLower() != "false")
@@ -78,8 +79,11 @@ namespace Codecepticon.Modules.Sign.CommandLine
CommandLineData.Sign.NewCertificate.Overwrite = (argument.Value.Length > 0);
}
break;
case "signtool":
CommandLineData.Sign.SignTool = argument.Value;
case "algorithm":
CommandLineData.Sign.SignatureAlgorithm = argument.Value.ToUpper();
break;
case "timestamp":
CommandLineData.Sign.TimestampServer = argument.Value;
break;
}
}
@@ -133,16 +133,6 @@ namespace Codecepticon.Modules.Sign.CommandLine
return false;
}
if (String.IsNullOrEmpty(CommandLineData.Sign.SignTool))
{
Logger.Info("SignTool path is empty - will try to find signtool.exe");
}
else if (!File.Exists(CommandLineData.Sign.SignTool))
{
Logger.Error("Path for signtool.exe does not exist: " + CommandLineData.Sign.SignTool);
return false;
}
// Validate the PFX Password.
if (!certificateManager.CheckPfxPassword(CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password))
{
@@ -150,6 +140,22 @@ namespace Codecepticon.Modules.Sign.CommandLine
return false;
}
if (String.IsNullOrEmpty(CommandLineData.Sign.SignatureAlgorithm))
{
Logger.Error("Signature Algorithm not set");
return false;
}
else if (!IsValidSignatureAlgorithm(CommandLineData.Sign.SignatureAlgorithm))
{
Logger.Error("Invalid signature algorithm selected");
return false;
}
if (String.IsNullOrEmpty(CommandLineData.Sign.TimestampServer))
{
CommandLineData.Sign.TimestampServer = ""; // Make sure it's not null.
}
break;
default:
Logger.Error("Invalid action: " + CommandLineData.Global.Action.ToString());
@@ -158,6 +164,12 @@ namespace Codecepticon.Modules.Sign.CommandLine
return true;
}
protected bool IsValidSignatureAlgorithm(string algorithm)
{
List<string> validAlgorithms = new() { "MD5", "SHA1", "SHA256", "SHA384", "SHA512" };
return validAlgorithms.Contains(algorithm);
}
protected string FixDN(string dn)
{
// BouncyCastle does not recognise S=XXX within an X509Name, and it has to be in the form of ST=XXX.
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
public interface ISigningTool
{
/// <summary>
/// Gets the name of the format the signing tool offers to sign.
/// </summary>
string FormatName { get; }
/// <summary>
/// Gets the list of hash algorithms supported by this signing tool.
/// </summary>
IReadOnlyList<string> SupportedHashAlgorithms { get; }
/// <summary>
/// Performs the signing of the given file through the request.
/// Might throw any exceptions describing the error during signing.
/// </summary>
/// <param name="signFileRequest">The request describing what to sign.</param>
/// <param name="cancellationToken">A token to support cancellation.</param>
/// <returns>The result of the signing operation.</returns>
SignFileResponse SignFile(SignFileRequest signFileRequest);
/// <summary>
/// Checks whether the given file is signed.
/// </summary>
/// <param name="inputFileName">The path to the file on disk.</param>
/// <param name="cancellationToken">A token to support cancellation.</param>
/// <returns>true if the file is considered signed, otherwise false.</returns>
/// <remarks>
/// Some tools might only do a very basic check and not a full validation on whether
/// all aspects of the signing are in place and valid.
/// </remarks>
bool IsFileSigned(string inputFileName);
}
}
@@ -0,0 +1,353 @@
using Codecepticon.Utils;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
/*
* This class was taken and customised from https://github.com/Danielku15/SigningServer, under MIT License.
*/
public class PortableExecutableSigningTool : ISigningTool
{
private static readonly Dictionary<string, (uint algId, string algOid, HashAlgorithmName algName)>
PeSupportedHashAlgorithms =
new(StringComparer
.OrdinalIgnoreCase)
{
["SHA1"] = (Win32SigningAPI.CALG_SHA1, Win32SigningAPI.OID_OIWSEC_SHA1, HashAlgorithmName.SHA1),
["MD5"] = (Win32SigningAPI.CALG_MD5, Win32SigningAPI.OID_RSA_MD5, HashAlgorithmName.MD5),
["SHA256"] = (Win32SigningAPI.CALG_SHA_256, Win32SigningAPI.OID_OIWSEC_SHA256, HashAlgorithmName.SHA256),
["SHA384"] = (Win32SigningAPI.CALG_SHA_384, Win32SigningAPI.OID_OIWSEC_SHA384, HashAlgorithmName.SHA384),
["SHA512"] = (Win32SigningAPI.CALG_SHA_512, Win32SigningAPI.OID_OIWSEC_SHA512, HashAlgorithmName.SHA512)
};
public virtual string FormatName => "Windows Portable Executables (PE)";
public virtual IReadOnlyList<string> SupportedHashAlgorithms => PeSupportedHashAlgorithms.Keys.ToArray();
public SignFileResponse SignFile(SignFileRequest signFileRequest)
{
var signFileResponse = new SignFileResponse();
var successResult = SignFileResponseStatus.FileSigned;
if (IsFileSigned(signFileRequest.InputFilePath))
{
if (signFileRequest.OverwriteSignature)
{
Logger.Verbose($"File {signFileRequest.InputFilePath} is already signed, removing signature");
UnsignFile(signFileRequest.InputFilePath);
successResult = SignFileResponseStatus.FileResigned;
}
else
{
Logger.Verbose($"File {signFileRequest.InputFilePath} is already signed, abort signing");
signFileResponse.Status = SignFileResponseStatus.FileAlreadySigned;
return signFileResponse;
}
}
if (!PeSupportedHashAlgorithms.TryGetValue(
signFileRequest.HashAlgorithm ?? "", out var algId))
{
algId = PeSupportedHashAlgorithms["SHA256"];
}
using var signerFileInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_FILE_INFO>(new Win32SigningAPI.SIGNER_FILE_INFO
{
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_FILE_INFO>(),
pwszFileName = signFileRequest.InputFilePath,
hFile = IntPtr.Zero
});
using var dwIndex = new UnmanagedStruct<uint>(0);
using var signerSubjectInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_SUBJECT_INFO>(
new Win32SigningAPI.SIGNER_SUBJECT_INFO
{
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_SUBJECT_INFO>(),
pdwIndex = dwIndex.Pointer,
dwSubjectChoice = Win32SigningAPI.SIGNER_SUBJECT_FILE,
union = { pSignerFileInfo = signerFileInfo.Pointer }
});
using var signerCertStoreInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_CERT_STORE_INFO>(
new Win32SigningAPI.SIGNER_CERT_STORE_INFO
{
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_CERT_STORE_INFO>(),
pSigningCert = signFileRequest.Certificate.Handle,
dwCertPolicy = Win32SigningAPI.SIGNER_CERT_POLICY_CHAIN,
hCertStore = IntPtr.Zero
});
using var signerCert = new UnmanagedStruct<Win32SigningAPI.SIGNER_CERT>(
new Win32SigningAPI.SIGNER_CERT
{
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_CERT>(),
dwCertChoice = Win32SigningAPI.SIGNER_CERT_STORE,
union = { pSpcChainInfo = signerCertStoreInfo.Pointer },
hwnd = IntPtr.Zero
});
using var signerSignatureInfo = new UnmanagedStruct<Win32SigningAPI.SIGNER_SIGNATURE_INFO>(
new Win32SigningAPI.SIGNER_SIGNATURE_INFO
{
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGNER_SIGNATURE_INFO>(),
algidHash = algId.algId,
dwAttrChoice = Win32SigningAPI.SIGNER_NO_ATTR,
union = { pAttrAuthcode = IntPtr.Zero },
psAuthenticated = IntPtr.Zero,
psUnauthenticated = IntPtr.Zero
});
var (hr, tshr) = SignAndTimestamp(
algId.algName,
algId.algOid,
signFileRequest.InputFilePath, signFileRequest.TimestampServer, signerSubjectInfo.Pointer,
signerCert.Pointer,
signerSignatureInfo.Pointer, signFileRequest.PrivateKey
);
if (hr == Win32SigningAPI.S_OK && tshr == Win32SigningAPI.S_OK)
{
Logger.Verbose($"{signFileRequest.InputFilePath} successfully signed");
signFileResponse.Status = successResult;
signFileResponse.ResultFiles = new[]
{
new SignFileResponseFileInfo(signFileRequest.OriginalFileName, signFileRequest.InputFilePath)
};
}
else if (hr != Win32SigningAPI.S_OK)
{
var exception = new Win32Exception(hr);
signFileResponse.Status = SignFileResponseStatus.FileNotSignedError;
signFileResponse.ErrorMessage = !string.IsNullOrEmpty(exception.Message)
? exception.Message
: $"signing file failed (0x{hr:x})";
if ((uint)hr == 0x8007000B)
{
signFileResponse.ErrorMessage =
$"The appxmanifest does not contain the expected publisher. Expected: <Identity ... Publisher\"{signFileRequest.Certificate.SubjectName}\" .. />.";
}
Logger.Error($"{signFileRequest.InputFilePath} signing failed {signFileResponse.ErrorMessage}");
}
else
{
var errorText = new Win32Exception(tshr).Message;
signFileResponse.Status = SignFileResponseStatus.FileNotSignedError;
signFileResponse.ErrorMessage = !string.IsNullOrEmpty(errorText)
? errorText
: $"timestamping failed (0x{hr:x})";
Logger.Error($"{signFileRequest.InputFilePath} timestamping failed {signFileResponse.ErrorMessage}");
}
return signFileResponse;
}
public bool IsFileSigned(string inputFileName)
{
using var winTrustFileInfo = new UnmanagedStruct<Win32SigningAPI.WINTRUST_FILE_INFO>(
new Win32SigningAPI.WINTRUST_FILE_INFO
{
cbStruct = (uint)Marshal.SizeOf<Win32SigningAPI.WINTRUST_FILE_INFO>(),
pcwszFilePath = inputFileName,
hFile = IntPtr.Zero,
pgKnownSubject = IntPtr.Zero
});
var winTrustData = new Win32SigningAPI.WINTRUST_DATA
{
cbStruct = (uint)Marshal.SizeOf<Win32SigningAPI.WINTRUST_DATA>(),
pPolicyCallbackData = IntPtr.Zero,
pSIPClientData = IntPtr.Zero,
dwUIChoice = Win32SigningAPI.WinTrustDataUIChoice.None,
fdwRevocationChecks = Win32SigningAPI.WinTrustDataRevocationChecks.None,
dwUnionChoice = Win32SigningAPI.WinTrustDataUnionChoice.File,
dwStateAction = Win32SigningAPI.WinTrustDataStateAction.Verify,
hWVTStateData = IntPtr.Zero,
pwszURLReference = IntPtr.Zero,
dwUIContext = 0,
union = { pFile = winTrustFileInfo.Pointer }
};
var actionId = new Guid(Win32SigningAPI.WINTRUST_ACTION_GENERIC_VERIFY_V2);
var result = Win32SigningAPI.WinVerifyTrust(IntPtr.Zero, actionId, winTrustData);
Logger.Debug($"WinVerifyTrust returned {result}");
switch (result)
{
case Win32SigningAPI.WinVerifyTrustResult.Success:
return true;
case Win32SigningAPI.WinVerifyTrustResult.FileNotSigned:
var dwLastError = (uint)Marshal.GetLastWin32Error();
switch (dwLastError)
{
case (uint)Win32SigningAPI.WinVerifyTrustResult.FileNotSigned:
return false;
case (uint)Win32SigningAPI.WinVerifyTrustResult.SubjectFormUnknown:
return true;
case (uint)Win32SigningAPI.WinVerifyTrustResult.ProviderUnknown:
return true;
default:
return false;
}
case Win32SigningAPI.WinVerifyTrustResult.UntrustedRoot:
return true;
case Win32SigningAPI.WinVerifyTrustResult.SubjectExplicitlyDistrusted:
return true;
case Win32SigningAPI.WinVerifyTrustResult.SubjectNotTrusted:
return true;
case Win32SigningAPI.WinVerifyTrustResult.LocalSecurityOption:
return true;
default:
return false;
}
}
private protected virtual (int hr, int tshr) SignAndTimestamp(
HashAlgorithmName hashAlgorithmName,
string timestampHashOid,
string inputFileName,
string timestampServer,
/*PSIGNER_SUBJECT_INFO*/IntPtr signerSubjectInfo,
/*PSIGNER_CERT*/IntPtr signerCert,
/*PSIGNER_SIGNATURE_INFO*/ IntPtr signerSignatureInfo,
AsymmetricAlgorithm privateKey)
{
Logger.Debug($"Call signing of {inputFileName}");
int SignCallback(IntPtr pCertContext, IntPtr pvExtra, uint algId, byte[] pDigestToSign, uint dwDigestToSign,
ref Win32SigningAPI.CRYPTOAPI_BLOB blob)
{
byte[] digest;
try
{
switch (privateKey)
{
case DSA dsa:
digest = dsa.CreateSignature(pDigestToSign);
break;
case ECDsa ecdsa:
digest = ecdsa.SignHash(pDigestToSign);
break;
case RSA rsa:
digest = rsa.SignHash(pDigestToSign, hashAlgorithmName, RSASignaturePadding.Pkcs1);
break;
default:
return Win32SigningAPI.E_INVALIDARG;
}
}
catch (Exception e)
{
var hr = e.HResult != 0 ? e.HResult : Win32SigningAPI.NTE_BAD_KEY;
Logger.Error("Failed to sign data reporting: " + hr);
return hr;
}
var resultPtr = Marshal.AllocHGlobal(digest.Length);
Marshal.Copy(digest, 0, resultPtr, digest.Length);
blob.pbData = resultPtr;
blob.cbData = (uint)digest.Length;
return Win32SigningAPI.S_OK;
}
Win32SigningAPI.SignCallback callbackDelegate = SignCallback;
using var unmanagedSignerParams = new UnmanagedStruct<Win32SigningAPI.SIGNER_SIGN_EX3_PARAMS>();
using var unmanagedSignInfo = new UnmanagedStruct<Win32SigningAPI.SIGN_INFO>(new Win32SigningAPI.SIGN_INFO
{
cbSize = (uint)Marshal.SizeOf<Win32SigningAPI.SIGN_INFO>(),
callback = Marshal.GetFunctionPointerForDelegate(callbackDelegate),
pvOpaque = IntPtr.Zero
});
var signerParams = new Win32SigningAPI.SIGNER_SIGN_EX3_PARAMS
{
dwFlags = Win32SigningAPI.SIGN_CALLBACK_UNDOCUMENTED,
pSubjectInfo = signerSubjectInfo,
pSigningCert = signerCert,
pSignatureInfo = signerSignatureInfo,
pProviderInfo = IntPtr.Zero,
psRequest = IntPtr.Zero,
pCryptoPolicy = IntPtr.Zero,
pSignCallback = unmanagedSignInfo.Pointer
};
unmanagedSignerParams.Fill(signerParams);
var hr = Win32SigningAPI.SignerSignEx3(
signerParams.dwFlags,
signerParams.pSubjectInfo,
signerParams.pSigningCert,
signerParams.pSignatureInfo,
signerParams.pProviderInfo,
signerParams.dwTimestampFlags,
signerParams.pszTimestampAlgorithmOid,
signerParams.pwszTimestampURL,
signerParams.psRequest,
IntPtr.Zero,
signerParams.pSignerContext,
signerParams.pCryptoPolicy,
signerParams.pSignCallback,
signerParams.pReserved
);
if (signerParams.pSignerContext != IntPtr.Zero)
{
var signerContext = new IntPtr();
Marshal.PtrToStructure(signerParams.pSignerContext, signerContext);
Win32SigningAPI.SignerFreeSignerContext(signerContext);
}
var tshr = Win32SigningAPI.S_OK;
if (hr == Win32SigningAPI.S_OK && !string.IsNullOrWhiteSpace(timestampServer))
{
Logger.Verbose($"Timestamping with url {timestampServer}");
var timestampRetries = 5;
do
{
tshr = timestampHashOid == Win32SigningAPI.OID_OIWSEC_SHA1
? Win32SigningAPI.SignerTimeStamp(signerSubjectInfo, timestampServer)
: Win32SigningAPI.SignerTimeStampEx2(
Win32SigningAPI.SIGNER_TIMESTAMP_RFC3161,
signerSubjectInfo,
timestampServer,
timestampHashOid,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);
if (tshr == Win32SigningAPI.S_OK)
{
Logger.Verbose("Timestamping succeeded");
}
else
{
Logger.Error($"Timestamping failed with {tshr}, retries: {timestampRetries}");
Thread.Sleep(1000);
}
} while (tshr != Win32SigningAPI.S_OK && (timestampRetries--) > 0);
}
return (hr, tshr);
}
public virtual void UnsignFile(string fileName)
{
using var file = new FileStream(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
// TODO: remove multiple certificates here?
if (Win32SigningAPI.ImageEnumerateCertificates(file.SafeFileHandle, Win32SigningAPI.CERT_SECTION_TYPE_ANY,
out var dwNumCerts) &&
dwNumCerts == 1)
{
Win32SigningAPI.ImageRemoveCertificate(file.SafeFileHandle, 0);
}
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
public class SignFileRequest
{
/// <summary>
/// Gets or sets the absolute path to the file being signed.
/// </summary>
public string InputFilePath { get; set; }
/// <summary>
/// Gets or sets the certificate used during the signing operation.
/// Typically embedded into the signed file (without private keys).
/// </summary>
public X509Certificate2 Certificate { get; set; }
/// <summary>
/// Gets or sets the private key used for performing the signing operations.
/// This key must match the <see cref="Certificate"/> to avoid corrupt signatures.
/// </summary>
public AsymmetricAlgorithm PrivateKey { get; set; }
/// <summary>
/// Gets or sets the original name of the file being signed. <see cref="InputFilePath"/>
/// might point to a temporarily name while <see cref="OriginalFileName"/> is the name of
/// the file as provided by the client. Might be used to generate auxiliary files.
/// </summary>
public string OriginalFileName { get; set; }
/// <summary>
/// Gets or sets the timestamping server which should be used for timestamping the signatures.
/// </summary>
public string TimestampServer { get; set; }
/// <summary>
/// Gets or sets the name of the hash algorithm to be used for the signatures.
/// </summary>
public string HashAlgorithm { get; set; }
/// <summary>
/// Gets or sets whether any existing signatures should be overwritten.
/// If this is not set, and a file is already signed, the signing operation will fail.
/// </summary>
public bool OverwriteSignature { get; set; }
}
}
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
public enum SignFileResponseStatus
{
/// <summary>
/// File was successfully signed
/// </summary>
FileSigned,
/// <summary>
/// Files was successfully signed, an existing signature was removed
/// </summary>
FileResigned,
/// <summary>
/// The file was already signed and therefore signing was skipped.
/// </summary>
FileAlreadySigned,
/// <summary>
/// The file was not signed because the given file format cannot be signed or is not supported.
/// </summary>
FileNotSignedUnsupportedFormat,
/// <summary>
/// The file was not signed because an unexpected error happened.
/// </summary>
FileNotSignedError,
/// <summary>
/// The file was not signed because the singing request was noth authorized.
/// </summary>
FileNotSignedUnauthorized
}
public class SignFileResponse
{
/// <summary>
/// The result status of the signing
/// </summary>
public SignFileResponseStatus Status { get; set; }
/// <summary>
/// The detailed error message in case <see cref="Status"/> is set to <see cref="SignFileResponseStatus.FileNotSignedError"/>
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// The result files consisting typically of the signed file.
/// In some scenarios additional files might be provided (e.g. Android v4 idsig)
/// </summary>
public IList<SignFileResponseFileInfo> ResultFiles { get; set; }
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
public class SignFileResponseFileInfo
{
/// <summary>
/// The name of the output file as it should be named on the client side.
/// </summary>
public string FileName { get; }
/// <summary>
/// The full path to the disk holding the output file which should be sent to the client.
/// </summary>
public string OutputFilePath { get; }
public SignFileResponseFileInfo(string fileName, string outputFilePath)
{
FileName = fileName;
OutputFilePath = outputFilePath;
}
}
}
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
internal sealed class UnmanagedStruct<T> : IDisposable
where T : struct
{
public IntPtr Pointer { get; private set; }
public UnmanagedStruct()
{
Pointer = Marshal.AllocHGlobal(Marshal.SizeOf<T>());
}
public void Fill(T value)
{
Marshal.StructureToPtr(value, Pointer, false);
}
public UnmanagedStruct(T v) : this()
{
Marshal.StructureToPtr(v, Pointer, false);
}
public void Dispose()
{
if (Pointer != IntPtr.Zero)
{
Marshal.FreeHGlobal(Pointer);
Pointer = IntPtr.Zero;
}
}
}
}
@@ -0,0 +1,397 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Codecepticon.Modules.Sign.MsSign
{
internal static class Win32SigningAPI
{
public const uint SIGN_CALLBACK_UNDOCUMENTED = 0x400;
public const string OID_OIWSEC_SHA1 = "1.3.14.3.2.26";
public const string OID_RSA_MD5 = "1.2.840.113549.2.5";
public const string OID_OIWSEC_SHA256 = "2.16.840.1.101.3.4.2.1";
public const string OID_OIWSEC_SHA384 = "2.16.840.1.101.3.4.2.2";
public const string OID_OIWSEC_SHA512 = "2.16.840.1.101.3.4.2.3";
public const uint SIGNER_TIMESTAMP_RFC3161 = 2;
public const int S_OK = 0;
public const uint SIGNER_NO_ATTR = 0;
public const uint SIGNER_CERT_STORE = 2;
public const uint SIGNER_CERT_POLICY_CHAIN = 2;
public const uint SIGNER_SUBJECT_FILE = 1;
public const int E_INVALIDARG = unchecked((int)0x80070057);
public const string WINTRUST_ACTION_GENERIC_VERIFY_V2 = "{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}";
public const uint ALG_CLASS_HASH = (4 << 13);
public const uint ALG_TYPE_ANY = (0);
public const uint ALG_SID_SHA1 = 4;
public const uint ALG_SID_MD5 = 3;
public const uint ALG_SID_SHA_256 = 12;
public const uint ALG_SID_SHA_384 = 13;
public const uint ALG_SID_SHA_512 = 14;
public const uint CALG_SHA1 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1;
public const uint CALG_MD5 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5;
public const uint CALG_SHA_256 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256;
public const uint CALG_SHA_384 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384;
public const uint CALG_SHA_512 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SIGNER_CERT
{
public uint cbSize;
public uint dwCertChoice;
public SIGNER_CERT_UNION union;
public IntPtr hwnd;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SIGNER_SIGNATURE_INFO
{
public uint cbSize;
public uint algidHash;
public uint dwAttrChoice;
public SIGNER_SIGNATURE_INFO_UNION union;
public /*PCRYPT_ATTRIBUTES*/ IntPtr psAuthenticated;
public /*PCRYPT_ATTRIBUTES*/ IntPtr psUnauthenticated;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct SIGNER_SIGNATURE_INFO_UNION
{
[FieldOffset(0)] public /*PSIGNER_ATTR_AUTHCODE*/ IntPtr pAttrAuthcode;
}
[StructLayout(LayoutKind.Sequential)]
public struct SIGNER_CERT_STORE_INFO
{
public uint cbSize;
public /*PCERT_CONTEXT*/ IntPtr pSigningCert;
public uint dwCertPolicy;
public IntPtr hCertStore;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct SIGNER_CERT_UNION
{
[FieldOffset(0)] public /*PSIGNER_CERT_STORE_INFO*/ IntPtr pSpcChainInfo;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct SIGNER_SUBJECT_INFO_UNION
{
[FieldOffset(0)] public /*PSIGNER_FILE_INFO*/ IntPtr pSignerFileInfo;
// [FieldOffset(0)]
// public SIGNER_BLOB_INFO* pSignerBlobInfo;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SIGNER_FILE_INFO
{
public uint cbSize;
public string pwszFileName;
public IntPtr hFile;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SIGNER_SUBJECT_INFO
{
public uint cbSize;
public IntPtr pdwIndex;
public uint dwSubjectChoice;
public SIGNER_SUBJECT_INFO_UNION union;
}
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SignerSignEx3(
[In] uint dwFlags,
[In] /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo,
[In] /*PSIGNER_CERT*/ IntPtr pSignerCert,
[In] /*PSIGNER_SIGNATURE_INFO*/ IntPtr pSignatureInfo,
[In, Optional] /*PSIGNER_PROVIDER_INFO*/ IntPtr pProviderInfo,
[In, Optional] uint dwTimestampFlags,
[In, Optional, MarshalAs(UnmanagedType.LPStr)]
string pszAlgorithmOid,
[In, Optional] string pwszTimestampURL,
[In, Optional] /*PCRYPT_ATTRIBUTES*/ IntPtr psRequest,
[In, Optional] IntPtr pSipData,
[Out] /*PPSIGNER_CONTEXT*/IntPtr ppSignerContext,
[In, Optional] IntPtr pCryptoPolicy,
[In] /*SIGN_INFO*/IntPtr pSignInfo,
[Optional] IntPtr pReserved
);
[StructLayout(LayoutKind.Sequential)]
public struct SIGN_INFO
{
public uint cbSize;
public IntPtr callback;
public IntPtr pvOpaque;
}
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SignerFreeSignerContext(
[In] /*PSIGNER_CONTEXT*/ IntPtr pSignerContext
);
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SignerTimeStamp(
[In] /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo,
[In] string pwszHttpTimeStamp,
[In, Optional] /*PCRYPT_ATTRIBUTES*/ IntPtr psRequest,
[In, Optional] IntPtr pSipData
);
[DllImport("mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int SignerTimeStampEx2(
[In] uint dwFlags,
[In] /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo,
[In] string pwszHttpTimeStamp,
[In, MarshalAs(UnmanagedType.LPStr)] string dwAlgId,
[In, Optional] /*PCRYPT_ATTRIBUTES*/ IntPtr psRequest,
[In, Optional] IntPtr pSipData,
[Out] /*PPSIGNER_CONTEXT*/IntPtr ppSignerContext
);
public enum WinVerifyTrustResult : uint
{
Success = 0,
ProviderUnknown = 0x800b0001, // Trust provider is not recognized on this system
SubjectFormUnknown = 0x800b0003, // Trust provider does not support the form specified for the subject
SubjectNotTrusted = 0x800b0004, // Subject failed the specified verification action
FileNotSigned = 0x800B0100, // TRUST_E_NOSIGNATURE - File was not signed
SubjectExplicitlyDistrusted = 0x800B0111, // Signer's certificate is in the Untrusted Publishers store
UntrustedRoot =
0x800B0109, // CERT_E_UNTRUSTEDROOT - A certification chain processed correctly but terminated in a root certificate that is not trusted by the trust provider.
LocalSecurityOption =
0x80092026 // CRYPT_E_SECURITY_SETTINGS
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WINTRUST_DATA
{
public uint cbStruct;
public IntPtr pPolicyCallbackData;
public IntPtr pSIPClientData;
public WinTrustDataUIChoice dwUIChoice;
public WinTrustDataRevocationChecks fdwRevocationChecks;
public WinTrustDataUnionChoice dwUnionChoice;
public WINTRUST_DATA_UNION union;
public WinTrustDataStateAction dwStateAction;
public IntPtr hWVTStateData;
public IntPtr pwszURLReference;
public uint dwProvFlags;
public uint dwUIContext;
}
public enum WinTrustDataStateAction : uint
{
Verify = 0x00000001
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
public struct WINTRUST_DATA_UNION
{
[FieldOffset(0)] public /*PWINTRUST_FILE_INFO*/ IntPtr pFile; // individual file
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WINTRUST_FILE_INFO
{
public uint cbStruct;
public string pcwszFilePath;
public IntPtr hFile;
public IntPtr pgKnownSubject;
}
public enum WinTrustDataUIChoice : uint
{
None = 2
}
public enum WinTrustDataRevocationChecks : uint
{
None = 0x00000000
}
public enum WinTrustDataUnionChoice : uint
{
File = 1
}
[DllImport("wintrust.dll", ExactSpelling = true, SetLastError = false, CharSet = CharSet.Unicode)]
public static extern WinVerifyTrustResult WinVerifyTrust(
[In] IntPtr hwnd,
[In] [MarshalAs(UnmanagedType.LPStruct)]
Guid pgActionID,
[In] WINTRUST_DATA pWVTData
);
[DllImport("imagehlp.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ImageEnumerateCertificates(
[In] SafeFileHandle FileHandle,
[In] uint TypeFilter,
[Out] out uint CertificateCount,
[In, Out, Optional] uint[] Indices,
[In, Optional] uint IndexCount
);
public const uint CERT_SECTION_TYPE_ANY = 0xFF;
[DllImport("imagehlp.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool ImageRemoveCertificate(SafeFileHandle fileHandle, uint index);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SIGNER_SIGN_EX3_PARAMS
{
public uint dwFlags;
public /*PSIGNER_SUBJECT_INFO*/ IntPtr pSubjectInfo;
public /*PSIGNER_CERT*/ IntPtr pSigningCert;
public /*PSIGNER_SIGNATURE_INFO*/ IntPtr pSignatureInfo;
public /*PSIGNER_PROVIDER_INFO*/ IntPtr pProviderInfo;
public uint dwTimestampFlags;
[MarshalAs(UnmanagedType.LPStr)] public string pszTimestampAlgorithmOid;
[MarshalAs(UnmanagedType.LPWStr)] public string pwszTimestampURL;
public IntPtr psRequest;
public /*PSIGN_INFO*/ IntPtr pSignCallback;
public /*PPSIGNER_CONTEXT*/ IntPtr pSignerContext;
public IntPtr pCryptoPolicy;
public IntPtr pReserved;
}
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
public delegate int SignCallback(
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr pCertContext,
[In, MarshalAs(UnmanagedType.SysInt)] IntPtr pvExtra,
[In, MarshalAs(UnmanagedType.U4)] uint algId,
[In, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 4)]
byte[] pDigestToSign,
[In, MarshalAs(UnmanagedType.U4)] uint dwDigestToSign,
[In, Out] ref CRYPTOAPI_BLOB blob
);
[StructLayout(LayoutKind.Sequential)]
public struct CRYPTOAPI_BLOB
{
public uint cbData;
public IntPtr pbData;
}
[StructLayout(LayoutKind.Sequential)]
public struct APPX_SIP_CLIENT_DATA
{
public /*PSIGNER_SIGN_EX2_PARAMS or PSIGNER_SIGN_EX3_PARAMS*/ IntPtr pSignerParams;
public /*LPVOID*/ IntPtr pAppxSipState;
}
public const int NTE_BAD_KEY = unchecked((int)0x80090003);
public const int TRUST_E_SUBJECT_FORM_UNKNOWN = unchecked((int)0x800B0003);
public const int TRUST_E_BAD_DIGEST = unchecked((int)0x80096010);
public const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
public const string szOID_OIWSEC_sha1 = "1.3.14.3.2.26";
public const string szOID_NIST_sha256 = "2.16.840.1.101.3.4.2.1";
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int SetDllDirectoryW(string strPathName);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr LoadLibraryExW(string strFileName, IntPtr hFile, uint ulFlags);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool FreeLibrary(IntPtr hModule);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CRYPT_DATA_BLOB
{
internal uint cbData;
internal IntPtr pbData;
}
[DllImport("clr.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int _AxlPublicKeyBlobToPublicKeyToken(
[In] ref CRYPT_DATA_BLOB pCspPublicKeyBlob,
[In, Out] ref IntPtr ppwszPublicKeyToken);
[DllImport("clr.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int _AxlGetIssuerPublicKeyHash(
[In] IntPtr pCertContext,
[In, Out] ref IntPtr ppwszPublicKeyHash);
[DllImport("clr.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int CertTimestampAuthenticodeLicense(
[In] ref CRYPT_DATA_BLOB pSignedLicenseBlob,
[In] string pwszTimestampURI,
[In, Out] ref CRYPT_DATA_BLOB pTimestampSignatureBlob);
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool HeapFree(
[In] IntPtr hHeap,
[In] uint dwFlags,
[In] IntPtr lpMem);
[DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetProcessHeap();
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_TIMESTAMP_PARA
{
public IntPtr pszTSAPolicyId;
public bool fRequestCerts;
public CRYPTOAPI_BLOB Nonce;
public int cExtension;
public IntPtr rgExtension;
}
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_TIMESTAMP_CONTEXT
{
public uint cbEncoded;
public IntPtr pbEncoded;
public IntPtr pTimeStamp;
}
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi)]
public static extern void CryptMemFree(IntPtr pv);
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool CertFreeCertificateContext(IntPtr pCertContext);
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
public static extern bool CertCloseStore(IntPtr pCertContext, int dwFlags);
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
[DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CryptRetrieveTimeStamp(
[In][MarshalAs(UnmanagedType.LPWStr)] string wszUrl,
[In] uint dwRetrievalFlags,
[In] int dwTimeout,
[In][MarshalAs(UnmanagedType.LPStr)] string pszHashId,
[In, Out] ref CRYPT_TIMESTAMP_PARA pPara,
[In] byte[] pbData,
[In] int cbData,
[In, Out] ref IntPtr ppTsContext,
[In, Out] ref IntPtr ppTsSigner,
[In, Out] ref IntPtr phStore);
}
}
+38 -31
View File
@@ -1,9 +1,11 @@
using Codecepticon.CommandLine;
using Codecepticon.Modules.Sign.MsSign;
using Codecepticon.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
@@ -19,10 +21,6 @@ namespace Codecepticon.Modules.Sign
GenerateCertificate();
break;
case CommandLineData.Action.Sign:
if (!FindSignTool())
{
return;
}
SignExecutable();
break;
}
@@ -50,39 +48,48 @@ namespace Codecepticon.Modules.Sign
return true;
}
protected bool FindSignTool()
{
if (String.IsNullOrEmpty(CommandLineData.Sign.SignTool))
{
Logger.Info("No signtool.exe specified, will look for it now...");
SignToolManager signToolManager = new SignToolManager();
CommandLineData.Sign.SignTool = signToolManager.Find();
if (String.IsNullOrEmpty(CommandLineData.Sign.SignTool) || !File.Exists(CommandLineData.Sign.SignTool))
{
Logger.Error("Could not find signtool.exe");
return false;
}
Logger.Info("Found signtool.exe: " + CommandLineData.Sign.SignTool);
}
return File.Exists(CommandLineData.Sign.SignTool);
}
protected bool SignExecutable()
{
string stdOutput = "";
string stdError = "";
Logger.Info("Signing executable...");
SignToolManager signToolManager = new SignToolManager();
bool result = signToolManager.SignExecutable(CommandLineData.Sign.SignTool, CommandLineData.Global.Project.Path, CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password, ref stdOutput, ref stdError);
if (!result)
Logger.Info("Loading certificate...");
X509Certificate2 certificate;
try
{
Logger.Error("There was an error while signing the file:");
Logger.Error("", true, false);
Logger.Error(stdError, true, false);
certificate = new(CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password);
} catch (Exception e)
{
Logger.Error("Could not load PFX file: " + CommandLineData.Sign.NewCertificate.PfxFile);
Logger.Error(e.Message);
return false;
}
try
{
Logger.Info("Signing executable...");
SignFileRequest request = new()
{
Certificate = certificate,
PrivateKey = certificate.GetRSAPrivateKey(),
OverwriteSignature = true,
InputFilePath = CommandLineData.Global.Project.Path,
HashAlgorithm = CommandLineData.Sign.SignatureAlgorithm,
TimestampServer = CommandLineData.Sign.TimestampServer,
};
PortableExecutableSigningTool signingTool = new();
SignFileResponse response = signingTool.SignFile(request);
if (response.Status != SignFileResponseStatus.FileSigned && response.Status != SignFileResponseStatus.FileResigned)
{
return false;
}
} catch (Exception e)
{
Logger.Error("Could not sign executable");
Logger.Error(e.Message);
return false;
}
Logger.Success("Executable signed");
return true;
}
+1
View File
@@ -84,6 +84,7 @@
"properties": "%properties%",
"variables": "%variables%",
"parameters": "%parameters%",
"structs": "%structs%",
"cmdline": "%cmdline%"
}
</script>
+35 -8
View File
@@ -267,6 +267,12 @@
<label class="form-check-label" for="csharp-rename-variables">Variables</label>
</div>
<!-- Structs -->
<div class="form-check form-switch">
<input class="csharp-rename-item form-check-input" type="checkbox" data-argument="s" id="csharp-rename-structs">
<label class="form-check-label" for="csharp-rename-structs">Structs</label>
</div>
<!-- Command Line -->
<div class="form-check form-switch">
<input class="csharp-rename-item form-check-input" type="checkbox" data-argument="o" id="csharp-rename-commandline">
@@ -486,11 +492,28 @@
<input type="text" id="password" class="form-control" value="">
</div>
<!-- Sign Tool Location -->
<div class="mb-3 box-sign">
<label for="signtool" class="form-label">Microsoft SignTool</label><small class="ms-2">(Location of signtool.exe on the system. Run 'cd C:\ &amp;&amp; dir /b /s signtool.exe' to find)</small>
<input type="text" id="signtool" class="form-control" value="" placeholder="Leave empty to auto-locate">
<!-- Timestamp Server & Signature Algorithm -->
<div class="row mb-3 box-sign">
<div class="col">
<div>
<label for="timestamp" class="form-label">Timestamp Server</label>
<input type="text" id="timestamp" class="form-control" value="" placeholder="http://timestamp.sectigo.com">
</div>
</div>
<div class="col">
<div>
<label for="algorithm" class="form-label">Signature Algorithm</label><span class="ms-1 text-danger">*</span>
<select class="form-select" id="algorithm">
<option value="SHA256">SHA256</option>
<option value="SHA384">SHA384</option>
<option value="SHA512">SHA512</option>
<option value="SHA1">SHA1</option>
<option value="MD5">MD5</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -552,7 +575,8 @@
certCopyFrom: function() { return this.element('#copy-from').value; },
getDateTime: function(selector) { let value = this.element(selector).value.replace('T', ' '); if (value.length === 0) { return ''; }; let seconds = this.getRandomInt(0, 59); seconds = (seconds >= 10 ? seconds : '0' + seconds); return value + ':' + seconds; },
getRandomInt: function(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); },
signTool: function() { return this.element('#signtool').value; },
getTimestamp: function() { return this.element('#timestamp').value; },
getSignatureAlgorithm: function() { return this.element('#algorithm').value; },
fileToSign: function() { return this.element('#executable').value; },
getRenameItems: function(module) {
@@ -975,9 +999,12 @@
output['path'] = PageData.quoteIfNeeded(PageData.fileToSign());
}
// Sign tool.
if (PageData.signTool().length > 0) {
output['signtool'] = PageData.quoteIfNeeded(PageData.signTool());
if (PageData.getTimestamp().length > 0) {
output['timestamp'] = PageData.getTimestamp();
}
if (PageData.getSignatureAlgorithm().length > 0) {
output['algorithm'] = PageData.getSignatureAlgorithm();
}
}
+2 -1
View File
@@ -1,6 +1,7 @@
MIT License
Copyright (c) 2022 Accenture Security
Copyright (c) 2022 - 02/2024 Accenture Security
Copyright (c) 03/2024 - Present - Pavel Tsakalidis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+5 -3
View File
@@ -1,4 +1,4 @@
# Codecepticon
# Codecepticon ![version](https://img.shields.io/github/v/tag/sadreck/Codecepticon?label=version&style=flat-square)
## Table of Contents
@@ -16,6 +16,7 @@
* [Functionality Deep Dive](docs/Functionality.md)
* [Tips](docs/Tips.md)
* [Known Issues](docs/KnownIssues.md)
* [Sign Executables](#sign-executables)
* [FAQ](#faq)
* [Troubleshooting](#troubleshooting)
* [Contributions](#contributions)
@@ -52,7 +53,7 @@ Codecepticon allows you to obfuscate and rewrite code, but also provides feature
### Open and Compile
Open Codecepticon, wait until all NuGet packages are downloaded and then build the solution.
Open Codecepticon, wait until all NuGet packages are downloaded and then build the solution. You can also download a pre-compiled `Release`.
## Using Codecepticon
@@ -118,7 +119,7 @@ Therefore it is **critical** to always test your result in a local environment f
### Sign Executables
It is also possible to use Codecepticon to digitally sign executable files (your compiled output). Codecepticon can both generate a signing certificate for you, and also use it (or any other you may provide) to sign your target file. Please note that for this functionality you will need Microsoft's [SignTool](https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool) - although it should be automatically installed when you install Visual Studio 2022.
It is also possible to use Codecepticon to digitally sign executable files (your compiled output). Codecepticon can both generate a signing certificate for you, and also use it (or any other you may provide) to sign your target file.
## FAQ
@@ -167,3 +168,4 @@ Whether it's a typo, a bug, or a new feature, Codecepticon is very open to contr
* https://github.com/MagicMau/ProceduralNameGenerator
* https://github.com/uwol/proleap-vb6-parser
* https://github.com/dwyl/english-words
* https://github.com/Danielku15/SigningServer
+1
View File
@@ -173,6 +173,7 @@ The level of customisation supported by the C# module is:
* Properties
* Parameters
* Variables
* Structs
* Command Line _(only for targets that have a pre-existing Profile - like SharpHound, Rubeus, etc)_
This means that it is possible to choose which identifiers should be obfuscated.