首页
社区
课程
招聘
[翻译]通过滥用一个Windows中的糟糕假设来检测调试器
发表于: 2017-9-23 17:23 9280

[翻译]通过滥用一个Windows中的糟糕假设来检测调试器

2017-9-23 17:23
9280

通过滥用一个Windows中的糟糕假设来检测调试器


 

翻译 by 银雁冰

 

原文链接

 

这篇博客将会审视一个微软在十多年前处理软件断点时所用的假设,利用这一点可以检测到大部分(市面上所有的?)用户态和内核态调试器。

 

x86架构可以用多种方式编码一条特定的汇编指令。举个例子,将两个寄存器eaxebx累加,并且将结果存储到eax中可采用如下助记符:add eax, ebx。这可以被编码成字节序列0x03 0xC30x01 0xD8.这两组机器码代表同一个汇编操作。

 

如果你只关心反调试技巧(并不想结合上下文理解它的原理),请将进度条滚动到这篇博客的最下面。对于那些想要勇敢地读完整篇文章的人,请系好安全带(老司机要开车了)。


int 3的漫长历史

一个int 3指令可以被编码成一个单字节指令0xCC,也可以通过不常见的方式被编码成多指令序列0xCD 0x03:

来自Intel指令手册(第2卷第3章第3.2节)

 

所以,如果Windows遇到一个多字节的int 3指令会发生什么呢?我们写一个简单的C++程序来看一下:

/*
*    Module Name:
*        int3.cpp
*
*    Abstract:
*        Examines the difference in operation between a 
*        multi-byte int 3 (0xCD 0x03) and a single-byte 
*        int 3 (0xCC).
*
*    Author:
*        Nemanja (Nemi) Mulasmajic <nm@triplefault.io>
*            http://triplefault.io
*/
#pragma warning(disable: 4710)

#pragma warning(push, 0)
#include <Windows.h>
#include <stdio.h>
#pragma warning(pop)

// The size of an architecture page on x86/x64.
#define PAGE_SIZE 0x1000

// Single-byte int 3 stub.
BYTE _Int3[] = 
{ 
    0xCC,            /* int 3*/
    0xC3            /* ret */
};

// Multi-byte int 3 stub.
BYTE _LongInt3[] =
{
    0xCD, 0x03,        /* int 3 */
    0xC3            /* ret */
};

/*
*    Handles exception processing for our int 3s.
*/
DWORD WINAPI ExceptionFilter(_In_ PEXCEPTION_POINTERS ExceptionInformation)
{
    // Malformed exception information.
    if (!ExceptionInformation || !ExceptionInformation->ExceptionRecord || !ExceptionInformation->ContextRecord)
        return EXCEPTION_EXECUTE_HANDLER;

    // This is the only type of exception we should see...
    if (ExceptionInformation->ExceptionRecord->ExceptionCode != EXCEPTION_BREAKPOINT)
        return EXCEPTION_EXECUTE_HANDLER;

    printf("[+] ExceptionRecord->ExceptionAddress: 0x%p.\n", ExceptionInformation->ExceptionRecord->ExceptionAddress);

#if defined(_M_AMD64)
#define IP Rip
#elif defined(_M_IX86)
#define IP Eip
#else
#error "Compiling for unhandled architecture."
#endif

    const BYTE* InstructionPointer = (const BYTE*)ExceptionInformation->ContextRecord->IP;
    printf("[+] ContextRecord->IP: 0x%p.\n", InstructionPointer);
    printf("\t@ [0: 0x%X...]\n", InstructionPointer[0]);

#undef IP

    return EXCEPTION_EXECUTE_HANDLER;
}

