Wednesday, December 30, 2020

Allowing standard users to run a privileged scheduled task

A Super User asker wanted to allow all users to perform one specific action that usually requires administrative privileges. Application developers wanting to do this should create a service with an RPC mechanism, but a quick way for a scripter to do it is to take advantage of the Task Scheduler service. First, a scheduled task that runs something as SYSTEM (no triggers necessary) can be created as usual.

Then the trick is to adjust the task's access control list. Neither the Task Scheduler MMC snap-in nor the relevant PowerShell module provide a way to change tasks' security descriptors, but the IRegisteredTask COM interface implements IDispatch, so it can be used in PowerShell. To obtain a task object:

$ts = New-Object -ComObject 'Schedule.Service'
$ts.Connect('localhost')
$task = $ts.GetFolder('\').GetTask($taskName)

The GetSecurityDescriptor method takes a flags argument specifying which parts of the security descriptor to get as a SDDL string; 4 gets just the DACL. Appending (A;;FRFX;;;BU) adds an extra access control entry that allows (A) read (FR) and execute (FX) access to the Users group (BU). The adjusted SDDL can be applied with the SetSecurityDescriptor method. Standard users will then be able to see and run the task, but not alter it to do something else or delete it. 

Tuesday, December 29, 2020

Applying shims to standard Windows programs

While browsing for information about the Windows application compatibility shim engine, I found someone remarking that they had trouble applying shims to Notepad for demonstration purposes.

It is possible to shim standard Windows programs like Notepad, but an extra step is required. Like Windows DLLs, EXEs under System32 are excluded from hooking by the default inex policy. The shim definition must therefore have an INCLUDE directive for the executable, such as in this ShimDBC XML fragment:

<SHIM NAME="CustomShim" FILE="AcRes.dll" RUNTIME_PLATFORM="X86_ANY,AMD64">
    <INCLUDE MODULE="notepad.exe"/>
</SHIM>

The shim can then be applied as usual:

<APP NAME="Notepad">
    <EXE NAME="notepad.exe" RUNTIME_PLATFORM="AMD64" FILE_DESCRIPTION="Notepad">
        <SHIM NAME="CustomShim"/>
    </EXE>
</APP>

A few standard shims - specifically RedirectEXE, InjectDll, and TerminateExe - refuse to perform their function on system EXEs as determined by a function called ShimLib::IsSystemExeFromSelf, which appears to check whether the EXE file is owned by TrustedInstaller. That's not a concern for custom shims, though.

Sunday, December 27, 2020

64-bit support in the custom shim development kit

The initial release of my custom Windows appcompat shim development kit only supported 32-bit shim modules. Today I downloaded IDA 7, which became freely available recentlyish and supports 64-bit disassembly. I looked at the 64-bit versions of a couple of the official shim modules and the shim engine interface looked very familiar, keeping in mind that x64 has only one calling convention, reminiscent of fastcall. The 64-bit version of apphelp.dll seemed to have all the exports I used in my 32-bit shims.

So I optimistically compiled my apphelp import library in the x64 configuration, configured the CustomShim project to use it in the x64 configuration as well, and compiled the shim module as 64-bit. I addressed a few compiler warnings that resulted from assuming size_t is the same as int, then went to register my 64-bit shim module. Playing around with ShimDBC, I found that RUNTIME_PLATFORM attributes accept comma-separated lists of platforms, so I can indicate that a shim exists in both the 32-bit and 64-bit modules with X86_ANY,AMD64. It turns out that one SDB can apply fixes to both 32-bit and 64-bit executables - just leave off the -op switch when compiling in Custom mode to make one big SDB - and Windows accepts this, so it's not clear why there's an AppPatch64 directory or why the Compatibility Administrator comes in two versions.

After installing my new dual-bitness SDB and placing the 64-bit version of my shim module in System32, I observed my example shims applied to Sysinternals AccessChk64 and Autoruns64. The updates to the Visual Studio projects and documentation are live on GitHub.

Friday, December 25, 2020

Custom Windows application compatibility shim development kit

As might be guessed from my recent posts, I've been poking at the Windows application compatibility infrastructure for a while. I am now pleased to present a custom shim development kit that can be used to create real 32-bit shim modules that provide real shims applied with the real shim engine by the same mechanism as for the standard shims you see in the Compatibility Administrator. Custom shims are of course not supported by Microsoft, but my approach appears to work on Windows 10 version 2004. I am aware that Detours exists; I did this just for fun, though the shim engine's support for COM hooks turned out to be more convenient than Detours' in my opinion. WinRT hooks are apparently also a thing the shim engine can do, but I don't know how they work and don't currently facilitate them.

You can read about how to build custom shims in the shim module's README and how to use/apply/debug them in the overview README, so I'll just use this post for extra details and asides. 

But first, a demonstration. The example shim module includes a shim called FakeSchTask that hooks (i.e. intercepts) the Task Scheduler 2 COM API to make the application see a scheduled task in the root folder that isn't actually there. Like the other example shim, this is pretty silly and is implemented in a way targeted specifically at a Sysinternals utility I had laying around (Autoruns in this case), but it's a fine example of how to write a shim. The fake scheduled task's name defaults to "Fake Task" but can be specified as the shim command line. Placing the compiled shim module as AcRes.dll in SysWOW64, compiling the XML in the project's README using ShimDBC, installing the SDB, and running Autoruns:

Notice the top Task Scheduler entry in Autoruns and its absence from the real list

The first step in making this happen, once it's established that the process being started needs to be shimmed, is that the shim engine in apphelp.dll loads each needed shim by calling the GetHookAPIs function exported from the shim's DLL. It's supposed to return a pointer to an array of HOOKAPI structures specifying imported functions to redirect through the shim. At some point this involves comparing the requested shim name to the name of each shim implemented in the DLL and getting the right one's hook array. Microsoft's shim modules have, per shim, a namespace containing one major function that handles various callbacks including installation and sometimes a couple other functions, apparently implemented with a library called ShimLib that probably provides some macros in addition to the functions common to the several shim modules. I went for a more object-oriented approach, implementing each shim as a subclass of the abstract Shim class. My GetHookAPIs scans the list of shim instances for the one with the requested name, invokes the command line processing then hook-list-populating functions of that shim object, and adds it to the list of active shims for later notification.

Throughout the process's life, the other shim module exported function NotifyShims is called many times with different reason codes. After shims are installed with GetHookAPIs, the shim engine notifies shim modules about every DLL the executable is linked with (and presumably their dependencies), providing a pointed to the loader data table entry. (I wouldn't have been able to identify that structure myself. Thanks to the ReactOS project for having some of this reverse-engineered.) After all those, it notifies shim modules that everything is initialized. This appears to be a good time to install COM hooks. As DLLs are delay-loaded or unloaded, there may be more DLL-related notifications.

