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

146 Views Asked by At

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

There are 1 best solutions below

0
vengy On BEST ANSWER

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