前几天在论坛上看到一篇关于360HOOK框架的文章,写得不错。实际上,之前很多人都逆向破解了hookport.sys,而且教主还写了一套非常详细的文章。我才疏学浅,只是想补充一些科普知识,帮助像我这样的新手成长。这篇文章主要介绍了一些360的精妙应用,至于它Hook了哪些函数,大家可以去网上搜索教主的那篇文章。其实它就是一个结构。
亮点一:Hook KiFastCallEntry
科普一下KiFastCallEntry,快速系统调用SYSENTER。也就是说,每次系统调用的时候,最终都会通过KiFastCallEntry。我们来看看KiFastCallEntry函数的关键代码:
```assembly
8054257d 8bb324010000 mov esi,dword ptr [ebx+124h] //获得CurrentThread
80542583 ff33 push dword ptr [ebx]
80542585 c703ffffffff mov dword ptr [ebx],0FFFFFFFFh
8054258b 8b6e18 mov ebp,dword ptr [esi+18h]
8054258e 6a01 push 1
80542590 83ec48 sub esp,48h
80542593 81ed9c020000 sub ebp,29Ch
80542599 c6864001000001 mov byte ptr [esi+140h],1
805425a0 3bec cmp ebp,esp
805425a2 758d jne nt!KiFastCallEntry2+0x49 (80542531)
805425a4 83652c00 and dword ptr [ebp+2Ch],0
805425a8 f6462cff test byte ptr [esi+2Ch],0FFh
805425ac 89ae34010000 mov dword ptr [esi+134h],ebp
```
这段代码展示了KiFastCallEntry函数的关键部分。首先,它获取当前线程(CurrentThread),然后将ebp寄存器的值设置为0FFFFFFFF。接下来,它将ebp的值存储到esi指向的内存地址中。最后,它检查ebp和esp的值,如果它们相等,则跳转到KiFastCallEntry2函数的末尾。
以下是重构后的代码段:
```plaintext
805425b2 0f8538feffff jne nt!Dr_FastCallDrSave (805423f0)
805425b8 8b5d60 mov ebx,dword ptr [ebp+60h]
805425bb 8b7d68 mov edi,dword ptr [ebp+68h]
805425be 89550c mov dword ptr [ebp+0Ch],edx
805425c1 c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h
805425c8 895d00 mov dword ptr [ebp],ebx
805425cb 897d04 mov dword ptr [ebp+4],edi
805425ce fb sti
805425cf 8bf8 mov edi,eax //eax=SSDTindex
805425d1 c1ef08 shr edi,8 //除以256
805425d4 83e730 and edi,30h //判断是否为SSDTSHADOW或SSDT,要就是0 要就是0X10
805425d7 8bcf mov ecx,edi
805425d9 03bee0000000 add edi,dword ptr [esi+0E0h] //通过esi=KTHREAD->ServiceTable获得当前线程使用的ServiceTable;
805425df 8bd8 mov ebx,eax //SSDTID
805425e1 25ff0f0000 and eax,0FFFh
805425e6 3b4708 cmp eax,dword ptr [edi+8]
805425e9 0f8333fdffff jae nt!KiBBTUnexpectedRange (80542322)
```
这段代码似乎是一段汇编语言的代码,但是没有明确的主题或功能,因此很难进行重构。然而,我可以为你提供一些基本的重构建议:
1. 保持一致性:尽量保持代码的格式和风格一致,例如缩进、空格和括号的使用等。
2. 提取函数:如果有多个相似的功能,可以考虑将它们提取为单独的函数,以提高代码的可读性和可维护性。
3. 注释:对于复杂的代码段,添加适当的注释可以帮助其他人理解你的代码的目的和工作方式。
4. 错误处理:考虑添加错误处理机制,以便在出现问题时能够给出有用的错误信息。
5. 变量命名:尝试使用具有描述性的变量名,以便其他人(以及你自己在未来)能够理解它们的用途。
由于缺乏具体的上下文信息,我无法提供更具体的重构建议。如果你能提供更多的信息,我会很乐意帮助你进一步重构这段代码。
在这段代码中,我们可以看到360的Hook机制。首先,我们需要了解一些寄存器的含义:EDI=SSDTaddr,EBX=系统服务地址,EAX=SSDTindex。有了这些寄存器,我们可以省去很多麻烦。
接下来,我们分析360是如何进行Hook的:
1. 将edi指向当前函数的ebp寄存器地址;
2. 保存ebp寄存器;
3. 将esp寄存器指向ebp寄存器;
4. 减少栈指针esp的值,为后续操作做准备;
5. 保存edi、esi、ebx这三个寄存器的值;
6. 保存ZwSetEvent的地址;
7. 初始化一个AnsiString类型的DestinationString;
8. 调用ds:RtlInitAnsiString进行初始化;
9. 获取ZwSetEventINDEXID = DB;
10. 保存ZwSetEventaddrid的值;
11. 初始化一个SpinLock。
通过以上步骤,360成功地实现了Hook机制。
```
; 以下是重构后的内容:
; 获取当前的CPL(Current Processor Level)值
mov eax, cr0 ; 将CR0寄存器的值放入EAX
and eax, 0FFFEFFFFh ; 与掩码进行位与操作以获取最低有效位(Least Significant Bit),即CPL值
push eax ; 将CPL值压入栈
; 初始化自旋锁
call ds:KeInitializeSpinLock
; 尝试获取自旋锁
lea ecx, [ebp+SpinLock] ; 将SpinLock地址加载到ECX寄存器
call edi ; 调用KfAcquireSpinLock函数,将返回的事件句柄存储在EDI寄存器中
; 如果成功获取到自旋锁,则继续执行后续操作
mov cl, al ; 将AL寄存器的值保存到CL寄存器中,用于判断是否需要释放自旋锁
push eax ; 将事件句柄压入栈
push ebx ; 将EBX寄存器清空,用于保存上一次的状态
push edx ; 将EDX寄存器清空,用于保存上一次的状态
cli ; 关闭中断
mov eax, cr0 ; 将CR0寄存器的值放入EAX
mov [ebp+var_C], eax ; 将CPL值保存到[ebp+var_C]内存位置
and eax, 0FFFEFFFFh ; 与掩码进行位与操作以获取最低有效位(Least Significant Bit),即CPL值
mov cr0, eax ; 将修改后的CPL值写回CR0寄存器
mov eax, ZwSetEventaddrid //EAX=INDEXID
mov edx, KeServiceDescriptorTableaddr//EDX=SSDTADDR
mov edx, [edx]
mov ebx, [edx+eax*4] //获取ZwSetEventADDR
mov ZwSetEventaddr, ebx //保持
mov ebx, offset sub_192A8//HOOK //将hook地址保存到EBX寄存器中
mov [edx+eax*4], ebx //更新服务描述表中的事件地址
mov eax, [ebp+var_C] //将CPL值加载到EAX寄存器中
mov cr0, eax //将修改后的CPL值写回CR0寄存器
sti //开启中断
pop edx //恢复EDX寄存器的值
pop ebx //恢复EBX寄存器的值
pop eax //恢复EAX寄存器的值
mov dl, cl //将CL寄存器的值复制到DL寄存器中,用于计算NewIrql的值
lea ecx, [ebp+SpinLock] //将SpinLock地址加载到ECX寄存器
call ds:KfReleaseSpinLock //释放自旋锁并将结果保存在esi和eax寄存器中
mov eax, DWORD PTR [ebp+1B850] //从内存中读取保存的事件句柄值(DWORD类型)到EAX寄存器中
call ds:ZwSetEvent //触发事件处理过程
```
mov eax, fanhuiaddrs // 调用后返回地址8054263c
cmp eax, esi
jnz short loc_19510
mov eax, P
cmp eax, esi
jmp loc_195AB
cmp addbp4addr5, esi// 这个是HOOK返回地址8054262a
jnz loc_19629
第三步,我们来看下它的HOOK函数
.text:000192AA push ebp
.text:000192AB mov ebp, esp
.text:000192AD push ecx
.text:000192AE push ecx
.text:000192AF mov eax, [ebp+arg_0] // 获取第一个参数EventHandle
.text:000192B2 cmp eax, dword_1B850 // 看看是不是自己调用
.text:000192B8 push ebx
.text:000192B9 push esi
.text:000192BA push edi
.text:000192BB jnz loc_193CB // 如果不是就跳到原服务调用
.text:000192C1 call ds:ExGetPreviousMode
.text:000192C7 test al, al
.text:000192C9 jnz loc_193CB// 比较是不是USERMODULE调用还是跳过
.text:000192CF push 206B6444h ; Tag
重构后的内容如下:
```plaintext
.text:000192D4 push 5 ; NumberOfBytes
.text:000192D6 push 0 ; PoolType
.text:000192D8 call ds:ExAllocatePoolWithTag //声请一块5字节内存
.text:000192DE test eax, eax
.text:000192E0 mov P, eax //保存到全局变量
.text:000192E5 jz loc_193C7
.text:000192EB mov byte ptr [eax], 0E9h //第一个E9 JMP
.text:000192EE mov eax, ds:NtBuildNumber
.text:000192F3 cmp word ptr [eax], 1770h //比较版本号
.text:000192F8 mov eax, P
.text:000192FD mov ecx, offset byte_19284
.text:00019302 jge short loc_19309
.text:00019304 mov ecx, offset byte_19260 //取过滤函数地址
.text:00019309
.text:00019309 loc_19309:
.text:00019309 sub ecx, eax
.text:0001930B sub ecx, 5
```
以下是重构后的代码:
```assembly
.text:0001930E mov [eax+1], ecx //这几步都懂的,写跳转
.text:00019311 lea eax, [ebp+SpinLock]
.text:00019314 push eax ; SpinLock
.text:00019315 call ds:KeInitializeSpinLock
.text:0001931B lea ecx, [ebp+SpinLock] ; SpinLock
.text:0001931E call ds:KfAcquireSpinLock
.text:00019324 mov bl, al
.text:00019326 push eax
.text:00019327 push ecx
.text:00019328 push edx
.text:00019329 push esi
.text:0001932A push edi
.text:0001932B cli
.text:0001932C mov eax, cr0
.text:0001932F mov [ebp+arg_0], eax
.text:00019332 and eax, 0FFFEFFFFh
.text:00019337 mov cr0, eax
.text:0001933A mov eax, KeServiceDescriptorTableaddr
```
以下是重构后的代码:
```assembly
.text:0001933F mov eax, [eax] //这里先重新写回
.text:00019341 mov edx, ZwSetEventaddrid
.text:00019347 lea eax, [eax+edx*4]
.text:0001934A mov edx, ZwSetEventaddr
.text:00019350 mov [eax], edx
.text:00019352 mov eax, [ebp+4] //获取返回地址8054263c
.text:00019355 mov edx, eax
.text:00019357 mov fanhuiaddrs, edx //保存返回地址
.text:0001935D
.text:0001935D loc_1935D:
.text:0001935D mov edi, eax //EAX=返回地址
.text:0001935F lea esi, dword_1B680 //特征码2be1c1e9 也就是sub esp, ecx shr ecx, 2
查找确定KiFastCallEntryHOOK位置
.text:00019365 mov ecx, 5
.text:0001936A repe cmpsb
.text:0001936C push eax
.text:0001936D setz al
.text:00019370 test al, al
.text:00019372 pop eax
```
以下是根据提供的内容重构后的代码段:
.text:00019373 jnz short loc_19381
.text:00019375 dec eax
.text:00019376 push edx
.text:00019377 sub edx, eax
.text:00019379 cmp edx, 64h
.text:0001937C pop edx
.text:0001937D ja short loc_193B0
.text:0001937F jmp short loc_1935D
.text:00019381 add eax, 5 //确定HOOK后返回地址
.text:00019384 mov addbp4addr5, eax //保存
.text:00019389 mov edx, P
.text:0001938F sub edx, eax
.text:00019391 lea eax, byte_1B688
.text:00019397 mov [eax+1], edx
.text:0001939A mov edi, addbp4addr5
.text:000193A0 sub edi, 5
重构后的代码如下:
.text:000193A3 lea esi, byte_1B688
.text:000193A9 mov ecx, 5
.text:000193AE rep movsb //这一段都知道的,HOOK
.text:000193B0
.text:000193B0 loc_193B0:
.text:000193B0 mov eax, [ebp+arg_0]
.text:000193B3 mov cr0, eax
.text:000193B6 sti
.text:000193B7 pop edi
.text:000193B8 pop esi
.text:000193B9 pop edx
.text:000193BA pop ecx
.text:000193BB pop eax
.text:000193BC mov dl, bl ; NewIrql
.text:000193BE lea ecx, [ebp+SpinLock] ; SpinLock
.text:000193C1 call ds:KfReleaseSpinLock
.text:000193C7
.text:000193C7 loc_193C7:
.text:000193C7 xor eax, eax
.text:000193C9 jmp short loc_193DF
.text:000193CB
.text:000193CB
.text:000193CB loc_193CB:
```c
#include
#include
HANDLE hand = (HANDLE)0x288C58F1; // 这里的几个全局变量大家自己定义,这里特别标出这个主要是考虑要判断
NTSTATUS NewZwSetEvent(HANDLE EventHandle, PLONG PreviousState)
{
NTSTATUS status;
char *p, *ps;
char code[5] = {0xe9, 0, 0, 0, 0};
char code1[5] = {0xe9, 0, 0, 0, 0};
ULONG addr, addr1, addr2;
KIRQL Irql;
p = code;
ps = code1;
addr = (ULONG_PTR)hand + 0x193CB; // 这里需要自己计算具体的地址
*(PULONG_PTR)(p) = (ULONG_PTR)ps + sizeof(code);
p += sizeof(ULONG_PTR);
*(PULONG_PTR)(p) = addr;
p += sizeof(ULONG_PTR);
*(PULONG_PTR)(p) = (ULONG_PTR)PreviousState + sizeof(PLONG);
p += sizeof(ULONG_PTR);
*(PULONG_PTR)(p) = &Irql;
p += sizeof(ULONG_PTR);
*(PULONG_PTR)(p) = (ULONG_PTR)ZwSetEventaddr + sizeof(void *); //这里是确定不是自己调用就调用原服务
p += sizeof(ULONG_PTR);
*(PULONG_PTR)(p) = (ULONG_PTR)&status;
}
```
以下是根据您提供的内容重构的代码:
```assembly
if (EventHandle != hand || ExGetPreviousMode() == UserMode) //判断一下是不是自己调用
{
status = OldZwSetEventEventHandle, PreviousState;
}
else
{
_asm
{
mov eax, [ebp + 4]
mov addrss, eax
}
DbgPrint("addrs is %08X
", addrss);
p = (ULONG)addrss;
ps = (char*)ExAllocatePoolWithTag(0, 5, 0); //这里判断一下哈,免得失败了 本人很懒
while (1)
{
if(*p == 0x2b) //这里大家多判断几个特征,本人很懒
{
hookaddrs = (ULONG)p; //HOOK地址
Ret = (ULONG)p + 5; //返回地址
break;
}
p--;
}
DbgPrint("hookaddrs is %08X
", hookaddrs);
addr1 = (ULONG)ps;
addr = (ULONG)addr1 - hookaddrs - 5; //1个跳转
addr2 = (ULONG)MyHook1 - addr1 - 5; //2个跳转
*(ULONG*)(code + 1) = addr;
*(ULONG*)(code1 + 1) = addr2;
WPOFF();
Irql = KeRaiseIrqlToDpcLevel();
RtlCopyMemory(hookaddrs, code, 5);
RtlCopyMemory(addr1, code1, 5);
KeLowerIrql(Irql);
WPON();
status = STATUS_SUCCESS;
}
return status;
}
```
第4步是查看过滤函数。以下是过滤函数的代码:
```assembly
pushf //保护现场标志寄存器清零
pusha //数据栈指针寄存器清零
push edi //SSDTADDR寄存器清零
push ebx //服务函数地址寄存器清零
push eax //SSDTINDEX寄存器清零
call sub_191AC //调用过滤函数
mov [esp + 10h], eax //将结果压入堆栈顶(esp+10h)处
popa //清除数据栈指针寄存器中的内容,并弹出数据栈顶部的数据到eax、ebx、edi和esi中,恢复现场标志寄存器的值(因为在调用之前已经清零)
popf //清除现场标志寄存器中的信息,恢复现场标志寄存器的值(因为在调用之前已经清零)
sub esp, ecx //减少栈指针,为下一组指令腾出空间(ecx为5字节即5*4即为20字节的空间需求)
shr ecx, 2 //右移2位得到10字节的空间需求(因为每条跳转指令占用3字节空间,所以需要10条跳转指令的空间)
push addbp4addr5 //将计算出的跳转地址压入堆栈顶(esp+40h,即esp+28h+3*4)处,准备跳转执行过滤后的服务函数地址(MyHook1)处的代码段
retn //程序从过滤函数返回执行被过滤后的服务函数地址处的代码段
好的,我会尽力帮你完成这篇文章。你可以先提供一些关于你需要重构的文章的信息吗?例如,你需要重构哪些部分?你希望在文章中包含哪些内容?这些信息将有助于我更好地理解你的需求并为你提供帮助。