src/programFrames/TinySharp.cs
//code from https://blog.washi.dev/posts/tinysharp/
using System; using System.IO; using System.Text; using System.Linq; using AsmResolver; using AsmResolver.DotNet; using AsmResolver.DotNet.Builder.Metadata.Blob; using AsmResolver.DotNet.Builder.Metadata.Strings; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; using AsmResolver.IO; using AsmResolver.PE; using AsmResolver.PE.DotNet.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.DotNet.Metadata.Tables.Rows; using AsmResolver.PE.Win32Resources; using AsmResolver.PE.Win32Resources.Icon; using AsmResolver.PE.Win32Resources.Version; using AsmResolver.PE.File; using AsmResolver.PE.File.Headers; namespace TinySharp { public class Program { public static Program Compile( string outputValue, string architecture = "x64", int ExitCode = 0, bool hasOutput = true ) { 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(new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.Void, 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(new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.IntPtr, module.CorLibTypeFactory.Int32), ThrowErrorListener.Instance), 1 )); methodTable.Add(new MethodDefinitionRow( SegmentReference.Null, MethodImplAttributes.PreserveSig, MethodAttributes.Static | MethodAttributes.PInvokeImpl, stringsStreamBuffer.GetStringIndex("WriteConsoleW"), blobStreamBuffer.GetBlobIndex(new DummyProvider(), MethodSignature.CreateStatic(module.CorLibTypeFactory.Void, 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(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. image.DotNetDirectory = new DotNetDirectory { EntryPoint = new MetadataToken(TableIndex.Method, hasOutput?allASCIIoutput?2u:3u:1u), Metadata = new Metadata { VersionString = "v1.0.", // Needs the "." at the end. (original: v4.0.30319) Streams = { tablesStream, blobStreamBuffer.CreateStream(), stringsStreamBuffer.CreateStream() } } }; 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; } private DataSegment OutSegment; private IPEImage Image; public void Build(string OutFile) { // Assemble PE file. var file = new MyBuilder().CreateFile(this.Image); // Put string to print in the padding data. if (OutSegment != null) file.ExtraSectionData = this.OutSegment; file.Write(OutFile); } public void SetWin32Icon(string IconFile) { // Create a new icon group with 1 icon. var newGroup = new IconGroupDirectory { Type = 1, Count = 1 }; // Add the icon to the group (header stripped). byte[] header = File.ReadAllBytes(IconFile); var iconBytes = BitConverter.ToUInt32(header, 14); // cut head and tail byte[] icon = header.Skip(0x16).Take((int)iconBytes).ToArray(); newGroup.AddEntry( new IconGroupDirectoryEntry { Id = (ushort) 1, // Icon ID (must be unique) BytesInRes = iconBytes, PixelBitCount = BitConverter.ToUInt16(header, 12), Width = header[6], Height = header[7], ColorCount = 0, ColorPlanes = 1 }, new IconEntry { RawIcon = icon } ); // Add icon to icon resource group. var newResource = new IconResource(); newResource.AddEntry(0x7f00, newGroup); // Add resource to PE. if (this.Image.Resources == null) this.Image.Resources = new ResourceDirectory(0u); this.Image.Resources.Entries.Insert(0, new ResourceDirectory(ResourceType.Icon)); this.Image.Resources.Entries.Insert(1, new ResourceDirectory(ResourceType.GroupIcon)); newResource.WriteToDirectory(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.WriteToDirectory(this.Image.Resources); } } internal class DummyProvider: ITypeCodedIndexProvider { public uint GetTypeDefOrRefIndex(ITypeDefOrRef type) { throw new NotImplementedException(); } } public class MyBuilder: ManagedPEFileBuilder { protected override PESection CreateTextSection(IPEImage image, ManagedPEBuilderContext context) { // We override this method to only have it emit the bare minimum .text section. var methodTable = context.DotNetSegment.DotNetDirectory.Metadata.GetStream<TablesStream>().GetTable<MethodDefinitionRow>(); for (uint rid = 1; rid <= methodTable.Count; rid++) { var methodRow = methodTable.GetByRid(rid); var bodySegment = methodRow.Body.IsBounded ? methodRow.Body.GetSegment() : null; if (bodySegment != null) { context.DotNetSegment.MethodBodyTable.AddNativeBody(bodySegment, 4); methodRow.Body = bodySegment.ToReference(); } } return new PESection(".text", SectionFlags.ContentCode | SectionFlags.MemoryRead, context.DotNetSegment); } } } |