探索黑客技术攻防,实战研究与安全创新

导航菜单

编程实现突破 Win64内核保护机制

我在之前的文章里说过,要使你的驱动在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而不蓝屏。

本文为网络安全技术研究记录,文中技术研究环境为本地搭建或经过目标主体授权测试研究,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均已修复,在挖掘、提交相关漏洞的过程中,应严格遵守相关法律法规。