-
Notifications
You must be signed in to change notification settings - Fork 11
Exceptions
VB exceptions are handled by a SEH (Structured Error Handler). It's possible to use these SEH in different ways. Microsoft lets you use them in C++ with special keywords, like __try and __catch.
However, SEH comes in another flavor, which is SEH in stack frames. Although not commonly known, or even documented officially, they exist. See here for more info.
All the sample EXE files I've made set up the SEH by registering the handler on the function prologue. The usual code involves obtaining the address of the current Thread Information Block or TIB, and then setting up an ad-hoc EXCEPTION_REGISTRATION structure.
Let's use as a reference an EXE file that contains the test case with the OnErrorResumeNextTest subroutine. The code is as follows:
Private Sub OnErrorResumeNextTest()
Dim l As Integer
'Dim o As Object
On Error Resume Next
'o.test
MsgBox "This shouldn't be visible " & 1 / 0
MsgBox "This should be visible"
End Sub
The disassembly of said function could look like this:
.text:004033E0 ?OnErrorResumeNextTest@Module1@@AAGXXZ proc near
.text:004033E0 var_D0 = qword ptr -0D0h
.text:004033E0 var_78 = dword ptr -78h
.text:004033E0 var_70 = dword ptr -70h
.text:004033E0 var_68 = dword ptr -68h
.text:004033E0 var_60 = dword ptr -60h
.text:004033E0 var_58 = dword ptr -58h
.text:004033E0 var_50 = dword ptr -50h
.text:004033E0 var_48 = dword ptr -48h
.text:004033E0 var_40 = dword ptr -40h
.text:004033E0 var_38 = dword ptr -38h
.text:004033E0 var_30 = dword ptr -30h
.text:004033E0 var_28 = byte ptr -28h
.text:004033E0 var_20 = dword ptr -20h
.text:004033E0 var_18 = dword ptr -18h
.text:004033E0 var_14 = dword ptr -14h
.text:004033E0 var_10 = dword ptr -10h
.text:004033E0 var_C = dword ptr -0Ch
.text:004033E0 var_4 = dword ptr -4
.text:004033E0 this = dword ptr 8
.text:004033E0
.text:004033E0 push ebp
.text:004033E1 mov ebp, esp
.text:004033E3 sub esp, 18h
.text:004033E6 push offset ___vbaExceptHandler
.text:004033EB mov eax, large fs:0
.text:004033F1 push eax
.text:004033F2 mov large fs:0, esp
.text:004033F9 mov eax, 9Ch
.text:004033FE call ___vbaChkstk
.text:00403403 push ebx
.text:00403404 push esi
.text:00403405 push edi
.text:00403406 mov [ebp-18h], esp
.text:00403409 mov dword ptr [ebp-14h], offset dword_4011D0
.text:00403410 mov dword ptr [ebp-10h], 0
.text:00403417 mov dword ptr [ebp-0Ch], 0
.text:0040341E mov dword ptr [ebp-4], 1
.text:00403425 mov dword ptr [ebp-4], 2
.text:0040342C push 0FFFFFFFFh
.text:0040342E call ds:__imp_@__vbaOnError
; .... more ....
Let's dig a little bit on the first instructions. The first 3 instructions create a local frame, which is a common thing to see. Usually this is called a function prologue, as it creates space on the stack, and sets up register ebp for local addressing of local variables.
Following these, a certainly weird pattern is found.
-
push offset ___vbaExceptHandleris pushing into the stack the address of the imported___vbaExceptHandlerfunction from the MSVBVM60.DLL. -
mov eax, large fs:0is obtaining the value of the first DWORD element of the TIB, because the TIB is stored infs:[0]. It turns out that the first element of the TIB is the current SEH frame. This is followed by apush eax. -
mov large fs:0, espis actually overwriting the current SEH frame pointer with the one created in steps 1 and 2. Even if it doesn't look like, by pushing the exception handler and then the previous SEH frame pointer, the code has created aEXCEPTION_REGISTRATIONstructure on the stack, whose first DWORD element is the new exception handler function, and the second one is the pointer to the old SEH frame. See Figure 2 _except_handler_function. -
mov eax, 9Chand the subsequentcall ___vbaChkstkare not related to the exceptions, HOWEVER, they play a fundamental role in VB applications. This should be covered in another entry on this Wiki, but in short, it allocates the requested size in bytes (0x9c in this example) on the stack, but it also zeroes it. -
push ebx,push esi,push ediare irrelevant. - The following 6
movinstructions are setting up a structure stored in the stack frame. I've figured that this is a structure, and I called itvbaProcedureLocalStorage.-
mov [ebp-18h], espstores the current ESP (stack pointer). -
mov dword ptr [ebp-14h], offset dword_4011D0stores the pointer of another structure, which I calledvbaProcedureInfo. -
mov dword ptr [ebp-10h], 0unknown at this time, otherInfo1 -
mov dword ptr [ebp-0Ch], 0unknown at this time, otherInfo2 -
mov dword ptr [ebp-4], 1this stores the index of the current VB instruction that precedes a call of a function that could raise an exception -
mov dword ptr [ebp-4], 2same as above, but it's important to note that this starts at index 2, as it'll become clear later
-
Now, the structure looks like this:
typedef struct
{
LPVOID * uhm1;
LPVOID * uhm2;
LPVOID * ESP;
vbaProcedureInfo * lpProcedureInfo;
DWORD otherInfo1;
DWORD otherInfo2;
DWORD otherInfo3;
DWORD OnErrorNextInstructionIndex;
} vbaProcedureLocalStorage;
As seen before, the code set up 5 32-bit values on the stack, but it's been proven that the actual structure contains two additional pointers. These are named uhm1 and uhm2, and their meaning is still unknown.
VB's compiler updates the OnErrorNextInstructionIndex on the vbaProcedureLocalStorage structure every single time before it calls a function that could raise an exception.
On the example code provided above, there are two calls to a MsgBox, the first one looks like this:
.text:00403434 mov dword ptr [ebp-4], 3 ; <----------- here OnErrorNextInstructionIndex is updated to "3"
.text:0040343B mov [ebp+var_60], 80020004h
.text:00403442 mov [ebp+var_68], 0Ah
.text:00403449 mov [ebp+var_50], 80020004h
.text:00403450 mov [ebp+var_58], 0Ah
.text:00403457 mov [ebp+var_40], 80020004h
.text:0040345E mov [ebp+var_48], 0Ah
.text:00403465 push offset ___vba@04570278 ; "This shouldn't be visible "
.text:0040346A fld ds:__real@8@3fff8000000000000000
.text:00403470 cmp __adjust_fdiv, 0
.text:00403477 jnz short loc_403481
.text:00403479 fdiv ds:__real@8@00000000000000000000
.text:0040347F jmp short loc_403492
.text:00403481 ; ---------------------------------------------------------------------------
.text:00403481
.text:00403481 loc_403481: ; CODE XREF: Module1::OnErrorResumeNextTest(void)+97↑j
.text:00403481 push dword ptr ds:__real@8@00000000000000000000+4
.text:00403487 push dword ptr ds:__real@8@00000000000000000000
.text:0040348D call __adj_fdiv_m64
.text:00403492
.text:00403492 loc_403492: ; CODE XREF: Module1::OnErrorResumeNextTest(void)+9F↑j
.text:00403492 fnstsw ax
.text:00403494 test al, 0Dh
.text:00403496 jnz loc_4035BD
.text:0040349C sub esp, 8
.text:0040349F fstp [esp+0D0h+var_D0]
.text:004034A2 call ds:__imp____vbaStrR8
.text:004034A8 mov edx, eax
.text:004034AA lea ecx, [ebp+var_28]
.text:004034AD call ds:__imp____vbaStrMove
.text:004034B3 push eax
.text:004034B4 call ds:__imp____vbaStrCat
.text:004034BA mov [ebp+var_30], eax
.text:004034BD mov [ebp+var_38], 8
.text:004034C4 lea eax, [ebp+var_68]
.text:004034C7 push eax
.text:004034C8 lea ecx, [ebp+var_58]
.text:004034CB push ecx
.text:004034CC lea edx, [ebp+var_48]
.text:004034CF push edx
.text:004034D0 push 0
.text:004034D2 lea eax, [ebp+var_38]
.text:004034D5 push eax
.text:004034D6 call ds:__imp____vba@rtcMsgBox ; <----------- MsgBox call here
.text:004034DC lea ecx, [ebp+var_28]
.text:004034DF call ds:__imp_@__vbaFreeStr
.text:004034E5 lea ecx, [ebp+var_68]
.text:004034E8 push ecx
.text:004034E9 lea edx, [ebp+var_58]
.text:004034EC push edx
.text:004034ED lea eax, [ebp+var_48]
.text:004034F0 push eax
.text:004034F1 lea ecx, [ebp+var_38]
.text:004034F4 push ecx
.text:004034F5 push 4
.text:004034F7 call ds:__imp_@__vbaFreeVarList
.text:004034FD add esp, 14h
The first instruction of this snippet has updated the index to 3, prior to calling the rtcMsgBox (VB did some behind-the-scenes variable duplication before calling rtcMsgBox). Please note that this causes an exception inside
The next update of this index is to 4:
.text:00403500 mov dword ptr [ebp-4], 4 ; <----------- here OnErrorNextInstructionIndex is updated to "4"
.text:00403507 mov [ebp+var_60], 80020004h
.text:0040350E mov [ebp+var_68], 0Ah
.text:00403515 mov [ebp+var_50], 80020004h
.text:0040351C mov [ebp+var_58], 0Ah
.text:00403523 mov [ebp+var_40], 80020004h
.text:0040352A mov [ebp+var_48], 0Ah
.text:00403531 mov [ebp+var_70], offset ___vba@045702B8 ; "This should be visible"
.text:00403538 mov [ebp+var_78], 8
.text:0040353F lea edx, [ebp+var_78]
.text:00403542 lea ecx, [ebp+var_38]
.text:00403545 call ds:__imp_@__vbaVarDup
.text:0040354B lea edx, [ebp+var_68]
.text:0040354E push edx
.text:0040354F lea eax, [ebp+var_58]
.text:00403552 push eax
.text:00403553 lea ecx, [ebp+var_48]
.text:00403556 push ecx
.text:00403557 push 0
.text:00403559 lea edx, [ebp+var_38]
.text:0040355C push edx
.text:0040355D call ds:__imp____vba@rtcMsgBox
.text:00403563 lea eax, [ebp+var_68]
.text:00403566 push eax
.text:00403567 lea ecx, [ebp+var_58]
.text:0040356A push ecx
.text:0040356B lea edx, [ebp+var_48]
.text:0040356E push edx
.text:0040356F lea eax, [ebp+var_38]
.text:00403572 push eax
.text:00403573 push 4
.text:00403575 call ds:__imp_@__vbaFreeVarList
.text:0040357B add esp, 14h
Windows will call the error handlers in the SEH frame in order. As calls to most of MSVBVM60.DLL's can cause an exception, this handler should handle the case when that happens.
The handler has to have the following declaration:
EXCEPTION_DISPOSITION __cdecl __vbaExceptHandler(
struct _EXCEPTION_RECORD *ExceptionRecord,
void *EstablisherFrame,
struct _CONTEXT *ContextRecord,
void *DispatcherContext
)
MSVBVM60.DLL raises VB-exceptions with an ExceptionCode of 0xC000008F, two arguments, and both of them are 0xdeadcafe.
This means, if we receive an exception, it's possible to figure out if it was raised by MSVBVM60.DLL by checking the value of the ExceptionRecord.
To be continued!