Skip to content

Commit 2a7f055

Browse files
committed
Basic support for information extraction.
1 parent a9ce02a commit 2a7f055

12 files changed

+145
-11
lines changed

AuthenticodeLint/AuthenticodeLint.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
<Compile Include="CheckEngine.cs" />
5858
<Compile Include="CommandLineParser.cs" />
5959
<Compile Include="ConfigurationValidator.cs" />
60+
<Compile Include="Extraction.cs" />
6061
<Compile Include="GraphBuilders.cs" />
6162
<Compile Include="Interop\CertStoreSafeHandle.cs" />
6263
<Compile Include="Interop\Crypt32.cs" />
@@ -89,6 +90,7 @@
8990
<Compile Include="Signature.cs" />
9091
<Compile Include="SignatureExtractor.cs" />
9192
<Compile Include="Graph.cs" />
93+
<Compile Include="SignatureHasher.cs" />
9294
<Compile Include="SignerInfoExtensions.cs" />
9395
<Compile Include="StdOutRuleResultCollector.cs" />
9496
<Compile Include="VerboseSignatureTextWriter.cs" />

AuthenticodeLint/CheckEngine.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public RuleEngineResult RunAllRules(string file, Graph<Signature> signatures, Li
6565
}
6666
collectors.ForEach(c => c.CollectResult(rule, result, verboseWriter.Messages));
6767
}
68+
if (configuration.ExtractPath != null)
69+
{
70+
Extraction.ExtractToDisk(file, configuration, signatures);
71+
}
6872
collectors.ForEach(c => c.CompleteSet());
6973
return engineResult;
7074
}

AuthenticodeLint/ConfigurationValidator.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@ public class CheckConfiguration
1212
public HashSet<int> SuppressErrorIDs { get; }
1313
public bool Verbose { get; }
1414
public RevocationChecking RevocationMode {get;}
15+
public string ExtractPath { get; }
1516

16-
public CheckConfiguration(IReadOnlyList<string> inputPaths, string reportPath, bool quiet, HashSet<int> suppressErrorIDs, bool verbose, RevocationChecking revocationMode)
17+
public CheckConfiguration(IReadOnlyList<string> inputPaths, string reportPath, bool quiet, HashSet<int> suppressErrorIDs, bool verbose, RevocationChecking revocationMode, string extract)
1718
{
1819
InputPaths = inputPaths;
1920
ReportPath = reportPath;
2021
Quiet = quiet;
2122
SuppressErrorIDs = suppressErrorIDs;
2223
Verbose = verbose;
2324
RevocationMode = revocationMode;
25+
ExtractPath = extract;
2426
}
2527
}
2628

@@ -52,6 +54,13 @@ public static bool ValidateAndPrint(CheckConfiguration configuration, TextWriter
5254
success = false;
5355
}
5456
}
57+
if (configuration.ExtractPath != null)
58+
{
59+
if (!Directory.Exists(configuration.ExtractPath))
60+
{
61+
printer.WriteLine($"Directory {configuration.ExtractPath} does not exist.");
62+
}
63+
}
5564
return success;
5665
}
5766
}

AuthenticodeLint/Extraction.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using AuthenticodeLint.Interop;
2+
using System.IO;
3+
using System.Security.Cryptography.X509Certificates;
4+
using System.Text;
5+
6+
namespace AuthenticodeLint
7+
{
8+
public class Extraction
9+
{
10+
public static void ExtractToDisk(string file, CheckConfiguration configuration, Graph<Signature> signatureGraph)
11+
{
12+
var fileDirectory = Path.Combine(configuration.ExtractPath, Path.GetFileName(file));
13+
if (Directory.Exists(fileDirectory))
14+
{
15+
Directory.Delete(fileDirectory, true);
16+
}
17+
Directory.CreateDirectory(fileDirectory);
18+
var signatures = signatureGraph.VisitAll();
19+
foreach(var signature in signatures)
20+
{
21+
var signatureHash = HashHelpers.GetHashForSignature(signature.SignerInfo);
22+
var signatureDirectory = Path.Combine(fileDirectory, signatureHash);
23+
var certificateDirectory = Path.Combine(signatureDirectory, "Certificates");
24+
if (!Directory.Exists(certificateDirectory))
25+
{
26+
Directory.CreateDirectory(certificateDirectory);
27+
}
28+
foreach(var certificate in signature.AdditionalCertificates)
29+
{
30+
var thumbprint = certificate.Thumbprint;
31+
var serialized = SerializeCertificate(certificate);
32+
if (serialized != null)
33+
{
34+
File.WriteAllText(Path.Combine(certificateDirectory, thumbprint + ".cer"), serialized);
35+
}
36+
}
37+
}
38+
}
39+
40+
private static string SerializeCertificate(X509Certificate2 certificate)
41+
{
42+
string base64Certificate = null;
43+
var binaryCertificate = certificate.Export(X509ContentType.Cert);
44+
45+
uint size = 0;
46+
if (Crypt32.CryptBinaryToString(binaryCertificate, (uint)binaryCertificate.Length, CryptBinaryToStringFlags.CRYPT_STRING_BASE64HEADER, null, ref size))
47+
{
48+
var builder = new StringBuilder((int)size);
49+
if (Crypt32.CryptBinaryToString(binaryCertificate, (uint)binaryCertificate.Length, CryptBinaryToStringFlags.CRYPT_STRING_BASE64HEADER, builder, ref size))
50+
{
51+
base64Certificate = builder.ToString();
52+
}
53+
}
54+
return base64Certificate;
55+
}
56+
}
57+
}

