obfuz/Plugins/dnlib/DotNet/StrongNameSigner.cs

172 lines
5.9 KiB
C#
Raw Normal View History

2025-04-08 20:31:44 +08:00
// dnlib: See LICENSE.txt for more info
using System;
using System.IO;
using System.Security.Cryptography;
namespace dnlib.DotNet {
/// <summary>
/// Strong name signs an assembly. It supports normal strong name signing and the new
/// (.NET Framework 4.5) enhanced strong name signing.
/// </summary>
public readonly struct StrongNameSigner {
readonly Stream stream;
readonly long baseOffset;
/// <summary>
/// Constructor
/// </summary>
/// <param name="stream">.NET PE file stream</param>
public StrongNameSigner(Stream stream)
: this(stream, 0) {
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="stream">.NET PE file stream</param>
/// <param name="baseOffset">Offset in <paramref name="stream"/> of the first byte of
/// the PE file.</param>
public StrongNameSigner(Stream stream, long baseOffset) {
this.stream = stream;
this.baseOffset = baseOffset;
}
/// <summary>
/// Calculates the strong name signature and writes it to the stream. The signature
/// is also returned.
/// </summary>
/// <param name="snk">Strong name key used for signing</param>
/// <param name="snSigOffset">Offset (relative to the start of the PE file) of the strong
/// name signature.</param>
/// <returns>The strong name signature</returns>
public byte[] WriteSignature(StrongNameKey snk, long snSigOffset) {
var sign = CalculateSignature(snk, snSigOffset);
stream.Position = baseOffset + snSigOffset;
stream.Write(sign, 0, sign.Length);
return sign;
}
/// <summary>
/// Calculates and returns the strong name signature
/// </summary>
/// <param name="snk">Strong name key used for signing</param>
/// <param name="snSigOffset">Offset (relative to start of PE file) of the strong
/// name signature.</param>
/// <returns>The strong name signature</returns>
public byte[] CalculateSignature(StrongNameKey snk, long snSigOffset) {
uint snSigSize = (uint)snk.SignatureSize;
var hashAlg = snk.HashAlgorithm == 0 ? AssemblyHashAlgorithm.SHA1 : snk.HashAlgorithm;
var hash = StrongNameHashData(hashAlg, snSigOffset, snSigSize);
var snSig = GetStrongNameSignature(snk, hashAlg, hash);
if (snSig.Length != snSigSize)
throw new InvalidOperationException("Invalid strong name signature size");
return snSig;
}
/// <summary>
/// Strong name hashes the .NET file
/// </summary>
/// <param name="hashAlg">Hash algorithm</param>
/// <param name="snSigOffset">Strong name sig offset (relative to start of .NET PE file)</param>
/// <param name="snSigSize">Size of strong name signature</param>
/// <returns>The strong name hash of the .NET file</returns>
byte[] StrongNameHashData(AssemblyHashAlgorithm hashAlg, long snSigOffset, uint snSigSize) {
var reader = new BinaryReader(stream);
snSigOffset += baseOffset;
long snSigOffsetEnd = snSigOffset + snSigSize;
using (var hasher = new AssemblyHash(hashAlg)) {
var buffer = new byte[0x8000];
// Hash the DOS header. It's defined to be all data from the start of
// the file up to the NT headers.
stream.Position = baseOffset + 0x3C;
uint ntHeadersOffs = reader.ReadUInt32();
stream.Position = baseOffset;
hasher.Hash(stream, ntHeadersOffs, buffer);
// Hash NT headers, but hash authenticode + checksum as 0s
stream.Position += 6;
int numSections = reader.ReadUInt16();
stream.Position -= 8;
hasher.Hash(stream, 0x18, buffer); // magic + FileHeader
bool is32bit = reader.ReadUInt16() == 0x010B;
stream.Position -= 2;
int optHeaderSize = is32bit ? 0x60 : 0x70;
if (stream.Read(buffer, 0, optHeaderSize) != optHeaderSize)
throw new IOException("Could not read data");
// Clear checksum
for (int i = 0; i < 4; i++)
buffer[0x40 + i] = 0;
hasher.Hash(buffer, 0, optHeaderSize);
const int imageDirsSize = 16 * 8;
if (stream.Read(buffer, 0, imageDirsSize) != imageDirsSize)
throw new IOException("Could not read data");
// Clear authenticode data dir
for (int i = 0; i < 8; i++)
buffer[4 * 8 + i] = 0;
hasher.Hash(buffer, 0, imageDirsSize);
// Hash section headers
long sectHeadersOffs = stream.Position;
hasher.Hash(stream, (uint)numSections * 0x28, buffer);
// Hash all raw section data but make sure we don't hash the location
// where the strong name signature will be stored.
for (int i = 0; i < numSections; i++) {
stream.Position = sectHeadersOffs + i * 0x28 + 0x10;
uint sizeOfRawData = reader.ReadUInt32();
uint pointerToRawData = reader.ReadUInt32();
stream.Position = baseOffset + pointerToRawData;
while (sizeOfRawData > 0) {
var pos = stream.Position;
if (snSigOffset <= pos && pos < snSigOffsetEnd) {
uint skipSize = (uint)(snSigOffsetEnd - pos);
if (skipSize >= sizeOfRawData)
break;
sizeOfRawData -= skipSize;
stream.Position += skipSize;
continue;
}
if (pos >= snSigOffsetEnd) {
hasher.Hash(stream, sizeOfRawData, buffer);
break;
}
uint maxLen = (uint)Math.Min(snSigOffset - pos, sizeOfRawData);
hasher.Hash(stream, maxLen, buffer);
sizeOfRawData -= maxLen;
}
}
return hasher.ComputeHash();
}
}
/// <summary>
/// Returns the strong name signature
/// </summary>
/// <param name="snk">Strong name key</param>
/// <param name="hashAlg">Hash algorithm</param>
/// <param name="hash">Strong name hash of the .NET PE file</param>
/// <returns>Strong name signature</returns>
byte[] GetStrongNameSignature(StrongNameKey snk, AssemblyHashAlgorithm hashAlg, byte[] hash) {
using (var rsa = snk.CreateRSA()) {
var rsaFmt = new RSAPKCS1SignatureFormatter(rsa);
string hashName = hashAlg.GetName() ?? AssemblyHashAlgorithm.SHA1.GetName();
rsaFmt.SetHashAlgorithm(hashName);
var snSig = rsaFmt.CreateSignature(hash);
Array.Reverse(snSig);
return snSig;
}
}
}
}