Thursday, December 17, 2020

Turning the Compatibility Administrator into ShimDBC

Microsoft creates the application compatibility database (SDB) files from XML using an internal utility apparently named ShimDBC. Geoff Chappell found that this utility is embedded within Compatadmin.exe, the Compatibility Administrator utility that comes with the freely available Windows Assessment and Deployment Kit. To access ShimDBC functionality, we need to patch Compatadmin to pass command-line arguments of our choosing through to it. The linked article provides a patch, but for an older version. I'll provide a general approach for producing such a patch and provide the specific changes for the 32-bit Windows 10 2004 edition (file version 10.1.19041.1).

We will need two utilities: a disassembler and a hex editor. I'll use IDA 5 (though apparently version 7 is free now) and XVI32. Fire up IDA and open the PE executable Compatadmin.exe using the default settings. Tell IDA to download the PDB when prompted, wait for the initial autoanalysis to complete, then use the Functions tab to navigate to the WinMain function, the starting point of the application. We can't just write our whole patch over the beginning of this function, though, because the loader might decide to load the module at a non-preferred address, in which case it would add offsets to addresses of called functions to compensate, so we'll need to use an existing fixup site. Look down for the first call instruction and click it to see its address in the status bar.


In the above screenshot, this call instruction is at 0x3A467 in the file and at an offset of 0x29 from the start of the function. We need this to calculate a jump offset from the first instruction. We're going to replace the first instruction with a short jump to get to the call site, and a short jump takes 2 bytes, so the jump offset will be 2 less: 0x27. Click the first instruction (a mov) and record its address from the left eight-hex-digit display: 0x3A43E for me. Open XVI32 to a copy of the Compatadmin executable and use Address | Goto to go to that absolute address. The highlighted byte should be hex 8B. Type over that byte with EB (short jump), then the next byte with the offset to the call site. To double-check the jump offset, use Goto again to go relative down by your offset. The byte you land on should be FF (call absolute indirect).

We now need to find the import address table entry for GetCommandLineW so we can pass the command line to the database compiler. Start typing that function name in IDA's Imports window and it should take you to the entry. We will need the Address value in little endian, so the 004B60D0 I see becomes D0 60 4B 00. Back in XVI32, leave the FF 15 intact and overwrite the next four bytes with your little-endian address. The ShimDBC invoker was compiled as a fastcall function, so it takes its first/only argument in the ECX register, so we need to move the result from EAX to ECX: overwrite the next two bytes with 89 C1.

At this point we are getting unfortunately close to another call instruction, whose address could be adjusted by the loader, thereby ruining our code. We will need to jump over it. Back in the main viewer on WinMain, click the first instruction after the second call.


From its file offset (0x3A477 in the screenshot), subtract the current XVI32 cursor position (0x3A46F for me), then subtract 2 more for the size of the short jump: I get 6. Like before, overwrite the next byte with EB, then the next with the jump offset.

Now we need to make a near call to the ShimDBC invoker, named ShimdbcExecute in the current version. Use IDA's Functions tab to navigate to it, click its first instruction (a push for me), and note its file offset: I get 0x5630F. From that, subtract the file offset of the first post-call instruction we just used, and further subtract 5 to account for the length of the call instruction: I get 0x1BE93, or 93 BE 01 00 as a little-endian double-word. In XVI32, go to the absolute address of that first post-call instruction. Overwrite the byte you land on with E8, then overwrite the following four bytes with the offset.

Finally, we just need to exit the function restoring the stack appropriately for the sixteen bytes' worth of parameters that were passed. Overwrite the next three bytes with C2 10 00.

The EXE is still declared as a GUI program, so to get console output from it you would need to redirect it to a file. That can be fixed by changing the subsystem ID in the PE header. Near the very beginning of the file, you will see the letters "PE": select the P, then go relative down 0x5C bytes. You should land on an 02 (GUI) - change it to 03 (CUI).

In summary, my changes are:

  • At 0x3A467: EB 27 (replacing 8B FF)
  • At 0x3A46A: D0 60 4B 00 (replacing D8 60 4B 00)
  • Following at 0x3A46D: 89 C1 (replacing 6A 04)
  • Following at 0x3A46F: EB 06 (replacing 8B FE)
  • At 0x3A477: E8 93 BE 01 00 (replacing 33 C0 66 89 84)
  • Following at 0x3A47C: C2 10 00 (replacing 24 80 01)
  • At 0x154: 03 (replacing 02)
For additional convenience, you can remove the program's desire to run as administrator by searching for "highestAvailable" and typing over it in the right/ASCII pane with "asInvoker" plus spaces after the closing quote to cover the extra length.

No comments:

Post a Comment