6461 lines
168 KiB
C
6461 lines
168 KiB
C
/*
|
|
* Process Hacker -
|
|
* native wrapper and support functions
|
|
*
|
|
* Copyright (C) 2009-2016 wj32
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <ph.h>
|
|
#include <mapimg.h>
|
|
#include <kphuser.h>
|
|
#include <lsasup.h>
|
|
#include <apiimport.h>
|
|
|
|
#define PH_DEVICE_PREFIX_LENGTH 64
|
|
#define PH_DEVICE_MUP_PREFIX_MAX_COUNT 16
|
|
|
|
typedef BOOLEAN (NTAPI *PPHP_ENUM_PROCESS_MODULES_CALLBACK)(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PLDR_DATA_TABLE_ENTRY Entry,
|
|
_In_ PVOID AddressOfEntry,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
);
|
|
|
|
typedef BOOLEAN (NTAPI *PPHP_ENUM_PROCESS_MODULES32_CALLBACK)(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PLDR_DATA_TABLE_ENTRY32 Entry,
|
|
_In_ ULONG AddressOfEntry,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
);
|
|
|
|
static PH_INITONCE PhDevicePrefixesInitOnce = PH_INITONCE_INIT;
|
|
|
|
static UNICODE_STRING PhDevicePrefixes[26];
|
|
static PH_QUEUED_LOCK PhDevicePrefixesLock = PH_QUEUED_LOCK_INIT;
|
|
|
|
static PPH_STRING PhDeviceMupPrefixes[PH_DEVICE_MUP_PREFIX_MAX_COUNT] = { 0 };
|
|
static ULONG PhDeviceMupPrefixesCount = 0;
|
|
static PH_QUEUED_LOCK PhDeviceMupPrefixesLock = PH_QUEUED_LOCK_INIT;
|
|
|
|
static PH_INITONCE PhPredefineKeyInitOnce = PH_INITONCE_INIT;
|
|
static UNICODE_STRING PhPredefineKeyNames[PH_KEY_MAXIMUM_PREDEFINE] =
|
|
{
|
|
RTL_CONSTANT_STRING(L"\\Registry\\Machine"),
|
|
RTL_CONSTANT_STRING(L"\\Registry\\User"),
|
|
RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Classes"),
|
|
{ 0, 0, NULL }
|
|
};
|
|
static HANDLE PhPredefineKeyHandles[PH_KEY_MAXIMUM_PREDEFINE] = { 0 };
|
|
|
|
/**
|
|
* Queries information about the token of the current process.
|
|
*/
|
|
PH_TOKEN_ATTRIBUTES PhGetOwnTokenAttributes(
|
|
VOID
|
|
)
|
|
{
|
|
static PH_INITONCE initOnce = PH_INITONCE_INIT;
|
|
static PH_TOKEN_ATTRIBUTES attributes;
|
|
|
|
if (PhBeginInitOnce(&initOnce))
|
|
{
|
|
if (NT_SUCCESS(NtOpenProcessToken(
|
|
NtCurrentProcess(),
|
|
TOKEN_QUERY,
|
|
&attributes.TokenHandle
|
|
)))
|
|
{
|
|
BOOLEAN elevated = TRUE;
|
|
TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeFull;
|
|
|
|
if (WINDOWS_HAS_UAC)
|
|
{
|
|
PhGetTokenIsElevated(attributes.TokenHandle, &elevated);
|
|
PhGetTokenElevationType(attributes.TokenHandle, &elevationType);
|
|
}
|
|
|
|
attributes.Elevated = elevated;
|
|
attributes.ElevationType = elevationType;
|
|
}
|
|
|
|
PhEndInitOnce(&initOnce);
|
|
}
|
|
|
|
return attributes;
|
|
}
|
|
|
|
/**
|
|
* Opens a process.
|
|
*
|
|
* \param ProcessHandle A variable which receives a handle to the process.
|
|
* \param DesiredAccess The desired access to the process.
|
|
* \param ProcessId The ID of the process.
|
|
*/
|
|
NTSTATUS PhOpenProcess(
|
|
_Out_ PHANDLE ProcessHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_ HANDLE ProcessId
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
CLIENT_ID clientId;
|
|
|
|
clientId.UniqueProcess = ProcessId;
|
|
clientId.UniqueThread = NULL;
|
|
|
|
if (KphIsVerified() && (DesiredAccess & KPH_PROCESS_READ_ACCESS) == DesiredAccess)
|
|
{
|
|
status = KphOpenProcess(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
&clientId
|
|
);
|
|
}
|
|
else
|
|
{
|
|
InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL);
|
|
status = NtOpenProcess(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
&objectAttributes,
|
|
&clientId
|
|
);
|
|
|
|
if (status == STATUS_ACCESS_DENIED && KphIsVerified())
|
|
{
|
|
status = KphOpenProcess(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
&clientId
|
|
);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/** Limited API for untrusted/external code. */
|
|
NTSTATUS PhOpenProcessPublic(
|
|
_Out_ PHANDLE ProcessHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_ HANDLE ProcessId
|
|
)
|
|
{
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
CLIENT_ID clientId;
|
|
|
|
InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL);
|
|
clientId.UniqueProcess = ProcessId;
|
|
clientId.UniqueThread = NULL;
|
|
|
|
return NtOpenProcess(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
&objectAttributes,
|
|
&clientId
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Opens a thread.
|
|
*
|
|
* \param ThreadHandle A variable which receives a handle to the thread.
|
|
* \param DesiredAccess The desired access to the thread.
|
|
* \param ThreadId The ID of the thread.
|
|
*/
|
|
NTSTATUS PhOpenThread(
|
|
_Out_ PHANDLE ThreadHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_ HANDLE ThreadId
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
CLIENT_ID clientId;
|
|
|
|
clientId.UniqueProcess = NULL;
|
|
clientId.UniqueThread = ThreadId;
|
|
|
|
if (KphIsVerified() && (DesiredAccess & KPH_THREAD_READ_ACCESS) == DesiredAccess)
|
|
{
|
|
status = KphOpenThread(
|
|
ThreadHandle,
|
|
DesiredAccess,
|
|
&clientId
|
|
);
|
|
}
|
|
else
|
|
{
|
|
InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL);
|
|
status = NtOpenThread(
|
|
ThreadHandle,
|
|
DesiredAccess,
|
|
&objectAttributes,
|
|
&clientId
|
|
);
|
|
|
|
if (status == STATUS_ACCESS_DENIED && KphIsVerified())
|
|
{
|
|
status = KphOpenThread(
|
|
ThreadHandle,
|
|
DesiredAccess,
|
|
&clientId
|
|
);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/** Limited API for untrusted/external code. */
|
|
NTSTATUS PhOpenThreadPublic(
|
|
_Out_ PHANDLE ThreadHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_ HANDLE ThreadId
|
|
)
|
|
{
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
CLIENT_ID clientId;
|
|
|
|
clientId.UniqueProcess = NULL;
|
|
clientId.UniqueThread = ThreadId;
|
|
|
|
InitializeObjectAttributes(&objectAttributes, NULL, 0, NULL, NULL);
|
|
|
|
return NtOpenThread(
|
|
ThreadHandle,
|
|
DesiredAccess,
|
|
&objectAttributes,
|
|
&clientId
|
|
);
|
|
}
|
|
|
|
NTSTATUS PhOpenThreadProcess(
|
|
_In_ HANDLE ThreadHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_Out_ PHANDLE ProcessHandle
|
|
)
|
|
{
|
|
if (KphIsConnected())
|
|
{
|
|
return KphOpenThreadProcess(
|
|
ThreadHandle,
|
|
DesiredAccess,
|
|
ProcessHandle
|
|
);
|
|
}
|
|
else
|
|
{
|
|
NTSTATUS status;
|
|
THREAD_BASIC_INFORMATION basicInfo;
|
|
|
|
if (!NT_SUCCESS(status = PhGetThreadBasicInformation(
|
|
ThreadHandle,
|
|
&basicInfo
|
|
)))
|
|
return status;
|
|
|
|
return PhOpenProcess(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
basicInfo.ClientId.UniqueProcess
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens a process token.
|
|
*
|
|
* \param ProcessHandle A handle to a process.
|
|
* \param DesiredAccess The desired access to the token.
|
|
* \param TokenHandle A variable which receives a handle to the token.
|
|
*/
|
|
NTSTATUS PhOpenProcessToken(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_Out_ PHANDLE TokenHandle
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (KphIsVerified() && (DesiredAccess & KPH_TOKEN_READ_ACCESS) == DesiredAccess)
|
|
{
|
|
status = KphOpenProcessToken(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
TokenHandle
|
|
);
|
|
}
|
|
else
|
|
{
|
|
status = NtOpenProcessToken(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
TokenHandle
|
|
);
|
|
|
|
if (status == STATUS_ACCESS_DENIED && KphIsVerified())
|
|
{
|
|
status = KphOpenProcessToken(
|
|
ProcessHandle,
|
|
DesiredAccess,
|
|
TokenHandle
|
|
);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetObjectSecurity(
|
|
_In_ HANDLE Handle,
|
|
_In_ SECURITY_INFORMATION SecurityInformation,
|
|
_Out_ PSECURITY_DESCRIPTOR *SecurityDescriptor
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
ULONG bufferSize;
|
|
PVOID buffer;
|
|
|
|
bufferSize = 0x100;
|
|
buffer = PhAllocate(bufferSize);
|
|
// This is required (especially for File objects) because some drivers don't seem to handle
|
|
// QuerySecurity properly.
|
|
memset(buffer, 0, bufferSize);
|
|
|
|
status = NtQuerySecurityObject(
|
|
Handle,
|
|
SecurityInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
memset(buffer, 0, bufferSize);
|
|
|
|
status = NtQuerySecurityObject(
|
|
Handle,
|
|
SecurityInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
*SecurityDescriptor = (PSECURITY_DESCRIPTOR)buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhSetObjectSecurity(
|
|
_In_ HANDLE Handle,
|
|
_In_ SECURITY_INFORMATION SecurityInformation,
|
|
_In_ PSECURITY_DESCRIPTOR SecurityDescriptor
|
|
)
|
|
{
|
|
return NtSetSecurityObject(
|
|
Handle,
|
|
SecurityInformation,
|
|
SecurityDescriptor
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Terminates a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have PROCESS_TERMINATE access.
|
|
* \param ExitStatus A status value that indicates why the process is being terminated.
|
|
*/
|
|
NTSTATUS PhTerminateProcess(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ NTSTATUS ExitStatus
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (KphIsVerified())
|
|
{
|
|
status = KphTerminateProcess(
|
|
ProcessHandle,
|
|
ExitStatus
|
|
);
|
|
|
|
if (status != STATUS_NOT_SUPPORTED)
|
|
return status;
|
|
}
|
|
|
|
return NtTerminateProcess(
|
|
ProcessHandle,
|
|
ExitStatus
|
|
);
|
|
}
|
|
|
|
/** Limited API for untrusted/external code. */
|
|
NTSTATUS PhTerminateProcessPublic(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ NTSTATUS ExitStatus
|
|
)
|
|
{
|
|
return NtTerminateProcess(
|
|
ProcessHandle,
|
|
ExitStatus
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Queries variable-sized information for a process. The function allocates a buffer to contain the
|
|
* information.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The access required depends on the information class
|
|
* specified.
|
|
* \param ProcessInformationClass The information class to retrieve.
|
|
* \param Buffer A variable which receives a pointer to a buffer containing the information. You
|
|
* must free the buffer using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhpQueryProcessVariableSize(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PROCESSINFOCLASS ProcessInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG returnLength = 0;
|
|
|
|
status = NtQueryInformationProcess(
|
|
ProcessHandle,
|
|
ProcessInformationClass,
|
|
NULL,
|
|
0,
|
|
&returnLength
|
|
);
|
|
|
|
if (status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL && status != STATUS_INFO_LENGTH_MISMATCH)
|
|
return status;
|
|
|
|
buffer = PhAllocate(returnLength);
|
|
status = NtQueryInformationProcess(
|
|
ProcessHandle,
|
|
ProcessInformationClass,
|
|
buffer,
|
|
returnLength,
|
|
&returnLength
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets the file name of the process' image.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION access.
|
|
* \param FileName A variable which receives a pointer to a string containing the file name. You
|
|
* must free the string using PhDereferenceObject() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetProcessImageFileName(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PPH_STRING *FileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PUNICODE_STRING fileName;
|
|
|
|
status = PhpQueryProcessVariableSize(
|
|
ProcessHandle,
|
|
ProcessImageFileName,
|
|
&fileName
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
*FileName = PhCreateStringFromUnicodeString(fileName);
|
|
PhFree(fileName);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets the Win32 file name of the process' image.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION access.
|
|
* \param FileName A variable which receives a pointer to a string containing the file name. You
|
|
* must free the string using PhDereferenceObject() when you no longer need it.
|
|
*
|
|
* \remarks This function is only available on Windows Vista and above.
|
|
*/
|
|
NTSTATUS PhGetProcessImageFileNameWin32(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PPH_STRING *FileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PUNICODE_STRING fileName;
|
|
|
|
status = PhpQueryProcessVariableSize(
|
|
ProcessHandle,
|
|
ProcessImageFileNameWin32,
|
|
&fileName
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
*FileName = PhCreateStringFromUnicodeString(fileName);
|
|
PhFree(fileName);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets a string stored in a process' parameters structure.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access.
|
|
* \param Offset The string to retrieve.
|
|
* \param String A variable which receives a pointer to the requested string. You must free the
|
|
* string using PhDereferenceObject() when you no longer need it.
|
|
*
|
|
* \retval STATUS_INVALID_PARAMETER_2 An invalid value was specified in the Offset parameter.
|
|
*/
|
|
NTSTATUS PhGetProcessPebString(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PH_PEB_OFFSET Offset,
|
|
_Out_ PPH_STRING *String
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PPH_STRING string;
|
|
ULONG offset;
|
|
|
|
#define PEB_OFFSET_CASE(Enum, Field) \
|
|
case Enum: offset = FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, Field); break; \
|
|
case Enum | PhpoWow64: offset = FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS32, Field); break
|
|
|
|
switch (Offset)
|
|
{
|
|
PEB_OFFSET_CASE(PhpoCurrentDirectory, CurrentDirectory);
|
|
PEB_OFFSET_CASE(PhpoDllPath, DllPath);
|
|
PEB_OFFSET_CASE(PhpoImagePathName, ImagePathName);
|
|
PEB_OFFSET_CASE(PhpoCommandLine, CommandLine);
|
|
PEB_OFFSET_CASE(PhpoWindowTitle, WindowTitle);
|
|
PEB_OFFSET_CASE(PhpoDesktopInfo, DesktopInfo);
|
|
PEB_OFFSET_CASE(PhpoShellInfo, ShellInfo);
|
|
PEB_OFFSET_CASE(PhpoRuntimeData, RuntimeData);
|
|
default:
|
|
return STATUS_INVALID_PARAMETER_2;
|
|
}
|
|
|
|
if (!(Offset & PhpoWow64))
|
|
{
|
|
PROCESS_BASIC_INFORMATION basicInfo;
|
|
PVOID processParameters;
|
|
UNICODE_STRING unicodeString;
|
|
|
|
// Get the PEB address.
|
|
if (!NT_SUCCESS(status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo)))
|
|
return status;
|
|
|
|
// Read the address of the process parameters.
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)),
|
|
&processParameters,
|
|
sizeof(PVOID),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
// Read the string structure.
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(processParameters, offset),
|
|
&unicodeString,
|
|
sizeof(UNICODE_STRING),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
string = PhCreateStringEx(NULL, unicodeString.Length);
|
|
|
|
// Read the string contents.
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
unicodeString.Buffer,
|
|
string->Buffer,
|
|
string->Length,
|
|
NULL
|
|
)))
|
|
{
|
|
PhDereferenceObject(string);
|
|
return status;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PVOID peb32;
|
|
ULONG processParameters32;
|
|
UNICODE_STRING32 unicodeString32;
|
|
|
|
if (!NT_SUCCESS(status = PhGetProcessPeb32(ProcessHandle, &peb32)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessParameters)),
|
|
&processParameters32,
|
|
sizeof(ULONG),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(processParameters32, offset),
|
|
&unicodeString32,
|
|
sizeof(UNICODE_STRING32),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
string = PhCreateStringEx(NULL, unicodeString32.Length);
|
|
|
|
// Read the string contents.
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
UlongToPtr(unicodeString32.Buffer),
|
|
string->Buffer,
|
|
string->Length,
|
|
NULL
|
|
)))
|
|
{
|
|
PhDereferenceObject(string);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
*String = string;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets a process' command line.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION. Before Windows 8.1, the handle must also have PROCESS_VM_READ
|
|
* access.
|
|
* \param String A variable which receives a pointer to a string containing the command line. You
|
|
* must free the string using PhDereferenceObject() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetProcessCommandLine(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PPH_STRING *CommandLine
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (WindowsVersion >= WINDOWS_8_1)
|
|
{
|
|
PUNICODE_STRING commandLine;
|
|
|
|
status = PhpQueryProcessVariableSize(
|
|
ProcessHandle,
|
|
ProcessCommandLineInformation,
|
|
&commandLine
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*CommandLine = PhCreateStringFromUnicodeString(commandLine);
|
|
PhFree(commandLine);
|
|
|
|
return status;
|
|
}
|
|
}
|
|
|
|
return PhGetProcessPebString(ProcessHandle, PhpoCommandLine, CommandLine);
|
|
}
|
|
|
|
/**
|
|
* Gets the window flags and window title of a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION. Before Windows 7 SP1, the handle must also have
|
|
* PROCESS_VM_READ access.
|
|
* \param WindowFlags A variable which receives the window flags.
|
|
* \param WindowTitle A variable which receives a pointer to the window title. You must free the
|
|
* string using PhDereferenceObject() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetProcessWindowTitle(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PULONG WindowFlags,
|
|
_Out_ PPH_STRING *WindowTitle
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
#ifdef _WIN64
|
|
BOOLEAN isWow64 = FALSE;
|
|
#endif
|
|
ULONG windowFlags;
|
|
|
|
if (WindowsVersion >= WINDOWS_7)
|
|
{
|
|
PPROCESS_WINDOW_INFORMATION windowInfo;
|
|
|
|
status = PhpQueryProcessVariableSize(
|
|
ProcessHandle,
|
|
ProcessWindowInformation,
|
|
&windowInfo
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*WindowFlags = windowInfo->WindowFlags;
|
|
*WindowTitle = PhCreateStringEx(windowInfo->WindowTitle, windowInfo->WindowTitleLength);
|
|
PhFree(windowInfo);
|
|
|
|
return status;
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
PhGetProcessIsWow64(ProcessHandle, &isWow64);
|
|
|
|
if (!isWow64)
|
|
#endif
|
|
{
|
|
PROCESS_BASIC_INFORMATION basicInfo;
|
|
PVOID processParameters;
|
|
|
|
// Get the PEB address.
|
|
if (!NT_SUCCESS(status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo)))
|
|
return status;
|
|
|
|
// Read the address of the process parameters.
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)),
|
|
&processParameters,
|
|
sizeof(PVOID),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
// Read the window flags.
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(processParameters, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, WindowFlags)),
|
|
&windowFlags,
|
|
sizeof(ULONG),
|
|
NULL
|
|
)))
|
|
return status;
|
|
}
|
|
#ifdef _WIN64
|
|
else
|
|
{
|
|
PVOID peb32;
|
|
ULONG processParameters32;
|
|
|
|
if (!NT_SUCCESS(status = PhGetProcessPeb32(ProcessHandle, &peb32)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessParameters)),
|
|
&processParameters32,
|
|
sizeof(ULONG),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(processParameters32, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS32, WindowFlags)),
|
|
&windowFlags,
|
|
sizeof(ULONG),
|
|
NULL
|
|
)))
|
|
return status;
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN64
|
|
status = PhGetProcessPebString(ProcessHandle, PhpoWindowTitle | (isWow64 ? PhpoWow64 : 0), WindowTitle);
|
|
#else
|
|
status = PhGetProcessPebString(ProcessHandle, PhpoWindowTitle, WindowTitle);
|
|
#endif
|
|
|
|
if (NT_SUCCESS(status))
|
|
*WindowFlags = windowFlags;
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetProcessDepStatus(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PULONG DepStatus
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
ULONG executeFlags;
|
|
ULONG depStatus;
|
|
|
|
if (!NT_SUCCESS(status = PhGetProcessExecuteFlags(
|
|
ProcessHandle,
|
|
&executeFlags
|
|
)))
|
|
return status;
|
|
|
|
// Check if execution of data pages is enabled.
|
|
if (executeFlags & MEM_EXECUTE_OPTION_ENABLE)
|
|
depStatus = 0;
|
|
else
|
|
depStatus = PH_PROCESS_DEP_ENABLED;
|
|
|
|
if (executeFlags & MEM_EXECUTE_OPTION_DISABLE_THUNK_EMULATION)
|
|
depStatus |= PH_PROCESS_DEP_ATL_THUNK_EMULATION_DISABLED;
|
|
if (executeFlags & MEM_EXECUTE_OPTION_PERMANENT)
|
|
depStatus |= PH_PROCESS_DEP_PERMANENT;
|
|
|
|
*DepStatus = depStatus;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets a process' environment block.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION and
|
|
* PROCESS_VM_READ access.
|
|
* \param Flags A combination of flags.
|
|
* \li \c PH_GET_PROCESS_ENVIRONMENT_WOW64 Retrieve the environment block from the WOW64 PEB.
|
|
* \param Environment A variable which will receive a pointer to the environment block copied from
|
|
* the process. You must free the block using PhFreePage() when you no longer need it.
|
|
* \param EnvironmentLength A variable which will receive the length of the environment block, in
|
|
* bytes.
|
|
*/
|
|
NTSTATUS PhGetProcessEnvironment(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ ULONG Flags,
|
|
_Out_ PVOID *Environment,
|
|
_Out_ PULONG EnvironmentLength
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID environmentRemote;
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
PVOID environment;
|
|
ULONG environmentLength;
|
|
|
|
if (!(Flags & PH_GET_PROCESS_ENVIRONMENT_WOW64))
|
|
{
|
|
PROCESS_BASIC_INFORMATION basicInfo;
|
|
PVOID processParameters;
|
|
|
|
status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)),
|
|
&processParameters,
|
|
sizeof(PVOID),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(processParameters, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS, Environment)),
|
|
&environmentRemote,
|
|
sizeof(PVOID),
|
|
NULL
|
|
)))
|
|
return status;
|
|
}
|
|
else
|
|
{
|
|
PVOID peb32;
|
|
ULONG processParameters32;
|
|
ULONG environmentRemote32;
|
|
|
|
status = PhGetProcessPeb32(ProcessHandle, &peb32);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(peb32, FIELD_OFFSET(PEB32, ProcessParameters)),
|
|
&processParameters32,
|
|
sizeof(ULONG),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(processParameters32, FIELD_OFFSET(RTL_USER_PROCESS_PARAMETERS32, Environment)),
|
|
&environmentRemote32,
|
|
sizeof(ULONG),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
environmentRemote = UlongToPtr(environmentRemote32);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status = NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
environmentRemote,
|
|
MemoryBasicInformation,
|
|
&mbi,
|
|
sizeof(MEMORY_BASIC_INFORMATION),
|
|
NULL
|
|
)))
|
|
return status;
|
|
|
|
environmentLength = (ULONG)(mbi.RegionSize -
|
|
((ULONG_PTR)environmentRemote - (ULONG_PTR)mbi.BaseAddress));
|
|
|
|
// Read in the entire region of memory.
|
|
|
|
environment = PhAllocatePage(environmentLength, NULL);
|
|
|
|
if (!environment)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
if (!NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
environmentRemote,
|
|
environment,
|
|
environmentLength,
|
|
NULL
|
|
)))
|
|
{
|
|
PhFreePage(environment);
|
|
return status;
|
|
}
|
|
|
|
*Environment = environment;
|
|
|
|
if (EnvironmentLength)
|
|
*EnvironmentLength = environmentLength;
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOLEAN PhEnumProcessEnvironmentVariables(
|
|
_In_ PVOID Environment,
|
|
_In_ ULONG EnvironmentLength,
|
|
_Inout_ PULONG EnumerationKey,
|
|
_Out_ PPH_ENVIRONMENT_VARIABLE Variable
|
|
)
|
|
{
|
|
ULONG length;
|
|
ULONG startIndex;
|
|
PWCHAR name;
|
|
ULONG nameLength;
|
|
PWCHAR value;
|
|
ULONG valueLength;
|
|
PWCHAR currentChar;
|
|
ULONG currentIndex;
|
|
|
|
length = EnvironmentLength / sizeof(WCHAR);
|
|
|
|
currentIndex = *EnumerationKey;
|
|
currentChar = (PWCHAR)Environment + currentIndex;
|
|
startIndex = currentIndex;
|
|
name = currentChar;
|
|
|
|
// Find the end of the name.
|
|
while (TRUE)
|
|
{
|
|
if (currentIndex >= length)
|
|
return FALSE;
|
|
if (*currentChar == '=')
|
|
break;
|
|
if (*currentChar == 0)
|
|
return FALSE; // no more variables
|
|
|
|
currentIndex++;
|
|
currentChar++;
|
|
}
|
|
|
|
nameLength = currentIndex - startIndex;
|
|
|
|
currentIndex++;
|
|
currentChar++;
|
|
startIndex = currentIndex;
|
|
value = currentChar;
|
|
|
|
// Find the end of the value.
|
|
while (TRUE)
|
|
{
|
|
if (currentIndex >= length)
|
|
return FALSE;
|
|
if (*currentChar == 0)
|
|
break;
|
|
|
|
currentIndex++;
|
|
currentChar++;
|
|
}
|
|
|
|
valueLength = currentIndex - startIndex;
|
|
|
|
currentIndex++;
|
|
*EnumerationKey = currentIndex;
|
|
|
|
Variable->Name.Buffer = name;
|
|
Variable->Name.Length = nameLength * sizeof(WCHAR);
|
|
Variable->Value.Buffer = value;
|
|
Variable->Value.Length = valueLength * sizeof(WCHAR);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Gets the file name of a mapped section.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION
|
|
* access.
|
|
* \param BaseAddress The base address of the section view.
|
|
* \param FileName A variable which receives a pointer to a string containing the file name of the
|
|
* section. You must free the string using PhDereferenceObject() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetProcessMappedFileName(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PVOID BaseAddress,
|
|
_Out_ PPH_STRING *FileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
SIZE_T bufferSize;
|
|
SIZE_T returnLength;
|
|
PUNICODE_STRING unicodeString;
|
|
|
|
bufferSize = 0x100;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
BaseAddress,
|
|
MemoryMappedFilenameInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&returnLength
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize = returnLength;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
BaseAddress,
|
|
MemoryMappedFilenameInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&returnLength
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
unicodeString = (PUNICODE_STRING)buffer;
|
|
*FileName = PhCreateStringEx(
|
|
unicodeString->Buffer,
|
|
unicodeString->Length
|
|
);
|
|
PhFree(buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets working set information for a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION
|
|
* access.
|
|
* \param WorkingSetInformation A variable which receives a pointer to the information. You must
|
|
* free the buffer using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetProcessWorkingSetInformation(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PMEMORY_WORKING_SET_INFORMATION *WorkingSetInformation
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
SIZE_T bufferSize;
|
|
|
|
bufferSize = 0x8000;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while ((status = NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
NULL,
|
|
MemoryWorkingSetInformation,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
)) == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
// Fail if we're resizing the buffer to something very large.
|
|
if (bufferSize > PH_LARGE_BUFFER_SIZE)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
*WorkingSetInformation = (PMEMORY_WORKING_SET_INFORMATION)buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets working set counters for a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have PROCESS_QUERY_INFORMATION
|
|
* access.
|
|
* \param WsCounters A variable which receives the counters.
|
|
*/
|
|
NTSTATUS PhGetProcessWsCounters(
|
|
_In_ HANDLE ProcessHandle,
|
|
_Out_ PPH_PROCESS_WS_COUNTERS WsCounters
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PMEMORY_WORKING_SET_INFORMATION wsInfo;
|
|
PH_PROCESS_WS_COUNTERS wsCounters;
|
|
ULONG_PTR i;
|
|
|
|
if (!NT_SUCCESS(status = PhGetProcessWorkingSetInformation(
|
|
ProcessHandle,
|
|
&wsInfo
|
|
)))
|
|
return status;
|
|
|
|
memset(&wsCounters, 0, sizeof(PH_PROCESS_WS_COUNTERS));
|
|
|
|
for (i = 0; i < wsInfo->NumberOfEntries; i++)
|
|
{
|
|
wsCounters.NumberOfPages++;
|
|
|
|
if (wsInfo->WorkingSetInfo[i].ShareCount > 1)
|
|
wsCounters.NumberOfSharedPages++;
|
|
if (wsInfo->WorkingSetInfo[i].ShareCount == 0)
|
|
wsCounters.NumberOfPrivatePages++;
|
|
if (wsInfo->WorkingSetInfo[i].Shared)
|
|
wsCounters.NumberOfShareablePages++;
|
|
}
|
|
|
|
PhFree(wsInfo);
|
|
|
|
*WsCounters = wsCounters;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Causes a process to load a DLL.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ
|
|
* and PROCESS_VM_WRITE access.
|
|
* \param FileName The file name of the DLL to inject.
|
|
* \param Timeout The timeout, in milliseconds, for the process to load the DLL.
|
|
*
|
|
* \remarks If the process does not load the DLL before the timeout expires it may crash. Choose the
|
|
* timeout value carefully.
|
|
*/
|
|
NTSTATUS PhInjectDllProcess(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PWSTR FileName,
|
|
_In_opt_ PLARGE_INTEGER Timeout
|
|
)
|
|
{
|
|
#ifdef _WIN64
|
|
static PVOID loadLibraryW32 = NULL;
|
|
#endif
|
|
|
|
NTSTATUS status;
|
|
#ifdef _WIN64
|
|
BOOLEAN isWow64 = FALSE;
|
|
BOOLEAN isModule32 = FALSE;
|
|
PH_MAPPED_IMAGE mappedImage;
|
|
#endif
|
|
PVOID threadStart;
|
|
PH_STRINGREF fileName;
|
|
PVOID baseAddress = NULL;
|
|
SIZE_T allocSize;
|
|
HANDLE threadHandle;
|
|
|
|
#ifdef _WIN64
|
|
PhGetProcessIsWow64(ProcessHandle, &isWow64);
|
|
|
|
if (isWow64)
|
|
{
|
|
if (!NT_SUCCESS(status = PhLoadMappedImage(FileName, NULL, TRUE, &mappedImage)))
|
|
return status;
|
|
|
|
isModule32 = mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC;
|
|
PhUnloadMappedImage(&mappedImage);
|
|
}
|
|
|
|
if (!isModule32)
|
|
{
|
|
#endif
|
|
threadStart = PhGetModuleProcAddress(L"kernel32.dll", "LoadLibraryW");
|
|
#ifdef _WIN64
|
|
}
|
|
else
|
|
{
|
|
threadStart = loadLibraryW32;
|
|
|
|
if (!threadStart)
|
|
{
|
|
PPH_STRING kernel32FileName;
|
|
|
|
kernel32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\kernel32.dll");
|
|
status = PhGetProcedureAddressRemote(
|
|
ProcessHandle,
|
|
kernel32FileName->Buffer,
|
|
"LoadLibraryW",
|
|
0,
|
|
&loadLibraryW32,
|
|
NULL
|
|
);
|
|
PhDereferenceObject(kernel32FileName);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
threadStart = loadLibraryW32;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PhInitializeStringRefLongHint(&fileName, FileName);
|
|
allocSize = fileName.Length + sizeof(WCHAR);
|
|
|
|
if (!NT_SUCCESS(status = NtAllocateVirtualMemory(
|
|
ProcessHandle,
|
|
&baseAddress,
|
|
0,
|
|
&allocSize,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE
|
|
)))
|
|
return status;
|
|
|
|
if (!NT_SUCCESS(status = NtWriteVirtualMemory(
|
|
ProcessHandle,
|
|
baseAddress,
|
|
fileName.Buffer,
|
|
fileName.Length + sizeof(WCHAR),
|
|
NULL
|
|
)))
|
|
goto FreeExit;
|
|
|
|
// Vista seems to support native threads better than XP.
|
|
if (WindowsVersion >= WINDOWS_VISTA)
|
|
{
|
|
if (!NT_SUCCESS(status = RtlCreateUserThread(
|
|
ProcessHandle,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
0,
|
|
0,
|
|
(PUSER_THREAD_START_ROUTINE)threadStart,
|
|
baseAddress,
|
|
&threadHandle,
|
|
NULL
|
|
)))
|
|
goto FreeExit;
|
|
}
|
|
else
|
|
{
|
|
if (!(threadHandle = CreateRemoteThread(
|
|
ProcessHandle,
|
|
NULL,
|
|
0,
|
|
(PTHREAD_START_ROUTINE)threadStart,
|
|
baseAddress,
|
|
0,
|
|
NULL
|
|
)))
|
|
{
|
|
status = PhGetLastWin32ErrorAsNtStatus();
|
|
goto FreeExit;
|
|
}
|
|
}
|
|
|
|
// Wait for the thread to finish.
|
|
status = NtWaitForSingleObject(threadHandle, FALSE, Timeout);
|
|
NtClose(threadHandle);
|
|
|
|
FreeExit:
|
|
// Size needs to be zero if we're freeing.
|
|
allocSize = 0;
|
|
NtFreeVirtualMemory(
|
|
ProcessHandle,
|
|
&baseAddress,
|
|
&allocSize,
|
|
MEM_RELEASE
|
|
);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Causes a process to unload a DLL.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ
|
|
* and PROCESS_VM_WRITE access.
|
|
* \param BaseAddress The base address of the DLL to unload.
|
|
* \param Timeout The timeout, in milliseconds, for the process to unload the DLL.
|
|
*/
|
|
NTSTATUS PhUnloadDllProcess(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PVOID BaseAddress,
|
|
_In_opt_ PLARGE_INTEGER Timeout
|
|
)
|
|
{
|
|
#ifdef _WIN64
|
|
static PVOID ldrUnloadDll32 = NULL;
|
|
#endif
|
|
|
|
NTSTATUS status;
|
|
#ifdef _WIN64
|
|
BOOLEAN isWow64 = FALSE;
|
|
BOOLEAN isModule32 = FALSE;
|
|
#endif
|
|
HANDLE threadHandle;
|
|
THREAD_BASIC_INFORMATION basicInfo;
|
|
PVOID threadStart;
|
|
|
|
#ifdef _WIN64
|
|
PhGetProcessIsWow64(ProcessHandle, &isWow64);
|
|
#endif
|
|
|
|
// No point trying to set the load count on Windows 8 and higher, because NT now uses a DAG of
|
|
// loader nodes.
|
|
if (WindowsVersion < WINDOWS_8)
|
|
{
|
|
status = PhSetProcessModuleLoadCount(
|
|
ProcessHandle,
|
|
BaseAddress,
|
|
1
|
|
);
|
|
|
|
#ifdef _WIN64
|
|
if (isWow64 && status == STATUS_DLL_NOT_FOUND)
|
|
{
|
|
// The DLL might be 32-bit.
|
|
status = PhSetProcessModuleLoadCount32(
|
|
ProcessHandle,
|
|
BaseAddress,
|
|
1
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
isModule32 = TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
if (!isModule32)
|
|
{
|
|
#endif
|
|
threadStart = PhGetModuleProcAddress(L"ntdll.dll", "LdrUnloadDll");
|
|
#ifdef _WIN64
|
|
}
|
|
else
|
|
{
|
|
threadStart = ldrUnloadDll32;
|
|
|
|
if (!threadStart)
|
|
{
|
|
PPH_STRING ntdll32FileName;
|
|
|
|
ntdll32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\ntdll.dll");
|
|
status = PhGetProcedureAddressRemote(
|
|
ProcessHandle,
|
|
ntdll32FileName->Buffer,
|
|
"LdrUnloadDll",
|
|
0,
|
|
&ldrUnloadDll32,
|
|
NULL
|
|
);
|
|
PhDereferenceObject(ntdll32FileName);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
threadStart = ldrUnloadDll32;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (WindowsVersion >= WINDOWS_VISTA)
|
|
{
|
|
status = RtlCreateUserThread(
|
|
ProcessHandle,
|
|
NULL,
|
|
FALSE,
|
|
0,
|
|
0,
|
|
0,
|
|
(PUSER_THREAD_START_ROUTINE)threadStart,
|
|
BaseAddress,
|
|
&threadHandle,
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
if (!(threadHandle = CreateRemoteThread(
|
|
ProcessHandle,
|
|
NULL,
|
|
0,
|
|
(PTHREAD_START_ROUTINE)threadStart,
|
|
BaseAddress,
|
|
0,
|
|
NULL
|
|
)))
|
|
{
|
|
status = PhGetLastWin32ErrorAsNtStatus();
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
status = NtWaitForSingleObject(threadHandle, FALSE, Timeout);
|
|
|
|
if (status == STATUS_WAIT_0)
|
|
{
|
|
status = PhGetThreadBasicInformation(threadHandle, &basicInfo);
|
|
|
|
if (NT_SUCCESS(status))
|
|
status = basicInfo.ExitStatus;
|
|
}
|
|
|
|
NtClose(threadHandle);
|
|
|
|
return status;
|
|
}
|
|
|
|
// Contributed by dmex
|
|
/**
|
|
* Sets an environment variable in a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ
|
|
* and PROCESS_VM_WRITE access.
|
|
* \param Name The name of the environment variable to set.
|
|
* \param Value The new value of the environment variable. If this parameter is NULL, the
|
|
* environment variable is deleted.
|
|
* \param Timeout The timeout, in milliseconds, for the process to set the environment variable.
|
|
*/
|
|
NTSTATUS PhSetEnvironmentVariableRemote(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPH_STRINGREF Name,
|
|
_In_opt_ PPH_STRINGREF Value,
|
|
_In_opt_ PLARGE_INTEGER Timeout
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
#ifdef _WIN64
|
|
BOOLEAN isWow64;
|
|
#endif
|
|
PPH_STRING ntdllFileName = NULL;
|
|
PPH_STRING kernel32FileName = NULL;
|
|
PVOID nameBaseAddress = NULL;
|
|
PVOID valueBaseAddress = NULL;
|
|
SIZE_T nameAllocationSize = 0;
|
|
SIZE_T valueAllocationSize = 0;
|
|
PVOID rtlExitUserThread = NULL;
|
|
PVOID setEnvironmentVariableW = NULL;
|
|
HANDLE threadHandle = NULL;
|
|
|
|
nameAllocationSize = Name->Length + sizeof(WCHAR);
|
|
|
|
if (Value)
|
|
valueAllocationSize = Value->Length + sizeof(WCHAR);
|
|
|
|
#ifdef _WIN64
|
|
if (!NT_SUCCESS(status = PhGetProcessIsWow64(ProcessHandle, &isWow64)))
|
|
goto CleanupExit;
|
|
|
|
if (isWow64)
|
|
{
|
|
ntdllFileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\ntdll.dll");
|
|
kernel32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\SysWow64\\kernel32.dll");
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
ntdllFileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\System32\\ntdll.dll");
|
|
kernel32FileName = PhConcatStrings2(USER_SHARED_DATA->NtSystemRoot, L"\\System32\\kernel32.dll");
|
|
#ifdef _WIN64
|
|
}
|
|
#endif
|
|
|
|
if (!NT_SUCCESS(status = PhGetProcedureAddressRemote(
|
|
ProcessHandle,
|
|
ntdllFileName->Buffer,
|
|
"RtlExitUserThread",
|
|
0,
|
|
&rtlExitUserThread,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
if (!NT_SUCCESS(status = PhGetProcedureAddressRemote(
|
|
ProcessHandle,
|
|
kernel32FileName->Buffer,
|
|
"SetEnvironmentVariableW",
|
|
0,
|
|
&setEnvironmentVariableW,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
if (!NT_SUCCESS(status = NtAllocateVirtualMemory(
|
|
ProcessHandle,
|
|
&nameBaseAddress,
|
|
0,
|
|
&nameAllocationSize,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
if (!NT_SUCCESS(status = NtWriteVirtualMemory(
|
|
ProcessHandle,
|
|
nameBaseAddress,
|
|
Name->Buffer,
|
|
Name->Length,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
|
|
if (Value)
|
|
{
|
|
if (!NT_SUCCESS(status = NtAllocateVirtualMemory(
|
|
ProcessHandle,
|
|
&valueBaseAddress,
|
|
0,
|
|
&valueAllocationSize,
|
|
MEM_COMMIT,
|
|
PAGE_READWRITE
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
if (!NT_SUCCESS(status = NtWriteVirtualMemory(
|
|
ProcessHandle,
|
|
valueBaseAddress,
|
|
Value->Buffer,
|
|
Value->Length,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
}
|
|
|
|
if (WindowsVersion >= WINDOWS_VISTA)
|
|
{
|
|
if (!NT_SUCCESS(status = RtlCreateUserThread(
|
|
ProcessHandle,
|
|
NULL,
|
|
TRUE,
|
|
0,
|
|
0,
|
|
0,
|
|
(PUSER_THREAD_START_ROUTINE)rtlExitUserThread,
|
|
NULL,
|
|
&threadHandle,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!(threadHandle = CreateRemoteThread(
|
|
ProcessHandle,
|
|
NULL,
|
|
0,
|
|
(PTHREAD_START_ROUTINE)rtlExitUserThread,
|
|
NULL,
|
|
CREATE_SUSPENDED,
|
|
NULL
|
|
)))
|
|
{
|
|
status = PhGetLastWin32ErrorAsNtStatus();
|
|
goto CleanupExit;
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
if (isWow64)
|
|
{
|
|
// NtQueueApcThread doesn't work for WOW64 processes - we need to use RtlQueueApcWow64Thread
|
|
// instead.
|
|
if (!NT_SUCCESS(status = RtlQueueApcWow64Thread(
|
|
threadHandle,
|
|
setEnvironmentVariableW,
|
|
nameBaseAddress,
|
|
valueBaseAddress,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
if (!NT_SUCCESS(status = NtQueueApcThread(
|
|
threadHandle,
|
|
setEnvironmentVariableW,
|
|
nameBaseAddress,
|
|
valueBaseAddress,
|
|
NULL
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
#ifdef _WIN64
|
|
}
|
|
#endif
|
|
|
|
// This causes our APC to be executed.
|
|
NtResumeThread(threadHandle, NULL);
|
|
status = NtWaitForSingleObject(threadHandle, FALSE, Timeout);
|
|
|
|
CleanupExit:
|
|
if (threadHandle)
|
|
NtClose(threadHandle);
|
|
if (nameBaseAddress)
|
|
{
|
|
nameAllocationSize = 0;
|
|
NtFreeVirtualMemory(
|
|
ProcessHandle,
|
|
&nameBaseAddress,
|
|
&nameAllocationSize,
|
|
MEM_RELEASE
|
|
);
|
|
}
|
|
if (valueBaseAddress)
|
|
{
|
|
valueAllocationSize = 0;
|
|
NtFreeVirtualMemory(
|
|
ProcessHandle,
|
|
&valueBaseAddress,
|
|
&valueAllocationSize,
|
|
MEM_RELEASE
|
|
);
|
|
}
|
|
PhClearReference(&ntdllFileName);
|
|
PhClearReference(&kernel32FileName);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetJobProcessIdList(
|
|
_In_ HANDLE JobHandle,
|
|
_Out_ PJOBOBJECT_BASIC_PROCESS_ID_LIST *ProcessIdList
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
|
|
bufferSize = 0x100;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQueryInformationJobObject(
|
|
JobHandle,
|
|
JobObjectBasicProcessIdList,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQueryInformationJobObject(
|
|
JobHandle,
|
|
JobObjectBasicProcessIdList,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
*ProcessIdList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST)buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Queries variable-sized information for a token. The function allocates a buffer to contain the
|
|
* information.
|
|
*
|
|
* \param TokenHandle A handle to a token. The access required depends on the information class
|
|
* specified.
|
|
* \param TokenInformationClass The information class to retrieve.
|
|
* \param Buffer A variable which receives a pointer to a buffer containing the information. You
|
|
* must free the buffer using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhpQueryTokenVariableSize(
|
|
_In_ HANDLE TokenHandle,
|
|
_In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG returnLength = 0;
|
|
|
|
NtQueryInformationToken(
|
|
TokenHandle,
|
|
TokenInformationClass,
|
|
NULL,
|
|
0,
|
|
&returnLength
|
|
);
|
|
buffer = PhAllocate(returnLength);
|
|
status = NtQueryInformationToken(
|
|
TokenHandle,
|
|
TokenInformationClass,
|
|
buffer,
|
|
returnLength,
|
|
&returnLength
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Queries variable-sized information for a token. The function allocates a buffer to contain the
|
|
* information.
|
|
*
|
|
* \param TokenHandle A handle to a token. The access required depends on the information class
|
|
* specified.
|
|
* \param TokenInformationClass The information class to retrieve.
|
|
* \param Buffer A variable which receives a pointer to a buffer containing the information. You
|
|
* must free the buffer using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhQueryTokenVariableSize(
|
|
_In_ HANDLE TokenHandle,
|
|
_In_ TOKEN_INFORMATION_CLASS TokenInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
return PhpQueryTokenVariableSize(
|
|
TokenHandle,
|
|
TokenInformationClass,
|
|
Buffer
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a token's user.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access.
|
|
* \param User A variable which receives a pointer to a structure containing the token's user. You
|
|
* must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetTokenUser(
|
|
_In_ HANDLE TokenHandle,
|
|
_Out_ PTOKEN_USER *User
|
|
)
|
|
{
|
|
return PhpQueryTokenVariableSize(
|
|
TokenHandle,
|
|
TokenUser,
|
|
User
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a token's owner.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access.
|
|
* \param Owner A variable which receives a pointer to a structure containing the token's owner. You
|
|
* must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetTokenOwner(
|
|
_In_ HANDLE TokenHandle,
|
|
_Out_ PTOKEN_OWNER *Owner
|
|
)
|
|
{
|
|
return PhpQueryTokenVariableSize(
|
|
TokenHandle,
|
|
TokenOwner,
|
|
Owner
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a token's primary group.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access.
|
|
* \param PrimaryGroup A variable which receives a pointer to a structure containing the token's
|
|
* primary group. You must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetTokenPrimaryGroup(
|
|
_In_ HANDLE TokenHandle,
|
|
_Out_ PTOKEN_PRIMARY_GROUP *PrimaryGroup
|
|
)
|
|
{
|
|
return PhpQueryTokenVariableSize(
|
|
TokenHandle,
|
|
TokenPrimaryGroup,
|
|
PrimaryGroup
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a token's groups.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access.
|
|
* \param Groups A variable which receives a pointer to a structure containing the token's groups.
|
|
* You must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetTokenGroups(
|
|
_In_ HANDLE TokenHandle,
|
|
_Out_ PTOKEN_GROUPS *Groups
|
|
)
|
|
{
|
|
return PhpQueryTokenVariableSize(
|
|
TokenHandle,
|
|
TokenGroups,
|
|
Groups
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a token's privileges.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access.
|
|
* \param Privileges A variable which receives a pointer to a structure containing the token's
|
|
* privileges. You must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhGetTokenPrivileges(
|
|
_In_ HANDLE TokenHandle,
|
|
_Out_ PTOKEN_PRIVILEGES *Privileges
|
|
)
|
|
{
|
|
return PhpQueryTokenVariableSize(
|
|
TokenHandle,
|
|
TokenPrivileges,
|
|
Privileges
|
|
);
|
|
}
|
|
|
|
NTSTATUS PhSetTokenSessionId(
|
|
_In_ HANDLE TokenHandle,
|
|
_In_ ULONG SessionId
|
|
)
|
|
{
|
|
return NtSetInformationToken(
|
|
TokenHandle,
|
|
TokenSessionId,
|
|
&SessionId,
|
|
sizeof(ULONG)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Modifies a token privilege.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_ADJUST_PRIVILEGES access.
|
|
* \param PrivilegeName The name of the privilege to modify. If this parameter is NULL, you must
|
|
* specify a LUID in the \a PrivilegeLuid parameter.
|
|
* \param PrivilegeLuid The LUID of the privilege to modify. If this parameter is NULL, you must
|
|
* specify a name in the \a PrivilegeName parameter.
|
|
* \param Attributes The new attributes of the privilege.
|
|
*/
|
|
BOOLEAN PhSetTokenPrivilege(
|
|
_In_ HANDLE TokenHandle,
|
|
_In_opt_ PWSTR PrivilegeName,
|
|
_In_opt_ PLUID PrivilegeLuid,
|
|
_In_ ULONG Attributes
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
TOKEN_PRIVILEGES privileges;
|
|
|
|
privileges.PrivilegeCount = 1;
|
|
privileges.Privileges[0].Attributes = Attributes;
|
|
|
|
if (PrivilegeLuid)
|
|
{
|
|
privileges.Privileges[0].Luid = *PrivilegeLuid;
|
|
}
|
|
else if (PrivilegeName)
|
|
{
|
|
PH_STRINGREF privilegeName;
|
|
|
|
PhInitializeStringRef(&privilegeName, PrivilegeName);
|
|
|
|
if (!PhLookupPrivilegeValue(
|
|
&privilegeName,
|
|
&privileges.Privileges[0].Luid
|
|
))
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status = NtAdjustPrivilegesToken(
|
|
TokenHandle,
|
|
FALSE,
|
|
&privileges,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
)))
|
|
return FALSE;
|
|
|
|
if (status == STATUS_NOT_ALL_ASSIGNED)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN PhSetTokenPrivilege2(
|
|
_In_ HANDLE TokenHandle,
|
|
_In_ LONG Privilege,
|
|
_In_ ULONG Attributes
|
|
)
|
|
{
|
|
LUID privilegeLuid;
|
|
|
|
privilegeLuid = RtlConvertLongToLuid(Privilege);
|
|
|
|
return PhSetTokenPrivilege(TokenHandle, NULL, &privilegeLuid, Attributes);
|
|
}
|
|
|
|
/**
|
|
* Sets whether virtualization is enabled for a token.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_WRITE access.
|
|
* \param IsVirtualizationEnabled A boolean indicating whether virtualization is to be enabled for
|
|
* the token.
|
|
*/
|
|
NTSTATUS PhSetTokenIsVirtualizationEnabled(
|
|
_In_ HANDLE TokenHandle,
|
|
_In_ BOOLEAN IsVirtualizationEnabled
|
|
)
|
|
{
|
|
ULONG virtualizationEnabled;
|
|
|
|
virtualizationEnabled = IsVirtualizationEnabled;
|
|
|
|
return NtSetInformationToken(
|
|
TokenHandle,
|
|
TokenVirtualizationEnabled,
|
|
&virtualizationEnabled,
|
|
sizeof(ULONG)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Gets a token's integrity level.
|
|
*
|
|
* \param TokenHandle A handle to a token. The handle must have TOKEN_QUERY access.
|
|
* \param IntegrityLevel A variable which receives the integrity level of the token.
|
|
* \param IntegrityString A variable which receives a pointer to a string containing a string
|
|
* representation of the integrity level.
|
|
*/
|
|
NTSTATUS PhGetTokenIntegrityLevel(
|
|
_In_ HANDLE TokenHandle,
|
|
_Out_opt_ PMANDATORY_LEVEL IntegrityLevel,
|
|
_Out_opt_ PWSTR *IntegrityString
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PTOKEN_MANDATORY_LABEL mandatoryLabel;
|
|
ULONG subAuthority;
|
|
MANDATORY_LEVEL integrityLevel;
|
|
PWSTR integrityString;
|
|
|
|
status = PhpQueryTokenVariableSize(TokenHandle, TokenIntegrityLevel, &mandatoryLabel);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
subAuthority = *RtlSubAuthoritySid(mandatoryLabel->Label.Sid, 0);
|
|
PhFree(mandatoryLabel);
|
|
|
|
switch (subAuthority)
|
|
{
|
|
case SECURITY_MANDATORY_UNTRUSTED_RID:
|
|
integrityLevel = MandatoryLevelUntrusted;
|
|
integrityString = L"Untrusted";
|
|
break;
|
|
case SECURITY_MANDATORY_LOW_RID:
|
|
integrityLevel = MandatoryLevelLow;
|
|
integrityString = L"Low";
|
|
break;
|
|
case SECURITY_MANDATORY_MEDIUM_RID:
|
|
integrityLevel = MandatoryLevelMedium;
|
|
integrityString = L"Medium";
|
|
break;
|
|
case SECURITY_MANDATORY_HIGH_RID:
|
|
integrityLevel = MandatoryLevelHigh;
|
|
integrityString = L"High";
|
|
break;
|
|
case SECURITY_MANDATORY_SYSTEM_RID:
|
|
integrityLevel = MandatoryLevelSystem;
|
|
integrityString = L"System";
|
|
break;
|
|
case SECURITY_MANDATORY_PROTECTED_PROCESS_RID:
|
|
integrityLevel = MandatoryLevelSecureProcess;
|
|
integrityString = L"Protected";
|
|
break;
|
|
default:
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (IntegrityLevel)
|
|
*IntegrityLevel = integrityLevel;
|
|
if (IntegrityString)
|
|
*IntegrityString = integrityString;
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpQueryFileVariableSize(
|
|
_In_ HANDLE FileHandle,
|
|
_In_ FILE_INFORMATION_CLASS FileInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK isb;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x200;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
status = NtQueryInformationFile(
|
|
FileHandle,
|
|
&isb,
|
|
buffer,
|
|
bufferSize,
|
|
FileInformationClass
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetFileSize(
|
|
_In_ HANDLE FileHandle,
|
|
_Out_ PLARGE_INTEGER Size
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
FILE_STANDARD_INFORMATION standardInfo;
|
|
IO_STATUS_BLOCK isb;
|
|
|
|
status = NtQueryInformationFile(
|
|
FileHandle,
|
|
&isb,
|
|
&standardInfo,
|
|
sizeof(FILE_STANDARD_INFORMATION),
|
|
FileStandardInformation
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
*Size = standardInfo.EndOfFile;
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhSetFileSize(
|
|
_In_ HANDLE FileHandle,
|
|
_In_ PLARGE_INTEGER Size
|
|
)
|
|
{
|
|
FILE_END_OF_FILE_INFORMATION endOfFileInfo;
|
|
IO_STATUS_BLOCK isb;
|
|
|
|
endOfFileInfo.EndOfFile = *Size;
|
|
|
|
return NtSetInformationFile(
|
|
FileHandle,
|
|
&isb,
|
|
&endOfFileInfo,
|
|
sizeof(FILE_END_OF_FILE_INFORMATION),
|
|
FileEndOfFileInformation
|
|
);
|
|
}
|
|
|
|
NTSTATUS PhpQueryTransactionManagerVariableSize(
|
|
_In_ HANDLE TransactionManagerHandle,
|
|
_In_ TRANSACTIONMANAGER_INFORMATION_CLASS TransactionManagerInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x100;
|
|
|
|
if (!NtQueryInformationTransactionManager_Import())
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
status = NtQueryInformationTransactionManager_Import()(
|
|
TransactionManagerHandle,
|
|
TransactionManagerInformationClass,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
if (bufferSize > 1 * 1024 * 1024)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetTransactionManagerBasicInformation(
|
|
_In_ HANDLE TransactionManagerHandle,
|
|
_Out_ PTRANSACTIONMANAGER_BASIC_INFORMATION BasicInformation
|
|
)
|
|
{
|
|
if (NtQueryInformationTransactionManager_Import())
|
|
{
|
|
return NtQueryInformationTransactionManager_Import()(
|
|
TransactionManagerHandle,
|
|
TransactionManagerBasicInformation,
|
|
BasicInformation,
|
|
sizeof(TRANSACTIONMANAGER_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
NTSTATUS PhGetTransactionManagerLogFileName(
|
|
_In_ HANDLE TransactionManagerHandle,
|
|
_Out_ PPH_STRING *LogFileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PTRANSACTIONMANAGER_LOGPATH_INFORMATION logPathInfo;
|
|
|
|
status = PhpQueryTransactionManagerVariableSize(
|
|
TransactionManagerHandle,
|
|
TransactionManagerLogPathInformation,
|
|
&logPathInfo
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
*LogFileName = PhCreateStringEx(
|
|
logPathInfo->LogPath,
|
|
logPathInfo->LogPathLength
|
|
);
|
|
PhFree(logPathInfo);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpQueryTransactionVariableSize(
|
|
_In_ HANDLE TransactionHandle,
|
|
_In_ TRANSACTION_INFORMATION_CLASS TransactionInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x100;
|
|
|
|
if (!NtQueryInformationTransaction_Import())
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
status = NtQueryInformationTransaction_Import()(
|
|
TransactionHandle,
|
|
TransactionInformationClass,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
if (bufferSize > 1 * 1024 * 1024)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetTransactionBasicInformation(
|
|
_In_ HANDLE TransactionHandle,
|
|
_Out_ PTRANSACTION_BASIC_INFORMATION BasicInformation
|
|
)
|
|
{
|
|
if (NtQueryInformationTransaction_Import())
|
|
{
|
|
return NtQueryInformationTransaction_Import()(
|
|
TransactionHandle,
|
|
TransactionBasicInformation,
|
|
BasicInformation,
|
|
sizeof(TRANSACTION_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
NTSTATUS PhGetTransactionPropertiesInformation(
|
|
_In_ HANDLE TransactionHandle,
|
|
_Out_opt_ PLARGE_INTEGER Timeout,
|
|
_Out_opt_ TRANSACTION_OUTCOME *Outcome,
|
|
_Out_opt_ PPH_STRING *Description
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PTRANSACTION_PROPERTIES_INFORMATION propertiesInfo;
|
|
|
|
status = PhpQueryTransactionVariableSize(
|
|
TransactionHandle,
|
|
TransactionPropertiesInformation,
|
|
&propertiesInfo
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (Timeout)
|
|
{
|
|
*Timeout = propertiesInfo->Timeout;
|
|
}
|
|
|
|
if (Outcome)
|
|
{
|
|
*Outcome = propertiesInfo->Outcome;
|
|
}
|
|
|
|
if (Description)
|
|
{
|
|
*Description = PhCreateStringEx(
|
|
propertiesInfo->Description,
|
|
propertiesInfo->DescriptionLength
|
|
);
|
|
}
|
|
|
|
PhFree(propertiesInfo);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpQueryResourceManagerVariableSize(
|
|
_In_ HANDLE ResourceManagerHandle,
|
|
_In_ RESOURCEMANAGER_INFORMATION_CLASS ResourceManagerInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x100;
|
|
|
|
if (!NtQueryInformationResourceManager_Import())
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
status = NtQueryInformationResourceManager_Import()(
|
|
ResourceManagerHandle,
|
|
ResourceManagerInformationClass,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
if (bufferSize > 1 * 1024 * 1024)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetResourceManagerBasicInformation(
|
|
_In_ HANDLE ResourceManagerHandle,
|
|
_Out_opt_ PGUID Guid,
|
|
_Out_opt_ PPH_STRING *Description
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PRESOURCEMANAGER_BASIC_INFORMATION basicInfo;
|
|
|
|
status = PhpQueryResourceManagerVariableSize(
|
|
ResourceManagerHandle,
|
|
ResourceManagerBasicInformation,
|
|
&basicInfo
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (Guid)
|
|
{
|
|
*Guid = basicInfo->ResourceManagerId;
|
|
}
|
|
|
|
if (Description)
|
|
{
|
|
*Description = PhCreateStringEx(
|
|
basicInfo->Description,
|
|
basicInfo->DescriptionLength
|
|
);
|
|
}
|
|
|
|
PhFree(basicInfo);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhGetEnlistmentBasicInformation(
|
|
_In_ HANDLE EnlistmentHandle,
|
|
_Out_ PENLISTMENT_BASIC_INFORMATION BasicInformation
|
|
)
|
|
{
|
|
if (NtQueryInformationEnlistment_Import())
|
|
{
|
|
return NtQueryInformationEnlistment_Import()(
|
|
EnlistmentHandle,
|
|
EnlistmentBasicInformation,
|
|
BasicInformation,
|
|
sizeof(ENLISTMENT_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
typedef struct _OPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT
|
|
{
|
|
NTSTATUS Status;
|
|
PVOID BaseAddress;
|
|
HANDLE DriverHandle;
|
|
} OPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT, *POPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT;
|
|
|
|
BOOLEAN NTAPI PhpOpenDriverByBaseAddressCallback(
|
|
_In_ PPH_STRINGREF Name,
|
|
_In_ PPH_STRINGREF TypeName,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
static PH_STRINGREF driverDirectoryName = PH_STRINGREF_INIT(L"\\Driver\\");
|
|
|
|
NTSTATUS status;
|
|
POPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT context = Context;
|
|
PPH_STRING driverName;
|
|
UNICODE_STRING driverNameUs;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
HANDLE driverHandle;
|
|
DRIVER_BASIC_INFORMATION basicInfo;
|
|
|
|
driverName = PhConcatStringRef2(&driverDirectoryName, Name);
|
|
|
|
if (!PhStringRefToUnicodeString(&driverName->sr, &driverNameUs))
|
|
{
|
|
PhDereferenceObject(driverName);
|
|
return TRUE;
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
&driverNameUs,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = KphOpenDriver(&driverHandle, SYNCHRONIZE, &objectAttributes);
|
|
PhDereferenceObject(driverName);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return TRUE;
|
|
|
|
status = KphQueryInformationDriver(
|
|
driverHandle,
|
|
DriverBasicInformation,
|
|
&basicInfo,
|
|
sizeof(DRIVER_BASIC_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
if (basicInfo.DriverStart == context->BaseAddress)
|
|
{
|
|
context->Status = STATUS_SUCCESS;
|
|
context->DriverHandle = driverHandle;
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
NtClose(driverHandle);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Opens a driver object using a base address.
|
|
*
|
|
* \param DriverHandle A variable which receives a handle to the driver object.
|
|
* \param BaseAddress The base address of the driver to open.
|
|
*
|
|
* \retval STATUS_OBJECT_NAME_NOT_FOUND The driver could not be found.
|
|
*
|
|
* \remarks This function requires a valid KProcessHacker handle.
|
|
*/
|
|
NTSTATUS PhOpenDriverByBaseAddress(
|
|
_Out_ PHANDLE DriverHandle,
|
|
_In_ PVOID BaseAddress
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING driverDirectoryName;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
HANDLE driverDirectoryHandle;
|
|
OPEN_DRIVER_BY_BASE_ADDRESS_CONTEXT context;
|
|
|
|
RtlInitUnicodeString(
|
|
&driverDirectoryName,
|
|
L"\\Driver"
|
|
);
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
&driverDirectoryName,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status = NtOpenDirectoryObject(
|
|
&driverDirectoryHandle,
|
|
DIRECTORY_QUERY,
|
|
&objectAttributes
|
|
)))
|
|
return status;
|
|
|
|
context.Status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
context.BaseAddress = BaseAddress;
|
|
|
|
status = PhEnumDirectoryObjects(
|
|
driverDirectoryHandle,
|
|
PhpOpenDriverByBaseAddressCallback,
|
|
&context
|
|
);
|
|
NtClose(driverDirectoryHandle);
|
|
|
|
if (!NT_SUCCESS(status) && !NT_SUCCESS(context.Status))
|
|
return status;
|
|
|
|
if (NT_SUCCESS(context.Status))
|
|
{
|
|
*DriverHandle = context.DriverHandle;
|
|
}
|
|
|
|
return context.Status;
|
|
}
|
|
|
|
/**
|
|
* Queries variable-sized information for a driver. The function allocates a buffer to contain the
|
|
* information.
|
|
*
|
|
* \param DriverHandle A handle to a driver. The access required depends on the information class
|
|
* specified.
|
|
* \param DriverInformationClass The information class to retrieve.
|
|
* \param Buffer A variable which receives a pointer to a buffer containing the information. You
|
|
* must free the buffer using PhFree() when you no longer need it.
|
|
*
|
|
* \remarks This function requires a valid KProcessHacker handle.
|
|
*/
|
|
NTSTATUS PhpQueryDriverVariableSize(
|
|
_In_ HANDLE DriverHandle,
|
|
_In_ DRIVER_INFORMATION_CLASS DriverInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG returnLength = 0;
|
|
|
|
KphQueryInformationDriver(
|
|
DriverHandle,
|
|
DriverInformationClass,
|
|
NULL,
|
|
0,
|
|
&returnLength
|
|
);
|
|
buffer = PhAllocate(returnLength);
|
|
status = KphQueryInformationDriver(
|
|
DriverHandle,
|
|
DriverInformationClass,
|
|
buffer,
|
|
returnLength,
|
|
&returnLength
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*Buffer = buffer;
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets the object name of a driver.
|
|
*
|
|
* \param DriverHandle A handle to a driver.
|
|
* \param Name A variable which receives a pointer to a string containing the object name. You must
|
|
* free the string using PhDereferenceObject() when you no longer need it.
|
|
*
|
|
* \remarks This function requires a valid KProcessHacker handle.
|
|
*/
|
|
NTSTATUS PhGetDriverName(
|
|
_In_ HANDLE DriverHandle,
|
|
_Out_ PPH_STRING *Name
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PUNICODE_STRING unicodeString;
|
|
|
|
if (!NT_SUCCESS(status = PhpQueryDriverVariableSize(
|
|
DriverHandle,
|
|
DriverNameInformation,
|
|
&unicodeString
|
|
)))
|
|
return status;
|
|
|
|
*Name = PhCreateStringEx(
|
|
unicodeString->Buffer,
|
|
unicodeString->Length
|
|
);
|
|
PhFree(unicodeString);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets the service key name of a driver.
|
|
*
|
|
* \param DriverHandle A handle to a driver.
|
|
* \param ServiceKeyName A variable which receives a pointer to a string containing the service key
|
|
* name. You must free the string using PhDereferenceObject() when you no longer need it.
|
|
*
|
|
* \remarks This function requires a valid KProcessHacker handle.
|
|
*/
|
|
NTSTATUS PhGetDriverServiceKeyName(
|
|
_In_ HANDLE DriverHandle,
|
|
_Out_ PPH_STRING *ServiceKeyName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PUNICODE_STRING unicodeString;
|
|
|
|
if (!NT_SUCCESS(status = PhpQueryDriverVariableSize(
|
|
DriverHandle,
|
|
DriverServiceKeyNameInformation,
|
|
&unicodeString
|
|
)))
|
|
return status;
|
|
|
|
*ServiceKeyName = PhCreateStringEx(
|
|
unicodeString->Buffer,
|
|
unicodeString->Length
|
|
);
|
|
PhFree(unicodeString);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpUnloadDriver(
|
|
_In_ PPH_STRING ServiceKeyName
|
|
)
|
|
{
|
|
static PH_STRINGREF fullServicesKeyName = PH_STRINGREF_INIT(L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\");
|
|
|
|
NTSTATUS status;
|
|
PPH_STRING fullServiceKeyName;
|
|
UNICODE_STRING fullServiceKeyNameUs;
|
|
HANDLE serviceKeyHandle;
|
|
ULONG disposition;
|
|
|
|
fullServiceKeyName = PhConcatStringRef2(&fullServicesKeyName, &ServiceKeyName->sr);
|
|
|
|
if (!PhStringRefToUnicodeString(&fullServiceKeyName->sr, &fullServiceKeyNameUs))
|
|
{
|
|
PhDereferenceObject(fullServiceKeyName);
|
|
return STATUS_NAME_TOO_LONG;
|
|
}
|
|
|
|
if (NT_SUCCESS(status = PhCreateKey(
|
|
&serviceKeyHandle,
|
|
KEY_WRITE | DELETE,
|
|
NULL,
|
|
&fullServiceKeyName->sr,
|
|
0,
|
|
0,
|
|
&disposition
|
|
)))
|
|
{
|
|
if (disposition == REG_CREATED_NEW_KEY)
|
|
{
|
|
static UNICODE_STRING imagePath = RTL_CONSTANT_STRING(L"\\SystemRoot\\system32\\drivers\\ntfs.sys");
|
|
|
|
UNICODE_STRING valueName;
|
|
ULONG dword;
|
|
|
|
// Set up the required values.
|
|
dword = 1;
|
|
RtlInitUnicodeString(&valueName, L"ErrorControl");
|
|
NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_DWORD, &dword, sizeof(ULONG));
|
|
RtlInitUnicodeString(&valueName, L"Start");
|
|
NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_DWORD, &dword, sizeof(ULONG));
|
|
RtlInitUnicodeString(&valueName, L"Type");
|
|
NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_DWORD, &dword, sizeof(ULONG));
|
|
|
|
// Use a bogus name.
|
|
RtlInitUnicodeString(&valueName, L"ImagePath");
|
|
NtSetValueKey(serviceKeyHandle, &valueName, 0, REG_SZ, imagePath.Buffer, imagePath.Length + 2);
|
|
}
|
|
|
|
status = NtUnloadDriver(&fullServiceKeyNameUs);
|
|
|
|
if (disposition == REG_CREATED_NEW_KEY)
|
|
{
|
|
// We added values, not subkeys, so this function will work correctly.
|
|
NtDeleteKey(serviceKeyHandle);
|
|
}
|
|
|
|
NtClose(serviceKeyHandle);
|
|
}
|
|
|
|
PhDereferenceObject(fullServiceKeyName);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Unloads a driver.
|
|
*
|
|
* \param BaseAddress The base address of the driver. This parameter can be NULL if a value is
|
|
* specified in \c Name.
|
|
* \param Name The base name of the driver. This parameter can be NULL if a value is specified in
|
|
* \c BaseAddress and KProcessHacker is loaded.
|
|
*
|
|
* \retval STATUS_INVALID_PARAMETER_MIX Both \c BaseAddress and \c Name were null, or \c Name was
|
|
* not specified and KProcessHacker is not loaded.
|
|
* \retval STATUS_OBJECT_NAME_NOT_FOUND The driver could not be found.
|
|
*/
|
|
NTSTATUS PhUnloadDriver(
|
|
_In_opt_ PVOID BaseAddress,
|
|
_In_opt_ PWSTR Name
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE driverHandle;
|
|
PPH_STRING serviceKeyName = NULL;
|
|
|
|
if (!BaseAddress && !Name)
|
|
return STATUS_INVALID_PARAMETER_MIX;
|
|
if (!Name && !KphIsConnected())
|
|
return STATUS_INVALID_PARAMETER_MIX;
|
|
|
|
// Try to get the service key name by scanning the Driver directory.
|
|
|
|
if (KphIsConnected() && BaseAddress)
|
|
{
|
|
if (NT_SUCCESS(PhOpenDriverByBaseAddress(
|
|
&driverHandle,
|
|
BaseAddress
|
|
)))
|
|
{
|
|
PhGetDriverServiceKeyName(driverHandle, &serviceKeyName);
|
|
NtClose(driverHandle);
|
|
}
|
|
}
|
|
|
|
// Use the base name if we didn't get the service key name.
|
|
|
|
if (!serviceKeyName && Name)
|
|
{
|
|
PPH_STRING name;
|
|
|
|
name = PhCreateString(Name);
|
|
|
|
// Remove the extension if it is present.
|
|
if (PhEndsWithString2(name, L".sys", TRUE))
|
|
{
|
|
serviceKeyName = PhSubstring(name, 0, name->Length / 2 - 4);
|
|
PhDereferenceObject(name);
|
|
}
|
|
else
|
|
{
|
|
serviceKeyName = name;
|
|
}
|
|
}
|
|
|
|
if (!serviceKeyName)
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
status = PhpUnloadDriver(serviceKeyName);
|
|
PhDereferenceObject(serviceKeyName);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhpEnumProcessModules(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPHP_ENUM_PROCESS_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PROCESS_BASIC_INFORMATION basicInfo;
|
|
PPEB_LDR_DATA ldr;
|
|
PEB_LDR_DATA pebLdrData;
|
|
PLIST_ENTRY startLink;
|
|
PLIST_ENTRY currentLink;
|
|
ULONG dataTableEntrySize;
|
|
LDR_DATA_TABLE_ENTRY currentEntry;
|
|
ULONG i;
|
|
|
|
// Get the PEB address.
|
|
status = PhGetProcessBasicInformation(ProcessHandle, &basicInfo);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Read the address of the loader data.
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(basicInfo.PebBaseAddress, FIELD_OFFSET(PEB, Ldr)),
|
|
&ldr,
|
|
sizeof(PVOID),
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Read the loader data.
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
ldr,
|
|
&pebLdrData,
|
|
sizeof(PEB_LDR_DATA),
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (!pebLdrData.Initialized)
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
if (WindowsVersion >= WINDOWS_8)
|
|
dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN8;
|
|
else if (WindowsVersion >= WINDOWS_7)
|
|
dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN7;
|
|
else
|
|
dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WINXP;
|
|
|
|
// Traverse the linked list (in load order).
|
|
|
|
i = 0;
|
|
startLink = PTR_ADD_OFFSET(ldr, FIELD_OFFSET(PEB_LDR_DATA, InLoadOrderModuleList));
|
|
currentLink = pebLdrData.InLoadOrderModuleList.Flink;
|
|
|
|
while (
|
|
currentLink != startLink &&
|
|
i <= PH_ENUM_PROCESS_MODULES_LIMIT
|
|
)
|
|
{
|
|
PVOID addressOfEntry;
|
|
|
|
addressOfEntry = CONTAINING_RECORD(currentLink, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
addressOfEntry,
|
|
¤tEntry,
|
|
dataTableEntrySize,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Make sure the entry is valid.
|
|
if (currentEntry.DllBase)
|
|
{
|
|
// Execute the callback.
|
|
if (!Callback(
|
|
ProcessHandle,
|
|
¤tEntry,
|
|
addressOfEntry,
|
|
Context1,
|
|
Context2
|
|
))
|
|
break;
|
|
}
|
|
|
|
currentLink = currentEntry.InLoadOrderLinks.Flink;
|
|
i++;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOLEAN NTAPI PhpEnumProcessModulesCallback(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PLDR_DATA_TABLE_ENTRY Entry,
|
|
_In_ PVOID AddressOfEntry,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PPH_ENUM_PROCESS_MODULES_PARAMETERS parameters;
|
|
BOOLEAN cont;
|
|
PPH_STRING mappedFileName;
|
|
PWSTR fullDllNameOriginal;
|
|
PWSTR fullDllNameBuffer;
|
|
PWSTR baseDllNameOriginal;
|
|
PWSTR baseDllNameBuffer;
|
|
|
|
parameters = Context1;
|
|
mappedFileName = NULL;
|
|
|
|
if (parameters->Flags & PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME)
|
|
{
|
|
PhGetProcessMappedFileName(ProcessHandle, Entry->DllBase, &mappedFileName);
|
|
}
|
|
|
|
if (mappedFileName)
|
|
{
|
|
ULONG_PTR indexOfLastBackslash;
|
|
|
|
PhStringRefToUnicodeString(&mappedFileName->sr, &Entry->FullDllName);
|
|
indexOfLastBackslash = PhFindLastCharInString(mappedFileName, 0, '\\');
|
|
|
|
if (indexOfLastBackslash != -1)
|
|
{
|
|
Entry->BaseDllName.Buffer = Entry->FullDllName.Buffer + indexOfLastBackslash + 1;
|
|
Entry->BaseDllName.Length = Entry->FullDllName.Length - (USHORT)indexOfLastBackslash * 2 - 2;
|
|
Entry->BaseDllName.MaximumLength = Entry->BaseDllName.Length;
|
|
}
|
|
else
|
|
{
|
|
Entry->BaseDllName = Entry->FullDllName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read the full DLL name string and add a null terminator.
|
|
|
|
fullDllNameOriginal = Entry->FullDllName.Buffer;
|
|
fullDllNameBuffer = PhAllocate(Entry->FullDllName.Length + 2);
|
|
Entry->FullDllName.Buffer = fullDllNameBuffer;
|
|
|
|
if (NT_SUCCESS(status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
fullDllNameOriginal,
|
|
fullDllNameBuffer,
|
|
Entry->FullDllName.Length,
|
|
NULL
|
|
)))
|
|
{
|
|
fullDllNameBuffer[Entry->FullDllName.Length / 2] = 0;
|
|
}
|
|
else
|
|
{
|
|
fullDllNameBuffer[0] = 0;
|
|
Entry->FullDllName.Length = 0;
|
|
}
|
|
|
|
baseDllNameOriginal = Entry->BaseDllName.Buffer;
|
|
|
|
// Try to use the buffer we just read in.
|
|
if (
|
|
NT_SUCCESS(status) &&
|
|
(ULONG_PTR)baseDllNameOriginal >= (ULONG_PTR)fullDllNameOriginal &&
|
|
(ULONG_PTR)baseDllNameOriginal + Entry->BaseDllName.Length >= (ULONG_PTR)baseDllNameOriginal &&
|
|
(ULONG_PTR)baseDllNameOriginal + Entry->BaseDllName.Length <= (ULONG_PTR)fullDllNameOriginal + Entry->FullDllName.Length
|
|
)
|
|
{
|
|
baseDllNameBuffer = NULL;
|
|
|
|
Entry->BaseDllName.Buffer = (PWCHAR)((ULONG_PTR)Entry->FullDllName.Buffer +
|
|
((ULONG_PTR)baseDllNameOriginal - (ULONG_PTR)fullDllNameOriginal));
|
|
}
|
|
else
|
|
{
|
|
// Read the base DLL name string and add a null terminator.
|
|
|
|
baseDllNameBuffer = PhAllocate(Entry->BaseDllName.Length + 2);
|
|
Entry->BaseDllName.Buffer = baseDllNameBuffer;
|
|
|
|
if (NT_SUCCESS(NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
baseDllNameOriginal,
|
|
baseDllNameBuffer,
|
|
Entry->BaseDllName.Length,
|
|
NULL
|
|
)))
|
|
{
|
|
baseDllNameBuffer[Entry->BaseDllName.Length / 2] = 0;
|
|
}
|
|
else
|
|
{
|
|
baseDllNameBuffer[0] = 0;
|
|
Entry->BaseDllName.Length = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute the callback.
|
|
cont = parameters->Callback(Entry, parameters->Context);
|
|
|
|
if (mappedFileName)
|
|
{
|
|
PhDereferenceObject(mappedFileName);
|
|
}
|
|
else
|
|
{
|
|
PhFree(fullDllNameBuffer);
|
|
|
|
if (baseDllNameBuffer)
|
|
PhFree(baseDllNameBuffer);
|
|
}
|
|
|
|
return cont;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the modules loaded by a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access.
|
|
* \param Callback A callback function which is executed for each process module.
|
|
* \param Context A user-defined value to pass to the callback function.
|
|
*/
|
|
NTSTATUS PhEnumProcessModules(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPH_ENUM_PROCESS_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PH_ENUM_PROCESS_MODULES_PARAMETERS parameters;
|
|
|
|
parameters.Callback = Callback;
|
|
parameters.Context = Context;
|
|
parameters.Flags = 0;
|
|
|
|
return PhEnumProcessModulesEx(ProcessHandle, ¶meters);
|
|
}
|
|
|
|
/**
|
|
* Enumerates the modules loaded by a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. If
|
|
* \c PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME is specified in \a Parameters, the handle should
|
|
* have PROCESS_QUERY_INFORMATION access.
|
|
* \param Parameters The enumeration parameters.
|
|
*/
|
|
NTSTATUS PhEnumProcessModulesEx(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPH_ENUM_PROCESS_MODULES_PARAMETERS Parameters
|
|
)
|
|
{
|
|
return PhpEnumProcessModules(
|
|
ProcessHandle,
|
|
PhpEnumProcessModulesCallback,
|
|
Parameters,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
typedef struct _SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT
|
|
{
|
|
NTSTATUS Status;
|
|
PVOID BaseAddress;
|
|
ULONG LoadCount;
|
|
} SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT, *PSET_PROCESS_MODULE_LOAD_COUNT_CONTEXT;
|
|
|
|
BOOLEAN NTAPI PhpSetProcessModuleLoadCountCallback(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PLDR_DATA_TABLE_ENTRY Entry,
|
|
_In_ PVOID AddressOfEntry,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
)
|
|
{
|
|
PSET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context = Context1;
|
|
|
|
if (Entry->DllBase == context->BaseAddress)
|
|
{
|
|
context->Status = NtWriteVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(AddressOfEntry, FIELD_OFFSET(LDR_DATA_TABLE_ENTRY, ObsoleteLoadCount)),
|
|
&context->LoadCount,
|
|
sizeof(USHORT),
|
|
NULL
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Sets the load count of a process module.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ and PROCESS_VM_WRITE access.
|
|
* \param BaseAddress The base address of a module.
|
|
* \param LoadCount The new load count of the module.
|
|
*
|
|
* \retval STATUS_DLL_NOT_FOUND The module was not found.
|
|
*/
|
|
NTSTATUS PhSetProcessModuleLoadCount(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PVOID BaseAddress,
|
|
_In_ ULONG LoadCount
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context;
|
|
|
|
context.Status = STATUS_DLL_NOT_FOUND;
|
|
context.BaseAddress = BaseAddress;
|
|
context.LoadCount = LoadCount;
|
|
|
|
status = PhpEnumProcessModules(
|
|
ProcessHandle,
|
|
PhpSetProcessModuleLoadCountCallback,
|
|
&context,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
return context.Status;
|
|
}
|
|
|
|
NTSTATUS PhpEnumProcessModules32(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPHP_ENUM_PROCESS_MODULES32_CALLBACK Callback,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PPEB32 peb;
|
|
ULONG ldr; // PEB_LDR_DATA32 *32
|
|
PEB_LDR_DATA32 pebLdrData;
|
|
ULONG startLink; // LIST_ENTRY32 *32
|
|
ULONG currentLink; // LIST_ENTRY32 *32
|
|
ULONG dataTableEntrySize;
|
|
LDR_DATA_TABLE_ENTRY32 currentEntry;
|
|
ULONG i;
|
|
|
|
// Get the 32-bit PEB address.
|
|
status = PhGetProcessPeb32(ProcessHandle, &peb);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (!peb)
|
|
return STATUS_NOT_SUPPORTED; // not a WOW64 process
|
|
|
|
// Read the address of the loader data.
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
PTR_ADD_OFFSET(peb, FIELD_OFFSET(PEB32, Ldr)),
|
|
&ldr,
|
|
sizeof(ULONG),
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Read the loader data.
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
UlongToPtr(ldr),
|
|
&pebLdrData,
|
|
sizeof(PEB_LDR_DATA32),
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (!pebLdrData.Initialized)
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
if (WindowsVersion >= WINDOWS_8)
|
|
dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN8_32;
|
|
else if (WindowsVersion >= WINDOWS_7)
|
|
dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WIN7_32;
|
|
else
|
|
dataTableEntrySize = LDR_DATA_TABLE_ENTRY_SIZE_WINXP_32;
|
|
|
|
// Traverse the linked list (in load order).
|
|
|
|
i = 0;
|
|
startLink = (ULONG)(ldr + FIELD_OFFSET(PEB_LDR_DATA32, InLoadOrderModuleList));
|
|
currentLink = pebLdrData.InLoadOrderModuleList.Flink;
|
|
|
|
while (
|
|
currentLink != startLink &&
|
|
i <= PH_ENUM_PROCESS_MODULES_LIMIT
|
|
)
|
|
{
|
|
ULONG addressOfEntry;
|
|
|
|
addressOfEntry = PtrToUlong(CONTAINING_RECORD(UlongToPtr(currentLink), LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks));
|
|
status = NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
UlongToPtr(addressOfEntry),
|
|
¤tEntry,
|
|
dataTableEntrySize,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Make sure the entry is valid.
|
|
if (currentEntry.DllBase)
|
|
{
|
|
// Execute the callback.
|
|
if (!Callback(
|
|
ProcessHandle,
|
|
¤tEntry,
|
|
addressOfEntry,
|
|
Context1,
|
|
Context2
|
|
))
|
|
break;
|
|
}
|
|
|
|
currentLink = currentEntry.InLoadOrderLinks.Flink;
|
|
i++;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOLEAN NTAPI PhpEnumProcessModules32Callback(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PLDR_DATA_TABLE_ENTRY32 Entry,
|
|
_In_ ULONG AddressOfEntry,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
)
|
|
{
|
|
static PH_STRINGREF system32String = PH_STRINGREF_INIT(L"\\system32\\");
|
|
|
|
PPH_ENUM_PROCESS_MODULES_PARAMETERS parameters;
|
|
BOOLEAN cont;
|
|
LDR_DATA_TABLE_ENTRY nativeEntry;
|
|
PPH_STRING mappedFileName;
|
|
PWSTR baseDllNameBuffer;
|
|
PWSTR fullDllNameBuffer;
|
|
PH_STRINGREF fullDllName;
|
|
PH_STRINGREF systemRootString;
|
|
|
|
parameters = Context1;
|
|
|
|
// Convert the 32-bit entry to a native-sized entry.
|
|
|
|
memset(&nativeEntry, 0, sizeof(LDR_DATA_TABLE_ENTRY));
|
|
nativeEntry.DllBase = UlongToPtr(Entry->DllBase);
|
|
nativeEntry.EntryPoint = UlongToPtr(Entry->EntryPoint);
|
|
nativeEntry.SizeOfImage = Entry->SizeOfImage;
|
|
UStr32ToUStr(&nativeEntry.FullDllName, &Entry->FullDllName);
|
|
UStr32ToUStr(&nativeEntry.BaseDllName, &Entry->BaseDllName);
|
|
nativeEntry.Flags = Entry->Flags;
|
|
nativeEntry.ObsoleteLoadCount = Entry->ObsoleteLoadCount;
|
|
nativeEntry.TlsIndex = Entry->TlsIndex;
|
|
nativeEntry.TimeDateStamp = Entry->TimeDateStamp;
|
|
nativeEntry.OriginalBase = Entry->OriginalBase;
|
|
nativeEntry.LoadTime = Entry->LoadTime;
|
|
nativeEntry.BaseNameHashValue = Entry->BaseNameHashValue;
|
|
nativeEntry.LoadReason = Entry->LoadReason;
|
|
|
|
mappedFileName = NULL;
|
|
|
|
if (parameters->Flags & PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME)
|
|
{
|
|
PhGetProcessMappedFileName(ProcessHandle, nativeEntry.DllBase, &mappedFileName);
|
|
}
|
|
|
|
if (mappedFileName)
|
|
{
|
|
ULONG_PTR indexOfLastBackslash;
|
|
|
|
PhStringRefToUnicodeString(&mappedFileName->sr, &nativeEntry.FullDllName);
|
|
indexOfLastBackslash = PhFindLastCharInString(mappedFileName, 0, '\\');
|
|
|
|
if (indexOfLastBackslash != -1)
|
|
{
|
|
nativeEntry.BaseDllName.Buffer = nativeEntry.FullDllName.Buffer + indexOfLastBackslash + 1;
|
|
nativeEntry.BaseDllName.Length = nativeEntry.FullDllName.Length - (USHORT)indexOfLastBackslash * 2 - 2;
|
|
nativeEntry.BaseDllName.MaximumLength = nativeEntry.BaseDllName.Length;
|
|
}
|
|
else
|
|
{
|
|
nativeEntry.BaseDllName = nativeEntry.FullDllName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Read the base DLL name string and add a null terminator.
|
|
|
|
baseDllNameBuffer = PhAllocate(nativeEntry.BaseDllName.Length + 2);
|
|
|
|
if (NT_SUCCESS(NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
nativeEntry.BaseDllName.Buffer,
|
|
baseDllNameBuffer,
|
|
nativeEntry.BaseDllName.Length,
|
|
NULL
|
|
)))
|
|
{
|
|
baseDllNameBuffer[nativeEntry.BaseDllName.Length / 2] = 0;
|
|
}
|
|
else
|
|
{
|
|
baseDllNameBuffer[0] = 0;
|
|
nativeEntry.BaseDllName.Length = 0;
|
|
}
|
|
|
|
nativeEntry.BaseDllName.Buffer = baseDllNameBuffer;
|
|
|
|
// Read the full DLL name string and add a null terminator.
|
|
|
|
fullDllNameBuffer = PhAllocate(nativeEntry.FullDllName.Length + 2);
|
|
|
|
if (NT_SUCCESS(NtReadVirtualMemory(
|
|
ProcessHandle,
|
|
nativeEntry.FullDllName.Buffer,
|
|
fullDllNameBuffer,
|
|
nativeEntry.FullDllName.Length,
|
|
NULL
|
|
)))
|
|
{
|
|
fullDllNameBuffer[nativeEntry.FullDllName.Length / 2] = 0;
|
|
|
|
if (!(parameters->Flags & PH_ENUM_PROCESS_MODULES_DONT_RESOLVE_WOW64_FS))
|
|
{
|
|
// WOW64 file system redirection - convert "system32" to "SysWOW64".
|
|
if (!(nativeEntry.FullDllName.Length & 1)) // validate the string length
|
|
{
|
|
fullDllName.Buffer = fullDllNameBuffer;
|
|
fullDllName.Length = nativeEntry.FullDllName.Length;
|
|
|
|
PhGetSystemRoot(&systemRootString);
|
|
|
|
if (PhStartsWithStringRef(&fullDllName, &systemRootString, TRUE))
|
|
{
|
|
PhSkipStringRef(&fullDllName, systemRootString.Length);
|
|
|
|
if (PhStartsWithStringRef(&fullDllName, &system32String, TRUE))
|
|
{
|
|
fullDllName.Buffer[1] = 'S';
|
|
fullDllName.Buffer[4] = 'W';
|
|
fullDllName.Buffer[5] = 'O';
|
|
fullDllName.Buffer[6] = 'W';
|
|
fullDllName.Buffer[7] = '6';
|
|
fullDllName.Buffer[8] = '4';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fullDllNameBuffer[0] = 0;
|
|
nativeEntry.FullDllName.Length = 0;
|
|
}
|
|
|
|
nativeEntry.FullDllName.Buffer = fullDllNameBuffer;
|
|
}
|
|
|
|
// Execute the callback.
|
|
cont = parameters->Callback(&nativeEntry, parameters->Context);
|
|
|
|
if (mappedFileName)
|
|
{
|
|
PhDereferenceObject(mappedFileName);
|
|
}
|
|
else
|
|
{
|
|
PhFree(baseDllNameBuffer);
|
|
PhFree(fullDllNameBuffer);
|
|
}
|
|
|
|
return cont;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the 32-bit modules loaded by a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access.
|
|
* \param Callback A callback function which is executed for each process module.
|
|
* \param Context A user-defined value to pass to the callback function.
|
|
*
|
|
* \retval STATUS_NOT_SUPPORTED The process is not running under WOW64.
|
|
*
|
|
* \remarks Do not use this function under a 32-bit environment.
|
|
*/
|
|
NTSTATUS PhEnumProcessModules32(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPH_ENUM_PROCESS_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PH_ENUM_PROCESS_MODULES_PARAMETERS parameters;
|
|
|
|
parameters.Callback = Callback;
|
|
parameters.Context = Context;
|
|
parameters.Flags = 0;
|
|
|
|
return PhEnumProcessModules32Ex(ProcessHandle, ¶meters);
|
|
}
|
|
|
|
/**
|
|
* Enumerates the 32-bit modules loaded by a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access. If
|
|
* \c PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME is specified in \a Parameters, the handle should
|
|
* have PROCESS_QUERY_INFORMATION access.
|
|
* \param Parameters The enumeration parameters.
|
|
*
|
|
* \retval STATUS_NOT_SUPPORTED The process is not running under WOW64.
|
|
*
|
|
* \remarks Do not use this function under a 32-bit environment.
|
|
*/
|
|
NTSTATUS PhEnumProcessModules32Ex(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PPH_ENUM_PROCESS_MODULES_PARAMETERS Parameters
|
|
)
|
|
{
|
|
return PhpEnumProcessModules32(
|
|
ProcessHandle,
|
|
PhpEnumProcessModules32Callback,
|
|
Parameters,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
BOOLEAN NTAPI PhpSetProcessModuleLoadCount32Callback(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PLDR_DATA_TABLE_ENTRY32 Entry,
|
|
_In_ ULONG AddressOfEntry,
|
|
_In_opt_ PVOID Context1,
|
|
_In_opt_ PVOID Context2
|
|
)
|
|
{
|
|
PSET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context = Context1;
|
|
|
|
if (UlongToPtr(Entry->DllBase) == context->BaseAddress)
|
|
{
|
|
context->Status = NtWriteVirtualMemory(
|
|
ProcessHandle,
|
|
UlongToPtr(AddressOfEntry + FIELD_OFFSET(LDR_DATA_TABLE_ENTRY32, ObsoleteLoadCount)),
|
|
&context->LoadCount,
|
|
sizeof(USHORT),
|
|
NULL
|
|
);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Sets the load count of a 32-bit process module.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ and PROCESS_VM_WRITE access.
|
|
* \param BaseAddress The base address of a module.
|
|
* \param LoadCount The new load count of the module.
|
|
*
|
|
* \retval STATUS_DLL_NOT_FOUND The module was not found.
|
|
* \retval STATUS_NOT_SUPPORTED The process is not running under WOW64.
|
|
*
|
|
* \remarks Do not use this function under a 32-bit environment.
|
|
*/
|
|
NTSTATUS PhSetProcessModuleLoadCount32(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PVOID BaseAddress,
|
|
_In_ ULONG LoadCount
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
SET_PROCESS_MODULE_LOAD_COUNT_CONTEXT context;
|
|
|
|
context.Status = STATUS_DLL_NOT_FOUND;
|
|
context.BaseAddress = BaseAddress;
|
|
context.LoadCount = LoadCount;
|
|
|
|
status = PhpEnumProcessModules32(
|
|
ProcessHandle,
|
|
PhpSetProcessModuleLoadCount32Callback,
|
|
&context,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
return context.Status;
|
|
}
|
|
|
|
typedef struct _GET_PROCEDURE_ADDRESS_REMOTE_CONTEXT
|
|
{
|
|
PH_STRINGREF FileName;
|
|
PVOID DllBase;
|
|
} GET_PROCEDURE_ADDRESS_REMOTE_CONTEXT, *PGET_PROCEDURE_ADDRESS_REMOTE_CONTEXT;
|
|
|
|
static BOOLEAN PhpGetProcedureAddressRemoteCallback(
|
|
_In_ PLDR_DATA_TABLE_ENTRY Module,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PGET_PROCEDURE_ADDRESS_REMOTE_CONTEXT context = Context;
|
|
PH_STRINGREF fullDllName;
|
|
|
|
PhUnicodeStringToStringRef(&Module->FullDllName, &fullDllName);
|
|
|
|
if (PhEqualStringRef(&fullDllName, &context->FileName, TRUE))
|
|
{
|
|
context->DllBase = Module->DllBase;
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Gets the address of a procedure in a process.
|
|
*
|
|
* \param ProcessHandle A handle to a process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access.
|
|
* \param FileName The file name of the DLL containing the procedure.
|
|
* \param ProcedureName The name of the procedure.
|
|
* \param ProcedureNumber The ordinal of the procedure.
|
|
* \param ProcedureAddress A variable which receives the address of the procedure in the address
|
|
* space of the process.
|
|
* \param DllBase A variable which receives the base address of the DLL containing the procedure.
|
|
*/
|
|
NTSTATUS PhGetProcedureAddressRemote(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ PWSTR FileName,
|
|
_In_opt_ PSTR ProcedureName,
|
|
_In_opt_ ULONG ProcedureNumber,
|
|
_Out_ PVOID *ProcedureAddress,
|
|
_Out_opt_ PVOID *DllBase
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PH_MAPPED_IMAGE mappedImage;
|
|
PH_MAPPED_IMAGE_EXPORTS exports;
|
|
GET_PROCEDURE_ADDRESS_REMOTE_CONTEXT context;
|
|
|
|
if (!NT_SUCCESS(status = PhLoadMappedImage(FileName, NULL, TRUE, &mappedImage)))
|
|
return status;
|
|
|
|
PhInitializeStringRef(&context.FileName, FileName);
|
|
context.DllBase = NULL;
|
|
|
|
if (mappedImage.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
|
|
{
|
|
#ifdef _WIN64
|
|
status = PhEnumProcessModules32(ProcessHandle, PhpGetProcedureAddressRemoteCallback, &context);
|
|
#else
|
|
status = PhEnumProcessModules(ProcessHandle, PhpGetProcedureAddressRemoteCallback, &context);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#ifdef _WIN64
|
|
status = PhEnumProcessModules(ProcessHandle, PhpGetProcedureAddressRemoteCallback, &context);
|
|
#else
|
|
status = STATUS_NOT_SUPPORTED;
|
|
#endif
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
goto CleanupExit;
|
|
|
|
if (!NT_SUCCESS(status = PhGetMappedImageExports(&exports, &mappedImage)))
|
|
goto CleanupExit;
|
|
|
|
status = PhGetMappedImageExportFunctionRemote(
|
|
&exports,
|
|
ProcedureName,
|
|
(USHORT)ProcedureNumber,
|
|
context.DllBase,
|
|
ProcedureAddress
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
if (DllBase)
|
|
*DllBase = context.DllBase;
|
|
}
|
|
|
|
CleanupExit:
|
|
PhUnloadMappedImage(&mappedImage);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the modules loaded by the kernel.
|
|
*
|
|
* \param Modules A variable which receives a pointer to a structure containing information about
|
|
* the kernel modules. You must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhEnumKernelModules(
|
|
_Out_ PRTL_PROCESS_MODULES *Modules
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 2048;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemModuleInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemModuleInformation,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
*Modules = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the modules loaded by the kernel.
|
|
*
|
|
* \param Modules A variable which receives a pointer to a structure containing information about
|
|
* the kernel modules. You must free the structure using PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhEnumKernelModulesEx(
|
|
_Out_ PRTL_PROCESS_MODULE_INFORMATION_EX *Modules
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 2048;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemModuleInformationEx,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemModuleInformationEx,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
*Modules = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets the file name of the kernel image.
|
|
*
|
|
* \return A pointer to a string containing the kernel image file name. You must free the string
|
|
* using PhDereferenceObject() when you no longer need it.
|
|
*/
|
|
PPH_STRING PhGetKernelFileName(
|
|
VOID
|
|
)
|
|
{
|
|
PRTL_PROCESS_MODULES modules;
|
|
PPH_STRING fileName = NULL;
|
|
|
|
if (!NT_SUCCESS(PhEnumKernelModules(&modules)))
|
|
return NULL;
|
|
|
|
if (modules->NumberOfModules >= 1)
|
|
{
|
|
fileName = PhConvertMultiByteToUtf16(modules->Modules[0].FullPathName);
|
|
}
|
|
|
|
PhFree(modules);
|
|
|
|
return fileName;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the running processes.
|
|
*
|
|
* \param Processes A variable which receives a pointer to a buffer containing process information.
|
|
* You must free the buffer using PhFree() when you no longer need it.
|
|
*
|
|
* \remarks You can use the \ref PH_FIRST_PROCESS and \ref PH_NEXT_PROCESS macros to process the
|
|
* information contained in the buffer.
|
|
*/
|
|
NTSTATUS PhEnumProcesses(
|
|
_Out_ PVOID *Processes
|
|
)
|
|
{
|
|
return PhEnumProcessesEx(Processes, SystemProcessInformation);
|
|
}
|
|
|
|
/**
|
|
* Enumerates the running processes.
|
|
*
|
|
* \param Processes A variable which receives a pointer to a buffer containing process information.
|
|
* You must free the buffer using PhFree() when you no longer need it.
|
|
*
|
|
* \remarks You can use the \ref PH_FIRST_PROCESS and \ref PH_NEXT_PROCESS macros to process the
|
|
* information contained in the buffer.
|
|
*/
|
|
NTSTATUS PhEnumProcessesEx(
|
|
_Out_ PVOID *Processes,
|
|
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass
|
|
)
|
|
{
|
|
static ULONG initialBufferSize[3] = { 0x4000, 0x4000, 0x4000 };
|
|
NTSTATUS status;
|
|
ULONG classIndex;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
|
|
switch (SystemInformationClass)
|
|
{
|
|
case SystemProcessInformation:
|
|
classIndex = 0;
|
|
break;
|
|
case SystemExtendedProcessInformation:
|
|
classIndex = 1;
|
|
break;
|
|
case SystemFullProcessInformation:
|
|
classIndex = 2;
|
|
break;
|
|
default:
|
|
return STATUS_INVALID_INFO_CLASS;
|
|
}
|
|
|
|
bufferSize = initialBufferSize[classIndex];
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
status = NtQuerySystemInformation(
|
|
SystemInformationClass,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
if (bufferSize <= 0x100000) initialBufferSize[classIndex] = bufferSize;
|
|
*Processes = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the running processes for a session.
|
|
*
|
|
* \param Processes A variable which receives a pointer to a buffer containing process information.
|
|
* You must free the buffer using PhFree() when you no longer need it.
|
|
* \param SessionId A session ID.
|
|
*
|
|
* \remarks You can use the \ref PH_FIRST_PROCESS and \ref PH_NEXT_PROCESS macros to process the
|
|
* information contained in the buffer.
|
|
*/
|
|
NTSTATUS PhEnumProcessesForSession(
|
|
_Out_ PVOID *Processes,
|
|
_In_ ULONG SessionId
|
|
)
|
|
{
|
|
static ULONG initialBufferSize = 0x4000;
|
|
NTSTATUS status;
|
|
SYSTEM_SESSION_PROCESS_INFORMATION sessionProcessInfo;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
|
|
bufferSize = initialBufferSize;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
sessionProcessInfo.SessionId = SessionId;
|
|
|
|
while (TRUE)
|
|
{
|
|
sessionProcessInfo.SizeOfBuf = bufferSize;
|
|
sessionProcessInfo.Buffer = buffer;
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemSessionProcessInformation,
|
|
&sessionProcessInfo,
|
|
sizeof(SYSTEM_SESSION_PROCESS_INFORMATION),
|
|
&bufferSize // size of the inner buffer gets returned
|
|
);
|
|
|
|
if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
if (bufferSize <= 0x100000) initialBufferSize = bufferSize;
|
|
*Processes = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Finds the process information structure for a specific process.
|
|
*
|
|
* \param Processes A pointer to a buffer returned by PhEnumProcesses().
|
|
* \param ProcessId The ID of the process.
|
|
*
|
|
* \return A pointer to the process information structure for the specified process, or NULL if the
|
|
* structure could not be found.
|
|
*/
|
|
PSYSTEM_PROCESS_INFORMATION PhFindProcessInformation(
|
|
_In_ PVOID Processes,
|
|
_In_ HANDLE ProcessId
|
|
)
|
|
{
|
|
PSYSTEM_PROCESS_INFORMATION process;
|
|
|
|
process = PH_FIRST_PROCESS(Processes);
|
|
|
|
do
|
|
{
|
|
if (process->UniqueProcessId == ProcessId)
|
|
return process;
|
|
} while (process = PH_NEXT_PROCESS(process));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Finds the process information structure for a specific process.
|
|
*
|
|
* \param Processes A pointer to a buffer returned by PhEnumProcesses().
|
|
* \param ImageName The image name to search for.
|
|
*
|
|
* \return A pointer to the process information structure for the specified process, or NULL if the
|
|
* structure could not be found.
|
|
*/
|
|
PSYSTEM_PROCESS_INFORMATION PhFindProcessInformationByImageName(
|
|
_In_ PVOID Processes,
|
|
_In_ PPH_STRINGREF ImageName
|
|
)
|
|
{
|
|
PSYSTEM_PROCESS_INFORMATION process;
|
|
PH_STRINGREF processImageName;
|
|
|
|
process = PH_FIRST_PROCESS(Processes);
|
|
|
|
do
|
|
{
|
|
PhUnicodeStringToStringRef(&process->ImageName, &processImageName);
|
|
|
|
if (PhEqualStringRef(&processImageName, ImageName, TRUE))
|
|
return process;
|
|
} while (process = PH_NEXT_PROCESS(process));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Enumerates all open handles.
|
|
*
|
|
* \param Handles A variable which receives a pointer to a structure containing information about
|
|
* all opened handles. You must free the structure using PhFree() when you no longer need it.
|
|
*
|
|
* \retval STATUS_INSUFFICIENT_RESOURCES The handle information returned by the kernel is too large.
|
|
*/
|
|
NTSTATUS PhEnumHandles(
|
|
_Out_ PSYSTEM_HANDLE_INFORMATION *Handles
|
|
)
|
|
{
|
|
static ULONG initialBufferSize = 0x4000;
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
|
|
bufferSize = initialBufferSize;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while ((status = NtQuerySystemInformation(
|
|
SystemHandleInformation,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
)) == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
// Fail if we're resizing the buffer to something very large.
|
|
if (bufferSize > PH_LARGE_BUFFER_SIZE)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
if (bufferSize <= 0x100000) initialBufferSize = bufferSize;
|
|
*Handles = (PSYSTEM_HANDLE_INFORMATION)buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Enumerates all open handles.
|
|
*
|
|
* \param Handles A variable which receives a pointer to a structure containing information about
|
|
* all opened handles. You must free the structure using PhFree() when you no longer need it.
|
|
*
|
|
* \retval STATUS_INSUFFICIENT_RESOURCES The handle information returned by the kernel is too large.
|
|
*
|
|
* \remarks This function is only available starting with Windows XP.
|
|
*/
|
|
NTSTATUS PhEnumHandlesEx(
|
|
_Out_ PSYSTEM_HANDLE_INFORMATION_EX *Handles
|
|
)
|
|
{
|
|
static ULONG initialBufferSize = 0x10000;
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
|
|
bufferSize = initialBufferSize;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while ((status = NtQuerySystemInformation(
|
|
SystemExtendedHandleInformation,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
)) == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
// Fail if we're resizing the buffer to something very large.
|
|
if (bufferSize > PH_LARGE_BUFFER_SIZE)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
if (bufferSize <= 0x200000) initialBufferSize = bufferSize;
|
|
*Handles = (PSYSTEM_HANDLE_INFORMATION_EX)buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Enumerates all pagefiles.
|
|
*
|
|
* \param Pagefiles A variable which receives a pointer to a buffer containing information about all
|
|
* active pagefiles. You must free the structure using PhFree() when you no longer need it.
|
|
*
|
|
* \retval STATUS_INSUFFICIENT_RESOURCES The handle information returned by the kernel is too large.
|
|
*/
|
|
NTSTATUS PhEnumPagefiles(
|
|
_Out_ PVOID *Pagefiles
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x200;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while ((status = NtQuerySystemInformation(
|
|
SystemPageFileInformation,
|
|
buffer,
|
|
bufferSize,
|
|
NULL
|
|
)) == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
|
|
// Fail if we're resizing the buffer to something very large.
|
|
if (bufferSize > PH_LARGE_BUFFER_SIZE)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
*Pagefiles = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets the file name of a process' image.
|
|
*
|
|
* \param ProcessId The ID of the process.
|
|
* \param FileName A variable which receives a pointer to a string containing the file name. You
|
|
* must free the string using PhDereferenceObject() when you no longer need it.
|
|
*
|
|
* \remarks This function only works on Windows Vista and above. There does not appear to be any
|
|
* access checking performed by the kernel for this.
|
|
*/
|
|
NTSTATUS PhGetProcessImageFileNameByProcessId(
|
|
_In_ HANDLE ProcessId,
|
|
_Out_ PPH_STRING *FileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x100;
|
|
SYSTEM_PROCESS_ID_INFORMATION processIdInfo;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
processIdInfo.ProcessId = ProcessId;
|
|
processIdInfo.ImageName.Length = 0;
|
|
processIdInfo.ImageName.MaximumLength = (USHORT)bufferSize;
|
|
processIdInfo.ImageName.Buffer = buffer;
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemProcessIdInformation,
|
|
&processIdInfo,
|
|
sizeof(SYSTEM_PROCESS_ID_INFORMATION),
|
|
NULL
|
|
);
|
|
|
|
if (status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
// Required length is stored in MaximumLength.
|
|
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(processIdInfo.ImageName.MaximumLength);
|
|
processIdInfo.ImageName.Buffer = buffer;
|
|
|
|
status = NtQuerySystemInformation(
|
|
SystemProcessIdInformation,
|
|
&processIdInfo,
|
|
sizeof(SYSTEM_PROCESS_ID_INFORMATION),
|
|
NULL
|
|
);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
*FileName = PhCreateStringFromUnicodeString(&processIdInfo.ImageName);
|
|
PhFree(buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Determines if a process is managed.
|
|
*
|
|
* \param ProcessId The ID of the process.
|
|
* \param IsDotNet A variable which receives a boolean indicating whether the process is managed.
|
|
*/
|
|
NTSTATUS PhGetProcessIsDotNet(
|
|
_In_ HANDLE ProcessId,
|
|
_Out_ PBOOLEAN IsDotNet
|
|
)
|
|
{
|
|
return PhGetProcessIsDotNetEx(ProcessId, NULL, 0, IsDotNet, NULL);
|
|
}
|
|
|
|
BOOLEAN NTAPI PhpIsDotNetEnumProcessModulesCallback(
|
|
_In_ PLDR_DATA_TABLE_ENTRY Module,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
static UNICODE_STRING clrString = RTL_CONSTANT_STRING(L"clr.dll");
|
|
static UNICODE_STRING mscorwksString = RTL_CONSTANT_STRING(L"mscorwks.dll");
|
|
static UNICODE_STRING mscorsvrString = RTL_CONSTANT_STRING(L"mscorsvr.dll");
|
|
static UNICODE_STRING mscorlibString = RTL_CONSTANT_STRING(L"mscorlib.dll");
|
|
static UNICODE_STRING mscorlibNiString = RTL_CONSTANT_STRING(L"mscorlib.ni.dll");
|
|
static UNICODE_STRING clrjitString = RTL_CONSTANT_STRING(L"clrjit.dll");
|
|
static UNICODE_STRING frameworkString = RTL_CONSTANT_STRING(L"\\Microsoft.NET\\Framework\\");
|
|
static UNICODE_STRING framework64String = RTL_CONSTANT_STRING(L"\\Microsoft.NET\\Framework64\\");
|
|
|
|
if (
|
|
RtlEqualUnicodeString(&Module->BaseDllName, &clrString, TRUE) ||
|
|
RtlEqualUnicodeString(&Module->BaseDllName, &mscorwksString, TRUE) ||
|
|
RtlEqualUnicodeString(&Module->BaseDllName, &mscorsvrString, TRUE)
|
|
)
|
|
{
|
|
UNICODE_STRING fileName;
|
|
PH_STRINGREF systemRootSr;
|
|
UNICODE_STRING systemRoot;
|
|
PUNICODE_STRING frameworkPart;
|
|
|
|
#ifdef _WIN64
|
|
if (*(PULONG)Context & PH_CLR_PROCESS_IS_WOW64)
|
|
{
|
|
#endif
|
|
frameworkPart = &frameworkString;
|
|
#ifdef _WIN64
|
|
}
|
|
else
|
|
{
|
|
frameworkPart = &framework64String;
|
|
}
|
|
#endif
|
|
|
|
fileName = Module->FullDllName;
|
|
PhGetSystemRoot(&systemRootSr);
|
|
PhStringRefToUnicodeString(&systemRootSr, &systemRoot);
|
|
|
|
if (RtlPrefixUnicodeString(&systemRoot, &fileName, TRUE))
|
|
{
|
|
fileName.Buffer = (PWCHAR)((PCHAR)fileName.Buffer + systemRoot.Length);
|
|
fileName.Length -= systemRoot.Length;
|
|
|
|
if (RtlPrefixUnicodeString(frameworkPart, &fileName, TRUE))
|
|
{
|
|
fileName.Buffer = (PWCHAR)((PCHAR)fileName.Buffer + frameworkPart->Length);
|
|
fileName.Length -= frameworkPart->Length;
|
|
|
|
if (fileName.Length >= 4 * sizeof(WCHAR)) // vx.x
|
|
{
|
|
if (fileName.Buffer[1] == '1')
|
|
{
|
|
if (fileName.Buffer[3] == '0')
|
|
*(PULONG)Context |= PH_CLR_VERSION_1_0;
|
|
else if (fileName.Buffer[3] == '1')
|
|
*(PULONG)Context |= PH_CLR_VERSION_1_1;
|
|
}
|
|
else if (fileName.Buffer[1] == '2')
|
|
{
|
|
*(PULONG)Context |= PH_CLR_VERSION_2_0;
|
|
}
|
|
else if (fileName.Buffer[1] >= '4' && fileName.Buffer[1] <= '9')
|
|
{
|
|
*(PULONG)Context |= PH_CLR_VERSION_4_ABOVE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (
|
|
RtlEqualUnicodeString(&Module->BaseDllName, &mscorlibString, TRUE) ||
|
|
RtlEqualUnicodeString(&Module->BaseDllName, &mscorlibNiString, TRUE)
|
|
)
|
|
{
|
|
*(PULONG)Context |= PH_CLR_MSCORLIB_PRESENT;
|
|
}
|
|
else if (RtlEqualUnicodeString(&Module->BaseDllName, &clrjitString, TRUE))
|
|
{
|
|
*(PULONG)Context |= PH_CLR_JIT_PRESENT;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Determines if a process is managed.
|
|
*
|
|
* \param ProcessId The ID of the process.
|
|
* \param ProcessHandle An optional handle to the process. The handle must have
|
|
* PROCESS_QUERY_LIMITED_INFORMATION and PROCESS_VM_READ access.
|
|
* \param InFlags A combination of flags.
|
|
* \li \c PH_CLR_USE_SECTION_CHECK Checks for the existence of related section objects to determine
|
|
* whether the process is managed.
|
|
* \li \c PH_CLR_NO_WOW64_CHECK Instead of a separate query, uses the presence of the
|
|
* \c PH_CLR_KNOWN_IS_WOW64 flag to determine whether the process is running under WOW64.
|
|
* \li \c PH_CLR_KNOWN_IS_WOW64 When \c PH_CLR_NO_WOW64_CHECK is specified, indicates that the
|
|
* process is managed.
|
|
* \param IsDotNet A variable which receives a boolean indicating whether the process is managed.
|
|
* \param Flags A variable which receives additional flags.
|
|
*/
|
|
NTSTATUS PhGetProcessIsDotNetEx(
|
|
_In_ HANDLE ProcessId,
|
|
_In_opt_ HANDLE ProcessHandle,
|
|
_In_ ULONG InFlags,
|
|
_Out_opt_ PBOOLEAN IsDotNet,
|
|
_Out_opt_ PULONG Flags
|
|
)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
HANDLE processHandle;
|
|
ULONG flags;
|
|
#ifdef _WIN64
|
|
BOOLEAN isWow64;
|
|
#endif
|
|
|
|
if (InFlags & PH_CLR_USE_SECTION_CHECK)
|
|
{
|
|
HANDLE sectionHandle;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
PPH_STRING sectionName;
|
|
UNICODE_STRING sectionNameUs;
|
|
PH_FORMAT format[2];
|
|
|
|
// Most .NET processes have a handle open to a section named
|
|
// \BaseNamedObjects\Cor_Private_IPCBlock(_v4)_<ProcessId>. This is the same object used by
|
|
// the ICorPublish::GetProcess function. Instead of calling that function, we simply check
|
|
// for the existence of that section object. This means:
|
|
// * Better performance.
|
|
// * No need for admin rights to get .NET status of processes owned by other users.
|
|
|
|
PhInitFormatIU(&format[1], (ULONG_PTR)ProcessId);
|
|
|
|
// Version 4 section object
|
|
|
|
PhInitFormatS(&format[0], L"\\BaseNamedObjects\\Cor_Private_IPCBlock_v4_");
|
|
sectionName = PhFormat(format, 2, 96);
|
|
PhStringRefToUnicodeString(§ionName->sr, §ionNameUs);
|
|
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
§ionNameUs,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
status = NtOpenSection(
|
|
§ionHandle,
|
|
SECTION_QUERY,
|
|
&objectAttributes
|
|
);
|
|
PhDereferenceObject(sectionName);
|
|
|
|
if (NT_SUCCESS(status) || status == STATUS_ACCESS_DENIED)
|
|
{
|
|
if (NT_SUCCESS(status))
|
|
NtClose(sectionHandle);
|
|
|
|
if (IsDotNet)
|
|
*IsDotNet = TRUE;
|
|
|
|
if (Flags)
|
|
*Flags = PH_CLR_VERSION_4_ABOVE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Version 2 section object
|
|
|
|
PhInitFormatS(&format[0], L"\\BaseNamedObjects\\Cor_Private_IPCBlock_");
|
|
sectionName = PhFormat(format, 2, 90);
|
|
PhStringRefToUnicodeString(§ionName->sr, §ionNameUs);
|
|
|
|
InitializeObjectAttributes(
|
|
&objectAttributes,
|
|
§ionNameUs,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
status = NtOpenSection(
|
|
§ionHandle,
|
|
SECTION_QUERY,
|
|
&objectAttributes
|
|
);
|
|
PhDereferenceObject(sectionName);
|
|
|
|
if (NT_SUCCESS(status) || status == STATUS_ACCESS_DENIED)
|
|
{
|
|
if (NT_SUCCESS(status))
|
|
NtClose(sectionHandle);
|
|
|
|
if (IsDotNet)
|
|
*IsDotNet = TRUE;
|
|
|
|
if (Flags)
|
|
*Flags = PH_CLR_VERSION_2_0;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
flags = 0;
|
|
processHandle = NULL;
|
|
|
|
if (!ProcessHandle)
|
|
{
|
|
if (!NT_SUCCESS(status = PhOpenProcess(&processHandle, ProcessQueryAccess | PROCESS_VM_READ, ProcessId)))
|
|
return status;
|
|
|
|
ProcessHandle = processHandle;
|
|
}
|
|
|
|
#ifdef _WIN64
|
|
if (InFlags & PH_CLR_NO_WOW64_CHECK)
|
|
{
|
|
isWow64 = !!(InFlags & PH_CLR_KNOWN_IS_WOW64);
|
|
}
|
|
else
|
|
{
|
|
isWow64 = FALSE;
|
|
PhGetProcessIsWow64(ProcessHandle, &isWow64);
|
|
}
|
|
|
|
if (isWow64)
|
|
{
|
|
flags |= PH_CLR_PROCESS_IS_WOW64;
|
|
PhEnumProcessModules32(ProcessHandle, PhpIsDotNetEnumProcessModulesCallback, &flags);
|
|
}
|
|
else
|
|
{
|
|
#endif
|
|
PhEnumProcessModules(ProcessHandle, PhpIsDotNetEnumProcessModulesCallback, &flags);
|
|
#ifdef _WIN64
|
|
}
|
|
#endif
|
|
|
|
if (processHandle)
|
|
NtClose(processHandle);
|
|
|
|
if (IsDotNet)
|
|
*IsDotNet = (flags & PH_CLR_VERSION_MASK) && (flags & (PH_CLR_MSCORLIB_PRESENT | PH_CLR_JIT_PRESENT));
|
|
|
|
if (Flags)
|
|
*Flags = flags;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Enumerates the objects in a directory object.
|
|
*
|
|
* \param DirectoryHandle A handle to a directory. The handle must have DIRECTORY_QUERY access.
|
|
* \param Callback A callback function which is executed for each object.
|
|
* \param Context A user-defined value to pass to the callback function.
|
|
*/
|
|
NTSTATUS PhEnumDirectoryObjects(
|
|
_In_ HANDLE DirectoryHandle,
|
|
_In_ PPH_ENUM_DIRECTORY_OBJECTS Callback,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
ULONG context = 0;
|
|
BOOLEAN firstTime = TRUE;
|
|
ULONG bufferSize;
|
|
POBJECT_DIRECTORY_INFORMATION buffer;
|
|
ULONG i;
|
|
BOOLEAN cont;
|
|
|
|
bufferSize = 0x200;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
// Get a batch of entries.
|
|
|
|
while ((status = NtQueryDirectoryObject(
|
|
DirectoryHandle,
|
|
buffer,
|
|
bufferSize,
|
|
FALSE,
|
|
firstTime,
|
|
&context,
|
|
NULL
|
|
)) == STATUS_MORE_ENTRIES)
|
|
{
|
|
// Check if we have at least one entry. If not, we'll double the buffer size and try
|
|
// again.
|
|
if (buffer[0].Name.Buffer)
|
|
break;
|
|
|
|
// Make sure we don't use too much memory.
|
|
if (bufferSize > PH_LARGE_BUFFER_SIZE)
|
|
{
|
|
PhFree(buffer);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
|
|
// Read the batch and execute the callback function for each object.
|
|
|
|
i = 0;
|
|
cont = TRUE;
|
|
|
|
while (TRUE)
|
|
{
|
|
POBJECT_DIRECTORY_INFORMATION info;
|
|
PH_STRINGREF name;
|
|
PH_STRINGREF typeName;
|
|
|
|
info = &buffer[i];
|
|
|
|
if (!info->Name.Buffer)
|
|
break;
|
|
|
|
PhUnicodeStringToStringRef(&info->Name, &name);
|
|
PhUnicodeStringToStringRef(&info->TypeName, &typeName);
|
|
|
|
cont = Callback(&name, &typeName, Context);
|
|
|
|
if (!cont)
|
|
break;
|
|
|
|
i++;
|
|
}
|
|
|
|
if (!cont)
|
|
break;
|
|
|
|
if (status != STATUS_MORE_ENTRIES)
|
|
break;
|
|
|
|
firstTime = FALSE;
|
|
}
|
|
|
|
PhFree(buffer);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS PhEnumDirectoryFile(
|
|
_In_ HANDLE FileHandle,
|
|
_In_opt_ PUNICODE_STRING SearchPattern,
|
|
_In_ PPH_ENUM_DIRECTORY_FILE Callback,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK isb;
|
|
BOOLEAN firstTime = TRUE;
|
|
PVOID buffer;
|
|
ULONG bufferSize = 0x400;
|
|
ULONG i;
|
|
BOOLEAN cont;
|
|
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
while (TRUE)
|
|
{
|
|
// Query the directory, doubling the buffer each time NtQueryDirectoryFile fails.
|
|
while (TRUE)
|
|
{
|
|
status = NtQueryDirectoryFile(
|
|
FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&isb,
|
|
buffer,
|
|
bufferSize,
|
|
FileDirectoryInformation,
|
|
FALSE,
|
|
SearchPattern,
|
|
firstTime
|
|
);
|
|
|
|
// Our ISB is on the stack, so we have to wait for the operation to complete before
|
|
// continuing.
|
|
if (status == STATUS_PENDING)
|
|
{
|
|
status = NtWaitForSingleObject(FileHandle, FALSE, NULL);
|
|
|
|
if (NT_SUCCESS(status))
|
|
status = isb.Status;
|
|
}
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW || status == STATUS_INFO_LENGTH_MISMATCH)
|
|
{
|
|
PhFree(buffer);
|
|
bufferSize *= 2;
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we don't have any entries to read, exit.
|
|
if (status == STATUS_NO_MORE_FILES)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
if (!NT_SUCCESS(status))
|
|
break;
|
|
|
|
// Read the batch and execute the callback function for each file.
|
|
|
|
i = 0;
|
|
cont = TRUE;
|
|
|
|
while (TRUE)
|
|
{
|
|
PFILE_DIRECTORY_INFORMATION information;
|
|
|
|
information = (PFILE_DIRECTORY_INFORMATION)(PTR_ADD_OFFSET(buffer, i));
|
|
|
|
if (!Callback(
|
|
information,
|
|
Context
|
|
))
|
|
{
|
|
cont = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (information->NextEntryOffset != 0)
|
|
i += information->NextEntryOffset;
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (!cont)
|
|
break;
|
|
|
|
firstTime = FALSE;
|
|
}
|
|
|
|
PhFree(buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhEnumFileStreams(
|
|
_In_ HANDLE FileHandle,
|
|
_Out_ PVOID *Streams
|
|
)
|
|
{
|
|
return PhpQueryFileVariableSize(
|
|
FileHandle,
|
|
FileStreamInformation,
|
|
Streams
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Initializes the device prefixes module.
|
|
*/
|
|
VOID PhpInitializeDevicePrefixes(
|
|
VOID
|
|
)
|
|
{
|
|
ULONG i;
|
|
PUCHAR buffer;
|
|
|
|
// Allocate one buffer for all 26 prefixes to reduce overhead.
|
|
buffer = PhAllocate(PH_DEVICE_PREFIX_LENGTH * sizeof(WCHAR) * 26);
|
|
|
|
for (i = 0; i < 26; i++)
|
|
{
|
|
PhDevicePrefixes[i].Length = 0;
|
|
PhDevicePrefixes[i].MaximumLength = PH_DEVICE_PREFIX_LENGTH * sizeof(WCHAR);
|
|
PhDevicePrefixes[i].Buffer = (PWCHAR)buffer;
|
|
buffer += PH_DEVICE_PREFIX_LENGTH * sizeof(WCHAR);
|
|
}
|
|
}
|
|
|
|
VOID PhUpdateMupDevicePrefixes(
|
|
VOID
|
|
)
|
|
{
|
|
static PH_STRINGREF orderKeyName = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Control\\NetworkProvider\\Order");
|
|
static PH_STRINGREF servicesStringPart = PH_STRINGREF_INIT(L"System\\CurrentControlSet\\Services\\");
|
|
static PH_STRINGREF networkProviderStringPart = PH_STRINGREF_INIT(L"\\NetworkProvider");
|
|
|
|
HANDLE orderKeyHandle;
|
|
PPH_STRING providerOrder = NULL;
|
|
ULONG i;
|
|
PH_STRINGREF remainingPart;
|
|
PH_STRINGREF part;
|
|
|
|
// The provider names are stored in the ProviderOrder value in this key:
|
|
// HKLM\System\CurrentControlSet\Control\NetworkProvider\Order
|
|
// Each name can then be looked up, its device name in the DeviceName value in:
|
|
// HKLM\System\CurrentControlSet\Services\<ProviderName>\NetworkProvider
|
|
|
|
// Note that we assume the providers only claim their device name. Some providers such as DFS
|
|
// claim an extra part, and are not resolved correctly here.
|
|
|
|
if (NT_SUCCESS(PhOpenKey(
|
|
&orderKeyHandle,
|
|
KEY_READ,
|
|
PH_KEY_LOCAL_MACHINE,
|
|
&orderKeyName,
|
|
0
|
|
)))
|
|
{
|
|
providerOrder = PhQueryRegistryString(orderKeyHandle, L"ProviderOrder");
|
|
NtClose(orderKeyHandle);
|
|
}
|
|
|
|
if (!providerOrder)
|
|
return;
|
|
|
|
PhAcquireQueuedLockExclusive(&PhDeviceMupPrefixesLock);
|
|
|
|
for (i = 0; i < PhDeviceMupPrefixesCount; i++)
|
|
{
|
|
PhDereferenceObject(PhDeviceMupPrefixes[i]);
|
|
PhDeviceMupPrefixes[i] = NULL;
|
|
}
|
|
|
|
PhDeviceMupPrefixesCount = 0;
|
|
|
|
PhDeviceMupPrefixes[PhDeviceMupPrefixesCount++] = PhCreateString(L"\\Device\\Mup");
|
|
|
|
// DFS claims an extra part of file names, which we don't handle.
|
|
/*if (WindowsVersion >= WINDOWS_VISTA)
|
|
PhDeviceMupPrefixes[PhDeviceMupPrefixesCount++] = PhCreateString(L"\\Device\\DfsClient");
|
|
else
|
|
PhDeviceMupPrefixes[PhDeviceMupPrefixesCount++] = PhCreateString(L"\\Device\\WinDfs");*/
|
|
|
|
remainingPart = providerOrder->sr;
|
|
|
|
while (remainingPart.Length != 0)
|
|
{
|
|
PPH_STRING serviceKeyName;
|
|
HANDLE networkProviderKeyHandle;
|
|
PPH_STRING deviceName;
|
|
|
|
if (PhDeviceMupPrefixesCount == PH_DEVICE_MUP_PREFIX_MAX_COUNT)
|
|
break;
|
|
|
|
PhSplitStringRefAtChar(&remainingPart, ',', &part, &remainingPart);
|
|
|
|
if (part.Length != 0)
|
|
{
|
|
serviceKeyName = PhConcatStringRef3(&servicesStringPart, &part, &networkProviderStringPart);
|
|
|
|
if (NT_SUCCESS(PhOpenKey(
|
|
&networkProviderKeyHandle,
|
|
KEY_READ,
|
|
PH_KEY_LOCAL_MACHINE,
|
|
&serviceKeyName->sr,
|
|
0
|
|
)))
|
|
{
|
|
if (deviceName = PhQueryRegistryString(networkProviderKeyHandle, L"DeviceName"))
|
|
{
|
|
PhDeviceMupPrefixes[PhDeviceMupPrefixesCount] = deviceName;
|
|
PhDeviceMupPrefixesCount++;
|
|
}
|
|
|
|
NtClose(networkProviderKeyHandle);
|
|
}
|
|
|
|
PhDereferenceObject(serviceKeyName);
|
|
}
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&PhDeviceMupPrefixesLock);
|
|
|
|
PhDereferenceObject(providerOrder);
|
|
}
|
|
|
|
/**
|
|
* Updates the DOS device names array.
|
|
*/
|
|
VOID PhUpdateDosDevicePrefixes(
|
|
VOID
|
|
)
|
|
{
|
|
WCHAR deviceNameBuffer[7] = L"\\??\\ :";
|
|
ULONG i;
|
|
|
|
for (i = 0; i < 26; i++)
|
|
{
|
|
HANDLE linkHandle;
|
|
OBJECT_ATTRIBUTES oa;
|
|
UNICODE_STRING deviceName;
|
|
|
|
deviceNameBuffer[4] = (WCHAR)('A' + i);
|
|
deviceName.Buffer = deviceNameBuffer;
|
|
deviceName.Length = 6 * sizeof(WCHAR);
|
|
|
|
InitializeObjectAttributes(
|
|
&oa,
|
|
&deviceName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (NT_SUCCESS(NtOpenSymbolicLinkObject(
|
|
&linkHandle,
|
|
SYMBOLIC_LINK_QUERY,
|
|
&oa
|
|
)))
|
|
{
|
|
PhAcquireQueuedLockExclusive(&PhDevicePrefixesLock);
|
|
|
|
if (!NT_SUCCESS(NtQuerySymbolicLinkObject(
|
|
linkHandle,
|
|
&PhDevicePrefixes[i],
|
|
NULL
|
|
)))
|
|
{
|
|
PhDevicePrefixes[i].Length = 0;
|
|
}
|
|
|
|
PhReleaseQueuedLockExclusive(&PhDevicePrefixesLock);
|
|
|
|
NtClose(linkHandle);
|
|
}
|
|
else
|
|
{
|
|
PhDevicePrefixes[i].Length = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolves a NT path into a Win32 path.
|
|
*
|
|
* \param Name A string containing the path to resolve.
|
|
*
|
|
* \return A pointer to a string containing the Win32 path. You must free the string using
|
|
* PhDereferenceObject() when you no longer need it.
|
|
*/
|
|
PPH_STRING PhResolveDevicePrefix(
|
|
_In_ PPH_STRING Name
|
|
)
|
|
{
|
|
ULONG i;
|
|
PPH_STRING newName = NULL;
|
|
|
|
if (PhBeginInitOnce(&PhDevicePrefixesInitOnce))
|
|
{
|
|
PhpInitializeDevicePrefixes();
|
|
PhUpdateDosDevicePrefixes();
|
|
PhUpdateMupDevicePrefixes();
|
|
|
|
PhEndInitOnce(&PhDevicePrefixesInitOnce);
|
|
}
|
|
|
|
// Go through the DOS devices and try to find a matching prefix.
|
|
for (i = 0; i < 26; i++)
|
|
{
|
|
BOOLEAN isPrefix = FALSE;
|
|
PH_STRINGREF prefix;
|
|
|
|
PhAcquireQueuedLockShared(&PhDevicePrefixesLock);
|
|
|
|
PhUnicodeStringToStringRef(&PhDevicePrefixes[i], &prefix);
|
|
|
|
if (prefix.Length != 0)
|
|
{
|
|
if (PhStartsWithStringRef(&Name->sr, &prefix, TRUE))
|
|
{
|
|
// To ensure we match the longest prefix, make sure the next character is a
|
|
// backslash or the path is equal to the prefix.
|
|
if (Name->Length == prefix.Length || Name->Buffer[prefix.Length / sizeof(WCHAR)] == '\\')
|
|
{
|
|
isPrefix = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
PhReleaseQueuedLockShared(&PhDevicePrefixesLock);
|
|
|
|
if (isPrefix)
|
|
{
|
|
// <letter>:path
|
|
newName = PhCreateStringEx(NULL, 2 * sizeof(WCHAR) + Name->Length - prefix.Length);
|
|
newName->Buffer[0] = (WCHAR)('A' + i);
|
|
newName->Buffer[1] = ':';
|
|
memcpy(
|
|
&newName->Buffer[2],
|
|
&Name->Buffer[prefix.Length / sizeof(WCHAR)],
|
|
Name->Length - prefix.Length
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == 26)
|
|
{
|
|
// Resolve network providers.
|
|
|
|
PhAcquireQueuedLockShared(&PhDeviceMupPrefixesLock);
|
|
|
|
for (i = 0; i < PhDeviceMupPrefixesCount; i++)
|
|
{
|
|
BOOLEAN isPrefix = FALSE;
|
|
SIZE_T prefixLength;
|
|
|
|
prefixLength = PhDeviceMupPrefixes[i]->Length;
|
|
|
|
if (prefixLength != 0)
|
|
{
|
|
if (PhStartsWithString(Name, PhDeviceMupPrefixes[i], TRUE))
|
|
{
|
|
// To ensure we match the longest prefix, make sure the next character is a
|
|
// backslash. Don't resolve if the name *is* the prefix. Otherwise, we will end
|
|
// up with a useless string like "\".
|
|
if (Name->Length != prefixLength && Name->Buffer[prefixLength / sizeof(WCHAR)] == '\\')
|
|
{
|
|
isPrefix = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isPrefix)
|
|
{
|
|
// \path
|
|
newName = PhCreateStringEx(NULL, 1 * sizeof(WCHAR) + Name->Length - prefixLength);
|
|
newName->Buffer[0] = '\\';
|
|
memcpy(
|
|
&newName->Buffer[1],
|
|
&Name->Buffer[prefixLength / sizeof(WCHAR)],
|
|
Name->Length - prefixLength
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
PhReleaseQueuedLockShared(&PhDeviceMupPrefixesLock);
|
|
}
|
|
|
|
return newName;
|
|
}
|
|
|
|
/**
|
|
* Converts a file name into Win32 format.
|
|
*
|
|
* \param FileName A string containing a file name.
|
|
*
|
|
* \return A pointer to a string containing the Win32 file name. You must free the string using
|
|
* PhDereferenceObject() when you no longer need it.
|
|
*
|
|
* \remarks This function may convert NT object name paths to invalid ones. If the path to be
|
|
* converted is not necessarily a file name, use PhResolveDevicePrefix().
|
|
*/
|
|
PPH_STRING PhGetFileName(
|
|
_In_ PPH_STRING FileName
|
|
)
|
|
{
|
|
PPH_STRING newFileName;
|
|
|
|
newFileName = FileName;
|
|
|
|
// "\??\" refers to \GLOBAL??\. Just remove it.
|
|
if (PhStartsWithString2(FileName, L"\\??\\", FALSE))
|
|
{
|
|
newFileName = PhCreateStringEx(NULL, FileName->Length - 4 * 2);
|
|
memcpy(newFileName->Buffer, &FileName->Buffer[4], FileName->Length - 4 * 2);
|
|
}
|
|
// "\SystemRoot" means "C:\Windows".
|
|
else if (PhStartsWithString2(FileName, L"\\SystemRoot", TRUE))
|
|
{
|
|
PH_STRINGREF systemRoot;
|
|
|
|
PhGetSystemRoot(&systemRoot);
|
|
newFileName = PhCreateStringEx(NULL, systemRoot.Length + FileName->Length - 11 * 2);
|
|
memcpy(newFileName->Buffer, systemRoot.Buffer, systemRoot.Length);
|
|
memcpy((PCHAR)newFileName->Buffer + systemRoot.Length, &FileName->Buffer[11], FileName->Length - 11 * 2);
|
|
}
|
|
// "system32\" means "C:\Windows\system32\".
|
|
else if (PhStartsWithString2(FileName, L"system32\\", TRUE))
|
|
{
|
|
PH_STRINGREF systemRoot;
|
|
|
|
PhGetSystemRoot(&systemRoot);
|
|
newFileName = PhCreateStringEx(NULL, systemRoot.Length + 2 + FileName->Length);
|
|
memcpy(newFileName->Buffer, systemRoot.Buffer, systemRoot.Length);
|
|
newFileName->Buffer[systemRoot.Length / 2] = '\\';
|
|
memcpy((PCHAR)newFileName->Buffer + systemRoot.Length + 2, FileName->Buffer, FileName->Length);
|
|
}
|
|
else if (FileName->Length != 0 && FileName->Buffer[0] == '\\')
|
|
{
|
|
PPH_STRING resolvedName;
|
|
|
|
resolvedName = PhResolveDevicePrefix(FileName);
|
|
|
|
if (resolvedName)
|
|
{
|
|
newFileName = resolvedName;
|
|
}
|
|
else
|
|
{
|
|
// We didn't find a match.
|
|
// If the file name starts with "\Windows", prepend the system drive.
|
|
if (PhStartsWithString2(newFileName, L"\\Windows", TRUE))
|
|
{
|
|
newFileName = PhCreateStringEx(NULL, FileName->Length + 2 * 2);
|
|
newFileName->Buffer[0] = USER_SHARED_DATA->NtSystemRoot[0];
|
|
newFileName->Buffer[1] = ':';
|
|
memcpy(&newFileName->Buffer[2], FileName->Buffer, FileName->Length);
|
|
}
|
|
else
|
|
{
|
|
PhReferenceObject(newFileName);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just return the supplied file name. Note that we need to add a reference.
|
|
PhReferenceObject(newFileName);
|
|
}
|
|
|
|
return newFileName;
|
|
}
|
|
|
|
typedef struct _ENUM_GENERIC_PROCESS_MODULES_CONTEXT
|
|
{
|
|
PPH_ENUM_GENERIC_MODULES_CALLBACK Callback;
|
|
PVOID Context;
|
|
ULONG Type;
|
|
PPH_HASHTABLE BaseAddressHashtable;
|
|
|
|
ULONG LoadOrderIndex;
|
|
} ENUM_GENERIC_PROCESS_MODULES_CONTEXT, *PENUM_GENERIC_PROCESS_MODULES_CONTEXT;
|
|
|
|
static BOOLEAN EnumGenericProcessModulesCallback(
|
|
_In_ PLDR_DATA_TABLE_ENTRY Module,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
PENUM_GENERIC_PROCESS_MODULES_CONTEXT context;
|
|
PH_MODULE_INFO moduleInfo;
|
|
PPH_STRING fileName;
|
|
BOOLEAN cont;
|
|
|
|
context = (PENUM_GENERIC_PROCESS_MODULES_CONTEXT)Context;
|
|
|
|
// Check if we have a duplicate base address.
|
|
if (PhFindEntryHashtable(context->BaseAddressHashtable, &Module->DllBase))
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
PhAddEntryHashtable(context->BaseAddressHashtable, &Module->DllBase);
|
|
}
|
|
|
|
fileName = PhCreateStringFromUnicodeString(&Module->FullDllName);
|
|
|
|
moduleInfo.Type = context->Type;
|
|
moduleInfo.BaseAddress = Module->DllBase;
|
|
moduleInfo.Size = Module->SizeOfImage;
|
|
moduleInfo.EntryPoint = Module->EntryPoint;
|
|
moduleInfo.Flags = Module->Flags;
|
|
moduleInfo.Name = PhCreateStringFromUnicodeString(&Module->BaseDllName);
|
|
moduleInfo.FileName = PhGetFileName(fileName);
|
|
moduleInfo.LoadOrderIndex = (USHORT)(context->LoadOrderIndex++);
|
|
moduleInfo.LoadCount = Module->ObsoleteLoadCount;
|
|
|
|
if (WindowsVersion >= WINDOWS_8)
|
|
{
|
|
moduleInfo.LoadReason = (USHORT)Module->LoadReason;
|
|
moduleInfo.LoadTime = Module->LoadTime;
|
|
}
|
|
else
|
|
{
|
|
moduleInfo.LoadReason = -1;
|
|
moduleInfo.LoadTime.QuadPart = 0;
|
|
}
|
|
|
|
PhDereferenceObject(fileName);
|
|
|
|
cont = context->Callback(&moduleInfo, context->Context);
|
|
|
|
PhDereferenceObject(moduleInfo.Name);
|
|
PhDereferenceObject(moduleInfo.FileName);
|
|
|
|
return cont;
|
|
}
|
|
|
|
VOID PhpRtlModulesToGenericModules(
|
|
_In_ PRTL_PROCESS_MODULES Modules,
|
|
_In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context,
|
|
_In_ PPH_HASHTABLE BaseAddressHashtable
|
|
)
|
|
{
|
|
PRTL_PROCESS_MODULE_INFORMATION module;
|
|
ULONG i;
|
|
PH_MODULE_INFO moduleInfo;
|
|
BOOLEAN cont;
|
|
|
|
for (i = 0; i < Modules->NumberOfModules; i++)
|
|
{
|
|
PPH_STRING fileName;
|
|
|
|
module = &Modules->Modules[i];
|
|
|
|
// Check if we have a duplicate base address.
|
|
if (PhFindEntryHashtable(BaseAddressHashtable, &module->ImageBase))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
PhAddEntryHashtable(BaseAddressHashtable, &module->ImageBase);
|
|
}
|
|
|
|
fileName = PhConvertMultiByteToUtf16(module->FullPathName);
|
|
|
|
if ((ULONG_PTR)module->ImageBase <= PhSystemBasicInformation.MaximumUserModeAddress)
|
|
moduleInfo.Type = PH_MODULE_TYPE_MODULE;
|
|
else
|
|
moduleInfo.Type = PH_MODULE_TYPE_KERNEL_MODULE;
|
|
|
|
moduleInfo.BaseAddress = module->ImageBase;
|
|
moduleInfo.Size = module->ImageSize;
|
|
moduleInfo.EntryPoint = NULL;
|
|
moduleInfo.Flags = module->Flags;
|
|
moduleInfo.Name = PhConvertMultiByteToUtf16(&module->FullPathName[module->OffsetToFileName]);
|
|
moduleInfo.FileName = PhGetFileName(fileName); // convert to DOS file name
|
|
moduleInfo.LoadOrderIndex = module->LoadOrderIndex;
|
|
moduleInfo.LoadCount = module->LoadCount;
|
|
moduleInfo.LoadReason = -1;
|
|
moduleInfo.LoadTime.QuadPart = 0;
|
|
|
|
PhDereferenceObject(fileName);
|
|
|
|
if (module->OffsetToFileName == 0)
|
|
{
|
|
static PH_STRINGREF driversString = PH_STRINGREF_INIT(L"\\System32\\Drivers\\");
|
|
PH_STRINGREF systemRoot;
|
|
PPH_STRING newFileName;
|
|
|
|
// We only have the file name, without a path. The driver must be in the default drivers
|
|
// directory.
|
|
PhGetSystemRoot(&systemRoot);
|
|
newFileName = PhConcatStringRef3(&systemRoot, &driversString, &moduleInfo.Name->sr);
|
|
PhDereferenceObject(moduleInfo.FileName);
|
|
moduleInfo.FileName = newFileName;
|
|
}
|
|
|
|
cont = Callback(&moduleInfo, Context);
|
|
|
|
PhDereferenceObject(moduleInfo.Name);
|
|
PhDereferenceObject(moduleInfo.FileName);
|
|
|
|
if (!cont)
|
|
break;
|
|
}
|
|
}
|
|
|
|
VOID PhpRtlModulesExToGenericModules(
|
|
_In_ PRTL_PROCESS_MODULE_INFORMATION_EX Modules,
|
|
_In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context,
|
|
_In_ PPH_HASHTABLE BaseAddressHashtable
|
|
)
|
|
{
|
|
PRTL_PROCESS_MODULE_INFORMATION_EX module;
|
|
PH_MODULE_INFO moduleInfo;
|
|
BOOLEAN cont;
|
|
|
|
module = Modules;
|
|
|
|
while (module->NextOffset != 0)
|
|
{
|
|
PPH_STRING fileName;
|
|
|
|
// Check if we have a duplicate base address.
|
|
if (PhFindEntryHashtable(BaseAddressHashtable, &module->BaseInfo.ImageBase))
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
PhAddEntryHashtable(BaseAddressHashtable, &module->BaseInfo.ImageBase);
|
|
}
|
|
|
|
fileName = PhConvertMultiByteToUtf16(module->BaseInfo.FullPathName);
|
|
|
|
if ((ULONG_PTR)module->BaseInfo.ImageBase <= PhSystemBasicInformation.MaximumUserModeAddress)
|
|
moduleInfo.Type = PH_MODULE_TYPE_MODULE;
|
|
else
|
|
moduleInfo.Type = PH_MODULE_TYPE_KERNEL_MODULE;
|
|
|
|
moduleInfo.BaseAddress = module->BaseInfo.ImageBase;
|
|
moduleInfo.Size = module->BaseInfo.ImageSize;
|
|
moduleInfo.EntryPoint = NULL;
|
|
moduleInfo.Flags = module->BaseInfo.Flags;
|
|
moduleInfo.Name = PhConvertMultiByteToUtf16(&module->BaseInfo.FullPathName[module->BaseInfo.OffsetToFileName]);
|
|
moduleInfo.FileName = PhGetFileName(fileName); // convert to DOS file name
|
|
moduleInfo.LoadOrderIndex = module->BaseInfo.LoadOrderIndex;
|
|
moduleInfo.LoadCount = module->BaseInfo.LoadCount;
|
|
moduleInfo.LoadReason = -1;
|
|
moduleInfo.LoadTime.QuadPart = 0;
|
|
|
|
PhDereferenceObject(fileName);
|
|
|
|
cont = Callback(&moduleInfo, Context);
|
|
|
|
PhDereferenceObject(moduleInfo.Name);
|
|
PhDereferenceObject(moduleInfo.FileName);
|
|
|
|
if (!cont)
|
|
break;
|
|
|
|
module = PTR_ADD_OFFSET(module, module->NextOffset);
|
|
}
|
|
}
|
|
|
|
BOOLEAN PhpCallbackMappedFileOrImage(
|
|
_In_ PVOID AllocationBase,
|
|
_In_ SIZE_T AllocationSize,
|
|
_In_ ULONG Type,
|
|
_In_ PPH_STRING FileName,
|
|
_In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context,
|
|
_In_ PPH_HASHTABLE BaseAddressHashtable
|
|
)
|
|
{
|
|
PH_MODULE_INFO moduleInfo;
|
|
BOOLEAN cont;
|
|
|
|
moduleInfo.Type = Type;
|
|
moduleInfo.BaseAddress = AllocationBase;
|
|
moduleInfo.Size = (ULONG)AllocationSize;
|
|
moduleInfo.EntryPoint = NULL;
|
|
moduleInfo.Flags = 0;
|
|
moduleInfo.FileName = PhGetFileName(FileName);
|
|
moduleInfo.Name = PhGetBaseName(moduleInfo.FileName);
|
|
moduleInfo.LoadOrderIndex = -1;
|
|
moduleInfo.LoadCount = -1;
|
|
moduleInfo.LoadReason = -1;
|
|
moduleInfo.LoadTime.QuadPart = 0;
|
|
|
|
cont = Callback(&moduleInfo, Context);
|
|
|
|
PhDereferenceObject(moduleInfo.FileName);
|
|
PhDereferenceObject(moduleInfo.Name);
|
|
|
|
return cont;
|
|
}
|
|
|
|
VOID PhpEnumGenericMappedFilesAndImages(
|
|
_In_ HANDLE ProcessHandle,
|
|
_In_ ULONG Flags,
|
|
_In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context,
|
|
_In_ PPH_HASHTABLE BaseAddressHashtable
|
|
)
|
|
{
|
|
BOOLEAN querySucceeded;
|
|
PVOID baseAddress;
|
|
MEMORY_BASIC_INFORMATION basicInfo;
|
|
|
|
baseAddress = (PVOID)0;
|
|
|
|
if (!NT_SUCCESS(NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
baseAddress,
|
|
MemoryBasicInformation,
|
|
&basicInfo,
|
|
sizeof(MEMORY_BASIC_INFORMATION),
|
|
NULL
|
|
)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
querySucceeded = TRUE;
|
|
|
|
while (querySucceeded)
|
|
{
|
|
PVOID allocationBase;
|
|
SIZE_T allocationSize;
|
|
ULONG type;
|
|
PPH_STRING fileName;
|
|
BOOLEAN cont;
|
|
|
|
if (basicInfo.Type == MEM_MAPPED || basicInfo.Type == MEM_IMAGE)
|
|
{
|
|
if (basicInfo.Type == MEM_MAPPED)
|
|
type = PH_MODULE_TYPE_MAPPED_FILE;
|
|
else
|
|
type = PH_MODULE_TYPE_MAPPED_IMAGE;
|
|
|
|
// Find the total allocation size.
|
|
|
|
allocationBase = basicInfo.AllocationBase;
|
|
allocationSize = 0;
|
|
|
|
do
|
|
{
|
|
baseAddress = (PVOID)((ULONG_PTR)baseAddress + basicInfo.RegionSize);
|
|
allocationSize += basicInfo.RegionSize;
|
|
|
|
if (!NT_SUCCESS(NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
baseAddress,
|
|
MemoryBasicInformation,
|
|
&basicInfo,
|
|
sizeof(MEMORY_BASIC_INFORMATION),
|
|
NULL
|
|
)))
|
|
{
|
|
querySucceeded = FALSE;
|
|
break;
|
|
}
|
|
} while (basicInfo.AllocationBase == allocationBase);
|
|
|
|
if ((type == PH_MODULE_TYPE_MAPPED_FILE && !(Flags & PH_ENUM_GENERIC_MAPPED_FILES)) ||
|
|
(type == PH_MODULE_TYPE_MAPPED_IMAGE && !(Flags & PH_ENUM_GENERIC_MAPPED_IMAGES)))
|
|
{
|
|
// The user doesn't want this type of entry.
|
|
continue;
|
|
}
|
|
|
|
// Check if we have a duplicate base address.
|
|
if (PhFindEntryHashtable(BaseAddressHashtable, &allocationBase))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!NT_SUCCESS(PhGetProcessMappedFileName(
|
|
ProcessHandle,
|
|
allocationBase,
|
|
&fileName
|
|
)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PhAddEntryHashtable(BaseAddressHashtable, &allocationBase);
|
|
|
|
cont = PhpCallbackMappedFileOrImage(
|
|
allocationBase,
|
|
allocationSize,
|
|
type,
|
|
fileName,
|
|
Callback,
|
|
Context,
|
|
BaseAddressHashtable
|
|
);
|
|
|
|
PhDereferenceObject(fileName);
|
|
|
|
if (!cont)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
baseAddress = (PVOID)((ULONG_PTR)baseAddress + basicInfo.RegionSize);
|
|
|
|
if (!NT_SUCCESS(NtQueryVirtualMemory(
|
|
ProcessHandle,
|
|
baseAddress,
|
|
MemoryBasicInformation,
|
|
&basicInfo,
|
|
sizeof(MEMORY_BASIC_INFORMATION),
|
|
NULL
|
|
)))
|
|
{
|
|
querySucceeded = FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOLEAN NTAPI PhpBaseAddressHashtableEqualFunction(
|
|
_In_ PVOID Entry1,
|
|
_In_ PVOID Entry2
|
|
)
|
|
{
|
|
return *(PVOID *)Entry1 == *(PVOID *)Entry2;
|
|
}
|
|
|
|
ULONG NTAPI PhpBaseAddressHashtableHashFunction(
|
|
_In_ PVOID Entry
|
|
)
|
|
{
|
|
return PhHashIntPtr((ULONG_PTR)*(PVOID *)Entry);
|
|
}
|
|
|
|
/**
|
|
* Enumerates the modules loaded by a process.
|
|
*
|
|
* \param ProcessId The ID of a process. If \ref SYSTEM_PROCESS_ID is specified the function
|
|
* enumerates the kernel modules.
|
|
* \param ProcessHandle A handle to the process.
|
|
* \param Flags Flags controlling the information to retrieve.
|
|
* \li \c PH_ENUM_GENERIC_MAPPED_FILES Enumerate mapped files.
|
|
* \li \c PH_ENUM_GENERIC_MAPPED_IMAGES Enumerate mapped images (those which are not mapped by the
|
|
* loader).
|
|
* \param Callback A callback function which is executed for each module.
|
|
* \param Context A user-defined value to pass to the callback function.
|
|
*/
|
|
NTSTATUS PhEnumGenericModules(
|
|
_In_ HANDLE ProcessId,
|
|
_In_opt_ HANDLE ProcessHandle,
|
|
_In_ ULONG Flags,
|
|
_In_ PPH_ENUM_GENERIC_MODULES_CALLBACK Callback,
|
|
_In_opt_ PVOID Context
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PPH_HASHTABLE baseAddressHashtable;
|
|
|
|
baseAddressHashtable = PhCreateHashtable(
|
|
sizeof(PVOID),
|
|
PhpBaseAddressHashtableEqualFunction,
|
|
PhpBaseAddressHashtableHashFunction,
|
|
32
|
|
);
|
|
|
|
if (ProcessId == SYSTEM_PROCESS_ID)
|
|
{
|
|
// Kernel modules
|
|
|
|
PVOID modules;
|
|
|
|
if (NT_SUCCESS(status = PhEnumKernelModules((PRTL_PROCESS_MODULES *)&modules)))
|
|
{
|
|
PhpRtlModulesToGenericModules(
|
|
modules,
|
|
Callback,
|
|
Context,
|
|
baseAddressHashtable
|
|
);
|
|
PhFree(modules);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Process modules
|
|
|
|
BOOLEAN opened = FALSE;
|
|
#ifdef _WIN64
|
|
BOOLEAN isWow64 = FALSE;
|
|
#endif
|
|
ENUM_GENERIC_PROCESS_MODULES_CONTEXT context;
|
|
PH_ENUM_PROCESS_MODULES_PARAMETERS parameters;
|
|
|
|
if (!ProcessHandle)
|
|
{
|
|
if (!NT_SUCCESS(status = PhOpenProcess(
|
|
&ProcessHandle,
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, // needed for enumerating mapped files
|
|
ProcessId
|
|
)))
|
|
{
|
|
if (!NT_SUCCESS(status = PhOpenProcess(
|
|
&ProcessHandle,
|
|
ProcessQueryAccess | PROCESS_VM_READ,
|
|
ProcessId
|
|
)))
|
|
{
|
|
goto CleanupExit;
|
|
}
|
|
}
|
|
|
|
opened = TRUE;
|
|
}
|
|
|
|
context.Callback = Callback;
|
|
context.Context = Context;
|
|
context.Type = PH_MODULE_TYPE_MODULE;
|
|
context.BaseAddressHashtable = baseAddressHashtable;
|
|
context.LoadOrderIndex = 0;
|
|
|
|
parameters.Callback = EnumGenericProcessModulesCallback;
|
|
parameters.Context = &context;
|
|
parameters.Flags = PH_ENUM_PROCESS_MODULES_TRY_MAPPED_FILE_NAME;
|
|
|
|
status = PhEnumProcessModulesEx(
|
|
ProcessHandle,
|
|
¶meters
|
|
);
|
|
|
|
#ifdef _WIN64
|
|
PhGetProcessIsWow64(ProcessHandle, &isWow64);
|
|
|
|
// 32-bit process modules
|
|
if (isWow64)
|
|
{
|
|
context.Callback = Callback;
|
|
context.Context = Context;
|
|
context.Type = PH_MODULE_TYPE_WOW64_MODULE;
|
|
context.BaseAddressHashtable = baseAddressHashtable;
|
|
context.LoadOrderIndex = 0;
|
|
|
|
status = PhEnumProcessModules32Ex(
|
|
ProcessHandle,
|
|
¶meters
|
|
);
|
|
}
|
|
#endif
|
|
|
|
// Mapped files and mapped images
|
|
// This is done last because it provides the least amount of information.
|
|
|
|
if (Flags & (PH_ENUM_GENERIC_MAPPED_FILES | PH_ENUM_GENERIC_MAPPED_IMAGES))
|
|
{
|
|
PhpEnumGenericMappedFilesAndImages(
|
|
ProcessHandle,
|
|
Flags,
|
|
Callback,
|
|
Context,
|
|
baseAddressHashtable
|
|
);
|
|
}
|
|
|
|
if (opened)
|
|
NtClose(ProcessHandle);
|
|
}
|
|
|
|
CleanupExit:
|
|
PhDereferenceObject(baseAddressHashtable);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Initializes usage of predefined keys.
|
|
*/
|
|
VOID PhpInitializePredefineKeys(
|
|
VOID
|
|
)
|
|
{
|
|
static UNICODE_STRING currentUserPrefix = RTL_CONSTANT_STRING(L"\\Registry\\User\\");
|
|
|
|
NTSTATUS status;
|
|
HANDLE tokenHandle;
|
|
PTOKEN_USER tokenUser;
|
|
UNICODE_STRING stringSid;
|
|
WCHAR stringSidBuffer[MAX_UNICODE_STACK_BUFFER_LENGTH];
|
|
PUNICODE_STRING currentUserKeyName;
|
|
|
|
// Get the string SID of the current user.
|
|
if (NT_SUCCESS(status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY, &tokenHandle)))
|
|
{
|
|
if (NT_SUCCESS(status = PhGetTokenUser(tokenHandle, &tokenUser)))
|
|
{
|
|
stringSid.Buffer = stringSidBuffer;
|
|
stringSid.MaximumLength = sizeof(stringSidBuffer);
|
|
|
|
status = RtlConvertSidToUnicodeString(
|
|
&stringSid,
|
|
tokenUser->User.Sid,
|
|
FALSE
|
|
);
|
|
|
|
PhFree(tokenUser);
|
|
}
|
|
|
|
NtClose(tokenHandle);
|
|
}
|
|
|
|
// Construct the current user key name.
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
currentUserKeyName = &PhPredefineKeyNames[PH_KEY_CURRENT_USER_NUMBER];
|
|
currentUserKeyName->Length = currentUserPrefix.Length + stringSid.Length;
|
|
currentUserKeyName->Buffer = PhAllocate(currentUserKeyName->Length + sizeof(WCHAR));
|
|
memcpy(currentUserKeyName->Buffer, currentUserPrefix.Buffer, currentUserPrefix.Length);
|
|
memcpy(¤tUserKeyName->Buffer[currentUserPrefix.Length / sizeof(WCHAR)], stringSid.Buffer, stringSid.Length);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initializes the attributes of a key object for creating/opening.
|
|
*
|
|
* \param RootDirectory A handle to a root key, or one of the predefined keys. See PhCreateKey() for
|
|
* details.
|
|
* \param ObjectName The path to the key.
|
|
* \param Attributes Additional object flags.
|
|
* \param ObjectAttributes The OBJECT_ATTRIBUTES structure to initialize.
|
|
* \param NeedsClose A variable which receives a handle that must be closed when the create/open
|
|
* operation is finished. The variable may be set to NULL if no handle needs to be closed.
|
|
*/
|
|
NTSTATUS PhpInitializeKeyObjectAttributes(
|
|
_In_opt_ HANDLE RootDirectory,
|
|
_In_ PUNICODE_STRING ObjectName,
|
|
_In_ ULONG Attributes,
|
|
_Out_ POBJECT_ATTRIBUTES ObjectAttributes,
|
|
_Out_ PHANDLE NeedsClose
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
ULONG predefineIndex;
|
|
HANDLE predefineHandle;
|
|
OBJECT_ATTRIBUTES predefineObjectAttributes;
|
|
|
|
InitializeObjectAttributes(
|
|
ObjectAttributes,
|
|
ObjectName,
|
|
Attributes | OBJ_CASE_INSENSITIVE,
|
|
RootDirectory,
|
|
NULL
|
|
);
|
|
|
|
*NeedsClose = NULL;
|
|
|
|
if (RootDirectory && PH_KEY_IS_PREDEFINED(RootDirectory))
|
|
{
|
|
predefineIndex = PH_KEY_PREDEFINE_TO_NUMBER(RootDirectory);
|
|
|
|
if (predefineIndex < PH_KEY_MAXIMUM_PREDEFINE)
|
|
{
|
|
if (PhBeginInitOnce(&PhPredefineKeyInitOnce))
|
|
{
|
|
PhpInitializePredefineKeys();
|
|
PhEndInitOnce(&PhPredefineKeyInitOnce);
|
|
}
|
|
|
|
predefineHandle = PhPredefineKeyHandles[predefineIndex];
|
|
|
|
if (!predefineHandle)
|
|
{
|
|
// The predefined key has not been opened yet. Do so now.
|
|
|
|
if (!PhPredefineKeyNames[predefineIndex].Buffer) // we may have failed in getting the current user key name
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
InitializeObjectAttributes(
|
|
&predefineObjectAttributes,
|
|
&PhPredefineKeyNames[predefineIndex],
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = NtOpenKey(
|
|
&predefineHandle,
|
|
KEY_READ,
|
|
&predefineObjectAttributes
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
if (_InterlockedCompareExchangePointer(
|
|
&PhPredefineKeyHandles[predefineIndex],
|
|
predefineHandle,
|
|
NULL
|
|
) != NULL)
|
|
{
|
|
// Someone else already opened the key and cached it. Indicate that the caller
|
|
// needs to close the handle later, since it isn't shared.
|
|
*NeedsClose = predefineHandle;
|
|
}
|
|
}
|
|
|
|
ObjectAttributes->RootDirectory = predefineHandle;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Creates or opens a registry key.
|
|
*
|
|
* \param KeyHandle A variable which receives a handle to the key.
|
|
* \param DesiredAccess The desired access to the key.
|
|
* \param RootDirectory A handle to a root key, or one of the following predefined keys:
|
|
* \li \c PH_KEY_LOCAL_MACHINE Represents \\Registry\\Machine.
|
|
* \li \c PH_KEY_USERS Represents \\Registry\\User.
|
|
* \li \c PH_KEY_CLASSES_ROOT Represents \\Registry\\Machine\\Software\\Classes.
|
|
* \li \c PH_KEY_CURRENT_USER Represents \\Registry\\User\\[SID of current user].
|
|
* \param ObjectName The path to the key.
|
|
* \param Attributes Additional object flags.
|
|
* \param CreateOptions The options to apply when creating or opening the key.
|
|
* \param Disposition A variable which receives a value indicating whether a new key was created or
|
|
* an existing key was opened:
|
|
* \li \c REG_CREATED_NEW_KEY A new key was created.
|
|
* \li \c REG_OPENED_EXISTING_KEY An existing key was opened.
|
|
*/
|
|
NTSTATUS PhCreateKey(
|
|
_Out_ PHANDLE KeyHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ HANDLE RootDirectory,
|
|
_In_ PPH_STRINGREF ObjectName,
|
|
_In_ ULONG Attributes,
|
|
_In_ ULONG CreateOptions,
|
|
_Out_opt_ PULONG Disposition
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING objectName;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
HANDLE needsClose;
|
|
|
|
if (!PhStringRefToUnicodeString(ObjectName, &objectName))
|
|
return STATUS_NAME_TOO_LONG;
|
|
|
|
if (!NT_SUCCESS(status = PhpInitializeKeyObjectAttributes(
|
|
RootDirectory,
|
|
&objectName,
|
|
Attributes,
|
|
&objectAttributes,
|
|
&needsClose
|
|
)))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
status = NtCreateKey(
|
|
KeyHandle,
|
|
DesiredAccess,
|
|
&objectAttributes,
|
|
0,
|
|
NULL,
|
|
CreateOptions,
|
|
Disposition
|
|
);
|
|
|
|
if (needsClose)
|
|
NtClose(needsClose);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Opens a registry key.
|
|
*
|
|
* \param KeyHandle A variable which receives a handle to the key.
|
|
* \param DesiredAccess The desired access to the key.
|
|
* \param RootDirectory A handle to a root key, or one of the predefined keys. See PhCreateKey() for
|
|
* details.
|
|
* \param ObjectName The path to the key.
|
|
* \param Attributes Additional object flags.
|
|
*/
|
|
NTSTATUS PhOpenKey(
|
|
_Out_ PHANDLE KeyHandle,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ HANDLE RootDirectory,
|
|
_In_ PPH_STRINGREF ObjectName,
|
|
_In_ ULONG Attributes
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING objectName;
|
|
OBJECT_ATTRIBUTES objectAttributes;
|
|
HANDLE needsClose;
|
|
|
|
if (!PhStringRefToUnicodeString(ObjectName, &objectName))
|
|
return STATUS_NAME_TOO_LONG;
|
|
|
|
if (!NT_SUCCESS(status = PhpInitializeKeyObjectAttributes(
|
|
RootDirectory,
|
|
&objectName,
|
|
Attributes,
|
|
&objectAttributes,
|
|
&needsClose
|
|
)))
|
|
{
|
|
return status;
|
|
}
|
|
|
|
status = NtOpenKey(
|
|
KeyHandle,
|
|
DesiredAccess,
|
|
&objectAttributes
|
|
);
|
|
|
|
if (needsClose)
|
|
NtClose(needsClose);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets information about a registry key.
|
|
*
|
|
* \param KeyHandle A handle to the key.
|
|
* \param KeyInformationClass The information class to query.
|
|
* \param Buffer A variable which receives a pointer to a buffer containing information about the
|
|
* registry key. You must free the buffer with PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhQueryKey(
|
|
_In_ HANDLE KeyHandle,
|
|
_In_ KEY_INFORMATION_CLASS KeyInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
ULONG attempts = 16;
|
|
|
|
bufferSize = 0x100;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
do
|
|
{
|
|
status = NtQueryKey(
|
|
KeyHandle,
|
|
KeyInformationClass,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
break;
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
} while (--attempts);
|
|
|
|
*Buffer = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Gets a registry value of any type.
|
|
*
|
|
* \param KeyHandle A handle to the key.
|
|
* \param ValueName The name of the value.
|
|
* \param KeyValueInformationClass The information class to query.
|
|
* \param Buffer A variable which receives a pointer to a buffer containing information about the
|
|
* registry value. You must free the buffer with PhFree() when you no longer need it.
|
|
*/
|
|
NTSTATUS PhQueryValueKey(
|
|
_In_ HANDLE KeyHandle,
|
|
_In_opt_ PPH_STRINGREF ValueName,
|
|
_In_ KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
|
|
_Out_ PVOID *Buffer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING valueName;
|
|
PVOID buffer;
|
|
ULONG bufferSize;
|
|
ULONG attempts = 16;
|
|
|
|
if (ValueName)
|
|
{
|
|
if (!PhStringRefToUnicodeString(ValueName, &valueName))
|
|
return STATUS_NAME_TOO_LONG;
|
|
}
|
|
else
|
|
{
|
|
RtlInitUnicodeString(&valueName, NULL);
|
|
}
|
|
|
|
bufferSize = 0x100;
|
|
buffer = PhAllocate(bufferSize);
|
|
|
|
do
|
|
{
|
|
status = NtQueryValueKey(
|
|
KeyHandle,
|
|
&valueName,
|
|
KeyValueInformationClass,
|
|
buffer,
|
|
bufferSize,
|
|
&bufferSize
|
|
);
|
|
|
|
if (NT_SUCCESS(status))
|
|
break;
|
|
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
PhFree(buffer);
|
|
buffer = PhAllocate(bufferSize);
|
|
}
|
|
else
|
|
{
|
|
PhFree(buffer);
|
|
return status;
|
|
}
|
|
} while (--attempts);
|
|
|
|
*Buffer = buffer;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Creates or opens a file.
|
|
*
|
|
* \param FileHandle A variable that receives the file handle.
|
|
* \param FileName The Win32 file name.
|
|
* \param DesiredAccess The desired access to the file.
|
|
* \param FileAttributes File attributes applied if the file is created or overwritten.
|
|
* \param ShareAccess The file access granted to other threads.
|
|
* \li \c FILE_SHARE_READ Allows other threads to read from the file.
|
|
* \li \c FILE_SHARE_WRITE Allows other threads to write to the file.
|
|
* \li \c FILE_SHARE_DELETE Allows other threads to delete the file.
|
|
* \param CreateDisposition The action to perform if the file does or does not exist.
|
|
* \li \c FILE_SUPERSEDE If the file exists, replace it. Otherwise, create the file.
|
|
* \li \c FILE_CREATE If the file exists, fail. Otherwise, create the file.
|
|
* \li \c FILE_OPEN If the file exists, open it. Otherwise, fail.
|
|
* \li \c FILE_OPEN_IF If the file exists, open it. Otherwise, create the file.
|
|
* \li \c FILE_OVERWRITE If the file exists, open and overwrite it. Otherwise, fail.
|
|
* \li \c FILE_OVERWRITE_IF If the file exists, open and overwrite it. Otherwise, create the file.
|
|
* \param CreateOptions The options to apply when the file is opened or created.
|
|
*/
|
|
NTSTATUS PhCreateFileWin32(
|
|
_Out_ PHANDLE FileHandle,
|
|
_In_ PWSTR FileName,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ ULONG FileAttributes,
|
|
_In_ ULONG ShareAccess,
|
|
_In_ ULONG CreateDisposition,
|
|
_In_ ULONG CreateOptions
|
|
)
|
|
{
|
|
return PhCreateFileWin32Ex(
|
|
FileHandle,
|
|
FileName,
|
|
DesiredAccess,
|
|
FileAttributes,
|
|
ShareAccess,
|
|
CreateDisposition,
|
|
CreateOptions,
|
|
NULL
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates or opens a file.
|
|
*
|
|
* \param FileHandle A variable that receives the file handle.
|
|
* \param FileName The Win32 file name.
|
|
* \param DesiredAccess The desired access to the file.
|
|
* \param FileAttributes File attributes applied if the file is created or overwritten.
|
|
* \param ShareAccess The file access granted to other threads.
|
|
* \li \c FILE_SHARE_READ Allows other threads to read from the file.
|
|
* \li \c FILE_SHARE_WRITE Allows other threads to write to the file.
|
|
* \li \c FILE_SHARE_DELETE Allows other threads to delete the file.
|
|
* \param CreateDisposition The action to perform if the file does or does not exist.
|
|
* \li \c FILE_SUPERSEDE If the file exists, replace it. Otherwise, create the file.
|
|
* \li \c FILE_CREATE If the file exists, fail. Otherwise, create the file.
|
|
* \li \c FILE_OPEN If the file exists, open it. Otherwise, fail.
|
|
* \li \c FILE_OPEN_IF If the file exists, open it. Otherwise, create the file.
|
|
* \li \c FILE_OVERWRITE If the file exists, open and overwrite it. Otherwise, fail.
|
|
* \li \c FILE_OVERWRITE_IF If the file exists, open and overwrite it. Otherwise, create the file.
|
|
* \param CreateOptions The options to apply when the file is opened or created.
|
|
* \param CreateStatus A variable that receives creation information.
|
|
* \li \c FILE_SUPERSEDED The file was replaced because \c FILE_SUPERSEDE was specified in
|
|
* \a CreateDisposition.
|
|
* \li \c FILE_OPENED The file was opened because \c FILE_OPEN or \c FILE_OPEN_IF was specified in
|
|
* \a CreateDisposition.
|
|
* \li \c FILE_CREATED The file was created because \c FILE_CREATE or \c FILE_OPEN_IF was specified
|
|
* in \a CreateDisposition.
|
|
* \li \c FILE_OVERWRITTEN The file was overwritten because \c FILE_OVERWRITE or
|
|
* \c FILE_OVERWRITE_IF was specified in \a CreateDisposition.
|
|
* \li \c FILE_EXISTS The file was not opened because it already existed and \c FILE_CREATE was
|
|
* specified in \a CreateDisposition.
|
|
* \li \c FILE_DOES_NOT_EXIST The file was not opened because it did not exist and \c FILE_OPEN or
|
|
* \c FILE_OVERWRITE was specified in \a CreateDisposition.
|
|
*/
|
|
NTSTATUS PhCreateFileWin32Ex(
|
|
_Out_ PHANDLE FileHandle,
|
|
_In_ PWSTR FileName,
|
|
_In_ ACCESS_MASK DesiredAccess,
|
|
_In_opt_ ULONG FileAttributes,
|
|
_In_ ULONG ShareAccess,
|
|
_In_ ULONG CreateDisposition,
|
|
_In_ ULONG CreateOptions,
|
|
_Out_opt_ PULONG CreateStatus
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE fileHandle;
|
|
UNICODE_STRING fileName;
|
|
OBJECT_ATTRIBUTES oa;
|
|
IO_STATUS_BLOCK isb;
|
|
|
|
if (!FileAttributes)
|
|
FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
if (!RtlDosPathNameToNtPathName_U(
|
|
FileName,
|
|
&fileName,
|
|
NULL,
|
|
NULL
|
|
))
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
InitializeObjectAttributes(
|
|
&oa,
|
|
&fileName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = NtCreateFile(
|
|
&fileHandle,
|
|
DesiredAccess,
|
|
&oa,
|
|
&isb,
|
|
NULL,
|
|
FileAttributes,
|
|
ShareAccess,
|
|
CreateDisposition,
|
|
CreateOptions,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
*FileHandle = fileHandle;
|
|
}
|
|
|
|
if (CreateStatus)
|
|
*CreateStatus = (ULONG)isb.Information;
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Queries file attributes.
|
|
*
|
|
* \param FileName The Win32 file name.
|
|
* \param FileInformation A variable that receives the file information.
|
|
*/
|
|
NTSTATUS PhQueryFullAttributesFileWin32(
|
|
_In_ PWSTR FileName,
|
|
_Out_ PFILE_NETWORK_OPEN_INFORMATION FileInformation
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING fileName;
|
|
OBJECT_ATTRIBUTES oa;
|
|
|
|
if (!RtlDosPathNameToNtPathName_U(
|
|
FileName,
|
|
&fileName,
|
|
NULL,
|
|
NULL
|
|
))
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
|
|
InitializeObjectAttributes(
|
|
&oa,
|
|
&fileName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = NtQueryFullAttributesFile(&oa, FileInformation);
|
|
RtlFreeHeap(RtlProcessHeap(), 0, fileName.Buffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Deletes a file.
|
|
*
|
|
* \param FileName The Win32 file name.
|
|
*/
|
|
NTSTATUS PhDeleteFileWin32(
|
|
_In_ PWSTR FileName
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
HANDLE fileHandle;
|
|
|
|
status = PhCreateFileWin32(
|
|
&fileHandle,
|
|
FileName,
|
|
DELETE,
|
|
0,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OPEN,
|
|
FILE_DELETE_ON_CLOSE
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
NtClose(fileHandle);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhListenNamedPipe(
|
|
_In_ HANDLE FileHandle,
|
|
_In_opt_ HANDLE Event,
|
|
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
|
|
_In_opt_ PVOID ApcContext,
|
|
_Out_ PIO_STATUS_BLOCK IoStatusBlock
|
|
)
|
|
{
|
|
return NtFsControlFile(
|
|
FileHandle,
|
|
Event,
|
|
ApcRoutine,
|
|
ApcContext,
|
|
IoStatusBlock,
|
|
FSCTL_PIPE_LISTEN,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
NTSTATUS PhDisconnectNamedPipe(
|
|
_In_ HANDLE FileHandle
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK isb;
|
|
|
|
status = NtFsControlFile(
|
|
FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&isb,
|
|
FSCTL_PIPE_DISCONNECT,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
if (status == STATUS_PENDING)
|
|
{
|
|
status = NtWaitForSingleObject(FileHandle, FALSE, NULL);
|
|
|
|
if (NT_SUCCESS(status))
|
|
status = isb.Status;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhPeekNamedPipe(
|
|
_In_ HANDLE FileHandle,
|
|
_Out_writes_bytes_opt_(Length) PVOID Buffer,
|
|
_In_ ULONG Length,
|
|
_Out_opt_ PULONG NumberOfBytesRead,
|
|
_Out_opt_ PULONG NumberOfBytesAvailable,
|
|
_Out_opt_ PULONG NumberOfBytesLeftInMessage
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK isb;
|
|
PFILE_PIPE_PEEK_BUFFER peekBuffer;
|
|
ULONG peekBufferLength;
|
|
|
|
peekBufferLength = FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data) + Length;
|
|
peekBuffer = PhAllocate(peekBufferLength);
|
|
|
|
status = NtFsControlFile(
|
|
FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&isb,
|
|
FSCTL_PIPE_PEEK,
|
|
NULL,
|
|
0,
|
|
peekBuffer,
|
|
peekBufferLength
|
|
);
|
|
|
|
if (status == STATUS_PENDING)
|
|
{
|
|
status = NtWaitForSingleObject(FileHandle, FALSE, NULL);
|
|
|
|
if (NT_SUCCESS(status))
|
|
status = isb.Status;
|
|
}
|
|
|
|
// STATUS_BUFFER_OVERFLOW means that there is data remaining; this is normal.
|
|
if (status == STATUS_BUFFER_OVERFLOW)
|
|
status = STATUS_SUCCESS;
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
ULONG numberOfBytesRead;
|
|
|
|
if (Buffer || NumberOfBytesRead || NumberOfBytesLeftInMessage)
|
|
numberOfBytesRead = (ULONG)(isb.Information - FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data));
|
|
|
|
if (Buffer)
|
|
memcpy(Buffer, peekBuffer->Data, numberOfBytesRead);
|
|
|
|
if (NumberOfBytesRead)
|
|
*NumberOfBytesRead = numberOfBytesRead;
|
|
|
|
if (NumberOfBytesAvailable)
|
|
*NumberOfBytesAvailable = peekBuffer->ReadDataAvailable;
|
|
|
|
if (NumberOfBytesLeftInMessage)
|
|
*NumberOfBytesLeftInMessage = peekBuffer->MessageLength - numberOfBytesRead;
|
|
}
|
|
|
|
PhFree(peekBuffer);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhTransceiveNamedPipe(
|
|
_In_ HANDLE FileHandle,
|
|
_In_opt_ HANDLE Event,
|
|
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
|
|
_In_opt_ PVOID ApcContext,
|
|
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
|
|
_In_reads_bytes_(InputBufferLength) PVOID InputBuffer,
|
|
_In_ ULONG InputBufferLength,
|
|
_Out_writes_bytes_(OutputBufferLength) PVOID OutputBuffer,
|
|
_In_ ULONG OutputBufferLength
|
|
)
|
|
{
|
|
return NtFsControlFile(
|
|
FileHandle,
|
|
Event,
|
|
ApcRoutine,
|
|
ApcContext,
|
|
IoStatusBlock,
|
|
FSCTL_PIPE_TRANSCEIVE,
|
|
InputBuffer,
|
|
InputBufferLength,
|
|
OutputBuffer,
|
|
OutputBufferLength
|
|
);
|
|
}
|
|
|
|
NTSTATUS PhWaitForNamedPipe(
|
|
_In_opt_ PUNICODE_STRING FileSystemName,
|
|
_In_ PUNICODE_STRING Name,
|
|
_In_opt_ PLARGE_INTEGER Timeout,
|
|
_In_ BOOLEAN UseDefaultTimeout
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK isb;
|
|
UNICODE_STRING localNpfsName;
|
|
HANDLE fileSystemHandle;
|
|
OBJECT_ATTRIBUTES oa;
|
|
PFILE_PIPE_WAIT_FOR_BUFFER waitForBuffer;
|
|
ULONG waitForBufferLength;
|
|
|
|
if (!FileSystemName)
|
|
{
|
|
RtlInitUnicodeString(&localNpfsName, L"\\Device\\NamedPipe");
|
|
FileSystemName = &localNpfsName;
|
|
}
|
|
|
|
InitializeObjectAttributes(
|
|
&oa,
|
|
FileSystemName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
status = NtOpenFile(
|
|
&fileSystemHandle,
|
|
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
|
&oa,
|
|
&isb,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_SYNCHRONOUS_IO_NONALERT
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
waitForBufferLength = FIELD_OFFSET(FILE_PIPE_WAIT_FOR_BUFFER, Name) + Name->Length;
|
|
waitForBuffer = PhAllocate(waitForBufferLength);
|
|
|
|
if (UseDefaultTimeout)
|
|
{
|
|
waitForBuffer->TimeoutSpecified = FALSE;
|
|
}
|
|
else
|
|
{
|
|
if (Timeout)
|
|
{
|
|
waitForBuffer->Timeout = *Timeout;
|
|
}
|
|
else
|
|
{
|
|
waitForBuffer->Timeout.LowPart = 0;
|
|
waitForBuffer->Timeout.HighPart = MINLONG; // a very long time
|
|
}
|
|
|
|
waitForBuffer->TimeoutSpecified = TRUE;
|
|
}
|
|
|
|
waitForBuffer->NameLength = (ULONG)Name->Length;
|
|
memcpy(waitForBuffer->Name, Name->Buffer, Name->Length);
|
|
|
|
status = NtFsControlFile(
|
|
fileSystemHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&isb,
|
|
FSCTL_PIPE_WAIT,
|
|
waitForBuffer,
|
|
waitForBufferLength,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
PhFree(waitForBuffer);
|
|
NtClose(fileSystemHandle);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS PhImpersonateClientOfNamedPipe(
|
|
_In_ HANDLE FileHandle
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
IO_STATUS_BLOCK isb;
|
|
|
|
status = NtFsControlFile(
|
|
FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&isb,
|
|
FSCTL_PIPE_IMPERSONATE,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0
|
|
);
|
|
|
|
return status;
|
|
}
|