Thanks to visit codestin.com
Credit goes to github.com

Skip to content

tobiaskocur/aegis

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Aegis

Kernel-mode process protection driver using ObRegisterCallbacks to strip memory access permissions from handles. Basic implementation that hooks handle creation and removes PROCESS_VM_READ/WRITE/OPERATION from unauthorized access attempts.

Demo Screenshot

Demo GIF

Note: If the GIF doesn't animate, view it directly on Imgur

Overview

Driver registers OB callbacks at altitude 321123 and intercepts OB_OPERATION_HANDLE_CREATE and OB_OPERATION_HANDLE_DUPLICATE operations. When a process tries to open a handle to a protected target, we check the source process name against a whitelist (explorer.exe, csrss.exe, svchost.exe, MsMpEng.exe, RuntimeBroker.exe) and strip access rights if it's not allowed.

Current implementation is name-based matching using PsGetProcessImageFileName (undocumented API). Protection list is managed via a linked list with spinlock synchronization. Worker thread exists but refresh logic is not implemented yet.

User-mode component provides ImGui GUI for adding processes to the protection list. Communication happens through IOCTL codes over \\Device\\Aegis symlink using METHOD_BUFFERED transfer. The driver creates a symlink from \\DosDevices\\Aegis to \\Device\\Aegis so user-mode can access it via \\\\.\\Aegis.

Architecture

Kernel (km/):

  • driver.c - DriverEntry, OB registration, IOCTL dispatcher, device/symlink creation
  • protect_manager/ - Process list management, worker thread (refresh logic pending)

User-mode (um/):

  • client.cpp - Main loop, ImGui rendering, window management
  • gui/ - DirectX 11 + ImGui setup, custom fonts (Gilroy)
  • service_manager/ - SCM integration for driver loading/unloading
  • ioctl_manager/ - DeviceIoControl wrapper for driver communication

Technical Details

Kernel-Mode Implementation

  • OB Callbacks: Registered with altitude 321123 (3rd party standard is 321000+, set higher to avoid collisions)
  • Process Identification: Uses PsGetProcessImageFileName - undocumented API that returns just the .exe filename (max 15 chars)
  • Thread Safety: KSPIN_LOCK operations at DISPATCH_LEVEL (IRQL 2) to prevent race conditions
  • Memory Management:
    • NonPagedPool for protect_manager (always in RAM, faster access, limited pool)
    • PagedPool for individual process structures (can be paged out)
  • Worker Thread: System thread with KEVENT for async processing (currently stub)
  • IOCTL: METHOD_BUFFERED for safe buffer handling, device type 0x8000 (3rd party range)

User-Mode Implementation

  • GUI: ImGui with DirectX 11 swapchain, custom styling
  • Driver Loading: Service Control Manager API, creates service "AegisDriver" with SERVICE_DEMAND_START
  • Communication: DeviceIoControl with IOCTL codes defined in shared ioctl.h
  • Error Handling: Basic error display via MessageBox

Code Examples

OB Callback Registration:

// Register OB callbacks for handle creation/duplication
OB_CALLBACK_REGISTRATION ob_registration = { 0 };
OB_OPERATION_REGISTRATION op_registration = { 0 };
RtlSecureZeroMemory(&ob_registration, sizeof(ob_registration));
RtlSecureZeroMemory(&op_registration, sizeof(op_registration));

op_registration.ObjectType = PsProcessType;
op_registration.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
// we care about duplication because smart hackers or malware, often do not create handles 
// but duplicate current ones to not arouse suspicion
op_registration.PreOperation = pre_open_process;

RtlInitUnicodeString(&ob_registration.Altitude, L"321123"); 
// 321000 is standard for 3rd party drivers, we set it a bit higher to not have collision

status = ObRegisterCallbacks(&ob_registration, &reg_handle);

Handle Access Stripping:

