View Single Post
Author Message
Sam839
Zero Posts
Join Date: Apr 2019
Old 02-18-2021 , 12:42   [DEV/Windows] Interrupts, undefined opcodes.
Reply With Quote #1

Interrupt handler based on VEH (SEH) exceptions with unused interrupts and unused opcodes.

Content:
  • About SEH and VEH.
  • About Interrupts and unused opcodes.
  • How it works with VEH (SEH)?
  • Why it?


About SEH and VEH.

Structured exception handling (SEH) is a Microsoft extension to C to handle certain exceptional code situations, such as hardware faults, gracefully. Source

Vectored exception handlers are an extension to structured exception handling (SEH). In a nutshell, vectored exception handling is similar to regular SEH, with three key differences:
  • Handlers aren't tied to a specific function nor are they tied to a stack frame.
  • The compiler doesn't have keywords (such as try or catch) to add a new handler to the list of handlers.
  • Vectored exception handlers are explicitly added by your code, rather than as a byproduct of try/catch statements.
Source 1
Source 2

VEH does not replace Structured Exception Handling (SEH), rather VEH and SEH coexist, with VEH handlers having priority over SEH handlers.

About Interrupts and unused opcodes.

Interrupts:
In modern operating systems, the programmer often doesn't need to use interrupts. In Windows, for example, the programmer conducts business with the Win32 API. However, these API calls interface with the kernel, and the kernel will often trigger interrupts to perform different tasks. In older operating systems (specifically DOS), the programmer didn't have an API to use, and so they had to do all their work through interrupts. Source
A software interrupt is requested by the processor itself upon executing particular instructions or when certain conditions are met. Every software interrupt signal is associated with a particular interrupt handler. Source

I did do research work and can tell which interrupts are NOT used in the standard windows of the latest version.
Unused interrupts:
  • 0x0F
  • 0x16-0x1E
  • 0x21-0x28
  • 0x2A-0x2B
  • 0x2E
  • 0x37-0x50
  • 0x53-0x60
  • 0x63-0x6F
  • 0x78-0x80
  • 0x83-0x91
  • 0x93-0xA0
  • 0xA4-0xAF
  • 0xB8-0xD0
  • 0xD3-0xD6
  • 0xD9-0xDE
  • 0xE0
  • 0xE4-0xFD
  • 0xFF

Undocumented interrupts:
  • 0x17
  • 0x1A
  • 0x2A
  • 0x47
  • 0x4B
  • 0x53-0x57
  • 0x60
  • 0x69
  • 0x6F
  • 0x79
  • 0x80
  • 0x83-0x85
  • 0xF2-0xF9
  • 0xFC
  • 0xFD

Reserved for user interrupts:
  • 0x63-0x66
  • 0xF1

True unused interrupts (these interrupts should always be unused):
  • 0x7D
  • 0x7E
To avoid errors with driver interrupts and future interrupts, use truly unused interrupts.

Unused (Undefined) opcodes:
Generates an invalid opcode exception. UD (Instruction)
Unused opcodes (instructions):
  • UD (UD0) - 0x0F 0xFF
  • UD1 - 0x0F 0xB9
  • UD2 - 0x0F 0x0B

How it works with VEH (SEH)?

So. We repeated the theory and learned something new, but how does it work? I will briefly talk about this.
The called interrupt refers to the kernel, the kernel executes the code located at a specific address in the interrupt vector. If the interrupt is not used, the address will be "null", and the program code will refer to a non-existent address and throw an exception (0xC0000005 - EXCEPTION_ACCESS_VIOLATION), in this case we can use the exception handler and then read and modify the registers!

Sample program:
PHP Code:
#include <Windows.h>
#include <iostream>

#define CALL_FIRST 1 // Will be called first
#define CALL_LAST  0 // Will be called last after first

PVOID pVEH nullptr;

