Ghidra Tips to Analyse Kensington Verimark Fingerprint Scanner

Intro

It's been a while since part 3, though not for lack of progress on the analysis. The work over the last month or so has largely consisted of persistent analysis of the binary. There's not that much to share in terms of exciting updates, it's just analysing functions one-by-one, piecing together the puzzle.

However, there are few techniques I've discovered that I want to share. Much of this is likely known to experienced reverse engineers, but might not be known to everyone.

There are several smaller tips that I discovered while doing my analysis. Whenever I encountered a pain point I would try to find a solution, which sometimes provided me with a new tool in my toolbox. Ghidra is immensely powerful and I have surely not uncovered all the tricks yet.

Ghidra C++ Offset for Interfaces

The program I'm analysing is written in C++ (at least, I'm fairly sure, I don't really know). This means there are lots of interfaces and vtables which cause all sorts of headaches. From what I can tell, each interface that's implemented by this class has its own vtable (more on that later). Often these interfaces themselves implement other interfaces. One prominent example is the IUnknown interface. I had already created a class & corresponding struct using the instructions from the video I linked at the end of part 2. However, when looking at some of the functions, I declared their function signature to be a thiscall and gave it the correct type. Then fields seemingly didn't line up, including going negative! Take a look at this function snippet for example:

void FUN_18000a000(longlong param_1,char param_2,undefined8 param_3,undefined8 param_4)

{
      CBiometricDeviceUSB::FUN_18000c214
                ((CBiometricDeviceUSB *)(param_1 + -0x38),CONCAT71(in_register_00000011,1),
                 (byte)param_3);
  return;
}

Why is it param_1 - 0x38? This should be the this pointer, right? It is in a way: It's the this pointer from the perspective of the vtable corresponding to the offset in the class, from what I can tell. So the concrete class itself has no offset and gets a "proper" this, but then the first interface (or parent class, I'm honestly not sure) gets this + 0x8. However, it now wants to reference a function in the parent class. So it reverses this offset with this - 0x8. How do we represent that in Ghidra? For this, you find your struct pointer in the Symbol table and create a new typedef for it:

Ghidra Typedef

This creates a new data type, for example CBiometricDeviceUSB * __(()). Hit enter and then right click on it and select "Settings" (not "Edit"!). Now in "Component Offset" select the right offset. How to find the right value? I found the largest negative offset of related functions to be a good indicator. If it ends up looking wrong you can adjust it later. You can usually tell if it makes sense or not. So in the example above, 0x38. Once saved, Ghidra will now name your data type CBiometricDeviceUSB * __((offset(0x38))).

