Skip to content

Instantly share code, notes, and snippets.

@MichalStrehovsky
Created June 17, 2024 06:06
Show Gist options
  • Save MichalStrehovsky/0d9e467b750df3d22ecb53d8a4ba7c72 to your computer and use it in GitHub Desktop.
Save MichalStrehovsky/0d9e467b750df3d22ecb53d8a4ba7c72 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
PEHeaderBuilder peHeaderBuilder = new PEHeaderBuilder(machine: Machine.I386, imageCharacteristics: Characteristics.ExecutableImage);
ShimExeBuilder peBuilder = new ShimExeBuilder(peHeaderBuilder, "kernel32.dll", "ExitProcess");
BlobBuilder o = new BlobBuilder();
peBuilder.Serialize(o);
using var fs = File.Create("c:\\temp\\blah.exe");
o.WriteContentTo(fs);
class ShimExeBuilder : PEBuilder
{
private readonly byte[] _dllName;
private readonly byte[] _entrypointName;
private readonly PEDirectoriesBuilder _peDirectoriesBuilder;
private const string TextSectionName = ".text";
private const string RelocationSectionName = ".reloc";
private bool Is32Bit => Header.Machine is not Machine.Amd64 and not Machine.Arm64;
public ShimExeBuilder(PEHeaderBuilder header, string dllName, string entrypointName,
Func<IEnumerable<Blob>, BlobContentId> deterministicIdProvider = null) : base(header, deterministicIdProvider)
{
_dllName = Encoding.UTF8.GetBytes(dllName); // TODO: this is probably not UTF-8
_entrypointName = Encoding.UTF8.GetBytes(entrypointName); // TODO: this is probably not UTF-8
_peDirectoriesBuilder = new PEDirectoriesBuilder();
}
protected override ImmutableArray<Section> CreateSections()
{
var textSection = new Section(TextSectionName, SectionCharacteristics.MemRead | SectionCharacteristics.MemExecute | SectionCharacteristics.ContainsCode);
if (Header.Machine != Machine.I386)
return [textSection];
var relocSection = new Section(RelocationSectionName, SectionCharacteristics.MemRead | SectionCharacteristics.MemDiscardable | SectionCharacteristics.ContainsInitializedData);
return [textSection, relocSection];
}
protected override BlobBuilder SerializeSection(string name, SectionLocation location) =>
name switch
{
TextSectionName => SerializeTextSection(location),
RelocationSectionName => SerializeRelocationSection(location),
_ => throw new UnreachableException(),
};
private BlobBuilder SerializeTextSection(SectionLocation location)
{
var builder = new BlobBuilder();
//
// Write Import Address Table
//
int sizeOfImportAddressTable = Is32Bit ? 2 * sizeof(uint) : 2 * sizeof(ulong);
int importAddressTableRva = location.RelativeVirtualAddress + builder.Count;
{
int ilRva = sizeOfImportAddressTable + 40;
int hintRva = ilRva + (Is32Bit ? 12 : 16);
if (Is32Bit)
{
builder.WriteUInt32((uint)hintRva); // 4
builder.WriteUInt32(0); // 8
}
else
{
builder.WriteUInt64((uint)hintRva); // 8
builder.WriteUInt64(0); // 16
}
}
_peDirectoriesBuilder.ImportAddressTable = new DirectoryEntry(importAddressTableRva, sizeOfImportAddressTable);
Debug.Assert(builder.Count - (importAddressTableRva - location.RelativeVirtualAddress) == sizeOfImportAddressTable);
//
// Write Import Table
//
int importTableRva = location.RelativeVirtualAddress + builder.Count;
{
int ilRVA = importTableRva + 40;
int hintRva = ilRVA + (Is32Bit ? 12 : 16);
int nameRva = hintRva + _entrypointName.Length + 1 + 2;
// Import table
builder.WriteUInt32((uint)ilRVA); // 4
builder.WriteUInt32(0); // 8
builder.WriteUInt32(0); // 12
builder.WriteUInt32((uint)nameRva); // 16
builder.WriteUInt32((uint)importAddressTableRva); // 20
builder.WriteBytes(0, 20); // 40
// Import Lookup table
if (Is32Bit)
{
builder.WriteUInt32((uint)hintRva); // 44
builder.WriteUInt32(0); // 48
builder.WriteUInt32(0); // 52
}
else
{
builder.WriteUInt64((uint)hintRva); // 48
builder.WriteUInt64(0); // 56
}
// Hint table
builder.WriteUInt16(0); // Hint 54|58
builder.WriteBytes(_entrypointName); // 65|69
builder.WriteByte(0); // 66|70
}
_peDirectoriesBuilder.ImportTable = new DirectoryEntry(importTableRva, builder.Count - (importTableRva - location.RelativeVirtualAddress));
// Write Name Table
builder.WriteBytes(_dllName);
builder.WriteByte(0);
builder.WriteUInt16(0);
// Generate indirect jump machine code
_peDirectoriesBuilder.AddressOfEntryPoint = location.RelativeVirtualAddress + builder.Count;
// entry point code, consisting of a jump indirect
if (Header.Machine == Machine.I386)
{
builder.WriteByte(0xff);
builder.WriteByte(0x25);
builder.WriteUInt32((uint)importAddressTableRva + (uint)Header.ImageBase);
}
else if (Header.Machine == Machine.Amd64)
{
builder.WriteByte(0xff);
builder.WriteByte(0x25);
builder.WriteInt32(importAddressTableRva - _peDirectoriesBuilder.AddressOfEntryPoint - 6);
}
else
{
throw new NotImplementedException();
}
return builder;
}
private BlobBuilder SerializeRelocationSection(SectionLocation location)
{
var sectionBuilder = new BlobBuilder();
// Page RVA
sectionBuilder.WriteUInt32((((uint)_peDirectoriesBuilder.AddressOfEntryPoint + 2) / 0x1000) * 0x1000);
// Block size
sectionBuilder.WriteUInt32(12u);
uint offsetWithinPage = ((uint)_peDirectoriesBuilder.AddressOfEntryPoint + 2) % 0x1000;
uint relocType = 3u;
ushort s = (ushort)((relocType << 12) | offsetWithinPage);
sectionBuilder.WriteUInt16(s);
sectionBuilder.WriteUInt16(0); // next chunk's RVA
_peDirectoriesBuilder.BaseRelocationTable = new DirectoryEntry(location.RelativeVirtualAddress, sectionBuilder.Count);
return sectionBuilder;
}
protected override PEDirectoriesBuilder GetDirectories() => _peDirectoriesBuilder;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment