diff --git a/BurnOutSharp.Builder/AbstractSyntaxNotationOne.cs b/BurnOutSharp.Builder/AbstractSyntaxNotationOne.cs
new file mode 100644
index 00000000..5997dc27
--- /dev/null
+++ b/BurnOutSharp.Builder/AbstractSyntaxNotationOne.cs
@@ -0,0 +1,383 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+
+namespace BurnOutSharp.Builder
+{
+ ///
+ /// ASN.1 type indicators
+ ///
+ [Flags]
+ public enum ASN1Type : byte
+ {
+ #region Modifiers
+
+ V_ASN1_UNIVERSAL = 0x00,
+ V_ASN1_PRIMITIVE_TAG = 0x1F,
+ V_ASN1_CONSTRUCTED = 0x20,
+ V_ASN1_APPLICATION = 0x40,
+ V_ASN1_CONTEXT_SPECIFIC = 0x80,
+ V_ASN1_PRIVATE = 0xC0,
+
+ #endregion
+
+ #region Types
+
+ V_ASN1_EOC = 0x00,
+ V_ASN1_BOOLEAN = 0x01,
+ V_ASN1_INTEGER = 0x02,
+ V_ASN1_BIT_STRING = 0x03,
+ V_ASN1_OCTET_STRING = 0x04,
+ V_ASN1_NULL = 0x05,
+ V_ASN1_OBJECT = 0x06,
+ V_ASN1_OBJECT_DESCRIPTOR = 0x07,
+ V_ASN1_EXTERNAL = 0x08,
+ V_ASN1_REAL = 0x09,
+ V_ASN1_ENUMERATED = 0x0A,
+ V_ASN1_UTF8STRING = 0x0C,
+ V_ASN1_SEQUENCE = 0x10,
+ V_ASN1_SET = 0x11,
+ V_ASN1_NUMERICSTRING = 0x12,
+ V_ASN1_PRINTABLESTRING = 0x13,
+ V_ASN1_T61STRING = 0x14,
+ V_ASN1_TELETEXSTRING = 0x14,
+ V_ASN1_VIDEOTEXSTRING = 0x15,
+ V_ASN1_IA5STRING = 0x16,
+ V_ASN1_UTCTIME = 0x17,
+ V_ASN1_GENERALIZEDTIME = 0x18,
+ V_ASN1_GRAPHICSTRING = 0x19,
+ V_ASN1_ISO64STRING = 0x1A,
+ V_ASN1_VISIBLESTRING = 0x1A,
+ V_ASN1_GENERALSTRING = 0x1B,
+ V_ASN1_UNIVERSALSTRING = 0x1C,
+ V_ASN1_BMPSTRING = 0x1E,
+
+ #endregion
+ }
+
+ ///
+ /// ASN.1 Parser
+ ///
+ public class AbstractSyntaxNotationOne
+ {
+ ///
+ /// Parse a byte array into a DER-encoded ASN.1 structure
+ ///
+ /// Byte array representing the data
+ /// Current pointer into the data
+ ///
+ public static List Parse(byte[] data, int pointer)
+ {
+ // Create the output list to return
+ var topLevelValues = new List();
+
+ // Loop through the data and return all top-level values
+ while (pointer < data.Length)
+ {
+ var topLevelValue = new ASN1TypeLengthValue(data, ref pointer);
+ topLevelValues.Add(topLevelValue);
+ }
+
+ return topLevelValues;
+ }
+ }
+
+ ///
+ /// ASN.1 type/length/value class that all types are based on
+ ///
+ public class ASN1TypeLengthValue
+ {
+ ///
+ /// The ASN.1 type
+ ///
+ public ASN1Type Type { get; private set; }
+
+ ///
+ /// Length of the value
+ ///
+ public ulong Length { get; private set; }
+
+ ///
+ /// Generic value associated with
+ ///
+ public object Value { get; private set; }
+
+ ///
+ /// Read from the source data array at an index
+ ///
+ /// Byte array representing data to read
+ /// Index within the array to read at
+ public ASN1TypeLengthValue(byte[] data, ref int index)
+ {
+ // Get the type and modifiers
+ this.Type = (ASN1Type)data[index++];
+
+ // If we have an end indicator, we just return
+ if (this.Type == ASN1Type.V_ASN1_EOC)
+ return;
+
+ // Get the length of the value
+ this.Length = ReadLength(data, ref index);
+
+ // Read the value
+ if (this.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
+ {
+ var valueList = new List();
+
+ int currentIndex = index;
+ while (index < currentIndex + (int)this.Length)
+ {
+ valueList.Add(new ASN1TypeLengthValue(data, ref index));
+ }
+
+ this.Value = valueList.ToArray();
+ }
+ else
+ {
+ // TODO: Get more granular based on type
+ this.Value = data.ReadBytes(ref index, (int)this.Length);
+ }
+ }
+
+ ///
+ /// Format the TLV as a string
+ ///
+ /// [UNUSED] Padding level of the item when formatting
+ /// String representing the TLV, if possible
+ public string Format(int paddingLevel = 0)
+ {
+ // Create the left-padding string
+ string padding = new string(' ', paddingLevel);
+
+ // If we have an invalid item
+ if (this.Type == 0)
+ return $"{padding}UNKNOWN TYPE";
+
+ // Create the string builder
+ StringBuilder formatBuilder = new StringBuilder();
+
+ // Append the type
+ formatBuilder.Append($"{padding}Type: {this.Type}");
+ if (this.Type == ASN1Type.V_ASN1_EOC)
+ return formatBuilder.ToString();
+
+ // Append the length
+ formatBuilder.Append($", Length: {this.Length}");
+ if (this.Length == 0)
+ return formatBuilder.ToString();
+
+ // If we have a constructed type
+ if (this.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
+ {
+ var valueAsObjectArray = this.Value as ASN1TypeLengthValue[];
+ if (valueAsObjectArray == null)
+ {
+ formatBuilder.Append(", Value: [INVALID DATA TYPE]");
+ return formatBuilder.ToString();
+ }
+
+ formatBuilder.Append(", Value:\n");
+ for (int i = 0; i < valueAsObjectArray.Length; i++)
+ {
+ var child = valueAsObjectArray[i];
+ string childString = child.Format(paddingLevel + 1);
+ formatBuilder.Append($"{childString}\n");
+ }
+
+ return formatBuilder.ToString().TrimEnd('\n');
+ }
+
+ // Get the value as a byte array
+ byte[] valueAsByteArray = this.Value as byte[];
+ if (valueAsByteArray == null)
+ {
+ formatBuilder.Append(", Value: [INVALID DATA TYPE]");
+ return formatBuilder.ToString();
+ }
+
+ // If we have a primitive type
+ switch (this.Type)
+ {
+ ///
+ case ASN1Type.V_ASN1_BOOLEAN:
+ if (this.Length > 1 || valueAsByteArray.Length > 1)
+ formatBuilder.Append($" [Expected length of 1]");
+
+ bool booleanValue = valueAsByteArray[0] == 0x00 ? false : true;
+ formatBuilder.Append($", Value: {booleanValue}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_INTEGER:
+ Array.Reverse(valueAsByteArray);
+ BigInteger integerValue = new BigInteger(valueAsByteArray);
+ formatBuilder.Append($", Value: {integerValue}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_BIT_STRING:
+ // TODO: Read into a BitArray and print that out instead?
+ int unusedBits = valueAsByteArray[0];
+ formatBuilder.Append($", Value with {unusedBits} unused bits: {BitConverter.ToString(valueAsByteArray.Skip(1).ToArray()).Replace('-', ' ')}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_OCTET_STRING:
+ formatBuilder.Append($", Value: {BitConverter.ToString(valueAsByteArray).Replace('-', ' ')}");
+ break;
+
+ ///
+ ///
+ case ASN1Type.V_ASN1_OBJECT:
+ // The first byte contains nodes 1 and 2
+ int firstNode = Math.DivRem(valueAsByteArray[0], 40, out int secondNode);
+
+ // If there are only 2 nodes
+ if (this.Length == 1)
+ {
+ formatBuilder.Append($", Value: {firstNode}.{secondNode}");
+ break;
+ }
+
+ // Create a list for all remaining nodes
+ List objectNodes = new List();
+
+ // All other nodes are encoded uniquely
+ int objectValueOffset = 1;
+ while (objectValueOffset < (long)this.Length)
+ {
+ // If bit 7 is not set
+ if ((valueAsByteArray[objectValueOffset] & 0x80) == 0)
+ {
+ objectNodes.Add(valueAsByteArray[objectValueOffset]);
+ objectValueOffset++;
+ continue;
+ }
+
+ // Otherwise, read the encoded value in a loop
+ ulong dotValue = 0;
+ bool doneProcessing = false;
+
+ do
+ {
+ // Shift the current encoded value
+ dotValue <<= 7;
+
+ // If we have a leading zero byte, we're at the end
+ if ((valueAsByteArray[objectValueOffset] & 0x80) == 0)
+ doneProcessing = true;
+
+ // Clear the top byte
+ unchecked { valueAsByteArray[objectValueOffset] &= (byte)~0x80; }
+
+ // Add the new value to the result
+ dotValue |= valueAsByteArray[objectValueOffset];
+
+ // Increment the offset
+ objectValueOffset++;
+ } while (objectValueOffset < valueAsByteArray.Length && !doneProcessing);
+
+ // Add the parsed value to the output
+ objectNodes.Add(dotValue);
+ }
+
+ // TODO: Add dot form decoding to this
+ formatBuilder.Append($", Value: {firstNode}.{secondNode}.{string.Join(".", objectNodes)}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_UTF8STRING:
+ formatBuilder.Append($", Value: {Encoding.UTF8.GetString(valueAsByteArray)}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_PRINTABLESTRING:
+ formatBuilder.Append($", Value: {Encoding.ASCII.GetString(valueAsByteArray)}");
+ break;
+
+ //case ASN1Type.V_ASN1_T61STRING:
+ case ASN1Type.V_ASN1_TELETEXSTRING:
+ formatBuilder.Append($", Value: {Encoding.ASCII.GetString(valueAsByteArray)}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_IA5STRING:
+ formatBuilder.Append($", Value: {Encoding.ASCII.GetString(valueAsByteArray)}");
+ break;
+
+ case ASN1Type.V_ASN1_UTCTIME:
+ string utctimeString = Encoding.ASCII.GetString(valueAsByteArray);
+ if (DateTime.TryParse(utctimeString, out DateTime utctimeDateTime))
+ formatBuilder.Append($", Value: {utctimeDateTime}");
+ else
+ formatBuilder.Append($", Value: {utctimeString}");
+ break;
+
+ ///
+ case ASN1Type.V_ASN1_BMPSTRING:
+ formatBuilder.Append($", Value: {Encoding.Unicode.GetString(valueAsByteArray)}");
+ break;
+
+ default:
+ formatBuilder.Append($", Value (Unknown Format): {BitConverter.ToString(this.Value as byte[]).Replace('-', ' ')}");
+ break;
+ }
+
+ // Return the formatted string
+ return formatBuilder.ToString();
+ }
+
+ ///
+ /// Reads the length field for a type
+ ///
+ /// Byte array representing data to read
+ /// Index within the array to read at
+ /// The length value read from the array
+ private static ulong ReadLength(byte[] data, ref int index)
+ {
+ // If we have invalid data, throw an exception
+ if (data == null || index < 0 && index >= data.Length)
+ throw new ArgumentException();
+
+ // Read the first byte, assuming it's the length
+ byte length = data[index++];
+
+ // If the bit 7 is not set, then use the value as it is
+ if ((length & 0x80) == 0)
+ return length;
+
+ // Otherwise, use the value as the number of remaining bytes to read
+ int bytesToRead = length & ~0x80;
+ byte[] bytesRead = data.ReadBytes(ref index, bytesToRead);
+
+ // TODO: Write extensions to read big-endian
+
+ // Reverse the bytes to be in big-endian order
+ Array.Reverse(bytesRead);
+
+ switch (bytesRead.Length)
+ {
+ case 1:
+ return bytesRead[0];
+ case 2:
+ return BitConverter.ToUInt16(bytesRead, 0);
+ case 3:
+ Array.Resize(ref bytesRead, 4);
+ goto case 4;
+ case 4:
+ return BitConverter.ToUInt32(bytesRead, 0);
+ case 5:
+ case 6:
+ case 7:
+ Array.Resize(ref bytesRead, 8);
+ goto case 8;
+ case 8:
+ return BitConverter.ToUInt64(bytesRead, 0);
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
diff --git a/BurnOutSharp.Builder/Extensions.cs b/BurnOutSharp.Builder/Extensions.cs
index a0893db9..fffa06b0 100644
--- a/BurnOutSharp.Builder/Extensions.cs
+++ b/BurnOutSharp.Builder/Extensions.cs
@@ -24,6 +24,10 @@ namespace BurnOutSharp.Builder
///
public static byte[] ReadBytes(this byte[] content, ref int offset, int count)
{
+ // If there's an invalid byte count, don't do anything
+ if (count == 0)
+ return null;
+
byte[] buffer = new byte[count];
Array.Copy(content, offset, buffer, 0, Math.Min(count, content.Length - offset));
offset += count;
@@ -158,6 +162,10 @@ namespace BurnOutSharp.Builder
///
public static byte[] ReadBytes(this Stream stream, int count)
{
+ // If there's an invalid byte count, don't do anything
+ if (count == 0)
+ return null;
+
byte[] buffer = new byte[count];
stream.Read(buffer, 0, count);
return buffer;
diff --git a/BurnOutSharp.Builder/PortableExecutable.cs b/BurnOutSharp.Builder/PortableExecutable.cs
index 4d3b7e86..790d56a4 100644
--- a/BurnOutSharp.Builder/PortableExecutable.cs
+++ b/BurnOutSharp.Builder/PortableExecutable.cs
@@ -708,9 +708,13 @@ namespace BurnOutSharp.Builder
entry.Revision = (WindowsCertificateRevision)data.ReadUInt16(ref offset);
entry.CertificateType = (WindowsCertificateType)data.ReadUInt16(ref offset);
if (entry.Length > 0)
- entry.Certificate = data.ReadBytes(ref offset, (int)entry.Length);
+ entry.Certificate = data.ReadBytes(ref offset, (int)entry.Length - 8);
attributeCertificateTable.Add(entry);
+
+ // Align to the 8-byte boundary
+ while ((offset % 8) != 0)
+ _ = data.ReadByte(ref offset);
}
return attributeCertificateTable.ToArray();
@@ -1896,9 +1900,13 @@ namespace BurnOutSharp.Builder
entry.Revision = (WindowsCertificateRevision)data.ReadUInt16();
entry.CertificateType = (WindowsCertificateType)data.ReadUInt16();
if (entry.Length > 0)
- entry.Certificate = data.ReadBytes((int)entry.Length);
+ entry.Certificate = data.ReadBytes((int)entry.Length - 8);
attributeCertificateTable.Add(entry);
+
+ // Align to the 8-byte boundary
+ while ((data.Position % 8) != 0)
+ _ = data.ReadByteValue();
}
return attributeCertificateTable.ToArray();
diff --git a/ExecutableTest/Program.cs b/ExecutableTest/Program.cs
index 2f818279..e9f8e4fb 100644
--- a/ExecutableTest/Program.cs
+++ b/ExecutableTest/Program.cs
@@ -285,7 +285,6 @@ namespace ExecutableTest
}
}
-
if (executable.ResourceTable.TypeAndNameStrings.Count == 0)
{
Console.WriteLine(" No resource table type/name strings");
@@ -781,8 +780,34 @@ namespace ExecutableTest
Console.WriteLine($" Length = {entry.Length}");
Console.WriteLine($" Revision = {entry.Revision}");
Console.WriteLine($" Certificate type = {entry.CertificateType}");
- //Console.WriteLine($" Certificate = {BitConverter.ToString(entry.Certificate).Replace("-", string.Empty)}");
- // TODO: Add certificate type parsing
+ Console.WriteLine();
+ if (entry.CertificateType == BurnOutSharp.Models.PortableExecutable.WindowsCertificateType.WIN_CERT_TYPE_PKCS_SIGNED_DATA)
+ {
+ Console.WriteLine($" Certificate Data [Formatted]");
+ Console.WriteLine(" -------------------------");
+ var topLevelValues = AbstractSyntaxNotationOne.Parse(entry.Certificate, pointer: 0);
+ if (topLevelValues == null)
+ {
+ Console.WriteLine(" INVALID DATA FOUND");
+ Console.WriteLine($" {BitConverter.ToString(entry.Certificate).Replace("-", string.Empty)}");
+ }
+ else
+ {
+ foreach (ASN1TypeLengthValue tlv in topLevelValues)
+ {
+ string tlvString = tlv.Format(paddingLevel: 4);
+ Console.WriteLine(tlvString);
+ }
+ }
+ }
+ else
+ {
+ Console.WriteLine($" Certificate Data [Binary]");
+ Console.WriteLine(" -------------------------");
+ Console.WriteLine($" {BitConverter.ToString(entry.Certificate).Replace("-", string.Empty)}");
+ }
+
+ Console.WriteLine();
}
}
Console.WriteLine();