首页
社区
课程
招聘
1
数据代码隔离绕过crc
发表于: 2019-11-9 14:36 6783

数据代码隔离绕过crc

2019-11-9 14:36
6783

转发来自原文

随着Intel Nehalem引入sTLB,TLB隔离(曾经是一种可靠的技术)已成为历史。那些不得不HOOK用户模式的人开始研究虚拟机管理程序。特别是EPT违规。但是,实现虚拟机监控程序意味着实现庞大的,依赖于平台的代码,这并不是您尝试发布软件时的最佳方法-尤其是在您试图隐身的情况下,因为虚拟化很容易检测并且很难隐藏。


这就是分段发挥作用的地方。尽管我们长期以来一直使用CS == SS == DS模型,但自1978年以来,分段一直处于无效状态,但是可以起作用。CS的值指示如何执行指令,DS的值指示如何读取内存。实际上,我们希望从TLB拆分中获得什么。


尽管我们必须禁用Patchguard才能使用此技术(相对简单),但该技术将使我们做很多有趣的事情,例如欺骗返回指针,挂钩函数,而无需更改.text和大量的call指令。


 我们将必须钩住一堆内核函数并创建其他的段。但是在此之前,让我们先讨论一下其基本工作方式:


这项技术基本上是通过创建镜像模块(而不是原始模块)来工作的。我们分配一个与原始模块大小相等的内存,然后按1:1复制其内容。尽管代码数据位于不同的内存地址中,但由于代码引用的IP不会有所不同,因此我们无需进行任何重定位。然后,我们将克隆原始CS的GDT条目(无论是0x23还是0x33),并将GDT的Base设置为新分配的内存减去原始模块基地址(进程内各种dll模块 每个模块都有自己的数据段 代码段 我们镜像了整个dll  镜像dll的数据段减去原始dll的数据段的值正好是镜像dll和原始dll的基地址偏移),这很简单:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
typedefstruct _KGDTENTRY
 
{
 
uint8_t Limit0;
 
uint8_t Limit1;
 
uint8_t Base0;
 
uint8_t Base1;
 
uint8_t Base2;
 
uint8_t Access;
 
uint8_t Limit2 : 4;
 
uint8_t Unk : 1;
 
uint8_t L : 1;
 
uint8_t Db : 1;
 
uint8_t Granularity : 1;
 
uint8_t Base3;
 
} KGDTENTRY;
 
typedefstruct _SET_ENTRY_DPC_ARGS
 
{
 
uint16_t EntryId;
 
uint64_t Entry;
 
NTSTATUS Status;
 
uint64_t Error_Trgt;
 
uint64_t Error_Base;
 
uint64_t Error_Lmt;
 
} SET_ENTRY_DPC_ARGS;
 
staticvoidGdt_SetEntryDpc( KDPC *Dpc, SET_ENTRY_DPC_ARGS* Args, PVOID SystemArgument1, PVOID SystemArgument2 )
 
{
 
uint64_t Backup = DisableWP();
 
GDTR Gdtr;
 
_sgdt( &Gdtr );
 
uint64_t* Limit = Gdtr.Base + Gdtr.Limit + 1 - 8;
 
uint64_t* Target = Gdtr.Base + Args->EntryId * 8;
 
if( Target > Limit )
 
{
 
Args->Error_Trgt = Target;
 
Args->Error_Base = Gdtr.Base;
 
Args->Error_Lmt = Gdtr.Limit;
 
Args->Status = GDT_SEG_NOT_PRES;
 
Log("Target (%x) > Limit (%x) [%d]\n", Target, Limit, KeGetCurrentProcessorNumber());
 
}
 
else
 
{
 
*Target = Args->Entry;
 
Log("Target (%x) <= Limit (%x) [%d]\n", Target, Limit, KeGetCurrentProcessorNumber());
 
}
 
KeSignalCallDpcSynchronize( SystemArgument2 );
 
ResetWP( Backup );
 
KeSignalCallDpcDone( SystemArgument1 );
 
}
 
static NTSTATUS Gdt_SetEntry( uint16_t EntryId, uint64_t Entry )
 
{
 
static SET_ENTRY_DPC_ARGS Args;
 
Args.EntryId = EntryId;
 
Args.Entry = Entry;
 
Args.Status = STATUS_SUCCESS;
 
KeGenericCallDpc( Gdt_SetEntryDpc, &Args );
 
return Args.Status;
 
}
 
static NTSTATUS Gdt_SetupSeg( uint32_t Seg, uint8_t Wow64, uint32_t Base, uint32_t Limit )
 