/*
*    The entry point.
*/
int main(void)
{
    int status = -1;

    // Allocate executable memory.
    PBYTE Memory = (PBYTE)VirtualAlloc(NULL, PAGE_SIZE, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
    if (!Memory)
    {
        fprintf(stderr, "[-] ERROR: Failed to allocate memory.\n");
        goto Cleanup;
    }

    typedef void(*Function)();

    // Place our simple breakpoint stubs in executable memory.

    // Our single-byte int 3 will be at the start of this region.
    Function Int3 = (Function)&Memory[0];
    size_t BufferSize = sizeof(_Int3);
    memcpy(Int3, _Int3, BufferSize);

    printf("[+] Single-byte 'int 3' buffer: 0x%p.\n", Int3);

    // Our multi-byte int 3 will be immediately after.
    Function LongInt3 = (Function)&Memory[BufferSize];
    BufferSize = sizeof(_LongInt3);
    memcpy(LongInt3, _LongInt3, BufferSize);

    printf("[+] Multi-byte 'int 3' buffer: 0x%p.\n", LongInt3);

    // Execute both variants of int 3.

    printf("\n[+] Executing single-byte variant.\n");

    __try
    {
        Int3();
    }
    __except (ExceptionFilter(GetExceptionInformation())) {    }

    printf("\n[+] Executing multi-byte variant.\n");

    __try
    {
        LongInt3();
    }
    __except (ExceptionFilter(GetExceptionInformation())) {}

    status = 0;

Cleanup:

    // Free allocated memory.
    if (Memory)
    {
        VirtualFree(Memory, 0, MEM_FREE);
        Memory = NULL;
    }

    // Wait for [ENTER] key press to terminate the program.
    getchar();

    return status;
}

在你运行这个程序之后,你应该看到与之类似的输出:

 

一个单字节的int 3(0xCC)和预期的一致。代码块开始处位于0x000001BE94B90000。当这段代码被执行后,异常处理例程启动,我们看到_EXCEPTION_RECORD.ExceptionAddress_CONTEXT.Rip都位于0x000001BE94B90000,这就是int 3指令的开始处。完美!

 

多字节的int 3(0xCD 0x03)的代码块开始处位于0x000001BE94B90002。当这段代码被执行后,异常处理例程认为_EXCEPTION_RECORD.ExceptionAddress_CONTEXT.Rip都位于0x000001BE94B90003。这是int 3指令的中间位置。为什么?哪里出问题了?


那个假设

注意:从现在开始,所有的汇编代码和伪代码都从Windows x64 10.0.15063(创意者更新版本)提供的系统文件中重新构建。如果你想要跟着做下去,请确保你使用和我相同的操作系统版本!

 

微软假设所有的int 3都源自单字节的情况。

 

这个假设发生在中断处理过程很前面的时候。顾名思义,当一个中断发生的时候,例如当一个int 3被处理器执行时,控制流被CPU重定向到一个注册在IDT(中断控制描述符表)的例程。在Windows操作系统中,软件中断对应的例程可以在nt!KiBreakpointTrap的符号中找到:

.text:0000000140174EC0 ; void __fastcall __noreturn KiBreakpointTrap()
.text:0000000140174EC0 KiBreakpointTrap proc near              ; DATA XREF: .pdata:000000014039F540 o
.text:0000000140174EC0                                         ; INITDATA:000000014082A1A8 o
.text:0000000140174EC0
.text:0000000140174EC0 TrapInformation = _KTRAP_FRAME ptr -168h
.text:0000000140174EC0
.text:0000000140174EC0                 sub     rsp, 8
.text:0000000140174EC4                 push    rbp
.text:0000000140174EC5                 sub     rsp, 158h
.text:0000000140174ECC                 lea     rbp, [rsp+168h+TrapInformation._Xmm1]
.text:0000000140174ED4                 mov     [rbp+0E8h+TrapInformation.ExceptionActive], 1
.text:0000000140174ED8                 mov     [rbp+0E8h+TrapInformation._Rax], rax
.text:0000000140174EDC                 mov     [rbp+0E8h+TrapInformation._Rcx], rcx
.text:0000000140174EE0                 mov     [rbp+0E8h+TrapInformation._Rdx], rdx
.text:0000000140174EE4                 mov     [rbp+0E8h+TrapInformation._R8], r8
.text:0000000140174EE8                 mov     [rbp+0E8h+TrapInformation._R9], r9
.text:0000000140174EEC                 mov     [rbp+0E8h+TrapInformation._R10], r10
.text:0000000140174EF0                 mov     [rbp+0E8h+TrapInformation._R11], r11
.text:0000000140174EF4                 test    byte ptr [rbp+0E8h+TrapInformation.SegCs], 1
.text:0000000140174EFB                 jz      short ExecutingInKernelModeContext
.text:0000000140174EFD                 swapgs
.text:0000000140174F00                 mov     r10, gs:_KPCR.Prcb.CurrentThread
.text:0000000140174F09                 test    [r10+_ETHREAD.Tcb.Header.___u0.__s6.DebugActive], 80h
.text:0000000140174F0E                 jz      short DebugIsActive
.text:0000000140174F10                 mov     ecx, 0C0000102h ; IA32_KERNEL_GS_BASE
.text:0000000140174F15                 rdmsr
.text:0000000140174F17                 shl     rdx, 20h
.text:0000000140174F1B                 or      rax, rdx
.text:0000000140174F1E                 cmp     [r10+_ETHREAD.Tcb.Teb], rax
.text:0000000140174F25                 jz      short DebugIsActive
.text:0000000140174F27                 mov     rdx, [r10+_ETHREAD.Tcb.___u35.__s8.Ucb]
.text:0000000140174F2E                 bts     [r10+_ETHREAD.Tcb.___u16.MiscFlags], 8
.text:0000000140174F34                 dec     [r10+_ETHREAD.Tcb.___u35.__s4.SpecialApcDisable]
.text:0000000140174F3C                 mov     [rdx+80h], rax  ; Something indexed into Ucb structure...
.text:0000000140174F43
.text:0000000140174F43 DebugIsActive:                          ; CODE XREF: KiBreakpointTrap+4E j
.text:0000000140174F43                                         ; KiBreakpointTrap+65 j
.text:0000000140174F43                 test    [r10+_ETHREAD.Tcb.Header.___u0.__s6.DebugActive], 3
.text:0000000140174F48                 mov     word ptr [rbp+0E8h+TrapInformation.Dr7], 0
.text:0000000140174F51                 jz      short ExecutingInKernelModeContext
.text:0000000140174F53                 call    KiSaveDebugRegisterState
.text:0000000140174F58
.text:0000000140174F58 ExecutingInKernelModeContext:           ; CODE XREF: KiBreakpointTrap+3B j
.text:0000000140174F58                                         ; KiBreakpointTrap+91 j
.text:0000000140174F58                 cld
.text:0000000140174F59                 stmxcsr [rbp+0E8h+TrapInformation._MxCsr]
.text:0000000140174F5D                 ldmxcsr gs:_KPCR.Prcb._MxCsr
.text:0000000140174F66                 movaps  xmmword ptr [rbp+0E8h+TrapInformation._Xmm0.Low], xmm0
.text:0000000140174F6A                 movaps  xmmword ptr [rbp+0E8h+TrapInformation._Xmm1.Low], xmm1
.text:0000000140174F6E                 movaps  xmmword ptr [rbp+0E8h+TrapInformation._Xmm2.Low], xmm2
.text:0000000140174F72                 movaps  xmmword ptr [rbp+0E8h+TrapInformation._Xmm3.Low], xmm3
.text:0000000140174F76                 movaps  xmmword ptr [rbp+0E8h+TrapInformation._Xmm4.Low], xmm4
.text:0000000140174F7A                 movaps  xmmword ptr [rbp+0E8h+TrapInformation._Xmm5.Low], xmm5
.text:0000000140174F7E                 test    [rbp+0E8h+TrapInformation.EFlags], 200h ; Check for the interrupt flag in EFLAGS.
.text:0000000140174F88                 jz      short DontEnableInterrupts
.text:0000000140174F8A                 sti
.text:0000000140174F8B
.text:0000000140174F8B DontEnableInterrupts:                   ; CODE XREF: KiBreakpointTrap+C8 j
.text:0000000140174F8B                 mov     ecx, 80000003h  ; ExceptionCode
.text:0000000140174F90                 mov     edx, 1          ; NumberOfParameters
.text:0000000140174F95                 mov     r8, [rbp+0E8h+TrapInformation._Rip]
.text:0000000140174F9C                 dec     r8              ; ExceptionAddress
.text:0000000140174F9F                 mov     r9d, 0          ; Parameter1
.text:0000000140174FA5                 call    KiExceptionDispatch
.text:0000000140174FA5 KiBreakpointTrap endp

nt!KiBreakpointTrap做的第一件事是在栈上生成一个用来传递给后续例程的陷阱帧(_KTRAP_FRAME)。这个结构体的其中一个定义如下:

kd> dt nt!_KTRAP_FRAME -b
   +0x000 P1Home           : Uint8B
   +0x008 P2Home           : Uint8B
   +0x010 P3Home           : Uint8B
   +0x018 P4Home           : Uint8B
   +0x020 P5               : Uint8B
   +0x028 PreviousMode     : Char
   +0x029 PreviousIrql     : UChar
   +0x02a FaultIndicator   : UChar
   +0x02b ExceptionActive  : UChar
   +0x02c MxCsr            : Uint4B
   +0x030 Rax              : Uint8B
   +0x038 Rcx              : Uint8B
   +0x040 Rdx              : Uint8B
   +0x048 R8               : Uint8B
   +0x050 R9               : Uint8B
   +0x058 R10              : Uint8B
   +0x060 R11              : Uint8B
   +0x068 GsBase           : Uint8B
   +0x068 GsSwap           : Uint8B
   +0x070 Xmm0             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x080 Xmm1             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x090 Xmm2             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0a0 Xmm3             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0b0 Xmm4             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0c0 Xmm5             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0d0 FaultAddress     : Uint8B
   +0x0d0 ContextRecord    : Uint8B
   +0x0d8 Dr0              : Uint8B
   +0x0e0 Dr1              : Uint8B
   +0x0e8 Dr2              : Uint8B
   +0x0f0 Dr3              : Uint8B
   +0x0f8 Dr6              : Uint8B
   +0x100 Dr7              : Uint8B
   +0x108 DebugControl     : Uint8B
   +0x110 LastBranchToRip  : Uint8B
   +0x118 LastBranchFromRip : Uint8B
   +0x120 LastExceptionToRip : Uint8B
   +0x128 LastExceptionFromRip : Uint8B
   +0x130 SegDs            : Uint2B
   +0x132 SegEs            : Uint2B
   +0x134 SegFs            : Uint2B
   +0x136 SegGs            : Uint2B
   +0x138 TrapFrame        : Uint8B
   +0x140 Rbx              : Uint8B
   +0x148 Rdi              : Uint8B
   +0x150 Rsi              : Uint8B
   +0x158 Rbp              : Uint8B
   +0x160 ErrorCode        : Uint8B
   +0x160 ExceptionFrame   : Uint8B
   +0x168 Rip              : Uint8B
   +0x170 SegCs            : Uint2B
   +0x172 Fill0            : UChar
   +0x173 Logging          : UChar
   +0x174 Fill1            : Uint2B
   +0x178 EFlags           : Uint4B
   +0x17c Fill2            : Uint4B
   +0x180 Rsp              : Uint8B
   +0x188 SegSs            : Uint2B
   +0x18a Fill3            : Uint2B
   +0x18c Fill4            : Uint4B
kd> ?? sizeof(nt!_KTRAP_FRAME)
unsigned int64 0x190

这个结构体的部分成员在中断发生时自动被CPU填写,具体地说,是 +0x160 (_KTRAP_FRAME.ErrorCode)+0x188 (_KTRAP_FRAME.SegSs)这一区间内的成员。

来自Intel指令手册(第3卷第6章第6.12节)

 

_KTRAP_FRAME事实上是被CPU存储在栈上的成员的一个扩展。它的目的是提供一个地方,去存储那些易失性寄存器,这些寄存器在调用C语言编译生成的函数时会被破坏。

 

需要指出的一件非常重要的事是,被CPU保存在栈上的指令寄存器(eip) (_KTRAP_FRAME.Rip)会被设为引发异常指令的下一句指令。在我们的场景中,这意味着_KTRAP_FRAME.Rip成员会被设为紧跟int 3的下一条指令,在上面的例子中,这个指令是ret(0xC3)

 

在易失性寄存器的值被保存后,nt!KiBreakpointTrap执行一次快速的检查,以判断中断是由用户态(ring3)还是内核态(ring0)发生。如果执行流来自ring3,一个swapgs语句需要被执行,同时填写一些另外的调试寄存器。

 

最终,控制流会恢复,然后易失性浮点寄存器也会被保存到_KTRAP_FRAME。在进入更多的异常处理逻辑前,指令指针会被从_KTRAP_FRAME.Rip(在前面进入nt!KiBreakpointTrap时被CPU所保存)取出,并且减1,然后作为一个参数被传入nt!KiExceptionDispatch。另外,异常代码,EXCEPTION_BREAKPOINT(0x80000003),也会被传入。nt!KiExceptionDispatch的声明如下:

void KiExceptionDispatch(DWORD ExceptionCode, DWORD NumberOfParameters, PVOID ExceptionAddress, ...);

需要指出的是nt!KiExceptionDispatch (和nt!KiBreakpointTrap一样)是用手工汇编写成。它假设ecx包含异常代码,edx是异常参数的数量(最多3个),r8包含异常发生的地址,r9是第一个异常参数(如果有存在),r10是第二个异常参数(如果存在),r11是第三个异常参数(如果存在),rbp指向_KTRAP_FRAME结构体中的一个段(位于偏移+0x80处)

.text:00000001401778C0 ; void KiExceptionDispatch(DWORD ExceptionCode, DWORD NumberOfParameters, PVOID ExceptionAddress, ...)
.text:00000001401778C0 KiExceptionDispatch proc near           ; CODE XREF: KiDivideErrorFault+D9 p
.text:00000001401778C0                                         ; KiDebugTrapOrFault+193 p ...
.text:00000001401778C0
.text:00000001401778C0 ExceptionFrame  = _KEXCEPTION_FRAME ptr -1D8h
.text:00000001401778C0 ExceptionRecord = _EXCEPTION_RECORD ptr -98h
.text:00000001401778C0
.text:00000001401778C0                 sub     rsp, 1D8h
.text:00000001401778C7                 lea     rax, [rsp+1D8h+ExceptionFrame._Rbx]
.text:00000001401778CF                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm6.Low], xmm6
.text:00000001401778D4                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm7.Low], xmm7
.text:00000001401778D9                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm8.Low], xmm8
.text:00000001401778DF                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm9.Low], xmm9
.text:00000001401778E5                 movaps  xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm10.Low], xmm10
.text:00000001401778EB                 movaps  xmmword ptr [rax-80h], xmm11 ; ExceptionFrame._Xmm11.Low
.text:00000001401778F0                 movaps  xmmword ptr [rax-70h], xmm12 ; ExceptionFrame._Xmm12.Low
.text:00000001401778F5                 movaps  xmmword ptr [rax-60h], xmm13 ; ExceptionFrame._Xmm13.Low
.text:00000001401778FA                 movaps  xmmword ptr [rax-50h], xmm14 ; ExceptionFrame._Xmm14.Low
.text:00000001401778FF                 movaps  xmmword ptr [rax-40h], xmm15 ; ExceptionFrame._Xmm15.Low
.text:0000000140177904                 mov     [rax+_KEXCEPTION_FRAME_GP._Rbx], rbx ; Part of the same ExceptionFrame structure on the stack.
.text:0000000140177907                 mov     [rax+_KEXCEPTION_FRAME_GP._Rdi], rdi
.text:000000014017790B                 mov     [rax+_KEXCEPTION_FRAME_GP._Rsi], rsi
.text:000000014017790F                 mov     [rax+_KEXCEPTION_FRAME_GP._R12], r12
.text:0000000140177913                 mov     [rax+_KEXCEPTION_FRAME_GP._R13], r13
.text:0000000140177917                 mov     [rax+_KEXCEPTION_FRAME_GP._R14], r14
.text:000000014017791B                 mov     [rax+_KEXCEPTION_FRAME_GP._R15], r15
.text:000000014017791F                 mov     rax, gs:_KPCR.Prcb.CurrentThread
.text:0000000140177928                 bt      [rax+_ETHREAD.Tcb.___u16.MiscFlags], 8
.text:000000014017792D                 jnb     short IsKernelMode
.text:000000014017792F                 test    byte ptr [rbp+0F0h], 1 ; _KTRAP_FRAME.SegCs
.text:0000000140177936                 jz      short IsKernelMode
.text:0000000140177938                 call    KiUmsExceptionEntry
.text:000000014017793D
.text:000000014017793D IsKernelMode:                           ; CODE XREF: KiExceptionDispatch+6D j
.text:000000014017793D                                         ; KiExceptionDispatch+76 j
.text:000000014017793D                 lea     rdi, [rsp+1D8h+ExceptionFrame.Return]
.text:0000000140177945                 mov     ebx, ecx
.text:0000000140177947                 mov     rcx, 14h
.text:000000014017794E                 xor     eax, eax
.text:0000000140177950                 rep stosq               ; Zero out _EXCEPTION_RECORD structure.
.text:0000000140177953                 sub     rdi, 0A0h
.text:000000014017795A                 mov     [rdi+_EXCEPTION_RECORD.ExceptionCode], ebx
.text:000000014017795C                 mov     [rdi+_EXCEPTION_RECORD.ExceptionAddress], r8
.text:0000000140177960                 mov     [rdi+_EXCEPTION_RECORD.NumberParameters], edx
.text:0000000140177963                 mov     [rdi+_EXCEPTION_RECORD.ExceptionInformation], r9 ; ExceptionInformation[0]
.text:0000000140177967                 mov     [rdi+(_EXCEPTION_RECORD.ExceptionInformation+8)], r10 ; ExceptionInformation[1]
.text:000000014017796B                 mov     [rdi+(_EXCEPTION_RECORD.ExceptionInformation+10h)], r11 ; ExceptionInformation[2]
.text:000000014017796F                 mov     r9b, [rbp+0F0h] ; _KTRAP_FRAME.SegCs
.text:0000000140177976                 and     r9b, 1          ; PreviousMode
.text:000000014017797A                 mov     byte ptr [rsp+20h], 1 ; FirstChance
.text:000000014017797F                 lea     r8, [rbp-80h]   ; TrapFrame
.text:0000000140177983                 mov     rdx, rsp        ; ExceptionFrame
.text:0000000140177986                 mov     rcx, rdi        ; ExceptionRecord
.text:0000000140177989                 call    KiDispatchException
.text:000000014017798E                 lea     rcx, [rsp+1D8h+ExceptionFrame._Rbx]
.text:0000000140177996                 movaps  xmm6, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm6.Low]
.text:000000014017799B                 movaps  xmm7, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm7.Low]
.text:00000001401779A0                 movaps  xmm8, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm8.Low]
.text:00000001401779A6                 movaps  xmm9, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm9.Low]
.text:00000001401779AC                 movaps  xmm10, xmmword ptr [rsp+1D8h+ExceptionFrame._Xmm10.Low]
.text:00000001401779B2                 movaps  xmm11, xmmword ptr [rcx-80h] ; ExceptionFrame._Xmm11.Low
.text:00000001401779B7                 movaps  xmm12, xmmword ptr [rcx-70h] ; ExceptionFrame._Xmm12.Low
.text:00000001401779BC                 movaps  xmm13, xmmword ptr [rcx-60h] ; ExceptionFrame._Xmm13.Low
.text:00000001401779C1                 movaps  xmm14, xmmword ptr [rcx-50h] ; ExceptionFrame._Xmm14.Low
.text:00000001401779C6                 movaps  xmm15, xmmword ptr [rcx-40h] ; ExceptionFrame._Xmm15.Low
.text:00000001401779CB                 mov     rbx, [rcx+_KEXCEPTION_FRAME_GP._Rbx]
.text:00000001401779CE                 mov     rdi, [rcx+_KEXCEPTION_FRAME_GP._Rdi]
.text:00000001401779D2                 mov     rsi, [rcx+_KEXCEPTION_FRAME_GP._Rsi]
.text:00000001401779D6                 mov     r12, [rcx+_KEXCEPTION_FRAME_GP._R12]
.text:00000001401779DA                 mov     r13, [rcx+_KEXCEPTION_FRAME_GP._R13]
.text:00000001401779DE                 mov     r14, [rcx+_KEXCEPTION_FRAME_GP._R14]
.text:00000001401779E2                 mov     r15, [rcx+_KEXCEPTION_FRAME_GP._R15]
.text:00000001401779E6                 cli
.text:00000001401779E7                 test    byte ptr [rbp+0F0h], 1 ; _KTRAP_FRAME.SegCs
.text:00000001401779EE                 jz      IsKernelMode_2
.text:00000001401779F4                 mov     rcx, gs:_KPCR.Prcb.CurrentThread
.text:00000001401779FD                 cmp     [rcx+_ETHREAD.Tcb.___u27.ApcState.UserApcPending], 0
.text:0000000140177A04                 jz      short SkipApcProcessing
.text:0000000140177A06                 mov     ecx, 1
.text:0000000140177A0B                 mov     cr8, rcx
.text:0000000140177A0F                 sti
.text:0000000140177A10                 call    KiInitiateUserApc
.text:0000000140177A15                 cli
.text:0000000140177A16                 mov     ecx, 0
.text:0000000140177A1B                 mov     cr8, rcx
.text:0000000140177A1F
.text:0000000140177A1F SkipApcProcessing:                      ; CODE XREF: KiExceptionDispatch+144 j
.text:0000000140177A1F                 mov     rcx, gs:_KPCR.Prcb.CurrentThread
.text:0000000140177A28                 test    dword ptr [rcx+_ETHREAD.Tcb.Header.___u0.__s2.Type], 8000000h
.text:0000000140177A2E                 jz      short SkipSetContextState
.text:0000000140177A30                 call    KiRestoreSetContextState
.text:0000000140177A35
.text:0000000140177A35 SkipSetContextState:                    ; CODE XREF: KiExceptionDispatch+16E j
.text:0000000140177A35                 mov     rcx, gs:_KPCR.Prcb.CurrentThread
.text:0000000140177A3E                 test    dword ptr [rcx+_ETHREAD.Tcb.Header.___u0.__s2.Type], 40010000h
.text:0000000140177A44                 jz      short SkipUms
.text:0000000140177A46                 test    [rcx+_ETHREAD.Tcb.Header.___u0.__s7.DpcActive], 1
.text:0000000140177A4A                 jz      short SkipCopyCounters
.text:0000000140177A4C                 call    KiCopyCounters
.text:0000000140177A51                 mov     rcx, gs:_KPCR.Prcb.CurrentThread
.text:0000000140177A5A
.text:0000000140177A5A SkipCopyCounters:                       ; CODE XREF: KiExceptionDispatch+18A j
.text:0000000140177A5A                 test    [rcx+_ETHREAD.Tcb.Header.___u0.__s6.DebugActive], 40h
.text:0000000140177A5E                 jz      short SkipUms
.text:0000000140177A60                 lea     rsp, [rbp-80h] ; _KTRAP_FRAME.
.text:0000000140177A64                 mov     cl, 1
.text:0000000140177A66                 call    KiUmsExit
.text:0000000140177A6B
.text:0000000140177A6B SkipUms:                                ; CODE XREF: KiExceptionDispatch+184 j
.text:0000000140177A6B                                         ; KiExceptionDispatch+19E j
.text:0000000140177A6B                 ldmxcsr dword ptr [rbp-54h] ; _KTRAP_FRAME._MxCsr
.text:0000000140177A6F                 cmp     word ptr [rbp+80h], 0 ; _KTRAP_FRAME._Dr7
.text:0000000140177A77                 jz      short RestoreTrapFrame
.text:0000000140177A79                 call    KiRestoreDebugRegisterState
.text:0000000140177A7E
.text:0000000140177A7E RestoreTrapFrame:                       ; CODE XREF: KiExceptionDispatch+1B7 j
.text:0000000140177A7E                 movaps  xmm0, xmmword ptr [rbp-10h] ; _KTRAP_FRAME._Xmm0
.text:0000000140177A82                 movaps  xmm1, xmmword ptr [rbp+0] ; _KTRAP_FRAME._Xmm1
.text:0000000140177A86                 movaps  xmm2, xmmword ptr [rbp+10h] ; _KTRAP_FRAME._Xmm2
.text:0000000140177A8A                 movaps  xmm3, xmmword ptr [rbp+20h] ; _KTRAP_FRAME._Xmm3
.text:0000000140177A8E                 movaps  xmm4, xmmword ptr [rbp+30h] ; _KTRAP_FRAME._Xmm4
.text:0000000140177A92                 movaps  xmm5, xmmword ptr [rbp+40h] ; _KTRAP_FRAME._Xmm5
.text:0000000140177A96                 mov     r11, [rbp-20h]  ; _KTRAP_FRAME._R11
.text:0000000140177A9A                 mov     r10, [rbp-28h]  ; _KTRAP_FRAME._R10
.text:0000000140177A9E                 mov     r9, [rbp-30h]   ; _KTRAP_FRAME._R9
.text:0000000140177AA2                 mov     r8, [rbp-38h]   ; _KTRAP_FRAME._R8
.text:0000000140177AA6                 mov     rdx, [rbp-40h]  ; _KTRAP_FRAME._Rdx
.text:0000000140177AAA                 mov     rcx, [rbp-48h]  ; _KTRAP_FRAME._Rcx
.text:0000000140177AAE                 mov     rax, [rbp-50h]  ; _KTRAP_FRAME._Rax
.text:0000000140177AB2                 mov     rsp, rbp
.text:0000000140177AB5                 mov     rbp, [rbp+0D8h] ; _KTRAP_FRAME.Rbp
.text:0000000140177ABC                 add     rsp, 0E8h       ; Restore stack back to how it was when the CPU transferred
.text:0000000140177ABC                                         ; execution to the interrupt routine (exclude error code).
.text:0000000140177ABC                                         ;
.text:0000000140177ABC                                         ; Basically, prepare for iretq.
.text:0000000140177AC3                 swapgs                  ; Exchange gs back to usermode gs.
.text:0000000140177AC6                 iretq
.text:0000000140177AC8 ; ---------------------------------------------------------------------------
.text:0000000140177AC8
.text:0000000140177AC8 IsKernelMode_2:                         ; _KTRAP_FRAME._MxCsr
.text:0000000140177AC8                 ldmxcsr dword ptr [rbp-54h]
.text:0000000140177ACC                 movaps  xmm0, xmmword ptr [rbp-10h] ; _KTRAP_FRAME._Xmm0
.text:0000000140177AD0                 movaps  xmm1, xmmword ptr [rbp+0] ; _KTRAP_FRAME._Xmm1
.text:0000000140177AD4                 movaps  xmm2, xmmword ptr [rbp+10h] ; _KTRAP_FRAME._Xmm2
.text:0000000140177AD8                 movaps  xmm3, xmmword ptr [rbp+20h] ; _KTRAP_FRAME._Xmm3
.text:0000000140177ADC                 movaps  xmm4, xmmword ptr [rbp+30h] ; _KTRAP_FRAME._Xmm4
.text:0000000140177AE0                 movaps  xmm5, xmmword ptr [rbp+40h] ; _KTRAP_FRAME._Xmm5
.text:0000000140177AE4                 mov     r11, [rbp-20h]  ; _KTRAP_FRAME._R11
.text:0000000140177AE8                 mov     r10, [rbp-28h]  ; _KTRAP_FRAME._R10
.text:0000000140177AEC                 mov     r9, [rbp-30h]   ; _KTRAP_FRAME._R9
.text:0000000140177AF0                 mov     r8, [rbp-38h]   ; _KTRAP_FRAME._R8
.text:0000000140177AF4                 mov     rdx, [rbp-40h]  ; _KTRAP_FRAME._Rdx
.text:0000000140177AF8                 mov     rcx, [rbp-48h]  ; _KTRAP_FRAME._Rcx
.text:0000000140177AFC                 mov     rax, [rbp-50h]  ; _KTRAP_FRAME._Rax
.text:0000000140177B00                 mov     rsp, rbp
.text:0000000140177B03                 mov     rbp, [rbp+0D8h] ; _KTRAP_FRAME._Rbp
.text:0000000140177B0A                 add     rsp, 0E8h
.text:0000000140177B11                 iretq
.text:0000000140177B11 KiExceptionDispatch endp

nt!KiExceptionDispatch的入口处,第一件发生的事情是生成一个_KEXCEPTION_FRAME_KTRAP_FRAME用来存储易失性寄存器,_KEXCEPTION_FRAME则提供一个地方用来存储所有非易失性寄存器:

kd> dt nt!_KEXCEPTION_FRAME -b
   +0x000 P1Home           : Uint8B
   +0x008 P2Home           : Uint8B
   +0x010 P3Home           : Uint8B
   +0x018 P4Home           : Uint8B
   +0x020 P5               : Uint8B
   +0x028 Spare1           : Uint8B
   +0x030 Xmm6             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x040 Xmm7             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x050 Xmm8             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x060 Xmm9             : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x070 Xmm10            : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x080 Xmm11            : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x090 Xmm12            : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0a0 Xmm13            : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0b0 Xmm14            : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0c0 Xmm15            : _M128A
      +0x000 Low              : Uint8B
      +0x008 High             : Int8B
   +0x0d0 TrapFrame        : Uint8B
   +0x0d8 OutputBuffer     : Uint8B
   +0x0e0 OutputLength     : Uint8B
   +0x0e8 Spare2           : Uint8B
   +0x0f0 MxCsr            : Uint8B
   +0x0f8 Rbp              : Uint8B
   +0x100 Rbx              : Uint8B
   +0x108 Rdi              : Uint8B
   +0x110 Rsi              : Uint8B
   +0x118 R12              : Uint8B
   +0x120 R13              : Uint8B
   +0x128 R14              : Uint8B
   +0x130 R15              : Uint8B
   +0x138 Return           : Uint8B
