mirror of
https://github.com/sadreck/Codecepticon
synced 2026-08-09 13:07:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0b3e7a61c | ||
|
|
e4920c1f21 | ||
|
|
b9bd043ce8 | ||
|
|
babb024473 | ||
|
|
7057e0b6c3 | ||
|
|
6358dd2afc | ||
|
|
ca43cd89bb | ||
|
|
67f5fe9aaa | ||
|
|
933a7f56ba | ||
|
|
daee9f05af | ||
|
|
a079d0cfca | ||
|
|
ec5187307b | ||
|
|
0b5a6b23e3 | ||
|
|
611f8850b7 | ||
|
|
00d240d318 | ||
|
|
9c6bf156ef | ||
|
|
3306461683 | ||
|
|
eed8b6e0b4 |
@@ -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 }}
|
||||
@@ -32,3 +32,6 @@ Generated\ Files/
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Misc
|
||||
launchSettings.json
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
# 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`.
|
||||
|
||||
## v1.0.3
|
||||
|
||||
* `[Fix]` C#: Ensure that Delegate function/declarations are also renamed.
|
||||
|
||||
## v1.0.2
|
||||
|
||||
* `[New]` Mapping: Added checkbox to "Match Exact Word" when searching within the document.
|
||||
|
||||
@@ -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.0.3</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>
|
||||
@@ -63,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>
|
||||
|
||||
@@ -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_%%
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%%_HEADER_%%
|
||||
|
||||
Usage:
|
||||
Codecepticon.exe --module [csharp|powershell|vba] --help [OPTIONS]...
|
||||
Codecepticon.exe --module [csharp|powershell|vba|sign] --help [OPTIONS]...
|
||||
Codecepticon.exe --config [XML Config File]
|
||||
|
||||
Tip: Use the command line generator HTML file to make your life easier.
|
||||
@@ -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
|
||||
@@ -40,4 +40,5 @@
|
||||
Only works with --action unmap.
|
||||
--unmap-file [path] Specify an obfuscated file to unmap. Only works with --action unmap.
|
||||
|
||||
--verbose What it says.
|
||||
--verbose What it says.
|
||||
--debug What it says.
|
||||
@@ -0,0 +1,23 @@
|
||||
%%_HEADER_%%
|
||||
|
||||
Usage: Codecepticon.exe --module sign [OPTIONS]...
|
||||
|
||||
--action [cert|sign] Specify the action to be executed:
|
||||
cert: Generate a self-signed certificate (pfx).
|
||||
sign: Sign an executable using a pfx file.
|
||||
--subject Specify the subject for the new certificate (CN=Codecepticon,C=GB, etc).
|
||||
--issuer Specify the issuer for the new certificate (CN=Codecepticon Issuer,C=GB, etc).
|
||||
--copy-from Specify a signed file to copy the Subject/Issuer from. If --subject or --issuer are
|
||||
also set those will supercede this argument.
|
||||
--not-before Date from when the new certificate will be valid from, format is YYYY-MM-DD HH:MM:SS.
|
||||
--not-after Expiration date for the new certificate, format is YYYY-MM-DD HH:MM:SS.
|
||||
--pfx-file When used with '--action cert' this is where the pfx file will be saved as.
|
||||
When used with '--action sign' this is where the pfx file will be loaded from.
|
||||
--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)
|
||||
--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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using Codecepticon.Modules.CSharp.Profiles;
|
||||
using Codecepticon.Utils;
|
||||
using Codecepticon.Utils.MarkovWordGenerator;
|
||||
using Microsoft.PowerShell.Commands;
|
||||
|
||||
namespace Codecepticon.CommandLine
|
||||
{
|
||||
@@ -15,7 +16,9 @@ namespace Codecepticon.CommandLine
|
||||
{
|
||||
None = 0,
|
||||
Obfuscate = 1,
|
||||
Unmap = 2
|
||||
Unmap = 2,
|
||||
GenerateCertificate = 3,
|
||||
Sign = 4,
|
||||
}
|
||||
|
||||
public struct ProjectStruct
|
||||
@@ -64,6 +67,7 @@ namespace Codecepticon.CommandLine
|
||||
public bool Variables;
|
||||
public bool Parameters;
|
||||
public bool CommandLine;
|
||||
public bool Structs;
|
||||
}
|
||||
|
||||
public struct RewriteTemplateStruct
|
||||
@@ -137,6 +141,25 @@ namespace Codecepticon.CommandLine
|
||||
public Vb6RenamingStruct Rename;
|
||||
}
|
||||
|
||||
public struct SignNewCertificate
|
||||
{
|
||||
public string Subject;
|
||||
public string Issuer;
|
||||
public DateTime NotBefore;
|
||||
public DateTime NotAfter;
|
||||
public string Password;
|
||||
public bool Overwrite;
|
||||
public string PfxFile;
|
||||
public string CopyFrom;
|
||||
}
|
||||
|
||||
public struct SignSettings
|
||||
{
|
||||
public SignNewCertificate NewCertificate;
|
||||
public string TimestampServer;
|
||||
public string SignatureAlgorithm;
|
||||
}
|
||||
|
||||
public struct RenameGeneratorStruct
|
||||
{
|
||||
public NameGenerator.RandomNameGeneratorMethods Method;
|
||||
@@ -164,5 +187,6 @@ namespace Codecepticon.CommandLine
|
||||
public static CSharpSettings CSharp = new CSharpSettings();
|
||||
public static PowerShellSettings PowerShell = new PowerShellSettings();
|
||||
public static Vb6Settings Vb6 = new Vb6Settings();
|
||||
public static SignSettings Sign = new SignSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Codecepticon.Modules.VB6.CommandLine;
|
||||
using static Codecepticon.Modules.ModuleTypes;
|
||||
using Codecepticon.Utils;
|
||||
using System.Reflection;
|
||||
using Codecepticon.Modules.Sign.CommandLine;
|
||||
|
||||
namespace Codecepticon.CommandLine
|
||||
{
|
||||
@@ -85,6 +86,7 @@ namespace Codecepticon.CommandLine
|
||||
{ CodecepticonModules.CSharp, "CSharp.txt" },
|
||||
{ CodecepticonModules.Powershell, "PowerShell.txt" },
|
||||
{ CodecepticonModules.Vb6, "VBA.txt" },
|
||||
{ CodecepticonModules.Sign, "Sign.txt" }
|
||||
};
|
||||
|
||||
string fileName = helpMapping.ContainsKey(module) ? helpMapping[module] : null;
|
||||
@@ -145,6 +147,7 @@ namespace Codecepticon.CommandLine
|
||||
"csharp" or "cs" => CodecepticonModules.CSharp,
|
||||
"powershell" or "ps" => CodecepticonModules.Powershell,
|
||||
"vba" or "vb6" => CodecepticonModules.Vb6,
|
||||
"sign" => CodecepticonModules.Sign,
|
||||
_ => CodecepticonModules.Unknown
|
||||
};
|
||||
}
|
||||
@@ -274,6 +277,10 @@ namespace Codecepticon.CommandLine
|
||||
Vb6CommandLine Vb6CommandManager = new Vb6CommandLine(Args);
|
||||
result = Vb6CommandManager.Load();
|
||||
break;
|
||||
case CodecepticonModules.Sign:
|
||||
SignCommandLine SignCommandManager = new SignCommandLine(Args);
|
||||
result = SignCommandManager.Load();
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -333,6 +340,8 @@ namespace Codecepticon.CommandLine
|
||||
{
|
||||
"obfuscate" => CommandLineData.Action.Obfuscate,
|
||||
"unmap" => CommandLineData.Action.Unmap,
|
||||
"cert" => CommandLineData.Action.GenerateCertificate,
|
||||
"sign" => CommandLineData.Action.Sign,
|
||||
_ => CommandLineData.Action.None
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ namespace Codecepticon.Modules
|
||||
Unknown = 0,
|
||||
CSharp = 1,
|
||||
Powershell = 2,
|
||||
Vb6 = 3
|
||||
Vb6 = 3,
|
||||
Sign = 101
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using Codecepticon.Utils;
|
||||
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 int KeyLength = 2048;
|
||||
|
||||
public bool GenerateCertificate(string Subject, string Issuer, DateTime NotBefore, DateTime NotAfter, string Password, string PfxOutput)
|
||||
{
|
||||
// https://stackoverflow.com/a/48210587/2445959
|
||||
RSA keyPair = RSA.Create(KeyLength);
|
||||
|
||||
Logger.Verbose("Generating issuer certificate...");
|
||||
Logger.Verbose("Issuer is " + Issuer);
|
||||
X509Certificate2 certificateIssuer = GenerateIssuerCertificate(Issuer, NotBefore, NotAfter);
|
||||
|
||||
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 });
|
||||
|
||||
// Add the private key back to the certificate.
|
||||
X509Certificate2 certificate = cert.CopyWithPrivateKey(keyPair);
|
||||
|
||||
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
|
||||
{
|
||||
X509Certificate2 certificate = new(pfxFile, password);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public X509Certificate GetCertificateFromFile(string signedFile)
|
||||
{
|
||||
return X509Certificate.CreateFromSignedFile(signedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Codecepticon.CommandLine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.CommandLine
|
||||
{
|
||||
class SignCommandLine : CommandLineManager
|
||||
{
|
||||
public SignCommandLine(string[] args) : base(args)
|
||||
{
|
||||
Arguments = new Dictionary<string, string>
|
||||
{
|
||||
{ "issuer", "" },
|
||||
{ "subject", "" },
|
||||
{ "copy-from", "" },
|
||||
{ "not-before", "" },
|
||||
{ "not-after", "" },
|
||||
{ "password", "" },
|
||||
{ "pfx-file", "" },
|
||||
{ "overwrite", "switch" },
|
||||
{ "algorithm", "" },
|
||||
{ "timestamp", "" }
|
||||
};
|
||||
MergeArguments();
|
||||
}
|
||||
|
||||
protected override bool Parse(Dictionary<string, string> arguments)
|
||||
{
|
||||
if (!ParseGlobalArguments(arguments))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, string> argument in arguments)
|
||||
{
|
||||
switch (argument.Key.ToLower())
|
||||
{
|
||||
case "subject":
|
||||
CommandLineData.Sign.NewCertificate.Subject = argument.Value;
|
||||
break;
|
||||
case "issuer":
|
||||
CommandLineData.Sign.NewCertificate.Issuer = argument.Value;
|
||||
break;
|
||||
case "copy-from":
|
||||
CommandLineData.Sign.NewCertificate.CopyFrom = argument.Value;
|
||||
break;
|
||||
case "not-after":
|
||||
try
|
||||
{
|
||||
CommandLineData.Sign.NewCertificate.NotAfter = DateTime.ParseExact(argument.Value, "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Nothing.
|
||||
}
|
||||
break;
|
||||
case "not-before":
|
||||
try
|
||||
{
|
||||
CommandLineData.Sign.NewCertificate.NotBefore = DateTime.ParseExact(argument.Value, "yyyy-MM-dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Nothing.
|
||||
}
|
||||
break;
|
||||
case "password":
|
||||
CommandLineData.Sign.NewCertificate.Password = argument.Value;
|
||||
break;
|
||||
case "pfx-file":
|
||||
CommandLineData.Sign.NewCertificate.PfxFile = argument.Value;
|
||||
break;
|
||||
case "overwrite":
|
||||
if (argument.Value.ToLower() != "false")
|
||||
{
|
||||
CommandLineData.Sign.NewCertificate.Overwrite = (argument.Value.Length > 0);
|
||||
}
|
||||
break;
|
||||
case "algorithm":
|
||||
CommandLineData.Sign.SignatureAlgorithm = argument.Value.ToUpper();
|
||||
break;
|
||||
case "timestamp":
|
||||
CommandLineData.Sign.TimestampServer = argument.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ValidateCommandLine validate = new ValidateCommandLine();
|
||||
return validate.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
using Codecepticon.CommandLine;
|
||||
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;
|
||||
|
||||
namespace Codecepticon.Modules.Sign.CommandLine
|
||||
{
|
||||
class ValidateCommandLine : CommandLineValidator
|
||||
{
|
||||
public bool Run()
|
||||
{
|
||||
if (!ValidateParameters())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool ValidateParameters()
|
||||
{
|
||||
CertificateManager certificateManager = new CertificateManager();
|
||||
|
||||
switch (CommandLineData.Global.Action)
|
||||
{
|
||||
case CommandLineData.Action.GenerateCertificate:
|
||||
|
||||
string copyIssuer = "";
|
||||
string copySubject = "";
|
||||
|
||||
if (!String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.CopyFrom))
|
||||
{
|
||||
if (!File.Exists(CommandLineData.Sign.NewCertificate.CopyFrom))
|
||||
{
|
||||
Logger.Error("File to copy certificate details from, does not exist: " + CommandLineData.Sign.NewCertificate.CopyFrom);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
X509Certificate copyFromCertificate = certificateManager.GetCertificateFromFile(CommandLineData.Sign.NewCertificate.CopyFrom);
|
||||
copyIssuer = copyFromCertificate.Issuer;
|
||||
copySubject = copyFromCertificate.Subject;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Could not read the certificate from: " + CommandLineData.Sign.NewCertificate.CopyFrom + " - Are you sure it's signed?");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.Subject))
|
||||
{
|
||||
if (String.IsNullOrEmpty(copySubject))
|
||||
{
|
||||
Logger.Error("Certificate Subject is empty");
|
||||
return false;
|
||||
}
|
||||
CommandLineData.Sign.NewCertificate.Subject = copySubject;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.Issuer))
|
||||
{
|
||||
if (String.IsNullOrEmpty(copyIssuer))
|
||||
{
|
||||
Logger.Error("Certificate Issuer is empty");
|
||||
return false;
|
||||
}
|
||||
CommandLineData.Sign.NewCertificate.Issuer = copyIssuer;
|
||||
}
|
||||
|
||||
if (CommandLineData.Sign.NewCertificate.NotBefore == default)
|
||||
{
|
||||
Logger.Error("Certificate NotBefore is invalid");
|
||||
return false;
|
||||
}
|
||||
else if (CommandLineData.Sign.NewCertificate.NotAfter == default)
|
||||
{
|
||||
Logger.Error("Certificate NotAfter is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.Password))
|
||||
{
|
||||
Logger.Error("Certificate Password is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.PfxFile))
|
||||
{
|
||||
Logger.Error("Certificate Pfx path is empty");
|
||||
return false;
|
||||
}
|
||||
else if (File.Exists(CommandLineData.Sign.NewCertificate.PfxFile) && !CommandLineData.Sign.NewCertificate.Overwrite)
|
||||
{
|
||||
Logger.Error("Certificate Pfx path already exists and --overwrite was not set");
|
||||
return false;
|
||||
}
|
||||
|
||||
CommandLineData.Sign.NewCertificate.Subject = FixDN(CommandLineData.Sign.NewCertificate.Subject);
|
||||
CommandLineData.Sign.NewCertificate.Issuer = FixDN(CommandLineData.Sign.NewCertificate.Issuer);
|
||||
|
||||
Logger.Debug("New Certificate Subject: " + CommandLineData.Sign.NewCertificate.Subject);
|
||||
Logger.Debug("New Certificate Issuer: " + CommandLineData.Sign.NewCertificate.Issuer);
|
||||
Logger.Debug("New Certificate NotBefore: " + CommandLineData.Sign.NewCertificate.NotBefore);
|
||||
Logger.Debug("New Certificate NotAfter: " + CommandLineData.Sign.NewCertificate.NotAfter);
|
||||
Logger.Debug("New Certificate Password: " + CommandLineData.Sign.NewCertificate.Password);
|
||||
Logger.Debug("New Certificate PfxFile: " + CommandLineData.Sign.NewCertificate.PfxFile);
|
||||
|
||||
break;
|
||||
case CommandLineData.Action.Sign:
|
||||
if (String.IsNullOrEmpty(CommandLineData.Global.Project.Path) || !File.Exists(CommandLineData.Global.Project.Path))
|
||||
{
|
||||
Logger.Error("Target path is empty or does not exist: " + CommandLineData.Global.Project.Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.PfxFile) || !File.Exists(CommandLineData.Sign.NewCertificate.PfxFile))
|
||||
{
|
||||
Logger.Error("Certificate Pfx path is empty or does not exist: " + CommandLineData.Sign.NewCertificate.PfxFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(CommandLineData.Sign.NewCertificate.Password))
|
||||
{
|
||||
Logger.Error("Certificate Password is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate the PFX Password.
|
||||
if (!certificateManager.CheckPfxPassword(CommandLineData.Sign.NewCertificate.PfxFile, CommandLineData.Sign.NewCertificate.Password))
|
||||
{
|
||||
Logger.Error("Invalid PFX file password");
|
||||
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());
|
||||
return false;
|
||||
}
|
||||
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.
|
||||
// This function tries to convert the S= to ST=.
|
||||
|
||||
Dictionary<string, string> searchAndReplace = new Dictionary<string, string>
|
||||
{
|
||||
{ ",S=", ",ST=" },
|
||||
{ ", S=", ", ST=" }
|
||||
};
|
||||
|
||||
foreach (KeyValuePair<string, string> item in searchAndReplace)
|
||||
{
|
||||
if (dn.IndexOf(item.Key) < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
dn = dn.Replace(item.Key, item.Value);
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
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;
|
||||
|
||||
namespace Codecepticon.Modules.Sign
|
||||
{
|
||||
class SignManager : ModuleManager
|
||||
{
|
||||
public async Task Run()
|
||||
{
|
||||
switch (CommandLineData.Global.Action)
|
||||
{
|
||||
case CommandLineData.Action.GenerateCertificate:
|
||||
GenerateCertificate();
|
||||
break;
|
||||
case CommandLineData.Action.Sign:
|
||||
SignExecutable();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool GenerateCertificate()
|
||||
{
|
||||
CertificateManager certificateManager = new CertificateManager();
|
||||
Logger.Info("Generating certificate...");
|
||||
try
|
||||
{
|
||||
bool result = certificateManager.GenerateCertificate(CommandLineData.Sign.NewCertificate.Subject, CommandLineData.Sign.NewCertificate.Issuer, CommandLineData.Sign.NewCertificate.NotBefore, CommandLineData.Sign.NewCertificate.NotAfter, CommandLineData.Sign.NewCertificate.Password, CommandLineData.Sign.NewCertificate.PfxFile);
|
||||
if (!result)
|
||||
{
|
||||
Logger.Error("Could not generate self-signed certificate");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e.Message);
|
||||
return false;
|
||||
}
|
||||
Logger.Info("Certificate generated");
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool SignExecutable()
|
||||
{
|
||||
Logger.Info("Loading certificate...");
|
||||
|
||||
X509Certificate2 certificate;
|
||||
try
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Codecepticon.Utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Codecepticon.Modules.Sign
|
||||
{
|
||||
class SignToolManager
|
||||
{
|
||||
[DllImport("shell32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
static extern int SHGetSpecialFolderPath(IntPtr hwndOwner, IntPtr lpszPath, int nFolder, int fCreate);
|
||||
|
||||
private const int CSIDL_PROGRAM_FILES = 0x0026;
|
||||
private const int CSIDL_PROGRAM_FILESX86 = 0x002a;
|
||||
|
||||
public string Find()
|
||||
{
|
||||
List<string> systemPaths = new List<string>
|
||||
{
|
||||
GetSpecialFolder(CSIDL_PROGRAM_FILES).ToLower(),
|
||||
GetSpecialFolder(CSIDL_PROGRAM_FILESX86).ToLower()
|
||||
}.Distinct().ToList(); // If Codecepticon is compiled as x86 it will get the same folder twice.
|
||||
|
||||
List<string> foundFiles = SearchInFolders(systemPaths);
|
||||
if (foundFiles.Count == 0)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else if (foundFiles.Count == 1)
|
||||
{
|
||||
return foundFiles.First();
|
||||
}
|
||||
|
||||
string path = "";
|
||||
|
||||
Logger.Info("");
|
||||
Logger.Info("Multiple instances of signtool.exe were found, please select which one you would like to use:");
|
||||
Logger.Info("");
|
||||
for (int i = 0; i < foundFiles.Count; i++)
|
||||
{
|
||||
Logger.Info("\t[" + (i + 1) + "] " + foundFiles[i]);
|
||||
}
|
||||
Logger.Info("");
|
||||
|
||||
while (true)
|
||||
{
|
||||
Logger.Info("Please enter the number of your selection: ", false);
|
||||
var userResponse = Console.ReadLine();
|
||||
if (int.TryParse(userResponse, out int fileNumber) && fileNumber > 0 && fileNumber <= foundFiles.Count())
|
||||
{
|
||||
path = foundFiles[fileNumber - 1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private string GetSpecialFolder(int folder)
|
||||
{
|
||||
IntPtr path = Marshal.AllocHGlobal(260 * 2); // Unicode.
|
||||
SHGetSpecialFolderPath(IntPtr.Zero, path, folder, 0);
|
||||
string result = Marshal.PtrToStringUni(path);
|
||||
Marshal.FreeHGlobal(path);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<string> SearchInFolders(List<string> paths)
|
||||
{
|
||||
List<string> files = new List<string>();
|
||||
foreach (string path in paths)
|
||||
{
|
||||
string commandOutput = RunSearch(path);
|
||||
|
||||
string[] lines = commandOutput.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (!line.ToLower().StartsWith(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (files.Contains(line))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
files.Add(line);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private string RunSearch(string path)
|
||||
{
|
||||
Logger.Info("Running: cd \"" + path + "\" && dir /s /b signtool.exe");
|
||||
Process process = new Process();
|
||||
process.StartInfo.FileName = "cmd.exe";
|
||||
process.StartInfo.Arguments = "/c cd \"" + path + "\" && dir /s /b signtool.exe";
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.Start();
|
||||
return process.StandardOutput.ReadToEnd();
|
||||
}
|
||||
|
||||
public bool SignExecutable(string signToolPath, string executable, string pfxFile, string password, ref string stdOutput, ref string stdError)
|
||||
{
|
||||
// signtool.exe sign /f C:\data\tmp\self-signed.pfx /p Hello /fd SHA256 /tr http://localhost:8888/ C:\data\tmp\SignCerts\SignCerts\bin\Debug\net6.0\SignCerts2.exe
|
||||
string commandLineArguments = $"sign /f \"{pfxFile}\" /p \"{password}\" /fd SHA256 \"{executable}\"";
|
||||
Logger.Info($"Running: {signToolPath} {commandLineArguments}");
|
||||
|
||||
Process process = new Process();
|
||||
process.StartInfo.FileName = signToolPath;
|
||||
process.StartInfo.Arguments = commandLineArguments;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError= true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.Start();
|
||||
|
||||
stdOutput = process.StandardOutput.ReadToEnd().Trim();
|
||||
stdError = process.StandardError.ReadToEnd().Trim();
|
||||
|
||||
return stdError.Length == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Codecepticon.Modules.PowerShell;
|
||||
using Codecepticon.Modules.VB6;
|
||||
using static Codecepticon.Modules.ModuleTypes;
|
||||
using Newtonsoft.Json;
|
||||
using Codecepticon.Modules.Sign;
|
||||
|
||||
namespace Codecepticon
|
||||
{
|
||||
@@ -76,6 +77,10 @@ namespace Codecepticon
|
||||
Vb6Manager vb6Manager = new Vb6Manager();
|
||||
await vb6Manager.Run();
|
||||
break;
|
||||
case CodecepticonModules.Sign:
|
||||
SignManager signManager = new SignManager();
|
||||
await signManager.Run();
|
||||
break;
|
||||
default:
|
||||
Logger.Error("Code error: Module manager not implemented.");
|
||||
return;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Codecepticon": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@
|
||||
"properties": "%properties%",
|
||||
"variables": "%variables%",
|
||||
"parameters": "%parameters%",
|
||||
"structs": "%structs%",
|
||||
"cmdline": "%cmdline%"
|
||||
}
|
||||
</script>
|
||||
|
||||
+270
-16
@@ -60,9 +60,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General -->
|
||||
<!-- Modules -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">General</div>
|
||||
<div class="card-header">Module Selection</div>
|
||||
<div class="card-body">
|
||||
<!-- Module -->
|
||||
<div class="mb-3">
|
||||
@@ -71,15 +71,24 @@
|
||||
<option value="csharp">C#</option>
|
||||
<option value="powershell">PowerShell</option>
|
||||
<option value="vb6">VB6/VBA (Macro)</option>
|
||||
<option value="sign">Sign</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- General -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">General</div>
|
||||
<div class="card-body">
|
||||
<!-- Action -->
|
||||
<div class="mb-3">
|
||||
<label for="action" class="form-label">Action</label>
|
||||
<select id="action" class="form-select">
|
||||
<option value="obfuscate">Obfuscate</option>
|
||||
<option value="unmap">Unmap</option>
|
||||
<option value="obfuscate" class="family-obfuscation">Obfuscate</option>
|
||||
<option value="unmap" class="family-obfuscation">Unmap</option>
|
||||
<option value="cert" class="family-signing d-none">Generate Certificate</option>
|
||||
<option value="sign" class="family-signing d-none">Sign Executable</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -258,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">
|
||||
@@ -316,7 +331,7 @@
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="markov-min-words" class="form-label">Min Markov Concatenated Words<span class="ms-1 text-danger">*</span></label>
|
||||
<input type="number" id="markov-min-words" class="form-control" value="1">
|
||||
<input type="number" id="markov-min-words" class="form-control" value="3">
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="markov-max-words" class="form-label">Max Markov Concatenated Words<span class="ms-1 text-danger">*</span></label>
|
||||
@@ -410,6 +425,98 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-box-sign">
|
||||
<div class="card mt-5">
|
||||
<div class="card-header"><span class="box-cert">Generate Certificate</span><span class="box-sign d-none">Sign Executable</span></div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<!-- Certificate Subject -->
|
||||
<div class="mb-3 box-cert">
|
||||
<label for="subject" class="form-label">Subject</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="text" id="subject" class="form-control" value="" placeholder="CN=Microsoft Windows,C=US">
|
||||
</div>
|
||||
|
||||
<!-- Certificate Issuer -->
|
||||
<div class="mb-3 box-cert">
|
||||
<label for="issuer" class="form-label">Issuer</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="text" id="issuer" class="form-control" value="" placeholder="CN=Microsoft Issuer,C=US">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<!-- Copy certificate subject/issuer from an existing file -->
|
||||
<div class="mb-3 box-cert">
|
||||
<label for="copy-from" class="form-label">Copy Subject/Issuer from Existing File</label>
|
||||
<input type="text" id="copy-from" class="form-control" value="" placeholder="C:\Windows\System32\WerFault.exe">
|
||||
<small class="fst-italic">This will load the Subject and Issuer from the existing file. If you also set the subject/issuer on the left, those values will supercede the loaded values from the existing file.</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Not Before -->
|
||||
<div class="mb-3 box-cert">
|
||||
<label for="not-before" class="form-label">Not Before</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="datetime-local" id="not-before" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Not After -->
|
||||
<div class="mb-3 box-cert">
|
||||
<label for="not-after" class="form-label">Not After</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="datetime-local" id="not-after" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Executable Path -->
|
||||
<div class="mb-3 box-sign">
|
||||
<label for="executable" class="form-label">Executable Path</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="text" id="executable" class="form-control" value="" placeholder="C:\File\To\Sign.exe">
|
||||
</div>
|
||||
|
||||
<!-- Pfx Location -->
|
||||
<div class="mb-3 box-cert box-sign">
|
||||
<label for="pfx-file" class="form-label">PFX File Path</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="text" id="pfx-file" class="form-control" value="" placeholder="C:\Somewhere\PfxFile.pfx">
|
||||
</div>
|
||||
|
||||
<!-- Overwrite -->
|
||||
<div class="mb-3 box-cert">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="overwrite">
|
||||
<label class="form-check-label" for="overwrite">Overwrite if exists</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pfx Password -->
|
||||
<div class="mb-3 box-cert box-sign">
|
||||
<label for="password" class="form-label">PFX Password</label><span class="ms-1 text-danger">*</span>
|
||||
<input type="text" id="password" class="form-control" value="">
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -458,6 +565,19 @@
|
||||
quoteIfNeeded: function(value) { return this.format() == 'console' ? this.encloseInQuotes(value) : value; },
|
||||
buildPath: function() { return this.element('#build-path').value; },
|
||||
saveAs: function() { return this.element('#save-as').value; },
|
||||
certSubject: function() { return this.element('#subject').value; },
|
||||
certIssuer: function() { return this.element('#issuer').value; },
|
||||
certNotBefore: function() { return this.getDateTime('#not-before'); },
|
||||
certNotAfter: function() { return this.getDateTime('#not-after'); },
|
||||
certPfxFile: function() { return this.element('#pfx-file').value; },
|
||||
certPfxPassword: function() { return this.element('#password').value; },
|
||||
certOverwrite: function() { return this.element('#overwrite').checked; },
|
||||
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); },
|
||||
getTimestamp: function() { return this.element('#timestamp').value; },
|
||||
getSignatureAlgorithm: function() { return this.element('#algorithm').value; },
|
||||
fileToSign: function() { return this.element('#executable').value; },
|
||||
|
||||
getRenameItems: function(module) {
|
||||
let rename = [];
|
||||
@@ -490,14 +610,31 @@
|
||||
},
|
||||
|
||||
processUI: function() {
|
||||
switch (PageData.action()) {
|
||||
case "obfuscate":
|
||||
PageData.hide('.main-box-unmap');
|
||||
PageData.show('.main-box-obfuscate');
|
||||
// To keep this simple, split into families first.
|
||||
switch (PageData.module()) {
|
||||
case "csharp":
|
||||
case "powershell":
|
||||
case "vb6":
|
||||
PageData.hide('.main-box-sign');
|
||||
PageData.show('.main-box-unmap, .main-box-obfuscate');
|
||||
|
||||
PageData.show('.family-obfuscation');
|
||||
if (PageData.element('#action').value == 'cert' || PageData.element('#action').value == 'sign') {
|
||||
PageData.element('#action').value = 'obfuscate';
|
||||
}
|
||||
|
||||
PageData.hide('.family-signing');
|
||||
break;
|
||||
case "unmap":
|
||||
PageData.show('.main-box-unmap');
|
||||
PageData.hide('.main-box-obfuscate');
|
||||
case "sign":
|
||||
PageData.hide('.main-box-unmap, .main-box-obfuscate');
|
||||
PageData.show('.main-box-sign');
|
||||
|
||||
PageData.show('.family-signing');
|
||||
if (PageData.element('#action').value == 'obfuscate' || PageData.element('#action').value == 'unmap') {
|
||||
PageData.element('#action').value = 'cert';
|
||||
}
|
||||
PageData.hide('.family-obfuscation');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -514,11 +651,32 @@
|
||||
PageData.show('.box-vb6, .box-save-as');
|
||||
PageData.hide('.box-powershell, .box-csharp');
|
||||
break;
|
||||
case "sign":
|
||||
break;
|
||||
default:
|
||||
PageData.hide('.box-vb6, .box-powershell, .box-csharp, .box-save-as');
|
||||
break;
|
||||
}
|
||||
|
||||
switch (PageData.action()) {
|
||||
case "obfuscate":
|
||||
PageData.hide('.main-box-unmap');
|
||||
PageData.show('.main-box-obfuscate');
|
||||
break;
|
||||
case "unmap":
|
||||
PageData.show('.main-box-unmap');
|
||||
PageData.hide('.main-box-obfuscate');
|
||||
break;
|
||||
case "cert":
|
||||
PageData.hide('.box-sign');
|
||||
PageData.show('.box-cert');
|
||||
break;
|
||||
case "sign":
|
||||
PageData.hide('.box-cert');
|
||||
PageData.show('.box-sign');
|
||||
break;
|
||||
}
|
||||
|
||||
PageData.build() ? PageData.show('.box-build-path') : PageData.hide('.box-build-path');
|
||||
PageData.rename() ? PageData.show('.box-rename') : PageData.hide('.box-rename');
|
||||
PageData.rewriteStrings() ? PageData.show('.box-rewrite') : PageData.hide('.box-rewrite');
|
||||
@@ -562,6 +720,8 @@
|
||||
PageData.show('.box-rewrite-external-file');
|
||||
break;
|
||||
}
|
||||
|
||||
CodecepticonCmdGenerator.process();
|
||||
},
|
||||
|
||||
bindElements: function() {
|
||||
@@ -766,11 +926,105 @@
|
||||
return output;
|
||||
},
|
||||
|
||||
__generateSigningCommand: function() {
|
||||
let output = {};
|
||||
|
||||
output['action'] = 'cert';
|
||||
|
||||
// Module.
|
||||
if (PageData.module().length > 0) {
|
||||
output['module'] = PageData.module();
|
||||
}
|
||||
|
||||
// Action.
|
||||
if (PageData.action().length > 0) {
|
||||
output['action'] = PageData.action();
|
||||
}
|
||||
|
||||
// Verbose.
|
||||
if (PageData.verbose()) {
|
||||
output['verbose'] = true;
|
||||
}
|
||||
|
||||
// Debug.
|
||||
if (PageData.debug()) {
|
||||
output['debug'] = true;
|
||||
}
|
||||
|
||||
if (output['action'] == 'cert') {
|
||||
// Subject.
|
||||
if (PageData.certSubject().length > 0) {
|
||||
output['subject'] = PageData.quoteIfNeeded(PageData.certSubject());
|
||||
}
|
||||
|
||||
// Issuer.
|
||||
if (PageData.certIssuer().length > 0) {
|
||||
output['issuer'] = PageData.quoteIfNeeded(PageData.certIssuer());
|
||||
}
|
||||
|
||||
// Copy From.
|
||||
if (PageData.certCopyFrom().length > 0) {
|
||||
output['copy-from'] = PageData.quoteIfNeeded(PageData.certCopyFrom());
|
||||
}
|
||||
|
||||
// NotBefore.
|
||||
if (PageData.certNotBefore().length > 0) {
|
||||
output['not-before'] = PageData.quoteIfNeeded(PageData.certNotBefore());
|
||||
}
|
||||
|
||||
// NotAfter.
|
||||
if (PageData.certNotAfter().length > 0) {
|
||||
output['not-after'] = PageData.quoteIfNeeded(PageData.certNotAfter());
|
||||
}
|
||||
}
|
||||
|
||||
// Pfx File.
|
||||
if (PageData.certPfxFile().length > 0) {
|
||||
output['pfx-file'] = PageData.quoteIfNeeded(PageData.certPfxFile());
|
||||
|
||||
// Check if we need to overwrite.
|
||||
if (PageData.certOverwrite() && output['action'] == 'cert') {
|
||||
output['overwrite'] = true;
|
||||
}
|
||||
|
||||
// Password.
|
||||
if (PageData.certPfxPassword().length > 0) {
|
||||
output['password'] = PageData.quoteIfNeeded(PageData.certPfxPassword());
|
||||
}
|
||||
}
|
||||
|
||||
if (output['action'] == 'sign') {
|
||||
// File to sign.
|
||||
if (PageData.fileToSign().length > 0) {
|
||||
output['path'] = PageData.quoteIfNeeded(PageData.fileToSign());
|
||||
}
|
||||
|
||||
if (PageData.getTimestamp().length > 0) {
|
||||
output['timestamp'] = PageData.getTimestamp();
|
||||
}
|
||||
|
||||
if (PageData.getSignatureAlgorithm().length > 0) {
|
||||
output['algorithm'] = PageData.getSignatureAlgorithm();
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
process: function() {
|
||||
let commandLine = PageData.action() == 'obfuscate'
|
||||
? CodecepticonCmdGenerator.__generateObfuscationCommand()
|
||||
: CodecepticonCmdGenerator.__generateUnmappingCommand();
|
||||
;
|
||||
let commandLine = '';
|
||||
switch (PageData.action()) {
|
||||
case 'obfuscate':
|
||||
commandLine = CodecepticonCmdGenerator.__generateObfuscationCommand();
|
||||
break;
|
||||
case 'unmap':
|
||||
commandLine = CodecepticonCmdGenerator.__generateUnmappingCommand();
|
||||
break;
|
||||
case 'cert':
|
||||
case 'sign':
|
||||
commandLine = CodecepticonCmdGenerator.__generateSigningCommand();
|
||||
break;
|
||||
}
|
||||
PageData.commandLine(CodecepticonCmdGenerator.__buildCommandLine(commandLine));
|
||||
},
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Codecepticon
|
||||
# Codecepticon 
|
||||
|
||||
## 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
|
||||
|
||||
@@ -116,6 +117,10 @@ However, some values may exist in more than one category:
|
||||
|
||||
Therefore it is **critical** to always test your result in a local environment first.
|
||||
|
||||
### 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.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why isn't there a compiled version under Releases that I can download?
|
||||
@@ -163,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
|
||||
@@ -21,6 +21,9 @@
|
||||
* [C#](#c)
|
||||
* [VBA](#vba)
|
||||
* [PowerShell](#powershell)
|
||||
* [Signing Executables](#signing-executables)
|
||||
* [Generating Certificates](#generating-certificates)
|
||||
* [Signing](#signing)
|
||||
* [Mapping](#mapping)
|
||||
* [Unmapping](#unmapping)
|
||||
|
||||
@@ -170,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.
|
||||
@@ -192,6 +196,16 @@ The level of customisation supported by the PowerShell module is:
|
||||
|
||||
These restrictions exist for similar reasons as to the ones under [VBA](#vba).
|
||||
|
||||
## Signing Executables
|
||||
|
||||
### Generating Certificates
|
||||
|
||||
Codecepticon can be used to generate code signing certificates - which of course will be self-signed. The main requirement is that the Subject and Issuer are set to valid strings as those will be parsed by BouncyCastle during generation. It is also possible to copy the Subject and Issuer from existing files, by using the `--copy-from` argument and then passing an existing signed file.
|
||||
|
||||
### Signing
|
||||
|
||||
Once a PFX file has been created, it can be used to signed any executable you want, by using the `sign` action. Of course, it is possible to use any PFX file as long as it contains both the private and public key.
|
||||
|
||||
## Mapping
|
||||
|
||||
After each obfuscation, a mapping between the original and new names/values/etc is added into an HTML file that is easily searchable. Unless explicitly specified in the XML config or command line, this file will be created in the target's path by adding a `.html` extension in its name. So if your target is `C:\Something\Rubeus\Rubeus.sln` the mapping file will be `C:\Something\Rubeus\Rubeus.sln.html`. That file should act as a reference point for the commands you want to run, as the command line is very likely to have changed.
|
||||
|
||||
+5
-1
@@ -46,4 +46,8 @@ If you find yourself in a situation where you can run an executable but you cann
|
||||
|
||||
Usually you will not need the entire functionality that tools like `Rubeus`, `SharpHound`, or `SharpView` have to offer, however the larger the codebase is the easier it is to fingerprint different aspects of it. Therefore, consider trimming down a project until you are left with only the required functionality.
|
||||
|
||||
[JetBrains Resharper](https://www.jetbrains.com/resharper/) offers functionality to identify unused methods, classes, declarations, etc, to assist with this.
|
||||
[JetBrains Resharper](https://www.jetbrains.com/resharper/) offers functionality to identify unused methods, classes, declarations, etc, to assist with this.
|
||||
|
||||
## Sign Your Executables
|
||||
|
||||
It's quite suprising that by simply signing an executable can bypass some AV vendors, even if the certificate is invalid.
|
||||
Reference in New Issue
Block a user