进程枚举相关方法

admin 2023年12月23日17:28:11评论18 views字数 8383阅读27分56秒阅读模式

进程枚举-EnumProcesses

枚举进程

需要用到EnumProcesses函数:

BOOL EnumProcesses(  [out] DWORD   *lpidProcess,  [in]  DWORD   cb,  [out] LPDWORD lpcbNeeded);

第一个参数表示接收标识符列表数组的指针。

第二个参数表示pProcessIds 数组的大小。

第三个参数表示pProcessIds 数组中返回的字节。

EnumProcesses函数会返回进程的PID,它是以数组的方式进行返回的,但是也就仅仅只有PID,并没有进程的名称,所以我们需要获取到进程的名称。

这里我们可以使用OpenProcess GetModuleBaseName和EnumProcessModules这些Windows API函数来进行获取。

OpenProcess

OpenProcess函数用于打开具有PROCESS_QUERY_INFORMATION和PROCESS_VM_READ访问权限的PID句柄。

如下:

HANDLE OpenProcess(  [in] DWORD dwDesiredAccess,  [in] BOOL  bInheritHandle,  [in] DWORD dwProcessId);

dwDesiredAccess表示对进程对象的访问。根据进程的安全描述符检查此访问权限。

bInheritHandle参数如果为True,那么创建的进程将继承句柄,否则不会继承此句柄。

dwProcessId参数表示要打开进程的标识符。

GetModuleBaseName

DWORD GetModuleBaseNameA(  [in]           HANDLE  hProcess,  [in, optional] HMODULE hModule,  [out]          LPSTR   lpBaseName,  [in]           DWORD   nSize);
hProcess :进程的句柄,可以通过OpenProcess函数来获取。hModule : 模块的句柄。如果此参数为 NULL,则此函数返回用于创建调用进程的文件的名称。lpBaseName : 模块的名称nSize : lpBaseName 缓冲区的大小

EnumProcessModules

BOOL EnumProcessModules(  [in]  HANDLE  hProcess,  [out] HMODULE *lphModule,  [in]  DWORD   cb,  [out] LPDWORD lpcbNeeded);
hProcess : 进程的句柄lphModule : 接收句柄列表的数组cb : lphModule数组的大小lpcbNeeded :  lphModule 数组中存储所有模块句柄所需的字节数

OpenProcess用于打开具有PROCESS_QUERY_INFORMATION和PROCESS_VM_READ访问权限的PID句柄。

EnumProcessModules用于枚举打开的句柄中的所有模块。

拿到所有进程模块之后通过GetModuleBaseName函数确定进程的名称。

这里我们来看一下微软官方的进程枚举的代码:

#include <windows.h>#include <stdio.h>#include <tchar.h>#include <psapi.h>
// To ensure correct resolution of symbols, add Psapi.lib to TARGETLIBS// and compile with -DPSAPI_VERSION=1
void PrintProcessNameAndID( DWORD processID ){ TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
// Get a handle to the process.
HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID );
// Get the process name.
if (NULL != hProcess ) { HMODULE hMod; DWORD cbNeeded;
if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) ) { GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) ); } }
// Print the process name and identifier.
_tprintf( TEXT("%s (PID: %u)n"), szProcessName, processID );
// Release the handle to the process.
CloseHandle( hProcess );}
int main( void ){ // Get the list of process identifiers.
DWORD aProcesses[1024], cbNeeded, cProcesses; unsigned int i;
if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) ) { return 1; }

// Calculate how many process identifiers were returned.
cProcesses = cbNeeded / sizeof(DWORD);
// Print the name and process identifier for each process.
for ( i = 0; i < cProcesses; i++ ) { if( aProcesses[i] != 0 ) { PrintProcessNameAndID( aProcesses[i] ); } }
return 0;}

这里的PrintProcessNameAndID函数是用于打印枚举进程的进程名和PID,只有以实现相同的权限运行的进程才能检索其他信息,比如说这个代码生成的程序时以普通权限运行的,那么就无法检索到system权限运行的程序,如果尝试通过OpenProcess打开高权限的进程会直接报错。

我们来跟一下代码:

首先获取到进程标识符数,这里的进程标识符就是PID。

进程枚举相关方法

然后进入for循环,调用PrintProcessNameAndID函数,传入的参数就是前面定义的进程标识符的数组列表。