kd> ?? sizeof(nt!_KEXCEPTION_FRAME)
unsigned int64 0x140

nt!KiExceptionDispatch还会在栈上创建一个_EXCEPTION_RECORD.aspx)结构体。如果你写过任何Windows上的错误处理例程(可以是用户模式或内核模式),你会很熟悉这个数据结构,因为它包含一个子结构体_EXCEPTION_POINTERS.aspx)。我们在上面的例子里面同时使用了这两个结构体。

 

更进一步,这解释了我们谜团中的第一部分,顾名思义,为什么_EXCEPTION_RECORD.ExceptionAddress是不正确的。回想一下_EXCEPTION_RECORD.ExceptionAddress是从给nt!KiExceptionDispatch的r8寄存器参数中传递过来的,而这个值来自nt!KiBreakpointTrap。这个值正是被减1后的_KTRAP_FRAME.Rip成员的一份拷贝。

 

为了找出_CONTEXT.Rip成员是如何被填充的,我们需要将兔子洞再挖得深一点。

 

nt!KiExceptionDispatch会调用nt!KiDispatchException(是的,这两个单词的顺序被有意翻转),同时传入刚创建的_EXCEPTION_RECORD_KEXCEPTION_FRAME

void __fastcall KiDispatchException(_EXCEPTION_RECORD *ExceptionRecord, _KEXCEPTION_FRAME *ExceptionFrame, _KTRAP_FRAME *TrapFrame, KPROCESSOR_MODE PreviousMode, BOOLEAN FirstChance);

