通过.NET实现内存加载PE文件
0x00 前言
在之前的文章《从内存加载.NET程序集(execute-assembly)的利用分析》和《从内存加载.NET程序集(Assembly.Load)的利用分析》曾介绍了使用C Sharp从内存加载.NET程序集的方法,这次将要更近一步,介绍使用C Sharp从内存加载PE文件的方法
0x01 简介
本文将要介绍以下内容:
- 实现原理
- Casey Smith开源的PELoader.cs
- 扩展PELoader.cs的方法
- SharpPELoaderGenerater的实现细节
- 利用方法
0x02 内存加载PE文件的实现原理
实现原理如下:
- 读取PE文件,按照PE格式进行解析
- 申请内存,ImageBase作为内存基地址,SizeOfImage作为长度
- 将PE文件头复制到内存中
- 解析Section的地址并将Section复制到内存中
- 基于重定位表修改内存
- 解析导入表,加载所需的Dll
- 跳转到入口地址AddressOfEntryPoint,执行PE文件
0x03 Casey Smith开源的PELoader.cs
目前可供参考的地址:
https://github.com/re4lity/subTee-gits-backups/blob/master/PELoader.cs
这个代码可以使用.NET 4.0或者更高版本下的csc.exe进行编译
编译命令如下:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /unsafe PELoader.cs
代码实现了在内存中加载64位的mimikatz.exe
PELoader.cs在字符串KatzCompressed
存储经过编码后的mimikatz.exe
如果想要进行替换,可以参考我的代码,地址如下:
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/GzipandBase64.cs
执行后生成文件base64.txt,将其中的内容用来替换字符串KatzCompressed
0x04 扩展PELoader.cs的方法
1.增加支持的编译环境
PELoader.cs因为使用了.Add()
导致不支持.Net3.5,可以对此进行替换使其支持.Net3.5
2.支持32位程序的加载
需要区分32位和64位程序PE结构的不同,重新计算偏移
扩展PELoader.cs的代码已上传至github,地址如下:
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/SharpMimikatz_x86.cs
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/SharpMimikatz_x64.cs
分别对应内存加载32位和64位mimikatz的代码
支持.Net3.5及更新版本
SharpMimikatz_x86.cs的编译命令如下:
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
SharpMimikatz_x64.cs的编译命令如下:
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 SharpPELoaderGenerater的实现细节
以Casey Smith开源的PELoader.cs为模板,尝试用c#实现自动生成加载PE文件的模板
这里以SharpMimikatz_x64.cs为例,代码可分成以下三部分:
- 前半部分调用代码
- exe文件经过压缩后的字符串
- 后半部分的调用代码
代码的生成方法如下:
1.前半部分调用代码
由于前半部分的调用代码存在多个转义字符,直接保存在string数组中比较麻烦,这里的思路是将前半部分的代码先经过压缩编码后再保存到string数组中,这样还能够大幅缩小代码长度(31kb压缩至6kb)
压缩前半部分代码可使用如下c#代码:
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();
}
}
}
执行后生成文件SharpMimikatz_x86_part.txt和SharpMimikatz_x64_part.txt,内容为压缩编码后的前半部分调用代码
2.exe文件经过压缩后的字符串
可使用以下代码生成:
byte[] AsBytes = File.ReadAllBytes(@"mimikatz.exe");
byte[] compress = Compress(AsBytes);
string source = Convert.ToBase64String(compress);
3.后半部分的调用代码
这里需要使用转义字符
可使用以下代码进行定义:
string source3_x86 = "\";\r\n }\r\n } ";
作为代码生成模板,还需要区分exe是32位还是64位,判断方法如下:
通过IMAGE_FILE_HEADER
结构体中的Characteristics
字段,如果存在属性IMAGE_FILE_32BIT_MACHINE
,那么该exe文件为32位
完整代码已上传至github,地址如下:
https://github.com/3gstudent/Homework-of-C-Sharp/blob/master/SharpPELoaderGenerater.cs
用法实例:
SharpPELoaderGenerater.exe test.exe
如果test.exe为32位的程序,将会生成文件SharpPELoader_x86.cs
编译方法:
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
如果test.exe为64位的程序,将会生成文件SharpPELoader_x64.cs
编译方法:
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
补充:
使用c++编写test.exe时需要注意手动加上需要调用的dll
实例代码如下:
#include "stdafx.h"
#include <windows.h>
#pragma comment(lib,"User32.lib")
int main(int argc, char *argv[])
{
LoadLibrary(L"User32.dll");
MessageBox(NULL, NULL, NULL, MB_OK);
return 0;
}
0x06 小结
本文介绍了通过.NET实现内存加载PE文件的方法,以Casey Smith开源的PELoader.cs为模板,编写代码生成工具SharpPELoaderGenerate,分享代码开发需要注意的细节