我在之前的文章里说过,要使你的驱动在WIN64系统上加载,有两种方法:1.向微软缴纳高额的“保护费”(即购买驱动签名,虽然说把微软比喻为黑社会不太好,不过这种行径真的很像黑社会);2.破解微软的驱动签名验证机制。如果要在WIN64上进行内核HOOK,那么交保护费都不行,只能破解微软内核保护(PatchGuard)机制。有了这两个门神,不得不说使得WIN64系统十分安全。举个例子,我有个同学他对电脑一窍不通,之前用WIN XP时经常中毒要我帮忙重装系统,后来给他安装了WIN7 X64后,半年也没有中毒,而且还是在没有安装杀毒软件的情况下。但是对于普通的编程爱好者或者小公司而言,这两个门神会导致开发投入极高,甚至无法开发出安全类的软件,简而言之,就是不能在WIN64平台上吃底层安全这碗饭了。广东地区有句俗语,叫“断人财路犹如杀人父母”,但很明显,这句话的道理是全世界通用的。于是自从VISTA X64出来后,各种对付这两个门神的方法层出不穷,个人认为效果最好的就是fyyre放出的破解工具(disable_pg_ds)了,可惜这个工具不开源。
不久之前,我在 FASM官方论坛找到了类似这种工具的源代码,今天就把源码的分析心得写成文章分享给大家。顺便再次感叹下,外国人实在太大方了!在此特地感谢这位乐于分享的外国人Feryno!
要实现突破数字签名验证机制和 PatchGuard,必须修改两个文件,winload.exe以及ntoskrnl.exe。修改ntoskrnl.exe这个好理解,那么为什么要修改winload.exe呢?因为winload.exe 会校验 ntoskrnl.exe 有没有被修改过,如果发现它被修改过就拒绝加载修改过的ntoskrnl.exe。
首先把WINLOAD.EXE扔到IDA里,看看要修改哪些地方:
.text:00000000004057E8 OslInitializeCodeIntegrity proc nearOslpMain+61Cp; CODE XREF: ; DATA .text:00000000004057E8 XREF: .pdata:00000000004B2168o .text:00000000004057E8 .text:00000000004057E8 var_58 .text:00000000004057E8 var_50 .text:00000000004057E8 var_48 .text:00000000004057E8 var_38 .text:00000000004057E8 arg_8 .text:00000000004057E8 arg_18 = qword ptr -58h = dword ptr -50h = dword ptr -48h = qword ptr -38h = qword ptr 10h = qword ptr 20h .text:00000000004057E8 .text:00000000004057E8 .text:00000000004057EB .text:00000000004057EC .text:00000000004057ED .text:00000000004057EE .text:00000000004057F0 .text:00000000004057F2 .text:00000000004057F6 .text:00000000004057F9 .text:00000000004057FC .text:0000000000405800 .text:0000000000405807 .text:000000000040580B .text:000000000040580F .text:0000000000405812 .text:0000000000405816 BlImgQueryCodeIntegrityBootOptions mov push push push push push sub rax, rsp rbx rbp rdi r12 r13 rsp, 50h xor r13d, r13d r12d, ecx r8, [rax+18h] rcx, BlpApplicationEntry rdx, [rax+10h] [rax+20h], r13 rdi, r13 mov lea lea lea mov mov mov [rax-38h], r13 call
需要修改的是加粗的那行(00000000004057E8):
mov rax, rsp
修改为:
mov al,1 ret 48h,8Bh,C4h B0h,01h C3h
这么做的用途是跳过对BlImgQueryCodeIntegrityBootOptions的调用,据我了解,此函数随后会调用 ImgArchPcatLoadBootApplication,接下来还会调用BlImgLoadPEImageEx等函数来判断文件有没有签名以及checksum对不对。所以这是一个难缠的家伙,直接跳过。
接下来是修改ntoskrnl.exe的两个地方,下面是第一个地方:
PAGE:00000001403EAA60 SepInitializeCodeIntegrity proc near ; CODE XREF: SepInitializationPhase1+231p PAGE:00000001403EAA60 PAGE:00000001403EAA60 arg_0 PAGE:00000001403EAA60 PAGE:00000001403EAA60 PAGE:00000001403EAA65 PAGE:00000001403EAA66 PAGE:00000001403EAA6A PAGE:00000001403EAA6C PAGE:00000001403EAA72 PAGE:00000001403EAA78 PAGE:00000001403EAA7A PAGE:00000001403EAA81 PAGE:00000001403EAA84 = qword ptr 8 mov push sub xor cmp [rsp+arg_0], rbx rdi rsp, 20h ebx, ebx cs:InitIsWinPEMode, bl loc_1403EAB0C eax, eax jnz xor mov lea mov cs:g_CiEnabled, 1 edi, [rbx+6] cs:g_CiCallbacks, rax PAGE:00000001403EAA8B PAGE:00000001403EAA92 PAGE:00000001403EAA99 PAGE:00000001403EAAA0 PAGE:00000001403EAAA3 PAGE:00000001403EAAA5 PAGE:00000001403EAAAC PAGE:00000001403EAAAE PAGE:00000001403EAAB5 mov mov mov cmp jz cs:qword_14021EE48, rax cs:qword_14021EE50, rax rax, cs:qword_1402A8120 rax, rbx short loc_1403EAAF7 [rax+98h], rbx cmp jz short loc_1403EAAEE rcx, [rax+98h] mov lea lea rdx, ??_C@_0BJ@KFBEEMJI@DISABLE_INTEGRITY_CHECKS?$AA@NNGAKEGL@ PAGE:00000001403EAABC PAGE:00000001403EAAC1 PAGE:00000001403EAAC8 call mov SepIsOptionPresent rcx, cs:qword_1402A8120 rdx, ??_C@_0M@LNFBLGLD@TESTSIGNING?$AA@NNGAKEGL@ PAGE:00000001403EAACF PAGE:00000001403EAAD6 PAGE:00000001403EAAD8 PAGE:00000001403EAADB PAGE:00000001403EAAE0 PAGE:00000001403EAAE2 PAGE:00000001403EAAE9 PAGE:00000001403EAAEB PAGE:00000001403EAAEE PAGE:00000001403EAAEE loc_1403EAAEE: mov cmp rcx, [rcx+98h] eax, ebx cmovnz edi, ebx call cmp mov jz SepIsOptionPresent eax, ebx rax, cs:qword_1402A8120 short loc_1403EAAEE edi, 8 or ; CODE XREF: SepInitializeCodeIntegrity+4Cj PAGE:00000001403EAAEE ; SepInitializeCodeIntegrity+89j PAGE:00000001403EAAEE cmp jz rax, rbx PAGE:00000001403EAAF1 short loc_1403EAAF7 rbx, [rax+30h] PAGE:00000001403EAAF3 lea PAGE:00000001403EAAF7 PAGE:00000001403EAAF7 loc_1403EAAF7: ; CODE XREF: SepInitializeCodeIntegrity+43j PAGE:00000001403EAAF7 ; SepInitializeCodeIntegrity+91j PAGE:00000001403EAAF7 PAGE:00000001403EAAFE PAGE:00000001403EAB01 PAGE:00000001403EAB03 PAGE:00000001403EAB08 PAGE:00000001403EAB0A PAGE:00000001403EAB0C ; lea mov mov call mov jmp r8, g_CiCallbacks rdx, rbx ecx, edi CiInitialize ebx, eax short loc_1403EAB12 --------------------------------------------------------------------------- PAGE:00000001403EAB0C PAGE:00000001403EAB0C loc_1403EAB0C: ; CODE XREF: SepInitializeCodeIntegrity+12j PAGE:00000001403EAB0C mov cs:g_CiEnabled, bl PAGE:00000001403EAB12 PAGE:00000001403EAB12 loc_1403EAB12: ; CODE XREF: SepInitializeCodeIntegrity+AAj PAGE:00000001403EAB12 PAGE:00000001403EAB14 PAGE:00000001403EAB19 PAGE:00000001403EAB1D PAGE:00000001403EAB1E mov mov add pop retn eax, ebx rbx, [rsp+28h+arg_0] rsp, 20h rdi PAGE:00000001403EAB1E SepInitializeCodeIntegrity endp
需要修改的是加粗的那行(00000001403EAA72):
jnz loc_1403EAB0C
修改为:
0Fh,85h,94h,00h,00h,00h nop 90h jmp loc_1403EAB0C E9h,94h,00h,00h,00h
这样做的目的是跳过对是否为WINPE模式的判断,因为如果是WINPE模式的话就不会启动PatchGuard(以下有TDL4的简介作证:TDL4thereforedisablesthisfeaturebymakingthe system think that it is booting into the WinPE system restore mode – in whichPatchguard is disabled – early in the boot process.)。
第二个地方:
INIT:0000000140561340 sub_140561340 proc near ; CODE XREF: KiFilterFiberContext+FFp INIT:0000000140561340 INIT:0000000140561340 INIT:0000000140561340 var_F78 INIT:0000000140561340 var_F70 INIT:0000000140561340 var_F68 INIT:0000000140561340 var_F60 INIT:0000000140561340 var_F58 ... ; KiFilterFiberContext+187p = qword ptr -0F78h = qword ptr -0F70h = qword ptr -0F68h = qword ptr -0F60h = dword ptr -0F58h ... ... INIT:0000000140561340 var_48 INIT:0000000140561340 arg_0 INIT:0000000140561340 arg_8 INIT:0000000140561340 arg_10 INIT:0000000140561340 arg_18 INIT:0000000140561340 INIT:0000000140561340 INIT:0000000140561345 INIT:0000000140561349 = byte ptr -48h = dword ptr 8 = dword ptr 10h = dword ptr 18h = qword ptr 20h mov mov mov [rsp+arg_10], r8d [rsp+arg_8], edx [rsp+arg 0], ecx INIT:000000014056134D INIT:000000014056134E INIT:000000014056134F INIT:0000000140561350 INIT:0000000140561351 INIT:0000000140561353 INIT:0000000140561355 INIT:0000000140561357 INIT:0000000140561359 INIT:0000000140561360 INIT:0000000140561362 INIT:0000000140561368 INIT:000000014056136A INIT:000000014056136C INIT:0000000140561371 INIT:0000000140561371 loc_140561371: ; CODE XREF: sub_140561340+28j INIT:0000000140561371 FsRtlUninitializeSmallMcb INIT:0000000140561378 INIT:0000000140561380 INIT:0000000140561383 INIT:0000000140561388 INIT:000000014056138B INIT:0000000140561391 INIT:0000000140561399 INIT:000000014056139E INIT:00000001405613A1 ... push push push push push push push push sub rbx rbp rsi rdi r12 r13 r14 r15 rsp, 0F58h xor edi, edi cmp cs:InitSafeBootMode, edi short loc_140561371 al, 1 jz mov jmp loc_1405640D9 lea rbx, lea mov call cmp jz rdx, [rsp+0F98h+var_E40] rcx, rbx RtlPcToFileHeader rax, rdi loc_1405640D7 mov call cmp jz rcx, [rsp+0F98h+var_E40] RtlImageNtHeader rax, rdi loc_1405640D7 ... ... INIT:00000001405640D9 loc_1405640D9: sub_140561340+2Cj ; CODE XREF: ; INIT:00000001405640D9 sub_140561340+9C36j INIT:00000001405640D9 INIT:00000001405640E0 INIT:00000001405640E2 INIT:00000001405640E4 INIT:00000001405640E6 INIT:00000001405640E8 INIT:00000001405640E9 INIT:00000001405640EA INIT:00000001405640EB INIT:00000001405640EC add pop pop pop pop pop pop pop pop retn rsp, 0F58h r15 r14 r13 r12 rdi rsi rbp rbx
需要修改的是加粗的那行(0000000140561368):
jz short loc_140561371 74h,07h
修改为:
nop nop 90h 90h
这么修改的目的很明显,去掉这个条件跳转,让关于是否安全模式的判断失效。因为如果是安全模式,也不会启动PatchGuard机制(fyyre在博客里的原话:PatchGuard doesnot initialize if we boot into safe mode.)。经过这三处修改,数字签名校验机制和PatchGuard机制被彻底玩趴下了。但还没有完,修改过的两个文件还要重新计算checksum才行。接下来说说具体编码。由于Feryno是用汇编语言实现这一过程的,所以代码很长(560行),但是内容不多,手法也很普通(读取PE文件到buffer 搜索特征码查找位置 修改刚才提到的位置的字节内容 重新计算checksum 把修改过的buffer输出为文件),所以我估计用高级语言,一两百行就能搞定。核心代码:;//字节替换部分
patch_bytes: push push pop rbx rsi rdi rcx rsi cld lodsb ; load size of bytes to find ; end of file movzx lea ebx,al rdi,[file_buf] r8,[rdi+rdx] lea align 10h patch_bytes_L0: push rsi rdi ecx,ebx mov repz cmpsb pop rdi rsi jz scasb patch_bytes_L4 ; rdi+1 in 1-byte instruction lea rax,[rdi+rbx*1] rax,r8 cmp jbe patch_bytes_L0 ; end of file reached jmp patch_bytes_L9 patch_bytes_L4: ; matching bytes found, patch them add rsi,rbx lodsb ;loadsizeofbytestobe written movzx ecx,al repz movsb patch_bytes_L9: pop rdi rsi rbx ret ;//重新计算checksum reconstruct_crc: ; in: ecx = size of file struct IMAGE_DOS_HEADER e_magic rw rw 1 1 ; Magic number ; Bytes on last e_cblp page of file e_cp rw rw rw 1 1 1 ; Pages in file ; Relocations ;Sizeofheader e_crlc e_cparhdr in paragraphs e_minalloc paragraphs needed e_maxalloc paragraphs needed e_ss rw rw 1 1 ; Minimum extra ; Maximum extra rw 1 ; Initial (relative) SS value e_sp rw 1 ; Initial SP value e_csum rw rw 1 1 ; Checksum e_ip ; Initial IP value e_cs rw 1 1 ; Initial (relative) CS value e_lfarlc rw ; File address of relocation table e_ovno rw rw rw rw rw 1 1 ; Overlay ; Reserved ; OEM number e_res 4 words e_oemid 1 identifier (for e_oeminfo) e_oeminfo 1 ; OEM information; e_oemid specific e_res2 10 ; Reserved words e_lfanew rd ; File address of new exe header ends IMAGE_DOS_SIGNATURE struct IMAGE_FILE_HEADER Machine = rw rw 1 NumberOfSections TimeDateStamp PointerToSymbolTable NumberOfSymbols SizeOfOptionalHeader 1 rd rd rd rw 1 1 1 1 Characteristics rw 1 ends IMAGE_SIZEOF_FILE_HEADER IMAGE_FILE_MACHINE_AMD64 struct IMAGE_DATA_DIRECTORY VirtualAddress = = sizeof.IMAGE_FILE_HEADER ; = 20 8664h ; AMD64 (K8) rd rd 1 1 Size ends IMAGE_NUMBEROF_DIRECTORY_ENTRIES = struct IMAGE_OPTIONAL_HEADER64 ; Standard fields. Magic 16 rw rb rb rd rd rd rd rd 1 1 1 1 1 1 1 1 MajorLinkerVersion MinorLinkerVersion SizeOfCode SizeOfInitializedData SizeOfUninitializedData AddressOfEntryPoint BaseOfCode ; NT additional fields. ImageBase rq 1 SectionAlignment FileAlignment rd 1 rd rw rw 1 1 1 1 MajorOperatingSystemVersion MinorOperatingSystemVersion MajorImageVersion MinorImageVersion MajorSubsystemVersion MinorSubsystemVersion Win32VersionValue SizeOfImage rw rw rd rd 1 rw rw 1 1 1 rd rd 1 1 1 SizeOfHeaders CheckSum Subsystem rw rw rq 1 1 1 1 DllCharacteristics SizeOfStackReserve SizeOfStackCommit SizeOfHeapReserve SizeOfHeapCommit rq rq rq rd 1 1 LoaderFlags 1 NumberOfRvaAndSizes rd 1 ; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; DataDirectory IMAGE_DATA_DIRECTORY rb (IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1)*(sizeof.IMAGE_DATA_DIRECTORY) ends IMAGE_SIZEOF_NT_OPTIONAL64_HEADER = IMAGE_NT_OPTIONAL_HDR64_MAGIC struct IMAGE_NT_HEADERS64 Signature 240 = 020Bh 1 rd FileHeader IMAGE_FILE_HEADER OptionalHeader IMAGE_OPTIONAL_HEADER64 ends IMAGE_NT_SIGNATURE = lea cmp jnz mov add cmp jnz cmp rdx,[file_buf] [rdx+IMAGE_DOS_HEADER.e_magic],IMAGE_DOS_SIGNATURE reconstruct_crc_L9 eax,[rdx+IMAGE_DOS_HEADER.e_lfanew] rax,rdx [rax+IMAGE_NT_HEADERS64.Signature],IMAGE_NT_SIGNATURE reconstruct_crc_L9 [rax+IMAGE_NT_HEADERS64.FileHeader.Machine],IMAGE_FILE_MACHINE_AMD 64 jnz reconstruct_crc_L9 cmp [rax+IMAGE_NT_HEADERS64.OptionalHeader.Magic],IMAGE_NT_OPTIONAL_HD R64_MAGIC jnz reconstruct_crc_L9 ; CRC ; erase original file crc and [rax+IMAGE_NT_HEADERS64.OptionalHeader.CheckSum],0 ; ecx = size ; rdx = file_buf mov shr xor xor r10d,ecx ecx,1 r9d,r9d r8d,r8d calculate_checksum: mov add mov shr add add loop add r9w,[rdx] r8d,r9d r9w,r8w r8d,16 r8d,r9d rdx,2 calculate_checksum r8d,r10d mov [rax+IMAGE_NT_HEADERS64.OptionalHeader.CheckSum],r8d ; Checksum done reconstruct_crc_L9: ret ;//总体实现:相当于main函数;//基本流程:read_system_file -> patch_bytes -> reconstruct_crc ->write_system_file start: push sub rbx rsp,8*(4+2) rcx,[file0] read_system_file eax,eax lea call or mov ebx,eax ; size of file jz exit_failed edx,ebx mov ; size of the whole file ; pointer to patching lea rcx,[file0ed] data call patch_bytes mov rcx,[file3ed] patch_bytes edx,ebx lea call edx,ebx lea mov rcx,[file3ed] call lea patch_bytes mov edx,ebx rcx,[file3ed] call mov patch_bytes ecx,ebx ; size of the whole file ; size of the whole file call mov reconstruct_crc edx,ebx lea rcx,[file0n] write_system_file eax,ebx call cmp jnz exit_delete_file0n rcx,[file1] read_system_file eax,eax lea call or mov ebx,eax jnz L0 lea rcx,[file2] read_system_file eax,eax call or mov jz ebx,eax exit_failed L0: mov lea call mov lea call mov edx,ebx rcx,[file1ed] patch_bytes edx,ebx rcx,[file2ed] patch_bytes edx,ebx lea mov rcx,[file3ed] call patch_bytes edx,ebx lea call mov rcx,[file3ed] patch_bytes edx,ebx lea call mov rcx,[file3ed] patch_bytes ecx,ebx call mov reconstruct_crc edx,ebx lea rcx,[file1n] call eax,ebx jz write_system_file cmp ; exit_success exit_delete_file1n_file0n jmp exit_delete_file1n_file0n: lea rcx,[file1n] call [DeleteFileA] exit_delete_file0n: lea call rcx,[file0n] [DeleteFileA] exit_failed: lea jmp rbx,[msg_failed] exit_msg exit_success: lea rbx,[msg_success] exit_msg: ;STD_OUTPUT_HANDLE ;INVALID_HANDLE_VALUE = -11 = -1 ; mov rcx,STD_OUTPUT_HANDLE push pop STD_OUTPUT_HANDLE rcx call [GetStdHandle] push pop rax rcx if INVALID_HANDLE_VALUE = -1 inc rax else cmp rax,INVALID_HANDLE_VALUE end if jz exit and lea mov lea push pop lea call xor add rbx qword [rsp + 8*(4+0)],0 r9,[rsp + 8*(4+1)] r8d,msg_size rdx,[rbx] rbx rdx ; ; rcx,[rcx] [WriteFile] eax,eax ; already set exit: rsp,8*(4+2) pop Ret
接下来要用到批处理了,光有打过补丁的winload.exe和ntoskrnl.exe还不行,还需要建立新的BCD入口,把PEAUTH服务设为手动,防止在登陆时蓝屏:
@ECHO OFF ECHO. ECHO Creating patched copies of winload, ntkrnlmp/ntoskrnl... ECHO. patch.exe ECHO. ECHO Creating BCD Entry... ECHO. set ENTRY_GUID={46595952-454E-4F50-4747-554944FFFFFF} bcdedit -create %ENTRY_GUID% -d "DriverSigning&PatchGuard Disabled" -application OSLOADER bcdedit -set %ENTRY_GUID% device partition=%SYSTEMDRIVE% bcdedit -set %ENTRY_GUID% osdevice partition=%SYSTEMDRIVE% bcdedit -set %ENTRY_GUID% systemroot \Windows bcdedit -set %ENTRY_GUID% path \Windows\system32\freeload.exe bcdedit -set %ENTRY_GUID% kernel goodkrnl.exe bcdedit -set %ENTRY_GUID% recoveryenabled 0 bcdedit -set %ENTRY_GUID% nx OptOut bcdedit -set %ENTRY_GUID% nointegritychecks 1 bcdedit -set %ENTRY_GUID% testsigning 1 bcdedit -displayorder %ENTRY_GUID% -addlast bcdedit -timeout 5 bcdedit -default %ENTRY_GUID% ECHO. ECHO Setting PEAUTH service to manual... (avoid BSOD at login screen) ECHO. sc config peauth start= demand ECHO. ECHO Complete! ECHO. PAUSE
不得不提的是,在代码里似乎有一段跟破解PatchGuard和数字签名无关的内容,作者在帖子里说:so when you reboot, Patchguard is not intialized, driver digitalsignatures are not necessary, canonical address form of long mode virtual memoryischeckedcorrectly(长模式虚拟内存的规范地址形式被正确检查?就是加粗的这句,我尚未明白它的意义,他还举了“不规范”的地址形式,如:0000800000000000h、FFFF7FFFFFFFFFFFh)。如果有哪位读者明白它的意义,还请不吝赐教。本文代码在WINDOWS 7 X64和WINDOWS 2008 R2下测试成功,效果就是选择了相应的内核启动项后可以加载没有任何签名的内核驱动,也可以进行Kernel Inline Hook而不蓝屏。
本文为网络安全技术研究记录,文中技术研究环境为本地搭建或经过目标主体授权测试研究,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均已修复,在挖掘、提交相关漏洞的过程中,应严格遵守相关法律法规。