MmIsAddressValid
这个函数的作用是不够准确的,因为它只会判断该地址的一个字节的有效性,例如:
if(MmIsAddressValid(p1))
{
memcpy(p1,p2);
}
那么攻击者只需要传入一个字节在有效页,比如0x8000这一页是有效的的,0x9000这一页是未分配的,传入0x8FFF将会使得系统崩溃。
其次MmIsAddressValid
并不能准确地判断pageout的页面。来源于微软的说明:
典型的错误:
try{
ProbeForRead(Buff,Len,Alig);
}
__except(EXECUTE_HANDLER_EXCEPTION)
{
...
}
if (memcmp(Buff , Buff2 , Len)
.....
ProbeForRead 只检查在 probe 的那一刻, buff 地址是在用户态地址范围内。
在try,except块之外进行读写,这个时候并不能保证buff还是用户态的地址。
当一块缓存长度为0的时候,很多函数都不会对其进行检验而直接跳过,比如曾经的ProbeForXXXX:
所以当使用了ProbeForXXX验证了参数,一样要当心长度为0的时候的情况:
__try{
ProbeForRead(Str1,Len,sizeof(WCHAR));
if(wcsnicmp(Str1,Str2,wcslen(Str2))
{
....
}
__except(EXECUTE_HANDLER_EXCEPTION)
{
....
}
当Lenth = 0的时候,这样的代码一样会导致系统崩溃。而且还有一点需要特别注意:
长度为空的缓存如果用做一些函数的参数可能具有特别的意义,比如:对于 ObjectAttributes->ObjectName的 Length,如果为空,系统会以对应的参数打开ObjectAttributes->RootDirectory 的句柄,攻击者可以先以低权限得到一个受保护对象的句柄,再以长度为空的缓存,将句柄填入RootDirectory 来获取高权限的句柄。
千万不要判断用户态的指针是否为NULL来判别是否放行,因为Windows7及其之前是允许用户申请地址为0的内存的,所以导致了可绕过这类型的判断,
由此拓展的还有就是缓存对齐的绕过方式和不正确的内核函数的调用。
ProbeForRead 的第三个参数 Alig 即对齐, 如果没有正确地传递这个函数, 也会导致问题,例如对于 ObjectAttributes ,系统默认按 1 来对齐,如果在对其参数处理中使用 sizeof(ULONG)来对齐,就会对本来可以使用的参数引发异常,绕过保护或检查。
对于用户态句柄使用 ObRefenceObjectByHandle, 不指定类型仍可以获得对应的对象地址,但如果你直接访问这个对象,就会引发漏洞,
比较常见的错误:
ObReferenceObjectByHandle(FileHandle , Access , NULL(ObjectType) ,...&fileobject);
if(wcsnicmp(fileobject->FileName....)
攻击者可以传入非文件类型的句柄从而造成系统漏洞。
不能将任何用户态地址传入Zw函数来传递给内核,用户态内存未经过校验传递给 ZwXXX 会让系统忽略内存检查(因为 ZwXXX 调用时认为上个模式已经是内核模式),即使你进行了校验,传递这样的内存给系统也可以引发崩溃(例如内存页在调用时突然无效),即使你在外部有异常捕获,也可能造成内核内存泄露、对象泄露,甚至权限提升等严重问题。
常见的错误:
__try{
ProbeForRead(ObjectAttrbutes,sizeof(OBJECT_ATTRIBUTES),1);
ProbeForRead(ObjectAttributes->ObjectName , sizeof(UNICODE_STRING),1);
ProbeForRead(ObjectAttributes->ObjectName->Buffer ,
ObjectAttributes->ObjectName->Length,sizeof(WCHAR));
ZwOpenKey(&hkey,ObjectAttributes....)
//未校验全部参数就传递给 Zw 函数, ObjectAttributes 还有多个域
//即使全部校验了也不能传递给 Zw 函数,例如内存如果是无效的用户态内存,
//最后会被我们驱动的异常捕获,但是内核函数的数据回收很可能没有进行
}
接受用户输入的内核对象意味着可以轻易构造内核任意地址写入漏洞,不要在设备控制中接受任何用户输入的内核对象并将其传递给内核函数。
例如:
if(IoControlCode==IOCTL_RELEASE_MUTEX)
{
KeReleaseMutex((MY_EVT_INFO)(Irp->AssociatedIrp->SystemBuffer)->Mutex);
}
用户态程序只需要传递一个精心构造的 Mutex 对象 (可以位于用户态内存 ),就可以做到任意地址写入,提升权限。
例如可以对注册表、文件、内核内存、进程线程等操作的功能性接口,一定要非常小心, 如果不能完全杜绝存在被恶意利用的可能, 一定要限制设备控制的调用者,禁止一切非受信进程的调用。
如果不小心控制的话很可能会造成白利用这种攻击。
更新时间于9.20号下午5点。
如有更深一步的理解再更新。