Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ Tested from **Windows 10 1607** to **Windows 11 25H2 26200.6899** :heavy_check_m

## Parameters:

--copy-header to enable the copy driver header option by commandline
--free to automatically unmap the allocated memory
--indPages to map in allocated independent pages
--PassAllocationPtr to pass allocation ptr as first param
[PDB offset based build only]:
--copy-header to enable the copy driver header option by commandline
--free to automatically unmap the allocated memory
--indPages to map in allocated independent pages
--PassAllocationPtr to pass allocation ptr as first param
--url "<http(s)://...>" to map a driver from URL directly from memory (no .sys file written to disk)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new --url "<http(s)://...>" documentation implies that mapping a driver from an http:// URL is supported, but the implementation (kdmUtils::ReadUrlToMemory) actually allows plain HTTP for downloading the kernel driver image. Fetching kernel-mode code over unencrypted HTTP lets a network or DNS attacker tamper with the driver binary in transit and gain arbitrary kernel code execution on systems using --url with http:// endpoints. This feature should be documented and implemented as HTTPS-only (rejecting or strongly warning against http://), so all driver downloads are integrity-protected in transit.

Suggested change
--url "<http(s)://...>" to map a driver from URL directly from memory (no .sys file written to disk)
--url "<https://...>" to map a driver from an HTTPS URL directly from memory (no .sys file written to disk; plain http:// URLs are not supported for security reasons)

Copilot uses AI. Check for mistakes.
[PDB offset based build only]:
--offsetsPath "FilePath" to include your own offsets file path (by default .\offsets.ini)(if FilePath contains spaces, it must be enclosed in quotation marks)
--dontUpdateOffsets to execute without updating the offsets file (warning: you have to be sure that the offsets are not outdated to your current windows build, or you risk a potential BSOD)

Expand Down
3 changes: 2 additions & 1 deletion kdmapper/include/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ namespace kdmUtils
{
std::wstring GetFullTempPath();
bool ReadFileToMemory(const std::wstring& file_path, std::vector<BYTE>* out_buffer);
bool ReadUrlToMemory(const std::wstring& url, std::vector<BYTE>* out_buffer);
bool CreateFileFromMemory(const std::wstring& desired_file_path, const char* address, size_t size);
uint64_t GetKernelModuleAddress(const std::string& module_name);
BOOLEAN bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask);
uintptr_t FindPattern(uintptr_t dwAddress, uintptr_t dwLen, BYTE* bMask, const char* szMask);
PVOID FindSection(const char* sectionName, uintptr_t modulePtr, PULONG size);
std::wstring GetCurrentAppFolder();
}
}
137 changes: 83 additions & 54 deletions kdmapper/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,26 +97,27 @@ void PauseIfParentIsExplorer() {
}
}

void help() {
kdmLog(L"\r\n\r\n[!] Incorrect Usage!" << std::endl);
kdmLog(L"[+] Usage: kdmapper.exe [--free][--indPages][--PassAllocationPtr][--copy-header]");
#ifdef PDB_OFFSETS
kdmLog(L"[--dontUpdateOffsets [--offsetsPath \"FilePath\"]]");
#endif
kdmLog(L" driver" << std::endl);
PauseIfParentIsExplorer();
}