Shims are loaded very early in the process's life, so you have to be very careful about what features are available when. Loading the CLR in an attempt to write shims in a managed language would not go well. Using C++'s new operator in GetHookAPIs originally crashed with an access violation, probably because something important hadn't been loaded yet, so I replaced the standard memory management operators with implementations that wrap LocalAlloc and LocalFree, which are available. Even so, declaring static/global variables with constructors that allocate (e.g. std::vector) crashed the process with exit code 0xC0000142. This is why my knownShims and activeShims variables are pointers that are initialized inside functions called by GetHookAPIs at the earliest. They are never destructed, but there's only one leak of each type per process, so it's not really a leak.

While writing up a Stack Overflow answer that used my kit, I tried to use SprintDLL to quickly check that a Win32 function was being hooked. By default .NET applications' PInvoke calls won't be hooked because clr.dll is excluded by the standard "inex policy", which I learned by reading the shim engine logs after seeing SprintDLL fail to reflect the hook. If your shim is getting installed but isn't working, reading the ApphelpDebug log is a good way to see what's going on. 

Unfortunately I also noticed that SprintDLL sometimes failed to start when being shimmed, hanging for multiple seconds then exiting with a breakpoint-related NTSTATUS. The shim engine log said it "failed to lock engine", which is an error-level log event, so it brought the process down when no debugger was around to handle the DebugBreak. Running under WinDbg, a thread did indeed fail to RtlEnterCriticalSection on the engine lock, while the thread holding that lock was in the ASL-log-writing code waiting to acquire some other lock: classic deadlock. (ASL is the shims logging system, as opposed to the shim engine log. Maybe it stands for AppCompat shim log or layer.) Disabling the shims log fixed the problem. Along the way I noticed that shim engine logs also get sent to debug output and that a bunch of shim-related stuff happens before the debugger breaks into a new process, which I'm sure would make more complex issues interesting to debug.

Speaking of ways to crash the process, it is critical to get the calling convention right for hook functions and pointers to original functions. If a convention is not specified, it defaults to fastcall, which is not right for any Win32 function I know of, leading to stack imbalance then an access violation or stack cookie fast fail. Similarly, getting the parameter sizes wrong, either directly in the signature or by associating a hook function with a real function you didn't mean, will ruin the stack.