这个函数会在_KTRAP_FRAME和_KEXCEPTION_FRAME之外构造一个_CONTEXT:通过调用辅助例程KeContextFromKFrame。在_CONTEXT被创建后,会对_EXCEPTION_RECORD.ExceptionCode做一次检查(作为nt!KiExceptionDispatch的一个参数被接收),以判断它是否是STATUS_BREAKPOINT (0x80000003)。如果是,_CONTEXT.Rip成员会被减1:

...

KeContextFromKframes(TrapFrame, ExceptionFrame, &ContextFrame);

if ( ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT /* 0x80000003 */ )
    --ContextFrame.Rip;

...

这解释了最后一个谜团,从而导致_CONTEXT.Rip的值也被破坏。


反调试技巧

知道了Windows如何处理不同的int 3类型后,就可以利用这一差异来检测反调试吗?答案是肯定的。

 

调试器会在异常发生的时候显示程序的状态。既然Windows会不正确地假设我们的int 3异常来自单字节的情况,完全可以迷惑调试器,让它读取“额外”内存。我们利用这种不一致来进行一趟到“守护页”的旅行。

 

正如我们在我们的第一个例子里看到的那样(见本文的开始),当多字节的int 3出现时,_EXCEPTION_RECORD.ExceptionAddress_CONTEXT.Rip会位于我们多字节指令的中间而不是开始。这意味着调试器将会不正确地认为抛出软件断点的那条指令开始于操作码0xC3。当我们引用靠谱的Intel指令手册时,我们可以看到这个操作码代表一个2字节add指令:

