Loading PE files into memory via .NET

Onedaysec
4 min read
1 views
Loading PE files into memory via .NET — One Day Sec default thumbnail

0x00 Preface

---

In previous articles 'Exploitation Analysis of Loading .NET Assemblies from Memory (execute-assembly)' and 'Exploitation Analysis of Loading .NET Assemblies from Memory (Assembly.Load)', methods for loading .NET assemblies from memory using C# were introduced. This time, we will go a step further to introduce methods for loading PE files from memory using C#.

0x01 Introduction

---

This article will cover the following:

  • Implementation Principles
  • Casey Smith's open-source PELoader.cs
  • Methods to extend PELoader.cs
  • Implementation details of SharpPELoaderGenerater
  • Exploitation methods

0x02 Implementation Principles of Loading PE Files into Memory

---

The implementation principles are as follows:

  1. Read the PE file and parse it according to the PE format
  2. Allocate memory, using ImageBase as the memory base address and SizeOfImage as the length
  3. Copy the PE file header into memory
  4. Parse the Section addresses and copy the Sections into memory
  5. Modify memory based on the relocation table
  6. Parse the import table and load the required DLLs
  7. Jump to the entry point AddressOfEntryPoint and execute the PE file

0x03 Casey Smith's open-source PELoader.cs

---

Currently available reference address:

https://github.com/re4lity/subTee-gits-backups/blob/master/PELoader.cs

This code can be compiled using csc.exe under .NET 4.0 or higher

The compilation command is as follows:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /unsafe PELoader.cs

The code implements loading 64-bit mimikatz.exe in memory

PELoader.cs stores the encoded mimikatz.exe in the string KatzCompressed

If you want to perform a replacement, you can refer to my code at the following address:

An open-source project

After execution, the file base64.txt is generated. Use its content to replace the string KatzCompressed

0x04 Extending the methods of PELoader.cs

---

1. Adding support for compilation environments

PELoader.cs uses .Add() which makes it incompatible with .Net 3.5. This can be replaced to support .Net 3.5

2. Supporting 32-bit program loading

It is necessary to distinguish between the PE structures of 32-bit and 64-bit programs and recalculate the offsets

The extended PELoader.cs code has been uploaded to GitHub at the following address:

An open-source project

An open-source project

Corresponding to the code for loading 32-bit and 64-bit mimikatz in memory, respectively

Supports .Net 3.5 and newer versions

The compilation command for SharpMimikatz_x86.cs is as follows:

C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe /unsafe /platform:x86 SharpMimikatz_x86.cs
or
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /unsafe /platform:x86 SharpMimikatz_x86.cs

The compilation command for SharpMimikatz_x64.cs is as follows:

C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe /unsafe /platform:x64 SharpMimikatz_x64.cs
or
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /unsafe /platform:x64 SharpMimikatz_x64.cs

0x05 Implementation Details of SharpPELoaderGenerater

---

Using Casey Smith's open-source PELoader.cs as a template, attempt to implement automatic generation of PE file loading templates in C#

Here, taking SharpMimikatz_x64.cs as an example, the code can be divided into the following three parts:

  1. Front-end calling code
  2. Compressed string of the exe file
  3. Back-end calling code

The code generation method is as follows:

1. First half calling code

Since the first half calling code contains multiple escape characters, storing it directly in a string array is cumbersome. The approach here is to compress and encode the first half code before storing it in the string array, which also significantly reduces code length (from 31kb to 6kb).

The first half code can be compressed using the following C# code:

using System;
using System.IO;
using System.IO.Compression;

namespace GenerateCode
{
class Program
{
static byte[] Compress(byte[] raw)
{
using (MemoryStream memory = new MemoryStream())
{
using (GZipStream gzip = new GZipStream(memory,
CompressionMode.Compress, true))
{
gzip.Write(raw, 0, raw.Length);
}
return memory.ToArray();
}
}
static void Main(string[] args)
{
byte[] AsBytes = File.ReadAllBytes(@"SharpMimikatz_x86_part.cs");
byte[] compress = Compress(AsBytes);
String AsBase64String = Convert.ToBase64String(compress);

StreamWriter sw = new StreamWriter(@"SharpMimikatz_x86_part.txt");
sw.Write(AsBase64String);
sw.Close();

byte[] AsBytes2 = File.ReadAllBytes(@"SharpMimikatz_x64_part.cs");
byte[] compress2 = Compress(AsBytes2);
String AsBase64String2 = Convert.ToBase64String(compress2);

StreamWriter sw2 = new StreamWriter(@"SharpMimikatz_x64_part.txt");
sw2.Write(AsBase64String2);
sw2.Close();
}
}
}

