Skip to content

Instantly share code, notes, and snippets.

@xpn
Last active July 21, 2023 13:07
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save xpn/747ba8e35deee0d12ad4749b17407c26 to your computer and use it in GitHub Desktop.
Save xpn/747ba8e35deee0d12ad4749b17407c26 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace HideMSIL
{
class Program
{
const string msilResourceName = "msil";
const string loaderResourceName = "loader";
const string loaderDllName = "loader.dll";
// Adds a loader stub to the target assembly
public static void AddLoaderStub(AssemblyDefinition cecilAssembly, MethodDefinition method, bool addRet)
{
var il = method.Body.GetILProcessor();
var loaderRef = new ModuleReference(loaderDllName);
cecilAssembly.MainModule.ModuleReferences.Add(loaderRef);
// Create our delegate
MethodDefinition ourlibrary = new MethodDefinition("Run", Mono.Cecil.MethodAttributes.Public |
Mono.Cecil.MethodAttributes.HideBySig |
Mono.Cecil.MethodAttributes.Static |
Mono.Cecil.MethodAttributes.PInvokeImpl,
cecilAssembly.MainModule.TypeSystem.Void
);
ourlibrary.PInvokeInfo = new PInvokeInfo(PInvokeAttributes.NoMangle | PInvokeAttributes.CharSetUnicode
| PInvokeAttributes.SupportsLastError |
PInvokeAttributes.CallConvWinapi,
"Run",
loaderRef
);
// Parameter for passing over our embedded resource
ourlibrary.Parameters.Add(new ParameterDefinition("data", Mono.Cecil.ParameterAttributes.None, cecilAssembly.MainModule.TypeSystem.IntPtr));
var t = new TypeDefinition("xpnsec", "win32", TypeAttributes.Class | TypeAttributes.Public, cecilAssembly.MainModule.TypeSystem.Object);
t.Methods.Add(ourlibrary);
cecilAssembly.MainModule.Types.Add(t);
var streamVar = new VariableDefinition(cecilAssembly.MainModule.Import(typeof(System.IO.Stream)));
var msilStreamVar = new VariableDefinition(cecilAssembly.MainModule.Import(typeof(System.IO.Stream)));
var fileStreamVar = new VariableDefinition(cecilAssembly.MainModule.Import(typeof(System.IO.FileStream)));
var dllPath = new VariableDefinition(cecilAssembly.MainModule.Import(typeof(System.String)));
var msilResContents = new VariableDefinition(cecilAssembly.MainModule.Import(typeof(byte[])));
var unmanagedMsil = new VariableDefinition(cecilAssembly.MainModule.Import(typeof(IntPtr)));
method.Body.Variables.Add(streamVar);
method.Body.Variables.Add(fileStreamVar);
method.Body.Variables.Add(dllPath);
method.Body.Variables.Add(msilStreamVar);
method.Body.Variables.Add(msilResContents);
method.Body.Variables.Add(unmanagedMsil);
il.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.Reflection.Assembly).GetMethod("GetExecutingAssembly"))));
il.Body.Instructions.Insert(1, Instruction.Create(OpCodes.Ldstr, loaderResourceName));
il.Body.Instructions.Insert(2, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.Reflection.Assembly).GetMethod("GetManifestResourceStream", new Type[] { typeof(string) }))));
il.Body.Instructions.Insert(3, Instruction.Create(OpCodes.Stloc, streamVar));
il.Body.Instructions.Insert(4, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.IO.Path).GetMethod("GetTempPath"))));
il.Body.Instructions.Insert(5, Instruction.Create(OpCodes.Ldstr, loaderDllName));
il.Body.Instructions.Insert(6, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.String).GetMethod("Concat", new Type[] { typeof(string), typeof(string) }))));
il.Body.Instructions.Insert(7, Instruction.Create(OpCodes.Stloc, dllPath));
il.Body.Instructions.Insert(8, Instruction.Create(OpCodes.Ldloc, dllPath));
il.Body.Instructions.Insert(9, Instruction.Create(OpCodes.Ldc_I4_2));
il.Body.Instructions.Insert(10, Instruction.Create(OpCodes.Newobj, cecilAssembly.MainModule.Import(typeof(System.IO.FileStream).GetConstructor(new Type[] { typeof(string), typeof(System.IO.FileMode) }))));
il.Body.Instructions.Insert(11, Instruction.Create(OpCodes.Stloc, fileStreamVar));
il.Body.Instructions.Insert(12, Instruction.Create(OpCodes.Ldloc, streamVar));
il.Body.Instructions.Insert(13, Instruction.Create(OpCodes.Ldloc, fileStreamVar));
il.Body.Instructions.Insert(14, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("CopyTo", new Type[] { typeof(System.IO.Stream) }))));
il.Body.Instructions.Insert(15, Instruction.Create(OpCodes.Ldloc, fileStreamVar));
il.Body.Instructions.Insert(16, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("Close"))));
il.Body.Instructions.Insert(17, Instruction.Create(OpCodes.Nop));
il.Body.Instructions.Insert(18, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.IO.Path).GetMethod("GetTempPath"))));
il.Body.Instructions.Insert(19, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.IO.Directory).GetMethod("SetCurrentDirectory"))));
il.Body.Instructions.Insert(20, Instruction.Create(OpCodes.Nop));
il.Body.Instructions.Insert(21, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.Reflection.Assembly).GetMethod("GetExecutingAssembly"))));
il.Body.Instructions.Insert(22, Instruction.Create(OpCodes.Ldstr, msilResourceName));
il.Body.Instructions.Insert(23, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.Reflection.Assembly).GetMethod("GetManifestResourceStream", new Type[] { typeof(string) }))));
il.Body.Instructions.Insert(24, Instruction.Create(OpCodes.Stloc, msilStreamVar));
il.Body.Instructions.Insert(25, Instruction.Create(OpCodes.Ldloc, msilStreamVar));
il.Body.Instructions.Insert(26, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("get_Length"))));
il.Body.Instructions.Insert(27, Instruction.Create(OpCodes.Conv_Ovf_I));
il.Body.Instructions.Insert(28, Instruction.Create(OpCodes.Newarr, cecilAssembly.MainModule.Import(typeof(byte))));
il.Body.Instructions.Insert(29, Instruction.Create(OpCodes.Stloc, msilResContents));
il.Body.Instructions.Insert(30, Instruction.Create(OpCodes.Ldloc, msilStreamVar));
il.Body.Instructions.Insert(31, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("get_Length"))));
il.Body.Instructions.Insert(32, Instruction.Create(OpCodes.Conv_I4));
il.Body.Instructions.Insert(33, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.Runtime.InteropServices.Marshal).GetMethod("AllocHGlobal", new Type[] { typeof(Int32) }))));
il.Body.Instructions.Insert(34, Instruction.Create(OpCodes.Stloc, unmanagedMsil));
il.Body.Instructions.Insert(35, Instruction.Create(OpCodes.Ldloc, msilStreamVar));
il.Body.Instructions.Insert(36, Instruction.Create(OpCodes.Ldloc, msilResContents));
il.Body.Instructions.Insert(37, Instruction.Create(OpCodes.Ldc_I4_0));
il.Body.Instructions.Insert(38, Instruction.Create(OpCodes.Ldloc, msilStreamVar));
il.Body.Instructions.Insert(39, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("get_Length"))));
il.Body.Instructions.Insert(40, Instruction.Create(OpCodes.Conv_I4));
il.Body.Instructions.Insert(41, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("Read", new Type[] { typeof(byte[]), typeof(Int32), typeof(Int32) }))));
il.Body.Instructions.Insert(42, Instruction.Create(OpCodes.Pop));
il.Body.Instructions.Insert(43, Instruction.Create(OpCodes.Ldloc, msilResContents));
il.Body.Instructions.Insert(44, Instruction.Create(OpCodes.Ldc_I4_0));
il.Body.Instructions.Insert(45, Instruction.Create(OpCodes.Ldloc, unmanagedMsil));
il.Body.Instructions.Insert(46, Instruction.Create(OpCodes.Ldloc, msilStreamVar));
il.Body.Instructions.Insert(47, Instruction.Create(OpCodes.Callvirt, cecilAssembly.MainModule.Import(typeof(System.IO.Stream).GetMethod("get_Length"))));
il.Body.Instructions.Insert(48, Instruction.Create(OpCodes.Conv_I4));
il.Body.Instructions.Insert(49, Instruction.Create(OpCodes.Call, cecilAssembly.MainModule.Import(typeof(System.Runtime.InteropServices.Marshal).GetMethod("Copy", new Type[] { typeof(byte[]), typeof(Int32), typeof(IntPtr), typeof(Int32) }))));
il.Body.Instructions.Insert(50, Instruction.Create(OpCodes.Ldloc, unmanagedMsil));
il.Body.Instructions.Insert(51, Instruction.Create(OpCodes.Call, ourlibrary));
// Add RET if we are creating the module constructor
if (addRet)
{
il.Body.Instructions.Insert(52, Instruction.Create(OpCodes.Ret));
}
}
// Cache to avoid repeating lookups for RVA
static int virtualAddress = 0;
static int pointerRawData = 0;
// Converts RVA to file offset by parsing PE headers
public static int GetFileOffsetFromRVA(FileStream file, int RVA)
{
if (virtualAddress != 0)
{
return RVA - virtualAddress + pointerRawData;
}
BinaryReader binaryReader = new BinaryReader(file);
var sectionHeaderCount = 0x0;
// Get offset to IMAGE_PE_HEADER
binaryReader.BaseStream.Seek(0x3C, SeekOrigin.Begin);
var nt_header_offset = binaryReader.ReadInt32();
// Get number of IMAGE_SECTION_HEADER's
binaryReader.BaseStream.Seek(nt_header_offset + 0x14, SeekOrigin.Begin);
sectionHeaderCount = binaryReader.ReadInt16();
// Get size of IMAGE_OPTIONAL_HEADER
binaryReader.BaseStream.Seek(nt_header_offset + 0x14, SeekOrigin.Begin);
var sizeOfOptionalHeader = binaryReader.ReadInt16();
// Loop through each IMAGE_SECTION_HEADER to find the .text section
for (int i = 0; i < sectionHeaderCount; i++)
{
binaryReader.BaseStream.Seek(nt_header_offset + 0x18 + (i * 0x28) + sizeOfOptionalHeader, SeekOrigin.Begin);
// Now at IMAGE_SECTION_HEADER, check if this is our .text section
var sectionHeaderName = binaryReader.ReadBytes(8);
if (sectionHeaderName[0] == '.' &&
sectionHeaderName[1] == 't' &&
sectionHeaderName[2] == 'e' &&
sectionHeaderName[3] == 'x' &&
sectionHeaderName[4] == 't' &&
sectionHeaderName[5] == '\0'
)
{
// Get Virtual Address
binaryReader.BaseStream.Seek(nt_header_offset + 0x18 + (i * 0x28) + sizeOfOptionalHeader + 0xC, SeekOrigin.Begin);
virtualAddress = binaryReader.ReadInt32();
// Pointer to raw data
binaryReader.BaseStream.Seek(nt_header_offset + 0x18 + (i * 0x28) + sizeOfOptionalHeader + 0x14, SeekOrigin.Begin);
pointerRawData = binaryReader.ReadInt32();
// Calculate RVA
return RVA - virtualAddress + pointerRawData;
}
}
return 0;
}
public static void Main(string[] args)
{
MethodDefinition moduleConstructor = null;
AssemblyDefinition cecilAssembly = null;
System.Reflection.Assembly nativeAssembly = null;
FileStream fs = null;
BinaryWriter bw;
List<byte> msilData = new List<byte>();
uint offset = 0;
byte[] nativeMethodBody = null;
string target, outputPath;
// Locations for writing temporary copies of our tool
string tempOut = Path.GetTempFileName();
string tempWorking = Path.GetTempFileName();
Console.WriteLine("Cecil Native Hook POC by @_xpn_\n");
if (args.Length != 2)
{
Console.WriteLine("Usage: {0} target.exe output.exe", System.Environment.GetCommandLineArgs()[0]);
return;
}
target = args[0];
outputPath = args[1];
try
{
// Open the target assembly for parsing
cecilAssembly = Mono.Cecil.AssemblyDefinition.ReadAssembly(target);
nativeAssembly = System.Reflection.Assembly.LoadFile(target);
}
catch (System.Exception e)
{
Console.WriteLine("[!] Error opening target assembly: " + e.Message);
return;
}
// Create a resource to add our extracted loader DLL to
var loaderDll = File.ReadAllBytes(@"JitHook.dll");
EmbeddedResource r = new EmbeddedResource(loaderResourceName, ManifestResourceAttributes.Public, loaderDll);
cecilAssembly.MainModule.Resources.Add(r);
// Search for existing Module constructor
var moduleType = cecilAssembly.MainModule.Types.FirstOrDefault(x => x.Name == "<Module>");
moduleConstructor = moduleType.Methods.FirstOrDefault(x => x.Name == ".cctor");
if (moduleConstructor == null)
{
// No module constructor exists, so we need to create one
moduleConstructor = new MethodDefinition(".cctor",
MethodAttributes.Private |
MethodAttributes.HideBySig |
MethodAttributes.Static |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName,
cecilAssembly.MainModule.TypeSystem.Void
);
moduleType.Methods.Add(moduleConstructor);
// Add our loader to the module constructor
AddLoaderStub(cecilAssembly, moduleConstructor, true);
}
else
{
// Add our loader to the module constructor
AddLoaderStub(cecilAssembly, moduleConstructor, false);
}
Console.WriteLine("[*] Loader added to generated assembly");
// Save our generated assembly to allow Cecil to do its optimisation
cecilAssembly.MainModule.Write(tempOut);
try
{
// Create a new working copy which we can modify using a BinaryWriter
File.Copy(tempOut, tempWorking, true);
fs = File.Open(tempWorking, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
bw = new BinaryWriter(fs);
}
catch (System.Exception e)
{
Console.WriteLine("[!] Error generating temp working copy: " + e.Message);
return;
}
// Work from the Cecil generated copy of the target (as metadata tables will have been changed by inserting our loader code)
cecilAssembly = Mono.Cecil.AssemblyDefinition.ReadAssembly(tempOut);
nativeAssembly = System.Reflection.Assembly.LoadFile(tempOut);
// We need to make 2 passes here:
// Pass 0 - Gather MSIL for resource file
// Pass 1 - Modify target
for (int pass = 0; pass < 2; pass++)
{
if (pass == 1)
{
// Add the resource consisting of raw IL to the target assembly for passing to the compileAssembly hook
try
{
cecilAssembly.MainModule.Resources.Add(new EmbeddedResource(msilResourceName, ManifestResourceAttributes.Public, msilData.ToArray()));
cecilAssembly.MainModule.Write(outputPath);
fs = File.Open(outputPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
bw = new BinaryWriter(fs);
}
catch (System.Exception e)
{
Console.WriteLine("[!] Error: Could not write output assembly: " + e.Message);
return;
}
}
// Get our class from the loaded module
foreach (var module in cecilAssembly.Modules)
{
foreach (var type in module.Types)
{
foreach (var method in type.Methods)
{
// Don't mess up inheritence / generic functions or our module init function
if (!method.IsConstructor &&
!method.IsGenericInstance &&
!method.IsAbstract &&
!method.IsNative &&
!method.IsPInvokeImpl &&
!method.IsUnmanaged &&
!method.IsUnmanagedExport &&
!method.IsVirtual &&
method != module.EntryPoint &&
!method.IsSpecialName
)
{
if (method.HasBody)
{
// Ensure we have enough room for our signature
if (method.Body.CodeSize < 11)
{
continue;
}
// Translate method RVA to file offset as we will be obfuscating outside of Cecil
var o = GetFileOffsetFromRVA(fs, method.RVA);
if (o == 0)
{
continue;
}
// Work with raw method location
fs.Seek(o, SeekOrigin.Begin);
//Console.WriteLine("[*] Obfuscating {0}.{1} - {2} bytes at offset {3}", type.FullName, method.Name, method.Body.CodeSize, o);
// Read Method Header
var header = fs.ReadByte();
var headerOffset = 0;
if ((header & 0x03) == (int)2)
{
// This is a thin header
headerOffset = 1;
}
else
{
// This is a fat header
headerOffset = 12;
}
try
{
var nativeMethod = nativeAssembly
.GetModules()
.First()
.GetType(type.FullName)
.GetMethod(method.Name, System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Public
);
if (nativeMethod == null)
{
continue;
}
// Get raw IL data
nativeMethodBody = nativeMethod.GetMethodBody().GetILAsByteArray();
}
catch
{
continue;
}
// Store the raw IL for adding to a resource later
msilData.AddRange(nativeMethodBody);
if (pass == 1)
{
// NOP out the method body (to avoid changing the size when restoring later)
for (int z = 0; z < method.Body.CodeSize; z++)
{
fs.Seek(o + headerOffset + z, SeekOrigin.Begin);
fs.WriteByte(0x0);
}
// Add the resource offset as a ldc_i4
fs.Seek(o + headerOffset + 4, SeekOrigin.Begin);
bw.Write((byte)0x20);
bw.Write((uint)offset);
// Update the next offset location
offset += (uint)nativeMethodBody.Length;
}
}
}
}
}
}
}
// Purge the file buffer
fs.Close();
Console.WriteLine("[*] Success: Output file at {0}", outputPath);
}
}
}
#include "stdafx.h"
typedef void*(*getJitExport)(void);
// Offsets:
// 56 - Address
// 106 - Address
unsigned char trampoline[] = {
0x54, 0x50, 0x53, 0x48, 0x8b, 0x44, 0x24, 0x48, 0x48, 0x8b, 0x5c, 0x24,
0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41,
0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x56, 0x57, 0x48, 0x83, 0xec,
0x60, 0x48, 0x89, 0xd1, 0x4c, 0x89, 0xc2, 0x4d, 0x89, 0xc8, 0x49, 0x89,
0xc1, 0x48, 0x89, 0x5c, 0x24, 0x28, 0x48, 0xb8, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0xff, 0xd0, 0x48, 0x83, 0xc4, 0x60, 0x5f, 0x5e,
0x41, 0x5f, 0x41, 0x5e, 0x41, 0x5d, 0x41, 0x5c, 0x41, 0x5b, 0x41, 0x5a,
0x41, 0x59, 0x41, 0x58, 0x5a, 0x59, 0x5b, 0x58, 0x5c, 0x48, 0x89, 0xe0,
0x48, 0x89, 0x58, 0x08, 0x48, 0x89, 0x68, 0x10, 0x48, 0x89, 0x70, 0x18,
0x48, 0xbb, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0xff, 0xe3
};
char *originalILData;
void compileMethodHook(class ICorJitInfo* compHnd, CORINFO_METHOD_INFO* methodInfo, unsigned flags, BYTE** entryAddress, ULONG* nativeSizeOfCode) {
DWORD oldProtect;
DWORD oldOldProtect;
if (methodInfo->ILCode[0] == 0x00 &&
methodInfo->ILCode[1] == 0x00 &&
methodInfo->ILCode[2] == 0x00 &&
methodInfo->ILCode[3] == 0x00 &&
methodInfo->ILCode[4] == 0x20)
{
// Use the DWORD as an offset into the resource consisting of the original IL
char *originalILPtr = originalILData + *(unsigned long *)(&methodInfo->ILCode[5]);
// Update the page protection of the IL to allow us to update
VirtualProtect(methodInfo->ILCode, methodInfo->ILCodeSize, PAGE_READWRITE, &oldProtect);
// Copy original IL in place of our signature
memcpy(methodInfo->ILCode, originalILPtr, methodInfo->ILCodeSize);
// Restore the page protection
VirtualProtect(methodInfo->ILCode, methodInfo->ILCodeSize, oldProtect, &oldOldProtect);
}
return;
}
extern "C" {
// Parameter passed from .NET containing raw IL data to be restored
__declspec(dllexport) void Run(void* d) {
originalILData = (char *)d;
DWORD oldProtect, oldNewProtect;
// JMP hook we will inject into the compileMethod function
char jmpHook[] = "\x48\xb8\x41\x41\x41\x41\x41\x41\x41\x41\xff\xe0";
// Get the exported getJit function
getJitExport getJit = (getJitExport)GetProcAddress(LoadLibraryA("clrjit.dll"), "getJit");
// Call to get an instance to vtable for ICorJitCompiler object
void *vtable = getJit();
// Get compileMethod method from vtable
void *compileMethod = *(void **)(*(void **)vtable);
// Update the JMP to our trampoline
*(void **)(jmpHook + 2) = &trampoline;
*(void **)(trampoline + 56) = &compileMethodHook;
*(void **)(trampoline + 110) = (char *)compileMethod + 0xF;
VirtualProtect(trampoline, sizeof(trampoline), PAGE_EXECUTE_READ, &oldProtect);
// Add our hook to compileMethod
VirtualProtect(compileMethod, sizeof(jmpHook), PAGE_EXECUTE_READWRITE, &oldProtect);
memcpy(compileMethod, jmpHook, sizeof(jmpHook));
VirtualProtect(compileMethod, sizeof(jmpHook), oldProtect, &oldNewProtect);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment