My goal is to find what two-byte opcodes generate an illegal instruction exception.
For example, opcodes 0F 0B UD2
raises an invalid opcode exception. The UD2
instruction is provided for software testing to explicitly generate an invalid opcode.
Warning Snake oil code ahead as I'm not familiar with Windows internals.
The code below allocates a 4K page with read/write/execute permissions and using UD2
as a starting point it tries to determine all the possible two-byte opcodes.
First, it copies the two-byte opcodes to the last two bytes of the 4K page
then executes them and checks for the exception code.
I figured that executing the last two page bytes would either
- Generate an illegal exception
EXCEPTION_ILLEGAL_INSTRUCTION
with exactly two bytes. - Generate an access violation
EXCEPTION_ACCESS_VIOLATION
when extending beyond the 4K page.
Running the code below shows interesting instructions plus many unknowns too:
Illegal opcodes 0x0f 0x0b (error 0xc000001d)
ud2 - Generates an invalid opcode.
Illegal opcodes 0x0f 0x37 (error 0xc000001d)
getsec - Exit authenticated code execution mode.
Illegal opcodes 0x0f 0xaa (error 0xc000001d)
rsm - Resume operation of interrupted program.
Question
The hack'ish code runs fine in this opcode range
Executing opcodes 0x0f 0x0b ... Executing opcodes 0x0f 0xcb
until it encounters these two opcodes
0x0f 0xcc bswap esp
It seems anything that manipulates the stack pointer causes issues whereby it's stuck at this point (clicking Continue
just repeats the message)
I've tried moving the opcode execution into its own thread since they have their own stack, but that didn't help!
Is there a way to preserve the stack pointers RSP
and RBP
or maybe there's a simple fix to resolve it?
(Built using M$ Visual C 2019)
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <intrin.h>
// The UD2 (0x0F, 0x0B) instruction is guaranteed to generate an invalid opcode exception.
DWORD InstructionResult;
void ExecuteOpcodes(LPVOID mem)
{
__try
{
// Execute opcodes...
((void(*)())((unsigned char*)mem 0xFFE))();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
InstructionResult = GetExceptionCode();
}
}
int main()
{
LPVOID mem = VirtualAlloc(NULL, 2, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
DWORD oldProtect = VirtualProtect(mem, 2, PAGE_EXECUTE_READWRITE, &oldProtect);
// Start searching at the UD2 (0x0F, 0x0B) instruction which is guaranteed to generate an invalid opcode exception.
for (int i = 15; i <= 255; i )
{
for (int j = 11; j <= 255; j )
{
// Write two byte opcodes at the 4K page end.
*((unsigned char*)mem 0xFFE) = i;
*((unsigned char*)mem 0xFFF) = j;
printf("Executing opcodes 0xx 0xx\n",i,j);
HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ExecuteOpcodes, mem, 0, 0);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
if (InstructionResult == EXCEPTION_ILLEGAL_INSTRUCTION)
{
printf("Illegal opcodes 0xx 0xx (error 0xx)\n", i, j, InstructionResult);
}
}
}
VirtualFree(mem, 0, MEM_RELEASE);
return 0;
}
CodePudding user response:
maybe there's a simple fix to resolve it?
The UNIX-standard way to resolve this is to do all the test execution in a child process.
When I last worked on Windows 15 years ago, creating a child process was very expensive (slow). But since you have fewer that 64K byte combinations to try, even a slow mechanism will get you all the answers in at most a few hours.