最近在准备一个进程查看(当然不只进程查看功能了)的工具,总结了在用户态下查找进程的几种方法。
当然,如果想要真正做到进程查看,还是要进入核心态中,因为在用户态是查不到什么东西的,但是可以用来和
核心态结果进行比较找出隐藏进程。(内核级病毒、木马在Ring0中可以很容易的做到隐藏而是用户态程序检测不到)。
//write by jingzhongrong
1、利用ToolHelp API
首先创建一个系统快照,然后通过对系统快照的访问完成进程的枚举
获取系统快照使用 CreateToolhelp32Snapshot 函数
函数原型声明如下:
HANDLE WINAPI CreateToolhelp32Snapshot( DWORD dwFlags, DWORD th32ProcessID);
将dwFlags设置为TH32CS_SNAPPROCESS用于获取进程快照。
函数调用成功后会返回一个快照的句柄,便可以使用Process32First、Process32Next进行枚举了
函数原型声明如下:
BOOL WINAPI Process32First( HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
BOOL WINAPI Process32Next( HANDLE hSnapshot, LPPROCESSENTRY32 lppe );下面是相关代码:
#include <windows.h> #include <tlhelp32.h> #include <stdio.h> void useToolHelp(){ HANDLE procSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0 ); if(procSnap == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot failed, %d " ,GetLastError()); return ; } // PROCESSENTRY32 procEntry = { 0 }; procEntry.dwSize = sizeof (PROCESSENTRY32); BOOL bRet = Process32First(procSnap,& procEntry); while (bRet) { wprintf(L"PID: %d (%s) " ,procEntry.th32ProcessID, procEntry.szExeFile); bRet = Process32Next(procSnap,& procEntry); } CloseHandle(procSnap);}void main(){ useToolHelp(); getchar();}
用此方法可以在进程ID和进程名称间进行转换,即通过进程名称获得进程ID,通过进程ID获取进程名称。
2、通过psapi.dll提供的函数
通过psapi.dll提供的EnumProcesses、EnumProcessModules实现
函数原型声明如下:
BOOL EnumProcesses( DWORD* pProcessIds, DWORD cb, DWORD* pBytesReturned ); BOOL EnumProcessModules( HANDLE hProcess, HMODULE* lphModule, DWORD cb, LPDWORD lpcbNeeded ); 相关代码如下:(参考MSDN) #include < windows.h > #include < stdio.h > #include < tchar.h > #include " psapi.h " #pragma comment(lib,"psapi.lib") void PrintProcessNameAndID(DWORD processID){ TCHAR szProcessName[MAX_PATH] = _T( " <unknown> " ); HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS /* | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ */ ,FALSE,processID); // Process name. if (NULL != hProcess) { HMODULE hMod; DWORD cbNeeded; if (EnumProcessModules(hProcess, & hMod, sizeof (hMod), & cbNeeded)) { GetModuleBaseName(hProcess,hMod,szProcessName, sizeof (szProcessName) / sizeof (TCHAR)); } } wprintf(_T( " PID: %d (%s) " ),processID,szProcessName); CloseHandle(hProcess);} void main( ){ DWORD aProcesses[ 1024 ], cbNeeded, cProcesses; unsigned int i; if ( ! EnumProcesses(aProcesses, sizeof (aProcesses), & cbNeeded)) return ; cProcesses = cbNeeded / sizeof (DWORD); for (i = 0 ;i < cProcesses;i ++ ) PrintProcessNameAndID(aProcesses[i]); getchar();}此方法由于需要进行OpenProcess操作,所以需要一定的权限,当权限不够时,有些进程将不能被打开。
下面给出提升权限的相关代码:
void RaisePrivilege(){ HANDLE hToken; TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1 ; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if(OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,& hToken)) { if(LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0 ].Luid)) { AdjustTokenPrivileges(hToken,FALSE,&tp,NULL,NULL,0 ); } } if (hToken) CloseHandle(hToken);}3、通过ntdll.dll提供的Native API
使用Native API 中的ZwQuerySystemInformation的SystemProcessesAndThreadsInformation系统调用枚举进程
由于该函数没有被导出,所以首先定义要使用到的结构和常量
typedef DWORD (WINAPI * ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);typedef struct _SYSTEM_PROCESS_INFORMATION{ DWORD NextEntryDelta; DWORD ThreadCount; DWORD Reserved1[6 ]; FILETIME ftCreateTime; FILETIME ftUserTime; FILETIME ftKernelTime; UNICODE_STRING ProcessName; DWORD BasePriority; DWORD ProcessId; DWORD InheritedFromProcessId; DWORD HandleCount; DWORD Reserved2[2 ]; DWORD VmCounters; DWORD dCommitCharge; PVOID ThreadInfos[1 ];}SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;#define SystemProcessesAndThreadsInformation 5
然后动态加载ntdll.dll,获得函数的地址。
便可以进行进程的枚举
相关代码如下:
#include <windows.h> #include <ntsecapi.h> #include <stdio.h> typedef DWORD (WINAPI * ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);typedef struct _SYSTEM_PROCESS_INFORMATION{ DWORD NextEntryDelta; DWORD ThreadCount; DWORD Reserved1[6 ]; FILETIME ftCreateTime; FILETIME ftUserTime; FILETIME ftKernelTime; UNICODE_STRING ProcessName; DWORD BasePriority; DWORD ProcessId; DWORD InheritedFromProcessId; DWORD HandleCount; DWORD Reserved2[2 ]; DWORD VmCounters; DWORD dCommitCharge; PVOID ThreadInfos[1 ];}SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION;#define SystemProcessesAndThreadsInformation 5 void main(){ HMODULE hNtDll = GetModuleHandle(L"ntdll.dll" ); if(! hNtDll) return ; ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation" ); ULONG cbBuffer = 0x10000 ; LPVOID pBuffer = NULL; pBuffer = malloc(cbBuffer); if(pBuffer == NULL) return ; ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,pBuffer,cbBuffer,NULL); PSYSTEM_PROCESS_INFORMATION pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer; for (;;) { wprintf(L"PID: %d (%ls) ",pInfo->ProcessId,pInfo-> ProcessName.Buffer); if(pInfo->NextEntryDelta == 0 ) break ; pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo-> NextEntryDelta); } free(pBuffer); getchar();}
代码在VS2005 release + XP SP2调试通过
4、通过进程打开的句柄来枚举进程
同样使用ZwQuerySystemInformation函数,使用SystemHandleInformation系统调用
相关代码如下:
#include <windows.h> #include <ntsecapi.h> #include <ntstatus.h> #include <stdio.h> typedef NTSTATUS (WINAPI * ZWQUERYSYSTEMINFORMATION)(DWORD, PVOID, DWORD, PDWORD);typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG ProcessId; UCHAR ObjectTypeNumber; UCHAR Flags; USHORT Handle; PVOID Object; ACCESS_MASK GrantedAccess;}SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION;typedef struct _SYSTEM_HANDLE_INFORMATION_EX { ULONG NumberOfHandles; SYSTEM_HANDLE_INFORMATION Information[1 ];}SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX;#define SystemHandleInformation 0x10 //16 void main(){ HMODULE hNtDll = LoadLibrary(L"ntdll.dll" ); if(! hNtDll) return ; ZWQUERYSYSTEMINFORMATION ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll,"ZwQuerySystemInformation" ); ULONG cbBuffer = 0x4000 ; LPVOID pBuffer = NULL; NTSTATUS s; do { pBuffer = malloc(cbBuffer); if(pBuffer == NULL) return ; memset(pBuffer,0 ,cbBuffer); s = ZwQuerySystemInformation(SystemHandleInformation,pBuffer,cbBuffer,NULL); if(s == STATUS_INFO_LENGTH_MISMATCH) { free(pBuffer); cbBuffer = cbBuffer * 2 ; } }while(s == STATUS_INFO_LENGTH_MISMATCH); PSYSTEM_HANDLE_INFORMATION_EX pInfo = (PSYSTEM_HANDLE_INFORMATION_EX)pBuffer; ULONG OldPID = 0 ; for(DWORD i = 0;i<pInfo->NumberOfHandles;i++ ) { if(OldPID != pInfo-> Information[i].ProcessId) { OldPID = pInfo-> Information[i].ProcessId; wprintf(L"PID: %d " ,OldPID); } } free(pBuffer); FreeLibrary(hNtDll); getchar();}
另外,在进行进程“隐藏”工作的时候,此处的句柄是一件容易被忽略的地方,因此需要注意隐藏由程序打开的相关句柄,由于系统中句柄数量经常变换,所以没有什么必要修改其中的NumberOfHandles域,因为如果修改此处的值,则需要不停对句柄的变化进行维护,开销比较大。
在用户态下的进程枚举已经变得不可靠,因为一个内核级的Rootkit很容易就能够更改这些函数的返回结果,因此进程的可靠枚举应在内核态中实现,可以通过编写驱动来实现