❌

Normal view

There are new articles available, click to refresh the page.
Before yesterdayAvast Threat Labs

Scripting Arbitrary VB6 Applications

4 January 2023 at 15:29

While doing malware analysis it is often required to interact with running code to discover how it operates. This can be done through techniques such as API hooking, debugging, system monitoring etc.Β Some tasks may even require the extraction, reconstitution, or reuse of malware code in order to perform a certain duty. This is common for code such as domain name generation and decryption routines.

Anytime there is an easy way to reuse existing functionality, my interest is piqued. The research presented in this post gives us another powerful mechanism of code reuse. The technique we will be detailing today is a method in which we can grant ourselves scripting access to any existing Visual Basic 6 (VB6) executable. This technique does not require any source code modifications.

Once implemented, we can access any loaded forms, embedded controls, and public members. This includes access to any class hierarchies held as form level public instances. With some additional work, any live class instance in the program could be accessed in a similar manner. Manually creating new instances of internal classes has also proven possible.Β 

Similar techniques have been explored in the past. What follows is my run at the problem set.

Background

All VB6 objects are COM Objects which support the IDispatch interface. This is a core feature of the language and allows for them to be trivially utilized by scripting engines. Since it is so easy, it is common for developers to internally add scripting support to their applications for automation purposes.

To develop a component that allows for external automation, Visual Basic 6 supports the ActiveX DLL and ActiveX Exe project types. If you have ever used a script which calls CreateObject(), you have already worked with ActiveX COM Objects. Support for COM is integrated deeply into the Windows operating system and entails many powerful features.

Knowing that all VB6 COM objects are inherently scriptable, can this functionality be enabled for random executables without source code access?

Probing the runtime

In the exploration of this question, I started examining the runtime to see how I might easily extract live object references and manually implement scriptable access.

The first place to look, with the largest impact, is the Forms object.

This object holds a reference to the loaded forms for each VB component. Once we have access to an individual form, we then would also have access to all of its embedded controls and public members. This would be a significant first step and where we begin our journey.

If we look at a native executable which uses the global Forms object we will see the following code generated:

This is easy enough to replicate and should give us a reference to the global Forms object on demand. (Note each VB component gets its own Global object. An ActiveX DLL can not access the main executables forms unless explicitly passed a reference)

Before we can test this theory, we first need a method to execute our own code inside the host VB6 process. DLL injection is the obvious answer, however it is not quite as straight forward as you might think.

There are two factors that must be considered when trying to run injection code in a VB6 process.

The first is runtime initialization. A certain number of internal steps must occur before you call any runtime functions. In our previous paper Binary Reuse of VB6 PCode Functions, this is what the call to CreateIExprSrvObj accomplished. We could inject into a fully loaded process, however that has some side effects which we will discuss later on. For our purposes we will require injection at process startup.

The second thing we must consider is that all VB6 executables use the single threaded apartment model (STA). To use runtime functions, we need to work within the main VB6 thread. Working from other threads will crash the process as the runtime tries to access Thread Local Storage (TLS) members it expects to exist. While people have devised ways to work in multiple threads with VB6, we will avoid it here.

Our initial criteria are:

  • must inject at process startup
  • can not call runtime functions until initialized
  • must only call runtime functions from main VB6 thread

The first experiment was to inject at startup and then place a hook on the user32.BeginPaint API. This hook is called from the main VB thread during form creation. At this point the runtime is fully initialized and ready for use. I created a quick mockup and gave it a shot.

Here we encounter our first road bump. Apparently vbaNew2 can not actually create an instance of this object and throws a Class not registered error. This is strange because we are literally using code lifted from the compiler itself.Β 

To validate this result I first confirmed my use of the vbaNew2 function with other known good data and then tested it while running from within the hook. Both worked.

I then took a closer look at the sample VB application and found a little surprise. In the disassembly above, we see it check to see if a cached object reference is already alive. If so, then creation is skipped.

The surprise is, even on its very first use in Form_Load, the object reference is already set. The vbaNew2 call is never used and merely a compiler artifact. In fact forcing the call to vbaNew2 leads to the same Class not registered error.Β 

The Forms object reference is being set before any user code executes. This indicates it must be a special case set by the runtime somewhere. A hardware breakpoint on the objRef_dataSect address quickly confirms this.

