进程完整路径获取方式详解

    技术2025-12-15  14

    获取进程对应可执行文件的完整路径,在编程中经常遇到。下面做了下详细分析。欢迎指正! 获取进程完整路径方法小结 最近在做一个xx项目,少不了要拦截进程创建.拦截进程创建的方法在这里就不多说了,免得跑题(其实也就是各种Hook的运用).拦截到进程创建后,需要获得进程对应的可执行文件完整路径,因为只有进一步分析可执行文件,才能获得更多信息来判断该进程的好坏.于是有了本文的产生. 很多人对于获取进程的完整路径可能不屑一顾.以前写过的代码中确实也遇到过,利用google几分钟内,你会得到几个Api,例如:GetModuleFileNameEx,GetProcessImageFileName等.但对于Ring3层提供的Api,Windows内核层做了什么,又有多少人很明确的知道?杀毒软件与病毒木马一直玩着猫和老鼠的游戏,病毒木马稍作手脚,Ring3获取进程完整路径时就会失败.只有我们知道这些Api的本质,才能以不变应万变.下面将从Windows内核层分析这些Api的本质,并提供几种获取进程完整路径及防止别人获取进程完整路径的方法. 一.  进程内核数据结构中与路径信息相关位置   _EPROCESS->_SE_AUDIT_PROCESS_CREATION_INFO->_OBJECT_NAME_INFORMATION       从上面的截图可以看到,在进程内核对象EPROCESS偏移0x1f4处存放着_SE_AUDIT_PROCESS_CREATION_INFO结构的指针,该指针处又存放着_OBJECT_NAME_INFORMATION结构的指针,而该结构中存放着该进程的NT式文件路径.   _EPROCESS->_SECTION_OBJECT->_SEGMENT_OBJECT->_CONTROL_AREA->_FILE_OBJECT:       从上面的截图可以看到进程内核对象偏移0x138处存放着指向_SECTION_OBJECT结构的指针,而该结构的0x14处存放着_SEGMENT_OBJECT结构指针,_SEGMENT_OBJECT结构偏移0x0处是_CONTROL_AREA结构的指针,该结构偏移0x24处是_FILE_OBJECT结构的指针,利用该结构的_DEVICE_OBJECT,FileName两个成员和RtlVolumeDeviceToDosName函数可以获取进程文件的DOS完整路径   _PEB->_PEB_LDR_DATA->_LDR_DATA_TABLE_ENTRY:     可以看到_PEB偏移0xc处是_PEB_LDR_DATA数据结构,该结构偏移0xc处是_LDR_DATA_TABLE_ENTRY数据结构.但是需要注意的是,上面的成员获取必须在对应进程的上下文.   _PEB->_RTL_USER_PROCESS_PARAMETERS:     同样是_PEB结构,偏移0x10处是_RTL_USER_PROCESS_PARAMETERS结构.同样需要在目标进程的上下文空间. 上面就是进程内核对象中存放路径的相关位置,当然也许还有其他地方,麻烦知道的大牛告诉我,谢谢! 二.  微软提供的获取进程路径相关函数分析 1.  GetProcessImageFileName函数: DWORD GetProcessImageFileName(   HANDLE hProcess,//目标进程句柄   LPTSTR lpImageFileName,//提供的空间,用于保存获取到的进程路径   DWORD nSize//空间大小 );//返回实际路径长度 上面的函数获取的进程可执行文件NT式完整路径.从前面的分析中我们可以看到, _EPROCESS->_SE_AUDIT_PROCESS_CREATION_INFO->_OBJECT_NAME_INFORMATION该处存放的就是NT式完整路径.我们来验证下: 0:000> uf PSAPI!GetProcessImageFileNameA PSAPI!GetProcessImageFileNameA: 76bc3dbd 8bff            mov     edi,edi 76bc3dbf 55              push    ebp 76bc3dc0 8bec            mov     ebp,esp 76bc3dc2 53              push    ebx 76bc3dc3 8b5d10          mov     ebx,dword ptr [ebp+10h] 76bc3dc6 56              push    esi 76bc3dc7 57              push    edi 76bc3dc8 8d5c1b08        lea     ebx,[ebx+ebx+8] 76bc3dcc 53              push    ebx 76bc3dcd 33ff            xor     edi,edi 76bc3dcf 57              push    edi 76bc3dd0 ff155c10bc76    call    dword ptr [PSAPI!_imp__LocalAlloc (76bc105c)] 76bc3dd6 8bf0            mov     esi,eax 76bc3dd8 3bf7            cmp     esi,edi 76bc3dda 7504            jne     PSAPI!GetProcessImageFileNameA+0x23 (76bc3de0) PSAPI!GetProcessImageFileNameA+0x1f: 76bc3ddc 33db            xor     ebx,ebx 76bc3dde eb53            jmp     PSAPI!GetProcessImageFileNameA+0x76 (76bc3e33) PSAPI!GetProcessImageFileNameA+0x23: 76bc3de0 57              push    edi 76bc3de1 53              push    ebx 76bc3de2 56              push    esi 76bc3de3 6a1b            push    1Bh 76bc3de5 ff7508          push    dword ptr [ebp+8] 76bc3de8 ff15d810bc76    call    dword ptr [PSAPI!_imp__NtQueryInformationProcess (76bc10d8)] 76bc3dee 3d040000c0      cmp     eax,0C0000004h 76bc3df3 7503            jne     PSAPI!GetProcessImageFileNameA+0x3b (76bc3df8) PSAPI!GetProcessImageFileNameA+0x38: 76bc3df5 83c01f          add     eax,1Fh PSAPI!GetProcessImageFileNameA+0x3b: 76bc3df8 3bc7            cmp     eax,edi 76bc3dfa 7d12            jge     PSAPI!GetProcessImageFileNameA+0x51 (76bc3e0e) PSAPI!GetProcessImageFileNameA+0x3f: 76bc3dfc 50              push    eax 76bc3dfd ff15e410bc76    call    dword ptr [PSAPI!_imp__RtlNtStatusToDosError (76bc10e4)] 76bc3e03 50              push    eax 76bc3e04 ff156010bc76    call    dword ptr [PSAPI!_imp__SetLastError (76bc1060)] 76bc3e0a 33db            xor     ebx,ebx 76bc3e0c eb1e            jmp     PSAPI!GetProcessImageFileNameA+0x6f (76bc3e2c) PSAPI!GetProcessImageFileNameA+0x51: 76bc3e0e 0fb706          movzx   eax,word ptr [esi] 76bc3e11 57              push    edi 76bc3e12 57              push    edi 76bc3e13 ff7510          push    dword ptr [ebp+10h] 76bc3e16 ff750c          push    dword ptr [ebp+0Ch] 76bc3e19 50              push    eax 76bc3e1a ff7604          push    dword ptr [esi+4] 76bc3e1d 57              push    edi 76bc3e1e 57              push    edi 76bc3e1f ff156810bc76    call    dword ptr [PSAPI!_imp__WideCharToMultiByte (76bc1068)] 76bc3e25 8bd8            mov     ebx,eax 76bc3e27 3bdf            cmp     ebx,edi 76bc3e29 7401            je      PSAPI!GetProcessImageFileNameA+0x6f (76bc3e2c) PSAPI!GetProcessImageFileNameA+0x6e: 76bc3e2b 4b              dec     ebx PSAPI!GetProcessImageFileNameA+0x6f: 76bc3e2c 56              push    esi 76bc3e2d ff155810bc76    call    dword ptr [PSAPI!_imp__LocalFree (76bc1058)] 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;     }     if (Process->SectionObject == NULL) {         ExReleaseRundownProtection (&Process->RundownProtect);         return STATUS_UNSUCCESSFUL;     } //从进程内核对象的SectionObject处获取文件对象     FileObject = MmGetFileObjectForSection ((PVOID)Process->SectionObject);     *OutFileObject = FileObject;     ObReferenceObject (FileObject);     ExReleaseRundownProtection (&Process->RundownProtect);     return STATUS_SUCCESS; } 这个函数主要从_EPROCESS的_SECTION_OBJECT处获取文件对象,注意要防止进程退出。我们再深入看下MmGetFileObjectForSection这个函数: PFILE_OBJECT MmGetFileObjectForSection (     IN PVOID Section     ) /*++ Routine Description:     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         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;         }     }     *pAuditName = pInternalAuditName;     return Status; } 从上面的代码可以看到,起始就是调用一个ObQueryNameString函数,如果失败则将进程路径信息设置成空,返回。关于ObQueryNameString的源码我就不详细分析了,参见博文http://blog.csdn.net/misterliwei/archive/2009/08/20/4467301.aspx,主要是根据内核对象中的_OBJECT_HEADER和_OBJECT_HEADER_NAME_INFO结构获取。到此GetProcessImageFileName函数已经完全暴露在我们面前。 我们再简单回顾下:GetProcessImageFileName需要传入一个进程句柄,然后调用NtQueryInformationProcess函数的0x1b号功能,首先获取进程内核对象,然后调用SeLocateProcessImageName,该函数首先查看_EPROCESS结构0x1f4处的_SE_AUDIT_PROCESS_CREATION_INFO是否为空,如果非空直接从该处获取;反之需要自己获取,并填充到该处。首先会从_EPROCESS中获取_FILE_OBJECT对象,获取方法为:_EPROCESS->_SECTION_OBJECT->_SEGMENT_OBJECT->_CONTROL_AREA->_FILE_OBJECT,获取到文件对象后,利用SeInitializeProcessAuditName函数获取文件对象的路径,这个函数里边主要调用ObQueryNameString函数,该函数是未文档化的函数,简单申明后可以直接使用。该函数主要是遍历对象目录树,组建文件路径。 通过以上的分析,我有了以下简单的想法: 防御: (1)  该函数调用的前提条件是:进程的句柄。而应用层获取进程句柄的函数是OpenProcess,那么我们只要在这个函数上做下过滤,不让别人将自己映射到它的句柄表即可。 (2)  我们可以看到该函数其实调用的是NtQueryInformationProcess的0x1b号功能,那么我们只要在NtQueryInformationProcesss上做过滤同样可以。更深层数的hook,例如SeLocateProcessImageName、ObQueryNameString等同理。 (3)  从上面分析的过程可以看到,其实信息源是在_FILE_OBJECT对象的目录树中,_EPROCESS中的_SE_AUDIT_PROCESS_CREATION_INFO也只不过是个副本。因此我们可以尝试自己遍历_FILE_OBJECT对象目录树,然后将其到根目录的每个结点名删除,当然_SE_ADUIT_PROCESS_CREATION_INFO处的必须先删除。这可以说从根本上解决了GetProcessImageFileName函数的调用。只是猜想,未实践。不知道这样的擦出,会不会对进程本身产生影响!知道的麻烦告知!谢谢。 保护: (1)  针对OpenProcess的过滤,我们可以在内核中通过ObOpenObjetByPointer、KeStackAttachProcess将目标对象映射到我们指定的进程空间,然后将句柄传到应用层,就可以直接操作了。当然既然已经到内核层,也就没必要到应用层了。 (2)  对于NtQueryInformationProcess、SeLocateProcessImageName、ObQueryNameString等的Hook,我们既然知道其原理,就可以自己实现了。 (3)  对于最后这种信息的擦除,只能看看别的地方是否曾经保存过进程路径信息了。如_PEB中。 2.  GetModuleFileNameEx函数 我们还是从源码看起,没有什么比源码更具说服力的了。   可以看到代码首先调用PSAPI!FindModule函数,然后调用ReadProcessMemory。我们一个个看。首先是PSAPI!FindModule函数:   可以看到这次调用NtQueryInformationProcess的0号功能函数,获取的数据结构为: typedef struct _PROCESS_BASIC_INFORMATION {     PVOID Reserved1;     PPEB PebBaseAddress;//偏移0x4处     PVOID Reserved2[2];     ULONG_PTR UniqueProcessId;     PVOID Reserved3; } PROCESS_BASIC_INFORMATION; 可以看到偏移0x4处,也就是上面的ebp-0x1c处保存着Peb的指针,从_PEB结构可以看到偏移0xc处恰好为_LDR结构, 后面的就不看了。可以断定,该函数是从_PEB的_PEB_LDR_DATA中获取进程路径信息。同样我们怎么防护呢?原理与上面的基本相同。这个函数也需要进程句柄,所以可以过滤OpenProcess,同样可以利用更深层次的Hook,以及擦除信息。 3.  ReadProcessMemory+_RTL_USER_PROCESS_PARAMETERS 因为_RTL_USER_PROCESS_PARAMETERS中存放着进程的完整路径,结构如下所示:   这种方法的防御保护方法就不再重复了,同上。 4.  _FILE_OBJECT结构 该种方法是在内核实现的。看下_FILE_OBJECT的结构,   可以看到_FILE_OBJECT对象中的FileName中保存进程路径信息,再根据DeviceObject获取驱动器名,组合下就可以获取进程完整路径。 三.  后记 由前面的分析,我们可以看到:在内核态如果我们想要获取进程的Dos完整路径,我们可以先利用ZwQueryInformationProcess的0x1b号功能,获取到进程的NT路径,然后再利用ZwOpenFile打开文件,再利用ObReferenceObjectByHandle获取到文件对象,然后利用RtlVolumeDeviceToDosName获得驱动器名,再组合下即可。
    最新回复(0)