首页
社区
课程
招聘
[原创]EFER hook
2022-11-4 22:17 28115

[原创]EFER hook

2022-11-4 22:17
28115

最近在看HyperDbg这个项目,看到了一种基于VT的syscall挂钩方法,记录一下。EFER(Extended Feature Enable Register)是MSR寄存器的一种,相关功能可以在intel白皮书卷3A,第2.2.1节中找到。

EFER寄存器的SCE位用来控制对SYSCALL/SYSRET的支持。
在卷2B,第4.3节可以找到SYSCALL指令的操作

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
IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
  THEN #UD;
FI;
RCX ← RIP; (* Will contain address of next instruction *)
RIP ← IA32_LSTAR;
R11 ← RFLAGS;
RFLAGS ← RFLAGS AND NOT(IA32_FMASK);
CS.Selector ← IA32_STAR[47:32] AND FFFCH (* Operating system provides CS; RPL forced to 0 *)
(* Set rest of CS to a fixed value *)
CS.Base ← 0; (* Flat segment *)
CS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
CS.Type 11; (* Execute/read code, accessed *)
CS.S ← 1;
CS.DPL ← 0;
CS.P ← 1;
CS.L ← 1; (* Entry is to 64-bit mode *)
CS.D ← 0; (* Required if CS.L = 1 *)
CS.G ← 1; (* 4-KByte granularity *)
CPL ← 0;
SS.Selector ← IA32_STAR[47:32] + 8; (* SS just above CS *)
(* Set rest of SS to a fixed value *)
SS.Base ← 0; (* Flat segment *)
SS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
SS.Type 3; (* Read/write data, accessed *)
SS.S ← 1;
SS.DPL ← 0;
SS.P ← 1;
SS.B ← 1; (* 32-bit stack segment *)
SS.G ← 1; (* 4-KByte granularity *)

可以看出当EFER寄存器的SCE位为0时会产生#UD异常,如果我们手动将SCE清零,那么就会导致#UD异常,再通过设置VT中对#UD异常的响应事件就可以拦截SYSCALL。
用代码模拟SYSCALL:

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
_Use_decl_annotations_
BOOLEAN
SyscallHookEmulateSYSCALL(PGUEST_REGS Regs)
{
    VMX_SEGMENT_SELECTOR Cs, Ss;
    UINT32               InstructionLength;
    UINT64               MsrValue;
    UINT64               GuestRip;
    UINT64               GuestRflags;
 
    //
    // Reading guest's RIP
    //
    __vmx_vmread(VMCS_GUEST_RIP, &GuestRip);
 
    //
    // Reading instruction length
    //
    __vmx_vmread(VMCS_VMEXIT_INSTRUCTION_LENGTH, &InstructionLength);
 
    //
    // Reading guest's Rflags
    //
    __vmx_vmread(VMCS_GUEST_RFLAGS, &GuestRflags);
 
    //
    // Save the address of the instruction following SYSCALL into RCX and then
    // load RIP from IA32_LSTAR.
    //
    MsrValue  = __readmsr(IA32_LSTAR);
    Regs->rcx = GuestRip + InstructionLength;
    GuestRip  = MsrValue;
    __vmx_vmwrite(VMCS_GUEST_RIP, GuestRip);
 
    //
    // Save RFLAGS into R11 and then mask RFLAGS using IA32_FMASK
    //
    MsrValue  = __readmsr(IA32_FMASK);
    Regs->r11 = GuestRflags;
    GuestRflags &= ~(MsrValue | X86_FLAGS_RF);
    __vmx_vmwrite(VMCS_GUEST_RFLAGS, GuestRflags);
 
    //
    // Load the CS and SS selectors with values derived from bits 47:32 of IA32_STAR
    //
    MsrValue             = __readmsr(IA32_STAR);
    Cs.Selector          = (UINT16)((MsrValue >> 32) & ~3); // STAR[47:32] & ~RPL3
    Cs.Base              = 0;                               // flat segment
    Cs.Limit             = (UINT32)~0;                      // 4GB limit
    Cs.Attributes.AsUInt = 0xA09B;                          // L+DB+P+S+DPL0+Code
    SetGuestCs(&Cs);
 
    Ss.Selector          = (UINT16)(((MsrValue >> 32) & ~3) + 8); // STAR[47:32] + 8
    Ss.Base              = 0;                                     // flat segment
    Ss.Limit             = (UINT32)~0;                            // 4GB limit
    Ss.Attributes.AsUInt = 0xC093;                                // G+DB+P+S+DPL0+Data
    SetGuestSs(&Ss);
 
    return TRUE;
}

