您的位置:首页 > 理论基础 > 计算机网络

Shadow Hook实现网络安全打印监控

2014-06-04 14:04 405 查看

如何实现对打印的监控,微软提出的一种解决方案就是时刻检测放到打印队列中的打印任务,发现有任务出现,就从中筛选出来提供给调用者。对此功能的实现,微软的确公开了一套完整的代码,并且能够实现我们基本想要的功能,但是在实现功能之余,我又进行了更深一层的研究和测试,通过Hook win32k.sys内的打印相关的4个函数就完美地实现了打印监控功能。

我们先分析一下当系统完成一次打印任务需要调用的几个核心函数(对我们的程序有重要作用的),如表1所示。

Gdi32.dll

Win32k.sys

开始打印任务

StartDoc

NtGdiStartDoc

打印每一页

StartPage

NtGdiStartPage

结束每一页

EndPage

NtGdiEndPage

结束打印任务

EndDoc

NtGdiEndDoc

表1
其中的StartDoc和EndDoc是来控制每次打印任务的开始和结束;StartPage和EndPage控制每一页的打印,我的理解是需要打印的文档有多少页,StartPage和EndPage函数对就要调用多少次,实际上对函数调用测试也验证了这一点。

既然已经清楚了函数的功能和对应关系,想必如何实现应该有了点思路吧?对了,就是在这几个函数上动手脚——Hook!到底是Ring3下的Hook还是Ring0下的Hook就应该是仁者见仁了,本文通过在Ring0下的Hook来实现打印监控的功能!

下面是我们目前要解决的问题,我将逐个击破。

1) 如何定位到KeServiceDescriptorTableShadow?

2)在不同的操作系统版本下,Win32k.sys内的函数索引不同,我们该如何解决?

2) Win32k.sys的特殊性,并不是在每个进程都有映射,该如何进行Shadow Hook?

4)如何捕获到打印相关的信息?

如何定位到KeServiceDescriptorTableShadow,黑防以前的文章中都有描述,这里我只简单的说明一下。定位的思路是在KeAddSystemServiceTable函数中具体的信息,如何定位网上已给出了一套完整的代码,自己在Windows多个版本中测试时发现可以兼容!

nt!KeAddSystemServiceTable:

805a11d4 8bffmov
edi,edi

805a11d6 55push
ebp

805a11d7 8becmov
ebp,esp

805a11d9 837d1803cmp
dword ptr [ebp+18h],3

805a11dd 7760ja
nt!KeAddSystemServiceTable+0x6b (805a123f)

805a11df 8b4518mov
eax,dword ptr [ebp+18h]

805a11e2 c1e004shl
eax,4

805a11e5 83b800c7558000cmpdword ptr nt!KeServiceDescriptorTable (8055c700)[eax],0

805a11ec 7551jnent!KeAddSystemServiceTable+0x6b (805a123f)

805a11ee 8d88c0c65580leaecx,nt!KeServiceDescriptorTableShadow (8055c6c0)[eax]

805a11f4 833900cmpdword ptr [ecx],0

805a11f7 7546jnent!KeAddSystemServiceTable+0x6b (805a123f)

具体实现定位的代码如下:

ULONG
GetAddressOfShadowTable()

//得到SSDT Shadow的函数地址

{

unsigned
int i;

unsigned
char *p;

unsigned
int dwordatbyte;

p = (unsigned char*) KeAddSystemServiceTable;//该函数没有文档化,使用时需导出

for(i = 0;
i < 4096; i++,
p++)

{

__try

{

dwordatbyte = *(unsigned
int*)p;

}

__except(EXCEPTION_EXECUTE_HANDLER)

{

return 0;

}

if(MmIsAddressValid((PVOID)dwordatbyte))

{

if(memcmp((PVOID)dwordatbyte, &KeServiceDescriptorTable,
16) == 0)

{

if((PVOID)dwordatbyte == &KeServiceDescriptorTable)

{

continue;

}

return
dwordatbyte;

}

}//在KeAddSystemServiceTable中搜索匹配得到KeServiceDescriptorTableShadow

}

return 0;

}

既然定位KeServiceDescriptorTableShadow的代码能够在Windows的多个版本下准确的定位,我们的Hook也应该尽量做到最大的兼容性。不过有一个问题,就是在Shadow中的每个函数的具体索引并不能像SSDT那样可以轻松得到,我们可以通过不同的操作系统版本来给出具体的数值,毕竟只有4个函数,而我们做的只是判断一下当前运行的版本,然后填入正确的索引值即可!同时,这里似乎还隐含着一个问题,我们待会儿再说。先看看如何判断当前版本。

VOID
GetFunIndex()

