Search code examples
windowsdebuggingassemblyx86-64masm64

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


The MASM program below enables the Trap Flag (TF) but it causes the program to exit prematurely

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

In order to enable single stepping of a processor, the x86 architecture provides a single bit Trap Flag (TF) that can be set in the FLAGS register. The FLAGS register is a status register on x86 processors that contains various bit-flags that control or describe the state of the processor. If the Trap Flag is set to true then the processor will make a call to interrupt 1 (INT 1) after each instruction is executed.

; +------+--------+-------------+----------------------------------------+
; | 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

When the TF is not set, the program outputs

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

When the TF is set, the program outputs

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

Interestingly, stepping thru the program with Windbg, the program tries to set TF=1, but Windbg prevents it?

tf

Question

Is there an easy way to implement an INT 1 handler, so the program can catch it?

Maybe something like this

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

    iretd   ; Return from interrupt handler
INT1handler ENDP

Comments

From the comments section, it was noted

If you are running under a debugger, the debugger gets the trace interrupt. This is one trick programs use as an anti-debugger technique.

Added this C program to demo that trick:

#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;
}

Solution

  • Added Vectored Exception Handling (VEH) to catch the INT 1.

    The program now completes normally.

    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
    

    The output shows the VEH is called when TF is set

    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
    

    Built using these commands

    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