进程完整路径获取方式底层实现剖析

背景:

最近有个功能点是获取进程名和进程完整路径,一般在内核层获取进程名使用“ PsGetProcessImageFileName”,跟进了一下这个函数,原理是直接读取相应进程EPROCESS结构体的ImageFileName成员,但是这个成员定义是UCHAR[15],所以获取的进程名最多只能 (15+‘\0’)个字符,超过 16个字节就给截断了,然后仔细研究了一下这一块。

NTKERNELAPI
UCHAR *
PsGetProcessImageFileName(
    __in PEPROCESS Process
    );

IDA查看该函数
在这里插入图片描述
所以这个函数获取进程名是通过 PEPROCESS+0x5A8

1: kd> dt _EPROCESS
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x438 ProcessLock      : _EX_PUSH_LOCK
   +0x440 UniqueProcessId  : Ptr64 Void
       ...
   +0x518 SectionObject    : Ptr64 Void
      ...
   +0x5a0 ImageFilePointer : Ptr64 _FILE_OBJECT
   +0x5a8 ImageFileName    : [15] UChar
   +0x5b7 PriorityClass    : UChar
   +0x5b8 SecurityPort     : Ptr64 Void
   +0x5c0 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO

通过windbg查看EPROCESS,可见0x5A8的偏移处正是ImageFileName

一、EPROCESS中与路径相关的数据结构

随便找个进程举例,这里使用notepad++.exe进程

1.通过_SE_AUDIT_PROCESS_CREATION_INFO成员

1: kd> !process 0 0 notepad++.exe
PROCESS ffffe7062c059080
    SessionId: 1  Cid: 1458    Peb: 00bed000  ParentCid: 157c
    DirBase: aaff0002  ObjectTable: ffffd602daf294c0  HandleCount: 287.
    Image: notepad++.exe


1: kd> dt _EPROCESS ffffe7062c059080 -y SeAuditProcessCreationInfo
ntdll!_EPROCESS
   +0x5c0 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO

找到EPROCESS的SeAuditProcessCreationInfo成员

1: kd> dt _SE_AUDIT_PROCESS_CREATION_INFO ffffe7062c059080+0x5c0
ntdll!_SE_AUDIT_PROCESS_CREATION_INFO
   +0x000 ImageFileName    : 0xffffe706`2b876d40 _OBJECT_NAME_INFORMATION
1: kd> dt 0xffffe706`2b876d40 _OBJECT_NAME_INFORMATION
ntdll!_OBJECT_NAME_INFORMATION
   +0x000 Name             : _UNICODE_STRING "\Device\HarddiskVolume3\soft\Notepad++\notepad++.exe"

(说明:\Device\HarddiskVolume3 是一个设备对象符号链接,表示硬盘的一个卷。\soft\Notepad++\notepad++.exe 是该卷上的路径,即 \Device\HarddiskVolume3\soft\Notepad++\notepad++.exe 表示 Notepad++ 可执行文件的完整路径,其中 Notepad++ 安装在硬盘的第三个卷的 \soft\ 目录下)
所以通过 Process->SeAuditProcessCreationInfo.ImageFileName->Name 可以获取到进程完整路径。

2.通过SectionObject

1: kd> dt _EPROCESS ffffe7062c059080 -y SectionObject
ntdll!_EPROCESS
   +0x518 SectionObject : 0xffffd602`da1073f0 Void
1: kd> !object 0xffffd602`da1073f0
Object: ffffd602da1073f0  Type: (ffffe70624e96900) Section
    ObjectHeader: ffffd602da1073c0 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: 00000000  Name: \soft\Notepad++\notepad++.exe

以前的系统是EPROCESS的SectionObject成员是 _SECTION_OBJECT结构的指针, 而该结构的0x14处存放着_SEGMENT_OBJECT结构指针,_SEGMENT_OBJECT结构偏移0x0处是_CONTROL_AREA结构的指针,该结构偏移0x24处是_FILE_OBJECT结构的指针,利用该结构的_DEVICE_OBJECT,FileName两个成员和RtlVolumeDeviceToDosName函数可以获取进程文件的DOS完整路径。
现在Win10的我还没搞懂…
总结来说: Process->SectionObject->Segment->ControlArea->FilePointer(_EPROCESS->_SECTION_OBJECT->_SEGMENT_OBJECT->_CONTROL_AREA->_FILE_OBJECT)

3.通过 PEB的 Ldr

1: kd> dt _EPROCESS ffffe7062c059080 -y Peb
ntdll!_EPROCESS
   +0x550 Peb : 0x00000000`00bed000 _PEB
1: kd> dt 0x00000000`00bed000 _PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0 ''
   +0x003 BitField         : 0x4 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 IsPackagedProcess : 0y0
   +0x003 IsAppContainer   : 0y0
   +0x003 IsProtectedProcessLight : 0y0
   +0x003 IsLongPathAwareProcess : 0y0
   +0x004 Padding0         : [4]  ""
   +0x008 Mutant           : 0xffffffff`ffffffff Void
   +0x010 ImageBaseAddress : 0x00000000`00dc0000 Void
   +0x018 Ldr              : 0x00007ffe`1d6fc4c0 _PEB_LDR_DATA
   +0x020 ProcessParameters : 0x00000000`01381d70 _RTL_USER_PROCESS_PARAMETERS
   +0x028 SubSystemData    : (null)
   +0x030 ProcessHeap      : 0x00000000`01380000 Void
    ...

首先找到PEB结构,然后找到Ldr,该成员是_PEB_LDR_DATA结构的指针,然后在_PEB_LDR_DATA的0x10处找到 _LDR_DATA_TABLE_ENTRY数据结构.但是需要注意的是,上面的成员获取必须在对应进程的上下文.

1: kd> dt 0x00007ffe`1d6fc4c0 _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
   +0x000 Length           : 0x58
   +0x004 Initialized      : 0x1 ''
   +0x008 SsHandle         : (null)
   +0x010 InLoadOrderModuleList : _LIST_ENTRY [ 0x00000000`01382790 - 0x00000000`01382e50 ]
   +0x020 InMemoryOrderModuleList : _LIST_ENTRY [ 0x00000000`013827a0 - 0x00000000`01382e60 ]
   +0x030 InInitializationOrderModuleList : _LIST_ENTRY [ 0x00000000`01382620 - 0x00000000`01382e70 ]
   +0x040 EntryInProgress  : (null)
   +0x048 ShutdownInProgress : 0 ''
   +0x050 ShutdownThreadId : (null)
1: kd> dt _LDR_DATA_TABLE_ENTRY 0x00000000`01382790
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x00000000`01382600 - 0x00007ffe`1d6fc4d0 ]
   +0x010 InMemoryOrderLinks : _LIST_ENTRY [ 0x00000000`01382610 - 0x00007ffe`1d6fc4e0 ]
   +0x020 InInitializationOrderLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x030 DllBase          : 0x00000000`00dc0000 Void
   +0x038 EntryPoint       : 0x00000000`00ff0949 Void
   +0x040 SizeOfImage      : 0x4d7000
   +0x048 FullDllName      : _UNICODE_STRING "C:\soft\Notepad++\notepad++.exe"
   +0x058 BaseDllName      : _UNICODE_STRING "notepad++.exe"
   +0x068 FlagGroup        : [4]  "???"
   +0x068 Flags            : 0x22c4
    ...

总结来说就是: PEB->Ldr->InLoadOrderLinks->FullDllName或者 PEB->Ldr->InMemoryOrderLinks->FullDllName

4.通过 PEB的 ProcessParameters

1: kd> dt _EPROCESS ffffe7062c059080 -y Peb
ntdll!_EPROCESS
   +0x550 Peb : 0x00000000`00bed000 _PEB
1: kd> dt 0x00000000`00bed000 _PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0 ''
   +0x003 BitField         : 0x4 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 IsPackagedProcess : 0y0
   +0x003 IsAppContainer   : 0y0
   +0x003 IsProtectedProcessLight : 0y0
   +0x003 IsLongPathAwareProcess : 0y0
   +0x004 Padding0         : [4]  ""
   +0x008 Mutant           : 0xffffffff`ffffffff Void
   +0x010 ImageBaseAddress : 0x00000000`00dc0000 Void
   +0x018 Ldr              : 0x00007ffe`1d6fc4c0 _PEB_LDR_DATA
   +0x020 ProcessParameters : 0x00000000`01381d70 _RTL_USER_PROCESS_PARAMETERS
   +0x028 SubSystemData    : (null)
   +0x030 ProcessHeap      : 0x00000000`01380000 Void
    ...
