Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

The Kernel-Bridge Framework

5.00/5 (10 votes)
1 Nov 2018GPL34 min read 14.8K  
The Windows kernel-hacking library and development framework written on C++17

Introduction

Have you ever dreamed about Windows kernel hacking? Want to do something restricted by OS? Or go to the deep internals of the Windows kernel and do something directly with hardware, but thought that it is rocket science? Well, let's see, how deep the rabbit hole is!

The Red Pill and Welcome to the Matrix, Neo!

GitHub: https://github.com/HoShiMin/Kernel-Bridge

So, What Is It?

I'm glad to present you the kernel-mode development framework, API and ready-to-use kernel driver and usermode library written in C++17/C++20 to use it for your kernel researching and building your own kernel components.

It supports work with user- and kernel-memory, memory of another processes, IO, MSRs, processes and threads, physical memory, remote code execution, filtering of the file system, loading unsigned modules and drivers and much more.

Well, How to Use It in My Project?

Files hierarchy of the Kernel-Bridge framework:

  • /User-Bridge/API - All usermode wrappers, headers and APIs for your usermode project
  • /Kernel-Bridge/API - All kernelmode APIs (with no external dependencies) you can use in your own driver
  • /SharedTypes/ - Both usermode and kernelmode shared headers

Ok, first of all, you should load this driver. You can do it in 2 ways: load as common driver or load as minifilter. Loading as minifilter allows you to use extended features like file system filtering or usermode Ob***- and Ps***-callbacks.

C++
#include <Windows.h>

#include "WdkTypes.h"
#include "CtlTypes.h"
#include "User-Bridge.h"

int main()
{
    using namespace KbLoader;

    // Unloading previous loaded instance:
    KbUnload();

    BOOL Status = KbLoadAsFilter(
        L"X:\\Folder\\Path\\To\\Kernel-Bridge.sys",
        L"260000" // Altitude of minifilter
    );

    if (!Status) 
        return 0; // Unable to load driver!

    // Successfully loaded!
    // Now you can use the User-Bridge API! 
    
    ...

    KbUnload();
    return 0;
}

Ok, What's Next? I Wanna Hack!

Patience, my dear!

Well, let's start with the most common question in the hacking community: how to read/write a process memory!

C++
using namespace Processes::MemoryManagement;

constexpr int Size = 64;
BYTE Buffer[Size] = {};

BOOL Status = KbReadProcessMemory( // Or KbWriteProcessMemory if you want to write
    ProcessId,
    0x7FFF0000, // Desired address in context of ProcessId
    &Buffer,
    Size
);

Now, one level deeper - reading of the kernel memory:

C++
using namespace VirtualMemory;

constexpr int Size = 64;
BYTE Buffer[Size];

// Reading the kernel memory to Buffer:
BOOL Status = KbCopyMoveMemory(
    reinterpret_cast<WdkTypes::PVOID>(Buffer),
    0xFFFFF80000C00000, // Any kernel address
    Size,
    FALSE // Buffers doesn't intersects
);

What about hardware-specific operations, like an I/O? Yes, we can do it! And more! We can do it in usermode by usermode forwarding using the IOPL bits in the EFlags register that allows us to use in/out/cli/sti instructions in usermode.

C++
#include <intrin.h>

using namespace IO::Iopl;

// Let's beep on the system beeper from usermode!
// All of __in***/__out*** instructions are privileged for the Ring0!

KbRaiseIopl(); // Now __in***/__out*** (such as __cli/__sti) are allowed in usermode!

ULONG Frequency = 1000; // 1 kHz
ULONG Divider = 1193182 / Frequency;

__outbyte(0x43, 0xB6); // Set the beeper regime

// Set the beeper divider:
__outbyte(0x42, static_cast<unsigned char>(Divider));
__outbyte(0x42, static_cast<unsigned char>(Divider >> 8));

__outbyte(0x61, __inbyte(0x61) | 3); // Start the beeper!

for (int i = 0; i < 5000; i++); // Check it with Sleep(); but IOPL may resets in some cases!

__outbyte(0x61, __inbyte(0x61) & 252); // Stop the beeper!

KbResetIopl();

Well, I see you amazed! What about Ring0 shells execution in usermode?

C++
using namespace KernelShells;

// Calling KeStallExecutionProcessor:
ULONG Result = 1337;
KbExecuteShellCode(
    [](
        _GetKernelProcAddress GetKernelProcAddress,
        PVOID Argument
    ) -> ULONG {
        using _KeStallExecutionProcessor = VOID(WINAPI*)(ULONG Microseconds);
        auto Stall = reinterpret_cast<_KeStallExecutionProcessor>(
            GetKernelProcAddress(L"KeStallExecutionProcessor")
        );
        Stall(1000 * 1000); // Stalling CPU for 1 second

        ULONG Value = *static_cast<PULONG>(Argument);
        return Value == 1337 ? 0x1EE7C0DE : 0;
    },
    &Result, // Argument
    &Result  // Result
);