To reduce accidental mismatches as much as possible, I did some macro and template tricks. The shim engine provides the original/next function in a field of the HOOKAPI structure that was given to it by GetHookAPIs, so calling it from inside a hook function involves indexing that array, which requires keeping track of the order hooks were declared and using the right index in each hook function - easy to get wrong if done manually. Instead of having you build the array yourself, I provided an ADD_HOOK macro for use in RegisterHooks overrides that appends the hook information to a vector maintained by the Shim superclass and records its index in a struct templated by the hook function, effectively stapling the index to the function. The index is looked up in that struct by the DEFINE_NEXT macro (for use inside hook functions) and passed to a superclass function that extracts the next function pointer from the hooks array. By using decltype in a cast it spares you from repeating, and possibly mismatching, the function signature. Hook functions have to be static; the macro gets access to the shim instance through a static variable set in the constructor defined by the SHIM_INSTANCE macro. I would have liked to parameterize by hook function name to remove the need to pass even the current hook function to DEFINE_NEXT, but the Microsoft C++ compiler doesn't seem to fully support string literal template parameters, and I didn't want to use a map because of the runtime cost.

COM original function lookup is simpler because the SE_COM_Lookup function provided by the shim engine already takes a "this" pointer and hook function, both of which are easily accessible and not prone to accidental mismatches. It is pretty easy, though, to mismatch the vtable index during member function hook registration. Unfortunately I can't do anything about this unless CINTERFACE is defined, which would disable the nice C++ interfaces, so you'll just have to very carefully count the indexes of fields in the headers' C Vtbl structs yourself.

Hooking member functions of registered COM classes is pretty straightforward: you tell SE_COM_AddHook the class ID, interface ID, function index, and your replacement function and it intercepts the COM instantiation machinery (used by e.g. CoCreateInstance) to hook the function when an object of that type is created. The shim engine cannot, however, detect or interfere with COM objects allocated directly, like those created and returned by existing objects. You can still register hooks by IID and vtable index, but such hooks can't be applied automatically. Instead, once you get ahold of an object implementing that interface (in a hook function that was applied automatically), you can call SE_COM_HookObject to apply the hooks to it. Despite the function name, that generally applies the hook to the entire class since all instances of a class usually all point to the same vtable. The shim engine can't copy the vtable because it doesn't know the size. If you only want your hooks to interfere with some instances of the internally instantiated class, you will need some way for them to distinguish interesting instances. My FakeSchTask shim's hooks of ITaskFolder use get_Name to check if the folder is the root folder. Before I noticed that helpful property, I noted the pointer to the root folder in the ITaskService::GetFolder hook and checked the "this" pointer against it in the ITaskFolder hooks, but this was a mess because objects can be freed, allowing the address to be reused. The shim engine doesn't seem to support multiple hooks of the same COM function, and since it already hooked all the IUnknown functions, I couldn't instrument Release. Instead of juggling pointers, for IRegisteredTaskCollection objects produced by ITaskFolder::GetTasks, I wrapped the real objects of interest in an instance of my own FakeTaskCollection class before returning from my GetTasks hook. That way I didn't have to hook any IRegisteredTaskCollection functions. To save you some boilerplate I put the base interface IUnknown implementation and IDispatch stub, which could be useful for other wrapper classes, in a separate file. Implementing IDispatch in plain C++ is not my idea of a good time, so my objects don't actually support scripting - which is conveniently not necessary for shimming Autoruns - but I hear that generating type libraries with MIDL can make this easier.

Interestingly, the Task Scheduler 2 header taskschd.h doesn't define the CLSID_TaskScheduler GUID the way most COM component headers do. It just declares the variable extern, requiring linking with taskschd.lib, which was not in the default linker input for me. If you get "unresolved external symbol" errors about class or interface IDs and have already included initguid.h, you probably need to reference another LIB.

Well! I think that's all the adventure notes I have. Please don't rely on any part of this for anything too important, but if you have any questions or suggestions, I'd be happy to take a look via the GitHub issue tracker.

Monday, December 21, 2020

Policy Plus - Explicit checkbox elements and policy state detection

This morning I received a report from a Policy Plus user that its assessment of a policy state was inconsistent with that of the real Local Group Policy Editor. The policy had been set to Disabled in the official editor, but Policy Plus reported it as Unknown, which indicates that some Registry evidence favors it being Enabled while other evidence suggest it's Disabled. Indeed, the policy's main Registry value was deleted, but the value corresponding to a checkbox element was present and set to 0. Zero is that element's "off" value, but an unchecked checkbox is different than the whole policy being Disabled.

Experimenting with several different policies involving checkbox elements, I found that the Local Group Policy Editor writes checkbox elements' "off" values to the Registry when the policy is Disabled if the element has data explicitly defined. If the element's data is left implicit/default, disabling the policy deletes its Registry value like I expected. I still think it would be better if things worked the way I had previously assumed, since the way it actually works allows cases (in real policies!) where "policy Enabled but checkbox unchecked" is literally indistinguishable from "policy Disabled", but Policy Plus needs to reflect reality rather than my preferred design.

