src/programFrames/TinySharp.cs
|
//code from https://blog.washi.dev/posts/tinysharp/
using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Linq; using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Builder.Metadata; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.IO; using AsmResolver.PE; using AsmResolver.PE.Builder; using AsmResolver.PE.Code; using AsmResolver.PE.DotNet; using AsmResolver.PE.DotNet.Cil; using AsmResolver.PE.DotNet.Metadata; using AsmResolver.PE.DotNet.Metadata.Tables; using AsmResolver.PE.Win32Resources; using AsmResolver.PE.Win32Resources.Icon; using AsmResolver.PE.Win32Resources.Version; using AsmResolver.PE.File; namespace TinySharp { public class Program { public static Program Compile( string targetRuntime, string architecture = "x64", string outputValue = "Hello World!", int ExitCode = 0, bool hasOutput = true, bool useMessageBox = false ) { if (useMessageBox && hasOutput) return CompileMessageBox(targetRuntime, architecture, outputValue, ExitCode); string baseFunction = "7"; bool allASCIIoutput = outputValue.All(c => c >= 0 && c <= 127); var module = new ModuleDefinition("Dummy"); // Segment containing our string to print. DataSegment segment = new DataSegment((allASCIIoutput?Encoding.ASCII:Encoding.Unicode).GetBytes(outputValue+'\0')); var PEKind = OptionalHeaderMagic.PE64; var ArchType = MachineType.Amd64; if (architecture != "x64") { PEKind = OptionalHeaderMagic.PE32; ArchType = MachineType.I386; } // Initialize a new PE image and set up some default values. var image = new PEImage { ImageBase = 0x00000000004e0000, PEKind = PEKind, MachineType = ArchType }; // Ensure PE is loaded at the provided image base. image.DllCharacteristics &= ~DllCharacteristics.DynamicBase; // Create new metadata streams. var tablesStream = new TablesStream(); var blobStreamBuffer = new BlobStreamBuffer(); var stringsStreamBuffer = new StringsStreamBuffer(); // Add empty module row. tablesStream.GetTable<ModuleDefinitionRow>().Add(new ModuleDefinitionRow()); // Add container type def for our main function (<Module>). tablesStream.GetTable<TypeDefinitionRow>().Add(new TypeDefinitionRow( 0, 0, 0, 0, 1, 1 )); var methodTable = tablesStream.GetTable<MethodDefinitionRow>(); // Add puts method. if (hasOutput) if(allASCIIoutput) { baseFunction = "puts"; methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("puts"), blobStreamBuffer.GetBlobIndex( module, new DummyProvider(), MethodSignature.CreateStatic( module.CorLibTypeFactory.Void, new[] { module.CorLibTypeFactory.IntPtr }), ThrowErrorListener.Instance), 1 )); } else { baseFunction = "WriteConsoleW"; methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("GetStdHandle"), blobStreamBuffer.GetBlobIndex( module, new DummyProvider(), MethodSignature.CreateStatic( module.CorLibTypeFactory.IntPtr, new[] { module.CorLibTypeFactory.Int32 }), ThrowErrorListener.Instance), 1 )); methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("WriteConsoleW"), blobStreamBuffer.GetBlobIndex( module, new DummyProvider(), MethodSignature.CreateStatic( module.CorLibTypeFactory.Void, new[] { module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.Int32, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr }), ThrowErrorListener.Instance), 1 )); } // Add main method calling puts. using(var codeStream = new MemoryStream()) { var assembler = new CilAssembler(new BinaryStreamWriter(codeStream), new CilOperandBuilder(new OriginalMetadataTokenProvider(null), ThrowErrorListener.Instance)); uint patchIndex = 0; if (hasOutput) { if(allASCIIoutput) { patchIndex = 2; assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, 5112224)); assembler.WriteInstruction(new CilInstruction(CilOpCodes.Call, new MetadataToken(TableIndex.Method, 1))); } else { patchIndex = 12; assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, -11)); // STD_OUTPUT_HANDLE assembler.WriteInstruction(new CilInstruction(CilOpCodes.Call, new MetadataToken(TableIndex.Method, 1))); assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, 5112224)); assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, outputValue.Length)); // size of string assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, 0x00000000)); // reserve size outputed assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, 0x00000000)); // reserved assembler.WriteInstruction(new CilInstruction(CilOpCodes.Call, new MetadataToken(TableIndex.Method, 2))); } } if (ExitCode != 0) assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ldc_I4, ExitCode)); assembler.WriteInstruction(new CilInstruction(CilOpCodes.Ret)); var body = new CilRawTinyMethodBody(codeStream.ToArray()).AsPatchedSegment(); if (hasOutput) body = body.Patch(patchIndex, AddressFixupType.Absolute32BitAddress, new Symbol(segment.ToReference())); var retype = module.CorLibTypeFactory.Void; if (ExitCode != 0) retype = module.CorLibTypeFactory.Int32; methodTable.Add(new MethodDefinitionRow( body.ToReference(), 0, MethodAttributes.Static, 0, blobStreamBuffer.GetBlobIndex( module, new DummyProvider(), MethodSignature.CreateStatic(retype), ThrowErrorListener.Instance), 1 )); } if (hasOutput) { // Add urctbase module reference var baseLibrary = allASCIIoutput ? "ucrtbase" : "Kernel32"; tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex(baseLibrary))); // Add P/Invoke metadata to the puts method. if (allASCIIoutput) tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvCdecl, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 1)), stringsStreamBuffer.GetStringIndex("puts"), 1 )); else { tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvCdecl, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 1)), stringsStreamBuffer.GetStringIndex("GetStdHandle"), 1 )); tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvCdecl, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 2)), stringsStreamBuffer.GetStringIndex("WriteConsoleW"), 1 )); } } // Define assembly manifest. tablesStream.GetTable<AssemblyDefinitionRow>().Add(new AssemblyDefinitionRow( 0, 1, 0, 0, 0, 0, 0, stringsStreamBuffer.GetStringIndex(baseFunction), // The CLR does not allow for assemblies with a null name. Reuse the name "puts" to safe space. 0 )); // Add all .NET metadata to the PE image. var metadataDirectory = new MetadataDirectory { VersionString = targetRuntime == "Framework2.0" ? "v2.0." : "v4.0." }; metadataDirectory.Streams.Add(tablesStream); metadataDirectory.Streams.Add(blobStreamBuffer.CreateStream()); metadataDirectory.Streams.Add(stringsStreamBuffer.CreateStream()); image.DotNetDirectory = new DotNetDirectory { EntryPoint = new MetadataToken(TableIndex.Method, hasOutput?allASCIIoutput?2u:3u:1u), Metadata = metadataDirectory }; if (architecture == "anycpu") image.DotNetDirectory.Flags &= ~DotNetDirectoryFlags.Bit32Required; var result = new Program(); result.Image = image; // Put string to print in the padding data. if (hasOutput) result.OutSegment = segment; return result; } /// <summary>Build minimal PE that shows MessageBoxW(text, caption) then exits. Used for -noConsole const output. /// Caption is resolved at runtime: first tries Win32 version resource FileDescription (title), falls back to /// GetModuleFileNameW+PathFindFileNameW (filename), so both survive exe rename and honour -title.</summary> private static Program CompileMessageBox(string targetRuntime, string architecture, string outputValue, int ExitCode) { var module = new ModuleDefinition("Dummy"); DataSegment textSegment = new DataSegment(Encoding.Unicode.GetBytes(outputValue + '\0')); // VerQueryValueW subBlock path — matches AsmResolver StringTable(language:0, codepage:0x4b0) DataSegment subBlockStr = new DataSegment(Encoding.Unicode.GetBytes("\\StringFileInfo\\000004b0\\FileDescription\0")); // Writable BSS segments (zero-initialised by OS loader, no file bytes) var pathBufSeg = new VirtualSegment(null, 260 * 2u); // GetModuleFileNameW path buffer var viBufSeg = new VirtualSegment(null, 4096u); // GetFileVersionInfoW data buffer var pValueBufSeg = new VirtualSegment(null, 8u); // VerQueryValueW output pointer (up to 8 bytes for x64) var lenBufSeg = new VirtualSegment(null, 4u); // VerQueryValueW output length (UINT) var PEKind = OptionalHeaderMagic.PE64; var ArchType = MachineType.Amd64; if (architecture != "x64") { PEKind = OptionalHeaderMagic.PE32; ArchType = MachineType.I386; } var image = new PEImage { ImageBase = 0x00000000004e0000, PEKind = PEKind, MachineType = ArchType }; image.DllCharacteristics &= ~DllCharacteristics.DynamicBase; var tablesStream = new TablesStream(); var blobStreamBuffer = new BlobStreamBuffer(); var stringsStreamBuffer = new StringsStreamBuffer(); tablesStream.GetTable<ModuleDefinitionRow>().Add(new ModuleDefinitionRow()); tablesStream.GetTable<TypeDefinitionRow>().Add(new TypeDefinitionRow(0, 0, 0, 0, 1, 1)); var methodTable = tablesStream.GetTable<MethodDefinitionRow>(); // 1: MessageBoxW (user32) methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("MessageBoxW"), blobStreamBuffer.GetBlobIndex(module, new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.Void, new[] { module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.UInt32 }), ThrowErrorListener.Instance), 1)); // 2: GetModuleFileNameW (kernel32): (hModule, lpFilename, nSize) -> void methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("GetModuleFileNameW"), blobStreamBuffer.GetBlobIndex(module, new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.Void, new[] { module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.UInt32 }), ThrowErrorListener.Instance), 1)); // 3: PathFindFileNameW (shlwapi): (pszPath) -> IntPtr methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("PathFindFileNameW"), blobStreamBuffer.GetBlobIndex(module, new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.IntPtr, new[] { module.CorLibTypeFactory.IntPtr }), ThrowErrorListener.Instance), 1)); // 4: GetFileVersionInfoW (version): (lpszFileName, dwHandle, dwLen, lpData) -> Int32 (BOOL) methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("GetFileVersionInfoW"), blobStreamBuffer.GetBlobIndex(module, new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.Int32, new[] { module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.UInt32, module.CorLibTypeFactory.UInt32, module.CorLibTypeFactory.IntPtr }), ThrowErrorListener.Instance), 1)); // 5: VerQueryValueW (version): (pBlock, lpSubBlock, lplpBuffer, puLen) -> Int32 (BOOL) methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("VerQueryValueW"), blobStreamBuffer.GetBlobIndex(module, new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.Int32, new[] { module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.IntPtr }), ThrowErrorListener.Instance), 1)); // Main (method #6) — fat CIL body (code > 63 bytes, tiny format limit). // IL layout (code stream offsets, before 12-byte fat header): // [0] ldc.i4 0 hModule=0 for GetModuleFileNameW // [5] ldc.i4 [pathBuf] PATCH @6 // [10] ldc.i4 260 // [15] call Method#2 GetModuleFileNameW(0, pathBuf, 260) // [20] ldc.i4 [pathBuf] PATCH @21 // [25] ldc.i4 0 dwHandle=0 // [30] ldc.i4 4096 dwLen // [35] ldc.i4 [viBuf] PATCH @36 // [40] call Method#4 GetFileVersionInfoW → BOOL on stack // [45] brfalse.s 55 → FALLBACK @102 (next=47, 102-47=55) // [47] ldc.i4 [viBuf] PATCH @48 // [52] ldc.i4 [subBlock] PATCH @53 // [57] ldc.i4 [pValueBuf] PATCH @58 // [62] ldc.i4 [lenBuf] PATCH @63 // [67] call Method#5 VerQueryValueW → BOOL on stack // [72] brfalse.s 28 → FALLBACK @102 (next=74, 102-74=28) // [74] ldc.i4 0 hWnd // [79] ldc.i4 [text] PATCH @80 // [84] ldc.i4 [pValueBuf] PATCH @85 // [89] ldind.i *pValueBuf → title pointer // [90] ldc.i4 0 uType // [95] call Method#1 MessageBoxW(0, text, titlePtr, 0) // [100] br.s DONE → @132 or @137 (offset 30 or 35) // FALLBACK @102: // [102] ldc.i4 0 hWnd // [107] ldc.i4 [text] PATCH @108 // [112] ldc.i4 [pathBuf] PATCH @113 // [117] call Method#3 PathFindFileNameW(pathBuf) → filenamePtr // [122] ldc.i4 0 uType // [127] call Method#1 MessageBoxW(0, text, filenamePtr, 0) // DONE @132 [or @137 if ExitCode!=0]: // [132] ldc.i4 ExitCode (only if ExitCode != 0) // [132|137] ret // Patch offsets in segment = code offset + 12 (fat header size). using (var codeStream = new MemoryStream()) { Action<int> writeLdc = v => { codeStream.WriteByte(0x20); var b = BitConverter.GetBytes(v); codeStream.Write(b, 0, 4); }; Action<int> writeCall = m => { codeStream.WriteByte(0x28); var b = BitConverter.GetBytes(0x06000000 | m); codeStream.Write(b, 0, 4); }; writeLdc(0); // [0] hModule=0 writeLdc(0); // [5] pathBuf PATCH@6 writeLdc(260); // [10] nSize writeCall(2); // [15] GetModuleFileNameW writeLdc(0); // [20] pathBuf PATCH@21 writeLdc(0); // [25] dwHandle=0 writeLdc(4096); // [30] dwLen writeLdc(0); // [35] viBuf PATCH@36 writeCall(4); // [40] GetFileVersionInfoW → BOOL codeStream.WriteByte(0x2C); // [45] brfalse.s codeStream.WriteByte(55); // [46] → FALLBACK @102 (next=47, 102-47=55) writeLdc(0); // [47] viBuf PATCH@48 writeLdc(0); // [52] subBlockStr PATCH@53 writeLdc(0); // [57] pValueBuf PATCH@58 writeLdc(0); // [62] lenBuf PATCH@63 writeCall(5); // [67] VerQueryValueW → BOOL codeStream.WriteByte(0x2C); // [72] brfalse.s codeStream.WriteByte(28); // [73] → FALLBACK @102 (next=74, 102-74=28) writeLdc(0); // [74] hWnd writeLdc(0); // [79] text PATCH@80 writeLdc(0); // [84] pValueBuf PATCH@85 codeStream.WriteByte(0x4D); // [89] ldind.i → dereference pValueBuf writeLdc(0); // [90] uType=0 writeCall(1); // [95] MessageBoxW(0, text, titlePtr, 0) codeStream.WriteByte(0x2B); // [100] br.s codeStream.WriteByte((byte)(30 + (ExitCode != 0 ? 5 : 0))); // [101] → DONE // FALLBACK @102 writeLdc(0); // [102] hWnd writeLdc(0); // [107] text PATCH@108 writeLdc(0); // [112] pathBuf PATCH@113 writeCall(3); // [117] PathFindFileNameW(pathBuf) → filenamePtr writeLdc(0); // [122] uType=0 writeCall(1); // [127] MessageBoxW(0, text, filenamePtr, 0) // DONE @132 if (ExitCode != 0) writeLdc(ExitCode); // [132] only when needed codeStream.WriteByte(0x2A); // ret byte[] code = codeStream.ToArray(); // Fat method header: flags=0x3003 (fat, hdrSize=3dwords), MaxStack=4, CodeSize, LocalVarSigTok=0 byte[] header = { 0x03, 0x30, 4, 0, (byte)code.Length, (byte)(code.Length >> 8), (byte)(code.Length >> 16), (byte)(code.Length >> 24), 0, 0, 0, 0 }; byte[] fullMethod = new byte[header.Length + code.Length]; Buffer.BlockCopy(header, 0, fullMethod, 0, header.Length); Buffer.BlockCopy(code, 0, fullMethod, header.Length, code.Length); // Patch offsets = code stream offset of int32 operand + 12 (fat header) var body = new DataSegment(fullMethod).AsPatchedSegment(); body = body.Patch(12 + 6, AddressFixupType.Absolute32BitAddress, new Symbol(pathBufSeg.ToReference())); body = body.Patch(12 + 21, AddressFixupType.Absolute32BitAddress, new Symbol(pathBufSeg.ToReference())); body = body.Patch(12 + 36, AddressFixupType.Absolute32BitAddress, new Symbol(viBufSeg.ToReference())); body = body.Patch(12 + 48, AddressFixupType.Absolute32BitAddress, new Symbol(viBufSeg.ToReference())); body = body.Patch(12 + 53, AddressFixupType.Absolute32BitAddress, new Symbol(subBlockStr.ToReference())); body = body.Patch(12 + 58, AddressFixupType.Absolute32BitAddress, new Symbol(pValueBufSeg.ToReference())); body = body.Patch(12 + 63, AddressFixupType.Absolute32BitAddress, new Symbol(lenBufSeg.ToReference())); body = body.Patch(12 + 80, AddressFixupType.Absolute32BitAddress, new Symbol(textSegment.ToReference())); body = body.Patch(12 + 85, AddressFixupType.Absolute32BitAddress, new Symbol(pValueBufSeg.ToReference())); body = body.Patch(12 + 108, AddressFixupType.Absolute32BitAddress, new Symbol(textSegment.ToReference())); body = body.Patch(12 + 113, AddressFixupType.Absolute32BitAddress, new Symbol(pathBufSeg.ToReference())); var retype = module.CorLibTypeFactory.Void; if (ExitCode != 0) retype = module.CorLibTypeFactory.Int32; methodTable.Add(new MethodDefinitionRow( body.ToReference(), 0, MethodAttributes.Static, 0, blobStreamBuffer.GetBlobIndex(module, new DummyProvider(), MethodSignature.CreateStatic(retype), ThrowErrorListener.Instance), 1)); } // Module references: 1=user32, 2=kernel32, 3=shlwapi, 4=version tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex("user32"))); tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex("kernel32"))); tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex("shlwapi"))); tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex("version"))); tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvStdcall, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 1)), stringsStreamBuffer.GetStringIndex("MessageBoxW"), 1)); tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvStdcall, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 2)), stringsStreamBuffer.GetStringIndex("GetModuleFileNameW"), 2)); tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvStdcall, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 3)), stringsStreamBuffer.GetStringIndex("PathFindFileNameW"), 3)); tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvStdcall, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 4)), stringsStreamBuffer.GetStringIndex("GetFileVersionInfoW"), 4)); tablesStream.GetTable<ImplementationMapRow>().Add(new ImplementationMapRow( ImplementationMapAttributes.CallConvStdcall, tablesStream.GetIndexEncoder(CodedIndex.MemberForwarded).EncodeToken(new MetadataToken(TableIndex.Method, 5)), stringsStreamBuffer.GetStringIndex("VerQueryValueW"), 4)); tablesStream.GetTable<AssemblyDefinitionRow>().Add(new AssemblyDefinitionRow( 0, 1, 0, 0, 0, 0, 0, stringsStreamBuffer.GetStringIndex("user32"), 0)); var metadataDirectory = new MetadataDirectory { VersionString = targetRuntime == "Framework2.0" ? "v2.0." : "v4.0." }; metadataDirectory.Streams.Add(tablesStream); metadataDirectory.Streams.Add(blobStreamBuffer.CreateStream()); metadataDirectory.Streams.Add(stringsStreamBuffer.CreateStream()); image.DotNetDirectory = new DotNetDirectory { // Main is method #6 (1=MessageBoxW,2=GetModuleFileNameW,3=PathFindFileNameW,4=GetFileVersionInfoW,5=VerQueryValueW,6=Main) EntryPoint = new MetadataToken(TableIndex.Method, 6u), Metadata = metadataDirectory }; if (architecture == "anycpu") image.DotNetDirectory.Flags &= ~DotNetDirectoryFlags.Bit32Required; var result = new Program(); result.Image = image; // Read-only data: text + VerQueryValueW subBlock path (both in .text) var roData = new SegmentBuilder(); roData.Add(textSegment); roData.Add(subBlockStr, 2); result.OutSegment = roData; // Writable BSS data (in .data) var bssData = new SegmentBuilder(); bssData.Add(pathBufSeg); bssData.Add(viBufSeg); bssData.Add(pValueBufSeg); bssData.Add(lenBufSeg); result.WritableSegment = bssData; return result; } private ISegment OutSegment; private ISegment WritableSegment; private PEImage Image; public void Build(string OutFile) { // Do NOT substitute ManagedPEFileBuilder: size would grow (see DESIGN at top of file). // OutSegment (read-only constants) → .text; WritableSegment (e.g. path buffer) → .data var file = new MinimalPEFileBuilder(OutSegment, WritableSegment).CreateFile(this.Image); file.Write(OutFile); } public void SetWin32Icon(string IconFile) { byte[] header = File.ReadAllBytes(IconFile); if (header.Length < 22) return; ushort count = BitConverter.ToUInt16(header, 4); if (count == 0) return; // First icon directory entry: width, height, colors, reserved, planes, bpp, size, offset byte w = header[6], h = header[7]; ushort planes = BitConverter.ToUInt16(header, 10); ushort bpp = BitConverter.ToUInt16(header, 12); uint size = BitConverter.ToUInt32(header, 14); uint offset = BitConverter.ToUInt32(header, 18); if (offset + size > (uint)header.Length) return; byte[] iconBytes = new byte[size]; Buffer.BlockCopy(header, (int)offset, iconBytes, 0, (int)size); var entry = new IconEntry(1, 0) { Width = w, Height = h, ColorCount = 0, Planes = planes, BitsPerPixel = bpp, PixelData = new DataSegment(iconBytes) }; var group = new IconGroup(1u, 0u) { Type = IconType.Icon }; group.Icons.Add(entry); var iconResource = new IconResource(IconType.Icon); iconResource.Groups.Add(group); if (this.Image.Resources == null) this.Image.Resources = new ResourceDirectory(0u); iconResource.InsertIntoDirectory(this.Image.Resources); } public void SetAssemblyInfo(string description, string company, string title, string product, string copyright, string trademark, string version) { // Create new version resource. var versionResource = new VersionInfoResource(); if (string.IsNullOrEmpty(version)) version = "0.0.0.0"; var TypedVersion = new System.Version(version); version = TypedVersion.ToString(); // Add info. var fixedVersionInfo = new FixedVersionInfo { FileVersion = TypedVersion, ProductVersion = TypedVersion, FileDate = (ulong)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), FileType = FileType.App, FileOS = FileOS.Windows32, FileSubType = FileSubType.DriverInstallable, }; versionResource.FixedVersionInfo = fixedVersionInfo; // Add strings. var stringFileInfo = new StringFileInfo(); var stringTable = new StringTable(0, 0x4b0){ { StringTable.ProductNameKey, product }, { StringTable.FileVersionKey, version }, { StringTable.ProductVersionKey, version }, { StringTable.FileDescriptionKey, title }, { StringTable.CommentsKey, description }, { StringTable.LegalCopyrightKey, copyright } }; stringFileInfo.Tables.Add(stringTable); versionResource.AddEntry(stringFileInfo); // Register translation. var varFileInfo = new VarFileInfo(); var varTable = new VarTable(); varTable.Values.Add(0x4b00000); varFileInfo.Tables.Add(varTable); versionResource.AddEntry(varFileInfo); // Add to resources. if (this.Image.Resources == null) this.Image.Resources = new ResourceDirectory(0u); versionResource.InsertIntoDirectory(this.Image.Resources); } } internal class DummyProvider: ITypeCodedIndexProvider { public uint GetTypeDefOrRefIndex(ITypeDefOrRef type, object extraArgument) { throw new NotImplementedException(); } } /// <summary> /// Minimal PE: FileAlignment 512, SectionAlignment 4096 (loader requires 4K section alignment), .text only, /// minimal data directories. Achieves 1024 bytes. Output string is passed into builder and laid out inside .text /// so CIL patch gets correct RVA. 512 bytes total is not possible (PE headers + one section >= 1024). /// </summary> internal sealed class MinimalPEFileBuilder : ManagedPEFileBuilder { private readonly ISegment _extraSectionData; private readonly ISegment _writableData; public MinimalPEFileBuilder(ISegment extraSectionData = null, ISegment writableData = null) { _extraSectionData = extraSectionData; _writableData = writableData; } protected override uint GetFileAlignment(PEFileBuilderContext context, PEFile outputFile) { return 512; } // Section alignment 4096 for loader; file alignment 512 for size. protected override uint GetSectionAlignment(PEFileBuilderContext context, PEFile outputFile) { return 4096; } protected override IEnumerable<PESection> CreateSections(PEFileBuilderContext context) { yield return CreateTextSection(context); // 可写数据(如 GetModuleFileNameW 路径缓冲区)放独立 .data 段,避免 .text 带写权限 if (_writableData != null) { yield return new PESection(".data", SectionFlags.ContentUninitializedData | SectionFlags.MemoryRead | SectionFlags.MemoryWrite, _writableData); } // 有 Win32 资源(如 icon、版本信息)时添加 .rsrc 段 if (context.Image.Resources != null) { yield return new PESection(".rsrc", SectionFlags.ContentInitializedData | SectionFlags.MemoryRead, context.ResourceDirectory); } } protected override void AssignDataDirectories(PEFileBuilderContext context, PEFile outputFile) { base.AssignDataDirectories(context, outputFile); outputFile.OptionalHeader.SetDataDirectory(DataDirectoryIndex.BaseRelocationDirectory, (ISegment)null); outputFile.OptionalHeader.SetDataDirectory(DataDirectoryIndex.DebugDirectory, (ISegment)null); if (context.Image.Resources == null) outputFile.OptionalHeader.SetDataDirectory(DataDirectoryIndex.ResourceDirectory, (ISegment)null); outputFile.OptionalHeader.SetDataDirectory(DataDirectoryIndex.ExportDirectory, (ISegment)null); } protected override PESection CreateTextSection(PEFileBuilderContext context) { var contents = new SegmentBuilder(); if (!context.ImportDirectory.IsEmpty) { contents.Add(context.ImportDirectory.ImportAddressDirectory); } var dotNet = context.Image.DotNetDirectory; if (dotNet != null) { contents.Add(dotNet); contents.Add(context.FieldRvaTable); contents.Add(context.MethodBodyTable); if (dotNet.Metadata != null) contents.Add(dotNet.Metadata, 4); if (dotNet.DotNetResources != null) contents.Add(dotNet.DotNetResources, 4); if (dotNet.StrongName != null) contents.Add(dotNet.StrongName, 4); if (dotNet.VTableFixups != null && dotNet.VTableFixups.Count > 0) contents.Add(dotNet.VTableFixups); if (dotNet.ExportAddressTable != null) contents.Add(dotNet.ExportAddressTable, 4); if (dotNet.ManagedNativeHeader != null) contents.Add(dotNet.ManagedNativeHeader, 4); } if (!context.ImportDirectory.IsEmpty) { contents.Add(context.ImportDirectory); } if (_extraSectionData != null) contents.Add(_extraSectionData); return new PESection(".text", SectionFlags.ContentCode | SectionFlags.MemoryExecute | SectionFlags.MemoryRead, contents); } } } |