admin 2025-04-08
165
在之前几篇文章已经学习了解了几种钩取的方法
●浅谈调试模式钩取
●浅谈热补丁
●浅谈内联钩取原理与实现
●导入地址表钩取技术
这篇文章就利用钩取方式完成进程隐藏的效果。
进程遍历方法在实现进程隐藏时,首先需要明确遍历进程的方法。
CreateToolhelp32SnapshotCreateToolhelp32Snapshot函数用于创建进程的镜像,当第二个参数为0时则是创建所有进程的镜像,那么就可以达到遍历所有进程的效果。
(){setlocale(LC_ALL,"zh_");DWORDprocesses[1024],dwResult,size;unsignedinti;//收集所有进程的进程号if(!EnumProcesses(processes,sizeof(processes),dwResult)){std::cout"EnumError"std::l;}//进程数量size=dwResult/sizeof(DWORD);for(i=0;isize;i++){//判断进程号是否为0if(processes[i]!=0){//用于存储进程路径TCHARszProcessName[MAX_PATH]={0};//使用查询权限打开进程HANDLEhProcess=OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE,processes[i]);if(hProcess!=NULL){HMODULEhMod;DWORDdwNeeded;//收集该进程的所有模块句柄,第一个句柄则为文件路径if(EnumProcessModules(hProcess,hMod,sizeof(hMod),dwNeeded)){//根据句柄获取文件路径GetModuleBaseName(hProcess,hMod,szProcessName,sizeof(szProcessName)/sizeof(TCHAR));}wprintf(L"进程路径:%s\t进程号:%d\n",szProcessName,processes[i]);}}}}
ZwQuerySystemInfomation
ZwQuerySystemInfomation函数是CreateToolhelp32Snapshot函数与EnumProcesses函数底层调用的函数,也用于遍历进程信息。代码参考
#pragmacomment(lib,"")//定义函数指针typedefNTSTATUS(WINAPI*NTQUERYSYSTEMINFORMATION)(INSYSTEM_INFORMATION_CLASSSystemInformationClass,INOUTPVOIDSystemInformation,INULONGSystemInformationLength,OUTPULONGReturnLength);intmain(){//设置编码setlocale(LC_ALL,"zh_");//获取模块地址HINSTANCEntdll_dll=GetModuleHandle(L"");if(ntdll_dll==NULL){std::cout"GetModuleError"std::l;exit(-1);}NTQUERYSYSTEMINFORMATIONZwQuerySystemInformation=NULL;//获取函数地址ZwQuerySystemInformation=(NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll_dll,"ZwQuerySystemInformation");if(ZwQuerySystemInformation!=NULL){SYSTEM_BASIC_INFORMATIONsbi={0};//查询系统基本信息NTSTATUSstatus=ZwQuerySystemInformation(SystemBasicInformation,(PVOID)sbi,sizeof(sbi),NULL);if(status==STATUS_SUCCESS){wprintf(L"处理器个数:%d\r\n",);}else{wprintf(L"ZwQuerySystemInfomationError\n");}DWORDdwNeedSize=0;BYTE*pBuffer=NULL;wprintf(L"\t----所有进程信息----\t\n");PSYSTEM_PROCESS_INFORMATIONpsp=NULL;//查询进程数量status=ZwQuerySystemInformation(SystemProcessInformation,NULL,0,dwNeedSize);if(status==STATUS_INFO_LENGTH_MISMATCH){pBuffer=newBYTE[dwNeedSize];//查询进程信息status=ZwQuerySystemInformation(SystemProcessInformation,(PVOID)pBuffer,dwNeedSize,NULL);if(status==STATUS_SUCCESS){psp=(PSYSTEM_PROCESS_INFORMATION)pBuffer;wprintf(L"\tPID\t线程数\t工作集大小\t进程名\n");do{//获取进程号wprintf(L"\t%d",psp-UniqueProcessId);//获取线程数量wprintf(L"\t%d",psp-NumberOfThreads);//获取工作集大小wprintf(L"\t%d",psp-WorkingSetSize/1024);//获取路径wprintf(L"\t%s\n",);//移动psp=(PSYSTEM_PROCESS_INFORMATION)((PBYTE)psp+psp-NextEntryOffset);}while(psp-NextEntryOffset!=0);delete[]pBuffer;pBuffer=NULL;}elseif(status==STATUS_UNSUCCESSFUL){wprintf(L"\nSTATUS_UNSUCCESSFUL");}elseif(status==STATUS_NOT_IMPLEMENTED){wprintf(L"\nSTATUS_NOT_IMPLEMENTED");}elseif(status==STATUS_INVALID_INFO_CLASS){wprintf(L"\nSTATUS_INVALID_INFO_CLASS");}elseif(status==STATUS_INFO_LENGTH_MISMATCH){wprintf(L"\nSTATUS_INFO_LENGTH_MISMATCH");}}}}
进程隐藏
通过上述分析可以知道遍历进程的方式有三种,分别是利用CreateToolhelp32Snapshot、EnumProcesses以及ZwQuerySystemInfomation函数
但是CreateToolhelp32Snapshot与EnumProcesses函数底层都是调用了ZwQuerySystemInfomation函数,因此我们只需要钩取该函数即可。
由于测试环境是Win11,因此需要判断在Win11情况下底层是否还是调用了ZwQuerySystemInfomation函数。
可以看到在Win11下还是会调用ZwQuerySystemInfomation函数,在用户态下该函数的名称为NtQuerySystemInformation函数。
这里采用内联钩取的方式对ZwQuerySystemInfomation进行钩取处理,具体怎么钩取在浅谈内联钩取原理与实现已经介绍过了,这里就不详细说明了。这里对自定义的ZwQuerySystemInfomation函数进行说明。
首先第一步需要进行脱钩处理,因为后续需要用到初始的ZwQuerySystemInfomation函数,紧接着获取待钩取函数的地址即可。
//脱钩UnHook("","ZwQuerySystemInformation",g_pOrgBytes);HMODULEhModule=GetModuleHandleA("");//获取待钩取函数的地址PROCpfnOld=GetProcAddress(hModule,"ZwQuerySystemInformation");//调用原始的ZwQuerySystemInfomation函数NTSTATUSstatus=((NTQUERYSYSTEMINFORMATION)pfnOld)(SystemInformationClass,SystemInformation,SystemInformationLength,ReturnLength);
为了隐藏指定进程,我们需要遍历进程信息,找到目标进程并且删除该进程信息实现隐藏的效果。这里需要知道的是进程信息都存储在SYSTEM_PROCESS_INFORMATION结构体中,该结构体是通过单链表对进程信息进行链接。因此我们通过匹配进程名称找到对应的SYSTEM_PROCESS_INFORMATION结构体,然后进行删除即可,效果如下图。
通过单链表中删除节点的操作,取出目标进程的结构体。代码如下
pCur=(PSYSTEM_PROCESS_INFORMATION)(SystemInformation);while(true){if(!lstrcmpi(,L"")){//需要隐藏的进程是最后一个节点if(pCur-NextEntryOffset==0)pPrev-NextEntryOffset=0;//不是最后一个节点,则将该节点取出elsepPrev-NextEntryOffset+=pCur-NextEntryOffset;}//不是需要隐藏的节点,则继续遍历elsepPrev=pCur;//链表遍历完毕if(pCur-NextEntryOffset==0)break;pCur=(PSYSTEM_PROCESS_INFORMATION)((PBYTE)pCur+pCur-NextEntryOffset);}
完整代码:
但是采用内联钩取的方法去钩取任务管理器就会出现一个问题,这里将断点取消,利用内联钩取的方式去隐藏进程。
首先利用bl命令查看断点
紧着利用bc[ID]删除断点
在注入之后任务管理器会在拷贝的时候发生异常
在经过一番调试后发现,由于多线程共同执行导致原本需要可写权限的段被修改为只读权限
在windbg可以用使用!vprot+address查看指定地址的权限,可以看到由于程序往只读权限的地址进行拷贝处理,所以导致了异常。
但是在执行拷贝阶段是先修改了该地址为可写权限,那么导致该原因的情况就是其他线程执行了权限恢复后切换到该线程中进行写,所以导致了这个问题。
因此内联钩取是存在多线程安全的问题,此时可以使用微软自己构建的钩取库Detours,可以在钩取过程中确保线程安全。
①网安学习成长路径思维导图
②60+网安经典常用工具包
③100+SRC漏洞分析报告
④150+网安攻防实战技术电子书
⑤最权威CISSP认证考试指南+题库
⑥超1800页CTF实战技巧手册
⑦最新网安大厂面试题合集(含答案)
项目地址:
环境配置参考:
使用vcpkg下载
::实例
挂钩
利用Detours挂钩非常简单,只需要根据下列顺序,并且将自定义函数的地址与被挂钩的地址即可完成挂钩处理。
//用于确保在DLL注入或加载时,恢复被Detours修改的进程镜像,保持稳定性DetourRestoreAfterWith();//开始一个新的事务来附加或分离DetourTransactionBegin();//进行线程上下文的更新DetourUpdateThread(GetCurrentThread());//挂钩DetourAttach((PVOID)TrueZwQuerySystemInformation,ZwQuerySystemInformationEx);//提交事务error=DetourTransactionCommit();
脱钩
然后根据顺序完成脱钩即可。
//开始一个新的事务来附加或分离DetourTransactionBegin();//进行线程上下文的更新DetourUpdateThread(GetCurrentThread());//脱钩DetourDetach((PVOID)TrueZwQuerySystemInformation,ZwQuerySystemInformationEx);//提交事务error=DetourTransactionCommit();挂钩的原理
从上述可以看到,Detours是通过事务确保了在DLL加载与卸载时后的原子性,但是如何确保多线程安全呢?后续通过调试去发现。
可以利用xntdl!ZwQuerySystemInformation查看函数地址,可以看到函数的未被挂钩前的情况如下图。
挂钩之后原始的指令被修改为一个跳转指令把前八个字节覆盖掉,剩余的3字节用垃圾指令填充。
该地址里面又是一个jmp指令,并且完成间接寻址的跳转。
该地址是自定义函数ZwQuerySystemInformationEx,因此该间接跳转是跳转到的自定义函数内部。
跳转到TrueZwQuerySystemInformation内部发现ZwQuerySystemInformation函数内部的八字节指令被移动到该函数内部。紧接着又完成一个跳转。
该跳转到ZwQuerySystemInformation函数内部紧接着完成ZwQuerySystemInformation函数的调用。
综上所述,整体流程如下图。实际上Detours实际上使用的是热补丁的思路,但是Detours并不是直接在原始的函数空间中进行补丁,而是开辟了一段临时空间,将指令存储在里面。因此在挂钩后不需要进行脱钩处理就可以调用原始函数。因此就不存在多线程中挂钩与脱钩的冲突。
完整代码: