2015-03-22 19:28:03 chip
Page 1219
📢 PUBLIC
March 22 2015
NetMon has become quite stable recently and I am
trying to break out of the VS cocoon, running it
from the command line. This has naturally introduced
a number of new problems.
First, I want to be able to run the user-mode component
without requiring admin privileges. The problem of loading
the driver has been solved, for the most part, by creating
the "autoload" service that loads NetMon.sys at boot. The
problem is that NetMonUI.exe is launched and run as the
current user. I am currently querying process information
in the NetMonUI process, external and after-the-fact with
respect to the actual operation. When NetMonUI is a non-admin
process, attempting to query information about other processes
fail with "access denied".
I see two solutions to this problem:
- User-mode Service: A user-mode process is created as a
service that automatically launches at startup under admin privileges.
This process then loads the driver as needed and queries process
information on behalf of the UI program (which is running as the
current user). This is the preferred, standard method. It will require
a significant rewrite of the NetMon project to migrate code out of
NetMonUI.exe to NetMonSvc.exe and the creation of an inter-process
communications channel (named pipe?) between them.
- Kernel-mode Queries: Determine the process information in
NetMon.sys and relay it to NetMonUI through an internal IOCTL command.
This would require minimal changes to the existing project. However,
the functions normally used to do this (ZwQueryInformationProcess)
have been deprecated in Windows 8. While they still exist as
undocumented functions, there is a good chance trying to use them in
Windows10 will cause problems.
I think the most expeditious route is #2, using the
well-(un)documented ZwQueryProcessInformation() and PEB methods. The
code should not be huge and can easily be removed later. This will
get me to the goal of a easily installed demo disk the quickest.
Eventually I will need a user-mode service, for this and many other
reasons. But this is a significant task and I have other tasks that
are higher priority.
=====
ZwQueryProcessInformation()
ntndis.com
has a great thread about using ZwQueryProcessInformation().
It appears ZwQueryProcessInformation() has been completely expunged from the
Windows 8.0 WDK.
Another thread
about this exact problem, dated December 10 2013. Leaked source from both
Kaspersky and eSet show they are still relying on ZwQueryProcessInformation().
eSet:
#include
typedef NTSTATUS (__stdcall *QUERY_INFO_PROCESS)(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
);
QUERY_INFO_PROCESS ZwQueryInformationProcess = NULL;
NTSTATUS GetProcessImageName(HANDLE ProcessHandle, PUNICODE_STRING ProcessImageName)
{
NTSTATUS status = STATUS_ACCESS_DENIED;
PUNICODE_STRING imageName = NULL;
ULONG returnedLength = 0;
ULONG bufferLength = 0;
PVOID buffer = NULL;
if(ZwQueryInformationProcess == NULL)
{
UNICODE_STRING routineName;
RtlInitUnicodeString(&routineName, L"ZwQueryInformationProcess");
ZwQueryInformationProcess = (QUERY_INFO_PROCESS) MmGetSystemRoutineAddress(&routineName);
if (NULL == ZwQueryInformationProcess) { return STATUS_INSUFFICIENT_RESOURCES; }
}
status = ZwQueryInformationProcess(ProcessHandle, ProcessImageFileName, NULL, 0, &returnedLength);
if(STATUS_INFO_LENGTH_MISMATCH != status) { return status; }
bufferLength = returnedLength - sizeof(UNICODE_STRING);
if(ProcessImageName->MaximumLength < bufferLength)
{
ProcessImageName->Length = (USHORT) bufferLength;
return STATUS_BUFFER_OVERFLOW;
}
buffer = ExAllocatePoolWithTag(PagedPool, returnedLength, 'ipgD');
if(NULL == buffer) { return STATUS_INSUFFICIENT_RESOURCES; }
status = ZwQueryInformationProcess(ProcessHandle, ProcessImageFileName, buffer, returnedLength, &returnedLength);
if(NT_SUCCESS(status))
{
imageName = (PUNICODE_STRING) buffer;
RtlCopyUnicodeString(ProcessImageName, imageName);
}
ExFreePool(buffer);
return status;
}
BOOLEAN RetrieveProcessNameByID(HANDLE ProcessId, PUNICODE_STRING pusImageFileName)
{
UNICODE_STRING ProcImgName = {0};
HANDLE hProcessHandle = NULL;
NTSTATUS status = STATUS_ACCESS_DENIED;
PEPROCESS eProcess = NULL;
int iEntryIndex = -1;
status = PsLookupProcessByProcessId(ProcessId, &eProcess);
if((!NT_SUCCESS(status)) || (!eProcess))
{
return FALSE;
}
status = ObOpenObjectByPointer(eProcess, 0, NULL, 0, 0, KernelMode, &hProcessHandle);
if((!NT_SUCCESS(status)) || (!hProcessHandle))
{
ObDereferenceObject(eProcess);
return FALSE;
}
ProcImgName.Length = 0;
ProcImgName.MaximumLength = 1024;
ProcImgName.Buffer = ExAllocatePoolWithTag(NonPagedPool, ProcImgName.MaximumLength, '2leN');
if(ProcImgName.Buffer == NULL)
{
ZwClose(hProcessHandle);
ObDereferenceObject(eProcess);
return FALSE;
}
RtlZeroMemory( ProcImgName.Buffer, ProcImgName.MaximumLength ) ;
status = GetProcessImageName(hProcessHandle, &ProcImgName);
if(!NT_SUCCESS(status))
{
DbgPrint("[NotifyProcessCreate] GetProcessImageName failed (0x%08x)\n", status);
ExFreePoolWithTag(ProcImgName.Buffer, '2leN');
ZwClose(hProcessHandle);
ObDereferenceObject(eProcess);
return FALSE;
}
if(pusImageFileName)
{
RtlCopyUnicodeString(pusImageFileName, &ProcImgName);
}
ExFreePoolWithTag(ProcImgName.Buffer, '2leN');
ZwClose(hProcessHandle);
ObDereferenceObject(eProcess);
return TRUE;
}
Well, that didn't work:
NetMon|ERR WRN[-27]: ProcFindFunction: System function 'ZwQueryProcessInformation' not found.
Oops... I transposed the name. It should be
"ZwQueryInformationProcess" not "ZwQueryProcessInformation". It is working now!
This task took about four hours, three of which were consumed by my
confusion around UNICODE_STRING. It still confuses me, as the usage seems
to be inconsistent. This is the definition of UNICODE_STRING:
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
From this I would assume I need to set Buffer to point to the allocated memory,
which may or may not follow the UNICODE_STRING header. However, when I try to
use it this way the system blows up with a BSoD. So I tried to allocate a buffer
for the entire UNICODE_STRING, header followed by data in a contiguous block.
This blows up as well. (I may need to still set Buffer to point to the beginning
of the string?)
Then there is confusion around ZwQueryInformationProcess(). This is the MSDN
reference:
When the ProcessInformationClass parameter is ProcessImageFileName,
the buffer pointed to by the ProcessInformation parameter should be
large enough to hold a UNICODE_STRING structure as well as the string
itself. The string stored in the Buffer member is the name of the
image file.
If the buffer is too small, the function fails with the
STATUS_INFO_LENGTH_MISMATCH error code and the ReturnLength parameter
is set to the required buffer size.
So I allocated a block of memory of
sizeof(UNICODE_STRING)+(MAX_FNAME+1)*2
bytes and initialize the UNICODE_STRING header. This BugChecks
with "BAD_POOL_CALLER" with a subcode of "double-free".
Eventually, I use the eSet code (almost) exactly as-is. Finally,
something works. It turns out that the UNICODE_STRING header will
be set inside ZwQueryInformationProcess(), all I need to do is provide
the raw memory buffer. So now I have something that has been running
for about four hours.
Here is the final working version:
Process.cpp:
/*************************************************************************/
/** Process.cpp: Query process information. **/
/** (C)2015 nlited systems inc, Chip Doran **/
/*************************************************************************/
#include <ntifs.h>
#include <ntstrsafe.h>
#include <WinDef.h>
#include "StdTypes.h"
#include "Errors.h"
#include "VerID.h"
#include "OS.h"
#include "Globals.h"
typedef NTSTATUS (__stdcall *ZWQUERYINFORMATIONPROCESS)(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
);
static ZWQUERYINFORMATIONPROCESS _ZwQueryInformationProcess= 0;
static void *ProcFindFunction(const WCHAR *Name);
/*************************************************************************/
/** Public interface **/
/*************************************************************************/
int ProcGetImagePath(UINT64 ProcID, WCHAR *pDst, UINT DstSz) {
int Err= ERR_OK;
NTSTATUS Status;
PEPROCESS eProc=0;
HANDLE hProc=0;
UNICODE_STRING *pName=0;
ULONG MaxLength= 1024; //Buffer length (bytes)
if(!_ZwQueryInformationProcess)
_ZwQueryInformationProcess= (ZWQUERYINFORMATIONPROCESS)ProcFindFunction(L"ZwQueryInformationProcess");
if(!_ZwQueryInformationProcess) {
Err= WarnA(ERR_NOT_SUPPORTED,FUNCA "Bummer, ZwQueryInformationProcess not found.");
} else if(!NT_SUCCESS(Status= PsLookupProcessByProcessId((HANDLE)ProcID,&eProc))) {
Err= WarnA(ERR_NOT_FOUND,FUNCA"ProcID %X not found.",ProcID);
} else if(!NT_SUCCESS(Status= ObOpenObjectByPointer(eProc,0,0,0,0,KernelMode,&hProc)) || !hProc) {
Err= WarnA(ERR_NOT_FOUND,FUNCA"Unable to transform eProc[%X] to hProc.",eProc);
} else if(!(pName= (UNICODE_STRING*)ExAllocatePoolWithTag(NonPagedPool,MaxLength,'ipgD'))) {
Err= WarnA(ERR_NO_MEM,FUNCA"Unable to alloc %d.",MaxLength);
} else {
RtlZeroMemory(pName,MaxLength);
//pName will be initialized by ZwQuery()
if(!NT_SUCCESS(Status= _ZwQueryInformationProcess(hProc,ProcessImageFileName,pName,MaxLength,&MaxLength))) {
Err= WarnA(ERR_FAILED,FUNCA"ZwQueryInformationProcess failed. [%X]",Status);
} else if(!pName->Length) {
Err= WarnA(ERR_NOT_FOUND,FUNCA"No info for ProcID[%X]",ProcID);
} else {
PrintA(PRINT_DEBUG,FUNCA"ProcID[%X] is '%wZ'",ProcID,pName);
RtlStringCchCopyUnicodeString(pDst,DstSz,pName);
}
ExFreePoolWithTag(pName,'ipgD');
}
if(hProc)
ZwClose(hProc);
if(eProc)
ObDereferenceObject(eProc);
return(Err);
}
/*************************************************************************/
/** Public internals **/
/*************************************************************************/
static void *ProcFindFunction(const WCHAR *Name) {
UNICODE_STRING NameU;
void *pFunc;
RtlInitUnicodeString(&NameU,Name);
pFunc= MmGetSystemRoutineAddress(&NameU);
if(!pFunc)
WarnA(ERR_NOT_SUPPORTED,FUNCA" System function '%S' not found.",Name);
return(pFunc);
}
I am also trying to figure out how to elevate my security rights
when NetMonUI is started as a normal user: RunAs
WebV7 (C)2018 nlited | Rendered by tikope in 31.673ms | 18.118.193.223