PSAPI!GetProcessImageFileNameA+0x76:
76bc3e33 5f pop edi
76bc3e34 5e pop esi
76bc3e35 8bc3 mov eax,ebx
76bc3e37 5b pop ebx
76bc3e38 5d pop ebp
76bc3e39 c20c00 ret 0Ch
从上面的代码可以看到GetProcessImageFileName是调用NtQueryInformationProcess的0x1b号功能。我们再看下wrk中NtQueryInformationProcess函数中的0x1b号功能代码:
NTSTATUS
NtQueryInformationProcess(
__in HANDLE ProcessHandle,
__in PROCESSINFOCLASS ProcessInformationClass,
__out_bcount(ProcessInformationLength) PVOID ProcessInformation,
__in ULONG ProcessInformationLength,
__out_opt PULONG ReturnLength
)
{
。。。。(省略)
switch ( ProcessInformationClass ) {
case ProcessImageFileName:(0x1b号功能)
{
ULONG LengthNeeded = 0;
//先根据进程句柄获取进程内核对象
st = ObReferenceObjectByHandle (ProcessHandle,
PROCESS_QUERY_INFORMATION,
PsProcessType,
PreviousMode,
&Process,
NULL);
if (!NT_SUCCESS (st)) {
return st;
}
//
// SeLocateProcessImageName will allocate space for a UNICODE_STRING and point pTempNameInfo
// at that string. This memory will be freed later in the routine.
//从进程内核对象中获取路径,然后复制到输出缓冲
st = SeLocateProcessImageName (Process, &pTempNameInfo);
if (!NT_SUCCESS(st)) {
ObDereferenceObject(Process);
return st;
}
LengthNeeded = sizeof(UNICODE_STRING) + pTempNameInfo->MaximumLength;
//
// Either of these may cause an access violation. The
// exception handler will return access violation as
// status code. No further cleanup needs to be done.
//
try {
if (ARGUMENT_PRESENT(ReturnLength) ) {
*ReturnLength = LengthNeeded;
}
if (ProcessInformationLength >= LengthNeeded) {
RtlCopyMemory(
ProcessInformation,
pTempNameInfo,
sizeof(UNICODE_STRING) + pTempNameInfo->MaximumLength
);
((PUNICODE_STRING) ProcessInformation)->Buffer = (PWSTR)((PUCHAR) ProcessInformation + sizeof(UNICODE_STRING));
} else {
st = STATUS_INFO_LENGTH_MISMATCH;
}
} except(EXCEPTION_EXECUTE_HANDLER) {
st = GetExceptionCode ();
}
ObDereferenceObject(Process);
ExFreePool( pTempNameInfo );
return st;
}
。。。。。。。。。。。(省略)
NtQueryInformationProcess函数先根据进程句柄获取进程内核对象,然后调用SeLocateProcessImageName函数,该函数是重点,其代码如下:
NTSTATUS
SeLocateProcessImageName(
__in PEPROCESS Process,
__deref_out PUNICODE_STRING *pImageFileName
)
/*++
Routine Description
This routine returns the ImageFileName information from the process, if available. This is a "lazy evaluation" wrapper
around SeInitializeProcessAuditName. If the image file name information has already been computed, then this call simply
allocates and returns a UNICODE_STRING with this information. Otherwise, the function determines the name, stores the name in the
EPROCESS structure, and then allocates and returns a UNICODE_STRING. Caller must free the memory returned in pImageFileName.
Arguments
Process - process for which to acquire the name
pImageFileName - output parameter to return name to caller
Return Value
NTSTATUS.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
PVOID FilePointer = NULL;
PVOID PreviousValue = NULL;
POBJECT_NAME_INFORMATION pProcessImageName = NULL;
PUNICODE_STRING pTempUS = NULL;
ULONG NameLength = 0;
PAGED_CODE();
*pImageFileName = NULL;
if (NULL == Process->SeAuditProcessCreationInfo.ImageFileName) {
//如果进程内核对象中的文件名未被计算,则根据进程的文件对象自己计算
// The name has not been predetermined. We must determine the process name. First, reference the
// PFILE_OBJECT and lookup the name. Then again check the process image name pointer against NULL.
// Finally, set the name.
//获取进程对象中的文件对象
Status = PsReferenceProcessFilePointer( Process, &FilePointer );
if (NT_SUCCESS(Status)) {
//
// Get the process name information.
//从文件对象中获取进程路径名
Status = SeInitializeProcessAuditName(
FilePointer,
TRUE, // skip audit policy
&pProcessImageName // to be allocated in nonpaged pool
);
if (NT_SUCCESS(Status)) {
//
// Only use the pProcessImageName if the field in the process is currently NULL.
//
PreviousValue = InterlockedCompareExchangePointer(
(PVOID *) &Process->SeAuditProcessCreationInfo.ImageFileName,
(PVOID) pProcessImageName,
(PVOID) NULL
);
if (NULL != PreviousValue) {
ExFreePool(pProcessImageName); // free what we caused to be allocated.
}
}
ObDereferenceObject( FilePointer );
}
}
//如果进程对象中的文件名已经计算,则直接从该处复制
if (NT_SUCCESS(Status)) {
//
// Allocate space for a buffer to contain the name for returning to the caller.
//
NameLength = sizeof(UNICODE_STRING) + Process->SeAuditProcessCreationInfo.ImageFileName->Name.MaximumLength;
pTempUS = ExAllocatePoolWithTag( NonPagedPool, NameLength, 'aPeS' );
if (NULL != pTempUS) {
RtlCopyMemory(
pTempUS,
&Process->SeAuditProcessCreationInfo.ImageFileName->Name,
NameLength
);
pTempUS->Buffer = (PWSTR)(((PUCHAR) pTempUS) + sizeof(UNICODE_STRING));
*pImageFileName = pTempUS;
} else {
Status = STATUS_NO_MEMORY;
}
}
return Status;
}
从上面的代码可以看出,函数首先检查_RPROCESS结构中的_SE_AUDIT_PROCESS_CREATION_INFO处是否为空,如果非空直接复制该处保存的进程路径信息;反之则需要根据进程对象的文件对象重新计算进程路径信息,先调用PsReferenceProcessFilePointer获取进程对象的文件对象,该函数代码如下:
NTSTATUS
PsReferenceProcessFilePointer (
IN PEPROCESS Process,
OUT PVOID *OutFileObject
)
/*++
Routine Description:
This routine returns a referenced pointer to the FilePointer of Process.
This is a rundown protected wrapper around MmGetFileObjectForSection.
Arguments:
Process - Supplies the process to query.
OutFileObject - Returns the file object backing the requested section if
success is returned.
Return Value:
NTSTATUS.
Environment:
Kernel mode, PASSIVE_LEVEL.
--*/
{
PFILE_OBJECT FileObject;
PAGED_CODE();
if (!ExAcquireRundownProtection (&Process->RundownProtect)) {
return STATUS_UNSUCCESSFUL;
}
This routine returns a pointer to the file object backing a section object.
Arguments:
Section - Supplies the section to query.
Return Value:
A pointer to the file object backing the argument section.
Environment:
Kernel mode, PASSIVE_LEVEL.
The caller must ensure that the section is valid for the
duration of the call.
--*/
{
PFILE_OBJECT FileObject;
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
ASSERT (Section != NULL);
FileObject = ((PSECTION)Section)->Segment->ControlArea->FilePointer;
return FileObject;
}
看到上面的代码很熟悉吧,上面我们已经分析过了。现在已经获取到进程文件对象了,我们来看下如何获取进程路径信息,
NTSTATUS
SeInitializeProcessAuditName (
__in __typefix(PFILE_OBJECT) PVOID FileObject,
__in BOOLEAN bIgnoreAuditPolicy,
__deref_out POBJECT_NAME_INFORMATION *pAuditName
)
/*++
Routine Description:
This routine initializes the executable name for auditing purposes. It allocates memory for the
image file name. This memory is pointed to by pAuditName.
Arguments:
FileObject - Supplies a pointer to a file object for the image being
executed.
bIgnoreAuditPolicy - boolean that indicates that the call should proceed without
regard to the system's auditing policy.
pAuditName - Supplies a pointer to a pointer for the object name information.
Return value:
NTSTATUS.
Environment:
KeAttached to the target process so not all system services are available.
--*/
{
NTSTATUS Status;
OBJECT_NAME_INFORMATION TempNameInfo;
ULONG ObjectNameInformationLength;
POBJECT_NAME_INFORMATION pInternalAuditName;
PFILE_OBJECT FilePointer;
PAGED_CODE();
ASSERT (pAuditName != NULL);
*pAuditName = NULL;
//
// Check if the caller would like to get the process name, even if auditing does not
// require it.
//
if (FALSE == bIgnoreAuditPolicy) {
//
// At the time of process creation, this routine should only proceed when Object Access or
// Detailed Tracking auditing is enabled. In all other cases, the process name is acquired
// when it is requested.
//
if (!SepAdtAuditThisEventWithContext( AuditCategoryObjectAccess, TRUE, FALSE, NULL ) &&
!SepAdtAuditThisEventWithContext( AuditCategoryDetailedTracking, TRUE, FALSE, NULL )) {
return STATUS_SUCCESS;
}
}
FilePointer = (PFILE_OBJECT) FileObject;
//
// Compute full path for imagefile.
// This first call to ObQueryNameString is guaranteed to fail.
// The ObjectNameInformationLength contains only a
// UNICODE_STRING, so if this call succeeded it would indicate
// an imagefile name of length 0. That is bad, so all return
// values except STATUS_BUFFER_OVERFLOW (from NTFS) and
// STATUS_BUFFER_TOO_SMALL (from DFS). This call gives
// me the buffer size that I need to store the image name.
//
pInternalAuditName = &TempNameInfo;
ObjectNameInformationLength = sizeof(OBJECT_NAME_INFORMATION);
//
Status = ObQueryNameString (FilePointer,
pInternalAuditName,
ObjectNameInformationLength,
&ObjectNameInformationLength);
if ((Status == STATUS_BUFFER_OVERFLOW) ||
(Status == STATUS_BUFFER_TOO_SMALL)) {
//
// Sanity check ObQueryNameString. Different filesystems
// may be buggy, so make sure that the return length makes
// sense (that it has room for a non-NULL Buffer in the
// UNICODE_STRING).
//
if (ObjectNameInformationLength > sizeof(OBJECT_NAME_INFORMATION)) {
pInternalAuditName = ExAllocatePoolWithTag (NonPagedPool,
ObjectNameInformationLength, 'aPeS');
if (pInternalAuditName != NULL) {
Status = ObQueryNameString (FilePointer,
pInternalAuditName,
ObjectNameInformationLength,
&ObjectNameInformationLength);
if (!NT_SUCCESS(Status)) {
#if DBG
DbgPrint("\n** ObqueryNameString failed with 0x%x.\n", Status);
#endif //DBG
//
// If the second call to ObQueryNameString did not succeed, then
// something is very wrong. Set the image name to NULL string.
//
// Free the memory that the first call to ObQueryNameString requested,
// and allocate enough space to store an empty UNICODE_STRING.
//
ExFreePool (pInternalAuditName);
ObjectNameInformationLength = sizeof(OBJECT_NAME_INFORMATION);
pInternalAuditName = ExAllocatePoolWithTag (NonPagedPool,
ObjectNameInformationLength,
'aPeS');
if (pInternalAuditName != NULL) {
RtlZeroMemory(pInternalAuditName, ObjectNameInformationLength);
//
// Status = STATUS_SUCCESS to allow the process creation to continue.
//
Status = STATUS_SUCCESS;
} else {
Status = STATUS_NO_MEMORY;
}
}
} else {
Status = STATUS_NO_MEMORY;
}
} else {
//
// If this happens, then ObQueryNameString is broken for the FS on which
// it was called.
//
#if DBG
DbgPrint("\n** ObqueryNameString failed with 0x%x.\n", Status);
#endif //DBG
ObjectNameInformationLength = sizeof(OBJECT_NAME_INFORMATION);
pInternalAuditName = ExAllocatePoolWithTag (NonPagedPool,
ObjectNameInformationLength, 'aPeS');
if (pInternalAuditName != NULL) {
RtlZeroMemory(pInternalAuditName, ObjectNameInformationLength);
//
// Status = STATUS_SUCCESS to allow the process creation to continue.
//
Status = STATUS_SUCCESS;
} else {
Status = STATUS_NO_MEMORY;
}
}
} else {
//
// If ObQueryNameString returns some other error code, we cannot
// be certain of which action to take, or whether it has properly
// set the ReturnLength. For example, ObQueryNameString has slightly
// different semantics under DFS than NTFS. Additionally, 3rd
// party file systems may also behave unpredictably. For these reasons,
// in the case of an unexpected error code from ObQueryNameString
// we set AuditName to zero length unicode string and allow process
// creation to continue.
//
#if DBG
DbgPrint("\n** ObqueryNameString failed with 0x%x.\n", Status);
#endif //DBG