前言
由于以前接触过一些安全相关的问题,所以对钩子也还是有点了解的,但是也就在了解层面上。前些时间因为想多开某游戏(并非用于搬砖),但是由于其自身会检测游戏是否正在运行,所以需要先将正常运行的游戏进程隐藏掉(当然实际上还需要一些其他操作,我这里就不多说了)。于是这个事情再一次被拿了出来。当然我的知识也是有限的,部分解释可能不恰当或者有错误,欢迎指出。
钩子技术,就是通过修改系统函数表或者函数入口几个字节,使得目标程序调用指定函数时跳转到自己的程序中进行处理(这里可以调用原函数也可以不调用)。我这里需要隐藏进程,也就是在游戏启动器调用系统函数检索进程的时候我们给予一个拦截,将游戏进程隐藏起来即可。
为了实现这一点,一般有两种方式,一种直接修改系统的函数表,但是这种方式因为安全问题已经很难通过普通程序实现,通常需要使用驱动进入系统内核,并且还涉及到许多其他问题。我们这里主要研究针对目标程序的hook。由于操作系统原理的一些东西,每个程序内部内存都是相互独立的一份虚拟空间(除非使用特定api)。比如程序a有0x1000存放着a的程序,b却在0x1000存放着它的数据,这是不冲突的,因为这个0x1000是虚拟出来的地址,而非真实存在物理内存的地址。所以如果想直接让目标程序调用指定函数时跳转到自己程序的话,就可以洗洗睡了,目标程序是根本调用不到你程序内的东西的。所以绝大多数的钩子都会使用一个dll注入到目标程序中,将钩子代码写在dll中注入到目标程序中。网上也有一些这样的库,使用起来非常方便,但是,如何将dll注入人就是需要解决的问题。加上多出一个dll可不太酷,所以之前这个问题就一咕再咕。
接下来的时间里,有人告诉我网上其他人做的多开器有无dll的版本(当然没有源码,甚至是收费的),所以这又激发了我的兴趣。经过一番搜索我便找到了这篇文章。它所做的确实就实现了这么个功能,但是一来是作者本身就只放了部分源码,其余部分需要自己补全,其次这段代码有不完善的地方,其实无dll的hook本质和有dll的一致,不过不是使用dll在目标程序内正常加载,而是直接将自身程序内存中的钩子拷贝到目标程序中(使用WriteProcessMemory
),但是这中间会出现许多地址产生变化,所以需要重新计算,所以我就来聊聊这几天我踩的坑。
警告:未经授权使用钩子技术修改其他程序可能产生法律问题,本文仅从技术角度进行讨论钩子实现原理以及无dll的实现。
本文的钩子技术在64位操作系统下使用32位编译通过,并能成功hook32位程序。同理可在32位系统中hook32位程序,但不支持hook64位程序(通过一定修改可以hook,具体细节在文末讨论)。另外为了避免生成调试代码产生干扰,我这里是采用release编译的,用debug编译是否可行没做测试。源代码有点乱,毕竟是一遍测试一遍改的,但是相比之下应该还是一个能跑起来的代码。
确定被hook函数与hook之后做啥
起初我拿到唯一有价值的信息就是游戏检索进程的方式是CreateToolhelp32Snapshot
,所以理论上我应该是hook这个函数就行了。但是出于许多原因(特别是我找到的那篇文章),我这里Hook更接近系统内核的ZwQuerySystemInformation
(CreateToolhelp32Snapshot
内部是调用ZwQuerySystemInformation
实现功能的)
好了假设我们hook了这个函数我们应该怎么做呢?
如果你想返回所有数据全部虚构的话,直接返回的你结果就行,但是如果你只是想捕获原函数的调用尝试或者对原函数的返回值进行修改的话,还是得调用原函数的。
我们先测试钩子是否可行,于是先写这么一个钩子函数,它先于函数运行。
- NTSTATUS
- NTAPI
- HOOK_ZwQuerySystemInformation(
- IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
- OUT PVOID SystemInformation,
- IN ULONG SystemInformationLength,
- OUT PULONG ReturnLength OPTIONAL
- )
- {
-
- NTSTATUS ntStatus=ZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,OPTIONAL);
- return ntStatus;
- }
好了,假设这就是我们的钩子函数,我们调用先调用自己钩子函数可以看到正常返回(缺失的定义请先引用windows.h
和Psapi.h
,剩下部分可参考文末完整代码,记得使用ZwQuerySystemInformation
前需要HINSTANCE hNTDLL = ::GetModuleHandle(TEXT("ntdll"));
和(FARPROC&)ZwQuerySystemInformation =::GetProcAddress(hNTDLL, "ZwQuerySystemInformation")
;对函数指针赋值,使用完之后别忘了使用FreeLibrary(hNTDLL);
释放dll句柄资源,调用参考可以参考后面完整代码的test函数。
好了我们已经实现一个啥都没做的中间层了,接下来就是对数据进行处理。
相比原文,我这里增加了ntStatus != STATUS_INFO_LENGTH_MISMATCH
的判断,这是我踩到的一个坑,我是用任务栏管理器做的实验(拷贝32位系统的任务栏管理器到64位系统),然后不知道是因为我电脑上开的软件过多还是任务栏管理器的机制就是先分配小内存不够再加(毕竟我自己检测的时候网上那个代码分配的内存也不够用),总之,这里是需要考虑因为传入存放结果数据内存不够而失败的情况。
第二个改动是原来判断是目标进程的id,我这里需要批量,所以使用名称。
第三个改动,是去掉break,批量判断需要一直判断完。
第四个改动,是对pPrev = pCurr;
语句增加了条件限制,原作者是找到就跳出循环了,但是我们这里如果循环继续的话,用来标记前一元素的指针将会是已经被从这个链上摘除的数据了,对它修改没有任何意义了,效果参考下图。
- NTSTATUS
- NTAPI
- HOOK_ZwQuerySystemInformation(
- IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
- OUT PVOID SystemInformation,
- IN ULONG SystemInformationLength,
- OUT PULONG ReturnLength OPTIONAL
- )
- {
- NTSTATUS ntStatus;
- PSYSTEM_PROCESSES pSystemProcesses=NULL,Prev;
-
- ntStatus=ZwQuerySystemInformation(SystemInformationClass,SystemInformation,SystemInformationLength,OPTIONAL)
- if (ntStatus != STATUS_INFO_LENGTH_MISMATCH && NT_SUCCESS(ntStatus) && SystemInformationClass == 5) {
- PSYSTEM_PROCESS_INFORMATION pCurr = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
- PSYSTEM_PROCESS_INFORMATION pPrev = NULL;
- while (pCurr)
- {
- LPWSTR pszProcessName = pCurr->ImageName.Buffer;
- if (pszProcessName != NULL)
- {
- if (0 == memcmp(pszProcessName, L"notepad.exe", pCurr->ImageName.Length>22 ? 22 : pCurr->ImageName.Length) || 0 == memcmp(pszProcessName, L"chrome.exe", pCurr->ImageName.Length>20 ? 20 : pCurr->ImageName.Length))
- {
- if (pPrev) // Middle or Last entry
- {
- if (pCurr->NextEntryOffset)
- pPrev->NextEntryOffset += pCurr->NextEntryOffset;
- else // we are last, so make prev the end
- pPrev->NextEntryOffset = 0;
- }
- else
- {
- if (pCurr->NextEntryOffset)
- {
- // we are first in the list, so move it forward
- SystemInformation = (UCHAR*)SystemInformation + pCurr->NextEntryOffset;
- }
- else // 唯一的进程
- SystemInformation = NULL;
- }
- }
- else
- {
- pPrev = pCurr;
- }
- }
- else
- {
- pPrev = pCurr;
- }
- if (pCurr->NextEntryOffset)
- {
- pCurr = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pCurr) + pCurr->NextEntryOffset);
- }
- else
- {
- pCurr = NULL;
- }
- }
- }
- return ntStatus;
- }
好,如果这时候调用我们的钩子函数还能成功并且能正确隐藏notepad和chrome的话,那就说明我们钩子函数本身的摘链是正确的,只要想办法注入即可(才怪)。
按hook流程走一遍
我们接下来应该就是改原本的系统函数入口的内容,让他跳转到我们的程序之中。那么这里会有几个问题。
第一如果我们直接覆盖目标函数的前几个字节,势必会使得原函数内容发生错误,同时,我们调用原函数的时候,如果仍然使用原入口的话,会产生一个死循环(HOOK函数调用原函数,原函数jump到HOOK函数起始位置,然后继续调原函数。。。)。那么理论上来说,我们要做的就是把目标函数的前几个字节复制出来自己执行,然后不能直接调用目标函数了,需要跳过前面跳转的几个字节。这里就需要用到汇编了。
这里还需要注意的是,原函数必须被call,而不是jump,因为原函数结尾有ret指令,如果是jump过去的话,当前地址是不会入栈的,原函数ret之后会直接返回给调用者,我们希望对结果进行处理就必须用call。使用call之后,又会有一个问题,因为call是函数调用,一些寄存器状态什么的可能会受到影响,所以,我们需要一个代理函数,我们call这个代理函数,然后由代理函数执行原函数开头几字节的指令然后jump到原函数被覆盖的字节之后。这里还需要注意的是,我们这里用的jump+偏移地址的指令是5字节,正好ZwQuerySystemInformation
第一个指令也是5字节,所以直接在代理函数中复制前5字节,跳转的时候也是原地址加5字节。但是如果原函第一个指令不是5字节或者想用6字节的绝对地址跳转,需要保证复制的指令是完整的而不是被打断的(每个指令的长度是不一致的,如果指令被截断,很可能会和后面的指令混在一起被错误执行,然后出现程序跑飞的情况),其次,空间必须足够放得下跳转指令。当然被拷贝的指令可以不止一条,但是不能有使用偏移地址的。具体可以自己调用一下然后用od过去看看。
结合上面所说的,我们可以做出这样一个代理函数:
- __declspec (naked) VOID ZwQuerySystemInformationProxy()
- {
- //这里备份五个字节就可以了的因为Zwxx的函数格式原因这里固定都是无个字节
- _asm {
- nop
- nop
- nop
- nop
- nop
- mov ebx, 0x88888888 //ZwQuerySystemInformation 方便特征定位
- jmp ebx
- }
- }
另外这里需要确保ebx不被前几个指令使用。否则需要换寄存器。至于调用,这是一个纯汇编函数,加上原作者用汇编形式调用的,所以我这里也用汇编调用。
- _asm {
- push ebx
- push ReturnLength
- push SystemInformationLength
- push SystemInformation
- push SystemInformationClass
- call ZwQuerySystemInformationProxy //让原来函数执行完成,只有这样函数才能返回我们需要的数据然后在数据里进行修改
- mov ntStatus, eax
- pop ebx
- }
那我们接下来就是将会被我们跳转指令覆盖的几个字节的数据复制到代理函数中,保证这些代码也能正常运行。同事注意我们在代理函数中并没有写出ZwQuerySystemInformation
因为我们要跳转的目标地址比较特殊,需要一个偏移量所以需要额外处理。我们编写hook函数来完成这个过程。
- BOOLEAN SetHook(){
- //1 将ZwQuerySystemInformation开头的5字节数据拷贝到代理函数中
- memcpy(ZwQuerySystemInformationProxy, ZwQuerySystemInformation, 5);
-
- //2 对我们的跳转地址赋值
- DWORD dwCodeStart = GetFunAddress((PUCHAR)ZwQuerySystemInformationProxy);
- for (int i = 0; i < 50; i++) {
- if (0x88888888 == *(DWORD *)(dwCodeStart + i)) {//找到
- *(DWORD *)(dwCodeStart + i) = (DWORD)ZwQuerySystemInformation+5;//赋值为ZwQuerySystemInformation起始地址向后偏移5字节,即跳过将来的跳转指令。
- }
- }
- }
这时,再次调用HOOK_ZwQuerySystemInformation
函数应该仍然能正常工作。接着,我们继续往下走,我们的目标是让调用ZwQuerySystemInformation
时跳转到我们的hook函数,而不是需要别人主动来调用我们的hook函数。因此我们接下来就是要让将ZwQuerySystemInformation
的前几个字节改成跳到我们HOOK_ZwQuerySystemInformation
函数的指令。
我hook我自己
为了方便将来hook其他程序,我们在SetHook
中增加参数DWORD dwProcessId
。在调用时先传入当前进程id(GetProcessId(GetCurrentProcess())
)测试。
还记得我之前说过没法直接读写其他程序内存的把?因此我们需要调用API函数来写入这个数据。使用这个函数之前我们需要先OpenProcess
打开进程。简单起见就是直接使用PROCESS_ALL_ACCESS
得到所有权限即可。理论上我们主要需要PROCESS_VM_OPERATION
。OpenProcessToken
- BYTE HookCode[5] = { 0xE9,0,0,0,0 };//0xE9是跳转指令的机器码,后面跟4字节的偏移地址
- //注意,这是32位机器码,可以注入到32位的程序中,如果需要hook64位程序,思路一样,但是这个机器码,指令长度等都会有变化。
- HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
- FALSE,
- dwProcessId
- );
- if (hProcess) {
- *(DWORD *)((char*)HookCode+1) = (DWORD)HOOK_ZwQuerySystemInformation - 5 - (DWORD)ZwQuerySystemInformation;
- //这里拆解下这句指令:
- //HookCode是我们暂存跳转指令的地方,他是一个指针,我们把它转换成char*类型,保证其步长是1字节,然后将其地址+1,其实也就是相当于&(HookCode[1]),
- //然后将其转换为DWORD *,也就是步长变为4字节,就是一个使用地址为HookCode[1]-HookCode[4]组成的一个4字节长度空间的指针,
- //也就是我们要存放跳转地址的指针。这样转换的过程中可能会涉及大端模式和小端模式的问题(具体请自行查阅资料),不过既然别人这么用了,那就说明这个东西是一致的
- //(DWORD)HOOK_ZwQuerySystemInformation是我们的目标地址,但他是偏移地址,因此我们要将其减去指令所在位置的地址。
- //那么我们指令在什么位置呢?当然是(DWORD)ZwQuerySystemInformation这里了啊。
- //可是为啥还要多减去5呢?因为这个偏移地址是相对jmp指令结束位置的,而(DWORD)ZwQuerySystemInformation是指令开始位置,它长度是5,所以还得再减去5。
- BOOL bRet = WriteProcessMemory(hProcess,
- ZwQuerySystemInformation,
- HookCode,
- 5,
- NULL
- );//将这5字节数据写到目标程序ZwQuerySystemInformation的位置,这样,别人调用ZwQuerySystemInformation就自动跳入hook函数了。
- if(!bRet) printf("fail to write ! error:%d", GetLastError());
- }
这里使用机器码是不可避免的,毕竟,我们没法直接让编译器、连接器把代码生成到其他程序中,我们也没办法去定位一个语句在自己程序中生成的位置(虽然可以用特征,但是同样会需要汇编基础)
好了不管怎么说现在应该实现了调用ZwQuerySystemInformation
和调用HOOK_ZwQuerySystemInformation
是一样效果了吧。那么我们进一步,别的程序可是没法调用我们程序中函数的,因此我们这些函数肯定是要放到其他程序中的。最简单的是把我们现在的代码编译成dll注入进去。不过注入,远程线程什么的,一个都不能少。这工作量也是不小的,而且需要一个exe和一个dll来完成,可不太酷。所以有个更加有意思的方法就是直接定位自己内存中的数据,然后拷贝到目标进程中去。
拷贝代码到目标程序hook
为了定位我们需要拷贝的代码在自身内存中的位置,我们需要定义两个空方法分别置于我们代码的起始位置和结束位置,这两个函数的指针地址就是我们代码的起始位置和结束位置(实际上起始位置可以用第一个函数的函数指针,但是定一个起始地址函数更加通用稳妥)
- __declspec (naked) VOID FunStart() {};//定义需要拷贝代码的起始位置
- //需要存放拷贝的内容,这里包括HOOK_ZwQuerySystemInformation和ZwQuerySystemInformationProxy两个函数。
- //......
- __declspec (naked) VOID FunEnd() { _asm {nop} };//定义函数结束的位置
接下来我们在我们的目标函数中申请一段内存用来存放这些代码,注意权限,读写执行:
- DWORD dwCodeStart, dwCodeEnd, dwCodeSize;
- dwCodeStart = GetFunAddress((PUCHAR)FunStart);
- dwCodeEnd = GetFunAddress((PUCHAR)FunEnd);
- dwCodeSize = dwCodeEnd - dwCodeStart;
- PVOID RemoteAllocBase = RemoteAllocBase = VirtualAllocEx(hProcess,
- NULL,
- dwCodeSize,
- MEM_COMMIT,
- PAGE_EXECUTE_READWRITE
- );
- if (RemoteAllocBase) {
- printf("\t申请内存地址:0x%x\n", RemoteAllocBase);
- //do something...
- }
确认内存分配到了之后,我们就可以把我们的内容写进去了。首先我们得让我们要拷贝的内存地址变成可读可写,然后就可以拷贝了:
- VirtualProtect((PVOID)dwCodeStart,
- dwCodeSize,
- PAGE_EXECUTE_READWRITE,
- &OldProtect
- );
- bRet = WriteProcessMemory(hProcess,
- RemoteAllocBase,
- (PVOID)dwCodeStart,
- dwCodeSize,
- NULL
- );
ok,树我们是完成了?并不,我们前面那个跳转的还是我们程序编译时生成的地址,如果hook别人的程序,别人程序中这个地址可不见得就是这些代码,并且我们分配的内存地址是操作系统动态决定的,所以需要重新计算。(但是我推荐先在自己程序中调试一遍,拷贝出来的代码将不会被vs的调试器监控到,如果有问题会导致被挂钩程序崩溃,即使是挂钩本程序,vs也只知道有异常,但是并不知道在哪。这样调试会非常痛苦)。
这个计算过程就是先拿到HOOK函数在本程序中的地址,减去起始的地址,得到偏移量。然后加上分配地址的起始地址,这样就得到在目标函数中的位置了。然后替换我们jmp的地址。
- dwFunAddress = GetFunAddress((PUCHAR)HOOK_ZwQuerySystemInformation);
- dwFunAddress -= dwCodeStart;
- dwFunAddress += (DWORD)RemoteAllocBase;
- *(DWORD *)((char*)HookCode+1) = (DWORD)dwFunAddress - 5 - (DWORD)ZwQuerySystemInformation;
如果一步步做过来的话,这一步应该在hook自己的时候还是能成功的。
是不是改下目标程序的挂钩位置就可以hook别的程序了呢?其实并不需要,因为同一个dll在不同程序的内存中的位置是一样的(别问我为啥,我也不知道,但是确实是这样),可以在PCHunter中查看进程模块中看到不同进程中的同一模块地址是一样的(注意32位程序的64位程序的模块本身就是不一样的)
不过现在这样hook其他程序的时候大概是会崩。进od调试,发现多了一个叫__security_check_cookie
的调用,然后程序跑飞了,查阅资料后得知那是安全检查。
解决办法就是在vs中关闭安全检查,解决方法:项目属性,C/C++ // 代码生成 //安全检查//禁止
再次调用,hook其他程序仍然会崩。我开始推测是不是其他函数调用(如memcmp
)也会存在问题,姑且都删了,自己逐字节比较,目标程序仍然会崩。
如果你对c语言足够熟悉的话,应该已经发现我之前犯了个大错。c语言是有内存分区的,其中就有一个文本常量区,也就是说我的文本数据并不会跟着程序一同被拷贝到目标程序中,它是一个指向常量区的一个指针,别的程序中可能这段地址并没有被分配过,程序跑个这里的时候尝试去那个地址取字符的时候就报野指针了。
要解决这个问题,我先想到的是咱们别用那个静态区的字符串了,我们写个char need[20];然后赋值不就好了吗?然而这个数组确实是在栈区了,但是这个栈是动态分配的,所以不会有初始值,给他赋值字符串,其实还是会从常量区取出来。所以要解决这个办法,最好是能让这些数据全部到代码区来,也就是手动逐字符比较,我尝试了switch case的方式,写前面两字符的比较时没有丝毫问题,但是数量多了之后有开始崩了。答案已经在嘴边了,就懒得开od去研究和验证了,毕竟我们的编译器已经不是我们写啥他就输出啥,你们可以试着关掉编译器的优化看看能不能解决,对于我来说,判断的了一个字符都足够了。
不过这里还有个思路,就是操作起来会有点复杂,但是更加合理一点,有兴趣的话可以去试试:既然我们能把我们的代码区搬到目标程序中,那我们同样可以搬常量区啊。可是常量区的地址怎么来?我们字符串的地址就是指向常量区我们要的字符串的指针,所以它存放的正是我们需要的起始地址。至于长度,你知道字符串的长度了,即使转换成宽字符了也同样可以得到长度啊,不是吗?这样我们就能将我们的文本常量塞进目标程序了。接下来就是怎么访问这些字符串了。
我们当然没法直接在程序中引用这些地址来做到访问,但是我们已经处理过类似问题了,我们只需要一个指向有特征的地址的指针(至于这个地址是否有被分配,存放啥都不重要)
结束收工
至此我们就完成了无dll的注入,如果需要注入64位程序,我这里提一下,其中一个是32位指令(包括提到的那个机器码)都要换64位了,另一个问题就是vs不允许我们在64位程序中c语言代码嵌入汇编,因此可能需要更换编译器或者另外用汇编写一些函数(asm后缀),然后分两段拷入目标(当然dll注入就没有这个问题)。
附上所有代码如下:
- #include <Windows.h>
- #include <Psapi.h>
- #include <stdio.h>
- #define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
- #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
- typedef enum _SYSTEM_INFORMATION_CLASS {
- SystemBasicInformation,// 0 Y N
- SystemProcessorInformation,// 1 Y N
- SystemPerformanceInformation,// 2 Y N
- SystemTimeOfDayInformation,// 3 Y N
- SystemNotImplemented1,// 4 Y N // SystemPathInformation
- SystemProcessesAndThreadsInformation,// 5 Y N
- SystemCallCounts,// 6 Y N
- SystemConfigurationInformation,// 7 Y N
- SystemProcessorTimes,// 8 Y N
- SystemGlobalFlag,// 9 Y Y
- SystemNotImplemented2,// 10 YN // SystemCallTimeInformation
- SystemModuleInformation,// 11 YN
- SystemLockInformation,// 12 YN
- SystemNotImplemented3,// 13 YN // SystemStackTraceInformation
- SystemNotImplemented4,// 14 YN // SystemPagedPoolInformation
- SystemNotImplemented5,// 15 YN // SystemNonPagedPoolInformation
- SystemHandleInformation,// 16 YN
- SystemObjectInformation,// 17 YN
- SystemPagefileInformation,// 18 YN
- SystemInstructionEmulationCounts,// 19 YN
- SystemInvalidInfoClass1,// 20
- SystemCacheInformation,// 21 YY
- SystemPoolTagInformation,// 22 YN
- SystemProcessorStatistics,// 23 YN
- SystemDpcInformation,// 24 YY
- SystemNotImplemented6,// 25 YN // SystemFullMemoryInformation
- SystemLoadImage,// 26 NY // SystemLoadGdiDriverInformation
- SystemUnloadImage,// 27 NY
- SystemTimeAdjustment,// 28 YY
- SystemNotImplemented7,// 29 YN // SystemSummaryMemoryInformation
- SystemNotImplemented8,// 30 YN // SystemNextEventIdInformation
- SystemNotImplemented9,// 31 YN // SystemEventIdsInformation
- SystemCrashDumpInformation,// 32 YN
- SystemExceptionInformation,// 33 YN
- SystemCrashDumpStateInformation,// 34 YY/N
- SystemKernelDebuggerInformation,// 35 YN
- SystemContextSwitchInformation,// 36 YN
- SystemRegistryQuotaInformation,// 37 YY
- SystemLoadAndCallImage,// 38 NY // SystemExtendServiceTableInformation
- SystemPrioritySeparation,// 39 NY
- SystemNotImplemented10,// 40 YN // SystemPlugPlayBusInformation
- SystemNotImplemented11,// 41 YN // SystemDockInformation
- SystemInvalidInfoClass2,// 42 // SystemPowerInformation
- SystemInvalidInfoClass3,// 43 // SystemProcessorSpeedInformation
- SystemTimeZoneInformation,// 44 YN
- SystemLookasideInformation,// 45 YN
- SystemSetTimeSlipEvent,// 46 NY
- SystemCreateSession,// 47 NY
- SystemDeleteSession,// 48 NY
- SystemInvalidInfoClass4,// 49
- SystemRangeStartInformation,// 50 YN
- SystemVerifierInformation,// 51 YY
- SystemAddVerifier,// 52 NY
- SystemSessionProcessesInformation// 53 YN
- } SYSTEM_INFORMATION_CLASS;
- typedef enum _THREAD_STATE
- {
- StateInitialized,
- StateReady,
- StateRunning,
- StateStandby,
- StateTerminated,
- StateWait,
- StateTransition,
- StateUnknown
- }THREAD_STATE;
- typedef enum _KWAIT_REASON
- {
- Executive,
- FreePage,
- PageIn,
- PoolAllocation,
- DelayExecution,
- Suspended,
- UserRequest,
- WrExecutive,
- WrFreePage,
- WrPageIn,
- WrPoolAllocation,
- WrDelayExecution,
- WrSuspended,
- WrUserRequest,
- WrEventPair,
- WrQueue,
- WrLpcReceive,
- WrLpcReply,
- WrVertualMemory,
- WrPageOut,
- WrRendezvous,
- Spare2,
- Spare3,
- Spare4,
- Spare5,
- Spare6,
- WrKernel
- }KWAIT_REASON;
- typedef struct _LSA_UNICODE_STRING
- {
- USHORT Length;
- USHORT MaximumLength;
- PWSTR Buffer;
- }LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
- typedef LSA_UNICODE_STRING UNICODE_STRING, *PUNICODE_STRING;
- typedef LONG KPRIORITY;
- typedef struct _CLIENT_ID
- {
- HANDLE UniqueProcess;
- HANDLE UniqueThread;
- }CLIENT_ID;
- typedef CLIENT_ID *PCLIENT_ID;
- typedef struct _VM_COUNTERS
- {
- ULONG PeakVirtualSize; //虚拟存储峰值大小;
- ULONG VirtualSize; //虚拟存储大小;
- ULONG PageFaultCount; //页故障数目;
- ULONG PeakWorkingSetSize; //工作集峰值大小;
- ULONG WorkingSetSize; //工作集大小;
- ULONG QuotaPeakPagedPoolUsage; //分页池使用配额峰值;
- ULONG QuotaPagedPoolUsage; //分页池使用配额;
- ULONG QuotaPeakNonPagedPoolUsage; //非分页池使用配额峰值;
- ULONG QuotaNonPagedPoolUsage; //非分页池使用配额;
- ULONG PagefileUsage; //页文件使用情况;
- ULONG PeakPagefileUsage; //页文件使用峰值;
- }VM_COUNTERS, *PVM_COUNTERS;
- typedef struct _SYSTEM_THREADS
- {
- LARGE_INTEGER KernelTime; //CPU内核模式使用时间;
- LARGE_INTEGER UserTime; //CPU用户模式使用时间;
- LARGE_INTEGER CreateTime; //线程创建时间;
- ULONG WaitTime; //等待时间;
- PVOID StartAddress; //线程开始的虚拟地址;
- CLIENT_ID ClientId; //线程标识符;
- KPRIORITY Priority; //线程优先级;
- KPRIORITY BasePriority; //基本优先级;
- ULONG ContextSwitchCount; //环境切换数目;
- THREAD_STATE State; //当前状态;
- KWAIT_REASON WaitReason; //等待原因;
- }SYSTEM_THREADS, *PSYSTEM_THREADS;
- typedef struct _SYSTEM_PROCESSES
- {
- ULONG NextEntryDelta; //构成结构序列的偏移量;
- ULONG ThreadCount; //线程数目;
- ULONG Reserved1[6];
- LARGE_INTEGER CreateTime; //创建时间;
- LARGE_INTEGER UserTime; //用户模式(Ring 3)的CPU时间;
- LARGE_INTEGER KernelTime; //内核模式(Ring 0)的CPU时间;
- UNICODE_STRING ProcessName; //进程名称;
- KPRIORITY BasePriority; //进程优先权;
- ULONG ProcessId; //进程标识符;
- ULONG InheritedFromProcessId; //父进程的标识符;
- ULONG HandleCount; //句柄数目;
- ULONG Reserved2[2];
- VM_COUNTERS VmCounters; //虚拟存储器的结构,见下;
- IO_COUNTERS IoCounters; //IO计数结构,见下;
- SYSTEM_THREADS Threads[1]; //进程相关线程的结构数组,见下;
- }SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;
- typedef struct _SYSTEM_THREAD_INFORMATION {
- LARGE_INTEGER KernelTime;
- LARGE_INTEGER UserTime;
- LARGE_INTEGER CreateTime;
- ULONG WaitTime;
- PVOID StartAddress;
- CLIENT_ID ClientId;
- KPRIORITY Priority;
- LONG BasePriority;
- ULONG ContextSwitchCount;
- ULONG State;
- KWAIT_REASON WaitReason;
- }SYSTEM_THREAD_INFORMATION, *PSYSTEM_THREAD_INFORMATION;
- typedef struct _SYSTEM_PROCESS_INFORMATION {
- ULONG NextEntryOffset;
- ULONG NumberOfThreads;
- LARGE_INTEGER Reserved[3];
- LARGE_INTEGER CreateTime;
- LARGE_INTEGER UserTime;
- LARGE_INTEGER KernelTime;
- UNICODE_STRING ImageName;
- KPRIORITY BasePriority;
- HANDLE ProcessId;
- HANDLE InheritedFromProcessId;
- ULONG HandleCount;
- ULONG Reserved2[2];
- ULONG PrivatePageCount;
- VM_COUNTERS VirtualMemoryCounters;
- IO_COUNTERS IoCounters;
- SYSTEM_THREAD_INFORMATION Threads[0];
- } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;
- extern "C" LONG(__stdcall *ZwQuerySystemInformation)(
- _In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
- _Inout_ PVOID SystemInformation,
- _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength
- ) = NULL;
- __declspec (naked) VOID FunStart() {};//定义函数开始的位置 release版本 没用
- __declspec (naked) VOID ZwQuerySystemInformationProxy()
- {
- //这里备份五个字节就可以了的因为Zwxx的函数格式原因这里固定都是无个字节
- _asm {
- nop
- nop
- nop
- nop
- nop
- mov ebx, 0x88888888 //ZwQuerySystemInformation 方便特征定位
- jmp ebx
- }
- }
- NTSTATUS
- NTAPI
- HOOK_ZwQuerySystemInformation(
- IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
- OUT PVOID SystemInformation,
- IN ULONG SystemInformationLength,
- OUT PULONG ReturnLength OPTIONAL
- )
- {
- NTSTATUS ntStatus;
- PSYSTEM_PROCESSES pSystemProcesses = NULL, Prev;
- _asm {
- push ebx
- push ReturnLength
- push SystemInformationLength
- push SystemInformation
- push SystemInformationClass
- call ZwQuerySystemInformationProxy //让原来函数执行完成,只有这样函数才能返回我们需要的数据然后在数据里进行修改
- mov ntStatus, eax
- pop ebx
- }
- if (ntStatus != STATUS_INFO_LENGTH_MISMATCH && NT_SUCCESS(ntStatus) && SystemInformationClass == 5) {
- PSYSTEM_PROCESS_INFORMATION pCurr = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
- PSYSTEM_PROCESS_INFORMATION pPrev = NULL;
- while (pCurr)
- {
- LPWSTR pszProcessName = pCurr->ImageName.Buffer;
- if (pszProcessName != NULL)
- {
- bool find = true;
- for (int i = 0;i<pCurr->ImageName.Length&&i<1;i++) {
- if (*((char*)pszProcessName + i) == 'c') {//隐藏开头的
- }
- else
- {
- find = false;
- break;
- }
- }
- //if (0 == memcmp(pszProcessName, L"notepad.exe", pCurr->ImageName.Length>22 ? 22 : pCurr->ImageName.Length) || 0 == memcmp(pszProcessName, L"chrome.exe", pCurr->ImageName.Length>20 ? 20 : pCurr->ImageName.Length))
- if (find)
- {
- if (pPrev) // Middle or Last entry
- {
- if (pCurr->NextEntryOffset)
- pPrev->NextEntryOffset += pCurr->NextEntryOffset;
- else // we are last, so make prev the end
- pPrev->NextEntryOffset = 0;
- }
- else
- {
- if (pCurr->NextEntryOffset)
- {
- // we are first in the list, so move it forward
- SystemInformation = (UCHAR*)SystemInformation + pCurr->NextEntryOffset;
- }
- else // 唯一的进程
- SystemInformation = NULL;
- }
- }
- else
- {
- pPrev = pCurr;
- }
- }
- else
- {
- pPrev = pCurr;
- }
- if (pCurr->NextEntryOffset)
- {
- pCurr = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pCurr) + pCurr->NextEntryOffset);
- }
- else
- {
- pCurr = NULL;
- }
- }
- }
- return ntStatus;
- }
- __declspec (naked) VOID FunEnd() { _asm {nop} };//定义函数结束的位置
- DWORD GetFunAddress(PUCHAR lpFunStart)
- {
- DWORD dwFunAddress;
- if (*lpFunStart == 0xE9)
- {
- //在Debug版本里VC会做一个跳转
- dwFunAddress = (DWORD)lpFunStart + *(DWORD *)(lpFunStart + 1) + 5;
- }
- else
- {
- dwFunAddress = (DWORD)lpFunStart;
- }
- return dwFunAddress;
- }
- BOOLEAN SetHook(DWORD dwProcessId, DWORD dwHideId)
- {
- BOOLEAN bRet = FALSE;
- DWORD OldProtect;
- DWORD dwCodeStart, dwCodeEnd, dwCodeSize;
- BYTE HookCode[5] = { 0xE9,0,0,0,0 };
- HANDLE hProcess = NULL;
- PVOID RemoteAllocBase = NULL;
- DWORD dwFunAddress;
- PUCHAR pBuffer;
- dwCodeStart = GetFunAddress((PUCHAR)FunStart);
- dwCodeEnd = GetFunAddress((PUCHAR)FunEnd);
- dwCodeSize = dwCodeEnd - dwCodeStart;
- hProcess = OpenProcess(PROCESS_ALL_ACCESS| PROCESS_VM_OPERATION,
- FALSE,
- dwProcessId
- );
- if (hProcess) {
- HANDLE hToken;
- if (OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken)) {
- printf("ok");
- TOKEN_PRIVILEGES tkp;
- LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);
- tkp.PrivilegeCount = 1;
- tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
- if(AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL)) printf("\nsucc");//通知系统修改进程权限
- PRIVILEGE_SET RequiredPrivileges = { 0 };
- RequiredPrivileges.Control = PRIVILEGE_SET_ALL_NECESSARY;
- RequiredPrivileges.PrivilegeCount = 1;
- RequiredPrivileges.Privilege[0].Luid = tkp.Privileges[0].Luid;
- RequiredPrivileges.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED;
- BOOL bResult = 0;
- PrivilegeCheck(hToken, &RequiredPrivileges, &bResult);
- if (bResult) printf("\n can debug");
- }else{
- printf("fail");
- }
- //OpenProcessToken(hProcess,)
- RemoteAllocBase = VirtualAllocEx(hProcess,
- NULL,
- dwCodeSize,
- MEM_COMMIT,
- PAGE_EXECUTE_READWRITE
- );
- if (RemoteAllocBase) {
- printf("\t申请内存地址:0x%x\n", RemoteAllocBase);
- //g_lpRemoteAllocBase = RemoteAllocBase;
- if (ZwQuerySystemInformation) {
- bRet = VirtualProtect((PVOID)dwCodeStart,
- dwCodeSize,
- PAGE_EXECUTE_READWRITE,
- &OldProtect
- );
- if (bRet) {
- memcpy(ZwQuerySystemInformationProxy, ZwQuerySystemInformation, 5); //这里可以在本进程中取备份代码也可以在远程进程中取一般正常情况是一样的
- //for (int i = 0; i < 50;i++) printf("\ndata %d is 0x%x\n",i, *(DWORD *)(dwCodeStart + i));
- //*(DWORD *)(dwCodeStart + 22) = (DWORD)ZwQuerySystemInformation;//这里不需要用特征定位,因为肯定是在第六个字节开始的地方
- for (int i = 0; i < 50; i++) {
- if (0x88888888 == *(DWORD *)(dwCodeStart + i)) {
- *(DWORD *)(dwCodeStart + i) = (DWORD)ZwQuerySystemInformation+5;
- printf("\nnumber is %d", dwCodeStart + i - (DWORD)ZwQuerySystemInformationProxy);
- }
- //printf("\ndata %d is 0x%x\n", i, *(DWORD *)(dwCodeStart + i));
- }
- *HookCode = 0xE9;
- dwFunAddress = GetFunAddress((PUCHAR)HOOK_ZwQuerySystemInformation);
- printf("\t原函数地址,:0x%x,初始地址:0x%x,代理地址:0x%x\n", HOOK_ZwQuerySystemInformation, dwCodeStart,ZwQuerySystemInformationProxy);
- dwFunAddress -= dwCodeStart;
- dwFunAddress += (DWORD)RemoteAllocBase; //计算HOOK_ZwQuerySystemInformation在目标进程中的地址
- printf("\tHOOK_ZwQuerySystemInformation内存地址:0x%x\n", dwFunAddress);
- *(DWORD *)((char*)HookCode+1) = (DWORD)dwFunAddress - 5 - (DWORD)ZwQuerySystemInformation;
- /*dwFunAddress = GetFunAddress((PUCHAR)HOOK_ZwQuerySystemInformation);
- for (pBuffer = (PUCHAR)dwFunAddress;
- pBuffer<(PUCHAR)dwFunAddress + (dwCodeEnd - dwFunAddress);
- pBuffer++
- )
- {
- if (*(DWORD *)pBuffer == 0x12345678) {
- *(DWORD *)pBuffer = dwHideId;
- break;
- }
- }*/
- VirtualProtect((PVOID)dwCodeStart,
- dwCodeSize,
- PAGE_EXECUTE_READWRITE,
- &OldProtect
- );
- }
- }
- bRet = WriteProcessMemory(hProcess,
- RemoteAllocBase,
- (PVOID)dwCodeStart,
- dwCodeSize,
- NULL
- );
- if (bRet) {
- bRet = WriteProcessMemory(hProcess,
- ZwQuerySystemInformation,
- HookCode,
- 5,
- NULL
- );
- if(!bRet) printf("fail to write ! error:%d", GetLastError());
- }
- }else{
- printf("%d", GetLastError());
- }
- CloseHandle(hProcess);
- }
- return bRet;
- }
- void test() {
- ULONG cbBuffer = 0x80000; //32k
- PVOID pSystemInfo;
- NTSTATUS status;
- PSYSTEM_PROCESS_INFORMATION pInfo;
- pSystemInfo = malloc(cbBuffer);
- status = HOOK_ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, pSystemInfo, cbBuffer, NULL);
- if (status == STATUS_INFO_LENGTH_MISMATCH||!NT_SUCCESS(status))
- {
- free(pSystemInfo);
- return;
- }
- pInfo = (PSYSTEM_PROCESS_INFORMATION)pSystemInfo; //把得到的信息放到pInfo中
- for (;;)
- {
- LPWSTR pszProcessName = pInfo->ImageName.Buffer;
- if (pszProcessName == NULL)
- {
- pszProcessName = L"NULL";
- }
- printf("PID:%d, process name:%S\n", pInfo->ProcessId, pszProcessName);
- if (pInfo->NextEntryOffset == 0) //==0,说明到达进程链的尾部了
- {
- break;
- }
- pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo) + pInfo->NextEntryOffset); //遍历
- }
- free(pSystemInfo);
- }
- int main() {
-
- //SetHook(GetProcessId(GetCurrentProcess()), 500);
- SetHook(9480, 444);
- test();
- system("pause");
-
- return 0;
- }