The VB runtime has an internal function named TipRegAppObject. This function will scan the VBHeader.ProjectInfo.ExternalTable looking for an entry of type 6 with a matching CLSID. If this is found, it will manually set the object instance address.

If the application developer never used the global Forms class, TipRegAppObject has nothing to do and no reference will be set.

We can not formally create the class we want, If the developer did not use it, then it will not be cached anywhere that is easy to grab. We could search for another way to try to find it, but we already have a universal spot where it is guaranteed to appear.

At this point I decided to hook an internal runtime function. Hooking a hardcoded offset has some downsides like locking us to a specific DLL version. For a research tool, this is an acceptable tradeoff.Β 

After some more analysis, I decided to hook one layer above TipRegAppObject at CVBApplication::Init(CVBApplication *this).

This gives us access to the full CVBApplication object. This includes access to more internal classes and a reference to the executable’s VBHeader structure.

Implementation

The initial injection routine now sets two hooks. One on CVBApplication::Init to grab a reference to internal runtime classes. The second on BeginPaint which allows us to trigger in the main VB thread once initialization is complete.

The next thing to consider is how to execute our final payload within the main VB6 thread. One technique is to simply load our own ActiveX DLL into the process passing a reference to the main components Forms object. This is basically forcing a plugin model into the application. This should work fine however it is not my first choice.Β 

My primary target is to gain some kind of remote scripting access. This is where the magic of the Running Object Table (ROT) comes into play. The ROT is how ActiveX exes register themselves as available for use with GetObject().

The COM object lives in one executable running as a server. External clients then connect and use it from another process.

While experimenting with manual ROT registration, I noticed that it worked for form objects, but would not allow access to internal classes.

Error: 0x62 - A property or method call cannot include a reference to a private object, either as an argument or as a return value

We know that the core implementation of the class fully supports scripting. This is not a problem when using an internal script engine or a plugin model. What difference is there between an ActiveX Exe class, and a standard executable class?

Comparing the two in a structure viewer, we find that the Object.ObjectType of ActiveX Exe classes have bit 0x800 set. If we patch this on disk, we now gain full access to all standard classes. The ObjectType can also be patched in memory, but it must be patched before the class is created to take effect. This is why we must inject at process creation.Β 

Our CVBApplication::Init hook is a perfect time to do this since we already have the reference to the VBHeader structure and no class instances have been created yet. Our complete CVBApplication_Init hook looks like the following:

The complete BeginPaint hook is below:

In this code we first disable our hook so it will not trigger again. We only needed an initial foothold in the main VB thread.

For continued access, we register a new System Menu item and subclass the main window.Β 

This allows us to manually trigger new features we might want in the future. With the window subclass in place, I also implemented a basic InterProcess Communications (IPC) server for programmatic access. These additions are not required but are useful for future exploration.Β 

Finally we register the main executable’s global Forms object in the ROT as remote.forms.

With this complete everything is now up and running!

A Windows Script Host (WSH) Javascript that interacts with our test application is shown below:

Even Python can be used:

Conclusion

In this post we have covered how to make any VB6 application remotely scriptable using built in features of the language and operating system.

While we did encounter several snags along the way, everything turned out to be manageable and resulted in a successful trial.

For a brief recap of events:

  • Target VB6 process is started with an injection DLL
  • New thread hooksΒ  CVBApplication_Init and BeginPaint
  • CVBApplication_Init hook:
    • Stores a reference to internal VB objects during runtime initialization
    • Walks VBHeader.ProjectInfo.ObjectTable setting all classes public
  • BeginPaint hook:
    • Runs from main VB6 thread
    • Adds a system menu item and subclasses main window for IPC (optional)
    • Registers internal Forms object in the ROTΒ 
  • IPC allows for bidirectional communications between injector and target process.Β 

Our first inroads are based on the forms collection for simplicity and depth of impact. This technique can be applied to any COM object instance.Β 

In addition to the forms collection, access to other internal objects can be intercepted with hooks on various runtime API such as vbaNew, vbaNew2, vbaFreeObj etc.Β 

