Saturday, June 26, 2021

Policy Plus - Registry IO of large unsigned values

A couple weeks ago, I received a report from a Policy Plus user of an error in saving the POL file. Based on the error message displayed, the failure occurred when RegistryKey.SetValue couldn't figure out how to write a .NET value as Registry data. Unfortunately, there was no indication what type the problematic value was. I tried editing every kind of policy element and they all worked as expected... until I tried a huge numeric value. Apparently SetValue tries to convert the data to a signed 32-bit integer when writing a DWord value (even though the Registry is unsigned, if I understand correctly), which fails for unsigned integers larger than 2,147,483,647. As a workaround, unsigned integer values are now reinterpreted bitwise as signed integers while writing to the Registry and vice versa when reading.

Friday, April 30, 2021

Policy Plus - Policy definition ACL fix

Microsoft apparently changed its mind about only publishing policy definitions through Windows Update - new MSIs are now available for download on the web - so I was able to update Policy Plus's Acquire ADMX feature to download the most recent (20H2) definitions, after dealing with the wrinkle that the directory structure inside the MSI archive changed somewhat.

I recently received an issue report from a Policy Plus user stating that acquiring ADMX files failed in the "securing destination" phase on Windows 7 Home. Sure enough, I got the same error when testing on Windows 7 specifically, but not on Windows 10. Evidently, the PolicyDefinitions folder's ACL is different across Windows versions. I fixed the immediate problem by setting the owner to Administrators (using the take-ownership privilege) in a separate step before adding the ACL that allows Administrators full control. While inspecting the ACLs, however, I also noticed that the newly installed policy definition files had ACLs from the temporary directory under the user's profile folder rather than the typical ACL for publicly readable Windows files. ACLs are now inherited from the PolicyDefinitions directory, like they should be. Finally, I added a check for whether Policy Plus is elevated and, if not, made all permission overrides skipped so that normal users can make their own personal, portable policy definitions folder.

These changes are live on GitHub.

Saturday, March 27, 2021

Why WaasMedic Agent hammers the disk

After a recent large Windows update I noticed that "WaasMedic Agent Exe", hosting the "Windows Update Medic Service", regularly caused 100% disk usage for over an hour at a time. Since this was hindering my machine's performance I spent a little time looking into what exactly it was doing.

With Sysinternals Process Explorer I saw that the process accessed all kinds of files deep in my user profile, virtually none of which should have anything to do with Windows Update. The only thread that appeared active during the disk usage had a stack including both WaaSMedicAgent.exe (as expected) and SedPlugins.dll. Disassembling and obtaining symbols with IDA 7, the offending call stack seems to be (more recent calls last):

WaaSMedicAgent.exe: PluginAction
SedPlugins.dll:     ?PluginDetectCondition@CSedimentDriverExternalFunctions@CSedimentDriver@WSD@@QEAAJPEBGPEA_N@Z
SedPlugins.dll:     ?DetectCondition@DiskCleanupPlugin@1WSD@@UEAAJPEAH@Z
SedPlugins.dll:     ?CollectCommonDiskInformation@CDiskFileCleanup@WSD@@QEAAJXZ
SedPlugins.dll:     ?CalculateDirSizeInternal@CDirUtil@WSD@@CA_JAEBV?$basic_string@GU?$char_traits@G@std@@V?$allocator@G@2@@std@@_J_N@Z
SedPlugins.dll:     (recursive call...)

The update medic service seems to be very modular, capable of hosting different plugins. The calls to PluginDetectCondition and DetectCondition are both indirect. SedPlugins.dll holds "sedimentation" plugins (?), which I imagine is some kind of internal project codename. I speculate that each plugin is supposed to check for and try to resolve a different issue that might interfere with Windows Update functionality. A "disk cleanup" plugin might make sure that a reasonable amount of space is free on the system drive and invoke cleanup of dispensable files if not.

Speculation aside, the disassembly of CollectCommonDiskInformation makes the problem very clear. It calculates and logs the total size of each of several directories: 

  • %WINDOWS%\installer
  • %WINDOWS%\SoftwareDistribution
  • %WINDOWS%
  • %WINDOWS%\serviceprofiles
  • \program files
  • \users
  • \programdata
  • \program files\WindowsApps
  • \$Windows.~BT
  • \Windows10Upgrade

My user profile folder contains a lot of small files in moderately deep directory hierarchies, so this is very slow. Also, some of these checks are redundant - the size of the Windows apps folder is already included in the size of Program Files. More importantly, this approach will spend a lot of time worrying about the size of things that can't be automatically cleaned up - I could purge my Gradle caches if I needed space, but Windows doesn't know that and can't know what's dispensable in general. Even more importantly, I have over 500 GB free on my system drive! Checking the disk free space is very fast (not recursive), so ideally the plugin should notice that cleanup is not necessary and skip expensive calculations.

[Update: I filed a Feedback Hub report.]

Since Windows Update is working as desired, I decided to disable this "medic" service until its disk hammering is fixed. This service seems to get special defense against modification through the services API, so I disabled it by setting the Start value to 4 in its SCM Registry key:

HKLM\SYSTEM\CurrentControlSet\Services\WaaSMedicSvc

Tuesday, January 12, 2021

Finding the local computer's machine account SID

The Get-ADComputer cmdlet, I hear, can get the SID of a computer's Active Directory machine account. But because it contacts Active Directory to look up the account, it won't work if not connected to the domain. If you have administrative privileges on the computer, you can elevate to SYSTEM (e.g. with PsExec) and get the computer's machine account SID from the default value in this Registry key:

HKLM\SECURITY\Policy\PolMachineAccountS

It's in binary SID format and so may be tricky to read by eye. If you just need the RID, that's the last four bytes, or you can get it as the only contents of the sister key PolMachineAccountR.

Wednesday, January 6, 2021

Logging into a domain account without connection to a real domain controller

Windows domain member computers cache some domain users' password hashes so that recent users of the machine can still log in even when the computer can't reach a domain controller to check the password against Active Directory. Of course, if the user was not one of the most recent logins or has never logged into the machine to get their credentials cached, they cannot log in without contacting a domain controller since Windows cannot verify their password or even their existence. It's pretty easy to get into a machine by using the cmd.exe-as-sethc.exe trick and creating a new local user at the login screen, but if you want to avoid offline writes to the disk for some reason, want the new profile to be associated with a domain user once reconnected to the domain, or just want to have a fun adventure (for some definition of "fun"), it's possible to set up a fake domain controller. To avoid bizarre behavior after reconnection to the real domain, you will need to know a domain user's username, NetBIOS domain name, and SID. Conveniently, all of this can be obtained on one line from whoami /user when logged into a domain-joined computer that presumably has a DC connection or cached credentials.

Of course, it would be very bad if just anyone could set up a fake DC and distribute arbitrary policy changes to execute arbitrary code without touching the machine. To prevent this, every domain-joined computer has a "machine account" in Active Directory and keeps the password to it in an LSA secret locally. The computer uses that password to log onto and verify the domain. If machine logon fails, e.g. due to a machine account password mismatch, user logon fails with "the trust relationship between this workstation and the primary domain failed." 

So we'll need to extract the machine password. Boot the target computer from a flash drive (Windows PE or portable Linux, doesn't matter) and copy out SYSTEM and SECURITY from C:\Windows\System32\config. On a computer you already control, start Mimikatz in interactive mode. Elevation is not necessary for the program itself, but it probably will be to tell your antivirus to stop complaining about the tool. Run this command to dump LSA secrets:

lsadump::secrets /system:C:\copied\SYSTEM /security:C:\copied\SECURITY

Note the $MACHINE.ACC secret's NTLM hash. The DC doesn't need the password itself.

