如何在启用陷阱标志时创建 INT 1 中断处理程序?

How to create an INT 1 interrupt handler when the Trap Flag is enabled?

提问人:vengy 提问时间:9/18/2023 最后编辑:vengy 更新时间:9/18/2023 访问量:107

问:

下面的 MASM 程序启用陷阱标志 (TF),但它会导致程序过早退出

pushf                   ; Push FLAGS onto the stack
pop     ax              ; Pop FLAGS into AX register
or      ax, 0100h       ; Set the Trap Flag bit 0100h = 0000000100000000
push    ax              ; Push the modified value onto the stack
popf                    ; Pop it back into FLAGS

为了实现处理器的单步执行,x86 架构提供了一个单位陷阱标志 (TF),可以在 FLAGS 寄存器。FLAGS 寄存器是 x86 上的状态寄存器 包含控制或描述的各种位标志的处理器 处理器的状态。如果 Trap Flag 设置为 true,则 处理器将在每个之后调用中断 1 (INT 1) 指令被执行。

; +------+--------+-------------+----------------------------------------+
; | Bit #|  Mask  | Abbreviation| Description                            |
; +------+--------+-------------+----------------------------------------+
; |  0   | 0x0001 |     CF      | Carry flag                             |
; |  1   | 0x0002 |     —       | Reserved                               |
; |  2   | 0x0004 |     PF      | Parity flag                            |
; |  3   | 0x0008 |     —       | Reserved                               |
; |  4   | 0x0010 |     AF      | Adjust flag                            |
; |  5   | 0x0020 |     —       | Reserved                               |
; |  6   | 0x0040 |     ZF      | Zero flag                              |
; |  7   | 0x0080 |     SF      | Sign flag                              |
; |  8   | 0x0100 |     TF      | Trap flag                              |
; |  9   | 0x0200 |     IF      | Interrupt flag                         |
; | 10   | 0x0400 |     DF      | Direction flag                         |
; | 11   | 0x0800 |     OF      | Overflow flag                          |
; |12-13 | 0x3000 |    IOPL     | I/O privilege level (286+ only),       |
; |      |        |             | always all-1s on 8086 and 186          |
; | 14   | 0x4000 |     NT      | Nested task flag (286+ only),          |
; |      |        |             | always 1 on 8086 and 186               |
; | 15   | 0x8000 |     MD      | Mode flag (NEC V-series only),         |
; |      |        |             | reserved on all Intel CPUs.            |
; |      |        |             | Always 1 on 8086/186, 0 on 286 and     |
; |      |        |             | later.                                 |
; +------+--------+-------------+----------------------------------------+

option casemap:none

extrn printf:proc

ALIGN_STACK MACRO num_args
    IF num_args LT 5 OR (num_args AND 1) EQ 0
        AND SPL, 0F0h
    ELSE
        OR SPL, 08h
    ENDIF
ENDM

RESTORE_STACK MACRO num_args
    IF num_args LT 5
        LEA RSP, [RSP + 5*8]
    ELSEIF (num_args AND 1) EQ 0
        LEA RSP, [RSP + (num_args + 1)*8]
    ELSE
        LEA RSP, [RSP + num_args*8]
    ENDIF
ENDM

.data  
    strCF   db "Bit # %d, Value  = %d",13,10,0
     
.code

PrintFlagsBit:
    pushfq                  ; Push RFLAGS onto the stack
    pop     r8              ; Load the RFLAGS from the stack into R8
    mov     cl, al          ; Move the lower 8 bits of RAX into CL
    shr     r8, cl          ; Shift right by CL, so the flags bit is now the LSB of R8
    and     r8, 1           ; Zero all other bits, except the LSB
    
    ;; call the printf function
    NUM_ARGS        = 3
    PUSH            RSP
    PUSH            [RSP]
    ALIGN_STACK     NUM_ARGS
    MOV             RDX, RAX
    LEA             RCX,strCF
    SUB             RSP,32
    CALL            printf
    RESTORE_STACK   NUM_ARGS
    POP             RSP    
    
    ret

