进程枚举-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函数确定进程的名称。
这里我们来看一下微软官方的进程枚举的代码:
// 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来获取这个函数。
如下代码:
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函数,第一次获取数组的大小,用于分配内存,第二次调用使用分配的内存。
首先获取数组的大小来分配缓冲区:
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();
}
分配完内存之后再次调用使用分配的缓冲区。
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成员,以便查找下一个元素的地址。
最后释放分配的内存即可。
完整代码如下:
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学安全):进程枚举相关方法
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论