1: kd> dt 0x00000000`01381d70 _RTL_USER_PROCESS_PARAMETERS
ntdll!_RTL_USER_PROCESS_PARAMETERS
   +0x000 MaximumLength    : 0x784
   +0x004 Length           : 0x784
   +0x008 Flags            : 0x6001
   +0x00c DebugFlags       : 0
   +0x010 ConsoleHandle    : (null)
   +0x018 ConsoleFlags     : 0
   +0x020 StandardInput    : (null)
   +0x028 StandardOutput   : (null)
   +0x030 StandardError    : (null)
   +0x038 CurrentDirectory : _CURDIR
   +0x050 DllPath          : _UNICODE_STRING ""
   +0x060 ImagePathName    : _UNICODE_STRING "C:\soft\Notepad++\notepad++.exe"
   +0x070 CommandLine      : _UNICODE_STRING ""C:\soft\Notepad++\notepad++.exe"  "C:\Users\pc\Desktop\DESKTOP-0I0A8RO.LOG""
   +0x080 Environment      : 0x00000000`01380fe0 Void
    ...

同样是_PEB结构,偏移0x020处是_RTL_USER_PROCESS_PARAMETERS结构.同样需要在目标进程的上下文空间.
总结来说就是: PEB->ProcessParameters->ImagePathName 或PEB->ProcessParameters->CommandLine 或PEB->ProcessParameters->WindowTitle
获取这些数据需要读取进程内存,而且在RING3可以修改掉,所以一般不推荐从PEB里取数据

二、微软提供的获取进程路径相关函数分析

1. GetProcessImageFileName函数:

DWORD GetProcessImageFileName(
  [in] HANDLE hProcess,,//目标进程句柄
  [out] LPSTR lpImageFileName,//提供的空间,用于保存获取到的进程路径
  [in] DWORD nSize//空间大小
);//返回实际路径长度
DLLKernel32.dll on Windows 7 and Windows Server 2008 R2; Psapi.dll (if PSAPI_VERSION=1) on Windows 7 and Windows Server 2008 R2; Psapi.dll on Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP

官方文档:https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessimagefilenamea
使用IDA查看Kernel32.dll或者Psapi.dll
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
无论是Kernel32.dll或者Psapi.dll导出的这个函数是个转发函数,它由api-ms-win-core-psapi-l1-1-0.dll导出,有经验的朋友可能知道很多api-ms*系dll在磁盘上根本找不到,找到它拖进ida,发现它只是个字符串
实际上微软试图将api体系结构和具体的实现分开,但往往一个dll中包含了大量不同体系的实现(如kernelbase),这样微软提出了一种名为(virtual dlls)的方案,通过虚拟dll建立一张映射表来转发到实现dll,这样就能把api体系与实现分开。
具体细节可参考:https://blog.quarkslab.com/runtime-dll-name-resolution-apisetschema-part-i.html
写个demo并下断点:

void GetImagePath()
{
       DWORD dwResult = ERROR_SUCCESS;
       wchar_t imagePath[MAX_PATH] = { 0 };
       __debugbreak();
       dwResult = GetProcessImageFileName(GetCurrentProcess(),  imagePath,MAX_PATH);
       if (dwResult != 0) {
              LOGE("imagePath:%ws", imagePath);
       }
       __debugbreak();
}

发现这个函数的实现在win32kfull.sys中,在win32kfull.sys中查看GetProcessImageFileName伪代码

__int64 __fastcall GetProcessImageFilename(void **a1, wchar_t **a2)
{
  void *v2; // rcx
  __int64 v4; // rbx
  __int64 ProcessImageFileName; // rax
  wchar_t *v6; // rax
  wchar_t *v7; // rax
  HANDLE Handle; // [rsp+60h] [rbp+18h] BYREF


  v2 = *a1;
  Handle = 0i64;
  v4 = 0i64;
  if (  ObOpenObjectByPointer(v2, 0x200u, 0i64, 0x400u, (POBJECT_TYPE)PsProcessType, 0, &Handle) >= 0 )
  {
    ProcessImageFileName = GetProcessImageFileName(Handle);
    v4 = ProcessImageFileName;
    if ( ProcessImageFileName )
    {
      v6 = wcsrchr(*(const wchar_t **)(ProcessImageFileName + 8), 0x5Cu);
      if ( v6 )
        v7 = v6 + 1;
      else
        v7 = *(wchar_t **)(v4 + 8);
      *a2 = v7;
    }
    ZwClose(Handle);
  }
  return v4;
}

