はじめに
Windows 10 では、幾つかの重要なプロセスが保護されていて、管理者権限やシステム権限を持っていてもデバッガーをアタッチすることが許可されていません。どんな層に需要があるか分かりませんが、これらの保護されたプロセスを、カーネル デバッガー、もしくはカーネル ドライバーを使って強引に jailbreak する方法について説明します。
さらに、Windows Defender のプロセス (MsMpEng.exe) には別の保護機能が実装されています。これもついでに解除してしまいましょう。Windows Defender に対する方法は、GitHub 上でオランダのハッカーから教えてもらいました。
環境
- OS: Windows 10 1709 x64 + KB4048955 [Version 10.0.16299.64]
- Debugger: WDK for Windows 10 1709 に入ってるやつ
現象
例えば、以下のように services.exe にデバッガーをアタッチしようとすると、Access denied エラーで怒られます。当然コマンドは管理者権限で実行していて、また psexec を使って SYSTEM 権限で試しても駄目です。
C:\MSWORK> C:\debuggers\amd64\cdb.exe -pn services.exe
Microsoft (R) Windows Debugger Version 10.0.16299.15 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
Cannot debug pid 588, Win32 error 0n5
"Access is denied."
Debuggee initialization failed, Win32 error 0n5
Access is denied.
C:\MSWORK> PsExec.exe -nobanner -s C:\debuggers\amd64\cdb.exe -pn services.exe
Microsoft (R) Windows Debugger Version 10.0.16299.15 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
Cannot debug pid 588, Win32 error 0n5
"Access is denied."
Debuggee initialization failed, Win32 error 0n5
Access is denied.
C:\debuggers\amd64\cdb.exe exited on DESKTOP-DN7BF9N with error code -2147024891.
保護機能の有無は、Process Explorer の Protection タブで確認できます。以下に示した環境では svchost.exe が一つ保護されていますが、これは AppXSvc サービスでした。
そんなのカーネル デバッガー使えばいいじゃん
鋭い。例えば services.exe であればこんな具合。
kd> !process 0 0 services.exe
PROCESS ffffc08402b44080
SessionId: 0 Cid: 0240 Peb: a60be60000 ParentCid: 01d0
DirBase: 168ca000 ObjectTable: ffffaf007248c880 HandleCount: 330.
Image: services.exe
kd> .process ffffc08402b44080
Implicit process is now ffffc084`02b44080
WARNING: .cache forcedecodeuser is not enabled
kd> .reload
Connected to Windows 10 16299 x64 target at (Thu Nov 23 17:11:34.073 2017 (UTC - 8:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
..........................................
Loading User Symbols
........................
Loading unloaded module list
......
kd> x services!CWin32ServiceRecord::SendControl
00007ff7`490fd1a0 services!CWin32ServiceRecord::SendControl (<no parameter info>)
kd> ba e1 services!CWin32ServiceRecord::SendControl
kd> g
Breakpoint 0 hit
services!CWin32ServiceRecord::SendControl:
0033:00007ff7`490fd1a0 4053 push rbx
kd> .reload
kd> !process -1 0
PROCESS ffffc08402b44080
SessionId: 0 Cid: 0240 Peb: a60be60000 ParentCid: 01d0
DirBase: 168ca000 ObjectTable: ffffaf007248c880 HandleCount: 331.
Image: services.exe
kd> k
Child-SP RetAddr Call Site
000000a6`0c77e548 00007ff7`49101869 services!CWin32ServiceRecord::SendControl
000000a6`0c77e550 00007ff7`491046b4 services!ScControlService+0x24d
000000a6`0c77e620 00007ffa`e8686d13 services!RControlService+0x54
000000a6`0c77e6b0 00007ffa`e8668855 RPCRT4!Invoke+0x73
000000a6`0c77e710 00007ffa`e866849a RPCRT4!NdrStubCall2+0x3a5
000000a6`0c77edb0 00007ffa`e86373b4 RPCRT4!NdrServerCall2+0x1a
000000a6`0c77ede0 00007ffa`e863654e RPCRT4!DispatchToStubInCNoAvrf+0x24
000000a6`0c77ee30 00007ffa`e8636cfb RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1be
000000a6`0c77ef00 00007ffa`e864082f RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
000000a6`0c77ef60 00007ffa`e8641396 RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
000000a6`0c77f040 00007ffa`e863d11e RPCRT4!LRPC_SCALL::HandleRequest+0x996
000000a6`0c77f150 00007ffa`e863e843 RPCRT4!LRPC_ADDRESS::HandleRequest+0x34e
000000a6`0c77f200 00007ffa`e866cc58 RPCRT4!LRPC_ADDRESS::ProcessIO+0x8a3
000000a6`0c77f340 00007ffa`ead565ae RPCRT4!LrpcIoComplete+0xd8
000000a6`0c77f3e0 00007ffa`ead54b26 ntdll!TppAlpcpExecuteCallback+0x22e
000000a6`0c77f460 00007ffa`e8d51fe4 ntdll!TppWorkerThread+0x886
000000a6`0c77f7f0 00007ffa`ead8ef91 KERNEL32!BaseThreadInitThunk+0x14
000000a6`0c77f820 00000000`00000000 ntdll!RtlUserThreadStart+0x21
kd> bc*
kd> bp services!CWin32ServiceRecord::StartInternal
kd> g
Breakpoint 0 hit
services!CWin32ServiceRecord::StartInternal:
0033:00007ff7`490feae0 48895c2420 mov qword ptr [rsp+20h],rbx
kd> .reload
kd> k
Child-SP RetAddr Call Site
000000a6`0c77e398 00007ff7`490fa71a services!CWin32ServiceRecord::StartInternal
000000a6`0c77e3a0 00007ff7`49102203 services!CServiceRecord::Start+0xa2
000000a6`0c77e400 00007ff7`490f996c services!ScStartMarkedServicesInServiceSet+0x16f
000000a6`0c77e490 00007ff7`490f9685 services!ScStartServicesInStartList+0x1f8
000000a6`0c77e560 00007ff7`4910b56b services!ScStartServiceAndDependencies+0x1cd
000000a6`0c77e630 00007ffa`e8686d13 services!RStartServiceW+0x10b
000000a6`0c77e6b0 00007ffa`e8668855 RPCRT4!Invoke+0x73
000000a6`0c77e710 00007ffa`e866849a RPCRT4!NdrStubCall2+0x3a5
000000a6`0c77edb0 00007ffa`e86373b4 RPCRT4!NdrServerCall2+0x1a
000000a6`0c77ede0 00007ffa`e863654e RPCRT4!DispatchToStubInCNoAvrf+0x24
000000a6`0c77ee30 00007ffa`e8636cfb RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1be
000000a6`0c77ef00 00007ffa`e864082f RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
000000a6`0c77ef60 00007ffa`e8641396 RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
000000a6`0c77f040 00007ffa`e863d11e RPCRT4!LRPC_SCALL::HandleRequest+0x996
000000a6`0c77f150 00007ffa`e863e843 RPCRT4!LRPC_ADDRESS::HandleRequest+0x34e
000000a6`0c77f200 00007ffa`e866cc58 RPCRT4!LRPC_ADDRESS::ProcessIO+0x8a3
000000a6`0c77f340 00007ffa`ead565ae RPCRT4!LrpcIoComplete+0xd8
000000a6`0c77f3e0 00007ffa`ead54b26 ntdll!TppAlpcpExecuteCallback+0x22e
000000a6`0c77f460 00007ffa`e8d51fe4 ntdll!TppWorkerThread+0x886
000000a6`0c77f7f0 00007ffa`ead8ef91 KERNEL32!BaseThreadInitThunk+0x14
000000a6`0c77f820 00000000`00000000 ntdll!RtlUserThreadStart+0x21
少々手間ですが余裕ですね。
終了。
いや待ってだがしかし、カーネル デバッガーは強力ですが、不便なことも多いです。それに最近は、そもそもカーネル デバッガーを繋げられないデバイスが増えてきています。ラップトップで、デバッグ対応の USB ポートが搭載されていない、かつ Ethernet ポートがない場合などです。USB-Ethernet アダプターだと Ethernet 経由でのカーネルデバッグができないのです。1394 ポートが復権して欲しいものです。
そんなわけで、カーネル デバッグは禁じ手です。何としてでもユーザーモード デバッガーを使いたいのです。
実は EPROCESS 内のフラグを変更するだけでいい
適当に "Protected Process Windows" などのキーワードで検索すると、情報は多く見つかります。結論から言えば、この保護機能は EPROCESS のフラグで管理されているので、それを変更してしまえば解除できます。
Alex Ionescu 氏のブログに詳しい解説があります。例えば以下の記事とか。が、読んでいない・・・。
The Evolution of Protected Processes Part 1: Pass-the-Hash Mitigations in Windows 8.1 « Alex Ionescu's Blog
http://www.alex-ionescu.com/?p=97
デバッグを可能にするには二つの方法があります。
- 保護プロセスの保護を解除する
- デバッガー プロセスの保護レベルをターゲットと同じにする
Jailbreak といえば普通に考えて 1. ですが、保護プロセスは保護プロセスによるアクセスを許可するみたいなので、逆にデバッガーの保護レベルを上げても目的は達成できます。単にデバッグするだけなら 2. のほうが安全だと思います。
では実際にやってみます。ここで結局カーネル デバッガーを使うのですが、フラグを変更するだけであれば Windows 10 ではローカル カーネル デバッグが使えるため、リモートからカーネル デバッガーをつなげる必要はありません。ハードウェアによる制限を受けないので便利です。
Local Kernel-Mode Debugging | Microsoft Docs
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/performing-local-kernel-debugging
services.exe を jailbreak してみます。
C:\MSWORK>C:\debuggers\amd64\kd.exe -kl
Microsoft (R) Windows Debugger Version 10.0.16299.15 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
Connected to Windows 10 16299 x64 target at (Thu Nov 23 17:35:15.310 2017 (UTC - 8:00)), ptr64 TRUE
************* Path validation summary **************
Response Time (ms) Location
Deferred cache*C:\symbols
Deferred srv*http://msdl.microsoft.com/download/symbols
Symbol search path is: cache*C:\symbols;srv*http://msdl.microsoft.com/download/symbols
Executable search path is:
Windows 10 Kernel Version 16299 MP (1 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 16299.15.amd64fre.rs3_release.170928-1534
Machine Name:
Kernel base = 0xfffff802`c9890000 PsLoadedModuleList = 0xfffff802`c9bf1fb0
Debug session time: Thu Nov 23 17:35:17.738 2017 (UTC - 8:00)
System Uptime: 0 days 0:01:53.131
lkd> !process 0 0 services.exe
PROCESS ffffd902066c1080
SessionId: 0 Cid: 0210 Peb: dcb29fc000 ParentCid: 01c8
DirBase: 1660a000 ObjectTable: ffff800f52d69c40 HandleCount: 322.
Image: services.exe
lkd> dt nt!_EPROCESS ffffd902066c1080 Protection.
+0x6ca Protection :
+0x000 Level : 0x61 'a'
+0x000 Type : 0y001
+0x000 Audit : 0y0
+0x000 Signer : 0y0110
lkd> db ffffd902066c1080+6ca l1
ffffd902`066c174a 61 a
lkd> eb ffffd902`066c174a 0
lkd> q
quit:
これだけです。Process Explorer で確認すると、確かに services.exe の Protection タブの値が消えており、デバッガーもアタッチできました。
services.exe は、保護レベルが PsProtectedSignerWinTcb-Light で、EPROCESS の元の値は 0x61 でした。PsProtectedSignerWindows-Light である SecurityHealthService.exe は 0x51 で、PsProtectedSignerAntimalware-Light では 0x31 になっていました。これらの値の意味は、上記 Alex Ionescu 氏のブログに説明があるように、_PS_PROTECTED_SIGNER の値によるものです。
_PS_PROTECTED_SIGNER
PsProtectedSignerNone = 0n0
PsProtectedSignerAuthenticode = 0n1
PsProtectedSignerCodeGen = 0n2
PsProtectedSignerAntimalware = 0n3
PsProtectedSignerLsa = 0n4
PsProtectedSignerWindows = 0n5
PsProtectedSignerWinTcb = 0n6
PsProtectedSignerMax = 0n7
では次に、デバッガーを保護対象にしてみます。といっても、デバッガーそのものではなく、デバッグ サーバーの dbgsrv.exe を保護対象にして、premote で繋げたほうが楽です。
C:\MSWORK> C:\debuggers\amd64\dbgsrv.exe -t tcp:port=8080
C:\MSWORK> C:\debuggers\amd64\kd.exe -kl
Microsoft (R) Windows Debugger Version 10.0.16299.15 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
Connected to Windows 10 16299 x64 target at (Thu Nov 23 17:57:09.214 2017 (UTC - 8:00)), ptr64 TRUE
************* Path validation summary **************
Response Time (ms) Location
Deferred cache*D:\symbols
Deferred srv*http://msdl.microsoft.com/download/symbols
Deferred srv*https://chromium-browser-symsrv.commondatastorage.googleapis.com
Symbol search path is: cache*D:\symbols;srv*http://msdl.microsoft.com/download/symbols;srv*https://chromium-browser-symsrv.commondatastorage.googleapis.com
Executable search path is:
Windows 10 Kernel Version 16299 MP (1 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 16299.15.amd64fre.rs3_release.170928-1534
Machine Name:
Kernel base = 0xfffff803`80e1c000 PsLoadedModuleList = 0xfffff803`8117dfb0
Debug session time: Thu Nov 23 17:57:09.714 2017 (UTC - 8:00)
System Uptime: 0 days 0:01:18.665
lkd> !process 0 0 dbgsrv.exe
PROCESS ffffa6052a766080
SessionId: 1 Cid: 1200 Peb: 24933f7000 ParentCid: 0a2c
DirBase: 4a14a000 ObjectTable: ffffbb80df84cf40 HandleCount: 101.
Image: dbgsrv.exe
lkd> dt nt!_EPROCESS ffffa6052a766080 Protection.
+0x6ca Protection :
+0x000 Level : 0 ''
+0x000 Type : 0y000
+0x000 Audit : 0y0
+0x000 Signer : 0y0000
lkd> eb ffffa6052a766080+6ca 61
lkd> q
quit:
Process Explorer で dbgsrv.exe が保護されたことを確認します。0x61 をセットしておけば、全ての保護プロセスにアタッチできます。簡単ですね。
カーネル ドライバーで jailbreak を自動化
カーネル デバッガーを使わず、動いているすべての保護プロセスを一気に jailbreak するカーネル ドライバーが GitHub にありました。
Mattiwatti/PPLKiller: Protected Processes Light Killer
https://github.com/Mattiwatti/PPLKiller
基本のロジックは簡単で、EPROCESS を全部列挙し、保護されているものがあれば EPROCESS のメンバーに対して軒並み 0 をセットするという豪快なものです。_EPROCESS::Protection だけでなく _EPROCESS::SignatureLevel と _EPROCESS::SectionSignatureLevel も書き換えていますが、デバッガーをアタッチするだけであれば Protection の変更だけで十分でした。
このドライバーの実装で興味深いのは、EPROCESS 構造体におけるオフセットを計算する部分です。OS のバージョンによってオフセットは異なるため、API で取ってきた保護レベルの値を、EPROCESS の途中からページ境界まで検索し、完全一致するところをオフセットと見なす、という方法を取っています。その比較のロジックが、Windows Defender が動いているとうまく動かなかったため、下記 PR を送り、それがきっかけとなって Windows Defender の保護機能について知ることができました。この人は Windows Defender を Windows 10 のメディアから削除しているので気づかなったらしい。気持ちは分かるがそこまでするものなのか。
Add SE_SIGNING_LEVEL_ANTIMALWARE for MsMpEng.exe by msmania · Pull Request #6 · Mattiwatti/PPLKiller
https://github.com/Mattiwatti/PPLKiller/pull/6
ドライバーの利用方法は簡単で、README に書かれている通り sc create でサービスとして登録して起動するだけです。DriverEntry から呼ばれる処理が保護を解除します。デバッガーだけでなく、複数のプロセスからターゲット プロセスへのアクセスが必要な場合には、このドライバーで一気に jailbreak してしまえば確かに楽です。jailbreak 以外にも幅広く応用が効きそうな手法なので覚えておきたいですね。
Windows Defender
dbgsrv.exe の保護レベルを 0x61 にすると、MsMpEng.exe にアタッチすることはできるようになります。が、以下に示すように初回のブレークインが失敗し、さらにブレークポイントを設定しようとしても access denied エラーで怒られます。どうやらデバッグ対象のメモリを書き換えることができないみたいです。
Break-in sent, waiting 30 seconds...
WARNING: Break-in timed out, suspending.
This is usually caused by another thread holding the loader lock
(638.51c): Wake debugger - code 80000007 (first chance)
ntdll!NtWaitForSingleObject+0x14:
00007ff9`8767fec4 c3 ret
0:000> |
. 0 id: 638 attach name: C:\Program Files\Windows Defender\MsMpEng.exe
0:000> k
Child-SP RetAddr Call Site
000000ca`6caff3c8 00007ff9`83ee3b2f ntdll!NtWaitForSingleObject+0x14
000000ca`6caff3d0 00007ff9`84ec6d74 KERNELBASE!WaitForSingleObjectEx+0x9f
000000ca`6caff470 00007ff9`84ec6631 sechost!ScSendResponseReceiveControls+0x138
000000ca`6caff5b0 00007ff9`84ec643f sechost!ScDispatcherLoop+0x135
000000ca`6caff6f0 00007ff9`7be84b00 sechost!StartServiceCtrlDispatcherW+0x4f
000000ca`6caff720 00007ff9`7be84236 mpsvc!CommonUtil::CServiceHandler::Dispatch+0xa0
000000ca`6caff790 00007ff6`bfa21aa0 mpsvc!ServiceCrtMain+0x176
000000ca`6caff800 00007ff6`bfa23425 MsMpEng!HrExeMain+0x15c
000000ca`6caff870 00007ff6`bfa2bf09 MsMpEng!wmain+0x129
000000ca`6caff8d0 00007ff9`87511fe4 MsMpEng!std::_Winerror_map+0x299
000000ca`6caff910 00007ff9`8764ef91 KERNEL32!BaseThreadInitThunk+0x14
000000ca`6caff940 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> bp 00007ff9`84ec6d74
0:000> g
Unable to insert breakpoint 0 at 00007ff9`84ec6d74, Win32 error 0n5
"Access is denied."
The breakpoint was set with BP. If you want breakpoints
to track module load/unload state you must use BU.
bp0 at 00007ff9`84ec6d74 failed
WaitForEvent failed, Win32 error 0n5
Access is denied.
ntdll!NtWaitForSingleObject+0x14:
00007ff9`8767fec4 c3 ret
Matthijs さんの指摘に従い、Process Hacker でデバッガー側が保持しているプロセスのハンドルを見ると、MsMpEng.exe に対するハンドルだけは権限が制限されていることがわかりました。実はこのとき初めて Process Hacker というツールを知りましたが、これ強力すぎます。もうほとんど Process Explorer は要らない。しかもオープンソース。ただプロセスの保護レベルを表示する機能がない。惜しい。
services.exe のハンドルと比べると、足りない権限は 0x2b、すなわち以下のフラグの組み合わせです。
- PROCESS_VM_WRITE (0x0020)
- PROCESS_VM_OPERATION (0x0008)
- PROCESS_CREATE_THREAD (0x0002)
- PROCESS_TERMINATE (0x0001)
彼が言うには、Windows Defender にはカーネル コンポーネントがあり、ObRegisterCallbacks を使ってコールバックを設定しているはずだ、とのこと。
というわけで、ObRegisterCallbacks にブレークポイントを張って確かめると、起動時に一回だけ呼ばれ、コールスタックを見ると WdFilter.sys というフィルター ドライバーが関与していました。調査の手間がほぼ全部省けた。
kd> .reboot
Shutdown occurred at (Thu Nov 23 20:40:28.268 2017 (UTC - 8:00))...unloading all symbol tables.
Using NET for debugging
Opened WinSock 2.0
Waiting to reconnect...
Connected to target 10.0.0.184 on port 50000 on local IP 10.0.0.64.
You can get the target MAC address by running .kdtargetmac command.
Connected to Windows 10 16299 x64 target at (Thu Nov 23 20:41:05.679 2017 (UTC - 8:00)), ptr64 TRUE
Kernel Debugger connection established.
************* Path validation summary **************
Response Time (ms) Location
Deferred cache*D:\symbols
Deferred srv*http://msdl.microsoft.com/download/symbols
Deferred srv*https://chromium-browser-symsrv.commondatastorage.googleapis.com
Symbol search path is: cache*D:\symbols;srv*http://msdl.microsoft.com/download/symbols;srv*https://chromium-browser-syms
rv.commondatastorage.googleapis.com
Executable search path is:
Windows 10 Kernel Version 16299 MP (1 procs) Free x64
Built by: 16299.15.amd64fre.rs3_release.170928-1534
Machine Name:
Kernel base = 0xfffff800`adc19000 PsLoadedModuleList = 0xfffff800`adf7afb0
System Uptime: 0 days 0:00:00.912
nt!DebugService2+0x5:
fffff800`add82cb5 cc int 3
kd> x nt!ObRegisterCallbacks
fffff800`ae1eb710 nt!ObRegisterCallbacks (void)
kd> bp nt!ObRegisterCallbacks
kd> g
Breakpoint 0 hit
nt!ObRegisterCallbacks:
fffff800`ae1eb710 48895c2408 mov qword ptr [rsp+8],rbx
kd> k
Child-SP RetAddr Call Site
fffff606`ede063d8 fffff80b`35d97de6 nt!ObRegisterCallbacks
fffff606`ede063e0 fffff80b`35d97ca4 WdFilter!MpObAddCallback+0x112
fffff606`ede064a0 fffff80b`35d97410 WdFilter!MpObInitialize+0x94
fffff606`ede06500 fffff80b`35d97030 WdFilter!DriverEntry+0x364
fffff606`ede066a0 fffff800`ae45462b WdFilter!GsDriverEntry+0x20
fffff606`ede066d0 fffff800`ae4542a9 nt!IopInitializeBuiltinDriver+0x337
fffff606`ede067a0 fffff800`ae45401b nt!PnpInitializeBootStartDriver+0x11d
fffff606`ede06860 fffff800`ae44b34d nt!IopInitializeBootDrivers+0x68f
fffff606`ede06aa0 fffff800`ae43ad91 nt!IoInitSystemPreDrivers+0x9c9
fffff606`ede06bb0 fffff800`ae1b738c nt!IoInitSystem+0x9
fffff606`ede06be0 fffff800`adc41f87 nt!Phase1Initialization+0x3c
fffff606`ede06c10 fffff800`add82676 nt!PspSystemThreadStartup+0x47
fffff606`ede06c60 00000000`00000000 nt!KiStartSystemThread+0x16
kd> lmvm wdfilter
start end module name
fffff80b`35d50000 fffff80b`35da1000 WdFilter (pdb symbols) d:\symbols\WdFilter.pdb\BFEDEDF631C72EB7EE881E04C65767A31\WdFilter.pdb
Loaded symbol image file: WdFilter.sys
Image path: WdFilter.sys
Image name: WdFilter.sys
Image was built with /Brepro flag.
Timestamp: 3B7152C1 (This is a reproducible build file hash, not a timestamp)
CheckSum: 0005B652
ImageSize: 00051000
File version: 4.12.16299.15
Product version: 4.12.16299.15
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 3.0 Driver
File date: 00000000.00000000
Translations: 0409.04b0
CompanyName: Microsoft Corporation
ProductName: Microsoft® Windows® Operating System
InternalName: WdFilter
OriginalFilename: WdFilter.sys
ProductVersion: 4.12.16299.15
FileVersion: 4.12.16299.15 (WinBuild.160101.0800)
FileDescription: Microsoft antimalware file system filter driver
LegalCopyright: © Microsoft Corporation. All rights reserved.
引数の構造体の定義は MSDN に書かれているので、登録しようとしているコールバック関数を突き止めるのは簡単です。OB_CALLBACK_REGISTRATION::OperationRegistration → OB_OPERATION_REGISTRATION::PreOperation の順で見るだけです。
kd> dpu @rcx l5
fffff606`ede06420 00000000`00020100
fffff606`ede06428 00000015`000e000c
fffff606`ede06430 fffff80b`35d595a0 "328010"
fffff606`ede06438 00000000`00000000
fffff606`ede06440 fffff606`ede06450 ".긁..."
kd> dps fffff606`ede06450 l4
fffff606`ede06450 fffff800`ae0120d0 nt!PsProcessType
fffff606`ede06458 00000000`00000003
fffff606`ede06460 fffff80b`35d741a0 WdFilter!MpObPreOperationCallback
fffff606`ede06468 00000000`00000000
コールバック関数は WdFilter!MpObPreOperationCallback でした。ここにブレークポイントを張ると、MSDN の ObRegisterCallbacks の説明にある通り、プロセスやスレッドのハンドル操作時に呼ばれることが分かります。
今回問題になっているのは、MsMpEng.exe にデバッガーをアタッチしても、書き込み権限が抜かれたしょぼいハンドルをデバッガーが掴まされることです。デバッガーがハンドルを取得するときに WdFilter!MpObPreOperationCallback がフックして権限を抜いているはずです。
デバッグ サーバーをデバッグすると、ターゲットにアタッチするときに呼ばれる OpenProcess の呼び出し中に、WdFilter のコールバック関数が確かに呼ばれていることが確認できました。厳密には確かめていませんが、対象のプロセスが MsMpEng だった場合、制限したハンドルを渡すように実装されているのだと思います。
kd> !process 0 0 dbgsrv.exe
PROCESS ffff81005716e080
SessionId: 1 Cid: 1320 Peb: b93937d000 ParentCid: 0bb8
DirBase: 4e9bd000 ObjectTable: ffffdd82b608f700 HandleCount: 101.
Image: dbgsrv.exe
kd> bp /p ffff81005716e080 WdFilter!MpObPreOperationCallback
kd> g
Breakpoint 0 hit
WdFilter!MpObPreOperationCallback:
fffff80b`35d741a0 4883ec28 sub rsp,28h
kd> .reload
Connected to Windows 10 16299 x64 target at (Thu Nov 23 20:56:52.409 2017 (UTC - 8:00)), ptr64 TRUE
Loading Kernel Symbols
...............................................................
................................................................
............................................
Loading User Symbols
.................................
Loading unloaded module list
......
kd> k
Child-SP RetAddr Call Site
fffff606`ef1d4518 fffff800`ae11fe4b WdFilter!MpObPreOperationCallback
fffff606`ef1d4520 fffff800`ae1119b5 nt!ObpCreateHandle+0xa5b
fffff606`ef1d4780 fffff800`ae10d784 nt!PsOpenProcess+0x535
fffff606`ef1d4ac0 fffff800`add88553 nt!NtOpenProcess+0x24
fffff606`ef1d4b00 00007ff9`59750304 nt!KiSystemServiceCopyEnd+0x13
000000b9`394ff638 00007ff9`55aedbfd ntdll!NtOpenProcess+0x14
000000b9`394ff640 00007ff9`38a71411 KERNELBASE!OpenProcess+0x4d
000000b9`394ff6b0 00007ff9`38a716f4 dbgeng!LiveUserDebugServices::ProcessIdToHandle+0x39
000000b9`394ff6e0 00007ff9`38a84259 dbgeng!LiveUserDebugServices::AttachProcess+0x64
000000b9`394ff750 00007ff9`38a3f1ae dbgeng!SFN_IUserDebugServicesN_AttachProcess+0x49
000000b9`394ff790 00007ff9`38a412a9 dbgeng!DbgRpcReceiveCalls+0x226
000000b9`394ff830 00007ff9`56c21fe4 dbgeng!DbgRpcClientThread+0x149
000000b9`394ff8c0 00007ff9`5971ef91 KERNEL32!BaseThreadInitThunk+0x14
000000b9`394ff8f0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
これをどうやって jailbreak するかですが、Matthijs さんは、wdfilter を無効化するか、ObRegisterCallbacks の呼び出し元である WdFilter!MpObAddCallback が必ず失敗するように書き換えたら MsMpEng.exe もデバッグできるようになったよ、と教えてくれました。この人親切だけど、ワイルド過ぎる。。。これがハッカーか。
どうせデバッガーでアセンブリを書き換えるなら、獲物は無傷なまま残したいので、コールバック関数のほうを書き換えることにしました。もしくは、コールバック関数のアドレスを別のアドレスに置き換えるのもよさそうですね。
アセンブリをざっと眺めます。
kd> u WdFilter!MpObPreOperationCallback
WdFilter!MpObPreOperationCallback:
fffff80b`35d741a0 4883ec28 sub rsp,28h
fffff80b`35d741a4 48837a0800 cmp qword ptr [rdx+8],0
fffff80b`35d741a9 7424 je WdFilter!MpObPreOperationCallback+0x2f (fffff80b`35d741cf)
...
kd> u (fffff80b`35d741cf)
WdFilter!MpObPreOperationCallback+0x2f:
fffff80b`35d741cf 33c0 xor eax,eax
fffff80b`35d741d1 4883c428 add rsp,28h
fffff80b`35d741d5 c3 ret
どうやら poi(rdx+8) が 0 だった場合は、何もせずに 0 を返すようです。というわけで、常に何もせずに 0 を返すように変えます。
kd> a WdFilter!MpObPreOperationCallback
fffff80b`35d741a0 xor eax,eax
xor eax,eax
fffff80b`35d741a2 ret
ret
fffff80b`35d741a3
kd> u WdFilter!MpObPreOperationCallback l3
WdFilter!MpObPreOperationCallback:
fffff80b`35d741a0 31c0 xor eax,eax
fffff80b`35d741a2 c3 ret
fffff80b`35d741a3 284883 sub byte ptr [rax-7Dh],cl
kd> g
これで jailbreak 完了です。
適当にデバッグしてみましょう。例えば CreateFile でブレークさせるとか。
0:025> |
. 0 id: e10 attach name: C:\Program Files\Windows Defender\MsMpEng.exe
0:025> bp KERNELBASE!CreateFileW
0:007> g
Breakpoint 0 hit
KERNELBASE!CreateFileW:
00007ff9`55af17e0 4883ec58 sub rsp,58h
0:007> k
Child-SP RetAddr Call Site
00000048`79dfd908 00007ff9`37161407 KERNELBASE!CreateFileW
00000048`79dfd910 00007ff9`36e8f8db mpengine!FreeSigFiles+0x2c77
00000048`79dfd970 00007ff9`36e8f7d7 mpengine+0x1f8db
00000048`79dfd9c0 00007ff9`36e8edcf mpengine+0x1f7d7
00000048`79dfda00 00007ff9`36e8e42a mpengine+0x1edcf
00000048`79dfdab0 00007ff9`36ee7e8e mpengine+0x1e42a
00000048`79dfdaf0 00007ff9`3720246b mpengine+0x77e8e
00000048`79dfdb40 00007ff9`37201f23 mpengine!FreeSigFiles+0xa3cdb
00000048`79dfdf60 00007ff9`36e7f45a mpengine!FreeSigFiles+0xa3793
00000048`79dfdfb0 00007ff9`36e7d3fc mpengine+0xf45a
00000048`79dfe250 00007ff9`36e95b4d mpengine+0xd3fc
00000048`79dfe310 00007ff9`36f1452e mpengine+0x25b4d
00000048`79dfe340 00007ff9`3710b857 mpengine!GetSigFiles+0x151ae
00000048`79dfeaf0 00007ff9`3710b7e8 mpengine!_rsignal+0xa27
00000048`79dfeb20 00007ff9`3b7e5084 mpengine!_rsignal+0x9b8
00000048`79dff080 00007ff9`47148c53 mpsvc!rsignal_wrapper+0xf4
00000048`79dff0f0 00007ff9`4714764e mprtp!RealtimeProtection::CCMEngine::GetProcessFilterFlagsFromImageFile+0xab
00000048`79dff210 00007ff9`47146f12 mprtp!RealtimeProtection::CRtpFilterManager::GetProcessFilterFlags+0xce
00000048`79dff2d0 00007ff9`47151f11 mprtp!RealtimeProtection::CFileSystemWatcher::HandleRequest+0x982
00000048`79dff9f0 00007ff9`47150a5c mprtp!RealtimeProtection::CFilterCommunicatorBase::CommunicatorMainFunction+0x3c1
00000048`79dffac0 00007ff9`56d2a8e6 mprtp!RealtimeProtection::CFilterCommunicatorBase::CommunicatorThread+0x2c
00000048`79dffb10 00007ff9`56d2a9bc msvcrt!_callthreadstartex+0x1e
00000048`79dffb40 00007ff9`56c21fe4 msvcrt!_threadstartex+0x7c
00000048`79dffb70 00007ff9`5971ef91 KERNEL32!BaseThreadInitThunk+0x14
00000048`79dffba0 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:024> lmvm mpengine
start end module name
00007ff9`36e70000 00007ff9`37b6f000 mpengine (export symbols) mpengine.dll
Loaded symbol image file: mpengine.dll
Image path: C:\ProgramData\Microsoft\Windows Defender\Definition Updates\{47F3489C-8ADA-481F-BA45-E1F37E7914C5}\mpengine.dll
Image name: mpengine.dll
Timestamp: Mon Oct 30 00:18:32 2017 (59F6D248)
CheckSum: 00D259B8
ImageSize: 00CFF000
File version: 1.1.14306.0
Product version: 1.1.14306.0
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 2.0 Dll
File date: 00000000.00000000
Translations: 0409.04b0
CompanyName: Microsoft Corporation
ProductName: Microsoft Malware Protection
InternalName: mpengine
OriginalFilename: mpengine.dll
ProductVersion: 1.1.14306.0
FileVersion: 1.1.14306.0
FileDescription: Microsoft Malware Protection Engine
LegalCopyright: © Microsoft Corporation. All rights reserved.
残念。エンジンのデバッグ シンボルが提供されていない。ファイル パスを見ると、この mpengine.dll というモジュールは定義ファイルとして配布されているようです。興味深い、が Windows Defender をデバッグする予定はないので今回はここまで。
まとめ
以上、EPROCESS の構造体を書き換えることで、保護されたプロセスを jailbreak し、さらに Windows Defender のカーネル モジュールの関数も書き換えて Windows Defender の防御も突破する方法について紹介しました。当然サポート対象外になる方法なので、良識の範囲内で利用してください。何を今更って感じですが。
他に得られた知見としては以下でしょうか。
- Process Hacker 超便利
- 簡単なコードで、EPROCESS を列挙して縦横無尽に書き換えるようなカーネル ドライバー (これ) を作れる
- そのドライバーの開発者がとても親切だった
Happy hacking!