src/programFrames/TinySharp.cs
//code from https://blog.washi.dev/posts/tinysharp/
using System; using System.IO; using System.Text; 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.File; using AsmResolver.PE.File.Headers; namespace TinySharp { public class Program { public static PEFile Compile( string outputValue, string architecture = "x64", int ExitCode = 0, bool hasOutput = true ) { string baseFunction = "7"; bool allASCIIoutput = true; for (int i = 0; i < outputValue.Length; i++) { if (outputValue[i] > 127 || outputValue[i] <= 0) { allASCIIoutput = false; break; } } var module = new ModuleDefinition("Dummy"); // Segment containing our string to print. DataSegment segment = null; if(allASCIIoutput) { segment = new DataSegment(Encoding.ASCII.GetBytes(outputValue+'\0')); } else{ segment = new DataSegment(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 )); } // Add urctbase module reference var baseLibrary = "Kernel32"; if (allASCIIoutput) baseLibrary = "ucrtbase"; if (hasOutput) tablesStream.GetTable<ModuleReferenceRow>().Add(new ModuleReferenceRow(stringsStreamBuffer.GetStringIndex(baseLibrary))); // Add P/Invoke metadata to the puts method. if (hasOutput) 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 = "v4.0.", // Needs the "." at the end. (original: v4.0.30319) Streams = { tablesStream, blobStreamBuffer.CreateStream(), stringsStreamBuffer.CreateStream() } } }; if (architecture == "anycpu") image.DotNetDirectory.Flags &= ~DotNetDirectoryFlags.Bit32Required; // Assemble PE file. var file = new MyBuilder().CreateFile(image); // Put string to print in the padding data. if (hasOutput) file.ExtraSectionData = segment; return file; } } 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); } } } |