原副标题:64和硕豫采用反弹表达式同时实现监视
序言
在32位的控制系统下,他们想同时实现这类监视极为单纯,只须要找出相关联的API同时实现挂勾操作方式方可检验民主化。但在64位控制系统下随著 Patch Guard 的导入,引致他们如果竭尽全力采用挂勾API的形式展开监视会再次出现不受控的情形再次出现。谷歌也考虑到了描述符的合作开发,因此对外开放了方便快捷使用者初始化的控制系统反弹API表达式,在64位控制系统下的监视,采用控制系统反弹相对于间接hook的形式常常是更值得追捧的另一方。
民主化监视&为保护 PsSetCreateProcessNotifyRoutineEx
那个表达式主要就是增设民主化反弹监视民主化建立与选择退出
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
[in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
[in] BOOLEAN Remove
) ;
PsSetCreateProcessNotifyRoutineEx 那个表达式并并非就行了就能采用的,谷歌为的是保证可靠性明确要求保有身分验证的驱动力才能采用此表达式。这儿谷歌怎样检验与否有身分验证呢?这儿就采用到了强制性完整性检查和
强制性完整性检查和是一类保证已经开始读取的十进制文档在读取前须要采用亲笔签名的思路, IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 象征在镜像时透过采用 /integritycheck 镜像器象征在PE头中展开增设,让已经开始读取的十进制文档要亲笔签名,那个象征使windows缓存命令行在读取时对十进制文档展开亲笔签名检查和
因此谷歌是透过读取十进制文档时与否存有象征来证实驱动力的公共信息身分与否为已知状态,这是强制性完整性检查和
这儿在内核里面,windows采用到 MmVerifyCallbackFunction 那个内核表达式来判断
到IDA里面竭尽全力跟 MmVerifyCallbackFunction 那个表达式,发现其逻辑是透过比较 [rax+68h] 与否包含了0x20来判断与否保有正确的身分验证
这儿的 rax 表示 DriverSection ,而 DriverSection 指向的是 _LDR_DATA_TABLE_ENTRY 结构,因此 [rax + 0x68] 指向的是 ProcessStaticImport
因此假如他们要采用 PsSetCreateProcessNotifyRoutineEx 那个表达式就须要保有身分验证,这儿他们就可以将 DriverObject->DriverSection->Flags 的值与 0x20 按位或方可
这儿他们就可以编写一个绕过强制性完整性检查和的表达式,注意一下在32位和64位结构体的定义不同,须要分开定义
BOOLEAN bypass_signcheck(PDRIVER_OBJECT pDriverObject)
{
# ifdef_WIN64
typedefstruct_ KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG64 __Undefined1;
ULONG64 __Undefined2;
ULONG64 __Undefined3;
ULONG64 NonPagedDebugInfo;
ULONG64 DllBase;
ULONG64 EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined5;
ULONG64 __Undefined6;
ULONG CheckSum;
ULONG __padding1;
ULONG TimeDateStamp;
ULONG __padding2;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
# else
typedefstruct_ KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG unknown1;
ULONG unknown2;
ULONG unknown3;
ULONG unknown4;
ULONG unknown5;
ULONG unknown6;
ULONG unknown7;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
# endif
PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
pLdrData->Flags = pLdrData->Flags |0x20;
returnTRUE;
}
到这儿他们就已经绕过了谷歌的强制性完整性检查和,能初始化 PsSetCreateProcessNotifyRoutineEx 表达式,可以看到 PsSetCreateProcessNotifyRoutineEx 的第一个参数指向 CREATE_PROCESS_NOTIFY_ROUTINE_EX ,来执行他们须要执行的反弹表达式,这儿他们竭尽全力看 PCREATE_PROCESS_NOTIFY_ROUTINE_EX 那个结构
PCREATE_PROCESS_NOTIFY_ROUTINE_EX
第一个参数是 Process ,指向 EPROCESS 结构,第二个参数 ProcessId 是PID,第三个参数 CreateInfo 是一个指向 PS_CREATE_NOTIFY_INFO 的指针,当它为 NULL 时表明民主化选择退出,不为 NULL 时表明民主化建立,里面存储着要建立的民主化信息
PCREATE_PROCESS_NOTIFY_ROUTINE_EX PcreateProcessNotifyRoutineEx;
voidPcreateProcessNotifyRoutineEx(
[_Inout_] PEPROCESS Process,
[in] HANDLE ProcessId,
[in, out, optional] PPS_CREATE_NOTIFY_INFO CreateInfo
)
{…}
msdn的定义如下
然后他们再去看一下 PS_CREATE_NOTIFY_INFO
PS_CREATE_NOTIFY_INFOtypedefstruct_ PS_CREATE_NOTIFY_INFO{
SIZE_T Size;
union{
ULONG Flags;
struct{
ULONG FileOpenNameAvailable : 1;
ULONG IsSubsystemProcess : 1;
ULONG Reserved : 30;
};
};
HANDLE ParentProcessId;
CLIENT_ID CreatingThreadId;
struct_ FILE_OBJECT* FileObject;
PCUNICODE_STRING ImageFileName;
PCUNICODE_STRING CommandLine;
NTSTATUS CreationStatus;
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
msdn定义如下
这儿的话他们要注意两个值,一个是 ImageFileName 即要建立的民主化名,一个是 CreationStatus ,他们可以看到msdn里面说驱动力程序可以将此值修改为错误代码以防止建立民主化,这儿他们假如想阻止民主化建立就可以把那个值增设为 STATUS_UNSUCCESSFUL
他们去WRK里面看一下同时实现,那个API是64位才有的,因此在WRK里面是没有 PsSetCreateProcessNotifyRoutineEx 那个表达式的,但是在32和硕豫有一个 PsSetCreateProcessNotifyRoutine ,他们看一下
透过源码可以发现是操作方式数组,那个数组里面存放的是他们填写的反弹,而操作方式控制系统会依次初始化反弹,那他们跟随数组查看发现是个定长数组,里面只有8项,在64位控制系统下,那个数组的长度变为的是64项
根据 PCREATE_PROCESS_NOTIFY_ROUTINE_EX 的结构定义反弹表达式
VOID CreateProcessNotifyEx(
__inout PEPROCESS Process,
__in HANDLE ProcessId,
__in_opt PPS_CREATE_NOTIFY_INFO CreateInfo
) ;
因此他们这儿透过 PsSetCreateProcessNotifyRoutineEx 增设反弹表达式,透过判断 status 的返回值判断反弹表达式与否增设成功
NTSTATUS SetReFunction
{
NTSTATUS status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)CreateProcessNotifyEx, FALSE);
if(!NT_SUCCESS(status))
{
DbgPrint( “反弹表达式增设失败, status=%X”, status);
}
else
{
DbgPrint( “民主化监视已开启\r\n”);
}
}
然后展开反弹表达式的同时实现
VOID CreateProcessNotifyEx(PEPROCESS Process, HANDLE ProcessId, PPS_CREATE_NOTIFY_INFO CreateInfo)
首先判断 CreateInfo 的值,假如为 NULL 则表示民主化选择退出,假如不为 NULL 才为民主化的建立
if(CreateInfo == NULL)
{
DbgPrint(“民主化选择退出\n”);
return;
}
STATUS_UNSUCCESSFUL 来阻止民主化的建立
else
{
pszImageFileName = PsGetProcessImageFileName(Process);
if(pszImageFileName)
DbgPrint( “新建立的民主化是:%s\r\n”, pszImageFileName);
if( strcmp(pszImageFileName, “test.exe”) == 0)
{
CreateInfo->CreationStatus = STATUS_UNSUCCESSFUL;
DbgPrint( “拦截民主化:%s成功\r\n”, pszImageFileName);
}
}
这儿他们的反弹函数就已经完成,这儿须要注意,在卸载驱动力的时候就须要将反弹表达式摘除,否则新建立或者选择退出的民主化会因为找不到反弹表达式而引致蓝屏
VOID DriverUnload(IN PDEVICE_OBJECT driverObject)
{
NTSTATUS status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)CreateProcessNotifyEx, TRUE);
if(!NT_SUCCESS(status))
{
DbgPrint( “反弹表达式删除失败\r\n status=%X”, status);
}
else
{
DbgPrint(“反弹表达式成功删除\r\n”);
}
DbgPrint( “驱动力卸载完成\r\n”);
}
同时实现效果
首先注册一下驱动力
然后这儿首先执行一下他们的exe
然后读取他们的驱动力可以看到这儿 test.exe 已经不能运行
因此这儿他们再卸载一下驱动力可以发现又可以运行成功
这儿可能有点不太明显,他们将拦截的exe改成 notepad.exe 看下效果
启动驱动力可以看到这儿启动失败
卸载驱动力方可启动成功
线程监视&为保护PsSetCreateThreadNotifyRoutine
线程监视采用到的API相对于民主化监视单纯,采用到 PsSetCreateThreadNotifyRoutine ,而那个值并不能像民主化操作的API一样展开操作方式,这儿他们首先先采用那个API来展开线程的监视
NTSTATUS PsSetCreateThreadNotifyRoutine(
[in] PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine
) ;
NotifyRoutine 指向 PCREATE_THREAD_NOTIFY_ROUTINE
PcreateThreadNotifyRoutine 的结构如下,第一个参数为PID,第二个参数为TID,第三个参数表示是建立线程还是删除线程,建立线程则为 TRUE ,删除线程则为 FALSE
PCREATE_THREAD_NOTIFY_ROUTINE PcreateThreadNotifyRoutine;
voidPcreateThreadNotifyRoutine(
[in] HANDLE ProcessId,
[in] HANDLE ThreadId,
[in] BOOLEAN Create
)
{…}
因此他们这儿就可以写出 CREATE_THREAD_NOTIFY_ROUTINE 表达式
VOID CreateThreadNotifyRoutine(HANDLE ProcessId, HANDLE ThreadId, BOOLEAN Create)
{
if(Create)
{
DbgPrint( “新建立的线程ID为:%d,所属民主化ID为:%d\r\n”, ThreadId, ProcessId);
}
else
{
DbgPrint( “新销毁的线程ID为:%d,所属民主化ID为:%d\r\n”, ThreadId, ProcessId);
}
}
这儿假如要将表达式摘除,就须要用到 PsRemoveCreateThreadNotifyRoutine 表达式,定义如下
NTSTATUS PsRemoveCreateThreadNotifyRoutine(
[in] PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine
) ;
NTSTATUS status = PsRemoveCreateThreadNotifyRoutine(CreateThreadNotifyRoutine);
if(!NT_SUCCESS(status))
{
DbgPrint( “反弹表达式删除失败\r\n status=%X”, status);
}
else
{
DbgPrint(“反弹表达式成功删除\r\n”);
}
他们假如想让展开线程的为保护该怎样操作方式呢?他们想阻止线程的建立,就首先要找出相关联的民主化,再去展开拦截,因为在64和硕豫都是透过反弹的形式同时实现,因此他们就可以透过找出线程反弹的地址,然后间接改为 ret 方可起到拦截线程建立的效果
1.透过PID找出EPROCESS
2.透过TID找出ETHREAD
3.透过EPROCESS得到民主化路径
4.透过民主化路径相关联民主化名
5.判断民主化名与否相同
<1>若相同则找出线程反弹表达式的地址修改内容为ret
<2>若不相同则选择退出
因此他们该怎样找出线程反弹表达式的地址呢?这儿查阅资料后发现,3环将反弹表达式的地址放在了 ETHREAD + 0x410 偏移的 Win32StartAddress 里面
因此这儿他们就能展开表达式的编写,首先他们透过 PsSetCreateThreadNotifyRoutine 注册一个线程反弹表达式
NTSTATUS status = PsSetCreateThreadNotifyRoutine(CreateThreadNotify);
if(!NT_SUCCESS(status))
{
DbgPrint( “反弹表达式增设失败, status=%X”, status);
}
else
{
DbgPrint( “线程监视已开启\r\n”);
}
status = PsLookupProcessByProcessId(ProcessId, &Process);
if(!NT_SUCCESS(status))
return;
status = PsLookupThreadByThreadId(ThreadId, &Thread);
pszImageName = PsGetProcessImageFileName(Process);
然后再判断民主化名与否为他们要为保护的线程
if( strstr(pszImageName, “notepad”) != NULL)
定位到反弹表达式的地址判断缓存空间与否可用
pWin32Address = *(UCHAR**)((UCHAR*)Thread + 0x410);
if(MmIsAddressValid(pWin32Address))
这儿的话定位到了反弹表达式的地址,假如他们要修改反弹表达式的值就要修改页为保护属性,但是在64和硕豫是不允许采用内联汇编的,这儿的话就须要采用到汇编生成 .obj 文档来采用
ClosePageProtect;
if(MmIsAddressValid(pWin32Address))
{
*pWin32Address = 0xC3;
}
OpenPageProtect;
修改完成之后这儿他们采用 ObDereferenceObject ,减少引用计数
if( Process )
ObDereferenceObject(Process);
if( Thread )
ObDereferenceObject(Thread);
这儿因为我在win10 x64上做的实验,这儿在关闭为保护属性的时候一直报错引致 0xC3 一直修改不成功,这儿就不放图了
模块监视&为保护 PsSetLoadImageNotifyRoutine
和之前的表达式一样都是指向一个结构,这儿是 LOAD_IMAGE_NOTIFY_ROUTINE
NTSTATUS PsSetLoadImageNotifyRoutine(
[in] PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine
) ;
PLOAD_IMAGE_NOTIFY_ROUTINE
这儿第一个参数是指向缓冲的Unicode字符串的指针,用于标识可执行映像文档,第二个参数表示PID,第三个参数指向 IMAGE_INFO
PLOAD_IMAGE_NOTIFY_ROUTINE SetLoadImageNotifyRoutine;
voidSetLoadImageNotifyRoutine(
_In_opt_ PUNICODE_STRING FullImageName,
_In_ HANDLE ProcessId,
_In_ PIMAGE_INFO ImageInfo
)
{ … }
IMAGE_INFOtypedefstruct_ IMAGE_INFO{
union{
ULONG Properties;
struct{
ULONG ImageAddressingMode : 8;
ULONG SystemModeImage : 1;
ULONG ImageMappedToAllPids :1;
ULONG ExtendedInfoPresent : 1;
ULONG MachineTypeMismatch : 1;
ULONG ImageSignatureLevel :4;
ULONG ImageSignatureType : 3;
ULONG ImagePartialMap : 1;
ULONG Reserved : 12;
};
};
PVOID ImageBase;
ULONG ImageSelector;
SIZE_T ImageSize;
ULONG ImageSectionNumber;
} IMAGE_INFO, *PIMAGE_INFO;
具体成员的作用如下
Properties ImageAddressingMode 始终增设为IMAGE_ADDRESSING_MODE_32BIT。
SystemModeImage 增设为一个用于新读取的内核模式组件(如驱动力程序),或者对于映射到使用者空间的映像增设为 0。
ImageMappedToAllPids 始终增设为0。
ExtendedInfoPresent 假如增设了ExtendedInfoPresent象征,则IMAGE_INFO结构是图像信息结构的较大扩展版本的一部分(请参阅IMAGE_INFO_EX)。在Windows Vista中添加。有关详细信息,请参阅本备注部分的“扩展版本的图像信息结构”。
MachineTypeMismatch 始终增设为 0。在Windows 8 / Windows Server 2012中添加。
ImageSignatureLevel 代码完整性标记为映像的亲笔签名级别。该值是ntddk.h中的#define SE SIGNING_LEVEL *常量之一。在Windows 8.1 / Windows Server 2012 R2中添加。
ImageSignatureType 代码完整性标记为映像的亲笔签名类型。该值是在ntddk.h中定义的SE_IMAGE_SIGNATURE_TYPE枚举值。在Windows 8.1 / Windows Server 2012 R2中添加。
ImagePartialMap 假如初始化的映像视图是不映射整个映像的部分视图,则该值不为零; 0假如视图映射整个图像。在Windows 10 / Windows Server 2016中添加。
Reserved 始终增设为 0。
ImageBase 增设为映像的虚拟基地址。
ImageSelector 始终增设为 0。
ImageSize 映像的虚拟大小(以字节为单位)。
ImageSectionNumber 始终增设为 0。
因此他们首先还是定义一下反弹表达式
voidSetLoadImageNotifyRoutine(
_In_opt_ PUNICODE_STRING FullImageName,
_In_ HANDLE ProcessId,
_In_ PIMAGE_INFO ImageInfo
)
他们的反弹表达式在接收到消息的时候模块已经读取完成了,因此这儿他们就只能展开模块的卸载操作方式,在模块的 ImageInfo 结构里面提供了读取的 ImageBase ,因此他们只须要找出OEP,方可计算得到 DriverEntry 的地址。因此他们找出入口点表达式的地址之后,就可以修改错误码为 STATUS_ACCESS_DENIED 即 0xC0000022 ,就能达到卸载驱动力模块的效果
mov eax, 0xC0000022
ret
相关联的硬编码为 B8 22 00 00 C0 C3
因此这儿他们就可以展开卸载驱动力模块表达式的编写,首先定义指针指向OEP
PIMAGE_DOS_HEADER pDosHeader = pLoadImageBase;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PCHAR)pDosHeader + pDosHeader->e_lfanew);
PVOID pAddressOfEntryPoint = (PVOID)((PCHAR)pDosHeader + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
然后写入shellcode,透过 MmCreateMdl 写入,我们知道在内核里面是不能就行了展开读写操作方式的,这儿就可以透过MDL写入的形式映射到虚拟缓存同时实现shellcode的写入
ULONG CodeSize = 6;
UCHAR pShellCode[ 6] = { 0xB8, 0x22, 0x00, 0x00, 0xC0, 0xC3};
PMDL pMdl = MmCreateMdl( NULL, pAddressOfEntryPoint, CodeSize);
采用 MmBuildMdlForNonPagedPool 更新MDL对物理缓存的描述并映射到虚拟缓存,然后写入数据,释放MDL
MmBuildMdlForNonPagedPool(pMdl);
PVOID pVoid = MmMapLockedPages(pMdl, KernelMode);
RtlCopyMemory(pVoid, pShellCode, ulShellCodeSize);
MmUnmapLockedPages(pVoid, pMdl);
IoFreeMdl(pMdl);
然后他们再尝试对DLL模块展开卸载,这儿的话就不能像卸载驱动力模块间接在入口点返回,因为DLL的入口点表达式的返回值并不能确定DLL能否读取成功。这儿windows提供了一个未文档化的表达式 MmUnmapViewOfSection 用来卸载民主化中已经读取的模块
windows为的是避免死锁,在展开模块读取反弹表达式的时候不能展开其他操作方式,也是说他们想卸载DLL模块则须要等所有模块读取完毕之后才能展开卸载操作方式
这儿来展开表达式的编写
NTSTATUS NoLoadDll(HANDLE ProcessId, PVOID pImageBase)
{
NTSTATUS status = STATUS_SUCCESS;
PROCESS pEProcess = NULL;
status = PsLookupProcessByProcessId(ProcessId, &pEProcess);
if(!NT_SUCCESS(status))
{
DbgPrint( “PsLookupProcessByProcessId error : %d\n”, status);
returnstatus;
}
status = MmUnmapViewOfSection(pEProcess, pImageBase);
if(!NT_SUCCESS(status))
{
DbgPrint( “MmUnmapViewOfSection error : %d\n”, status);
returnstatus;
}
returnstatus;
}
同时实现效果
这儿要同时实现的效果是阻止 DriverTest.sys 的读取
首先启动他们的监视驱动力,然后读取 DriverTest.sys 可以看到拒绝访问
然后再卸载他们的监视驱动力之后 DriverTest.sys 读取成功
然后他们再尝试注入 Test.dll ,可以看到注入失败
他们再去xp上尝试一下,首先加载驱动力
当他们打开一个程序的时候都会打印出当前民主化读取的dll模块
然后注入DLL,也是被拦截
原创稿件征集
征集原创技术文章中,欢迎投递
投稿邮箱:[email protected]
文章类型:黑客极客技术、信息安全热点安全研究分析等安全相关
透过审核并发布能收获200-800元不等的稿酬。
更多详情,点我查看!
靶场实操,戳