{
 
BOOLEAN Granularity = Limit > 0xFFFFF;
 
if( Granularity )
 
Limit /= 0x1000; // 4 kb
 
if( Limit > 0xFFFFF)
 
return GDT_LIM_TOO_BIG;
 
uint64_t SegBaseVal = Wow64 ? Gdt_GetEntry(GDT_ENTRY(0x23)) : Gdt_GetEntry(GDT_ENTRY(0x33));
 
KGDTENTRY* SegBase = &SegBaseVal;
 
SegBase->Base0 = ( Base >> 8 * 0) & 0xFF;
 
SegBase->Base1 = ( Base >> 8 * 1) & 0xFF;
 
SegBase->Base2 = ( Base >> 8 * 2) & 0xFF;
 
SegBase->Base3 = ( Base >> 8 * 3) & 0xFF;
 
SegBase->Limit0 = ( Limit >> 8 * 0) & 0xFF;
 
SegBase->Limit1 = ( Limit >> 8 * 1) & 0xFF;
 
SegBase->Limit2 = ( Limit >> 8 * 2) & 0xF;
 
SegBase->Granularity = Granularity;
 
returnGdt_SetEntry(GDT_ENTRY( Seg ), SegBaseVal );
 
}

只需复制原始段的值,相应地设置Base,Limit和granularity ,然后创建一个DPC以使用sgdt获取每个处理器的GDT 基地址 ,然后写入指定的索引。 (您可能会注意到我没有分配新的GDT,这是因为在替换GDT指针后,我的用户很少会遇到奇怪的系统冻结)


现在我们已经成功设置了新的段,这是我们遇到的第一个问题,即IP范围之外的问题。


让我们看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Module base (0x400000) /---------------------\ /---------------------\ (CS Base + 0x400000) 我们的镜像模块基地址
 
| ... | | ... |
 
| CALL 0x600214 | | CALL 0x600214 |
 
| MOV [0x312321], EAX | | MOV [0x312321], EAX |
 
| CALL 0x333333 | | CALL 0x333333 |
 
| ... | | ... |
 
| ... | | ... |
 
| ... | | ... |
 
| ... | | ... |
 
Module end (0x500000) \---------------------/ \---------------------/ (CS Base + 0x500000) 镜像模块结束地址


当该目标IP比模块基地址小(低级CALL 0x333333 ),或者当目标IP比模块基座更高(CALL 0x600214 ),这回产生问题,因为他们将分别执行CS 基地址+ 0x333333和  CS 基地址+ 0x600214,不在我们的镜像模块范围 可能是其它模块。


这第一个问题很容易处理。只需将GDT条目的Limit设置为模块大小,并在发生GPF(页面异常)时恢复CS为原始的cs  并设置IP = IP + Shadow Base-Real Base(此时ip指向镜像模块当前执行的地方 继续执行  并冒着泄漏shadow base的风险,因为需要push指向镜像模块的返回指针到堆栈,然后回到0x23:镜像模块处继续执行 执行完毕返回的时候 会回到镜像模块继续执行  执行的时候 一旦遇到call jmp等 目标地址不在镜像段范围内的  又会异常 此时我们恢复cs为镜像cs 继续在镜像原始地址下一条执行),或者自己解决调用,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
static BOOLEAN ResolveCall( ITRAP_FRAME* Frame, UCHAR* Instruction, uint32_t* Target, uint8_t* InstructionSize )
 
{
 
if( Instruction[0] != 0xE8 && Instruction[0] != 0xFF)
 
return FALSE;
 
hde32s s = {0};
 
*InstructionSize = hde32_disasm( Instruction, &s );
 
if( Instruction[0] == 0xFF && s.modrm_reg == 2)
 
{
 
if( s.sib)
 
{
 
if( s.modrm_mod == 0)
 
*Target = *( uint32_t* )(ResolveRegisterById( Frame, s.sib_index) * (1 << s.sib_scale) + s.disp.disp32);
 
elseif( s.modrm_mod == 1)
 
*Target = *( uint32_t* )(ResolveRegisterById( Frame, s.sib_base) + s.disp.disp32);
 
else
 
*Target = *( uint32_t* )(ResolveRegisterById( Frame, s.sib_base) + ResolveRegisterById( Frame, s.sib_index) * (1 << s.sib_scale) + s.disp.disp32);
 
}
 
else
 
{
 
if( s.modrm_mod == 0)
 
*Target = *( uint32_t* )( s.disp.disp32);
 
elseif( s.modrm_mod == 3)
 
*Target = ResolveRegisterById( Frame, s.modrm_rm);
 
elseif( s.modrm_mod == 2 || s.modrm_mod == 1)
 
*Target = *( uint32_t* )(ResolveRegisterById( Frame, s.modrm_rm) + s.disp.disp32);
 
}
 
return TRUE;
 
}
 
elseif( Instruction[0] == 0xE8)
 
{
 
*Target = Frame->Rip + s.imm.imm32 + 5;
 
return TRUE;
 
}
 
return FALSE;
 
}
 
hook处理GPF页面异常的中断
 
static BOOLEAN NTAPI HkOnGpf( ITRAP_FRAME* TrapFrame )
 
{
 
if( TrapFrame->Cs == SHADOW_HOOK_SEG )
 
{
 
// CALL | JMP | RET 目标地址不在本段范围内?
 
__swapgs();//切换gs到内核环境
 
SHADOW_MODULE_ENTRY Sme = GetShadowModuleFromRip(PsGetCurrentProcessId(), TrapFrame->Rip );//通过异常ip获取在镜像段的相关信息结构
 
if( Sme.ModuleReal)//原始模块基地址
 
{
 
uint64_t RspBackup = TrapFrame->Rsp;//异常时候的堆栈
 
_enable();
 
//Log( "Handling call to the outside of shadow module @ %llx\n", TrapFrame->Rip );
 
__try
 
{
 
uint32_t Destination = 0;
 
uint8_t InstructionSize = 0;
 
//解析 获取要跳转的目标地址
 
if(ResolveCall( TrapFrame, TrapFrame->Rip - Sme.ModuleReal + Sme.ModuleShadow, &Destination, &InstructionSize ))
 
{
 
uint32_t IsPageMapped = FALSE;
 
KIRQL Irql = RsAcquireSpinLockRaiseToDpc( &Rs_ProcessRecordSpinLock );
 
DWORD Pid = PsGetCurrentProcessId();
 
if( Rs_ProcessRecordsMaxPid > Pid && Rs_ProcessRecords[ Pid ])
 
{
 
for(int i = 0; i < ARRAYSIZE( Rs_ProcessRecords[ Pid ]->SpoofedProtect ); i++ )
 
{
 
if( !Rs_ProcessRecords[ Pid ]->SpoofedProtect[ i ].PageBase)
 
break;
 
if(( Rs_ProcessRecords[ Pid ]->SpoofedProtect[ i ].PageBase & ( ~0xFFF)) == ( TrapFrame->Rip & ( ~0xFFF)))
 
{
 
IsPageMapped = TRUE;//已经存在镜像
 
break;
 
}
 
}
 
}
 
RsReleaseSpinLock( &Rs_ProcessRecordSpinLock, Irql );
 
TrapFrame->Rsp -= 0x4;
 
*( uint32_t* ) TrapFrame->Rsp =
 
IsPageMapped
 
? ( TrapFrame->Rip + InstructionSize - Sme.ModuleReal + Sme.ModuleShadow)//如果目标内存已存在镜像 直接堆栈塞入在镜像的 指令返回地址 比如第一个CALL 0x600214
 
: ( TrapFrame->Rip + InstructionSize );//否则塞入原始模块的地址 指令返回地址
 
TrapFrame->Rip = Destination;//塞入目标地址
 
TrapFrame->Cs = WOW64_SEG;//指向原始的cs
 
//Log( " --> %llx (%d)\n", TrapFrame->Rip, IsPageMapped );
 
_disable();
 
__swapgs();
 
return TRUE;
 
}
 
}
 
__except(1)
 
{
 
}
 
_disable();
 
__swapgs();
 
TrapFrame->Rsp = RspBackup;//解析目标地址失败 可能是数据的访问 就直接恢复堆栈 比如第二个MOV [0x312321], EAX
 
TrapFrame->Rip -= Sme.ModuleReal;//#1但是第三条指令是 CALL 0x333333 那么执行完第二条后 因为用的cs是原始的 原始limit是整个进程范围 那么0x333333也可以执行 而不是镜像的 关键call的目标地址和下一条指令地址+偏移有关系 这里的0x333333只是打比喻 真实情况下 镜像的0x333333代表一个只有镜像才存在的目标指令地址 但是当cs是原始cs的时候 这个0x333333可能是另外一个地址 那里没有指令 可能全是nop 或者c3 导致直接异常
 
TrapFrame->Rip += Sme.ModuleShadow;//设置镜像模块的地址
 
TrapFrame->Cs = WOW64_SEG;//原始模块的cs
 
return TRUE;
 
}
 
else
 
{
 
// 从原始模块执行完毕 返回到镜像模块 发生异常的时候 切换cs到镜像cs
 
}
 
__swapgs();
 
}
 
return FALSE;
 
}