Some thoughts on future innovations of this technique:

  • track new class instances and add them to an exposed VB Collection in ROT
  • injector keeps a visual manager of live objects (using IPC callbacks)Β 
  • injector implements its own script host for integration
  • Ability to stall host process while accessing transitory objects
  • artificially increment reference counts to keep instances alive.Β 
  • Trigger arbitrary class/form creation using IPC and vbaNew (already tested)

Full source code for this research is included in the following git repository. This includes an injector, DLL, test app, and demonstration scripts.

The code is being released as a proof of concept work. Adaptations may be required for specific targets. This research is just the beginning of what is possible with this technique.

The post Scripting Arbitrary VB6 Applications appeared first on Avast Threat Labs.

Recovery of function prototypes in Visual Basic 6 executables

26 October 2022 at 13:53

I was recently probing into the Visual Basic 6 format trying to see how the IDispatch implementation resolved vtable offsets for functions.

While digging through the code, I came across several structures that I had not yet explored. These structures revealed a trove of information valuable to reverse engineers.

As we will show in this post, type information from internal structures will allow us to recover function prototypes for public object methods in standard VB6 executables.

This information is related to IDispatch internals and does not require a type library like those found with ActiveX components.

For our task of malware analysis, a static parsing approach will be of primary interest.

Background

Most VB6 code units are COM objects which support the IDispatch interface. This allows them to be easily passed around as generic types and used with scripting clients.

IDispatch allows functions to be called dynamically by name. The following VB6 code will operate through IDispatch methods:

This simple code demonstrates several things:

  • VB6 can call the proper method from a generic object type
  • named arguments in the improper order are correctly handled
  • values are automatically coerced to the correct type if possible (here the string β€œ5” is converted to long)

If we add a DebugBreak statement, we can also see that the string β€œmyFunc” is passed in as an argument to the underlying __vbaLateMemNamedCallLd function. This allows IDispatch::Invoke to call the proper function by string name.

From this we can intuit that the IDispatch implementation must know the complete function prototype along with argument names and types for correct invocation to occur.

We can further test this by changing an argument name, adding unexpected arguments, or setting an argument type to something that can not be coerced too long.

Each of these conditions raises an error before the function is called.

Note that there is no formal type library when this code is compiled as a standard executable. This type information is instead embedded somewhere within the binary itself.

So where is this type information stored, and how can we retrieve it?

The Hunt

If we start probing the runtime at BASIC_CLASS_Invoke we will encounter internal functions such as FuncSigOfMember, EpiGetInvokeArgs, CoerceArg, and so on. As we debug the code, we can catch access to known VB6 structures and watch where the code goes from there.

Before we get deeper, I will give a quick high-level overview of the Visual Basic 6 file format.

VB6 binaries have a series of nested structures that layout all code units, external references, forms etc. They start with the VBHeader and then branch out, covering the various aspects of the file.

In the same way that the Windows loader reads the PE file to set up proper memory layout, the VB runtime (msvbvm60.dll) reads in the VB6 file structures to ready it for execution within its environment.

Good references for the VB6 file format include:

The structure and fields names I use in this document primarily come from the Semi-VBDecompiler source. A useful structure browser is the free vbdec disassembler:
http://sandsprite.com/vbdec/

For our work, we will start with the ObjectTable found through the VBHeader->ProjectInfo->ObjectTable.

The ObjectTable->ObjectArray holds the number of Object structures as defined by its ObjectCount field. An example is shown below:

This is the top-level structure for each individual code object. Here we will find the ProcNamesArray containing ProcCount entries. This array reveals the public method names defined for the object.

The Meat

The Object->ObjInfo structure will also lead us to further information of interest. ObjInfo->PrivateObject will lead us to the main structure we are interested in for this blog post.

I could not find public documentation on this structure or those below it. What follows is what I have put together through analysis.

Development of this information had me working across four different windows simultaneously.

  • disassembly of the vb runtime
  • disassembly of the target executable
  • debugger stepping through the code and syncing the disasm views
  • specialized tool to view and search vb6 structures

The following is my current definition of the PrivateObj structure:

In the debugger, I saw vb runtime code accessing members within this structure to get to the vtable offsets for an Invoke call. I then compiled a number of source code variations while observing the effects on the structure. Some members are still unknown, but the primary fields of interest have been identified.

The PrivateObj structure leads us to arrays describing each code object’s public function, variable, and event type information.

