前置条件:关闭KVAS
注入
一个基本事实:在进程的地址空间中其实是映射了内核空间的,通过页表的User位确定用户态是否可访问,来实现用户态和内核态的隔离;题外话,Meltdown漏洞通过测信道的方式破除了这一隔离,因此后续引入了KVAS(或称KPTI)机制彻底隔离用户态和内核态。
绕过CoW机制实现全局Hook && Inject;或者称高位注入?
对于kernel32.dll 、ntdll.dll等进程必须加载的DLL,他们不必要在每个进程中都复制一遍,而是同一块物理地址映射到了不同的进程上下文中。
这将导致如果直接修改这块物理地址的话,将作用于全部进程。
利用这一绕过,随便hookkernel32.dll中一个常用的函数,比如TslGetValue或者CreateFileW之类的都可以,就成功劫持线程控制流了。
抠过来can1357的代码,他用了一个漏驱
1 TlsLockedHookController* TlsHookController = Mp_MapDllAndCreateHookEntry( DllPath, _TlsGetValue, Target, !Flags[ "noloadlib" ], [ & ] ( SIZE_T Size )
2
3 {
4
5 //return VirtualAlloc( 0, Size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE );
6
7 PVOID Memory = AllocateKernelMemory( CpCtx, KrCtx, Size );
8
9 ExposeKernelMemoryToProcess( Controller, Memory, Size, Controller.CurrentEProcess );
10
11 ZeroMemory( Memory, Size );
12
13 UsedRegions.push_back( { Memory, Size } );
14
15 return Memory;
16
17 } );
其中Mp_MapDllAndCreateHookEntry的是一个常见的解析PE结构反射注入DLL的流程,不过他在汇编层面处理了线程安全的问题,值得看看:
1 std::vector<BYTE> Prologue =
2
3 {
4
5 0x00, 0x00, // data
6
7 0xF0, 0xFE, 0x05, 0xF8, 0xFF, 0xFF, 0xFF, // lock inc byte ptr [rip-n]
8
9 // wait_lock:
10
11 0x80, 0x3D, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, // cmp byte ptr [rip-m], 0x0
12
13 0xF3, 0x90, // pause
14
15 0x74, 0xF5, // je wait_lock
16
17
18
19 0x48, 0xB8, 0xAA, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x00, 0x00, // mov rax, 0xAABBCCDDEEAA
20
21 // data_sync_lock:
22
23 0x0F, 0x0D, 0x08, // prefetchw [rax]
24
25 0x81, 0x38, 0xDD, 0xCC, 0xBB, 0xAA, // cmp dword ptr[rax], 0xAABBCCDD
26
27 0xF3, 0x90, // pause
28
29 0x75, 0xF3, // jne data_sync_lock
30
31
32
33 0xF0, 0xFE, 0x0D, 0xCF, 0xFF, 0xFF, 0xFF, // lock dec byte ptr [rip-n]
34
35 0x75, 0x41, // jnz continue_exec
这一注入比较核心的步骤是ExposeKernelMemoryToProcess,他将内核地址的内存暴露给了应用态:
1BOOL ExposeKernelMemoryToProcess( MemoryController& Mc, PVOID Memory, SIZE_T Size, uint64_t EProcess )
2
3{
4
5 Mc.AttachTo( EProcess );
6
7
8
9 BOOL Success = FALSE;
10
11
12
13 Mc.IterPhysRegion( Memory, Size, [ & ] ( PVOID Va, uint64_t Pa, SIZE_T Sz )
14
15 {
16
17 auto Info = Mc.QueryPageTableInfo( Va );
18
19
20
21 Info.Pml4e->user = TRUE;
22
23 Info.Pdpte->user = TRUE;
24
25 Info.Pde->user = TRUE;
26
27
28
29 if ( !Info.Pde || ( Info.Pte && ( !Info.Pte->present ) ) )
30
31 {
32
33 Success = FALSE;
34
35 }
36
37 else
38
39 {
40
41 if ( Info.Pte )
42
43 Info.Pte->user = TRUE;
44
45 }
46
47 } );
48
49
50
51 Mc.Detach();
52
53
54
55 return Success;
56
57}
这就是所谓的“高位注入”的地方,即在用户态执行0环的代码;绕过反作弊对自身可疑内存的搜索(寻常会反射注入到游戏自身,然后暴露出可执行内存、脏堆栈等等检测向量)
检测向量
检测Hook
当然前提是游戏本身没Hook这些东西,否则检测也就没意义了(自己检测自己)。
绕过这个很简单,找一个稍微冷门但是在游戏启动过程中被调用的API即可