Now go back to your function, but instead of making the function a thiscall (which doesn't allow you to adjust the offset), just emulate it manually: Rename the first parameter to this and then change it's data type to the one you created with the offset. Rename the function if you have a name for it, otherwise you can just prefix it with the class name. Now it looks like this:

void CBiometricDeviceUSB::FUN_18000a000
               (CBiometricDeviceUSB * __((offset(0x38))) this,char param_2,undefined8 param_3,
               undefined8 param_4)

{
      FUN_18000c214(ADJ(this),CONCAT71(in_register_00000011,1),(byte)param_3);
  return;
}

Ghidra makes the right inferences about fields and denotes it with the ADJ function (macro?). You can also see that the prefix from the class is gone since Ghidra recognises it belongs to the same class.

Ghidra vtable types

Initially, I was very confused by these multiple vtables as I couldn't quite make out why there'd be so many. Eventually, I worked out that each vtable seems to refer to an interface or possibly parent class. Many of these are interfaces defined my Microsoft so after importing all the types into Ghidra, these should be available. See next section on how to add more types.

In my case, I am very lucky that the developers of this driver left a lot of log messages and debug strings in their DLL. Particularly helpful are the many tracing calls that denote the correct function name. This has made it significantly easier to make sense of the structure and identify the correct function type. For example, while the function I shared above has no such string, another nearby vtable for this class provides a good example:

TraceFunctionEntry(local_30,"CBiometricDevice::OnArmWakeFromSx",0);

Combining the offset technique from above with a rename, we get this signature:

undefined8 CBiometricDeviceUSB::OnArmWakeFromSx(CBiometricDeviceUSB * __((offset(0x28))) this)

And look IPowerPolicyCallbackWakeFromSx::OnArmWakeFromSx is a real function that's part of an interface in Windows. It also inherits from IUnknown just like many of them. Navigating to where the vtable is defined in the disassembly listing, it's possible to find the start of that data structure and give it a type of IPowerPolicyCallbackWakeFromSxVtbl (just start searching for the name, it'll come up). We can also define this type on the data structure for CBiometricDeviceUSB we've been creating:

CBiometricDeviceUSB in Data Type editor

If we fill this in for every vtable we can find it can tell us a whole lot of stuff, especially when reading the corresponding docs from Microsoft. No need to understand everything right now, but it'll be much easier to make sense of once we get further in the analysis.

Ghidra Import Data Types

If the data type didn't come up in the above search, it's possible Ghidra hasn't imported them yet. I'm running Ghidra on Linux, so I don't have those files readily available on my system. Instead, I need to use my Windows VM to find the right files and copy them across and point Ghidra at them.

There's a little bit of a prerequisite of installing the right SDKs to get the right header files, but I honestly don't quite remember. There was a lot of searching of types and header names and following instructions. The Everything search tool has been immensely helpful. It's super fast to tell you where to find the headers you're looking for.

I ended up with these three folders that I copied back to Linux:

- 10.0.22621.0
- 10.0.26100.0
- wdf

If you have all the right files you can then import them into Ghidra. In the CodeBrowser tool, under File > Parse C Source you'll find a menu to import those headers into Ghidra types. Be careful with this, I suggest you back up your project first in case things go wrong. This has been very brittle for me.

First, find a profile that sounds right, e.g. I started with WindosProfile.prf. Make a copy as we'll be making changes to it. Then find the header file you want to import types from (the Microsoft website will tell you what they are). Select "Parse to Program" and let it run, it'll take a while. It will most likely fail.

Depending on the error message I've encountered a few failure modes. The first was the wrong parse options. These define certain global variables and other values I don't understand. With a lot of searching and trial and error I eventually landed on slightly modified defaults:

-D_MSC_VER=1924
-D_INTEGRAL_MAX_BITS=64
-DWINVER=0x0a00
-D_WIN32_WINNT=0x0a00
-D_AMD64_
-D_M_AMD64
-D_M_X64
-D_WIN64
-D_WIN32
-D_USE_ATTRIBUTES_FOR_SAL
-D_CRTBLD
-D_OPENMP_NOFORCE_MANIFEST
-DSTRSAFE_LIB
-DSTRSAFE_LIB_IMPL
-DLPSKBINFO=LPARAM
-DCONST=const
-D_CRT_SECURE_NO_WARNINGS
-D_CRT_NONSTDC_NO_DEPRECATE
-D_CRT_NONSTDC_NO_WARNINGS
-D_CRT_OBSOLETE_NO_DEPRECATE
-D_ALLOW_KEYWORD_MACROS
-D_ASSERT_OK
-DSTRSAFE_NO_DEPRECATE
-D__possibly_notnullterminated
"-Dtype_info=\"void *\"",
-D_ThrowInfo=ThrowInfo
-D__unaligned=
-v0
-D__inner_checkReturn=
-DWINAPI_PARTITION_APP=1
-DWINAPI_PARTITION_SYSTEM=1
-DWINAPI_PARTITION_GAMES=1
-DSECURITY_WIN32

I think I added something like -D_WIN32, but I'm not sure any more.

Another error I faced was simply a complaint that it couldn't find some types or headers. That one is fixed easily, I think the error mentioned what was missing. Find where the file is stored in the folders you discovered earlier. In the second section of the window add that folder as an Include path. Don't just add everything! You can end up including the wrong file which causes all sorts of headaches, including the next error. If you did this correctly, the error should change. You might have to add multiple folders until all the includes resolve. Note that this include path isn't recursive, you need to add each folder explicitly.

Now comes the final error, the worst of the bunch. If everything else worked out, you might get a very obscure error of parsing failures with very large line numbers. This means, some of the included files defined the same symbol but with a different meaning. For example, I think the symbol _M or something was defined weirdly. The pre-processor just replaces strings, but when it did, the resulting type was invalid and couldn't be parsed.

To fix this is slow and painful: Instead of "Parse to Program", select "Parse to File" and make a new folder to parse the file into. Wait while it does its work and when you get the error message, write it into a file somewhere. Here's an example of such an error message:

No Data Types added.  C Parser: Encountered errors during parse. in /home/flozza/src/fingerprint/driver/Include/10.0.22621.0/shared/usbspec.h near line 668 Error: Encountered "   "0x02 "" at line 2862635, column 12. Was expecting one of: "__declspec" ...  ...  ... "__readableTo" ... "_Alignas" ...  ...  ...  ... "[" ... "__declspec" ... "__readableTo" ...  ...  ... "[" ... "_Alignas" ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  ...  near token: struct Possibly Undefined : { Last Valid Datatype: _USB_HIGH_SPEED_MAXPACKET Check around synaWudfBioUsb132.dll around line: 2862635

The resulting file it created for me was a p2f.gdt_CParser.out at a whopping 87 MB! That's a very large text file but you can look at it, just maybe with tools that don't read the entire file into memory, use less or grep. I found a command like this helpful:

head -n 2862625 p2f.gdt_CParser.out| tail -n20

Around this area you should find the invalid data type and find why it is invalid. From my understanding this is after everything has been replaced by the pre-processor and now it gets parsed as C. So from my error above, I'd expect to find a stray 0x20 somewhere.

You then grep the original source headers for the types surrounding this value and find out what the pre-processor has replaced here. Say, for example, there was a _M defined here but in the final output it turned into 0x20. grep your way through all the headers for any mention of _M. This might require a bit of adjusting, you could try rg '#define _M ' or rg _M | rg 0x20.

What we're after is what file has defined _M as 0x20. I think this comes down to Ghidra not being a real C pre-processor and just doing a string replace, but it may well be that this would also be invalid if a proper compiler does it. In any case, somehow a file has entered into the collection of included files that defined something we don't want. Usually you'll find it's on the top list of "Source files to parse" in Ghidra, though if it's being included you might have to untangle what included it.

Once you have found the culprit and removed it, you can try again. Rinse and repeat this step until no such issues appear any more. It is entirely possible I did this to myself by trying to analyse a Windows binary on a Linux system and that Ghidra does not struggle like this if you run on Windows. I hope it helps if you're also struggling. I promise having those types in Ghidra will be helpful once they're in there.

And that's it for now. A few random Ghidra tips I felt like sharing. Since I've already applied many of these tips I don't really have a pristine environment to share the original state, so please excuse the lack of examples in some places.