离职在家有一段日子了,闲来无事,看下了A盾的代码(版本为0.2.5),从资料中看到他禁止驱动加载的逻辑是:
系统加载驱动要调用ZwLoadDriver或者ZwSetSystemInformation函数来实现。而这两个函数又都必调用SeSinglePrivilegeCheck来检查权限,所以A盾利用inline hook技术hook了SeSinglePrivilegeCheck函数,当指令流执行到此处时,通过[ebp+4]得到调用函数的返回地址,这个返回地址当然位于发起调用的函数体内,对比一下这个地址是不是在ZwLoadDriver或者ZwSetWindowsInformation函数体中,由此判断是不是系统在加载驱动。如果是在加载驱动返回false即可禁止,否则调用原来的SeSinglePrivilegeCheck函数。
相关的代码如下:
然而,我发现他的禁止驱动加载的模块在xp下无效,在win7也同样无效。为什么呢?
我们先找一份加载驱动的代码,既然它说驱动是通过ZwLoadDriver来加载的,那么我们直接找一份这样的代码来测试一下:
通过调试发现在进入SSDT之前,传入eax的SSDT的索引号为0x61,而在win7这个索引号是0x9b。
如下所示:
进入到内核层时,对应的是NtLoadDriver,如下所示,其中红色的正是被hook的目标函数:
而A盾中却是通过对比
ZwLoadDriver(注意此函数的开头为"Zw")的代码区域来实现禁止驱动加载。那ZwLoadDriver在内核中又是怎么回事呢?通过windbg查看一下:
看来只是简单地处理一下就交给系统去处理了。真正实现加载驱动的代码并不在这里。即这里并没有直接调用被hook的函数SeSinglePrivilegeCheck ,所以在调用SeSinglePrivilegeCheck时 取得的返回地址,是不可能在ZwLoadDriver函数体内的,这也就是在判断返回地址是否在ZwLoadDriver时永远为不满足的原因。
到此就明白了为什么禁止驱动加载失效了。
总结一下:A盾通过hook SeSinglePrivilegeCheck 来得到调用者的返回地址,按逆向出来的结果看,应该是比较这个返回地址在不在 NtLoadDriver函数体中,而不是判断在不在 ZwLoadDriver中。
通过把里面相关的代码捉出来,笔者实现了一个禁止加载驱动的代码,也许是因为NtLoadDriver没导出,所以我是通过ssdt指定的偏移得到NtLoadDriver的地址。代码上传在下面,兼容xp,win7平台。相信其他平台(非64 位)改一下偏移值应该也能跑起来。
附上成功禁止加载驱动的图:
至于通过 ZwSetSystemInformation来加载驱动的例子暂时没找到,如果在这一方案上有问题,估计也可能是对比的地址不合适所致。这里就不测试了
后记:不知什么时候开始,A盾的官网就上不去了?而且网上能看到关于这个软件的帖子都是2012~2013年的了,也不知这个bug后来的版本得到修正没有!挺有教育意义的一个工具就这样悄无声息,消声匿迹了???
系统加载驱动要调用ZwLoadDriver或者ZwSetSystemInformation函数来实现。而这两个函数又都必调用SeSinglePrivilegeCheck来检查权限,所以A盾利用inline hook技术hook了SeSinglePrivilegeCheck函数,当指令流执行到此处时,通过[ebp+4]得到调用函数的返回地址,这个返回地址当然位于发起调用的函数体内,对比一下这个地址是不是在ZwLoadDriver或者ZwSetWindowsInformation函数体中,由此判断是不是系统在加载驱动。如果是在加载驱动返回false即可禁止,否则调用原来的SeSinglePrivilegeCheck函数。
相关的代码如下:
代码:
//禁止驱动加载 NTSTATUS DisEnableDriverLoading() { int bRet; ulZwSetSystemInformationBase = GetSystemRoutineAddress(1,L"ZwSetSystemInformation"); ulNtLoadDriverBase = GetSystemRoutineAddress(1,L"ZwLoadDriver"); if (ulNtLoadDriverBase && ulZwSetSystemInformationBase) { ulNtLoadDriverSize = SizeOfProc(ulNtLoadDriverBase); ulZwSetSystemInformationSize = SizeOfProc(ulZwSetSystemInformationBase); } ulSeSinglePrivilegeCheck = GetSystemRoutineAddress(1,L"SeSinglePrivilegeCheck"); if (!ulSeSinglePrivilegeCheck || !ulNtLoadDriverBase || !ulZwSetSystemInformationBase) { return STATUS_UNSUCCESSFUL; } //计算reload后的地址,不然判断不对 ulNtLoadDriverBase = ulNtLoadDriverBase - SystemKernelModuleBase+ImageModuleBase; ulZwSetSystemInformationBase = ulZwSetSystemInformationBase - SystemKernelModuleBase+ImageModuleBase; ulReloadSeSinglePrivilegeCheck = ulSeSinglePrivilegeCheck - SystemKernelModuleBase+ImageModuleBase; //hook reload SeSinglePrivilegeCheck bRet = HookFunctionByHeaderAddress(ulReloadSeSinglePrivilegeCheck,ulSeSinglePrivilegeCheck,SeSinglePrivilegeCheckHookZone,&SeSinglePrivilegeCheckPatchCodeLen,&SeSinglePrivilegeCheckRet); if(bRet) { bRet = FALSE; bRet = HookFunctionByHeaderAddress( NewSeSinglePrivilegeCheck, ulReloadSeSinglePrivilegeCheck, SeSinglePrivilegeCheckHookZone, &SeSinglePrivilegeCheckPatchCodeLen, &SeSinglePrivilegeCheckRet ); if (bRet) { SeSinglePrivilegeCheckHooked = TRUE; //DbgPrint("hook SeSinglePrivilegeCheck success\n"); } } return STATUS_SUCCESS; } //权限检查的时候返回失败来达到禁止加载驱动 BOOLEAN __stdcall NewSeSinglePrivilegeCheck( __in LUID PrivilegeValue, __in KPROCESSOR_MODE PreviousMode ) { ULONG ulPage; if (!bIsInitSuccess) goto _FunctionRet; //取返回地址 _asm { mov eax,dword ptr[ebp+4] mov ulPage,eax } //KdPrint(("ulPage:%08x\r\n",ulPage)); //RPsGetCurrentProcess = ReLoadNtosCALL(L"PsGetCurrentProcess",SystemKernelModuleBase,ImageModuleBase); if (!RPsGetCurrentProcess) { goto _FunctionRet; } if (RPsGetCurrentProcess() == ProtectEProcess) { goto _FunctionRet; } if (ulPage >= ulNtLoadDriverBase && ulPage <= ulNtLoadDriverBase+ulNtLoadDriverSize) return FALSE; if (ulPage >= ulZwSetSystemInformationBase && ulPage <= ulZwSetSystemInformationBase+ulZwSetSystemInformationSize) return FALSE; _FunctionRet: OldSeSinglePrivilegeCheck = (SeSinglePrivilegeCheck_1)SeSinglePrivilegeCheckHookZone; return OldSeSinglePrivilegeCheck( PrivilegeValue, PreviousMode ); }
我们先找一份加载驱动的代码,既然它说驱动是通过ZwLoadDriver来加载的,那么我们直接找一份这样的代码来测试一下:
代码:
#include <windows.h> #include <stdio.h> typedef struct _LSA_UNICODE_STRING { USHORT Length; USHORT MaximumLength; PVOID Buffer; } LSA_UNICODE_STRING, *PLSA_UNICODE_STRING; typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING; // 申明ntdll中使用的函数 typedef DWORD (CALLBACK* RTLANSISTRINGTOUNICODESTRING)(PVOID, PVOID,DWORD); RTLANSISTRINGTOUNICODESTRING RtlAnsiStringToUnicodeString; typedef DWORD (CALLBACK* RTLFREEUNICODESTRING)(PVOID); RTLFREEUNICODESTRING RtlFreeUnicodeString; typedef DWORD (CALLBACK* ZWLOADDRIVER)(PVOID); ZWLOADDRIVER ZwLoadDriver; int LoadDriver(char * szDrvName, char * szDrvPath) { //修改注册表启动驱动程序 char szSubKey[200], szDrvFullPath[256]; LSA_UNICODE_STRING buf1; LSA_UNICODE_STRING buf2; int iBuffLen; HKEY hkResult; char Data[4]; DWORD dwOK; iBuffLen = sprintf(szSubKey,"System//CurrentControlSet//Services//%s",szDrvName); szSubKey[iBuffLen]=0; dwOK = RegCreateKey(HKEY_LOCAL_MACHINE,szSubKey,&hkResult); if(dwOK!=ERROR_SUCCESS) return false; Data[0]=1; Data[1]=0; Data[2]=0; Data[3]=0; dwOK=RegSetValueEx(hkResult,"Type",0,4,(const unsigned char *)Data,4); dwOK=RegSetValueEx(hkResult,"ErrorControl",0,4,(const unsigned char *)Data,4); dwOK=RegSetValueEx(hkResult,"Start",0,4,(const unsigned char *)Data,4); GetFullPathName(szDrvPath, 256, szDrvFullPath, NULL); printf("Loading driver: %s/r/n", szDrvFullPath); iBuffLen = sprintf(szSubKey,"//??//%s",szDrvFullPath); szSubKey[iBuffLen]=0; dwOK=RegSetValueEx(hkResult,"ImagePath",0,1,(const unsigned char *)szSubKey,iBuffLen); RegCloseKey(hkResult); iBuffLen = sprintf(szSubKey,"//Registry//Machine//System//CurrentControlSet//Services//%s",szDrvName); szSubKey[iBuffLen]=0; buf2.Buffer = (PVOID)szSubKey; buf2.Length = iBuffLen; RtlAnsiStringToUnicodeString(&buf1,&buf2,1); //加载驱动程序 dwOK = ZwLoadDriver(&buf1); RtlFreeUnicodeString(&buf1); iBuffLen=sprintf(szSubKey,"%s%s//Enum","System//CurrentControlSet//Services//",szDrvName); szSubKey[iBuffLen]=0; //删除注册表项 RegDeleteKey(HKEY_LOCAL_MACHINE,szSubKey); iBuffLen=sprintf(szSubKey,"%s%s//Security","System//CurrentControlSet//Services//",szDrvName); szSubKey[iBuffLen]=0; RegDeleteKey(HKEY_LOCAL_MACHINE,szSubKey); iBuffLen=sprintf(szSubKey,"%s%s","System//CurrentControlSet//Services//",szDrvName); szSubKey[iBuffLen]=0; RegDeleteKey(HKEY_LOCAL_MACHINE,szSubKey); iBuffLen=sprintf(szSubKey,".//%s",szDrvName); szSubKey[iBuffLen]=0; return true; } int main(int argc, char *argv[]) { printf("Load driver with ZwLoadDriver( )/r/n"); printf("Date: 8th May 2007/r/n"); printf("Modifed by: GaRY <wofeiwo_at_gmail_dot_com>/r/n/r/n"); if(argc != 3) { printf("Usage: %s <DriverFilename> <DriverPath>/r/n", argv[0]); exit(-1); } HMODULE hNtdll = NULL; hNtdll = LoadLibrary( "ntdll.dll" ); //从ntdll.dll里获取函数 if ( !hNtdll ) { printf( "LoadLibrary( NTDLL.DLL ) Error:%d/n", GetLastError() ); return false; } RtlAnsiStringToUnicodeString = (RTLANSISTRINGTOUNICODESTRING) GetProcAddress( hNtdll, "RtlAnsiStringToUnicodeString"); RtlFreeUnicodeString = (RTLFREEUNICODESTRING) GetProcAddress( hNtdll, "RtlFreeUnicodeString"); ZwLoadDriver = (ZWLOADDRIVER) GetProcAddress( hNtdll, "ZwLoadDriver"); //注册驱动程序 if(LoadDriver(argv[1], argv[2]) == false) return false; return true; }
如下所示:
进入到内核层时,对应的是NtLoadDriver,如下所示,其中红色的正是被hook的目标函数:
代码:
nt!NtLoadDriver:
83df6329 6a48 push 48h
83df632b 68a860cb83 push offset nt! ?? ::FNODOBFM::`string'+0x8ca8 (83cb60a8)
83df6330 e8f3eaecff call nt!_SEH_prolog4 (83cc4e28)
83df6335 33db xor ebx,ebx
83df6337 895de4 mov dword ptr [ebp-1Ch],ebx
83df633a 648b3524010000 mov esi,dword ptr fs:[124h]
83df6341 8a863a010000 mov al,byte ptr [esi+13Ah]
83df6347 8845e0 mov byte ptr [ebp-20h],al
83df634a 3ac3 cmp al,bl
83df634c 0f840b010000 je nt!NtLoadDriver+0x133 (83df645d)
83df6352 ff75e0 push dword ptr [ebp-20h]
83df6355 ff3544f6fb83 push dword ptr [nt!SeLoadDriverPrivilege+0x4 (83fbf644)]
83df635b ff3540f6fb83 push dword ptr [nt!SeLoadDriverPrivilege (83fbf640)]
83df6361 e8b8480a00 call nt!SeSinglePrivilegeCheck (83e9ac1e)
83df6366 84c0 test al,al
83df6368 750a jne nt!NtLoadDriver+0x4b (83df6374)
83df636a b8610000c0 mov eax,0C0000061h
83df636f e958010000 jmp nt!NtLoadDriver+0x1a2 (83df64cc)
83df6374 895dfc mov dword ptr [ebp-4],ebx
83df6377 8b4508 mov eax,dword ptr [ebp+8]
83df637a 8b0d20d7db83 mov ecx,dword ptr [nt!MmUserProbeAddress (83dbd720)]
83df6380 3bc1 cmp eax,ecx
83df6382 7202 jb nt!NtLoadDriver+0x5d (83df6386)
83df6384 8bc1 mov eax,ecx
83df6386 8b08 mov ecx,dword ptr [eax]
83df6388 894dd4 mov dword ptr [ebp-2Ch],ecx
83df638b 8b4804 mov ecx,dword ptr [eax+4]
83df638e 894dd8 mov dword ptr [ebp-28h],ecx
83df6391 66395dd4 cmp word ptr [ebp-2Ch],bx
83df6395 7511 jne nt!NtLoadDriver+0x7f (83df63a8)
83df6397 c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh
83df639e b80d0000c0 mov eax,0C000000Dh
83df63a3 e924010000 jmp nt!NtLoadDriver+0x1a2 (83df64cc)
代码:
0: kd> u ZwLoadDriver nt!ZwLoadDriver: 83c9589c b89b000000 mov eax,9Bh 83c958a1 8d542404 lea edx,[esp+4] 83c958a5 9c pushfd 83c958a6 6a08 push 8 83c958a8 e8911c0000 call nt!KiSystemService (83c9753e) 83c958ad c20400 ret 4 nt!ZwLoadKey: 83c958b0 b89c000000 mov eax,9Ch 83c958b5 8d542404 lea edx,[esp+4]
到此就明白了为什么禁止驱动加载失效了。
总结一下:A盾通过hook SeSinglePrivilegeCheck 来得到调用者的返回地址,按逆向出来的结果看,应该是比较这个返回地址在不在 NtLoadDriver函数体中,而不是判断在不在 ZwLoadDriver中。
通过把里面相关的代码捉出来,笔者实现了一个禁止加载驱动的代码,也许是因为NtLoadDriver没导出,所以我是通过ssdt指定的偏移得到NtLoadDriver的地址。代码上传在下面,兼容xp,win7平台。相信其他平台(非64 位)改一下偏移值应该也能跑起来。
附上成功禁止加载驱动的图:
至于通过 ZwSetSystemInformation来加载驱动的例子暂时没找到,如果在这一方案上有问题,估计也可能是对比的地址不合适所致。这里就不测试了
后记:不知什么时候开始,A盾的官网就上不去了?而且网上能看到关于这个软件的帖子都是2012~2013年的了,也不知这个bug后来的版本得到修正没有!挺有教育意义的一个工具就这样悄无声息,消声匿迹了???