看下SYSRET的操作,可以看出也是SCE位置0时抛出#UD异常

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
IF (CS.L ≠ 1 ) or (IA32_EFER.LMA ≠ 1) or (IA32_EFER.SCE ≠ 1)
(* Not in 64-Bit Mode or SYSCALL/SYSRET not enabled in IA32_EFER *)
  THEN #UD; FI;
IF (CPL ≠ 0) OR (RCX is not canonical) THEN #GP(0); FI;
IF (operand size is 64-bit)
  THEN (* Return to 64-Bit Mode *)
    RIP ← RCX;
  ELSE (* Return to Compatibility Mode *)
    RIP ← ECX;
FI;
RFLAGS ← (R11 & 3C7FD7H) | 2; (* Clear RF, VM, reserved bits; set bit 2 *)
IF (operand size is 64-bit)
  THEN CS.Selector ← IA32_STAR[63:48]+16;
  ELSE CS.Selector ← IA32_STAR[63:48];
FI;
CS.Selector ← CS.Selector OR 3; (* RPL forced to 3 *)
(* Set rest of CS to a fixed value *)
CS.Base ← 0; (* Flat segment *)
CS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
CS.Type 11; (* Execute/read code, accessed *)
CS.S ← 1;
CS.DPL ← 3;
CS.P ← 1;
IF (operand size is 64-bit)
  THEN (* Return to 64-Bit Mode *)
    CS.L ← 1; (* 64-bit code segment *)
    CS.D ← 0; (* Required if CS.L = 1 *)
  ELSE (* Return to Compatibility Mode *)
    CS.L ← 0; (* Compatibility mode *)
    CS.D ← 1; (* 32-bit code segment *)
FI;
CS.G ← 1; (* 4-KByte granularity *)
CPL ← 3;
SS.Selector ← (IA32_STAR[63:48]+8) OR 3; (* RPL forced to 3 *)
(* Set rest of SS to a fixed value *)
SS.Base ← 0; (* Flat segment *)
SS.Limit ← FFFFFH; (* With 4-KByte granularity, implies a 4-GByte limit *)
SS.Type 3; (* Read/write data, accessed *)
SS.S ← 1;
SS.DPL ← 3;
SS.P ← 1;
SS.B ← 1; (* 32-bit stack segment*)
SS.G ← 1; (* 4-KByte granularity *)

用代码模拟:

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
_Use_decl_annotations_
BOOLEAN
SyscallHookEmulateSYSRET(PGUEST_REGS Regs)
{
    VMX_SEGMENT_SELECTOR Cs, Ss;
    UINT64               MsrValue;
    UINT64               GuestRip;
    UINT64               GuestRflags;
 
    //
    // Load RIP from RCX
    //
    GuestRip = Regs->rcx;
    __vmx_vmwrite(VMCS_GUEST_RIP, GuestRip);
 
    //
    // Load RFLAGS from R11. Clear RF, VM, reserved bits
    //
    GuestRflags = (Regs->r11 & ~(X86_FLAGS_RF | X86_FLAGS_VM | X86_FLAGS_RESERVED_BITS)) | X86_FLAGS_FIXED;
    __vmx_vmwrite(VMCS_GUEST_RFLAGS, GuestRflags);
 
    //
    // SYSRET loads the CS and SS selectors with values derived from bits 63:48 of IA32_STAR
    //
    MsrValue             = __readmsr(IA32_STAR);
    Cs.Selector          = (UINT16)(((MsrValue >> 48) + 16) | 3); // (STAR[63:48]+16) | 3 (* RPL forced to 3 *)
    Cs.Base              = 0;                                     // Flat segment
    Cs.Limit             = (UINT32)~0;                            // 4GB limit
    Cs.Attributes.AsUInt = 0xA0FB;                                // L+DB+P+S+DPL3+Code
    SetGuestCs(&Cs);
 
    Ss.Selector          = (UINT16)(((MsrValue >> 48) + 8) | 3); // (STAR[63:48]+8) | 3 (* RPL forced to 3 *)
    Ss.Base              = 0;                                    // Flat segment
    Ss.Limit             = (UINT32)~0;                           // 4GB limit
    Ss.Attributes.AsUInt = 0xC0F3;                               // G+DB+P+S+DPL3+Data
    SetGuestSs(&Ss);
 
    return TRUE;
}