获取到第一个PID是4,尝试通过OpenProcess函数打开,前面说过如果我们这个程序是以普通用户权限运行的,那么它将无法打开高权限的进程。

进程枚举相关方法

我们可以看到System这个进程是以system权限运行的。

这里我们肯定是无法访问的,所以返回肯定是NULL的。

进程枚举相关方法

然后执行下面的操作,只能打印出进程的PID,并没有获取到进程的名称。

进程枚举相关方法

接下来我们获取到的进程PID是3876成功进入到了IF里面。

进程枚举相关方法

这里使用EnumProcessModules函数获取到进程中的模块句柄,拿到模块句柄之后就可以通过GetModuleBaseName来获取进程的名称了。

进程枚举相关方法

如上就是最基本上流程了。

其实就是通过OpenProcess函数来打开进程如果可以打开的话,那么证明是有权限的,通过GetModuleBaseName来获取进程的名称,如果打不打开那么就证明是高权限的进程。

如下打印:

进程枚举相关方法

进程枚举-NtQuerySystemInfomation

NtQuerySystemInfomation函数是系统直接调用的函数,就是R0层的函数,NtQuerySystemInfomation函数是从Ntdll.dll中导出的,因此我们需要使用GetModuleHanle和GetProcAddress,也可以使用SysWhispers3-master工具直接使用syscall。

NtQuerySystemInfomation如下介绍: 根据微软官方来说它可以检索系统的大部分信息。

__kernel_entry NTSTATUS NtQuerySystemInformation(  [in]            SYSTEM_INFORMATION_CLASS SystemInformationClass,  [in, out]       PVOID                    SystemInformation,  [in]            ULONG                    SystemInformationLength,  [out, optional] PULONG                   ReturnLength);

参数说明

SystemInformationClass : 要检索的系统信息类型 SYSTEM_INFORMATION_CLASS中枚举的值之一SystemInformation : 指向接收请求信息的缓冲区的指针。SystemInformationLength 参数指向的缓冲区的大小ReturnLength 指向函数写入所请求信息的实际大小的位置的可选指针。

我们前面说过需要使用GetModuleHanle和GetProcAddress来获取这个函数。

如下代码:

#include <iostream>#include <Windows.h>#include <winternl.h>
typedef NTSTATUS(NTAPI* pNtQuerySystemInfomation)( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SsystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
int main(){ pNtQuerySystemInfomation fnNtQuerySystemInfomation = NULL; fnNtQuerySystemInfomation = (pNtQuerySystemInfomation)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),"NtQuerySystemInformation"); getchar();}

进程枚举相关方法

参数前面已经介绍过了,由于我们是进程枚举所以我们第一个参数给它的值为SystemProcessInformation,它表示包含有关 每个进程的资源使用情况,包括 进程、峰值页面文件使用量以及 进程已分配。

使用SystemProcessInformation标志函数返回一个SYSTEM_PROCESS_INFORMATION 结构数组,每个数组都对应一个进程。

我们来看一下SYSTEM_PROCESS_INFORMATION 这个结构。

typedef struct _SYSTEM_PROCESS_INFORMATION {    ULONG NextEntryOffset;    ULONG NumberOfThreads;    BYTE Reserved1[48];    UNICODE_STRING ImageName;    KPRIORITY BasePriority;    HANDLE UniqueProcessId;    PVOID Reserved2;    ULONG HandleCount;    ULONG SessionId;    PVOID Reserved3;    SIZE_T PeakVirtualSize;    SIZE_T VirtualSize;    ULONG Reserved4;    SIZE_T PeakWorkingSetSize;    SIZE_T WorkingSetSize;    PVOID Reserved5;    SIZE_T QuotaPagedPoolUsage;    PVOID Reserved6;    SIZE_T QuotaNonPagedPoolUsage;    SIZE_T PagefileUsage;    SIZE_T PeakPagefileUsage;    SIZE_T PrivatePageCount;    LARGE_INTEGER Reserved7[6];} SYSTEM_PROCESS_INFORMATION;

因为我们需要枚举进程以及PID,所以这里我们只需要看到结构中的 UNICODE_STRING ImageName和进程ID UniqueProcessId上即可。此外NextEntryOffset用于将返回数组中的下一个元素。