现在,我们有了第二个问题: 第三个 CALL 0x333333 。这个问题是,无论我们做什么,这都是一个完全有效的操作,因为从技术上讲,它是在CS边界内(作为模块基数= 0x0),而不是在镜像模块边界;因此处理程序不会在这里帮助我们。参考##1


为了解决这个问题,我们可以像下面这样钩住PsMapSystemDlls ,在加载目标所需的DLL之前,只需在模块下边界下方保留虚拟内存  :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
static NTSTATUS NTAPI HkPsMapSystemDlls( PEPROCESS Process, BOOLEAN UseLargePages )
 
{
 
USING_SYMBOL( ZwAllocateVirtualMemory );
 
fnNtAllocateVirtualMemory ZwAllocateVirtualMemory = GET_SYMBOL( ZwAllocateVirtualMemory );
 
KAPC_STATE Apc;
 
KeStackAttachProcess( Process, &Apc );
 
MEMORY_BASIC_INFORMATION Mbi = {0};
 
while(1)
 
{
 
NTSTATUS Status = VvQueryVirtualMemory
 
(
 
NtCurrentProcess(),
 
PUCHAR ) Mbi.BaseAddress + Mbi.RegionSize,
 
MemoryBasicInformation,
 
&Mbi,
 
sizeof( Mbi ),
 
0
 
);
 
if( Status )
 
{
 
break;
 
}
 
else
 
{
 
struct
 
{
 
UNICODE_STRING Str;
 
wchar_t Buffer[1024];
 
} Buffer;
 
RtlZeroMemory( &Buffer, sizeof( Buffer ));
 
VvQueryVirtualMemory(NtCurrentProcess(), Mbi.BaseAddress, 2ull, &Buffer, sizeof( Buffer ), 0ull );
 
if( Buffer.Str.Buffer)
 
{
 
if(wcsstr( Buffer.Str.Buffer, L"<process name for simplicity>"))
 
{
 
Log("<process name> found @ %llx [0x%x bytes] (%ls)\n", Mbi.BaseAddress, Mbi.RegionSize, Buffer.Str.Buffer);
 
Log("Wasting %d MB...!\n", (( uint64_t ) Mbi.BaseAddress) / 1024 / 1024);
 
forPUCHAR Page = 0x10000; Page < ((PUCHAR)Mbi.BaseAddress - 0x20000); Page += 0x1000)
 
{
 
PVOID Base = Page;
 
SIZE_T Size = ( uint64_t ) Mbi.BaseAddress - ( uint64_t ) Page - 0x20000;
 
if(ZwAllocateVirtualMemory(NtCurrentProcess(), &Base, 0ull, &Size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE ) == 0)
 
{//在目标模块附近从0x10000一直分配到目标模块基地址处 实在想不通 搞这干啥 因为我们镜像模块 需要这快内存作为跳板?也许吧 发生异常
 
//他会吧目标指令写入到这里 恢复执行 作为跳板 跳到原始的地址去执行 总之作者文章写的模模糊糊 也没留代码 只能遐想了 估计是怕大手子
 
//拿去抄 所以只给思路与poc 剩下自己去实现 去调试
 
Log("Allocated %llx -> %llx\n", Page, Page + Size );
 
break;
 
}
 
}
 
break;
 
}
 
}
 
}
 
}
 
KeUnstackDetachProcess( &Apc );
 
return(( fnPsMapSystemDlls ) PsMapSystemDllsHook->OriginalBytes )( Process, UseLargePages );
 
}


在此阶段没有设置EPROCESS,并且我们无法打开该进程的句柄,因此我们必须使用KeStackAttachProcess和NtCurrentProcess()。只需使用NtQueryVirtualMemory和MemorySectionName搜索进程的目标库,然后尝试在其下分配内存。


瞧,问题解决了!尽管这听起来像是我们将浪费大量内存,但我还没有看到Windows将30MB以上内存映射到进程。


现在我们已经设置了镜像模块,剩下的就是在需要时将控制流重定向到镜像模块,这可以通过在原始页面上设置xd标志(页面不可执行 或调用NtProtectVirtualMemory并从NtQueryVirtualMemory欺骗保护)来实现。 。我们将需要自己(在执行时)处理分页的内存,但是除此之外,我们的页面错误挂钩将相对简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
static BOOLEAN NTAPI HkOnPageFault( ITRAP_FRAME* TrapFrame )
 