We will also need to know the domain's DNS/realm name and the target computer's name. If those are not known, they can be obtained from the exfiltrated SYSTEM hive, which can be opened in the Registry Editor with File | Load Hive when HKEY_LOCAL_MACHINE is selected. The domain DNS name can be found in the ControlSet001\Services\Tcpip\Parameters key; the computer name is in ControlSet001\Control\ComputerName\ComputerName.

With that information, we can set up a Samba Active Directory domain controller. I tested this on a standard Ubuntu 20.04 distribution in a VM based on this wiki page. First install the ifupdown package since we're going to remove the other networking components that might get in the way. (On the first try I only thought to install that after I ripped out the other network tools. Oops.) Disable the systemd-resolved service, then uninstall/purge the network-manager package. Remove the /etc/resolv.conf symlink and replace it with a new file specifying a public nameserver like 8.8.8.8. Rewrite /etc/network/interfaces to specify a static IP. Bring the network interface online with ifup eth0 and make sure the IP is correct with ip addr. Adjust /etc/hosts to resolve both the domain's fully-qualified DNS name (e.g. example.com) and the server's FQDN (e.g. mirage.example.com) to that static IP.

Now you can install the necessary packages for a DC. One of the Kerberos-related packages will prompt for realm information during setup - I think that configuration is going to get rewritten by Samba later, but just to be safe, give it the domain FQDN as the realm and the server's name as the server list. Delete the Samba configuration, Samba state, and Kerberos configuration as directed in "Preparing the Installation".

The domain SID is just the domain user's SID without the last component (the RID). Make sure to specify that when provisioning the domain:

sudo samba-tool domain provision --domain=EXAMPLE --domain-sid=S-1-5-X-Y-Z --realm=EXAMPLE.COM --adminpass=SecurePassword!

Copy Samba's desired Kerberos configuration into place:

sudo cp /var/lib/samba/private/krb5.conf /etc/krb5.conf

During the provisioning, Winbind was set up as the DNS server and set its forwarding server to the public server specified previously in resolv.conf. Now that Winbind handles name resolution, update /etc/resolv.conf to use the local server itself. Unmask, enable, and start the samba-ad-dc service. The domain controller should now be running. Test the three aspects of it.

Now things should move quickly. Create a machine account for the target computer:

sudo pdbedit -a -u TARGET$ -m

Set its NTLM password hash:

sudo pdbedit -r -u TARGET$ --set-nt-hash LONGHASH

Disable machine account password changes so the target doesn't change its password with the fake domain and render itself unable to connect to the real one:

sudo pdbedit -P "refuse machine password change" -C 1

Optionally disable user password complexity requirements:

sudo samba-tool domain passwordsettings set --complexity=off

Then create the user account you want to log in with (you will be prompted for a password):

sudo pdbedit -a -u person

At this point you can add the user to any other groups you want, perhaps Domain Administrators.

Despite pdbedit's documentation, Samba does not support setting account SIDs. We will therefore need to directly-ish edit the Samba state, specifically the LDB file in /var/lib/samba/private/sam.ldb.d/ corresponding to the main naming context for the domain. Install the ldb-tools package, stop the samba-ad-dc service, then invoke ldbedit to bring up a text editor on the user account object:

sudo ldbedit -e nano -H /var/lib/samba/private/sam.ldb.d/DC=EXAMPLE,DC=COM.ldb '(samaccountname=person)'

Find the objectSid line, change the last component (the RID) to match the real user's SID, save, and exit. The changes are immediately applied and you can start the Samba service again.

To make the target computer see the domain, you will need to adjust your DHCP server (probably your LAN's router) to specify the fake DC as the first DNS server. Power the target machine on and you should be able to log in with the credentials you decided for the user account! Once that one login works, the credentials will be cached, so the fake DC is not needed anymore. You can turn off the server and put the DHCP server's DNS settings back how they were.

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.