Understanding EXCEPTION_CONTINUE_EXECUTION

The structured exception handling (SEH) mechanism allows you to return to the instruction that generated the exception and try to execute it again. To do this, in the block __except need to pass a value EXCEPTION_CONTINUE_EXECUTION. It is important to remember that the return is to an assembler instruction, not a high-level language instruction.

Consider an example of a 32-bit application in which division by 0 occurs:

DWORD a = 0, b = 1, res = 0;

__try
{
	res = b / a;
	printf("res = %d\n", res);
}

__except (CustomFilter(&a, GetExceptionInformation())){ }

Let’s take a look at the assembly code:

By examining the code in the debugger, you can see that the exception is thrown by the command div. In the first argument of this instruction, located in the register EAXthe dividend is found, and the divisor (second argument) is taken from memory (dword ptr [a]). Thus, to “fix” the exception, you need to change the value stored at dword ptr [a], i.e. in a variable a.

Our exception filter function will take as the first argument the address of the memory region (variable a), the value in which you want to change. Next, the function dereferences the pointer and places a non-zero value in the specified memory region:

DWORD WINAPI CustomFilter(PVOID Arg, PEXCEPTION_POINTERS ExPtrs)
{
	// Only for x86, NOT x64!
	if (ExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
	{
		DWORD* ptr = (DWORD*)Arg;
		(*ptr)++;

		printf("Argument with zero value has been incremented\n");
		return EXCEPTION_CONTINUE_EXECUTION;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

As you can see, after our corrections, the calculations were performed correctly and the instruction following the division was executed – displaying the result on the screen:

Now let’s compile our example for x64 and run:

Now our program enters an infinite loop. What’s the matter?

Let’s take a look at the assembler code of the block __try:

Now the second argument of the instruction div stored in the register ECX. Therefore, even changing the value in memory (variable a), the value in the register ECX remains untouched. Therefore, when the exception handler returns to the instruction div, division by zero will be done again, an exception will be thrown, and then our exception handler will be called. Thus, we got an infinite loop.

To “fix” the exception, you need to change the value in the register ECXand then re-execute the instruction div. Luckily, this is pretty easy to do. The exception handler restores the context it had when the exception was thrown and then re-executes the instruction that caused the exception. The context stores, among other things, the values ​​of the registers. The thread context is stored in the structure CONTEXTthe pointer to which is stored in the structure EXCEPTION_POINTERSwhich can be obtained in the block __except with a macro GetExceptionInformation. GetExceptionInformation can only be called in an exception filter. In structure CONTEXT there is a field rcxThe in which the register value is stored RCXthe lower part of which is the register ECX. This value will be restored when the instruction that generated the exception is executed again.

Then the function CustomFilter should be modified as follows:

DWORD WINAPI CustomFilter(PVOID Arg, PEXCEPTION_POINTERS ExPtrs)
{
	// Only for x64, NOT x86!
	if (ExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
	{
		ExPtrs->ContextRecord->Rcx = 1;

		printf("Argument with zero value has been incremented\n");
		return EXCEPTION_CONTINUE_EXECUTION;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

Now the application does not enter the infinite loop, correctly performs calculations and executes the instruction following the division – displaying the result on the screen:

In order for the function CustomFilter worked for both bit depths (x86 and x64), you can use the conditional compilation directives:

DWORD WINAPI CustomFilter(PVOID Arg, PEXCEPTION_POINTERS ExPtrs)
{
	if (ExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO)
	{
		#if _WIN64
		ExPtrs->ContextRecord->Rcx = 1;
		
		#elif _WIN32
		DWORD* ptr = (DWORD*)Arg;
		(*ptr)++;

		#endif

		printf("Argument with zero value has been incremented\n");
		return EXCEPTION_CONTINUE_EXECUTION;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

Using a similar approach, we implement the handling of null pointer access errors. The code of the exception that occurs when trying to dereference a null pointer is − EXCEPTION_ACCESS_VIOLATION. However, this exception may also occur in other cases, for example, access to an invalid address. We will consider the simplest case. Additional checks must be performed to distinguish address zero from other exceptions.

The code that throws the exception:

BYTE* mem = NULL;

__try
{
	mem[0] = 'A';

	printf("mem = %s\n", mem);

	HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, mem);
}

__except(CustomFilter2(&mem, GetExceptionInformation())) { }

The null pointer dereference will happen in the line:

mem[0] = 'A';

This code translates into the following instructions:

Address stored in a variable memeis placed in the register EDX. To register ECX an index is placed, more precisely, an offset in bytes, a multiple of the size of the array elements, relative to the beginning of the memory region. Code 41h – hexadecimal character code value ‘A’ in an ASCII table. An access violation exception will be thrown by the statement

mov byte ptr [edx+ecx], 41h

Thus, to “fix” we need to put in the register EDX address of a valid memory region.

On x64, the code that throws the exception looks like this:

Here the offset is stored in a register RAXand the address of the memory region is in the register RCX. To “fix” the address of the correct memory region must be placed in the register RCX.

In the exception handler, we select a memory region in Heap and write data there – word characters Hello. The address of the given region must be stored not only in the appropriate register in the context, but also in the variable memein order to be able to free the allocated memory.

Function code CustomFilter2which works for both bit depths (x86 and x64):

DWORD WINAPI CustomFilter2(PVOID Arg, PEXCEPTION_POINTERS ExPtrs)
{
	if (ExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
	{
		BYTE** ptr = (BYTE**)Arg;

		*ptr = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 100);

		#if _WIN64
		ExPtrs->ContextRecord->Rcx = (ULONG_PTR)*ptr;

		#elif _WIN32
		ExPtrs->ContextRecord->Edx = (DWORD)*ptr;

		#endif

		(*ptr)[0] = 'H';
		(*ptr)[1] = 'e';
		(*ptr)[2] = 'l';
		(*ptr)[3] = 'l';
		(*ptr)[4] = 'o';

		printf("Memory has been allocated\n");
		return EXCEPTION_CONTINUE_EXECUTION;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

After starting the application, we see that after the exception is generated, a “correction” occurs – the allocation of a memory region and the writing of characters there Helloafter which the instruction that generated the exception is executed again, as a result of which the first character is replaced by ‘A’. The next step is to free the allocated memory.

What else can be done? You can return not to the machine instruction that generated the exception, but to one of the previous ones. For example, to an instruction that places the divisor value from memory into a register. The address of the next instruction to be executed is stored in a register. EIP (x86) or RIP (x64). Therefore, the value of this register is stored in the field eip (x86) or rip (x64) structures CONTEXT. Also, the address of the instruction that generated the exception is stored in the field ExceptionAddress invested in EXCEPTION_POINTERS structures EXCEPTION_RECORD.

Consider the already well-known division-by-zero example:

DWORD a = 0, b = 1, res = 0;

__try
{
	res = b / a;
	printf("res = %d\n", res);
}

__except (CustomFilter3(&a, GetExceptionInformation())){ }

To determine the address of the instruction to which we need to return control from the handler, we will use the disassembled listing.

x86:

As we said earlier, the divisor value in x86 code is taken directly from memory. Therefore, change the value of the register EIP we do not need. You only need to change the value of the variable awhich we already did in the previous example.

x64:

But with x64 code, the situation is more interesting. Variable value a taken from memory and placed in a register EAX:

mov eax, dword ptr [a]

And then this value is pushed onto the stack at the address [rbp+174h]:

mov dword ptr [rbp+174h], eax

Next, the value stored at this address is placed in the register ECX:

mov ecx, dword ptr [rbp+174h]

And finally the value in the register ECX used as a divisor in a command div:

div eax, ecx

Therefore, after changing the value of the variable ayou need to return to the instruction that loads the value a from memory, i.e. to:

mov eax, dword ptr [a]

Calculate the address difference between the command that caused the exception (div) and an instruction that reads the value of a from memory:

Delta = 0x7FF7A16D1992 - 0x7FF7A16D197E = 0x14 = 20

Thus, to reread the value a from memory needed from register value RIP subtract 20.

Filter function code CustomFilter3:

DWORD WINAPI CustomFilter3(PVOID Arg, PEXCEPTION_POINTERS ExPtrs)
{
	if (ExPtrs->ExceptionRecord->ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO) 
  {
		DWORD* ptr = (DWORD*)Arg;
		(*ptr)++;

		#if _WIN64
		ExPtrs->ContextRecord->Rip -= 20;
		#endif

		printf("Argument with zero value has been reinitialized\n");
		return EXCEPTION_CONTINUE_EXECUTION;
	}
	return EXCEPTION_CONTINUE_SEARCH;
}

When running our application, we again get the corrected result:

As you can see, structured exception handling is a very powerful and useful mechanism that not only allows you to “catch” an exception, but also correct the data that led to its generation.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *