Wednesday, September 11, 2019

ThunderBolt Security Configuration Item for SCCM

This comes out of a need to secure ThunderBolt PCIe against DMA attacks while still allowing users (rather than just administrators) to approve attached devices.  This attack is very serious as it can done on a machine that is in sleep or hibernate.  As we know most laptop users never turn their computers off, they just close the lid.  The computer goes to sleep and everything it was doing gets stored in memory.  DMA = Direct Memory Access, so someone comes along, plugs in a malicious device and grabs everything stored in memory without even opening the lid of the computer.

Mitigation is two parts.  One part is BIOS configuration.  By default any modern ThunderBolt enabled computer will come with the BIOS set to require "User Approval" of any device that gets attached.  The problem is (in the operating system) that approval require that the user actually be an administrator not just any user.  That is not a usable solution in an enterprise environment.  It also requires that the device(s) be approved every single time they are used.  So, let's say you have a Thunderbolt dock... an administrator has to come and approve it every time you dock your laptop.  That's just not workable.

So, we configure that BIOS to a newer standard, Secure Connect.  That allows the device to only require approval the first time it is used.  Unfortunately that approval still needs to be an administrator.

Finally we are to the point of my post.  Here are two scripts to create a configuration item to detect existence of ThunderBolt and remediate the registry so that normal users can approve device connections.

A tech said to me "I don't see how this is any different than setting the BIOS to No Security".  Well, as I explained to him, you must understand how the DMA attack works in this instance.  The attack happens by simply attaching a malicious device (USB Key, SD Card, whatever) to the machine and the machine accepts its DMA request.  This can happen even while the machine is in Sleep mode (no user interface).  So, the difference between this and no security is that a user must actually be logged in and active to approve the connection.  I can't just walk up to your machine while you are at lunch, plug in a USB key, and take everything that your OS has in memory away with me.  I would have to have a valid login to approve the connection.  The attack is a way around drive encryption.  Having a valid logon is a way around drive encryption so this method makes your machine as secure as a machine that does not have ThunderBolt at all, meaning that nobody can run this particular DMA without a valid logon.  Once someone has a valid logon, you have other mitigation that you need to have in place but that's a story for another time.

