本文共计5933个字,预计阅读时长22.2分钟。
首先明确一个事实,EPROCESS内部的一个成员(这里应该说是“EPROCESS的成员的成员的成员的成员”更为准确)名为FileObject,它记录了进程的路径。通过IoQueryFileDosDeviceName查询FileObject的信息即可获得进程路径。所以,获取进程路径,可以简化为获取进程的FileObject。
接下来看一下REACTOS是怎么获取FileObject的:
NTSTATUS NTAPI PsReferenceProcessFilePointer
( IN PEPROCESS Process, OUT PFILE_OBJECT * FileObject )
{ PSECTION Section; PAGED_CODE();/* Lock the process */ ExAcquireRundownProtection(&Process->RundownProtect); /* Get the section */ Section = Process->SectionObject; if (Section) { /* Get the file object and reference it */ *FileObject = MmGetFileObjectForSection((PVOID)Section); ObReferenceObject(*FileObject); } /* Release the protection */ ExReleaseRundownProtection(&Process->RundownProtect); /* Return status */ return Section ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
}
PFILE_OBJECT NTAPI MmGetFileObjectForSection ( IN PVOID Section )
{ PSECTION_OBJECT Section; ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); ASSERT(SectionObject != NULL);/* Check if it's an ARM3, or ReactOS section */ if (MiIsRosSectionObject(SectionObject) == FALSE) { /* Return the file pointer stored in the control area */ Section = SectionObject; return Section->Segment->ControlArea->FilePointer; } /* Return the file object */ return ((PROS_SECTION_OBJECT)SectionObject)->FileObject;
}
简单说,FileObject是这么取得的:Process->SectionObject->Segment->ControlArea->FilePointer。
在NT5的年代,PsReferenceProcessFilePointer是一个没有导出的函数,如果要从EPROCESS取得FilePointer,大概要4个硬编码(简单说就是自行实现PsReferenceProcessFilePointer)。但从VISTA-6000开始,PsReferenceProcessFilePointer被导出了,所以从此之后获得进程路径就不需要任何硬编码了。代码如下:
PUNICODE_STRING PsGetProcessFullName(PEPROCESS pTargetProcess) { PFILE_OBJECT pFileObject=NULL; POBJECT_NAME_INFORMATION pObjectNameInfo=NULL; if(!NT_SUCCESS(PsReferenceProcessFilePointer(pTargetProcess,&pFileObject))) return NULL; if(!NT_SUCCESS(IoQueryFileDosDeviceName(pFileObject,&pObjectNameInfo))) return NULL; return &(pObjectNameInfo->Name);//尚未释放内存 以及 ObDereferenceObject }
此外,IoQueryFileDosDeviceName是从XP开始才有的API。如果要支持WIN2000,除了要自己实现PsReferenceProcessFilePointer之外,还需要自己实现IoQueryFileDosDeviceName:1.使用ObQueryNameString查询FileObject的NT路径;2.把NT路径自行转为DOS路径。
除此之外,还有另外一个只需要一个硬编码即可获取的路径方法: Process->SeAuditProcessCreationInfo.ImageFileName->Name。 因为需要硬编码,因此只推荐在NT5系统上使用这个方法。可惜这个地方的数据似乎还不一定存在。见WRK:
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;
}
除了EPROCESS里记录了两份路径数据,在PEB里还记录了几份(如果是WOW64进程则还分PEB32和PEB64),他们分别是: PEB->ProcessParameters->ImagePathName PEB->ProcessParameters->CommandLine PEB->ProcessParameters->WindowTitle PEB->Ldr->InLoadOrderLinks->FullDllName PEB->Ldr->InMemoryOrderLinks->FullDllName 获取这些数据需要读取进程内存,而且在RING3可以修改掉,所以一般不推荐从PEB里取数据。
无句柄获取进程路径(无硬编码/代码10行/兼容WIN64)