main proc

    ; Carry flag Bit #1
    ; -----------------
    mov     ax, 0FFFFh      ; 0FFFFh + 1 = 010000h, but since AX is 16 bits,
    add     ax, 1           ; it wraps around the most significant bit into CF                               
    mov     eax, 1          ; Bit #1
    call    PrintFlagsBit
    
    ; Parity flag Bit #2
    ; ------------------    
    mov     al, 00110011b   ; This has 4 set bits - Even Parity
    test    al, al          ; TEST sets the PF = 1 (even parity) based on the value in AL                            
    mov     eax, 2          ; Bit #2
    call    PrintFlagsBit
    
    ; Adjust flag Bit #4
    ; ------------------        
    mov     al, 0Fh         ; 0000 1111 + 0000 0001 = 0001 0000
    add     al, 01h         ; Addition carried 3rd bit to 4th bit
    mov     eax, 4          ; Bit #4
    call    PrintFlagsBit
         
    ; Zero flag Bit #6
    ; -----------------        
    mov     eax, 5          ; Load EAX register with the value 5
    sub     eax, 5          ; Subtract 5 from EAX, result is zero, so ZF is set
    mov     eax, 6          ; Bit #6
    call    PrintFlagsBit        
    
    ; Sign flag Bit #7
    ; -----------------       
    mov     eax, 1          ; Load EAX register with the value 1
    neg     eax             ; Negate the value in EAX, most significant bit is 1, so SF is set
    mov     eax, 7          ; Bit #7
    call    PrintFlagsBit   
    
    ; Trap flag Bit #8
    ; ----------------
    ; Set TF flag
    pushf                   ; Push FLAGS onto the stack
    pop     ax              ; Pop FLAGS into AX register
    or      ax, 0100h       ; Set the Trap Flag bit 0100h = 0000000100000000
    push    ax              ; Push the modified value onto the stack
    popf                    ; Pop it back into FLAGS
    mov     eax, 8          ; Bit #8
    call    PrintFlagsBit  
    ; Unset TF flag
    pushf                   ; Push FLAGS onto the stack
    pop     ax              ; Pop FLAGS into AX register
    and     ax, 0FEFFh      ; Unset the Trap Flag bit 0FEFFh = 1111111011111111
    push    ax              ; Push the modified value onto the stack
    popf                    ; Pop it back into FLAGS    

    ; Interrupt flag Bit #9
    ; ---------------------       
    ;sti                    ; Set Interrupt Flag (already set)
    ;cli                    ; Clear Interrupt Flag
    mov     eax, 9          ; Bit #9
    call    PrintFlagsBit   
    
    ; Direction flag Bit #10
    ; ----------------------       
    std                     ; sets the DF flag, ensuring backward operations.   
    mov     eax, 10         ; Bit #10
    call    PrintFlagsBit   
    cld                     ; clears the DF flag, ensuring forward operations.    
    
    ; Overflow flag Bit #11
    ; ---------------------       
    mov     al, 07Fh        ; AL = 01111111 (unsigned 127, MSB 0)
    add     al, 01h         ; AL = 10000000 (signed -128, MSB 1)
    mov     eax, 11         ; Bit #11
    call    PrintFlagsBit       
    
    RET
main endp

end

设置 TF 时,程序输出

Bit # 1, Value  = 1
Bit # 2, Value  = 1
Bit # 4, Value  = 1
Bit # 6, Value  = 1
Bit # 7, Value  = 1
Bit # 8, Value  = 0
Bit # 9, Value  = 1
Bit # 10, Value  = 1
Bit # 11, Value  = 1

设置TF后,程序输出

Bit # 1, Value  = 1
Bit # 2, Value  = 1
Bit # 4, Value  = 1
Bit # 6, Value  = 1
Bit # 7, Value  = 1

有趣的是,使用 Windbg 单步执行程序时,程序尝试设置 TF=1,但 Windbg 阻止了它?

tf

问题

有没有一种简单的方法可以实现 INT 1 处理程序,以便程序可以捕获它?

也许是这样