When judging a policy's state, Policy Plus now considers the presence of a checkbox element's "off" value as weak evidence for the policy being Disabled. When saving the state of Disabled policies, it writes checkbox elements' "off" values, if specified, instead of deleting them. While I was at it, I adjusted the setting editor window to avoid crashing when editing a mixed-state policy, not that there should be any anymore. The changes are live on GitHub.

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.

Friday, December 11, 2020

When LoadLibrary fails with ERROR_MOD_NOT_FOUND for an existent library

Today I wrote a small DLL in C++ and tried to use it on another machine. Strangely, the host process didn't load it, nor could a manual LoadLibraryW call in SprintDLL. I did have the path right and Process Monitor confirmed that the file was being opened (by both the host process and CSRSS for some reason), but LoadLibraryW returned null with ERROR_MOD_NOT_FOUND. To get more details, I used the GFlags utility to enable "show loader snaps" for SprintDLL, attached WinDbg to the SprintDLL process, and tried the load call again. Output in the debugger showed that loading my DLL also triggered the loading of vcruntime140d.dll, which did not exist on my other machine. So a module was not being found - it was "just" a dependency instead of my library itself. Using a Release build, which links against the release version of that redistributable which is actually present on the other machine, fixed the problem.

Wednesday, December 9, 2020

Windows compatibility shim logs

One of the ways Windows provides backward compatibility with applications targeted to older OS versions is by loading compatibility shims into their processes to intercept and alter various Windows API calls. apphelp.dll provides most of the machinery for this, at least for user mode. Interestingly, that DLL and the shim DLLs it can load are capable of logging some information about what they're doing or errors they encounter, though those log entries don't go anywhere by default. Out of curiosity I poked around in IDA to figure out how to enable them.

The SepApplyDebugPolicy function, called early in SE_InitializeEngine, is responsible for initializing some global variables related to logging. It reads two environment variables, SHIM_DEBUG_LEVEL and SHIMENG_DEBUG_LEVEL, into global variables controlling the verbosity of shims' logs and AppHelp itself's logs respectively. Level 5 produces messages of all detail levels including "trace." These logs seem to be designed for consumption via ETW Trace Logging, but I was unable to record any relevant events with WPR. 

They can also, however, output to files. For shim logs, AppHelp also consults a SHIM_FILE_LOG environment variable which specifies a filename under %WINDOWS%\Temp to save the log as. The code tries to take just the filename - the segment after the last backslash - but ../ sequences can be used to traverse up the directory tree and place the log anywhere you have write access to. For AppHelp (the shim engine) itself, there is no environment variable to set filenames; logs are always saved in that temp directory with a name starting with AslLog_ApphelpDebug. The shim engine state (?) is saved as an XML document in an adjacent file with a name starting with AslLog_shimengstate. These logs have to be enabled with a LogFlags DWord value in HKLM\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags in the Registry. Setting that to 0xFF (255) does the job nicely.

For completeness' sake: SepApplyDebugPolicy also looks for a DWord value named ShowDebugInfo in that key, but the effect is not clear to me from the disassembly and I did not notice any difference in behavior.

Monday, December 7, 2020

Uninstalling old Windows Assessment and Deployment Kit versions

I recently went to use the Compatibility Administrator from the Windows Assessment and Deployment Kit, but was blocked by the Program Compatibility Assistant telling me it doesn't support my version of Windows. Indeed, I had installed it a long time ago on a previous build of Windows 10, 10586. So I downloaded the new ADK installer, but it refused to install unless I removed the old version first. That's reasonable, so I tried to uninstall the 10586 version from the Apps settings page, but it failed with "setup cannot locate WimMountAdkSetupAmd64.exe" and prompted me to "locate content." This is probably related to build 10586 no longer being supported and the relevant ADK downloads being removed from Microsoft's web site. I found a couple copies of that file laying around the disk, but when I selected them, the process failed at 33% with "unable to verify the integrity of downloaded content." Trying a "repair" installation, even with a newly downloaded copy of the old version's installer, failed with the same errors.

I could, however, use the "change" setup feature to remove all ADK components except Deployment Tools. Going back to a WimMountAdkSetupAmd64.exe file I found (Explorer search does a fine job of locating a copy under C:\ProgramData\Package Cache), I ran it from an administrative command prompt with the /uninstall switch. That completed without incident, after which using "change" in the ADK setup to remove the Deployment Tools component worked! Since that was the last component, the ADK vanished from the apps list and I could successfully install the new version.