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:
True unused interrupts (these interrupts should always be unused):
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 char* EIP = 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_FIRST, VEH);
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 eax, eax } // Zeroing the register
__asm { mov v_eax, eax } // 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_eax, eax } // Store EAX register in variable
printf("eax ud0 = %08X\n", v_eax); // Printing variable
// UD1
UD1
__asm { mov v_eax, eax } // Store EAX register in variable
printf("eax ud1 = %08X\n", v_eax); // Printing variable
// UD2
UD2
__asm { mov v_eax, eax } // 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, 2 } // Store 2 in EDI register
__asm { mov esi, 2 } // Store 2 in ESI register
__asm { int 0x7D } // Calling Interrupt
__asm { mov v_eax, eax } // Store EAX register in variable
printf("eax int7D = %08X\n", v_eax); // Printing variable
// int 7E (EAX=EDI-ESI)
__asm { mov edi, 2 } // Store 2 in EDI register
__asm { mov esi, 2 } // Store 2 in ESI register
__asm { int 0x7E } // Calling Interrupt
__asm { mov v_eax, eax } // 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_eax, eax } // 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!