Windows
kernel

Windows 10 の保護プロセスを jailbreak してデバッガーをアタッチする方法

はじめに

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 サービスでした。

01-procexp.png

そんなのカーネル デバッガー使えばいいじゃん

鋭い。例えば 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

デバッグを可能にするには二つの方法があります。

  1. 保護プロセスの保護を解除する
  2. デバッガー プロセスの保護レベルをターゲットと同じにする

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 タブの値が消えており、デバッガーもアタッチできました。

02-unprotected.png

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 をセットしておけば、全ての保護プロセスにアタッチできます。簡単ですね。

04-dbgsrv.png

カーネル ドライバーで 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 は要らない。しかもオープンソース。ただプロセスの保護レベルを表示する機能がない。惜しい。

03.PNG

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::OperationRegistrationOB_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!