LONG WINAPI VEH(PEXCEPTION_POINTERS pExcPtrs) {
    const 
unsigned charEIP const_cast<const unsigned char*>(reinterpret_cast<unsigned char*>(pExcPtrs->ContextRecord->Eip)); // Exception EIP
    
if ((EIP[0] == 0x0Fu) && (EIP[1] == 0xFFu) && (pExcPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION)) { // IF EIP = UD (UD0) AND ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION
        
pExcPtrs->ContextRecord->Eip += 2// Bypass current instruction
        
pExcPtrs->ContextRecord->Eax 1// Store 1 in EAX register
        
return EXCEPTION_CONTINUE_EXECUTION;
    }
    if ((
EIP[0] == 0x0Fu) && (EIP[1] == 0xB9u) && (pExcPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION)) { // IF EIP = UD1 AND ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION
        
pExcPtrs->ContextRecord->Eip += 2// Bypass current instruction
        
pExcPtrs->ContextRecord->Eax 2// Store 2 in EAX register
        
return EXCEPTION_CONTINUE_EXECUTION;
    }
    if ((
EIP[0] == 0x0Fu) && (EIP[1] == 0x0Bu) && (pExcPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION)) { // IF EIP = UD2 AND ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION
        
pExcPtrs->ContextRecord->Eip += 2// Bypass current instruction
        
pExcPtrs->ContextRecord->Eax 3// Store 3 in EAX register
        
return EXCEPTION_CONTINUE_EXECUTION;
    }
    if ((
EIP[0] == 0xCDu) && (pExcPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)) // IF EIP = Interrupt AND ExceptionCode = EXCEPTION_ACCESS_VIOLATION
    
{
        switch (
EIP[1]) {
            case 
0x7Du: { // IF InterruptNumber is 0x7D (int 0x7D)
                
pExcPtrs->ContextRecord->Eip += 2// Bypass current instruction
                
unsigned int edi pExcPtrs->ContextRecord->Edi;
                
unsigned int esi pExcPtrs->ContextRecord->Esi;
                
pExcPtrs->ContextRecord->Eax edi esi// EAX=EDI+ESI
                
break;
            }
            case 
0x7Eu: { // IF InterruptNumber is 0x7E (int 0x7E)
                
pExcPtrs->ContextRecord->Eip += 2// Bypass current instruction
                
unsigned int edi pExcPtrs->ContextRecord->Edi;
                
unsigned int esi pExcPtrs->ContextRecord->Esi;
                
pExcPtrs->ContextRecord->Eax edi esi// EAX=EDI-ESI
                
break;
            }
            default:
                
pExcPtrs->ContextRecord->Eip += 2// Bypass current instruction
                
pExcPtrs->ContextRecord->Eax 0// DEFAULT: EAX=0
        
}
    }
    return 
EXCEPTION_CONTINUE_EXECUTION;
}

#define UD0 __asm _emit 0x0F __asm _emit 0xFF
#define UD1 __asm _emit 0x0F __asm _emit 0xB9
#define UD2 __asm _emit 0x0F __asm _emit 0x0B

int main(void) {
#pragma region Adding VEH Handler
    
pVEH AddVectoredExceptionHandler(CALL_FIRSTVEH);
    if (!
pVEH) {
        
printf("AddVectoredExceptionHandler error!\n");
        return 
0;
    }
#pragma endregion

#pragma region Start
    // Reset+Start
    
unsigned int v_eax 0u// Variable for EAX
    
__asm { xor eaxeax // Zeroing the register
    
__asm mov v_eaxeax // Store EAX register in variable
    
printf("eax start = %08X\n"v_eax); // Printing variable
#pragma endregion

#pragma region Running UDs
    // UD0
    
UD0
    __asm 
mov v_eaxeax // Store EAX register in variable
    
printf("eax ud0 = %08X\n"v_eax); // Printing variable
    // UD1
    
UD1
    __asm 
mov v_eaxeax // Store EAX register in variable
    
printf("eax ud1 = %08X\n"v_eax); // Printing variable
    // UD2
    
UD2
    __asm 
mov v_eaxeax // Store EAX register in variable
    
printf("eax ud2 = %08X\n"v_eax); // Printing variable
#pragma endregion

#pragma region Running Interrupts
    // int 7D (EAX=EDI+ESI)
    
__asm mov edi// Store 2 in EDI register
    
__asm mov esi// Store 2 in ESI register
    
__asm int 0x7D // Calling Interrupt
    
__asm mov v_eaxeax // Store EAX register in variable
    
printf("eax int7D = %08X\n"v_eax); // Printing variable
    // int 7E (EAX=EDI-ESI)
    
__asm mov edi// Store 2 in EDI register
    
__asm mov esi// Store 2 in ESI register
    
__asm int 0x7E // Calling Interrupt
    
__asm mov v_eaxeax // Store EAX register in variable
    
printf("eax int7E = %08X\n"v_eax); // Printing variable
    // int F1 (Default: EAX=0)
    
__asm int 0xF1 // Calling Interrupt
    
__asm mov v_eaxeax // Store EAX register in variable
    
printf("eax intF1 = %08X\n"v_eax); // Printing variable
#pragma endregion

#pragma region Removing VEH Handler
    
if (pVEH) {
        
RemoveVectoredExceptionHandler(pVEH);
    }
#pragma endregion
    
return 0;

Why it?

The reasons for this may be:
  • Security - The use of interrupts complicates decompilation, but this can be bypassed if it is known that the program uses VEH (SEH).
  • Optimization - Large algorithms can be used without the need to import functions from a specific library, because VEH applies to the entire program and its modules.
  • Fun - How about the BIOS in the program?

Thanks for attention!

Last edited by Sam839; 03-21-2021 at 22:38. Reason: Added exception code for example + added php syntax.
Sam839 is offline