通过.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文件的实现原理


实现原理如下:

  1. 读取PE文件,按照PE格式进行解析
  2. 申请内存,ImageBase作为内存基地址,SizeOfImage作为长度
  3. 将PE文件头复制到内存中
  4. 解析Section的地址并将Section复制到内存中
  5. 基于重定位表修改内存
  6. 解析导入表,加载所需的Dll
  7. 跳转到入口地址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为例,代码可分成以下三部分:

  1. 前半部分调用代码
  2. exe文件经过压缩后的字符串
  3. 后半部分的调用代码

代码的生成方法如下:

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,分享代码开发需要注意的细节


LEAVE A REPLY

Written on March 12, 2020