After execution, files SharpMimikatz_x86_part.txt and SharpMimikatz_x64_part.txt are generated, containing the compressed and encoded first half of the calling code

The compressed string of the 2.exe file

Can be generated using the following code:

byte[] AsBytes = File.ReadAllBytes(@"mimikatz.exe");
byte[] compress = Compress(AsBytes);
string source = Convert.ToBase64String(compress);

3. Call code for the latter part

Escape characters need to be used here

Can be defined using the following code:

string source3_x86 = "\\";\r\n }\r\n } ";

As a code generation template, it is also necessary to distinguish whether the exe is 32-bit or 64-bit. The judgment method is as follows:

Through the Characteristics field in the IMAGE_FILE_HEADER structure, if the attribute IMAGE_FILE_32BIT_MACHINE exists, then the exe file is 32-bit

The complete code has been uploaded to GitHub, address as follows:

An open-source project

Usage example:

SharpPELoaderGenerater.exe test.exe

If test.exe is a 32-bit program, the file SharpPELoader_x86.cs will be generated

Compilation method:

C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe /unsafe /platform:x86 SharpPELoader_x86.cs
or
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /unsafe /platform:x86 SharpPELoader_x86.cs

If test.exe is a 64-bit program, it will generate the file SharpPELoader_x64.cs

Compilation method:

C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe /unsafe /platform:x64 SharpPELoader_x64.cs
or
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /unsafe /platform:x64 SharpPELoader_x64.cs

Additional note:

When writing test.exe in C++, it is necessary to manually add the DLLs that need to be called

Example code is as follows:

#include "stdafx.h"
#include
#pragma comment(lib,"User32.lib")
int main(int argc, char *argv[])
{
LoadLibrary(L"User32.dll");
MessageBox(NULL, NULL, NULL, MB_OK);
return 0;
}

0x06 Summary

---

This article introduces methods for implementing in-memory PE file loading via .NET. Using Casey Smith's open-source PELoader.cs as a template, we developed the code generation tool SharpPELoaderGenerate and share key details to note during code development.

Related Questions & Answers

What is SharpPELoaderGenerater and how does it automate PE loader generation?

SharpPELoaderGenerater is a tool that automatically generates C# code to load any given PE file into memory, based on the PELoader.cs template. It works by compressing the front‑half calling code and the executable itself, then combining them with an escape‑processed back‑half template. The tool automatically detects whether the input PE is 32‑bit or 64‑bit and produces `SharpPELoader_x86.cs` or `SharpPELoader_x64.cs`, which can then be compiled using the corresponding `csc.exe` command.

What modifications were made to PELoader.cs to support 32-bit executables and older .NET versions?

To support .NET 3.5, the `.Add()` method was replaced with alternative collection initialization syntax. For 32-bit support, the code now checks the `Characteristics` field in `IMAGE_FILE_HEADER` to determine whether the PE is 32-bit (`IMAGE_FILE_32BIT_MACHINE`) and recalculates offsets accordingly using the 32-bit PE structures. The extended versions, named `SharpMimikatz_x86.cs` and `SharpMimikatz_x64.cs`, can be compiled with the appropriate `csc.exe` and platform flags.

How does Casey Smith's PELoader.cs work and what limitations did the article address?

PELoader.cs encodes a compressed 64-bit version of mimikatz.exe as a base64 string and loads it into memory using the steps described above. The original code uses `.Add()` which is incompatible with .NET 3.5, and it only supports 64-bit executables. The article extended PELoader.cs to support both 32-bit and 64-bit PE files and to compile under .NET 3.5 or higher by replacing `.Add()` and distinguishing PE structures via the `IMAGE_FILE_HEADER` characteristics.

What are the main steps to load a PE file into memory from a .NET application?

The process involves reading and parsing the PE file according to its format, allocating memory using `ImageBase` as the base address and `SizeOfImage` as length, then copying the PE header and sections into memory. The relocation table is processed to adjust addresses, the import table is parsed to load required DLLs, and finally execution jumps to the `AddressOfEntryPoint`. This technique builds on earlier methods for [loading .NET assemblies from memory](/news/analysis-of-exploitation-techniques-for-loading-net-assemblies-from-memory-assembly-load), but extends them to native PE files. More details can be found in the full article [Loading PE files into memory via .NET](/news/loading-pe-files-into-memory-via-net).

Continue Reading