INT1handler PROC
    ; ...
    ; ...
    ; ...

    iretd   ; Return from interrupt handler
INT1handler ENDP

评论

从评论部分,有人注意到

如果在调试器下运行,则调试器将获取跟踪 中断。这是程序用作反调试器的一个技巧 技术。

添加了这个 C 程序来演示该技巧:

#include <stdio.h>
#include <windows.h>

int main() {
    BOOL isDebugged = TRUE;
    __try
    {
        __asm
        {
            pushfd
            or dword ptr[esp], 0x100 // Set the Trap Flag TF=1
            popfd                    // Load the value into EFLAGS register
            nop                      // Single-step exception (INT 01h) generated after executing the nop. 
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        // If an exception has been raised – debugger is not present
        isDebugged = FALSE;
    }
    if (isDebugged)
    {
        printf("Debugger Present!");
        exit(-1);
    }

    return 0;
}
Windows 调试 程序集 x86-64 masm64

评论

3赞 Raymond Chen 9/18/2023
如果在调试器下运行,则调试器将获取跟踪中断。这是程序用作反调试器技术的一个技巧。
0赞 RbMm 9/18/2023
如果异常传递给应用,但并不意味着调试器未附加到进程

答:

2赞 vengy 9/18/2023 #1

添加了向量异常处理 (VEH) 以捕获 INT 1。

程序现在正常完成。

VEH.asm

; +------+--------+-------------+----------------------------------------+
; | Bit #|  Mask  | Abbreviation| Description                            |
; +------+--------+-------------+----------------------------------------+
; |  0   | 0x0001 |     CF      | Carry flag                             |
; |  1   | 0x0002 |     —       | Reserved                               |
; |  2   | 0x0004 |     PF      | Parity flag                            |
; |  3   | 0x0008 |     —       | Reserved                               |
; |  4   | 0x0010 |     AF      | Adjust flag                            |
; |  5   | 0x0020 |     —       | Reserved                               |
; |  6   | 0x0040 |     ZF      | Zero flag                              |
; |  7   | 0x0080 |     SF      | Sign flag                              |
; |  8   | 0x0100 |     TF      | Trap flag                              |
; |  9   | 0x0200 |     IF      | Interrupt flag                         |
; | 10   | 0x0400 |     DF      | Direction flag                         |
; | 11   | 0x0800 |     OF      | Overflow flag                          |
; |12-13 | 0x3000 |    IOPL     | I/O privilege level (286+ only),       |
; |      |        |             | always all-1s on 8086 and 186          |
; | 14   | 0x4000 |     NT      | Nested task flag (286+ only),          |
; |      |        |             | always 1 on 8086 and 186               |
; | 15   | 0x8000 |     MD      | Mode flag (NEC V-series only),         |
; |      |        |             | reserved on all Intel CPUs.            |
; |      |        |             | Always 1 on 8086/186, 0 on 286 and     |
; |      |        |             | later.                                 |
; +------+--------+-------------+----------------------------------------+

option casemap:none


EXTERN printf:proc
EXTERN AddVectoredExceptionHandler:proc
EXTERN RemoveVectoredExceptionHandler:proc


ALIGN_STACK MACRO num_args
    IF num_args LT 5 OR (num_args AND 1) EQ 0
        AND SPL, 0F0h
    ELSE
        OR SPL, 08h
    ENDIF
ENDM


RESTORE_STACK MACRO num_args
    IF num_args LT 5
        LEA RSP, [RSP + 5*8]
    ELSEIF (num_args AND 1) EQ 0
        LEA RSP, [RSP + (num_args + 1)*8]
    ELSE
        LEA RSP, [RSP + num_args*8]
    ENDIF
ENDM


EXCEPTION_MAXIMUM_PARAMETERS equ 15
EXCEPTION_CONTINUE_EXECUTION equ -1
EXCEPTION_CONTINUE_SEARCH    equ 0
EXCEPTION_SINGLE_STEP        equ 080000004h


; Struct for exception record
EXCEPTION_RECORD STRUCT 8
    ExceptionCode             DWORD ?
    ExceptionFlags            DWORD ?
    ExceptionRecord           QWORD ?
    ExceptionAddress          QWORD ?
    NumberParameters          DWORD ? 
    __unusedAlignment         DWORD ?
    ExceptionInformation      QWORD EXCEPTION_MAXIMUM_PARAMETERS DUP(?)
EXCEPTION_RECORD ENDS


; Contains processor-specific register data
CONTEXT STRUCT
  P1Home QWORD ?
  P2Home QWORD ?
  P3Home QWORD ?
  P4Home QWORD ?
  P5Home QWORD ?
  P6Home QWORD ?
  ContextFlags DWORD ?
  MxCsr DWORD ?
  SegCs WORD ?
  SegDs WORD ?
  SegEs WORD ?
  SegFs WORD ?
  SegGs WORD ?
  SegSs WORD ?
  EFlags DWORD ?
  ; ...
CONTEXT ENDS


EXCEPTION_POINTERS STRUCT
  ExceptionRecord QWORD ?
  ContextRecord QWORD ?
EXCEPTION_POINTERS ENDS


.data  
    strCF   db "Bit # %d, Value  = %d",13,10,0
    message DB "Single step exception captured!",13,10
            DB "ExceptionCode: %X",13,10
            DB "EFlags: %X",13,10,0   


.data?
    hHandler          QWORD ?    


.code


VectoredHandler PROC
    ; Save non-volatile registers
    PUSH            RBX
    PUSH            RBP
    PUSH            RDI
    PUSH            RSI
    PUSH            R12
    PUSH            R13
    PUSH            R14
    PUSH            R15
    
    ; RCX holds a pointer to the EXCEPTION_POINTERS structure
    MOV             RSI, [RCX + EXCEPTION_POINTERS.ExceptionRecord]
    MOV             RDI, [RCX + EXCEPTION_POINTERS.ContextRecord]

    CMP             DWORD PTR [RSI + EXCEPTION_RECORD.ExceptionCode], EXCEPTION_SINGLE_STEP
    JNE             notOurException   
   
    ; Handle the exception
    NUM_ARGS        = 3
    PUSH            RSP
    PUSH            [RSP]
    ALIGN_STACK     NUM_ARGS
    MOV             R8D, [RDI + CONTEXT.EFlags]
    MOV             EDX, DWORD PTR [RSI + EXCEPTION_RECORD.ExceptionCode]
    LEA             RCX, message
    SUB             RSP, 32
    CALL            printf
    RESTORE_STACK   NUM_ARGS
    POP             RSP   
       
    ; Clear the TF flag in the EFlags/RFlags field of the CONTEXT (0FEFFh = 1111111011111111)
    AND             DWORD PTR [RDI + CONTEXT.EFlags], 0FEFFh     
    
    MOV             RAX, EXCEPTION_CONTINUE_EXECUTION
    JMP             outta_here
    
notOurException:
    MOV             RAX, EXCEPTION_CONTINUE_SEARCH

outta_here:    
    ; Restore non-volatile registers
    POP             R15
    POP             R14
    POP             R13
    POP             R12
    POP             RSI
    POP             RDI
    POP             RBP
    POP             RBX   
    
    RET
VectoredHandler ENDP


PrintFlagsBit:
    pushfq                          ; Push RFLAGS onto the stack
    pop             r8              ; Load the RFLAGS from the stack into R8
    mov             cl, al          ; Move the lower 8 bits of RAX into CL
    shr             r8, cl          ; Shift right by CL, so the flags bit is now the LSB of R8
    and             r8, 1           ; Zero all other bits, except the LSB
    
    ;; call the printf function
    NUM_ARGS        = 3
    PUSH            RSP
    PUSH            [RSP]
    ALIGN_STACK     NUM_ARGS
    MOV             RDX, RAX
    LEA             RCX,strCF
    SUB             RSP,32
    CALL            printf
    RESTORE_STACK   NUM_ARGS
    POP             RSP    
    
    ret
    

main proc

    NUM_ARGS        = 2
    PUSH            RSP
    PUSH            [RSP]
    ALIGN_STACK     NUM_ARGS
    LEA             RDX, VectoredHandler
    MOV             RCX,1
    SUB             RSP,32
    CALL            AddVectoredExceptionHandler
    RESTORE_STACK   NUM_ARGS
    POP             RSP       
    
    test            rax, rax
    jz              errorExit
    mov             [hHandler], rax 

    ; Carry flag Bit #1
    ; -----------------
    mov     ax, 0FFFFh      ; 0FFFFh + 1 = 010000h, but since AX is 16 bits,
    add     ax, 1           ; it wraps around the most significant bit into CF                               
    mov     eax, 1          ; Bit #1
    call    PrintFlagsBit
    
    ; Parity flag Bit #2
    ; ------------------    
    mov     al, 00110011b   ; This has 4 set bits - Even Parity
    test    al, al          ; TEST sets the PF = 1 (even parity) based on the value in AL                            
    mov     eax, 2          ; Bit #2
    call    PrintFlagsBit
    
    ; Adjust flag Bit #4
    ; ------------------        
    mov     al, 0Fh         ; 0000 1111 + 0000 0001 = 0001 0000
    add     al, 01h         ; Addition carried 3rd bit to 4th bit
    mov     eax, 4          ; Bit #4
    call    PrintFlagsBit
         
    ; Zero flag Bit #6
    ; -----------------        
    mov     eax, 5          ; Load EAX register with the value 5
    sub     eax, 5          ; Subtract 5 from EAX, result is zero, so ZF is set
    mov     eax, 6          ; Bit #6
    call    PrintFlagsBit        
    
    ; Sign flag Bit #7
    ; -----------------       
    mov     eax, 1          ; Load EAX register with the value 1
    neg     eax             ; Negate the value in EAX, most significant bit is 1, so SF is set
    mov     eax, 7          ; Bit #7
    call    PrintFlagsBit   
    
    ; Trap flag Bit #8
    ; ----------------
    pushfq                   ; Push FLAGS onto the stack
    or qword ptr [rsp], 100h ; Set the Trap Flag bit 0100h = 0000000100000000
    popfq                    ; Pop it back into FLAGS

    ; Interrupt flag Bit #9
    ; ---------------------       
    ;sti                    ; Set Interrupt Flag (already set)
    ;cli                    ; Clear Interrupt Flag
    mov     eax, 9          ; Bit #9
    call    PrintFlagsBit   
    
    ; Direction flag Bit #10
    ; ----------------------       
    std                     ; sets the DF flag, ensuring backward operations.   
    mov     eax, 10         ; Bit #10
    call    PrintFlagsBit   
    cld                     ; clears the DF flag, ensuring forward operations.    
    
    ; Overflow flag Bit #11
    ; ---------------------       
    mov     al, 07Fh        ; AL = 01111111 (unsigned 127, MSB 0)
    add     al, 01h         ; AL = 10000000 (signed -128, MSB 1)
    mov     eax, 11         ; Bit #11
    call    PrintFlagsBit    
    
    NUM_ARGS        = 1
    PUSH            RSP
    PUSH            [RSP]
    ALIGN_STACK     NUM_ARGS
    MOV             RCX,[hHandler]
    SUB             RSP,32
    CALL            RemoveVectoredExceptionHandler
    RESTORE_STACK   NUM_ARGS
    POP             RSP         
    
errorExit:    
    RET
main endp

end

输出显示 VEH 在设置时被调用TF

Bit # 1, Value  = 1
Bit # 2, Value  = 1
Bit # 4, Value  = 1
Bit # 6, Value  = 1
Bit # 7, Value  = 1
Single step exception captured!
ExceptionCode: 80000004
EFlags: 206
Bit # 9, Value  = 1
Bit # 10, Value  = 1
Bit # 11, Value  = 1

使用以下命令构建

if not defined DevEnvDir (
  call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvars64.bat"
)

ml64.exe VEH.asm /link /subsystem:console /defaultlib:kernel32.lib /defaultlib:libcmt.lib