手动清除SCE位并设置拦截:

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
VOID
SyscallHookConfigureEFER(BOOLEAN EnableEFERSyscallHook)
{
    IA32_EFER_REGISTER      MsrValue;
    IA32_VMX_BASIC_REGISTER VmxBasicMsr     = {0};
    UINT32                  VmEntryControls = 0;
    UINT32                  VmExitControls  = 0;
 
    //
    // Reading IA32_VMX_BASIC_MSR
    //
    VmxBasicMsr.AsUInt = __readmsr(IA32_VMX_BASIC);
 
    //
    // Read previous VM-Entry and VM-Exit controls
    //
    __vmx_vmread(VMCS_CTRL_VMENTRY_CONTROLS, &VmEntryControls);
    __vmx_vmread(VMCS_CTRL_PRIMARY_VMEXIT_CONTROLS, &VmExitControls);
 
    MsrValue.AsUInt = __readmsr(IA32_EFER);
 
    if (EnableEFERSyscallHook)
    {
        MsrValue.SyscallEnable = FALSE;
 
        //
        // Set VM-Entry controls to load EFER
        //
        __vmx_vmwrite(VMCS_CTRL_VMENTRY_CONTROLS, HvAdjustControls(VmEntryControls | VM_ENTRY_LOAD_IA32_EFER, VmxBasicMsr.VmxControls ? IA32_VMX_TRUE_ENTRY_CTLS : IA32_VMX_ENTRY_CTLS));
 
        //
        // Set VM-Exit controls to save EFER
        //
        __vmx_vmwrite(VMCS_CTRL_PRIMARY_VMEXIT_CONTROLS, HvAdjustControls(VmExitControls | VM_EXIT_SAVE_IA32_EFER, VmxBasicMsr.VmxControls ? IA32_VMX_TRUE_EXIT_CTLS : IA32_VMX_EXIT_CTLS));
 
        //
        // Set the GUEST EFER to use this value as the EFER
        //
        __vmx_vmwrite(VMCS_GUEST_EFER, MsrValue.AsUInt);
 
        //
        // also, we have to set exception bitmap to cause vm-exit on #UDs
        //
        HvSetExceptionBitmap(EXCEPTION_VECTOR_UNDEFINED_OPCODE);
    }
    else
    {
        MsrValue.SyscallEnable = TRUE;
 
        //
        // Set VM-Entry controls to load EFER
        //
        __vmx_vmwrite(VMCS_CTRL_VMENTRY_CONTROLS, HvAdjustControls(VmEntryControls & ~VM_ENTRY_LOAD_IA32_EFER, VmxBasicMsr.VmxControls ? IA32_VMX_TRUE_ENTRY_CTLS : IA32_VMX_ENTRY_CTLS));
 
        //
        // Set VM-Exit controls to save EFER
        //
        __vmx_vmwrite(VMCS_CTRL_PRIMARY_VMEXIT_CONTROLS, HvAdjustControls(VmExitControls & ~VM_EXIT_SAVE_IA32_EFER, VmxBasicMsr.VmxControls ? IA32_VMX_TRUE_EXIT_CTLS : IA32_VMX_EXIT_CTLS));
 
        //
        // Set the GUEST EFER to use this value as the EFER
        //
        __vmx_vmwrite(VMCS_GUEST_EFER, MsrValue.AsUInt);
 
        //
        // Because we're not save or load EFER on vm-exits so
        // we have to set it manually
        //
        __writemsr(IA32_EFER, MsrValue.AsUInt);
 
        //
        // unset the exception to not cause vm-exit on #UDs
        //
        ProtectedHvRemoveUndefinedInstructionForDisablingSyscallSysretCommands();
    }
}