OB_PREOP_CALLBACK_STATUS pre_open_process(PVOID reg_context, POB_PRE_OPERATION_INFORMATION info)
{
    if (info->ObjectType == *PsProcessType) {
        PEPROCESS proc = (PEPROCESS)info->Object;
        char* process_name = (char*)PsGetProcessImageFileName(proc);
        
        // Lock list at DISPATCH_LEVEL to prevent BSOD
        KIRQL irql;
        KeAcquireSpinLock(&g_protect_manager->list_lock, &irql);
        
        // Check if process is in protection list
        for (entry = g_protect_manager->process_list.Flink; 
             entry != &g_protect_manager->process_list; 
             entry = entry->Flink) {
            pprocess proces = CONTAINING_RECORD(entry, process, entry);
            if (strcmp(proces->path, process_name) == 0) {
                // Check source process - allow system processes
                PEPROCESS source_process = IoGetCurrentProcess();
                char* source_name = (char*)PsGetProcessImageFileName(source_process);
                if (strcmp(source_name, "explorer.exe") == 0 || 
                    strcmp(source_name, "csrss.exe") == 0 || ...) {
                    KeReleaseSpinLock(&g_protect_manager->list_lock, irql);
                    return OB_PREOP_SUCCESS;
                }
                
                // Strip VM_READ, VM_WRITE, VM_OPERATION (0x1, 0x10, 0x20)
                // We don't set to 0 because it can cause bugs
                info->Parameters->CreateHandleInformation.DesiredAccess &= ~(0x1 | 0x10 | 0x20);
                KeReleaseSpinLock(&g_protect_manager->list_lock, irql);
                return OB_PREOP_SUCCESS;
            }
        }
        KeReleaseSpinLock(&g_protect_manager->list_lock, irql);
    }
    return OB_PREOP_SUCCESS;
}

Process List Management:

NTSTATUS protect_manager_protect_process(pprotect_manager self, pprocess process)
{
    // KIRQL (Kernel Interrupt Request Level) 0-31 tells CPU to not disturb
    // 0 = PASSIVE_LEVEL, 2 = DISPATCH_LEVEL, 31 = HIGH_LEVEL
    KIRQL irql;
    
    // Acquire spinlock, raises IRQL to DISPATCH_LEVEL (2)
    // Locks the list so it doesn't change while we edit
    KeAcquireSpinLock(&self->list_lock, &irql);
    InsertTailList(&self->process_list, &process->entry);
    KeReleaseSpinLock(&self->list_lock, irql);
    
    // Signal worker thread to refresh
    self->refresh_needed = TRUE;
    KeSetEvent(&self->worker_event, 0, FALSE); // FALSE = auto revert IRQL
    return STATUS_SUCCESS;
}

User-Mode IOCTL Communication:

bool ioctl_manager::check(std::string name)
{
    // Open driver device via symlink \\\\.\\Aegis -> \\Device\\Aegis
    this->driver_object = CreateFile(this->path, GENERIC_READ | GENERIC_WRITE, 
                                      0, NULL, OPEN_EXISTING, 0, NULL);
    
    // Send process name to driver via IOCTL
    result = DeviceIoControl(this->driver_object, IOCTL_PROTECT_PID, 
                            (LPVOID)data.c_str(), data.size() + 1, 
                            NULL, 0, &bytes_returned, NULL);
    
    CloseHandle(this->driver_object); // don't forget to close handles
    return result;
}

All code is thoroughly commented with explanations of kernel concepts, IRQL levels, memory management, and potential pitfalls. Feel free to check out the source - it's educational and well-documented.

Build Requirements

  • Visual Studio with C++ workload
  • Windows Driver Kit (WDK)
  • Test signing enabled (bcdedit /set testsigning on)

Build outputs: km.sys (driver) and um.exe (user-mode app).

Known Issues / Limitations

  • No kernel-mode protection - Any KM driver can bypass this by directly accessing EPROCESS
  • Name-based matching only - Process name spoofing is possible, no PID validation
  • Hardcoded whitelist - System process list is incomplete and static
  • Handle duplication - Only handles OB_OPERATION_HANDLE_CREATE, duplication attacks possible
  • Worker thread incomplete - Refresh logic in protect_manager.c is empty
  • Deprecated APIs - Uses ExAllocatePool instead of ExAllocatePool2

TODO

  • DKOM implementation - Process hiding via unlinking from PsActiveProcessHead, driver hiding from PsLoadedModuleList
  • Thread protection - Extend OB callbacks to protect thread handles
  • PID-based protection - Add process ID validation instead of relying solely on names
  • Unprotect functionality - Add IOCTL to remove processes from protection list
  • Complete worker thread - Implement actual refresh logic for process list updates

Disclaimer

Educational purposes only. Uses undocumented APIs that may break with Windows updates. Requires test signing or proper code signing to run.

Releases

No releases published

Packages

No packages published