From: Arno Garrels on
Hello,

I know that there's a working, documented solution for XP, however
that returns a device name instead of a DOS name, and I really need
something that works at least in W2K as well. Here is a Q&D snippet
that works in current process but raises an exception while accessing
the ImagePathName->Buffer member.

-----

#define BASE_PROCESS_PEB_OFFSET 0x01B0
#define BASE_PEB_PROCESS_PARAMETER_OFFSET 0x0010
#define BASE_PROCESS_PARAMETER_FULL_IMAGE_NAME 0x003C

PCWSTR
GetProcessImageName(
HANDLE hProcessId
)
{
PEPROCESS ProcessPointer;
ULONG uLong = 0;

if (KeGetCurrentIrql() != PASSIVE_LEVEL){
DebugPrint(("*NO PASSIVE_LEVEL*\n"));
return NULL;
}

// If ProcessPointer points to current process, no problem!
// But we are called from the ProcessNotifyCallback, so when
// a new process is going to be created we need to get the
// new Process object.
uLong = PsLookupProcessByProcessId((ULONG)hProcessId, &ProcessPointer);
if (!NT_SUCCESS(uLong))
{
DebugPrint(("*PsLookupProcessByProcessId Failed 0x%x\n*", uLong));
return NULL;
}

try
{
uLong = (ULONG)(PVOID *) ProcessPointer;

if(uLong == 0 || uLong == 0xFFFFFFFF) {
DebugPrint(("*Invalid EPROCESS\n*"));
return 0;
}

//PEB
// It's XP in this case
uLong += BASE_PROCESS_PEB_OFFSET;

if((uLong = *(ULONG*)uLong) == 0){
DebugPrint(("*PebBaseAddr is NULL\n*"));
return 0;
}

// Peb->PRTL_USER_PROCESS_PARAMETERS
uLong += BASE_PEB_PROCESS_PARAMETER_OFFSET;
if ((uLong = *(ULONG*)uLong) == 0){
DebugPrint(("*PRTL_USER_PROCESS_PARAMETERS is NULL\n*"));
return 0;
}

try
{
// On dereferencing the ImagePathName member the exception is raised
// However the Length member is accessable and counts the correct
// number of bytes, so the buffer should cointain data.
// PRTL_USER_PROCESS_PARAMETERS->ImagePathName
uLong += BASE_PROCESS_PARAMETER_FULL_IMAGE_NAME;
if ((uLong = *(ULONG*)uLong) == 0){
DebugPrint(("*ImagePathName is NULL\n*"));
return NULL;
}
DebugPrint(("ImageName %ws\n", (PCWSTR)uLong));
return (PCWSTR)uLong;
}
except(EXCEPTION_EXECUTE_HANDLER)
{
uLong = GetExceptionCode();
DebugPrint((
"Exception while accessing ImagePathName.Buffer 0X%08X \n", uLong));
}
} finally {
ObDereferenceObject(ProcessPointer);
}
return 0;
}

-----

I tried KeAttachProcess()/KeDetachProcess() but no change, ZwReadVirtualMemory()
also returns an exception status.

Hopefully someone can help.

Arno Garrels

From: anton bassov on
On W2K you can do something that user-mode GetModuleFileName() and
GetModuleFileNameEx() do, i.e. obtain the image path right from PEB....

First of all, call ZwQueryInformationProcess() with infoclass 0 - it
will return the following structure :


typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
LONG BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION ;

Its second parameter is a pointer to PEB. Cast this pointer to the
following structure:

typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[9];
PPEB_LDR_DATA LoaderData;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
BYTE Reserved3[448];
ULONG SessionId;
} PEB, *PPEB;

Then cast 'ProcessParameters' of PEB structure to
RTL_USER_PROCESS_PARAMETERS, which is declared like:

typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[56];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
BYTE Reserved2[92];
} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;


At this point you will be able to get UNICODE_STRING that hold s the
full path to the image file on the disk....


I make an assumption that you are obtaining the path of the caller
process. If you want to obtain the path of some other process, you have
to attach your thread to its address space with KeStackAttachProcess()
before you access PEB.


All above mentioned structures are documented on MSDN, so that you are
not going to have any trouble with them


Anton Bassov

Arno Garrels wrote:
> Hello,
>
> I know that there's a working, documented solution for XP, however
> that returns a device name instead of a DOS name, and I really need
> something that works at least in W2K as well. Here is a Q&D snippet
> that works in current process but raises an exception while accessing
> the ImagePathName->Buffer member.
>
> -----
>
> #define BASE_PROCESS_PEB_OFFSET 0x01B0
> #define BASE_PEB_PROCESS_PARAMETER_OFFSET 0x0010
> #define BASE_PROCESS_PARAMETER_FULL_IMAGE_NAME 0x003C
>
> PCWSTR
> GetProcessImageName(
> HANDLE hProcessId
> )
> {
> PEPROCESS ProcessPointer;
> ULONG uLong = 0;
>
> if (KeGetCurrentIrql() != PASSIVE_LEVEL){
> DebugPrint(("*NO PASSIVE_LEVEL*\n"));
> return NULL;
> }
>
> // If ProcessPointer points to current process, no problem!
> // But we are called from the ProcessNotifyCallback, so when
> // a new process is going to be created we need to get the
> // new Process object.
> uLong = PsLookupProcessByProcessId((ULONG)hProcessId, &ProcessPointer);
> if (!NT_SUCCESS(uLong))
> {
> DebugPrint(("*PsLookupProcessByProcessId Failed 0x%x\n*", uLong));
> return NULL;
> }
>
> try
> {
> uLong = (ULONG)(PVOID *) ProcessPointer;
>
> if(uLong == 0 || uLong == 0xFFFFFFFF) {
> DebugPrint(("*Invalid EPROCESS\n*"));
> return 0;
> }
>
> //PEB
> // It's XP in this case
> uLong += BASE_PROCESS_PEB_OFFSET;
>
> if((uLong = *(ULONG*)uLong) == 0){
> DebugPrint(("*PebBaseAddr is NULL\n*"));
> return 0;
> }
>
> // Peb->PRTL_USER_PROCESS_PARAMETERS
> uLong += BASE_PEB_PROCESS_PARAMETER_OFFSET;
> if ((uLong = *(ULONG*)uLong) == 0){
> DebugPrint(("*PRTL_USER_PROCESS_PARAMETERS is NULL\n*"));
> return 0;
> }
>
> try
> {
> // On dereferencing the ImagePathName member the exception is raised
> // However the Length member is accessable and counts the correct
> // number of bytes, so the buffer should cointain data.
> // PRTL_USER_PROCESS_PARAMETERS->ImagePathName
> uLong += BASE_PROCESS_PARAMETER_FULL_IMAGE_NAME;
> if ((uLong = *(ULONG*)uLong) == 0){
> DebugPrint(("*ImagePathName is NULL\n*"));
> return NULL;
> }
> DebugPrint(("ImageName %ws\n", (PCWSTR)uLong));
> return (PCWSTR)uLong;
> }
> except(EXCEPTION_EXECUTE_HANDLER)
> {
> uLong = GetExceptionCode();
> DebugPrint((
> "Exception while accessing ImagePathName.Buffer 0X%08X \n", uLong));
> }
> } finally {
> ObDereferenceObject(ProcessPointer);
> }
> return 0;
> }
>
> -----
>
> I tried KeAttachProcess()/KeDetachProcess() but no change, ZwReadVirtualMemory()
> also returns an exception status.
>
> Hopefully someone can help.
>
> Arno Garrels

From: Arno Garrels on
anton bassov wrote:
>
> At this point you will be able to get UNICODE_STRING that hold s the
> full path to the image file on the disk....

Thanks for the speedy reply. However I don't see any difference, the
snippet I posted works fine in the current/caller process. AFAIR I tried
to get the PebBaseAddress by ZwQueryInformationProcess() as well but
that didn't make a difference.

>
> I make an assumption that you are obtaining the path of the caller
> process.