对#UD事件的响应:

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
_Use_decl_annotations_
BOOLEAN
SyscallHookHandleUD(PGUEST_REGS Regs, UINT32 CoreIndex)
{
    CR3_TYPE                GuestCr3;
    UINT64                  OriginalCr3;
    UINT64                  Rip;
    VIRTUAL_MACHINE_STATE * CurrentVmState = &g_GuestState[CoreIndex];
 
    //
    // Reading guest's RIP
    //
    __vmx_vmread(VMCS_GUEST_RIP, &Rip);
 
    if (g_IsUnsafeSyscallOrSysretHandling)
    {
        //
        // In some computers, we realized that safe accessing to memory
        // is problematic. It means that our syscall approach might not
        // working properly.
        // Based on our tests, we realized that system doesn't generate #UD
        // regularly. So, we can imagine that only kernel addresses are SYSRET
        // instruction and SYSCALL is on a user-mode RIP.
        // It's faster than our safe methods but if the system generates a #UD
        // then a BSOD will happen. But if the system is working regularly, then
        // no BSOD happens. For more information, see documentation at !syscall2
        // or !sysret2 commands
        //
        if (Rip & 0xff00000000000000)
        {
            goto EmulateSYSRET;
        }
        else
        {
            goto EmulateSYSCALL;
        }
    }
    else
    {
        //
        // Get the guest's running process's cr3
        //
        GuestCr3.Flags = GetRunningCr3OnTargetProcess().Flags;
 
        //
        // No, longer needs to be checked because we're sticking to system process
        // and we have to change the cr3
        //
        // if ((GuestCr3.Flags & PCID_MASK) != PCID_NONE)
 
        OriginalCr3 = __readcr3();
 
        __writecr3(GuestCr3.Flags);
 
        //
        // Read the memory
        //
        UCHAR InstructionBuffer[3] = {0};
 
        if (MemoryMapperCheckIfPageIsPresentByCr3(Rip, GuestCr3))
        {
            //
            // The page is safe to read (present)
            // It's not necessary to use MemoryMapperReadMemorySafeOnTargetProcess
            // because we already switched to the process's cr3
            //
            MemoryMapperReadMemorySafe(Rip, InstructionBuffer, 3);
        }
        else
        {
            //
            // The page is not present, we have to inject a #PF
            //
            CurrentVmState->IncrementRip = FALSE;
 
            //
            // For testing purpose
            //
            // LogInfo("#PF Injected");
 
            //
            // Inject #PF
            //
            EventInjectPageFault(Rip);
 
            //
            // We should not inject #UD
            //
            return FALSE;
        }
 
        __writecr3(OriginalCr3);
        //SYSCALL_INSTRUCTION
        if (InstructionBuffer[0] == 0x0F &&
            InstructionBuffer[1] == 0x05)
        {
            goto EmulateSYSCALL;
        }
        //SYSRET_INSTRUCTION
        if (InstructionBuffer[0] == 0x48 &&
            InstructionBuffer[1] == 0x0F &&
            InstructionBuffer[2] == 0x07)
        {
            goto EmulateSYSRET;
        }
 
        return FALSE;
    }
 
    //----------------------------------------------------------------------------------------
 
    //
    // Emulate SYSRET instruction
    //
EmulateSYSRET:
 
    //
    // Test
    //
    // LogInfo("SYSRET instruction => 0x%llX", Rip);
    //
 
    //
    // Perform the dispatching and the emulation of the SYSRET event
    //
    DispatchEventEferSysret(CoreIndex, Regs, Rip);
 
    return TRUE;
 
    //
    // Emulate SYSCALL instruction
    //
 
EmulateSYSCALL:
 
    //
    // Test
    //
    // LogInfo("SYSCALL instruction => 0x%llX , Process Id : 0x%x", Rip, PsGetCurrentProcessId());
    //
 
    //
    // Perform the dispatching and the emulation of the SYSCAKK event
    //
    DispatchEventEferSyscall(CoreIndex, Regs, Rip);
 
    return TRUE;
}

[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2022-11-4 22:19 被N1ptune编辑 ,原因: 修改格式
收藏
点赞9
打赏
分享
最新回复 (4)
雪    币: 2448
活跃值: (1570)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
maxwudi 2022-11-6 17:01
2
0

执行性能如何?

ps:https://revers.engineering/syscall-hooking-via-extended-feature-enable-register-efer/

最后于 2022-11-6 17:10 被maxwudi编辑 ,原因:
雪    币: 1475
活跃值: (3220)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小希希 2022-11-10 09:51
3
0
mark
雪    币: 135
活跃值: (1240)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
huojier 2022-11-10 16:17
4
0
amd似乎对syscall这块有更多逻辑.
雪    币: 12
活跃值: (209)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
clestor 2022-12-1 11:47
5
0
在VM试了一下,貌似不稳定,接近500~1000万次syscall的时候会引起异常炸掉
游客
登录 | 注册 方可回帖
返回