简介
随着攻防博弈的发展,无文件攻击在各项场景中出现频率越来越高,研究无文件攻击的实现与检测对于抵御外部攻击有着重要的意义。.Net程序集的内存加载是无文件攻击实施的手段之一,目前现有的加载技术大多适用于Net FrameWork框架, 本文针对.Net Core和.Net FrameWork中的加载方式和卸载方式进行了分析探讨。
01 加载
.Net程序集加载应用很普遍,像冰蝎中aspx马,就是使用Assembly.Load来加载各种功能的程序集从而实现webshell控制。yso.net工具中有条反序列化利用链通过Assembly.load直接加载恶意程序集。asp.net内存马也可以通过Assembly.load注入,还可以通过多阶段加载方式来进行防御规避。
根据微软官方文档,.NET中的程序集加载方式有下面几种:
Assembly.Load |
此方式将程序集加载到当前上下文 |
Assembly.LoadFrom |
此方式将程序集加载到当前上下文 |
Assembly.LoadFile |
此方式将程序集加载到当前上下文 |
Assembly.ReflectionOnlyLoad AssemblyReflectionOnlyLoadFrom |
程序集加载到反射上下文,无法执行代码 |
AppDomain.CreateInstance |
创建指定类型的实例 |
AppDomain.CreateInstanceAndUnwrap |
返回创建的实例的代理, 把其他应用程序域中的变量传到当前的应用程序域 |
Type.GetType |
|
AppDomain.Load |
|
AssemblyLoadContext.Load |
.Net Core中的新增,允许加载同一程序集的不同版本到不同上下文 |
以上加载方式,Assembly.Load和AssemblyLoadContext.Load支持字节流加载。
Assembly.LoadFrom 加载dll文件及依赖项。它还可以从远程加载程序集,支持http、file等协议,但从.Net FrameWork 4后,默认禁止远程加载。此外,如果目标程序集已经加载过,LoadFrom()不会重新进行加载。
Assembly.LoadFile 仅加载dll不加载依赖项。
AssemblyLoadContext.Load是Net core中为替代Appdomain提出的新的进程内隔离解决方案。Load会第一次解析、加载和返回程序集。如果第一次返回null,则加载程序会尝试将程序集加载到默认上下文AssemblyLoadContext.Default中。如果AssemblyLoadContext.Default无法解析程序集,则原始AssemblyLoadContext将有第二次机会解析程序集。运行时引发Resolving事件,我们可以自定义Resolving事件来编写依赖项解析行为。下面这个例子,将Test.dll加载到新的上下文中,Test.dll会打印出当前的上下文名字。
AssemblyLoadContext assemblyLoadContext = new AssemblyLoadContext("New1",true); //true允许unload
var assem = assemblyLoadContext.LoadFromStream(new MemoryStream( File.ReadAllBytes("Test.dll")));
assem.GetType("Test.Test").GetMethod("Run").Invoke(null,null);
运行结果如下,可以看到,程序集加载到了新的上下文中:
如果只是想在其他Appdomain执行方法,可以调用domain.DoCallBack()。CrossAppDomainDelegate是委托类型,签名如下:
[System.Runtime.InteropServices.ComVisible(true)]
public delegate void CrossAppDomainDelegate();
public void DoCallBack (CrossAppDomainDelegate callBackDelegate);
举个例子,运行该代码,可以把字节码加载到新的Appdomain中:
public static void Main(){
AppDomain domain = AppDomain.CreateDomain("MyDomain");
domain.DoCallBack(new CrossAppDomainDelegate(CallbackTest));
}
public static void CallbackTest()
{
Assembly.Load(System.IO.File.ReadAllBytes(@"E.dll"));
var s = AppDomain.CurrentDomain.FriendlyName;
}
02 卸载
在.NET Core 中,System.Runtime.Loader.AssemblyLoadContext类处理程序集的卸载。在Net FrameWork中,如果我们想要卸载单独的程序集,那是做不到的,想要在 .NET Framework 中卸载程序集,必须卸载包含它的所有应用程序域。假设内存加载了一个恶意程序集到新的应用程序域,来看一下应用程序域如何被卸载。卸载应用程序域使用AppDomain.Unload 方法。
下面看一段代码,这段代码的作用是弹窗,编译成程序集E.dll
namespace E
{
public class E
{
public void Run()
{
try
{
System.Windows.Forms.MessageBox.Show("Run","Title");
}
catch (Exception)
{
}
}
}
}
下面代码读取了E.dll的字节码,新创建一个Appdomain,然后以Load(byte[])方法加载程序集E.dll到新应用程序域当中,反射调用Run方法,最后卸载Appdomain。
byte[] bytes = File.ReadAllBytes("E.dll"); // dll bytes
AppDomain domain = AppDomain.CreateDomain("Test");
Assembly assembly = domain.Load(bytes);
MethodInfo method = assembly.GetType("E.E").GetMethod("Run");
if (method != null)
{
object o = assembly.CreateInstance("E.E");
try
{
method.Invoke(o, null);
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.ToString());
}
}
看起来没什么错误,但一旦运行后,就会报下面异常。
其原因是Appdomain.Load(byte[])加载的程序集,在Appdomain间是无法共享类型信息的。如果默认Appdomain没有加载这个程序集,那么就不会有关于此程序集的类型信息,第二个Appdomian用Load(Byte[])加载后,要把程序集对象传递到默认Appdomain中,但默认Appdomain找不到此对象的信息,所以会报错。解决方式就是使用跨应用程序域访问。
一般来说,有两种方式进行跨域通信,marshalbyrefobject和Serializable,前者是引用传递,后者是值传递。看个引用传递的例子。首先创建Proxy类继承MarshalByObject,MarshalByObject类属于基元类型,在默认Appdomain中存在。然后调用CreateInstanceAndUnwrap得到Proxy类的代理类。CreateInstanceAndUnwrap方法是CreateInstance和Objecthandle.Unwrap方法的结合,Proxy类实际上在MyDomain中实例化,然后通过Objecthandle.Unwrap包装后传递给默认Appdomain,这样便实现了跨AppDomain的对象传递。之后Proxy类的方法调用,实际上也是在Mydomain中执行的。
void Main(){
byte[] classbytes = File.ReadAllBytes("E.dll");
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
System.Security.Policy.Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain currentDomain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
Proxy p = (Proxy)currentDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, type.FullName);
p.LoadAssembly(classbytes);
AppDomain.Unload(currentDomain);
}
public class Proxy: MarshalByObject{
public Object LoadAssembly(byte[] bytes)
{
Console.WriteLine(Appdomain.Currentdomain.FriendlyName);
Assembly assembly = AppDomain.CurrentDomain.Load(bytes);
MethodInfo method = assembly.GetType("E.E").GetMethod("Run");
if (method != null)
{
object o = assembly.CreateInstance("E.E");
try
{
var res = method.Invoke(o, null);
return res;
}
catch (TargetInvocationException ex)
{
Console.WriteLine(ex.ToString());
}
}
return null;
}
}
运行结果如下:
03 总结
本文主要针对.Net Core和.Net FrameWork框架下的程序集加载技术进行了总结,针对程序集的卸载技术进行了分析,指出了使用无文件加载后,如何正确卸载程序集的方式。
附录:参考链接
[1]https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=net-7.0
[2]https://learn.microsoft.com/en-us/dotnet/core/dependency-loading/understanding-assemblyloadcontext
[3]https://learn.microsoft.com/en-us/dotnet/api/system.runtime.remoting.objecthandle?view=net-7.0
[4]https://3gstudent.github.io/%E4%BB%8E%E5%86%85%E5%AD%98%E5%8A%A0%E8%BD%BD.NET%E7%A8%8B%E5%BA%8F%E9%9B%86(Assembly.Load)%E7%9A%84%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90
绿盟科技天元实验室专注于新型实战化攻防对抗技术研究。
研究目标包括:漏洞利用技术、防御绕过技术、攻击隐匿技术、攻击持久化技术等蓝军技术,以及攻击技战术、攻击框架的研究。涵盖Web安全、终端安全、AD安全、云安全等多个技术领域的攻击技术研究,以及工业互联网、车联网等业务场景的攻击技术研究。通过研究攻击对抗技术,从攻击视角提供识别风险的方法和手段,为威胁对抗提供决策支撑。
M01N Team公众号
聚焦高级攻防对抗热点技术
绿盟科技蓝军技术研究战队
官方攻防交流群
网络安全一手资讯
攻防技术答疑解惑
扫码加好友即可拉群
原文始发于微信公众号(M01N Team):.Net内存加载与卸载探讨