-
Notifications
You must be signed in to change notification settings - Fork 160
/
Harden-Windows-Security.ps1
215 lines (196 loc) · 15.5 KB
/
Harden-Windows-Security.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
Function P {
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
Set-ExecutionPolicy -ExecutionPolicy 'Unrestricted' -Scope 'Process' -Force
[string]$PSDownloadURLMSIX = 'https://github.com/PowerShell/PowerShell/releases/download/v7.4.6/PowerShell-7.4.6-win.msixbundle'
[string]$PSMSIXDownloadPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'PowerShell.msixbundle')
try {
if ($PSVersionTable.PSEdition -eq 'Desktop' -and !(Get-Command -Name 'pwsh.exe' -ErrorAction Ignore)) {
$User = Get-LocalUser | Where-Object -FilterScript { $_.SID -eq ([System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value) }
Write-Verbose -Message 'Trying to Install PowerShell Core because it could not be found on the system' -Verbose
if ($User.PrincipalSource -eq 'MicrosoftAccount' -and (Get-Command -Name 'winget.exe' -ErrorAction Ignore)) {
# https://apps.microsoft.com/detail/9mz1snwt0n5d
Write-Verbose -Message 'Microsoft account detected, using Microsoft Store source for PowerShell installation through Winget'
$null = Winget install --id 9MZ1SNWT0N5D --accept-package-agreements --accept-source-agreements --source msstore
if ($LASTEXITCODE -ne 0) { throw "Failed to Install PowerShell Core using Winget: $LASTEXITCODE" }
}
else {
if ([System.IO.File]::Exists($PSMSIXDownloadPath)) { [System.IO.File]::Delete($PSMSIXDownloadPath) }
Write-Verbose -Message 'Local account detected or winget is not installed, cannot install PowerShell Core from Microsoft Store using Winget and msstore as the source. Downloading and Installing PowerShell directly from the official Microsoft GitHub repository using MSIX file'
Invoke-WebRequest -Uri $PSDownloadURLMSIX -OutFile $PSMSIXDownloadPath
Add-AppxPackage -Path $PSMSIXDownloadPath
}
}
}
finally { if ([System.IO.File]::Exists($PSMSIXDownloadPath)) { [System.IO.File]::Delete($PSMSIXDownloadPath) } }
pwsh.exe -NoProfile -NoLogo -NoExit -Command {
Set-ExecutionPolicy -ExecutionPolicy 'Unrestricted' -Scope 'Process' -Force
if (!(Get-Module -ListAvailable -Name 'Harden-Windows-Security-Module' -ErrorAction Ignore)) {
Write-Verbose -Message 'Installing the Harden Windows Security Module because it could not be found' -Verbose
Install-Module -Name 'Harden-Windows-Security-Module' -Force
}
Protect-WindowsSecurity -GUI
}
}
Function AppControl {
<#
.DESCRIPTION
Please refer to the provided link for all of the information about this function and detailed overview of the entire process.
.PARAMETER MSIXPath
The path to the AppControlManager MSIX file. If not provided, the latest MSIX file will be downloaded from the GitHub. It must have the version number and architecture in its file name as provided on GitHub or produced by Visual Studio.
.PARAMETER SignTool
The path to the Microsoft's Signtool.exe If not provided, the function automatically downloads the latest SignTool.exe from the Microsoft website in Nuget and will use it for the signing operations.
.LINK
https://github.com/HotCakeX/Harden-Windows-Security/wiki/AppControl-Manager
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $false)][string]$MSIXPath, [Parameter(Mandatory = $False)][string]$SignTool
)
$ErrorActionPreference = 'Stop'
if (!([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Warning -Message 'Please run this function as an Administrator'; return
}
Write-Verbose -Message 'Detecting the CPU Arch'
switch ($Env:PROCESSOR_ARCHITECTURE) {
'AMD64' { [string]$CPUArch = 'x64'; break }
'ARM64' { [string]$CPUArch = 'arm64'; break }
default { Throw [System.PlatformNotSupportedException] 'Only AMD64 and ARM64 architectures are supported.' }
}
[string]$CommonName = 'SelfSignedCertForAppControlManager'
[string]$WorkingDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), $CommonName)
[string]$CertificateOutputPath = [System.IO.Path]::Combine($WorkingDir, "$CommonName.cer")
[string]$HashingAlgorithm = 'Sha512'
# Pattern for AppControl Manager version and architecture extraction from file path and download link URL
[regex]$RegexPattern = '_(?<Version>\d+\.\d+\.\d+\.\d+)_(?<Architecture>x64|arm64)\.msix$'
Write-Verbose -Message 'Creating the working directory in the TEMP directory'
if ([System.IO.Directory]::Exists($WorkingDir)) { [System.IO.Directory]::Delete($WorkingDir, $true) }
$null = [System.IO.Directory]::CreateDirectory($WorkingDir)
try {
Write-Verbose -Message "Checking if a certificate with the common name '$CommonName' already exists."
[string[]]$CertStoresToCheck = @('Cert:\LocalMachine\My', 'Cert:\LocalMachine\Root', 'Cert:\LocalMachine\CA', 'Cert:\LocalMachine\TrustedPublisher', 'Cert:\CurrentUser\My', 'Cert:\CurrentUser\Root', 'Cert:\CurrentUser\CA', 'Cert:\CurrentUser\TrustedPublisher')
foreach ($Store in $CertStoresToCheck) {
foreach ($Item in (Get-ChildItem -Path $Store)) {
if ($Item.Subject -ieq "CN=$CommonName") {
Write-Verbose -Message "A certificate with the common name '$CommonName' in the store '$Store' already exists. Removing it."
$Item | Remove-Item -Force
}
}
}
Write-Verbose -Message 'Building the certificate'
[System.Collections.Hashtable]$Params = @{
Subject = "CN=$CommonName"
FriendlyName = $CommonName
CertStoreLocation = 'Cert:\CurrentUser\My'
KeyExportPolicy = 'NonExportable' # Private key is non-exportable for security
KeyLength = '4096' # A good balance between generation time and security
KeyAlgorithm = 'RSA'
HashAlgorithm = $HashingAlgorithm
KeySpec = 'Signature'
KeyUsage = 'DigitalSignature'
KeyUsageProperty = 'Sign'
Type = 'CodeSigningCert'
NotAfter = [System.DateTime](Get-Date).AddYears(100)
TextExtension = @('2.5.29.19={text}CA:FALSE', '2.5.29.37={text}1.3.6.1.5.5.7.3.3', '1.3.6.1.4.1.311.21.10={text}oid=1.3.6.1.5.5.7.3.3')
}
[System.Security.Cryptography.X509Certificates.X509Certificate2]$NewCertificate = New-SelfSignedCertificate @params
Write-Verbose -Message 'Exporting the certificate (public key only)'
$null = Export-Certificate -Cert $NewCertificate -FilePath $CertificateOutputPath -Type 'CERT' -Force
if ([string]::IsNullOrWhiteSpace($SignTool)) {
Write-Verbose -Message 'Finding the latest version of the Microsoft.Windows.SDK.BuildTools package from NuGet and Downloading it'
[string]$LatestSignToolVersion = (Invoke-RestMethod -Uri 'https://api.nuget.org/v3-flatcontainer/microsoft.windows.sdk.buildtools/index.json').versions | Select-Object -Last 1
Invoke-WebRequest -Uri "https://api.nuget.org/v3-flatcontainer/microsoft.windows.sdk.buildtools/${LatestSignToolVersion}/microsoft.windows.sdk.buildtools.${LatestSignToolVersion}.nupkg" -OutFile (Join-Path -Path $WorkingDir -ChildPath 'Microsoft.Windows.SDK.BuildTools.zip')
Write-Verbose -Message 'Extracting the nupkg'
Expand-Archive -Path "$WorkingDir\Microsoft.Windows.SDK.BuildTools.zip" -DestinationPath $WorkingDir -Force # Saving .nupkg as .zip to satisfy Windows PowerShell
Write-Verbose -Message 'Finding the Signtool.exe path in the extracted directory'
[string]$SignTool = (Get-Item -Path "$WorkingDir\bin\*\$CPUArch\signtool.exe").FullName
}
# Download the MSIX package if user did not provide the path to it
if ([string]::IsNullOrWhiteSpace($MSIXPath)) {
[string]$MSIXPath = [System.IO.Path]::Combine($WorkingDir, 'AppControl.Manager.msix')
# Download link for the latest version of AppControl manger is retrieved from this text file
[string]$MSIXPackageDownloadURL = Invoke-RestMethod -Uri 'https://raw.githubusercontent.com/HotCakeX/Harden-Windows-Security/refs/heads/main/AppControl%20Manager/DownloadURL.txt'
Write-Verbose -Message 'Downloading the MSIX package from the GitHub releases' -Verbose
$null = Invoke-WebRequest -Uri $MSIXPackageDownloadURL -OutFile $MSIXPath
# Get the version and architecture of the installing MSIX package app from the download URL
$RegexMatch = $RegexPattern.Match($MSIXPackageDownloadURL)
if ($RegexMatch.Success) {
$InstallingAppVersion = $RegexMatch.Groups['Version'].Value
$InstallingAppArchitecture = $RegexMatch.Groups['Architecture'].Value
}
else {
throw 'Could not get the version of the installing app from the MSIX download URL.'
}
}
else {
# Get the version and architecture of the installing MSIX package app from the User provided file path
$RegexMatch = $RegexPattern.Match($MSIXPath)
if ($RegexMatch.Success) {
$InstallingAppVersion = $RegexMatch.Groups['Version'].Value
$InstallingAppArchitecture = $RegexMatch.Groups['Architecture'].Value
}
else {
throw 'Could not get the version of the installing app from the -MSIX parameter value that you provided.'
}
}
Write-Verbose -Message 'Signing the App Control Manager MSIX package'
# In this step the SignTool detects the cert to use based on Common name + ThumbPrint + Hash Algo + Store Type + Store Name
if ($VerbosePreference -eq 'Continue') {
# Displays full debug logs if -Verbose is used or Verbose preference of the session is set to Continue
. $SignTool sign /debug /n $CommonName /fd $HashingAlgorithm /s 'My' /sha1 $NewCertificate.Thumbprint $MSIXPath
}
else {
# Displays no output if the command runs successfully, and displays minimal output if the command fails.
$null = . $SignTool sign /q /n $CommonName /fd $HashingAlgorithm /s 'My' /sha1 $NewCertificate.Thumbprint $MSIXPath
}
if ($LASTEXITCODE -ne 0) { throw "SignTool Failed. Exit Code: $LASTEXITCODE" }
$NewCertificate | Remove-Item -Force # Remove the certificate from Current User's personal certificate store because we don't need its private key anymore
Write-Verbose -Message "Adding the certificate to the 'Local Machine/Trusted Root Certification Authorities' store with public key only. This safely stores the certificate on your device, ensuring its private key does not exist so cannot be used to sign anything else."
$null = Import-Certificate -FilePath $CertificateOutputPath -CertStoreLocation 'Cert:\LocalMachine\Root'
Write-Verbose -Message 'Checking for existence of the application in unpacked format'
$PossibleExistingApp = Get-AppxPackage -Name 'AppControlManager'
if ($null -ne $PossibleExistingApp) {
if ($PossibleExistingApp.IsDevelopmentMode -eq $true) {
if ($PossibleExistingApp.SignatureKind -eq 'None') {
# Without this step, the installing would fail
Write-Verbose -Message 'The MSIX package is already installed in an unpacked format. Removing it and installing it again from the MSIX file.'
$PossibleExistingApp | Remove-AppxPackage -AllUsers
}
}
# Get the details of the currently installed app before attempting to install the new one
[string]$InstalledAppVersionBefore = $PossibleExistingApp.Version
[string]$InstalledAppArchitectureBefore = $PossibleExistingApp.Architecture
Write-Verbose -Message "The AppControl Manager app is already installed with version '$InstalledAppVersionBefore' and architecture '$InstalledAppArchitectureBefore'"
}
Write-Verbose -Message "Installing AppControl Manager MSIX Package version '$InstallingAppVersion' with architecture '$InstallingAppArchitecture'"
Add-AppPackage -Path $MSIXPath -ForceUpdateFromAnyVersion -DeferRegistrationWhenPackagesAreInUse
[string]$InstallingAppLocationToAdd = 'C:\Program Files\WindowsApps\AppControlManager_' + $InstallingAppVersion + '_' + $InstallingAppArchitecture + '__sadt7br7jpt02\'
Write-Verbose -Message "Adding the new app install's files To the ASR Rules exclusions."
# The cmdlet won't add duplicates
Add-MpPreference -AttackSurfaceReductionOnlyExclusions (($InstallingAppLocationToAdd + 'AppControlManager.exe'), ($InstallingAppLocationToAdd + 'AppControlManager.dll'))
# If the app version being installed is not the same as the app version currently installed
if ($InstallingAppVersion -ne $InstalledAppVersionBefore) {
if (![string]::IsNullOrWhiteSpace($InstalledAppVersionBefore) -and ![string]::IsNullOrWhiteSpace($InstalledAppArchitectureBefore)) {
Write-Verbose -Message 'Removing ASR Rules exclusions that belong to the previous app version.'
# No check is needed since the Remove-MpPreference accepts any string and only removes them if they exist
[string]$InstalledAppLocationToRemove = 'C:\Program Files\WindowsApps\AppControlManager_' + $InstalledAppVersionBefore + '_' + $InstalledAppArchitectureBefore + '__sadt7br7jpt02\'
Remove-MpPreference -AttackSurfaceReductionOnlyExclusions (($InstalledAppLocationToRemove + 'AppControlManager.exe'), ($InstalledAppLocationToRemove + 'AppControlManager.dll'))
}
}
else {
Write-Verbose -Message 'Current and new AppControl Manager versions are the same, skipping removing previous ASR rules exclusions.'
}
try {
$ValidateAdminCodeSignaturesRegPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$ValidateAdminCodeSignaturesRegName = 'ValidateAdminCodeSignatures'
$ValidateAdminCodeSignaturesRegValue = Get-ItemProperty -Path $ValidateAdminCodeSignaturesRegPath -Name $ValidateAdminCodeSignaturesRegName -ErrorAction SilentlyContinue
# This will cause the "A referral was returned from the server." error to show up when AppControl Manager tries to start.
if ($ValidateAdminCodeSignaturesRegValue.$ValidateAdminCodeSignaturesRegName -eq 1) {
Write-Warning -Message "A policy named 'Only elevate executables that are signed and validated' is conflicting with the AppControl Manager app and won't let it start because it's self-signed with your on-device keys. Please disable the policy. It can be found in Group Policy Editor -> Computer Configuration -> Windows Settings -> Security Settings -> Local Policies -> Security Options -> 'User Account Control: Only elevate executable files that are signed and validated'"
}
}
catch {}
}
finally { [System.IO.Directory]::Delete($WorkingDir, $true) } # Cleaning up the working directory in the TEMP directory
}