ObOpenObjectByPointer用于根据给定的内核对象指针获取该对象的句柄。这个函数通常用于从内核模式代码中获取对象的句柄,获取玩该进程对象的句柄后调用了GetProcessImageFileName(Handle),跟进去看,伪代码如下:

void *__fastcall GetProcessImageFileName(HANDLE ProcessHandle)
{
  void *v1; // rbx
  void *v3; // rax
  ULONG ProcessInformationLength; // [rsp+48h] [rbp+10h] BYREF


  v1 = 0i64;
  ProcessInformationLength = 0;
  if ( ZwQueryInformationProcess(ProcessHandle, ProcessImageFileNameWin32, 0i64, 0, &ProcessInformationLength) == 0xC0000004
    && ProcessInformationLength >= 0x10 )
  {
    v3 = (void *)AllocFreeTmpBuffer(ProcessInformationLength + 2);
    v1 = v3;
    if ( v3 )
    {
      memset(v3, 0, ProcessInformationLength + 2i64);
      if ( ZwQueryInformationProcess(ProcessHandle, ProcessImageFileNameWin32, v1, ProcessInformationLength, 0i64) < 0 )
      {
        FreeTmpBuffer(v1);
        return 0i64;
      }
    }
  }
  return v1;
}

绕了一圈发现是调用了ZwQueryInformationProcess,其中的ProcessInformationClass=0x2B(ProcessImageFileNameWin32)
在内核中,Zw* 函数最终会被映射到 Nt* 函数上,直接查看NtQueryInformationProcess的伪代码:

case 0x2B:
      result = ObReferenceObjectByHandleWithTag(
                 Handle,
                 0x1000u,
                 (POBJECT_TYPE)PsProcessType,
                 v12,
                 0x79517350u,
                 &Object,
                 0i64);
      DeviceMapInformation = result;
      if ( result < 0 )
        return result;
      v18 = (struct _EX_RUNDOWN_REF *)Object;
      v19 = (struct _FILE_OBJECT *)*((_QWORD *)Object + 0xB4);
      v197 = v19;
      if ( v19 )
      {
        if ( ExAcquireRundownProtection_0((PEX_RUNDOWN_REF)Object + 0x8B) )
        {
          ObfReferenceObject(v19);
          ExReleaseRundownProtection_0(v18 + 0x8B);
        }
        else
        {
          DeviceMapInformation = 0xC000010A;
        }
      }
      else
      {
        DeviceMapInformation = PsReferenceProcessFilePointer(Object, &v197);
        v19 = (struct _FILE_OBJECT *)v197;
      }
      ObfDereferenceObjectWithTag(v18, 0x79517350u);
      if ( DeviceMapInformation < 0 )
        return DeviceMapInformation;
      v20 = IoQueryFileDosDeviceName(v19, &ObjectNameInformation);
      HalPutDmaAdapter((PADAPTER_OBJECT)v19);
      if ( v20 >= 0 )
      {
        v21 = ObjectNameInformation;
        v22 = ObjectNameInformation->Name.MaximumLength + 0x10;
        if ( v22 <= (unsigned int)v5 )
        {
          *(_WORD *)v6 = ObjectNameInformation->Name.Length;
          *(_WORD *)(v6 + 2) = v21->Name.MaximumLength;
          if ( v21->Name.MaximumLength )
          {
            v9 = v6 + 0x10;
            memmove((void *)(v6 + 0x10), v21->Name.Buffer, v21->Name.MaximumLength);
          }
          *(_QWORD *)(v6 + 8) = v9;
        }
        else
        {
          v20 = 0xC0000004;
          WorkingSetInformation = 0xC0000004;
        }
        if ( a5 )
          *a5 = v22;
        ExFreePoolWithTag(v21, 0);
      }
      return v20;