来自Intel指令手册(第2卷第3章第3.2节)

 

如果我们将我们的多字节int 3指令放在一个内存页的最后会发生什么?

 

当操作系统提示(我们附加的)调试器一个断点异常发生时,指令指针会被指向(被操作系统)错误解释为一个add(0x03)指令开始处的内存地址。这会导致调试器去反汇编相邻页的数据(既然这是一个2字节长度的指令),然后有效阅读一个我们“合法”内存外的字节。

 

我们的技巧依赖于Windows上的一个事实:作为一种优化手段,Windows并不会将虚拟内存提交到物理内存,除非它必须需要它。也就是说大多数内存,尤其在用户态,是被分页的。当不在物理内存中的内存需要被使用时,一个缺页异常会发生。想要了解更多关于内存管理的知识,可以阅读我们网站上的下列文章:Introduction to IA-32e hardware pagingExploring Windows virtual memory management.

 

所以,我们可以通过调用QueryWorkingSetEx.aspx) 检测到读取相邻页的内存,因为这个过程会插入对应的PTE(页表入口)。如果相邻页位于我们进程的工作集中(例如,被调试器映射到我们的进程中),_PSAPI_WORKING_SET_EX_BLOCK.aspx) 中的有效位就会被设定。


PoC || GTFO

一个完整的例子如下:

/*
*    Module Name:
*        antidebug_long_int3.cpp
*
*    Abstract:
*        Attempts to detect the presence of a debugger
*        by issuing a multi-byte int 3 and inspecting 
*        page PTE mappings.
*
*    Author:
*        Nemanja (Nemi) Mulasmajic <nm@triplefault.io>
*            http://triplefault.io
*/

#pragma warning(disable: 4710)

#pragma warning(push, 0)
#include <windows.h>
#include <stdio.h>
#include <Psapi.h>
#pragma warning(pop)

#pragma comment(lib, "psapi.lib")

// The size of an architecture page on x86/x64.
#define PAGE_SIZE 0x1000

// A pseudo-handle that represents the current process.
#define NtCurrentProcess() ((HANDLE)-1)

// Multi-byte int 3.
BYTE _LongInt3[] = { 0xCD, 0x03 };

int main(void)
{
    int status = -1;

    // We allocate 2 contiguous pages of executable virtual memory.
    PBYTE Memory = (PBYTE)VirtualAlloc(NULL, (PAGE_SIZE * 2), (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READWRITE);
    if (!Memory)
    {
        fprintf(stderr, "[-] ERROR: Failed to allocate memory.\n");
        goto Cleanup;
    }

    // The first page, "page 1", will have the multi-byte form of int 3 
    // embedded at the very end of the page. This is done so that the next 
    // byte immediately after the multi-byte int 3 will span into "page 2".
    //
    // e.g:

    //    | Page 1            | Page 2
    //    [.....................0xCD 0x03][............................]

    PBYTE CodeLocation = &Memory[PAGE_SIZE - sizeof(_LongInt3)];
    PBYTE DeadPageLocation = &Memory[PAGE_SIZE];

    // Add the int 3 to the very end of page 1.
    memcpy(CodeLocation, _LongInt3, sizeof(_LongInt3));

    // Page 2 should never be accessed and therefore should not be 
    // present in our process' working set. 
    PSAPI_WORKING_SET_EX_INFORMATION wsi;
    wsi.VirtualAddress = DeadPageLocation;
    if (!QueryWorkingSetEx(NtCurrentProcess(), &wsi, sizeof(wsi)))
    {
        fprintf(stderr, "[-] ERROR: QueryWorkingSetEx failed with error: 0x%lx.\n", GetLastError());
        goto Cleanup;
    }

    if (wsi.VirtualAttributes.Valid)
    {
        fprintf(stderr, "[-] ERROR: Page is expected to be invalid. Make sure you have not inadvertently accessed this page.\n");
        goto Cleanup;
    }

    __try
    {
        // Invoke the long form of int 3.
        ((void(*)())CodeLocation)();
    }
    __except (EXCEPTION_EXECUTE_HANDLER) { }

    // If a debugger caught the exception, even if it was passed to the 
    // application via "go unhandled", it will have, most likely, mapped
    // page 2 into the application's memory space.
    if (!QueryWorkingSetEx(NtCurrentProcess(), &wsi, sizeof(wsi)))
    {
        fprintf(stderr, "[-] ERROR: QueryWorkingSetEx failed with error: 0x%lx.\n", GetLastError());
        goto Cleanup;
    }

    printf("[+] Debugger %s.\n", ((wsi.VirtualAttributes.Valid) ? "detected" : "not detected"));

    status = 0;

Cleanup:

    // Free allocated memory.
    if (Memory)
    {
        VirtualFree(Memory, 0, MEM_FREE);
        Memory = NULL;
    }

    // Wait for [ENTER] key press to terminate the program.
    getchar();

    return status;
}

[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 1
支持
分享
最新回复 (9)
雪    币: 8865
活跃值: (2379)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
2
X32下并不能检测OD+SOD。
X64下如果PEB那个Flag位不标记,也不会有问题。
拉闸


2017-9-23 17:51
0
雪    币: 9662
活跃值: (4588)
能力值: ( LV15,RANK:800 )
在线值:
发帖
回帖
粉丝
3
cvcvxk X32下并不能检测OD+SOD。X64下如果PEB那个Flag位不标记,也不会有问题。拉闸
多谢v校指点,学习了
2017-9-23 17:56
0
雪    币: 1176
活跃值: (1234)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
4
记得我看这篇文章的时候  x64dbg的作者在下面回复:已修复。。。
2017-9-24 09:45
1
雪    币: 272
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
Tennn 记得我看这篇文章的时候&nbsp; x64dbg的作者在下面回复:已修复。。。
这TM就尴尬了
2017-9-24 18:07
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢分享!!
2017-9-24 19:01
0
雪    币: 9662
活跃值: (4588)
能力值: ( LV15,RANK:800 )
在线值:
发帖
回帖
粉丝
7
Tennn 记得我看这篇文章的时候&nbsp; x64dbg的作者在下面回复:已修复。。。
我去看了一下原文评论,还真有,x64dbg的作者速度真快。。。
2017-9-25 10:10
0
雪    币: 2291
活跃值: (933)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
8
思路挺不错的
2017-9-25 10:51
0
雪    币: 1462
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
好久没来看看了。
2017-9-28 13:09
0
雪    币: 106
活跃值: (271)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
10
好文章,感谢分享......
2017-10-19 18:21
0
游客
登录 | 注册 方可回帖
返回
//