int wmain(const int argc, wchar_t** argv) {
SetUnhandledExceptionFilter(SimplestCrashHandler);
void help() {
kdmLog(L"\r\n\r\n[!] Incorrect Usage!" << std::endl);
kdmLog(L"[+] Usage: kdmapper.exe [--free][--indPages][--PassAllocationPtr][--copy-header][--url <http(s)://...>]");

#ifdef PDB_OFFSETS
kdmLog(L"[--dontUpdateOffsets [--offsetsPath \"FilePath\"]]");
#endif

kdmLog(L" [driver.sys]" << std::endl);

PauseIfParentIsExplorer();
}

int wmain(const int argc, wchar_t** argv) {
SetUnhandledExceptionFilter(SimplestCrashHandler);

bool free = paramExists(argc, argv, L"free") > 0;
bool indPagesMode = paramExists(argc, argv, L"indPages") > 0;
bool passAllocationPtr = paramExists(argc, argv, L"PassAllocationPtr") > 0;
bool copyHeader = paramExists(argc, argv, L"copy-header") > 0;
bool passAllocationPtr = paramExists(argc, argv, L"PassAllocationPtr") > 0;
bool copyHeader = paramExists(argc, argv, L"copy-header") > 0;
int urlParamIdx = paramExists(argc, argv, L"url");

if (free) {
kdmLog(L"[+] Free memory after driver execution enabled" << std::endl);
Expand Down Expand Up @@ -149,27 +150,41 @@ int wmain(const int argc, wchar_t** argv) {
offsetFilePath = argv[FilePathParamIdx + 1];
kdmLog("[+] Setting Offsets File Path To: " << offsetFilePath << std::endl);
}
#endif

int drvIndex = -1;
for (int i = 1; i < argc; i++) {
if (std::filesystem::path(argv[i]).extension().string().compare(".sys") == 0) {
drvIndex = i;
break;
}
}

if (drvIndex <= 0) {
help();
return -1;
}

const std::wstring driver_path = argv[drvIndex];

if (!std::filesystem::exists(driver_path)) {
kdmLog(L"[-] File " << driver_path << L" doesn't exist" << std::endl);
PauseIfParentIsExplorer();
return -1;
#endif

std::wstring driver_path;
std::wstring driver_url;

if (urlParamIdx > 0) {
if (urlParamIdx + 1 >= argc) {
kdmLog(L"[-] Missing value for --url" << std::endl);
help();
return -1;
}

driver_url = argv[urlParamIdx + 1];
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When --url is specified but the URL value is empty (e.g., kdmapper.exe --url ""), driver_url.empty() will be true and driver_path will also be empty (never set in the urlParamIdx > 0 branch). This causes the code to fall through to the file existence check, producing a confusing error message ("File doesn't exist") instead of indicating the URL is invalid. Consider adding a validation for an empty URL value immediately after line 165, similar to how the missing value is checked at line 159.

Suggested change
driver_url = argv[urlParamIdx + 1];
driver_url = argv[urlParamIdx + 1];
if (driver_url.empty()) {
kdmLog(L"[-] Empty value for --url" << std::endl);
help();
return -1;
}

Copilot uses AI. Check for mistakes.
}
else {
int drvIndex = -1;
for (int i = 1; i < argc; i++) {
if (std::filesystem::path(argv[i]).extension().string().compare(".sys") == 0) {
drvIndex = i;
break;
}
}

if (drvIndex <= 0) {
help();
return -1;
}

driver_path = argv[drvIndex];
}

if (driver_url.empty() && !std::filesystem::exists(driver_path)) {
kdmLog(L"[-] File " << driver_path << L" doesn't exist" << std::endl);
PauseIfParentIsExplorer();
return -1;
}

#ifdef PDB_OFFSETS
Expand All @@ -183,28 +198,42 @@ int wmain(const int argc, wchar_t** argv) {
if (!NT_SUCCESS(intel_driver::Load())) {
PauseIfParentIsExplorer();
return -1;
}

std::vector<uint8_t> raw_image = { 0 };
if (!kdmUtils::ReadFileToMemory(driver_path, &raw_image)) {
kdmLog(L"[-] Failed to read image to memory" << std::endl);
intel_driver::Unload();
PauseIfParentIsExplorer();
return -1;
}

std::vector<uint8_t> raw_image = { 0 };
if (!driver_url.empty()) {
kdmLog(L"[+] Downloading image from URL: " << driver_url << std::endl);
if (!kdmUtils::ReadUrlToMemory(driver_url, &raw_image)) {
kdmLog(L"[-] Failed to download image to memory" << std::endl);
intel_driver::Unload();
PauseIfParentIsExplorer();
return -1;
}
}
else if (!kdmUtils::ReadFileToMemory(driver_path, &raw_image)) {
kdmLog(L"[-] Failed to read image to memory" << std::endl);
intel_driver::Unload();
PauseIfParentIsExplorer();
return -1;
}

kdmapper::AllocationMode mode = kdmapper::AllocationMode::AllocatePool;

if (indPagesMode) {
mode = kdmapper::AllocationMode::AllocateIndependentPages;
}

NTSTATUS exitCode = 0;
if (!kdmapper::MapDriver(raw_image.data(), 0, 0, free, !copyHeader, mode, passAllocationPtr, callbackExample, &exitCode)) {
kdmLog(L"[-] Failed to map " << driver_path << std::endl);
intel_driver::Unload();
PauseIfParentIsExplorer();
return -1;

NTSTATUS exitCode = 0;
if (!kdmapper::MapDriver(raw_image.data(), 0, 0, free, !copyHeader, mode, passAllocationPtr, callbackExample, &exitCode)) {
if (!driver_url.empty()) {
kdmLog(L"[-] Failed to map image from URL " << driver_url << std::endl);
}
else {
kdmLog(L"[-] Failed to map " << driver_path << std::endl);
}
intel_driver::Unload();
PauseIfParentIsExplorer();
return -1;
}

if (!NT_SUCCESS(intel_driver::Unload())) {
Expand All @@ -215,4 +244,4 @@ int wmain(const int argc, wchar_t** argv) {

}

#endif
#endif
152 changes: 137 additions & 15 deletions kdmapper/utils.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
#include "utils.hpp"
#include <Windows.h>
#include <iostream>
#include <vector>
#include <fstream>

#include "nt.hpp"

#include "utils.hpp"
#include <Windows.h>
#include <winhttp.h>
#include <iostream>
#include <vector>
#include <fstream>

#include "nt.hpp"

#pragma comment(lib, "winhttp.lib")

std::wstring kdmUtils::GetFullTempPath() {
wchar_t temp_directory[MAX_PATH + 1] = { 0 };
const uint32_t get_temp_path_ret = GetTempPathW(sizeof(temp_directory) / 2, temp_directory);
Expand All @@ -19,19 +22,138 @@ std::wstring kdmUtils::GetFullTempPath() {
return std::wstring(temp_directory);
}

bool kdmUtils::ReadFileToMemory(const std::wstring& file_path, std::vector<BYTE>* out_buffer) {
std::ifstream file_ifstream(file_path, std::ios::binary);
bool kdmUtils::ReadFileToMemory(const std::wstring& file_path, std::vector<BYTE>* out_buffer) {
std::ifstream file_ifstream(file_path, std::ios::binary);

if (!file_ifstream)
return false;

out_buffer->assign((std::istreambuf_iterator<char>(file_ifstream)), std::istreambuf_iterator<char>());
file_ifstream.close();

return true;
}

bool kdmUtils::CreateFileFromMemory(const std::wstring& desired_file_path, const char* address, size_t size) {
return true;
}

bool kdmUtils::ReadUrlToMemory(const std::wstring& url, std::vector<BYTE>* out_buffer) {
out_buffer->clear();
DWORD statusCode = 0;
DWORD statusCodeSize = sizeof(statusCode);

URL_COMPONENTS urlComponents = {};
urlComponents.dwStructSize = sizeof(urlComponents);

wchar_t hostName[256] = {};
wchar_t urlPath[2048] = {};
wchar_t extraInfo[1024] = {};

urlComponents.lpszHostName = hostName;
urlComponents.dwHostNameLength = _countof(hostName);
urlComponents.lpszUrlPath = urlPath;
urlComponents.dwUrlPathLength = _countof(urlPath);
urlComponents.lpszExtraInfo = extraInfo;
urlComponents.dwExtraInfoLength = _countof(extraInfo);

if (!WinHttpCrackUrl(url.c_str(), static_cast<DWORD>(url.size()), 0, &urlComponents)) {
kdmLog(L"[-] Invalid URL: " << url << std::endl);
return false;
}

std::wstring host(hostName, urlComponents.dwHostNameLength);
std::wstring path(urlPath, urlComponents.dwUrlPathLength);
std::wstring query(extraInfo, urlComponents.dwExtraInfoLength);
std::wstring object = path + query;
if (object.empty()) {
object = L"/";
}

if (urlComponents.nScheme != INTERNET_SCHEME_HTTP && urlComponents.nScheme != INTERNET_SCHEME_HTTPS) {
kdmLog(L"[-] URL must use http or https" << std::endl);
return false;
}

const bool useHttps = urlComponents.nScheme == INTERNET_SCHEME_HTTPS;
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function allows downloading kernel drivers over plaintext HTTP, which is vulnerable to man-in-the-middle attacks where a malicious driver binary could be substituted in transit. Since this tool maps drivers into kernel space with full system privileges, consider either restricting to HTTPS only, or at minimum logging a warning when HTTP (non-HTTPS) is used so the user is aware of the risk.

Suggested change
const bool useHttps = urlComponents.nScheme == INTERNET_SCHEME_HTTPS;
const bool useHttps = urlComponents.nScheme == INTERNET_SCHEME_HTTPS;
if (!useHttps) {
kdmLog(L"[!] Warning: Downloading driver over insecure HTTP; this is vulnerable to man-in-the-middle attacks." << std::endl);
}

Copilot uses AI. Check for mistakes.

HINTERNET hSession = WinHttpOpen(L"kdmapper/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) {
kdmLog(L"[-] WinHttpOpen failed: " << GetLastError() << std::endl);
return false;
}

HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), urlComponents.nPort, 0);
if (!hConnect) {
kdmLog(L"[-] WinHttpConnect failed: " << GetLastError() << std::endl);
WinHttpCloseHandle(hSession);
return false;
}

HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", object.c_str(),
nullptr, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
useHttps ? WINHTTP_FLAG_SECURE : 0);
if (!hRequest) {
kdmLog(L"[-] WinHttpOpenRequest failed: " << GetLastError() << std::endl);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return false;
}

bool success = false;

if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
kdmLog(L"[-] WinHttpSendRequest failed: " << GetLastError() << std::endl);
goto cleanup;
}

if (!WinHttpReceiveResponse(hRequest, nullptr)) {
kdmLog(L"[-] WinHttpReceiveResponse failed: " << GetLastError() << std::endl);
goto cleanup;
}

if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &statusCodeSize, WINHTTP_NO_HEADER_INDEX)) {
kdmLog(L"[-] WinHttpQueryHeaders failed: " << GetLastError() << std::endl);
goto cleanup;
}

if (statusCode < 200 || statusCode >= 300) {
kdmLog(L"[-] HTTP request failed with status " << statusCode << std::endl);
goto cleanup;
}

for (;;) {
DWORD availableSize = 0;
if (!WinHttpQueryDataAvailable(hRequest, &availableSize)) {
kdmLog(L"[-] WinHttpQueryDataAvailable failed: " << GetLastError() << std::endl);
goto cleanup;
}

if (availableSize == 0) {
break;
}

const size_t previousSize = out_buffer->size();
out_buffer->resize(previousSize + availableSize);

DWORD downloadedSize = 0;
if (!WinHttpReadData(hRequest, out_buffer->data() + previousSize, availableSize, &downloadedSize)) {
kdmLog(L"[-] WinHttpReadData failed: " << GetLastError() << std::endl);
goto cleanup;
}

out_buffer->resize(previousSize + downloadedSize);
}

success = !out_buffer->empty();

cleanup:
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the download loop encounters a failure (e.g., WinHttpQueryDataAvailable or WinHttpReadData fails), the function returns false but out_buffer may still contain partial data. While the caller currently checks the return value and doesn't use the buffer on failure, it would be safer to clear out_buffer in the cleanup path when success is false, to avoid accidentally using partial/corrupt data if the calling code changes in the future.

Suggested change
WinHttpCloseHandle(hSession);
WinHttpCloseHandle(hSession);
if (!success && out_buffer) {
out_buffer->clear();
}

Copilot uses AI. Check for mistakes.
return success;
}

bool kdmUtils::CreateFileFromMemory(const std::wstring& desired_file_path, const char* address, size_t size) {
std::ofstream file_ofstream(desired_file_path.c_str(), std::ios_base::out | std::ios_base::binary);

if (!file_ofstream.write(address, size)) {
Expand Down Expand Up @@ -123,4 +245,4 @@ std::wstring kdmUtils::GetCurrentAppFolder() {
GetModuleFileNameW(NULL, buffer, 1024);
std::wstring::size_type pos = std::wstring(buffer).find_last_of(L"\\/");
return std::wstring(buffer).substr(0, pos);
}
}
Loading