分析上面代码:
1.使用ObReferenceObjectByHandleWithTag 通过句柄引用进程对象并将指针存储到 Object,然后获取文件对象, 通过检查 v19 是否非空,判断是否存在文件对象。
2.如果存在,通过 ExAcquireRundownProtection_0 获取 rundown 保护,并对文件对象进行引用计数操作。如果获取 rundown 保护失败,则返回错误码 0xC000010A;
3.如果不存在文件对象,调用 PsReferenceProcessFilePointer 获取进程的文件指针,并返回文件对象指针 v19。
4.调用 IoQueryFileDosDeviceName 查询文件对象的 DOS 设备名称( 这个函数里边主要调用ObQueryNameString函数,该函数主要是遍历对象目录树,组建文件路径)
5.如果查询 DOS 设备名称成功,将相关信息拷贝到输出缓冲区中,并通过 ExFreePoolWithTag 释放分配的内存。

总结:
1.GetProcessImageFileName需要传入一个进程句柄,然后调用NtQueryInformationProcess函数的0x2b号功能;
2.首先获取进程内核对象,然后通过硬编码的方式直接获取文件对象并判断文件对象是否存在,不存在则通过PsReferenceProcessFilePointer重新获取文件对象;
3.获取到文件对象后调用 IoQueryFileDosDeviceName 查询文件对象的 DOS 设备名称,并将结果存储在 ObjectNameInformation 中。

换句话说, 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。

2.QueryFullProcessImageName函数

BOOL QueryFullProcessImageNameA( 
    [in] HANDLE hProcess, 
    [in] DWORD dwFlags, 
    [out] LPSTR lpExeName, 
    [in, out] PDWORD lpdwSize 
);

注意 [in] dwFlags

ValueMeaning
0The name should use the Win32 path format.
PROCESS_NAME_NATIVE 0x00000001The name should use the native system path format.

官方文档:https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-queryfullprocessimagenamea
使用IDA查看伪代码:

BOOL __stdcall QueryFullProcessImageNameW(HANDLE hProcess, DWORD dwFlags, LPWSTR lpExeName, PDWORD lpdwSize)
{
  BOOL v4; // ebx
  DWORD v5; // esi
  bool v7; // cl
  ULONG v10; // r12d
  PVOID ProcessHeap; // rbp
  const void **Heap; // rax
  const void **v13; // rdi
  NTSTATUS InformationProcess; // eax
  __int64 v15; // rcx
  __int64 v17; // rcx
  __int64 v18; // rcx


  v4 = 0;
  v5 = dwFlags & 1;
  v7 = 0;
  if ( (dwFlags & 0xFFFFFFFE) == 0 )
    v7 = !(dwFlags & 1 & (unsigned __int8)((dwFlags & 1) - 1));
  if ( !v7 )
  {
    v18 = 0xC00000F0i64;
    goto LABEL_19;
  }
  if ( *lpdwSize > 0x7FFFFFF7 )
  {
    v18 = 0xC000000Di64;
LABEL_19:
    BaseSetLastNTError(v18);
    return v4;
  }
  v10 = 2 * *lpdwSize + 0x10;
  ProcessHeap = NtCurrentPeb()->ProcessHeap;
  Heap = (const void **)RtlAllocateHeap(ProcessHeap, KernelBaseGlobalData, v10);
  v13 = Heap;
  if ( !Heap )
  {
    v18 = 0xC0000017i64;
    goto LABEL_19;
  }
  InformationProcess = NtQueryInformationProcess(hProcess, (PROCESSINFOCLASS)(0x10 * (v5 ^ 1) + 0x1B), Heap, v10, 0i64);
  if ( InformationProcess == 0xC0000004 )
    InformationProcess = 0xC0000023;
  if ( InformationProcess < 0 )
  {
    v17 = (unsigned int)InformationProcess;
LABEL_14:
    BaseSetLastNTError(v17);
    goto LABEL_11;
  }
  memcpy_0(lpExeName, v13[1], *(unsigned __int16 *)v13);
  v15 = *(unsigned __int16 *)v13 >> 1;
  if ( (unsigned int)v15 >= *lpdwSize )
  {
    v17 = 0xC0000023i64;
    goto LABEL_14;
  }
  lpExeName[v15] = 0;
  v4 = 1;
  *lpdwSize = v15;
LABEL_11:
  RtlFreeHeap(ProcessHeap, 0, v13);
  return v4;
}