{
 
uint64_t FaultyPtr = __readcr2();
 
if( TrapFrame->Cs == WOW64_SEG ) // 原始cs
 
{
 
if( TrapFrame->ExceptionCode & (1 << 4) && // Caused by instruction fetch
 
TrapFrame->ExceptionCode & (1 << 0)) // Page is present
 
{
 
if( TrapFrame->Rip == FaultyPtr )//发生不可执行异常
 
{
 
__swapgs();
 
SHADOW_MODULE_ENTRY Sme = GetShadowModuleFromRip(PsGetCurrentProcessId(), TrapFrame->Rip );//获取原始ip对应的镜像ip等信息
 
//所有原始指令执行流程都劫持到镜像去
 
if( Sme.ModuleReal)
 
{
 
Log("Handling call to remapped page of module @ %llx\n", TrapFrame->Rip );
 
TrapFrame->Cs = SHADOW_HOOK_SEG;// 切换到镜像cs
 
__swapgs();
 
return TRUE;
 
}
 
__swapgs();
 
}
 
elseif( FaultyPtr - TrapFrame->Rip < 15)
 
{
 
//#2
 
__swapgs();
 
SHADOW_MODULE_ENTRY Sme = GetShadowModuleFromRip(PsGetCurrentProcessId(), TrapFrame->Rip );
 
if( Sme.ModuleReal)
 
{
 
Log("Fixed half instruction failure! %llx %llx\n", FaultyPtr, TrapFrame->Rip );
 
PUCHAR From = TrapFrame->Rip;
 
PUCHAR To = TrapFrame->Rip - Sme.ModuleReal + Sme.ModuleShadow;//在镜像的地址 之前我们已经在目标模块下面分配了一大块内存 用来做跳板
 
SIZE_T Size = FaultyPtr - TrapFrame->Rip;
 
_enable();//欢迎加入miao1yan.top群号835875625和755836982
 
memcpy( To, From, Size );//把原始目标rip的指令拷贝到跳板 跳板
 
_disable();
 
TrapFrame->Cs = SHADOW_HOOK_SEG;//切换cs到镜像cs 继续执行
 
__swapgs();
 
return TRUE;
 
}
 
__swapgs();
 
}
 
}
 
}
 
elseif( TrapFrame->Cs == SHADOW_HOOK_SEG )//如果是镜像cs执行时候发生异常
 
{
 
if( TrapFrame->ExceptionCode & (1 << 4) && // Caused by instruction fetch
 
!( TrapFrame->ExceptionCode & (1 << 0))) // Page is not present
 
{
 
// Page is not present
 
__swapgs();
 
_enable();
 
SHADOW_MODULE_ENTRY Sme = GetShadowModuleFromRip(PsGetCurrentProcessId(), FaultyPtr );//获取镜像rip相关信息
 
if( Sme.ModuleReal)//如果发生异常地址在镜像模块内
 
{
 
//Log( "Handling paged out memory (%llx)\n", FaultyPtr );
 
PUCHAR FaultyAdr = FaultyPtr - Sme.ModuleReal + Sme.ModuleShadow;
 
__try
 
{
 
volatile uint64_t volatile PageIn[1];
 
memcpy( PageIn, (volatile UCHAR volatile * ) FaultyAdr, 8);//换页了?那就尝试访问一下 刷新TLB 下回直接就访问TLB了
 
}
 
__except(1)
 
{
 
}
 
_disable();
 
__swapgs();
 
return TRUE;
 
}
 
_disable();
 
__swapgs();
 
return FALSE;
 
}
 
else
 
{
 
//目标地址 不在本模块GDT limit范围内 因为镜像cs基地址是之上的范围不能访问 一旦call访问那里 就会异常
 
//这里作者流了很多坑 也没有写清楚 我估计是 镜像的一些超出范围的指令被他 写成了jmp 23:跳板 因为跳板
 
//处的指令还没有 所以会发生异常 进入上面的分支处理了 见#2
 
TrapFrame->Cs = WOW64_SEG; // Windows doesnt handle it otherwise...
 
}
 
}
 
return FALSE;
 
}


现在,您可以使用jmp 0x23 :Trampoline HOOK 镜像模块, 并使用jmp 0xAA :RetPtr 返回, 或者根据需要更改指令。除了您需要使原始页面不执行之外,没有其他区别。




[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2019-11-9 14:37 被ZwCopyAll编辑 ,原因:
收藏
免费 1
支持
分享
赞赏记录
参与人
雪币
留言
时间
AperOdry
为你点赞~
2022-5-22 17:57
最新回复 (0)
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册