这里我们需要调用两次 NtQuerySystemInformation函数,第一次获取数组的大小,用于分配内存,第二次调用使用分配的内存。

首先获取数组的大小来分配缓冲区:

#include <iostream>#include <Windows.h>#include <winternl.h>
typedef NTSTATUS(NTAPI* pNtQuerySystemInfomation)( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SsystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
int main(){ ULONG ulen1 = NULL, ulen2 = NULL; PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL; NTSTATUS sts = NULL; pNtQuerySystemInfomation fnNtQuerySystemInfomation = NULL; fnNtQuerySystemInfomation = (pNtQuerySystemInfomation)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),"NtQuerySystemInformation"); fnNtQuerySystemInfomation(SystemProcessInformation, NULL, NULL, &ulen1); SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(SIZE_T)ulen1); if (SystemProcInfo == NULL) { printf("分配失败"); } getchar();}

进程枚举相关方法

分配完内存之后再次调用使用分配的缓冲区。

#include <iostream>#include <Windows.h>#include <winternl.h>
typedef NTSTATUS(NTAPI* pNtQuerySystemInfomation)( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SsystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
int main(){ ULONG ulen1 = NULL, ulen2 = NULL; PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL; NTSTATUS sts = NULL; pNtQuerySystemInfomation fnNtQuerySystemInfomation = NULL; fnNtQuerySystemInfomation = (pNtQuerySystemInfomation)GetProcAddress(GetModuleHandle(L"NTDLL.dll"),"NtQuerySystemInformation"); fnNtQuerySystemInfomation(SystemProcessInformation, NULL, NULL, &ulen1); SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(SIZE_T)ulen1); if (SystemProcInfo == NULL) { printf("分配失败"); return -1; } sts = fnNtQuerySystemInfomation(SystemProcessInformation,SystemProcInfo,ulen1,&ulen2); if (sts != 0x0) { return -1; } getchar();}

现在已经成功检索数组,下一步就是循环遍历数组并访问保存进程名称的ImageName.Buffer 每次迭代都要将进程名称与目标进程名称进行对比。

要访问数组中SYSTEM_PROCESS_INFORMATION 类型的每个元素必须使用NextEntryOffset成员,以便查找下一个元素的地址。

最后释放分配的内存即可。

完整代码如下:

#include <iostream>#include <Windows.h>#include <winternl.h>
typedef NTSTATUS(NTAPI* pNtQuerySystemInfomation)( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SsystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
BOOL GetThreadTest(LPCWSTR szProcName,DWORD* pdwPid,HANDLE* pProcess) { pNtQuerySystemInfomation fnNtQuerySystemInformation = NULL; ULONG ulen1 = NULL, ulen2 = NULL; PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL; NTSTATUS sts = NULL; PVOID pValueToFree = NULL; fnNtQuerySystemInformation = (pNtQuerySystemInfomation)GetProcAddress(GetModuleHandle(L"NTDLL.dll"), "NtQuerySystemInformation"); fnNtQuerySystemInformation(SystemProcessInformation, NULL, NULL, &ulen1); SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)ulen1); if (SystemProcInfo == NULL) { printf("分配失败"); return FALSE; } pValueToFree = SystemProcInfo; sts = fnNtQuerySystemInformation(SystemProcessInformation, SystemProcInfo, ulen1, &ulen2); if (sts !=0X0) { return FALSE; } while (TRUE) { if (SystemProcInfo->ImageName.Length && wcscmp(SystemProcInfo->ImageName.Buffer,szProcName) == 0) { *pdwPid = (DWORD)SystemProcInfo->UniqueProcessId; *pProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,(DWORD)SystemProcInfo->UniqueProcessId); break; } if (!SystemProcInfo->NextEntryOffset) { break; } SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset); } HeapFree(GetProcessHeap(),0,pValueToFree); if (*pdwPid == NULL || *pProcess == NULL) { return FALSE; } else { return TRUE; }
}
int main(){ LPCWSTR sc = L"notepad.exe"; DWORD pid = NULL; HANDLE process = NULL; GetThreadTest(sc,&pid,&process); printf("notepad.exe的pid是:%d", pid);
}

进程枚举相关方法

原文始发于微信公众号(Relay学安全):进程枚举相关方法

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月23日17:28:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   进程枚举相关方法https://cn-sec.com/archives/2331360.html

发表评论

匿名网友 填写信息