还是调用的 NtQueryInformationProcess,根据不同的dwFlags调用 NtQueryInformationProcess的不同的功能
dwFlags有0和1两个,根据0x10 * (dwFlags^ 1) + 0x1B分别为0x2B和0X1B;
分别对应了ring0和ring3不同的形式,那么根据逻辑传入的查询参数分别为27或43,我们写一个简单程序验证一下。

#include <windows.h>
#include <stdio.h>
#include <winternl.h>
#pragma comment(lib,"ntdll.lib")
int main()
{
UNICODE_STRING usRing0 = {0};
UNICODE_STRING usRing3 = { 0 };
NtQueryInformationProcess(GetCurrentProcess(),(PROCESSINFOCLASS)27,&usRing0, 0x1000, NULL);
NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)43, &usRing3, 0x1000, NULL);
getchar();
}

在这里插入图片描述
0x2B对应的PROCESSINFOCLASS为ProcessImageFileNameWin32和 GetProcessImageFileName函数底层实现一样;
0x1B对应的PROCESSINFOCLASS为ProcessImageFileName,就是查看PsQueryFullProcessImageName的伪代码发现是直接通过硬编码的方式读取EPROCESS的SeAuditProcessCreationInfo.ImageFileName->Name

case 0x1B:
      result = ObReferenceObjectByHandleWithTag(
                 Handle,
                 0x1000u,
                 (POBJECT_TYPE)PsProcessType,
                 v12,
                 0x79517350u,
                 &Object,
                 0i64);
      if ( result < 0 )
        return result;
      if ( (unsigned int)v5 >= 0x10 )
      {
        v16 = v5 - 0x10;
      }
      else
      {
        v6 = (unsigned __int64)&v250;
        v16 = 0;
      }
      if ( (unsigned int)v5 >= 0x10 )
        v9 = v230 + 0x10;
      v187 = v16;
      DeviceMapInformation = PsQueryFullProcessImageName(Object, v6, v9, &v187);
      ObfDereferenceObjectWithTag(Object, 0x79517350u);
      if ( DeviceMapInformation >= 0 && !v9 )
        DeviceMapInformation = 0xC0000004;
      if ( a5 && ((int)(DeviceMapInformation + 0x80000000) < 0 || DeviceMapInformation == 0xC0000004) )
        *a5 = v187 + 0x10;
      return DeviceMapInformation;
__int64 __fastcall PsQueryFullProcessImageName(__int64 a1, _OWORD *a2, void *a3, unsigned int *a4)
{
  const void **v6; // rdx
  unsigned int v7; // ecx
  unsigned __int64 v8; // rcx
  unsigned int v9; // ebx
  void *v10; // rax
  __int128 v12; // [rsp+28h] [rbp-20h]


  v6 = *(const void ***)(a1 + 0x5C0);
  v7 = *a4;
  *a4 = *((unsigned __int16 *)v6 + 1);
  if ( *((unsigned __int16 *)v6 + 1) > v7 )
  {
    return 0xC0000004;
  }
  else
  {
    v12 = *(_OWORD *)v6;
    v8 = (unsigned __int64)*v6 >> 0x10;
    v9 = 0;
    v10 = 0i64;
    if ( WORD1(*(_OWORD *)v6) )
      v10 = a3;
    *((_QWORD *)&v12 + 1) = v10;
    *a2 = v12;
    if ( (_WORD)v8 )
      memmove(a3, v6[1], *((unsigned __int16 *)v6 + 1));
  }
  return v9;
}

3.SeLocateProcessImageName函数

除此之外,还有一个可获取的路径方法: Process->SeAuditProcessCreationInfo.ImageFileName->Name

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;
}

( 用 SeLocateProcessImageName(eproc,&UsImageName)的时候,取到别的程序全路径是正常的,而system的值是Null)

参考:
1.NtQueryInformationProcess逆向:https://idiotc4t.com/redteam-research/untitled
2. 彻底改掉进程名 :https://bbs.kanxue.com/thread-96427.htm
3.驱动中获取进程完整路径名:https://boxcounter.com/technique/2009-07-23-%E9%A9%B1%E5%8A%A8%E4%B8%AD%E8%8E%B7%E5%8F%96%E8%BF%9B%E7%A8%8B%E5%AE%8C%E6%95%B4%E8%B7%AF%E5%BE%84%E5%90%8D/
4.在内核驱动中,获得到当前进程的全路径:https://codeantenna.com/a/hpQLrSNn87

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值