From 2bc14f35cc944b658f07d80fe43650d9c7356f56 Mon Sep 17 00:00:00 2001 From: Xusheng Date: Mon, 15 Jan 2024 20:19:38 +0800 Subject: [PATCH] Add support for Windows local/remote kernel debugging. Fix https://github.com/Vector35/debugger/issues/215 --- core/CMakeLists.txt | 4 + core/adapters/localwindowskerneladapter.cpp | 213 +++++++++++++++++ core/adapters/localwindowskerneladapter.h | 53 +++++ core/adapters/windowskerneladapter.cpp | 243 ++++++++++++++++++++ core/adapters/windowskerneladapter.h | 49 ++++ core/debugger.cpp | 6 +- docs/guide/debugger.md | 5 + docs/guide/windows-kd.md | 111 +++++++++ 8 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 core/adapters/localwindowskerneladapter.cpp create mode 100644 core/adapters/localwindowskerneladapter.h create mode 100644 core/adapters/windowskerneladapter.cpp create mode 100644 core/adapters/windowskerneladapter.h create mode 100644 docs/guide/windows-kd.md diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 30fe73d..4a38e8c 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -29,6 +29,10 @@ if(WIN32) adapters/dbgengadapter.h adapters/dbgengttdadapter.cpp adapters/dbgengttdadapter.h + adapters/windowskerneladapter.cpp + adapters/windowskerneladapter.h + adapters/localwindowskerneladapter.cpp + adapters/localwindowskerneladapter.h ) else() set(SOURCES ${COMMON_SOURCES} ${ADAPTER_SOURCES}) diff --git a/core/adapters/localwindowskerneladapter.cpp b/core/adapters/localwindowskerneladapter.cpp new file mode 100644 index 0000000..93ba7b5 --- /dev/null +++ b/core/adapters/localwindowskerneladapter.cpp @@ -0,0 +1,213 @@ +#include "localwindowskerneladapter.h" +#include + +using namespace BinaryNinjaDebugger; +using namespace std; + + +LocalWindowsKernelAdapter::LocalWindowsKernelAdapter(BinaryView* data) : DbgEngAdapter(data) +{ + m_usePDBFileName = false; +} + + +bool LocalWindowsKernelAdapter::ExecuteWithArgsInternal(const std::string& path, const std::string& args, + const std::string& workingDir, const LaunchConfigurations& configs) { + LogWarn("here"); + m_aboutToBeKilled = false; + + if (this->m_debugActive) { + this->Reset(); + } + + if (!Start()) { + this->Reset(); + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("Failed to initialize DbgEng"); + event.data.errorData.shortError = fmt::format("Failed to initialize DbgEng"); + PostDebuggerEvent(event); + return false; + } + + if (const auto result = this->m_debugClient->AttachKernel(DEBUG_ATTACH_LOCAL_KERNEL, nullptr); + result != S_OK) { + this->Reset(); + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("AttachKernel failed: 0x{:x}", result); + event.data.errorData.shortError = fmt::format("AttachKernel failed: 0x{:x}", result); + PostDebuggerEvent(event); + return false; + } + + // The WaitForEvent() must be called once before the engine fully attaches to the target. + if (!Wait()) { + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("WaitForEvent failed"); + event.data.errorData.shortError = fmt::format("WaitForEvent failed"); + PostDebuggerEvent(event); + } + + return true; +} + + +bool LocalWindowsKernelAdapter::Start() +{ + if (this->m_debugActive) + this->Reset(); + + auto handle = GetModuleHandleA("dbgeng.dll"); + if (handle == nullptr) + false; + + // HRESULT DebugCreate( + // [in] REFIID InterfaceId, + // [out] PVOID *Interface + // ); + typedef HRESULT(__stdcall * pfunDebugCreate)(REFIID, PVOID*); + auto DebugCreate = (pfunDebugCreate)GetProcAddress(handle, "DebugCreate"); + if (DebugCreate == nullptr) + return false; + + if (const auto result = DebugCreate(__uuidof(IDebugClient7), reinterpret_cast(&this->m_debugClient)); + result != S_OK) + throw std::runtime_error("Failed to create IDebugClient7"); + + QUERY_DEBUG_INTERFACE(IDebugControl7, &this->m_debugControl); + QUERY_DEBUG_INTERFACE(IDebugDataSpaces, &this->m_debugDataSpaces); + QUERY_DEBUG_INTERFACE(IDebugRegisters, &this->m_debugRegisters); + QUERY_DEBUG_INTERFACE(IDebugSymbols3, &this->m_debugSymbols); + QUERY_DEBUG_INTERFACE(IDebugSystemObjects, &this->m_debugSystemObjects); + + m_debugEventCallbacks.SetAdapter(this); + if (const auto result = this->m_debugClient->SetEventCallbacks(&this->m_debugEventCallbacks); result != S_OK) + { + LogWarn("Failed to set event callbacks"); + return false; + } + + m_outputCallbacks.SetAdapter(this); + if (const auto result = this->m_debugClient->SetOutputCallbacks(&this->m_outputCallbacks); result != S_OK) + { + LogWarn("Failed to set output callbacks"); + return false; + } + + m_inputCallbacks.SetDbgControl(m_debugControl); + if (const auto result = this->m_debugClient->SetInputCallbacks(&this->m_inputCallbacks); result != S_OK) + { + LogWarn("Failed to set input callbacks"); + return false; + } + + this->m_debugActive = true; + return true; +} + + +void LocalWindowsKernelAdapter::Reset() +{ + m_aboutToBeKilled = false; + + if (!this->m_debugActive) + return; + + // Free up the resources if the dbgsrv is launched by the adapter. Otherwise, the dbgsrv is launched outside BN, + // we should keep everything active. + SAFE_RELEASE(this->m_debugControl); + SAFE_RELEASE(this->m_debugDataSpaces); + SAFE_RELEASE(this->m_debugRegisters); + SAFE_RELEASE(this->m_debugSymbols); + SAFE_RELEASE(this->m_debugSystemObjects); + + if (this->m_debugClient) + { + this->m_debugClient->EndSession(DEBUG_END_PASSIVE); + m_server = 0; + } + + // There seems to be an internal ref-counting issue in the DbgEng TTD engine, that the reference for the debug + // client is not properly freed after the target has exited. To properly free the debug client instance, here we + // are calling Release() a few more times to ensure the ref count goes down to 0. Luckily this would not cause + // a UAF or crash. + // This might be related to the weird behavior of not terminating the target when we call TerminateProcesses(), + // (see comment in `DbgEngTTDAdapter::Quit()`). + // The same issue is not observed when we do forward debugging using the regular DbgEng. Also, I cannot reproduce + // the issue using my script https://github.com/xusheng6/dbgeng_test. + for (size_t i = 0; i < 100; i++) + m_debugClient->Release(); + + SAFE_RELEASE(this->m_debugClient); + + this->m_debugActive = false; +} + + +bool LocalWindowsKernelAdapter::Detach() +{ + m_aboutToBeKilled = true; + m_lastOperationIsStepInto = false; + if (!this->m_debugClient) + return false; + + if (this->m_debugClient->EndSession(DEBUG_END_PASSIVE) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + + +bool LocalWindowsKernelAdapter::Quit() +{ + m_aboutToBeKilled = true; + m_lastOperationIsStepInto = false; + if (!this->m_debugClient) + return false; + + if (this->m_debugClient->EndSession(DEBUG_END_PASSIVE) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + + +LocalWindowsKernelAdapterType::LocalWindowsKernelAdapterType() : DebugAdapterType("LOCAL_WINDOWS_KERNEL") {} + + +DebugAdapter* LocalWindowsKernelAdapterType::Create(BinaryNinja::BinaryView* data) +{ + // TODO: someone should free this. + return new LocalWindowsKernelAdapter(data); +} + + +bool LocalWindowsKernelAdapterType::IsValidForData(BinaryNinja::BinaryView* data) +{ + return data->GetTypeName() == "PE" || data->GetTypeName() == "Raw"; +} + + +bool LocalWindowsKernelAdapterType::CanConnect(BinaryNinja::BinaryView* data) +{ + return true; +} + + +bool LocalWindowsKernelAdapterType::CanExecute(BinaryNinja::BinaryView* data) +{ +#ifdef WIN32 + return true; +#endif + return false; +} + +void BinaryNinjaDebugger::InitLocalWindowsKernelAdapterType() +{ + static LocalWindowsKernelAdapterType localType; + DebugAdapterType::Register(&localType); +} diff --git a/core/adapters/localwindowskerneladapter.h b/core/adapters/localwindowskerneladapter.h new file mode 100644 index 0000000..ce5640c --- /dev/null +++ b/core/adapters/localwindowskerneladapter.h @@ -0,0 +1,53 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once +#include "dbgengadapter.h" + +namespace BinaryNinjaDebugger { + class LocalWindowsKernelAdapter: public DbgEngAdapter + { + public: + LocalWindowsKernelAdapter(BinaryView* data); + + [[nodiscard]] bool ExecuteWithArgsInternal(const std::string& path, const std::string& args, + const std::string& workingDir, const LaunchConfigurations& configs = {}) override; + + bool Start() override; + void Reset() override; + + bool Detach() override; + bool Quit() override; + + bool Attach(std::uint32_t pid) override { return false; } + bool Connect(const std::string& server, std::uint32_t port) override { return false; } + std::vector GetProcessList() override {return {}; } + bool ConnectToDebugServer(const std::string& server, std::uint32_t port) override { return false; } + bool DisconnectDebugServer() override { return false; } + }; + + class LocalWindowsKernelAdapterType : public DebugAdapterType + { + public: + LocalWindowsKernelAdapterType(); + virtual DebugAdapter* Create(BinaryNinja::BinaryView* data); + virtual bool IsValidForData(BinaryNinja::BinaryView* data); + virtual bool CanExecute(BinaryNinja::BinaryView* data); + virtual bool CanConnect(BinaryNinja::BinaryView* data); + }; + + void InitLocalWindowsKernelAdapterType(); +}; diff --git a/core/adapters/windowskerneladapter.cpp b/core/adapters/windowskerneladapter.cpp new file mode 100644 index 0000000..604a02a --- /dev/null +++ b/core/adapters/windowskerneladapter.cpp @@ -0,0 +1,243 @@ +#include "windowskerneladapter.h" +#include + +using namespace BinaryNinjaDebugger; +using namespace std; + + +WindowsKernelAdapter::WindowsKernelAdapter(BinaryView* data) : DbgEngAdapter(data) +{ + m_usePDBFileName = false; +} + + +bool WindowsKernelAdapter::ExecuteWithArgsInternal(const std::string& path, const std::string& args, + const std::string& workingDir, const LaunchConfigurations& configs) { + m_aboutToBeKilled = false; + + if (this->m_debugActive) { + this->Reset(); + } + + if (!Start()) { + this->Reset(); + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("Failed to initialize DbgEng"); + event.data.errorData.shortError = fmt::format("Failed to initialize DbgEng"); + PostDebuggerEvent(event); + return false; + } + + if (const auto result = this->m_debugControl->SetEngineOptions(DEBUG_ENGOPT_INITIAL_BREAK); result != S_OK) { + this->Reset(); + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("Failed to engine option DEBUG_ENGOPT_INITIAL_BREAK"); + event.data.errorData.shortError = fmt::format("Failed to engine option"); + PostDebuggerEvent(event); + return false; + } + + if (const auto result = this->m_debugClient->AttachKernel(DEBUG_ATTACH_KERNEL_CONNECTION, + const_cast(path.c_str())); + result != S_OK) { + this->Reset(); + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("AttachKernel failed: 0x{:x}", result); + event.data.errorData.shortError = fmt::format("AttachKernel failed: 0x{:x}", result); + PostDebuggerEvent(event); + return false; + } + + // The WaitForEvent() must be called once before the engine fully attaches to the target. + if (!Wait()) { + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("WaitForEvent failed"); + event.data.errorData.shortError = fmt::format("WaitForEvent failed"); + PostDebuggerEvent(event); + } + + // Apply the breakpoints added before the m_debugClient is created + ApplyBreakpoints(); + + auto settings = Settings::Instance(); + if (settings->Get("debugger.stopAtEntryPoint") && m_hasEntryFunction) { + AddBreakpoint(ModuleNameAndOffset(configs.inputFile, m_entryPoint - m_start)); + } + + if (!settings->Get("debugger.stopAtSystemEntryPoint")) { + if (this->m_debugControl->SetExecutionStatus(DEBUG_STATUS_GO) != S_OK) { + this->Reset(); + DebuggerEvent event; + event.type = LaunchFailureEventType; + event.data.errorData.error = fmt::format("Failed to resume the target after the system entry point"); + event.data.errorData.shortError = fmt::format("Failed to resume target"); + PostDebuggerEvent(event); + return false; + } + } + + return true; +} + + +bool WindowsKernelAdapter::Start() +{ + if (this->m_debugActive) + this->Reset(); + + auto handle = GetModuleHandleA("dbgeng.dll"); + if (handle == nullptr) + false; + + // HRESULT DebugCreate( + // [in] REFIID InterfaceId, + // [out] PVOID *Interface + // ); + typedef HRESULT(__stdcall * pfunDebugCreate)(REFIID, PVOID*); + auto DebugCreate = (pfunDebugCreate)GetProcAddress(handle, "DebugCreate"); + if (DebugCreate == nullptr) + return false; + + if (const auto result = DebugCreate(__uuidof(IDebugClient7), reinterpret_cast(&this->m_debugClient)); + result != S_OK) + throw std::runtime_error("Failed to create IDebugClient7"); + + QUERY_DEBUG_INTERFACE(IDebugControl7, &this->m_debugControl); + QUERY_DEBUG_INTERFACE(IDebugDataSpaces, &this->m_debugDataSpaces); + QUERY_DEBUG_INTERFACE(IDebugRegisters, &this->m_debugRegisters); + QUERY_DEBUG_INTERFACE(IDebugSymbols3, &this->m_debugSymbols); + QUERY_DEBUG_INTERFACE(IDebugSystemObjects, &this->m_debugSystemObjects); + + m_debugEventCallbacks.SetAdapter(this); + if (const auto result = this->m_debugClient->SetEventCallbacks(&this->m_debugEventCallbacks); result != S_OK) + { + LogWarn("Failed to set event callbacks"); + return false; + } + + m_outputCallbacks.SetAdapter(this); + if (const auto result = this->m_debugClient->SetOutputCallbacks(&this->m_outputCallbacks); result != S_OK) + { + LogWarn("Failed to set output callbacks"); + return false; + } + + m_inputCallbacks.SetDbgControl(m_debugControl); + if (const auto result = this->m_debugClient->SetInputCallbacks(&this->m_inputCallbacks); result != S_OK) + { + LogWarn("Failed to set input callbacks"); + return false; + } + + this->m_debugActive = true; + return true; +} + + +void WindowsKernelAdapter::Reset() +{ + m_aboutToBeKilled = false; + + if (!this->m_debugActive) + return; + + // Free up the resources if the dbgsrv is launched by the adapter. Otherwise, the dbgsrv is launched outside BN, + // we should keep everything active. + SAFE_RELEASE(this->m_debugControl); + SAFE_RELEASE(this->m_debugDataSpaces); + SAFE_RELEASE(this->m_debugRegisters); + SAFE_RELEASE(this->m_debugSymbols); + SAFE_RELEASE(this->m_debugSystemObjects); + + if (this->m_debugClient) + { + this->m_debugClient->EndSession(DEBUG_END_PASSIVE); + m_server = 0; + } + + // There seems to be an internal ref-counting issue in the DbgEng TTD engine, that the reference for the debug + // client is not properly freed after the target has exited. To properly free the debug client instance, here we + // are calling Release() a few more times to ensure the ref count goes down to 0. Luckily this would not cause + // a UAF or crash. + // This might be related to the weird behavior of not terminating the target when we call TerminateProcesses(), + // (see comment in `DbgEngTTDAdapter::Quit()`). + // The same issue is not observed when we do forward debugging using the regular DbgEng. Also, I cannot reproduce + // the issue using my script https://github.com/xusheng6/dbgeng_test. + for (size_t i = 0; i < 100; i++) + m_debugClient->Release(); + + SAFE_RELEASE(this->m_debugClient); + + this->m_debugActive = false; +} + + +bool WindowsKernelAdapter::Detach() +{ + m_aboutToBeKilled = true; + m_lastOperationIsStepInto = false; + if (!this->m_debugClient) + return false; + + if (this->m_debugClient->EndSession(DEBUG_END_PASSIVE) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + + +bool WindowsKernelAdapter::Quit() +{ + m_aboutToBeKilled = true; + m_lastOperationIsStepInto = false; + if (!this->m_debugClient) + return false; + + if (this->m_debugClient->EndSession(DEBUG_END_PASSIVE) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + + +WindowsKernelAdapterType::WindowsKernelAdapterType() : DebugAdapterType("WINDOWS_KERNEL") {} + + +DebugAdapter* WindowsKernelAdapterType::Create(BinaryNinja::BinaryView* data) +{ + // TODO: someone should free this. + return new WindowsKernelAdapter(data); +} + + +bool WindowsKernelAdapterType::IsValidForData(BinaryNinja::BinaryView* data) +{ + return data->GetTypeName() == "PE" || data->GetTypeName() == "Raw"; +} + + +bool WindowsKernelAdapterType::CanConnect(BinaryNinja::BinaryView* data) +{ + return true; +} + + +bool WindowsKernelAdapterType::CanExecute(BinaryNinja::BinaryView* data) +{ +#ifdef WIN32 + return true; +#endif + return false; +} + +void BinaryNinjaDebugger::InitWindowsKernelAdapterType() +{ + static WindowsKernelAdapterType localType; + DebugAdapterType::Register(&localType); +} diff --git a/core/adapters/windowskerneladapter.h b/core/adapters/windowskerneladapter.h new file mode 100644 index 0000000..2f2f0b8 --- /dev/null +++ b/core/adapters/windowskerneladapter.h @@ -0,0 +1,49 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once +#include "dbgengadapter.h" + +namespace BinaryNinjaDebugger { + class WindowsKernelAdapter: public DbgEngAdapter + { + public: + WindowsKernelAdapter(BinaryView* data); + + [[nodiscard]] bool ExecuteWithArgsInternal(const std::string& path, const std::string& args, + const std::string& workingDir, const LaunchConfigurations& configs = {}) override; + + bool Start() override; + void Reset() override; + + bool Detach() override; + bool Quit() override; + +// std::string GetDbgEngPath(const std::string& arch) override; + }; + + class WindowsKernelAdapterType : public DebugAdapterType + { + public: + WindowsKernelAdapterType(); + virtual DebugAdapter* Create(BinaryNinja::BinaryView* data); + virtual bool IsValidForData(BinaryNinja::BinaryView* data); + virtual bool CanExecute(BinaryNinja::BinaryView* data); + virtual bool CanConnect(BinaryNinja::BinaryView* data); + }; + + void InitWindowsKernelAdapterType(); +}; diff --git a/core/debugger.cpp b/core/debugger.cpp index c768b48..060d64f 100644 --- a/core/debugger.cpp +++ b/core/debugger.cpp @@ -20,6 +20,8 @@ limitations under the License. #ifdef WIN32 #include "adapters/dbgengadapter.h" #include "adapters/dbgengttdadapter.h" + #include "adapters/windowskerneladapter.h" + #include "adapters/localwindowskerneladapter.h" #endif using namespace BinaryNinja; @@ -29,7 +31,9 @@ void InitDebugAdapterTypes() { #ifdef WIN32 InitDbgEngAdapterType(); - InitDbgEngTTDAdapterType(); + InitDbgEngTTDAdapterType(); + InitWindowsKernelAdapterType(); + InitLocalWindowsKernelAdapterType(); #endif // Disable these adapters because they are not tested, and will get replaced later diff --git a/docs/guide/debugger.md b/docs/guide/debugger.md index 919fcdb..9d1eb80 100644 --- a/docs/guide/debugger.md +++ b/docs/guide/debugger.md @@ -336,6 +336,11 @@ See [Remote Debugging Guide](remote-debugging.md) See [Time Travel Debugging (beta) Guide](dbgeng-ttd.md) +### Windows Kernel Debugging + +See [Windows Kernel Debugging Guide](windows-kd.md) + + ## Known Issues and Workarounds There are some known issues and limitations with the current debugger. Here is a list including potential workarounds. diff --git a/docs/guide/windows-kd.md b/docs/guide/windows-kd.md new file mode 100644 index 0000000..a3fbde5 --- /dev/null +++ b/docs/guide/windows-kd.md @@ -0,0 +1,111 @@ +# Windows Kernel Debugging + +Binary Ninja debugger supports windows kernel debugging, either remote debugging using two machines, or local kernel debugging. +Below is the steps to configure it. + +## Remote Kernel Debugging + +At a high level, doing remote kernel debugging involves two steps: 1). setting up kernel debugging, 2). in Binary Ninja, +use the connection string to connect to it. There are multiple ways to configure Windows kernel debugging, and we will +use kdnet as as example. Other configurations should be similar. + +1. Setting up kernel debugging following the official [documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/setting-up-a-network-debugging-connection-automatically). Below is a quick recap: + - Copy `kdnet.exe` and `VerifiedNICList.xml` to the guest machine + - Find out the host machine IP address and an available port + - Ensure the network communication between the host and guest is smooth (e.g., check firewall settings) + - On the guest machine, run `kdnet.exe ` with administrative privilege, e.g., `kdnet.exe 192.168.56.1 50000` + - `kdnet` will print out a windbg command line which contains the connection string, e.g., `windbg -k net:port=50000,key=m94cdr7mkd1g.2kr136s4s2gjn.g4fjk4arnn69.zjgk4tc396li`. Copy this to the host machine +2. Start up kernel debugging in Binary Ninja + - Depending on your usage: + - If you wish to debug code in a specific driver, open the file in Binary Ninja and analyze it, or + - If you wish to debug the kernel in general, do not open any file, and create an empty binary view by pressing Ctrl+N/Command+N + - Open debugger sidebar, click `Debug Adapter Settings` button, then + - Set `Adapter Type` to `WINDOWS_KERNEL` + - Set `Executable Path` to the kernel debugging connection string, e.g, `net:port=50000,key=m94cdr7mkd1g.2kr136s4s2gjn.g4fjk4arnn69.zjgk4tc396li`. Note, do not include the `windbg -k` part in it. + - Click `Accept` + - Click the `Launch` button to start kernel debugging + - The debugger asks you to confirm the launch operation. Click `Yes` to proceed + - If you did not open a file in the first step, the debugger asks you to specify the platform for the debugger binary view. Select `windows-kernel-x86_64` or `windows-kernel-x86` accordingly. Click `Accept` to proceed + - The debugger will now connect to the guest machine. In the `Debugger Console`, it should print something similar to this: +``` +Microsoft (R) Windows Debugger Version 10.0.22621.1 AMD64 +Copyright (c) Microsoft Corporation. All rights reserved. + +Using NET for debugging +Opened WinSock 2.0 +Waiting to reconnect... +``` + +3. Finalizing the setup + - Reboot the guest machine by running `shutdown -r -t 0` in the guest command prompt + - The guest machine will then reboot and connect to the debugger + - We will see somethng similar to this in the `Debugger Console` in Binary Ninja: +``` +Connected to target 192.168.56.1 on port 50000 on local IP 192.168.56.1. +...... +System Uptime: 0 days 0:00:02.433 +Break instruction exception - code 80000003 (first chance) +******************************************************************************* +* * +* You are seeing this message because you pressed either * +* CTRL+C (if you run console kernel debugger) or, * +* CTRL+BREAK (if you run GUI kernel debugger), * +* on your debugger machine's keyboard. * +* * +* THIS IS NOT A BUG OR A SYSTEM CRASH * +* * +* If you did not intend to break into the debugger, press the "g" key, then * +* press the "Enter" key now. This message might immediately reappear. If it * +* does, press "g" and "Enter" again. * +* * +******************************************************************************* +``` + - Wait for a short while for the guest to boot, and it will break into the debugger: +``` +Break instruction exception - code 80000003 (first chance) +nt!KiInitializeMTRR+0x36512: +fffff801`7e281e36 cc int 3 +``` + - (Optional) If you wish to debug the early boot phase, place the appropriate breakpoint(s) using the WinDbg command line in the console + - Press the `Go` button in the debugger sidebar or type `g` in the debugger console to resume the target + - The guest should continue to boot and enter the desktop + - This might be slower compared to a regular boot process since the system is being debugged + - During this process, the guest may break into the debugger a few more times. This is normal for kernel debugging. Just resume the target and let it run + - If you have opened a driver file earlier, when it gets load, the debugger breaks at its `DriverEntry` + - If you did not open a driver file, once the guest boots into the desktop, click the `Pause` button to break into the debugger + - Proceed with the debugging as you would like to! + + +## Local Kernel Debugging + +1. Run Binary Ninja with administrative privilege +2. Create a new empty binary view by pressing Ctrl+N/Command+N +3. Open debugger sidebar, click `Debug Adapter Settings` +4. Select `LOCAL_WINDOWS_KERNEL` as the adapter type. Click `Accept` +5. Click the `Launch` button to start local kernel debugging +6. The `Debugger Console` should print something similar to this: +``` +Microsoft (R) Windows Debugger Version 10.0.22621.1 AMD64 +Copyright (c) Microsoft Corporation. All rights reserved. + +Connected to Windows 10 22621 x64 target at (Fri Jan 26 14:01:59.297 2024 (UTC + 8:00)), ptr64 TRUE +Symbol search path is: srv* +Executable search path is: +Windows 10 Kernel Version 22621 MP (24 procs) Free x64 +Product: WinNt, suite: TerminalServer SingleUserTS +Edition build lab: 22621.1.amd64fre.ni_release.220506-1250 +Machine Name: +Kernel base = 0xfffff805`51400000 PsLoadedModuleList = 0xfffff805`520134a0 +Debug session time: Fri Jan 26 14:01:59.511 2024 (UTC + 8:00) +System Uptime: 0 days 21:40:55.173 +``` +7. Proceed with the debugging as you would like to! + +## Troubleshooting & Known Issues + +1. Running certain commands that takes a while to complete can lead to a brief hang: https://github.com/Vector35/debugger/issues/532 +2. When we end the remote kernel debugging, the guest system is always paused: https://github.com/Vector35/debugger/issues/533 +3. Once we have enabled kernel debugging with `kdnet`, the system will always wait for a kernel debugger to connect during boot. +It would hang if we do not attach a kernel debugger, even if we do not plan to do kernel debugging at all. +To avoid this, I highly recommend taking a snapshot of the VM before attempting to do kernel debugging. +If you know there is a way to reset the kernel debugging status and let the system boot in the normal way, please let me know! \ No newline at end of file