Yup

> If you want to obtain the path of some other process, you
> have to attach your thread to its address space with
> KeStackAttachProcess() before you access PEB.

Well, I tried KeAttachProcess() before accessing the PEB, it should
switch to the context of the other process, I've not yet tried
KeStackAttachProcess(), is this the winning point?

Arno Garrels
From: anton bassov on
> However I don't see any difference,

Try to do it the way I say - you will see the difference right on the
spot.....


> the snippet I posted works fine in the current/caller process.

If this is the case, how would you explain the excerpt below then????


[begin quote]
Here is a Q&D snippet that works in current process but raises an
exception while accessing the ImagePathName->Buffer member.
[end quote]


The only reason why I provided structure declarations is to make you
understand that there is a bug in your code - image path is
UNICODE_STRING and not
PUNICODE_STRING, so that it should be accessed as
'ImagePathName.Buffer' , rather than
'ImagePathName->Buffer'......

The line 'ImagePathName->Buffer' is the only reason for the exception.
In general, it is better to overlay structures on memory blocks, rather
than just using offsets. Such approach may save you an awful lot of
time - it may take quite a while to find a bug like that, because
people normally suspect a problem to lie somewhere else....

Anton Bassov

Arno Garrels wrote:
> anton bassov wrote:
> >
> > At this point you will be able to get UNICODE_STRING that hold s the
> > full path to the image file on the disk....
>
> Thanks for the speedy reply. However I don't see any difference, the
> snippet I posted works fine in the current/caller process. AFAIR I tried
> to get the PebBaseAddress by ZwQueryInformationProcess() as well but
> that didn't make a difference.
>
> >
> > I make an assumption that you are obtaining the path of the caller
> > process.
>
> Yup
>
> > If you want to obtain the path of some other process, you
> > have to attach your thread to its address space with
> > KeStackAttachProcess() before you access PEB.
>
> Well, I tried KeAttachProcess() before accessing the PEB, it should
> switch to the context of the other process, I've not yet tried
> KeStackAttachProcess(), is this the winning point?
>
> Arno Garrels

From: Arno Garrels on
anton bassov wrote:

> The only reason why I provided structure declarations is to make you
> understand that there is a bug in your code - image path is
> UNICODE_STRING and not
> PUNICODE_STRING, so that it should be accessed as
> 'ImagePathName.Buffer' , rather than
> 'ImagePathName->Buffer'......

I do not see a bug, it's just a typo in the comment!

#define BASE_PROCESS_PARAMETER_FULL_IMAGE_NAME 0x003C

0:000> !kdex2x86.strct _RTL_USER_PROCESS_PARAMETERS
struct _RTL_USER_PROCESS_PARAMETERS (sizeof=656)
+000 uint32 MaximumLength
+004 uint32 Length
+008 uint32 Flags
+00c uint32 DebugFlags
+010 void *ConsoleHandle
+014 uint32 ConsoleFlags
+018 void *StandardInput
+01c void *StandardOutput
+020 void *StandardError
+024 struct _CURDIR CurrentDirectory
+024 struct _UNICODE_STRING DosPath
+024 uint16 Length
+026 uint16 MaximumLength
+028 uint16 *Buffer
+02c void *Handle
+030 struct _UNICODE_STRING DllPath
+030 uint16 Length
+032 uint16 MaximumLength
+034 uint16 *Buffer
+038 struct _UNICODE_STRING ImagePathName
+038 uint16 Length
+03a uint16 MaximumLength
+03c uint16 *Buffer

> In general, it is better to overlay structures on memory
> blocks, rather than just using offsets.

Agreed, but using structures gives the same exception.

Again, it works when called in current process!
The exception is raised when current context is parent context,
upon accessing the Buffer member of the UNICODE_STRING structure.
In other words the same always works with PsGetCurrentProcess() as
well as when the process is terminated.
Accessing the Length member is no problem also it always contains
the correct number of bytes. KeStackAttachProcess() as well as
KeAttachProcess() do not change anything.

Arno Garrels