The first script, detection:
This script will detect the existence of the ThunderBolt registry branch.  If it doesn't exist then your machine doesn't have ThunderBolt (or at least doesn't have drivers for it which amounts to the same thing, you are not vulnerable to the DMA attack) so it is compliant.  If your machine does have the branch then it checks the AccessRule item to ensure that it is set to 1.  By default this item will either not exist (IBM Lenovo) or be set to 0 (HP).

Create a new CI of type Script.  I named mine "Win10 Core - ThunderBolt PCIe Security" to add it to my Win10 Core baseline.
   The Setting Type is Script.
   The Data Type is String.
   Here is the Discovery Script:

#Created by Mark Randol - randoltech.blogspot.com
#This script detects existence of teh Thunderbolt Service registry branch
#and the setting of the "Approval Level" item within it
#Non-existence of the branch = compliant
#Non-existence ApprovalLevel within the branch = noncompliant
#ApprovalLevel 1 = compliant
#Any other value for ApprovalLevel = noncompliant
 
if (Test-Path -Path "HKLM:SYSTEM\CurrentControlSet\Services\ThunderboltService\TbtServiceSettings") {
$OutString = (Get-ItemProperty "HKLM:SYSTEM\CurrentControlSet\Services\ThunderboltService\TbtServiceSettings").ApprovalLevel
}
else {
$OutString = "1"
}
Write-Output $OutString
Return $OutString

Here is the Remediation Script:

# Change Thundebolt security approval level to allow users
# to accept Thundebolt equpiment without being local admin
# Originally created by Jens-Kristian Myklebust <jensmyklebust@outlook.com>
# Modified to work as an SCCM CI by Mark Randol - randoltech.blogspot.com

$logfile = "$env:windir\ccm\logs\Install_Fix-ThunderboltRegistry.log"
Start-Transcript -Path $logfile -force #start logging

$RegistryKey = "SYSTEM\CurrentControlSet\Services\ThunderboltService\TbtServiceSettings" #Registry key to modify
$RegistryPath = "HKLM:\$RegistryKey" #Full path to the registry key
$ServiceName = "AdobeARMservice" #"ThunderboltService"

#Set the registry Value
if ((Test-Path -Path $RegistryPath)){ #make sure we're on a machine that actually has the key
       
    $TheService = Get-Service -Name $ServiceName
    #Stop the Thunderbolt Service
    if (($TheService)) {
        $TheService | Stop-Service -Confirm:$false -Verbose
        $TheService.WaitForStatus('Stopped')
    }

    $ACLinfo = Get-Acl "HKLM:\$RegistryKey" #Store original ACL info

    #Give "SYSTEM" user full access to registry key
        $RegKeyDotNETItem = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($RegistryKey,[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::ChangePermissions)
        $DotNET_ACL = $RegKeyDotNETItem.GetAccessControl()
        $DotNET_AccessRule = New-Object System.Security.AccessControl.RegistryAccessRule ("System","FullControl","Allow") -Verbose
        $DotNET_ACL.SetAccessRule($DotNET_AccessRule)
        $DotNET_AccessRule = New-Object System.Security.AccessControl.RegistryAccessRule ($env:USERNAME,"FullControl","Allow") -Verbose
        $DotNET_ACL.SetAccessRule($DotNET_AccessRule)
        $RegKeyDotNETItem.SetAccessControl($DotNET_ACL)

    #check for existence of the property in the key
    #if the property doesn't exist, create it
    if (!((Get-ItemProperty $RegistryPath).ApprovalLevel)){
        New-ItemProperty -Path $RegistryPath -Name 'ApprovalLevel' -Value 1 -PropertyType DWORD -Force -Verbose
    }
    #if the property does exist, change it to the correct value
    else {
        Set-ItemProperty -Path $RegistryPath -Name 'ApprovalLevel' -Value 1 -Verbose
    }

    #Put the security back on the registry
    $RegKeyDotNETItem.SetAccessControl($ACLinfo)

    #Restart service
    if (($TheService)) {
        $TheService | Start-Service -Confirm:$false -Verbose
        $TheService.WaitForStatus('Running')
    }
#>
}
Stop-Transcript #stop logging

Create a compliance rule, I named mine "ApprovalLevel" as that is the registry item that we are dealing with.  Here are the settings for it:
Value = 1
Check the checkbox "Run the specified remediation script when this setting is noncompliant"
Check the checkbox "Report noncompliance if the setting instance is not found"

From there, add your new CI to a baseline, deploy the baseline at your (suspected) Thunderbolt enabled machines and it should set the setting for you.

With BIOS set to SecureConnect and this registry entry your users should be able to approve devices to connect to their computer and will only need to do it the first time similar to pairing a Bluetooth device.

- Enjoy

Wednesday, April 17, 2019

File Rename Script

Just a quick script to rename all files in a folder:

$theFolder = "\\server.domain.com\share\folder"
$theFiles = (Get-ChildItem -Path $myFolder -Filter Prefix*.type ).Name
foreach ($File in $myFiles)
{
    $FullPathFileName = $theFolder + "\" + $File
    $newName = ($File).Replace(" ","_")
    Rename-Item -Path $FullPathFileName -NewName $newName -Force
}

Wednesday, February 20, 2019

MBR to GPT conversion

This will run the MBR2GPT tool's validation before attempting conversion:

@ECHO OFF
MBR2GPT.EXE /validate
IF %ERRORLEVEL% NEQ 0 GOTO ERROR

MBR2GPT.EXE /convert
IF %ERRORLEVEL% NEQ 0 GOTO ERROR

:ERROR
EXIT /B %ERRORLEVEL%

Tuesday, February 19, 2019

Decrypt all of the BitLocker encrypted drives

I needed Bitlocker actually OFF, not just suspended, so here's the script that I used to ensure that it got there:


For Win10:
-----------------------------------
#Decrypt-BitlockerDrives.PS1
#Script by Mark Randol
#randoltech.blogspot.com
#This script finds all local encrypted volumes and decrypt them.
#Does not exit until decryption is completed.

CLS
Clear-BitLockerAutoUnlock #Since we're decrypting all of the drives, and any Auto-Unlock protectors are tied to the encryption on the system drive, these need to go away.

$PossibleDrives = (Get-BitLockerVolume).MountPoint  #get all of the drives that could possibly be encrypted

#Disable-BitLocker -MountPoint $PossibleDrives #start all of the discovered drives decrypting in parallel

foreach ($DriveLetter in $PossibleDrives) {  #step through the drives in series to ensure they get decrypted
    [int]$LastEncryptPercent = 100  #This variable stores the most recent change to the encryption percentage 
    [int]$CurrentEncryptPercent = 2  #This variable stores the current check that we are making on the encryption level
    #If these two variables are equal we know that no progress has been made in decryption since the last check
    do { #check the encryption level of the drive every five minutes until it is fully decrypted
        $CurrentEncryptPercent = (Get-BitLockerVolume -MountPoint $DriveLetter).EncryptionPercentage
        if ($CurrentEncryptPercent -ne $LastEncryptPercent) { #if the percentage of encryption has changed since the last check then write that to the output
            $OutputString = "Drive " + $DriveLetter + $CurrentEncryptPercent.ToString() + "% encrypted"
            Write-Output $OutputString
            $LastEncryptPercent = (Get-BitLockerVolume -MountPoint $DriveLetter).EncryptionPercentage #Since the encryption percentage has changed, lets store the this percentage as our "last" (most recent)
        }
        Start-Sleep -Seconds 300  #wait five minutes before checking again
    }
    while ($CurrentEncryptPercent -ne 0)
}
Write-Output (Get-BitLockerVolume)

---------------------------------



For Win7
#Decrypt-BitlockerDrives.PS1
#Script by Mark Randol
#randoltech.blogspot.com
#
#This script will list out all of the encryptable volumes on the local machine and decrypt them
#Do not exit until decryption is completed.
#there are simpler ways to do this with modern Powershell commands (Get-BitLockerVolume for example)
#but those methods do not work with a native Windows 7 PowerShell environment so this was
#developed to help facilitate Windows 7 to Windows 10 migration.
$WMINameSpace = "root\CIMv2\Security\MicrosoftVolumeEncryption"
$WMIClass = "Win32_EncryptableVolume"
$BitLockerDrives = (Get-Wmiobject -Namespace $WMINameSpace -Class $WMIClass -ComputerName $env:COMPUTERNAME).DriveLetter
foreach ($LockedDrive in $BitLockerDrives) {
    $Status = (Get-Wmiobject -Namespace $WMINameSpace -Class $WMIClass -ComputerName $env:COMPUTERNAME -Filter “DriveLetter=""$LockedDrive""”).ConversionStatus
    if ($Status -ne 0) {
        if ($Status -eq 1) {
            Invoke-Command {manage-bde.exe -off C:}
        }
}
foreach ($LockedDrive in $BitLockerDrives) {
    $Status = (Get-Wmiobject -Namespace $WMINameSpace -Class $WMIClass -ComputerName $env:COMPUTERNAME -Filter “DriveLetter=""$LockedDrive""”).ConversionStatus
    if ($Status -ne 0) {
        do {
            Start-Sleep 15
            $Status = (Get-Wmiobject -Namespace $WMINameSpace -Class $WMIClass -ComputerName $env:COMPUTERNAME -Filter “DriveLetter=""$LockedDrive""”).ConversionStatus
        }
        until ($Status -eq 0)
    }
}
{Exit $LASTEXITCODE}


Wednesday, January 16, 2019

Script to stamp the registry with your OSD variables


# Script by Mark Randol
# randoltech.blogspot.com

$registryPath = "HKLM:\Software\MyCompany\SCCM Operating System Deployment"
[String[]]$OSDVariables = "OSArchitecture","OSDAnswerFilePath","OSDComputerName","OSDImagePackageId","OSDImageVersion","OSDTargetSystemDrive","OSDTargetSystemParition","OSDTargetSystemRoot","OSVersionNumber","_OSDOSImagePackageId","_OSDTargetSystemRoot","_SMSTSAdvertID","_SMSTSAssignedSiteCode","_SMSTSBootImageID","_SMSTSBootMediaPackageID","_SMSTSLaunchMode","_SMSTSLogPath","_SMSTSMachineName","_SMSTSMediaType","_SMSTSOrgName","_SMSTSPackageID","_SMSTSPackageName","_SMSTSSiteCode","_SMSTSStandAloneMedia","_SMSTSSupportUnknownMachines","_SMSTSUserStatePath"

if (!(Test-Path $registryPath)) { New-Item -Path $registryPath -Force | Out-Null }

$InstallDate = Get-Date
New-ItemProperty -Path $registryPath -Name "OSInstallDateTime" -Value $InstallDate -PropertyType STRING -Force | Out-Null

$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
foreach ($OSDVariableName in $OSDVariables)
{
  $OSDVariableValue = $tsenv.Value($OSDVariableName)
  New-ItemProperty -Path $registryPath -Name $OSDVariableName -Value $OSDVariableValue -PropertyType STRING -Force | Out-Null
}

Thursday, January 10, 2019

Command lines for creating a bootable USB thumb drive

Command lines for creating a bootable USB thumb drive:

diskpart
list disk
sel dis #
clean
create par pri
sel par 1
format fs=ntfs quick <--- for Legacy
format fs=fat32 quick <--- for UEFI
act
exit

Thursday, October 18, 2018

Step-by-Step how PXE boots a machine using SCCM OSD

I had a problem that was quite difficult to work through and in order to figure it out I had to go really deep on PXE.  Figured this step-by-step might help someone else in the future.

  1. The network boot client computer sends a broadcast to entire network with option 60 (on any normal network this will only actually broadcast on the local subnet but IP helpers generally get it to the DHCP server).
  2. Both DHCP and the WDS server get the broadcast (either both are assigned as DHCP servers with IP helpers or DHCP options are set to forward the request to the WDS).
  3. DHCP offers an IP address to the client (keyword "offers", this hasn't been accepted yet).
  4. Before the client machine accepts the IP address it waits for a signal from the WDS server WDS.  Before sending the signal back to the client the WDS sever runs a stored procedure, LOOKUPDEVICE, against the SCCM database.  If the client machine is found in SCCM or if there is an advertisement for "Unknown Machines" collection then WDS signals the client to proceed with the PXE boot.
  5. The client machine now accepts the IP offered by DHCP.
  6. DHCP DORA finally completes when the DHCP server acknowledges the client IP assignment.  The client machine now has an IP address and is ready to proceed.
  7. The client machine downloads WDSNBP.COM from PXE server to detect the hardware architecture (x86 or x64)
  8. The client downloads the PXEBOOT.COM boot files for its architecture from PXE server.  The file downloaded at this step is controlled/ monitored by SMSPXE.
  9. SMSPXE runs a stored procedure called getbootaction and depending on the result, it gives the PXE boot files to client.
  10. The client machine now downloads the Boot image, bootmgr.exe and BCD store.  This is an SMB file transfer, all previous file transfers were TFTP. Boot image downloaded here would be dependent on the result of the architecture detection done earlier by WDSNBP file.
  11. Once the Boot image and the other two files are downloaded completely BootMGR and BCD store are used to initialize the WINPE environment.
For my particular problem it turned out to be a bad switch dropping some packets.  It didn't really present itself with the tiny little TFTP (UDP) downloads but as soon as we hit the first SMB file transfer (TCP) things failed.  Made it look like a DHCP handoff problem when it was actually a file transfer problem.  Would never have found it without understanding how this works.

Enjoy!

Another good reference on network boot process:
https://blogs.technet.microsoft.com/dominikheinz/2011/03/18/sccm-pxe-network-boot-process