Counts for the event and variable arrays are held directly within the PrivateObj itself. To get the count for the public functions, we have to reference the top-level Object->ProcCount field.

Note that there is a null pointer in the FuncType array above. This corresponds to a private function in the source code. If you look back, you will also see that same null entry was found in the ProcNames array listed earlier.

Each one of these type information structures is slightly different. First, we will look at what I am calling the FuncTypDesc structure.

I currently have it defined as follows:

The structure displayed above is for the following prototype:

This structure is where we start to get into the real meat of this article. The number of types defined for the method is held within the argSize field. The first three bits are only set for property types.

  • 111 is a property Set
  • 010 is a property Let
  • 001 is a property Get (bFlags bit one will additionally be set)

The type count will be the remaining bits divided by four.

If the bFlags bit one is set, the last entry represents the method’s return value. In all other scenarios, the types represent the arguments from one to arg count. VB6 supports a maximum of 59 user arguments to a method.

An argSize of 0 is possible for a sub routine that takes no arguments and has no return value.

The vOff field is the vtable offset for the member. The lowest bit is used as a flag in the runtime and cleared before use. Final adjusted values will all be 32-bit aligned.

The optionalVals field will be set if the method has optional parameters which include default values.

LpAryArgNames is a pointer to a string array. It is not null- terminated, so you should calculate the argument count before walking it.

After the structure, we see several extra bytes. These represent the type information for the prototype. This buffer size is dynamic.

A mapping of these values was determined by compiling variations and comparing output for changes. The following values have been identified.

The above code, shows that 0x20, 0x40, and 0x80 bits have been set to represent ByRef, Array, and Optional specifiers. These are combined with the base types identified in the enumeration. These do not align with the standard VARIANT VARENUM type values.

The comobj and internal flags are special cases that embed an additional 32-bit value after the type specifier byte.

This will link to the targets ObjInfo structure for internal objects.

If the comobj type is encountered, an offset to another structure will be found that specifies the objects library guid, clsid, library name, and dll path. We will show this structure later when we cover public variables.

It is important to note that these offsets appear to always be 32-bit aligned. This can introduce null padding between the type byte and the data offset. Null padding has also been observed between individual type bytes as well. Parsers will have to be tolerant of these variations.

Below is the type definition for the following prototype:

Next up comes a quick look at the PubVarDesc structure. Here is a structure for the following prototype:

This example shows flag 0x1D to declare an external COM object type. An offset then follows this to a structure which defines the details of the COM object itself.

The value 0x3c in the varOffset field is where the data for this variable is stored. For VB6 COM objects the ObjPtr() returns a structure where the first member is a pointer to the objects Vtable. The areas below that contain class instance data. Here myPublicVar would be found at ObjPtr()+0x3c.

Finally, we will look at an EventDesc structure for the following prototype:

This structure closely follows the layout of the PubFuncDesc type. One thing to note, however, is that I have not currently been able to locate a link to the Event name strings embedded within the compiled binaries.

In practice, they are embedded after the strings of the ProcNamesArray.

Note that the class can raise these events to call back to the consumer. There is no implementation of an event routine within the code object that defines it.

Conclusion

In this post, we have detailed several structures which will allow analysts to parse the VB object structures and extract function prototypes for public object members.

This type information is included as part of the standard IDispatch plumbing for every user-generated VB6 form, class, user control, etc. This does not apply to functions in BAS code modules as they are not COM objects internally.

It is also interesting to note that VB6 embeds this type data right along with the other internal structures in the .text section of the PE file. No type library is required, and this feature can not be disabled.

While the structures portion of the .text section can contain various compiler-generated native code stubs, all user code falls into the memory range as defined by the VBHeader.ProjectInfo.StartOfCode and EndOfCode offsets.

The framework required to analyze a VB6 binary and resolve the information laid out in this posting can be fairly complex. Luckily open-source implementations already exist to help ease the burden.

Structure and prototype extraction routines have already been included in the free vbdec disassembler. This tool can also generate IDC scripts to apply the appropriate structure definitions to a disassembly.

VB6 binaries are typically considered hard to analyze. Extracting internal function prototypes will be a very welcome addition for reverse engineers.

The post Recovery of function prototypes in Visual Basic 6 executables appeared first on Avast Threat Labs.

❌
❌