AuthenticodeLint/Interop/Crypt32.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Runtime.InteropServices;
3+
using System.Text;
34

45
namespace AuthenticodeLint.Interop
56
{
@@ -121,6 +122,36 @@ public static unsafe extern bool CryptMsgGetParam
121122
[param: In] void* pvData,
122123
[param: In, Out, MarshalAs(UnmanagedType.U4)] ref uint pcbData
123124
);
125+
126+
[method: DllImport("crypt32.dll", CallingConvention = CallingConvention.Winapi, EntryPoint = "CryptBinaryToString", SetLastError = true)]
127+
[return: MarshalAs(UnmanagedType.Bool)]
128+
public static unsafe extern bool CryptBinaryToString
129+
(
130+
[param: In] byte[] pbBinary,
131+
[param: In, MarshalAs(UnmanagedType.U4)] uint cbBinary,
132+
[param: In, MarshalAs(UnmanagedType.U4)] CryptBinaryToStringFlags dwFlags,
133+
[param: In, Out] StringBuilder pszString,
134+
[param: In, Out] ref uint pcchString
135+
);
136+
}
137+
138+
internal enum CryptBinaryToStringFlags : uint
139+
{
140+
CRYPT_STRING_BASE64HEADER = 0x00000000,
141+
CRYPT_STRING_BASE64 = 0x00000001,
142+
CRYPT_STRING_BINARY = 0x00000002,
143+
CRYPT_STRING_BASE64REQUESTHEADER = 0x00000003,
144+
CRYPT_STRING_HEX = 0x00000004,
145+
CRYPT_STRING_HEXASCII = 0x00000005,
146+
CRYPT_STRING_BASE64X509CRLHEADER = 0x00000009,
147+
CRYPT_STRING_HEXADDR = 0x0000000a,
148+
CRYPT_STRING_HEXASCIIADDR = 0x0000000b,
149+
CRYPT_STRING_HEXRAW = 0x0000000c,
150+
CRYPT_STRING_STRICT = 0x20000000,
151+
152+
CRYPT_STRING_NOCRLF = 0x40000000,
153+
CRYPT_STRING_NOCR = 0x80000000,
154+
124155
}
125156

126157
internal enum CryptQueryObjectType : uint

AuthenticodeLint/Program.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ static int Main(string[] args)
2424
bool quiet = false;
2525
bool verbose = false;
2626
string report = null;
27+
string extract = null;
2728
var revocation = RevocationChecking.None;
2829
foreach(var parameter in parsedCommandLine)
2930
{
@@ -89,10 +90,15 @@ static int Main(string[] args)
8990
}
9091
verbose = true;
9192
}
93+
9294
else if (parameter.Name == "report")
9395
{
9496
report = parameter.Value;
9597
}
98+
else if (parameter.Name == "extract")
99+
{
100+
extract = parameter.Value;
101+
}
96102
else if (parameter.Name == "revocation")
97103
{
98104
if (string.IsNullOrWhiteSpace(parameter.Value))
@@ -117,7 +123,7 @@ static int Main(string[] args)
117123
Console.Error.WriteLine("Input is expected. See -help for usage.");
118124
return ExitCodes.InvalidInputOrConfig;
119125
}
120-
var configuration = new CheckConfiguration(inputs, report, quiet, suppress, verbose, revocation);
126+
var configuration = new CheckConfiguration(inputs, report, quiet, suppress, verbose, revocation, extract);
121127

122128
if (!ConfigurationValidator.ValidateAndPrint(configuration, Console.Error))
123129
{
@@ -162,6 +168,7 @@ Checks the Authenticode signature of your binaries.
162168
-report: A path to produce an XML file as a report. Optional.
163169
-verbose: Show verbose output. Cannot be combined with -quiet.
164170
-revocation: Specify how revocation checking is done. Valid values are none, offline, online. None is the default.
171+
-extract: Extracts all signature information to the specified directory.
165172
166173
Exit codes:
167174

AuthenticodeLint/Rules/NoUnknownCertificatesRule.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Runtime.InteropServices;
66
using System.Security.Cryptography;
77
using System.Security.Cryptography.X509Certificates;
8+
using System.Text;
89

910
namespace AuthenticodeLint.Rules
1011
{
@@ -20,11 +21,11 @@ public RuleResult Validate(Graph<Signature> graph, SignatureLogger verboseWriter
2021
{
2122
var result = RuleResult.Pass;
2223
var signatures = graph.VisitAll();
23-
foreach(var signature in signatures)
24+
foreach (var signature in signatures)
2425
{
2526
var allEmbeddedCertificates = signature.AdditionalCertificates.Cast<X509Certificate2>().ToList();
2627
var certificatesRequiringEliminiation = new HashSet<X509Certificate2>(allEmbeddedCertificates, new CertificateThumbprintComparer());
27-
foreach(var certificate in allEmbeddedCertificates)
28+
foreach (var certificate in allEmbeddedCertificates)
2829
{
2930
if (!certificatesRequiringEliminiation.Contains(certificate))
3031
{
@@ -38,15 +39,15 @@ public RuleResult Validate(Graph<Signature> graph, SignatureLogger verboseWriter
3839
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
3940
//All we care is that we can even find an authority.
4041
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags & ~X509VerificationFlags.AllowUnknownCertificateAuthority;
41-
if(chain.Build(certificate))
42+
if (chain.Build(certificate))
4243
{
4344
certificatesRequiringEliminiation.ExceptWith(chain.ChainElements.Cast<X509ChainElement>().Select(c => c.Certificate));
4445
}
4546
}
4647
}
4748
if (certificatesRequiringEliminiation.Count > 0)
4849
{
49-
foreach(var certificate in certificatesRequiringEliminiation)
50+
foreach (var certificate in certificatesRequiringEliminiation)
5051
{
5152
verboseWriter.LogSignatureMessage(signature.SignerInfo, $"Signature contained untrusted certificate \"{certificate.Subject}\" ({certificate.Thumbprint}).");
5253
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Security.Cryptography.Pkcs;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace AuthenticodeLint
9+
{
10+
public static class HashHelpers
11+
{
12+
public static string GetHashForSignature(SignerInfo signature)
13+
{
14+
var digest = signature.SignatureDigest();
15+
var digestString = digest.Aggregate(new StringBuilder(), (acc, b) => acc.AppendFormat("{0:x2}", b)).ToString();
16+
return digestString;
17+
}
18+
19+
public static string HexEncode(byte[] data)
20+
{
21+
return data.Aggregate(new StringBuilder(), (acc, b) => acc.AppendFormat("{0:x2}", b)).ToString();
22+
}
23+
}
24+
}

AuthenticodeLint/VerboseSignatureTextWriter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ public class MemorySignatureLogger : SignatureLogger
1313

1414
public override void LogSignatureMessage(SignerInfo signature, string message)
1515
{
16-
var digest = signature.SignatureDigest();
17-
var digestString = digest.Aggregate(new StringBuilder(), (acc, b) => acc.AppendFormat("{0:x2}", b)).ToString();
16+
var digestString = HashHelpers.GetHashForSignature(signature);
1817
Messages.Add($"Signature {digestString}: {message}");
1918
}
2019
}

AuthenticodeLintTests/Rules/PublisherInformationPresentRuleTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace AuthenticodeLintTests.Rules
77
{
88
public class PublisherInformationPresentRuleTests
99
{
10-
private static CheckConfiguration Configuration => new CheckConfiguration(new List<string>(), null, false, new HashSet<int>(), false, RevocationChecking.None);
10+
private static CheckConfiguration Configuration => new CheckConfiguration(new List<string>(), null, false, new HashSet<int>(), false, RevocationChecking.None, null);
1111

1212
private static Graph<Signature> GetGraphForFile(string file)
1313
{

0 commit comments

Comments
 (0)