// Returns Result = 0x1EE7C0DE

Real Talk! I'm Amazed, but What About More Serious Features?

Hmmm, well... Let's think... about filtering subsystem!

Using the filtering features of the Kernel-Bridge project, you can easily filter the most common part of the file system IO and Ps*** and Ob*** events (by ObRegisterCallbacks and PsSet***NotifyRoutine).

It works on the simple scheme:

  1. The Kernel-Bridge driver registers as filter or sets up an Ob***/Ps*** callbacks
  2. Usermode app connects to the communication port opened by driver
  3. At the filtering routine, driver broadcasts events to the connected usermode clients
  4. Usermode clients doing some filtration (blocks access to the file, descends handle's access rights, etc.)
  5. Returning back to the kernel with the processed data that applies to the filter request

Let's subscribe to the Ob***- and Ps***-filter:

C++
#include <Windows.h>
#include <fltUser.h>
 
#include "CommPort.h"
#include "WdkTypes.h"
#include "FltTypes.h"
#include "Flt-Bridge.h"
 
...
 
// ObRegisterCallbacks notifier:
CommPortListener<KB_FLT_OB_CALLBACK_INFO, KbObCallbacks> ObCallbacks;
 
// Prevent to open our process with PROCESS_VM_READ rights:
Status = ObCallbacks.Subscribe([](
    CommPort& Port,
    MessagePacket<KB_FLT_OB_CALLBACK_INFO>& Message
) -> VOID {
    auto Data = static_cast<PKB_FLT_OB_CALLBACK_INFO>(Message.GetData());
    if (Data->Target.ProcessId == GetCurrentProcessId()) {
        Data->CreateResultAccess &= ~PROCESS_VM_READ;
        Data->DuplicateResultAccess &= ~PROCESS_VM_READ;
    }
    ReplyPacket<KB_FLT_OB_CALLBACK_INFO> Reply(Message, ERROR_SUCCESS, *Data);
    Port.Reply(Reply); // Reply info to driver
});
 
 
// Ps***-callbacks:
CommPortListener<KB_FLT_PS_IMAGE_INFO, KbPsImage> ProcessCallbacks;
 
Status = ProcessCallbacks.Subscribe([](
    CommPort& Port, 
    MessagePacket<KB_FLT_PS_IMAGE_INFO>& Message
) -> VOID {
    auto Data = static_cast<PKB_FLT_PS_IMAGE_INFO>(Message.GetData());
    printf(
        "[PID: %i | Base: 0x%I64X | Size: %ull]: %ws\r\n",
        (int)Data->ProcessId, Data->BaseAddress, (int)Data->ImageSize, Data->FullImageName
    );
});

Good Stuff! but What About Kernel Development?

Yes, it is a big part of the Kernel-Bridge framework.

It has a big set of API for work with a wide-range parts of the Windows kernel such as convenient containers (like strings or linked lists written on C++17). You can use them in your own kernel projects without any external dependencies.

All of Kernel-Bridge kernel API are locates in /Kernel-Bridge/API/ folder.

Just for example, using the kernel StringsAPI:

C++
#include <wdm.h>
#include <ntstrsafe.h>
#include <stdarg.h>

#include "StringsAPI.h"

WideString wString = L"Some string";
AnsiString aString = wString.GetAnsi().GetLowerCase() + " and another string!";

if (aString.Matches("*another*"))
    DbgPrint("%s\r\n", aString.GetData());

But I Want to Implement My Own Stuff!..

If you want to add your own IOCTL handler:

  1. Write the handler into the /Kernel-Bridge/Kernel-Bridge/IOCTLHandlers.cpp file
  2. Add it at the end of Handlers array in the DispatchIOCTL function
  3. Add the IOCTL number to the Ctls::KbCtlIndices enum at the CtlTypes.h to the same position as in the Handlers array in the IOCTLHandlers.cpp
  4. Call it from usermode in User-Bridge.cpp using the KbSendRequest function
  5. Enjoy the Ring0!

Awesome Work! and Afterwords...

Thank you for reading! This project has a much more features that I describe here and I have big plans for it.

But I need your help. I need contributors, testers and helpers for the deep kernel stuff.

If you are interested in it and Ring0-skilled enough to write a good, self-described and SDV-passing code, you're very welcome to become part of this project.

What is in the plans:

  • GUI platform for the runtime kernel researching and scripting engine
  • Code injectors
  • Instrumentation callbacks support
  • PTE-based direct memory manipulation and usermode exposing of the kernel memory
  • ObRegisterCallbacks bypass
  • Wiki-page with documentation
  • EV and Microsoft Hardware Center signing and publishing it to everyone
  • HLK tests passing, WHQL certification (it would be great!)

So, you're welcome to become a contributor! If you are interested, please leave a note in the comments section below.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)