Windows 10 Decontamination Scripts

Recently my personal laptop fell on some hard times and had to be sent to the laptop retirement home. The one hesitation I had with getting a new one was dealing with Windows 10. It has some security and user interface improvements, but there's a lot to hate. There's the tasteless advertising in your start menu turning the OS into adware, invasive collection of system runtime and personal information with only vague documentation of what is being collected, ignoring or even re-enabling data collection you've disabled (see below), and possibly most horrifying, the forced download of software from third parties who pay Microsoft to install their junk on your system (sometimes including severe vulnerabilities for which "just don't run it" is not exactly a reassuring answer).

But all of these arguably malicious actions are common in garden variety malware and otherwise misbehaving programs. As long as a few core security features still function, a tight lockdown policy may be enough to block them.

Sometimes in law dramas you'll find a trial lawyer ask the judge for "permission to treat the witness as hostile." That's basically what we're going to do to the OS, by taking the following steps. First the basics:

  1. Get the professional edition of Windows (for real bitlocker full disk encryption and local GPO's)
  2. At setup, do not connect to a wireless or wired network yet, set up a local account, and turn off all data collection you can
  3. Turn on bitlocker
  4. Generate a secure random password and use that for your account. No, don't re-use one you've used before. And no, don't try to come up with one yourself. You're not that clever. Really. Neither am I.
  5. Enable the Windows firewall, and set the default outbound and inbound policies to block.
  6. Disable all outbound allow rules except for the DHCP outs, DNS out, and ICMP6 router solicitation.
  7. Install your favorite browser (Chrome or Firefox) from an offline installer on a thumb drive.
  8. Add an outbound allow rule for that browser's executable only.

OK! You should now be greeted by something like this:

when you click the start menu. Isn't it beautiful? Instead of a page of annoying advertisements you have those nice quiet down arrows. Also yes, I'm doing this on Friday the 13th, because that's about how paranoid you have to be when dealing with Windows 10. As you install programs, you should maintain the tight outbound policy, making targeted exceptions. For example, add a rule to allow TCP port 3389 outbound from mstsc.exe if you need to use remote desktop. Whatever you do, do not set your outbound policy to allow or add an allow rule for svchost.exe outbound.

Windows Update

One of the main benefits of our setup we have is that our system will not update automatically. The main problem though, is that it will not update automatically. But we still do want security updates, so it would be nice to let those through, while still blocking any other unwanted updates and the other undesirable activity that is also conducted from the same process (svchost.exe). So instead, I installed Python and wrote my own, which was far less difficult than I had originally assumed since you can generally scrape all the info you want from

  • Download and install python and BeautifulSoup. I used 2.x but 3.x should work too.
  • Don't add it to your path, and make sure double-clicking python files opens them in a text editor instead of running them. You might consider using a portable (non-installed) version and/or a nonstandard location to help avoid python getting used as an exploit primitive or as part of a backdoor.
  • Add a rule to allow python to communicate out through the firewall
  • Save the following as a python file named (please note you may need to make a minor update when the version number changes)
# Find all the security and critical updates for Windows 10 1803 and download them
from bs4 import BeautifulSoup
import requests
import os.path
r = requests.get('')
soup = BeautifulSoup(r.text, 'html.parser')
for trow in soup.find_all(id='ctl00_catalogBody_updateMatches')[0].find_all('tr'):
  info = trow.find_all('td')[1].text.strip()
  dateparts = trow.find_all('td')[4].text.strip().split('/')
  updatetype = trow.find_all('td')[3].text.strip()
  if 'Security' in updatetype or 'Critical' in updatetype:
    uuid = trow.find_all('td')[7].find_all('input')[0]['id']
    gr ='', data={'updateIDs':'[{"size":0,"languages":"","uidInfo":"'+uuid+'","updateID":"'+uuid+'"}]','updateIDsBlockedForImport':'','wsusApiPresent':'','contentImport':'','sku':'','serverName':'','ssl':'','portNumber':'','version':''})
    start = gr.text.index('downloadInformation[0].files[0].url = ')
    end = gr.text.index(';',start)
    url = gr.text[start:end].split("'")[1]
    date = "%s-%s-%s" % (dateparts[2],dateparts[0],dateparts[1])
    print("%s %s %s" % (date,info, url))
    name = url.split('\\')[-1].split('/')[-1]
    if not os.path.isfile(name):
      r = requests.get(url, stream=True)
      with open(name, 'wb') as f:
        for chunk in r.iter_content(chunk_size=102400): 
          if chunk: # filter out keep-alive new chunks

This part could have been done in PowerShell, but since PowerShell is far more commonly used as an exploit primitive and is more likely to be used as part of Windows data collection, I prefer leaving all PowerShell network connectivity denied. The rest of the process is a little easier in PowerShell though, so the next step is to save the following PowerShell script, substituting the paths at the top for your own:

echo "Checking server..."
cd C:\Whichever\Folder\You\Made\For\The\Python\script
echo "Loading patches..."
$qfes = Get-WmiObject Win32_QuickFixEngineering
ls .\*.msu | %{
	$fn = $_.FullName
	echo $fn
	$thething = $false
	$qfes | %{
			$thething = $_.HotFixID
		echo ("We already have $thething")
		$sig = Get-AuthenticodeSignature $fn
		if($sig.Status -eq "Valid"){
			if($sig.SignerCertificate.GetIssuerName() -eq "C=US, S=Washington, L=Redmond, O=Microsoft Corporation, CN=Microsoft Code Signing PCA"){
				echo ("Installing "+$fn)
				wusa /quiet $fn /norestart | Out-File .\wusa.log -Append
Get-NetFirewallRule | where Enabled -eq $true | where Action -eq Allow | where {$_.DisplayGroup -and ($_.DisplayGroup -ne 'Core Networking')} | Disable-NetFirewallRule
cmd /c pause

This script will take the updates that the python script downloaded, check with WMI if we already have that update, then validate the signature and signer of the file before installing it.

See that part at the bottom with the firewall rules? That's pretty important, because updates will frequently re-enable or add outbound firewall allow rules to re-enable stat collection. This script will immediately disable them again, yet it does a good job of ensuring any firewall rules you have added don't get disabled. The trick is that all the Microsoft ones have a DisplayGroup, while unless you do something weird, yours will not. I found this behavior out when I installed the 1803 update. I'd recommend creating a scheduled task that repeatedly runs that line every so often just to make sure, since some rule changes may only happen on reboot after update, etc.

Major updates like that may force you to do some manual installation, but the others should be automatically handled by these scripts.

The other line at the bottom (cmd /c pause) should be dropped if you want to run these automatically in the background. If not, then simply create a shortcut to powershell.exe and add command line arguments to launch the PowerShell script. Then right-click on it, go to properties, then click the advanced button and check the "Run as Administrator" box, and you'll have a shortcut you can double-click each Patch Tuesday to handle your security updates.

, , ,

Comments are closed.