{

ULONG
majorVersion, minorVersion;

PsGetVersion( &majorVersion, &minorVersion,
NULL, NULL );//这个函数是核心!

if (
majorVersion == 5 && minorVersion == 2 )

{

DbgPrint("Running on Windows 2003");

//进行数据的修正

}

else
if ( majorVersion == 5 &&
minorVersion == 1 )

{

DbgPrint("Running on Windows XP");

//进行数据的修正

}

else
if ( majorVersion == 5 &&
minorVersion == 0 )

{

DbgPrint("Running on Windows 2000");

//进行数据的修正

}

}

通过该函数就可以轻松地实现我们对不同版本的操作系统兼容性的要求,具体的数值接下来会用表格的形式呈现出来!

到了这里,我们应该就可以进行Hook了,但是如何确保win32k.sys已经映射到了我们的进程空间内呢?否则如果我们直接操作,后果就是BSOD啊!其实这个很简单,因为我们直接在DriverEntry函数中实现了这一系列的功能,也就是当前运行的进程空间处于SYSTEM进程内,而在SYSTEM进程内没有映射win32k.sys,所以我们对Shadow的Hook导致蓝屏也显而易见啦!所以,在我们Hook之前先Attache到一个确信映射了该文件的进程内就是必须的了,而Csrss.sys就是我们的首选!现在,我们的问题明确了,就是找到csrss.exe进程后Attach!我使用了遍历的方式来实现,不过只要能实现就OK啦!

ULONG
GetCsrssProcessId()//如果找到,返回PID,没有找到,返回

{

NTSTATUS
m_status=STATUS_SUCCESS;

HANDLE
m_process_id=0;//返回的ID

char
m_name[16]={0};//进程的名字

ULONG
m_index=0;

PEPROCESS
m_eprocess;//进程的对象

for (m_index=0;m_index<65535;m_index+=4)

{

m_status=PsLookupProcessByProcessId((HANDLE)m_index,&m_eprocess);

if(NT_SUCCESS(m_status))

{

//检测一下当前得到的是不是活动进程

strncpy(m_name,(char *)((ULONG)m_eprocess+m_name_offset),16);

//m_name_offset就是上面提到的隐含问题,不过如果是通过另外的方式定位的话,这个问题就不存在了

if (_stricmp(m_name,"csrss.exe")==0)

{

//表明已经得到了该进程的PID

DbgPrint("获得到的PID为:%d\n",m_index);

return
m_index;

}

}

}

return 0;

}

如果Hook掉提到的那4个函数,方法和SSDT的Hook完全相同,这儿我就简单的贴出代码吧。

VOID
TryToHookFun()//这儿执行的前提是已经Attach到了进程

{

PEPROCESS
m_eprocess;

KIRQL
m_irql;

NTSTATUS
m_status=STATUS_SUCCESS;

ULONG
m_process_id=0;

KAPC_STATE
m_apc_state;

m_process_id=GetCsrssProcessId();

m_status=PsLookupProcessByProcessId((HANDLE)m_process_id,&m_eprocess);

if(NT_SUCCESS(m_status))

{

m_irql=KeRaiseIrqlToDpcLevel();

//下面开始附加进程

KeStackAttachProcess(m_eprocess,&m_apc_state);//附加到csrss.exej进程

CloseProtected();//关闭保护---这个代码黒防到处都是!

……

//这里实现的就是对Shadow函数的具体替换

RecoverProtected();

KeUnstackDetachProcess(&m_apc_state);//取消进程的附加

KeLowerIrql(m_irql);

}

}

表2是函数和进程名在不同版本下的具体数值或偏移量。

Windows 2000

Windows XP

Windows 2003

NtGdiStartDoc索引

280

290

289

NtGdiStartPage索引

281

291

290

NtGdiEndPage索引

126

131

131

NtGdiEndDoc索引

125

130

130

ImageFileName偏移

0x1fc

0x174

0x164

表2
至此,我们的前3个问题终于完美解决了,开始我们的打印监控了!如上所说,这4个函数的配合使用就可以轻松获得打印信息!打印的开始时间、文档名称等信息在NtGdiStartDoc中获得;打印的页数从NtGdiStartPage的调用次数中获得;结束时间在NtGdiEndDoc中获得!根据这4个函数的参数来看,都有一个HDC
参数,是不是可以根据这个HDC来捕获到打印内容呢?这个我还没有去测试,至少当前可以捕获到的信息已经满足一定的要求了,有兴趣的可以试试!

对于这4个函数如何配合使用,应该说是仁者见仁,智者见智,本文我只是简单实现了打印监控,然后按照一定格式写入了文件;如果读取,就在Ring3层写个程序把文件解析显示即可,同样达到了内核和用户层的交互,毕竟打印监控没有实时性的要求!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: