Skip to content

Instantly share code, notes, and snippets.

@nullbind
Last active November 6, 2023 02:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nullbind/c0cee65a281d30e4d53b618f026ccd12 to your computer and use it in GitHub Desktop.
Save nullbind/c0cee65a281d30e4d53b618f026ccd12 to your computer and use it in GitHub Desktop.
Get-WinProxyInfo.ps1
# Work in progress
# Automation goals
# 1 enumeration HTTP proxy configurations on Windows and AD domain joined systems
# 2 parse the proxies
# 3 test for unauthenticated outbound internet access.
# 4 produce inventory of available proxies and if auth is requires (proxy_url, proxy_port, proxy_source, authentication_required)
# ----------------------------------
# Get-HttpProxyInfo
# ----------------------------------
# Scott Sutherland (@_nullbind), NETSPI
# review: https://github.com/WAftring/NetScripts/blob/3965e8b1e7cc8502bce3ea1a774b7856b15c0c5b/Test-Proxy.ps1
# review: https://github.com/rgl/squid-cache-vagrant/blob/d4cb88ddc90499fe1ad1da6e9551b1abd44dda71/provision-windows-proxy.ps1
# review: https://github.com/theaquamarine/pacfiles/blob/bb65d99e0cf5c45d6c40c3f65da0385347492b66/pacparser/pacparser.cs
<#
url – The full URL being accessed in web browser. (http:// or https:// or ftp://)
host – The hostname from the above url. port numbers and sub-location is not included in this
return – Return value can be any of the following
DIRECT – Redirects requests directly to the destination
PROXY host:port – Redirects requests to Proxy server
SOCKS host:port – Redirects requests to SOCKS server
add wpad, add the ipv6 version
note for change proxy programmatically, netsh winhttp set proxy="127.0.0.1:9000" "10.*,172.*,192.168.*" + powershell
notes for presidence
#>
Function Get-HttpProxyInfo
{
[CmdletBinding()]
Param(
[Parameter(Mandatory = $false,
HelpMessage = 'Set specific domain. This is set to the userdnsdomain by default')]
[string]$Domain,
[Parameter(Mandatory = $false,
HelpMessage = 'Set specific domain controller. This is set to the logon server by default.')]
[string]$DomainController,
[Parameter(Mandatory = $false,
HelpMessage = 'User to authenticate to DC with.')]
[string]$Username,
[Parameter(Mandatory = $false,
HelpMessage = 'Password to authenticate to DC with.')]
[string]$Password
)
Process
{
# ---------------------------------------------
# Get Proxy Settings: ProxyServer User
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking ProxyServer user settings"
# Get proxy file
$ProxyServer = Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxyServer" -ErrorAction SilentlyContinue
# Get proxy status
$ProxyEnable = Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxyEnable" -ErrorAction SilentlyContinue
# Display object
if($ProxyServer -ne "")
{
$ProxyServerV = $ProxyServer | Select ProxyServer -ExpandProperty ProxyServer
$ProxyEnableV = $ProxyEnable | Select ProxyEnable -ExpandProperty ProxyEnable
Write-Verbose " [*] ProxyServer user settings:"
Write-Verbose " [*] - Registry Key : HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Setting\"
Write-Verbose " [*] - Registry Property : ProxyServer"
Write-Verbose " [*] - Proxy Server : $ProxyServerV"
Write-Verbose " [*] - Proxy Enabled : $ProxyEnableV"
}else{
Write-Verbose " [*] - ProxyServer user settings not found."
}
# ---------------------------------------------
# Get Proxy Settings: ProxyServer Machine
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking ProxyServer machine settings"
# Get proxy file
$ProxyServer = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxyServer" -ErrorAction SilentlyContinue
# Get proxy status
$ProxyEnable = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxyEnable" -ErrorAction SilentlyContinue
# Display object
if($ProxyServer -ne "")
{
$ProxyServerV = $ProxyServer | Select ProxyServer -ExpandProperty ProxyServer
$ProxyEnableV = $ProxyEnable | Select ProxyEnable -ExpandProperty ProxyEnable
Write-Verbose " [*] ProxyServer machine settings:"
Write-Verbose " [*] - Registry Key : HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Setting\"
Write-Verbose " [*] - Registry Property : ProxyServer"
Write-Verbose " [*] - Proxy Server : $ProxyServerV"
Write-Verbose " [*] - Proxy Enabled : $ProxyEnableV"
}else{
Write-Verbose " [*] - ProxyServer machine settings not found."
}
# ---------------------------------------------
# Get Proxy Settings: AutoConfigURL User
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking AutoConfigURL user settings"
# Get proxy file
$AutoConfigURLU = Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "AutoConfigURL" -ErrorAction SilentlyContinue
# Get proxy status
$AutoProxyUEnable = Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxyEnable" -ErrorAction SilentlyContinue
# Display object
if($AutoConfigURL -ne "")
{
$AutoConfigURLV = $AutoConfigURLU | Select AutoConfigURL -ExpandProperty AutoConfigURL
$AutoProxyUEnableV = $AutoProxyUEnable | Select ProxyEnable -ExpandProperty ProxyEnable
Write-Verbose " [*] AutoConfigURL user setting:"
Write-Verbose " [*] - Registry Key : HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Setting\"
Write-Verbose " [*] - Registry Property : AutoConfigURL"
Write-Verbose " [*] - Proxy Server : $AutoConfigURLV"
Write-Verbose " [*] - Proxy Enabled : $AutoProxyUEnableV"
}else{
Write-Verbose " [*] - AutoConfigURL user settings not found."
}
# ---------------------------------------------
# Get Proxy Settings: AutoConfigURL Machine
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking AutoConfigURL machine settings"
# Get proxy file
$AutoConfigURLM = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "AutoConfigURL" -ErrorAction SilentlyContinue
# Get proxy status
$AutoProxyMEnable = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings" -Name "ProxyEnable" -ErrorAction SilentlyContinue
# Display object
if($AutoConfigURL -ne "")
{
$AutoConfigURLV = $AutoConfigURLM | Select AutoConfigURL -ExpandProperty AutoConfigURL
$AutoProxyMEnableV = $AutoProxyMEnable | Select ProxyEnable -ExpandProperty ProxyEnable
Write-Verbose " [*] AutoConfigURL machine setting:"
Write-Verbose " [*] - Registry Key : HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Setting\"
Write-Verbose " [*] - Registry Property : AutoConfigURL"
Write-Verbose " [*] - Proxy Server : $AutoConfigURLV"
Write-Verbose " [*] - Proxy Enabled : $AutoProxyMEnableV"
}else{
Write-Verbose " [*] - AutoConfigURL machine setting not found."
}
# ---------------------------------------------
# Get Proxy Settings: WinHttpSettings User
# ---------------------------------------------
# Reference: https://github.com/Ichigo49/psh-tools/blob/ee2111986db6bd316e3e90214789d5c9f3837351/lib/Scripts/Proxy.ps1
Write-Verbose ""
Write-Verbose " [*] Checking WinHttpSettings user settings"
try{
$binval = (Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -Name WinHttpSettings -ErrorAction SilentlyContinue ).WinHttpSettings
$proxylength = $binval[12]
if ($proxylength -gt 0) {
$proxy = -join ($binval[(12+3+1)..(12+3+1+$proxylength-1)] | % {([char]$_)})
$bypasslength = $binval[(12+3+1+$proxylength)]
if ($bypasslength -gt 0) {
$bypasslist = -join ($binval[(12+3+1+$proxylength+3+1)..(12+3+1+$proxylength+3+1+$bypasslength)] | % {([char]$_)})
} else {
$bypasslist = '(none)'
}
Write-Verbose " [*] Current WinHTTP proxy user settings:"
Write-Verbose " [*] - Proxy Server(s): $proxy"
Write-Verbose " [*] - Bypass List : $bypasslist"
} else {
Write-Verbose " [*] - No WinHttpProxy user settings found."
}
}catch{
Write-Verbose " [*] - No WinHttpProxy user settings found."
}
# ---------------------------------------------
# Get Proxy Settings: WinHttpSettings Machine
# ---------------------------------------------
# Reference: https://github.com/Ichigo49/psh-tools/blob/ee2111986db6bd316e3e90214789d5c9f3837351/lib/Scripts/Proxy.ps1
Write-Verbose ""
Write-Verbose " [*] Checking WinHttpSettings machine settings"
try{
$binval = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections" -Name WinHttpSettings -ErrorAction SilentlyContinue).WinHttpSettings
$proxylength = $binval[12]
if ($proxylength -gt 0) {
$proxy = -join ($binval[(12+3+1)..(12+3+1+$proxylength-1)] | % {([char]$_)})
$bypasslength = $binval[(12+3+1+$proxylength)]
if ($bypasslength -gt 0) {
$bypasslist = -join ($binval[(12+3+1+$proxylength+3+1)..(12+3+1+$proxylength+3+1+$bypasslength)] | % {([char]$_)})
} else {
$bypasslist = '(none)'
}
Write-Verbose " [*] Current WinHTTP proxy machine settings:"
Write-Verbose " [*] - Proxy Server(s): $proxy"
Write-Verbose " [*] - Bypass List : $bypasslist"
} else {
Write-Verbose " [*] - No WinHttpProxy machine settings found."
}
}catch{
Write-Verbose " [*] - No WinHttpProxy machine settings found."
}
# ---------------------------------------------
# Get Proxy Settings: DefaultConnectionSettings
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking DefaultConnectionSettings settings"
#HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections
#DefaultConnectionSettings
# ---------------------------------------------
# Get Proxy Settings: SavedLegacySettings
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking SavedLegacySettings settings"
#HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections
#SavedLegacySettings
# ---------------------------------------------
# Get Proxy Settings: ProxyOverride
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking ProxyOverride settings"
#HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections
#ProxyOverride
# ---------------------------------------------
# Get Proxy Settings: GP Registry.pol Files
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Checking proxy settings deployed via Group Policy registry files"
$ProxyPacFiles = Get-GPRegistryPolicy | where valuedata -like "*.pac*" | select keyname,valuedata,filepath
$ProxyPacFileCount = $ProxyPacFiles.count
Write-Verbose " [*] Group policy registry.pol files deployed $ProxyPacFileCount .pac proxy files"
$ProxyPacFiles |
foreach {
$KeyName = $_.KeyName
$PacFile = $_.ValueData
$FilePath = $_.FilePath
Write-Verbose " [*] - File Path : $FilePath"
Write-Verbose " [*] - Registry Key : $KeyName"
Write-Verbose " [*] - Proxy Server : $PacFile"
}
# ---------------------------------------------
# Parse .pac files
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Parsing proxy .pac files"
# Create Proxy list
$ProxyList = @{}
# ---------------------------------------------
# Uniq proxy list
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Getting unique proxy list"
# ---------------------------------------------
# Test access without credentials
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Testing access through proxies without credentials"
# A proxy auto-config (PAC) file defines how web browsers and other user agents can automatically choose the appropriate proxy server (access method) for fetching a given URL
# ---------------------------------------------
# Test access with current/provided credentials
# ---------------------------------------------
Write-Verbose ""
Write-Verbose " [*] Testing access through proxies with credentials"
}
}
# Reference: https://gist.github.com/b4tman/a4ed14e37175dd47acc082abc3846fcf
function TestProxy{
[CmdletBinding()]
param(
[parameter(Mandatory=$true, Position=1)]
[string]$ProxyServer="",
[parameter(Mandatory=$false, Position=2)]
[string]$TestURL="https://www.netspi.com"
)
try {
Invoke-WebRequest -uri $TestURL -Method HEAD -MaximumRedirection 0 -TimeoutSec 14 -UseBasicParsing -UserAgent ([Microsoft.PowerShell.Commands.PSUserAgent]::InternetExplorer) -DisableKeepAlive -Proxy $ProxyServer -ErrorAction Stop | Out-Null
$result = $true
} catch [System.InvalidOperationException] {
if ($_.FullyQualifiedErrorId -match "MaximumRedirectExceeded") {
$result = $true
} else {
$result = $false
}
} catch {
$result = $false
}
return $result
}
# -------------------------------------------------------------
# SUPPORTING FUNCTIONS
# -------------------------------------------------------------
#requires -version 5
# ----------------------------------
# Get-GPRegistryPolicy
# ----------------------------------
# Parsing functions are based on https://github.com/PowerShell/GPRegistryPolicyParser by Zia Jalali.
# Wrapper and Modifications by Scott Sutherland (@_nullbind), NETSPI
Function Get-GPRegistryPolicy
{
<#
.SYNOPSIS
This script can be used to find and parse registry.pol files stored on domain controllers.
In some cases it can be used to identify cleartext passwords.
This is just a wrapper for https://github.com/PowerShell/GPRegistryPolicyParser.
By default this will write discovery registries entries to a .csv file.
.PARAMETER Domain
Set specific domain.
.PARAMETER DomainController
Set specific domain controller.
.Example
Get-GPRegistryPolicy -Verbose
.Example
Get-GPRegistryPolicy -Verbose -Domain domain.com -DomainController 192.168.1.5
#>
[CmdletBinding()]
Param(
[Parameter(Mandatory = $false,
HelpMessage = 'Set specific domain. This is set to the userdnsdomain by default')]
[string]$Domain,
[Parameter(Mandatory = $false,
HelpMessage = 'Set specific domain controller. This is set to the logon server by default.')]
[string]$DomainController
)
Process
{
# Domain Name
if(-not $domain){
$domain = $env:USERDNSDOMAIN
}
# Domain Controller
if(-not $DomainController){
$DomainController = $env:LOGONSERVER -replace "\\",""
}
# Policies Path
$ServerPath = "\\$DomainController\sysvol\$domain\Policies"
Write-Verbose " [*] - Target Domain: $domain"
Write-Verbose " [*] - Target DC : $DomainController"
Write-Verbose " [*] - Target Path : $ServerPath"
# Check number of policies
$Policies = Get-ChildItem \\$DomainController\sysvol\$Domain\Policies | select fullname
$PolCount = $Policies.Count
Write-Verbose " [*] - $PolCount policy folders found"
# Find registry.pol files
$RegistryPolFiles = New-Object System.Collections.ArrayList
$Policies |
Foreach {
# Get machine file
$PolicyPath = $_.fullname
$RegpolPath = "$PolicyPath\Machine\Registry.pol"
$RegistryPolFiles += $RegpolPath
# Get user file
$PolicyPath = $_.fullname
$RegpolPath = "$PolicyPath\User\Registry.pol"
$RegistryPolFiles += $RegpolPath
}
# Iterate through discovered files
$FileCount = $RegistryPolFiles.Count
Write-Verbose " [*] - $FileCount Registry.pol files are being parsed..."
$Results = $RegistryPolFiles |
Foreach {
# Parse discovered file
$FullPath = $_
$RegistrySettings = Parse-PolFile -Path "$FullPath" -ErrorAction SilentlyContinue
$RegistrySettings |
foreach{
# Build custom output object that includes the file path and the domain
$object = New-Object psobject
$object | add-member noteproperty Domain $Domain
$object | add-member noteproperty FilePath $FullPath
$object | add-member noteproperty KeyName $_.KeyName
$object | add-member noteproperty ValueType $_.ValueType
$object | add-member noteproperty ValueLength $_.ValueLength
$object | add-member noteproperty ValueData $_.ValueData
# Display entry
$object
}
}
# Status
$EntryCount = $Results.Count
Write-Verbose " [*] - $EntryCount registry entries parsed"
# Return all registry entries
$Results
}
}
###########################################################
#
# Group Policy - Registry Policy parser module
#
# Copyright (c) Microsoft Corporation, 2016
#
###########################################################
# Source: https://github.com/PowerShell/GPRegistryPolicyParser
<#
------------------------------------------ START OF LICENSE -----------------------------------------
PowerShell-GPRegistryPolicy-Cmdlets v.0.1
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------- END OF LICENSE ------------------------------------------
#>
data LocalizedData
{
# culture="en-US"
ConvertFrom-StringData @'
InvalidHeader = File '{0}' has an invalid header.
InvalidVersion = File '{0}' has an invalid version. It should be 1.
InvalidFormatBracket = File '{0}' has an invalid format. A [ or ] was expected at location {1}.
InvalidFormatSemicolon = File '{0}' has an invalid format. A ; was expected at location {1}.
OnlyCreatingKey = Some values are null. Only the registry key is created.
InvalidPath = Path {0} doesn't point to an existing registry key/property.
InternalError = Internal error while creating a registry entry for {0}
InvalidIntegerSize = Invalid size for an integer. Must be less than or equal to 8.
'@
}
# Import-LocalizedData LocalizedData #-filename GPRegistryPolicyParser.Strings.psd1
$script:REGFILE_SIGNATURE = 0x67655250 # PRef
$script:REGISTRY_FILE_VERSION = 0x00000001 #Initially defined as 1, then incremented each time the file format is changed.
$script:DefaultEntries = @(
"Software\Policies"
)
Enum RegType {
REG_NONE = 0 # No value type
REG_SZ = 1 # Unicode null terminated string
REG_EXPAND_SZ = 2 # Unicode null terminated string (with environmental variable references)
REG_BINARY = 3 # Free form binary
REG_DWORD = 4 # 32-bit number
REG_DWORD_LITTLE_ENDIAN = 4 # 32-bit number (same as REG_DWORD)
REG_DWORD_BIG_ENDIAN = 5 # 32-bit number
REG_LINK = 6 # Symbolic link (Unicode)
REG_MULTI_SZ = 7 # Multiple Unicode strings, delimited by \0, terminated by \0\0
REG_RESOURCE_LIST = 8 # Resource list in resource map
REG_FULL_RESOURCE_DESCRIPTOR = 9 # Resource list in hardware description
REG_RESOURCE_REQUIREMENTS_LIST = 10
REG_QWORD = 11 # 64-bit number
REG_QWORD_LITTLE_ENDIAN = 11 # 64-bit number (same as REG_QWORD)
}
Class GPRegistryPolicy
{
[string] $KeyName
[string] $ValueName
[RegType] $ValueType
[string] $ValueLength
[object] $ValueData
GPRegistryPolicy()
{
$this.KeyName = $Null
$this.ValueName = $Null
$this.ValueType = [RegType]::REG_NONE
$this.ValueLength = 0
$this.ValueData = $Null
}
GPRegistryPolicy(
[string] $KeyName,
[string] $ValueName,
[RegType] $ValueType,
[string] $ValueLength,
[object] $ValueData
)
{
$this.KeyName = $KeyName
$this.ValueName = $ValueName
$this.ValueType = $ValueType
$this.ValueLength = $ValueLength
$this.ValueData = $ValueData
}
[string] GetRegTypeString()
{
[string] $Result = ""
switch ($this.ValueType)
{
([RegType]::REG_SZ) { $Result = "String" }
([RegType]::REG_EXPAND_SZ) { $Result = "ExpandString" }
([RegType]::REG_BINARY) { $Result = "Binary" }
([RegType]::REG_DWORD) { $Result = "DWord" }
([RegType]::REG_MULTI_SZ) { $Result = "MultiString" }
([RegType]::REG_QWORD) { $Result = "QWord" }
default { $Result = "" }
}
return $Result
}
static [RegType] GetRegTypeFromString( [string] $Type )
{
$Result = [RegType]::REG_NONE
switch ($Type)
{
"String" { $Result = [RegType]::REG_SZ }
"ExpandString" { $Result = [RegType]::REG_EXPAND_SZ }
"Binary" { $Result = [RegType]::REG_BINARY }
"DWord" { $Result = [RegType]::REG_DWORD }
"MultiString" { $Result = [RegType]::REG_MULTI_SZ }
"QWord" { $Result = [RegType]::REG_QWORD }
default { $Result = [RegType]::REG_NONE }
}
return $Result
}
}
Function New-GPRegistryPolicy
{
param (
[Parameter(Mandatory=$true,Position=0)]
[ValidateNotNullOrEmpty()]
[string]
$keyName,
[Parameter(Position=1)]
[string]
$valueName = $null,
[Parameter(Position=2)]
[RegType]
$valueType = [RegType]::REG_NONE,
[Parameter(Position=3)]
[string]
$valueLength = $null,
[Parameter(Position=4)]
[object]
$valueData = $null
)
$Policy = [GPRegistryPolicy]::new($keyName, $valueName, $valueType, $valueLength, $valueData)
return $Policy;
}
Function Get-RegType
{
param (
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]
$Type
)
return [GPRegistryPolicy]::GetRegTypeFromString($Type)
}
<#
.SYNOPSIS
Reads and parses a .pol file.
.DESCRIPTION
Reads a .pol file, parses it and returns an array of Group Policy registry settings.
.PARAMETER Path
Specifies the path to the .pol file.
.EXAMPLE
C:\PS> Parse-PolFile -Path "C:\Registry.pol"
#>
Function Parse-PolFile
{
[OutputType([Array])]
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$Path
)
[Array] $RegistryPolicies = @()
$index = 0
[string] $policyContents = Get-Content $Path -Raw
[byte[]] $policyContentInBytes = Get-Content $Path -Raw -Encoding Byte
# 4 bytes are the signature PReg
$signature = [System.Text.Encoding]::ASCII.GetString($policyContents[0..3])
$index += 4
Assert ($signature -eq 'PReg') ($LocalizedData.InvalidHeader -f $Path)
# 4 bytes are the version
$version = [System.BitConverter]::ToInt32($policyContentInBytes, 4)
$index += 4
Assert ($version -eq 1) ($LocalizedData.InvalidVersion -f $Path)
# Start processing at byte 8
while($index -lt $policyContents.Length - 2)
{
[string]$keyName = $null
[string]$valueName = $null
[int]$valueType = $null
[int]$valueLength = $null
[object]$value = $null
# Next UNICODE character should be a [
$leftbracket = [System.BitConverter]::ToChar($policyContentInBytes, $index)
Assert ($leftbracket -eq '[') "Missing the openning bracket"
$index+=2
# Next UNICODE string will continue until the ; less the null terminator
$semicolon = $policyContents.IndexOf(";", $index)
Assert ($semicolon -ge 0) "Failed to locate the semicolon after key name."
$keyName = [System.Text.Encoding]::UNICODE.GetString($policyContents[($index)..($semicolon-3)]) # -3 to exclude the null termination and ';' characters
$index = $semicolon + 2
# Next UNICODE string will continue until the ; less the null terminator
$semicolon = $policyContents.IndexOf(";", $index)
Assert ($semicolon -ge 0) "Failed to locate the semicolon after value name."
$valueName = [System.Text.Encoding]::UNICODE.GetString($policyContents[($index)..($semicolon-3)]) # -3 to exclude the null termination and ';' characters
$index = $semicolon + 2
# Next DWORD will continue until the ;
$semicolon = $index + 4 # DWORD Size
Assert ([System.BitConverter]::ToChar($policyContentInBytes, $semicolon) -eq ';') "Failed to locate the semicolon after value type."
$valueType = [System.BitConverter]::ToInt32($policyContentInBytes, $index)
$index=$semicolon + 2 # Skip ';'
# Next DWORD will continue until the ;
$semicolon = $index + 4 # DWORD Size
Assert ([System.BitConverter]::ToChar($policyContentInBytes, $semicolon) -eq ';') "Failed to locate the semicolon after value length."
$valueLength = Convert-StringToInt -ValueString $policyContentInBytes[$index..($index+3)]
$index=$semicolon + 2 # Skip ';'
if ($valueLength -gt 0)
{
# String types less the null terminator for REG_SZ and REG_EXPAND_SZ
# REG_SZ: string type (ASCII)
if($valueType -eq [RegType]::REG_SZ)
{
[string] $value = [System.Text.Encoding]::UNICODE.GetString($policyContents[($index)..($index+$valueLength-3)]) # -3 to exclude the null termination and ']' characters
$index += $valueLength
}
# REG_EXPAND_SZ: string, includes %ENVVAR% (expanded by caller) (ASCII)
if($valueType -eq [RegType]::REG_EXPAND_SZ)
{
[string] $value = [System.Text.Encoding]::UNICODE.GetString($policyContents[($index)..($index+$valueLength-3)]) # -3 to exclude the null termination and ']' characters
$index += $valueLength
}
# For REG_MULTI_SZ leave the last null terminator
# REG_MULTI_SZ: multiple strings, delimited by \0, terminated by \0\0 (ASCII)
if($valueType -eq [RegType]::REG_MULTI_SZ)
{
[string] $value = [System.Text.Encoding]::UNICODE.GetString($policyContents[($index)..($index+$valueLength-3)])
$index += $valueLength
}
# REG_BINARY: binary values
if($valueType -eq [RegType]::REG_BINARY)
{
[byte[]] $value = $policyContentInBytes[($index)..($index+$valueLength-1)]
$index += $valueLength
}
}
# DWORD: (4 bytes) in little endian format
if($valueType -eq [RegType]::REG_DWORD)
{
$value = Convert-StringToInt -ValueString $policyContentInBytes[$index..($index+3)]
$index += 4
}
# QWORD: (8 bytes) in little endian format
if($valueType -eq [RegType]::REG_QWORD)
{
$value = Convert-StringToInt -ValueString $policyContentInBytes[$index..($index+7)]
$index += 8
}
# Next UNICODE character should be a ]
$rightbracket = $policyContents.IndexOf("]", $index) # Skip over null data value if one exists
Assert ($rightbracket -ge 0) "Missing the closing bracket."
$index = $rightbracket + 2
$entry = New-GPRegistryPolicy $keyName $valueName $valueType $valueLength $value
$RegistryPolicies += $entry
}
return $RegistryPolicies
}
<#
.SYNOPSIS
Reads registry policies from a list of entries.
.DESCRIPTION
Reads registry policies from a list of entries and returns an array of GPRegistryPolicies.
.PARAMETER Division
Specifies the division from which the registry entries will be read.
.EXAMPLE
C:\PS> Read-RegistryPolicies -Division "LocalMachine"
.EXAMPLE
C:\PS> Read-RegistryPolicies -Division "LocalMachine" -Entries @('Software\Policies\Microsoft\Windows', 'Software\Policies\Microsoft\WindowsFirewall')
#>
Function Read-RegistryPolicies
{
[OutputType([Array])]
param (
[ValidateSet("LocalMachine", "CurrentUser", "Users")]
[string]
$Division = "LocalMachine",
[string[]]
$Entries = $script:DefaultEntries
)
[Array] $RegistryPolicies = @()
switch ($Division)
{
'LocalMachine' { $Hive = [Microsoft.Win32.Registry]::LocalMachine }
'CurrentUser' { $Hive = [Microsoft.Win32.Registry]::CurrentUser }
'Users' { $Hive = [Microsoft.Win32.Registry]::Users }
}
foreach ($entry in $Entries)
{
#if (Test-Path -Path $entry)
if (IsRegistryKey -Path $entry -Hive $Hive)
{
# $entry is a key.
$Key = $Hive.OpenSubKey($entry)
# Add the key itself
$rp = New-GPRegistryPolicy -keyName $entry
$RegistryPolicies += $rp
# Check default value
if ($Key.GetValue(''))
{
$info = Get-RegKeyInfo -RegKey $Key -ValueName ''
$rp = New-GPRegistryPolicy -keyName $entry -valueName '' -valueType $info.Type -valueLength $info.Size -valueData $info.Data
$RegistryPolicies += $rp
}
if ($Key.ValueCount -gt 0)
{
# Copy values under the key
$ValueNames = $Key.GetValueNames()
foreach($value in $ValueNames)
{
if ([System.String]::IsNullOrEmpty($value))
{
$rp = New-GPRegistryPolicy -keyName $entry
}
else
{
$info = Get-RegKeyInfo -RegKey $Key -ValueName $value
$rp = New-GPRegistryPolicy -keyName $entry -valueName $value -valueType $info.Type -valueLength $info.Size -valueData $info.Data
}
$RegistryPolicies += $rp
}
}
if ($Key.SubKeyCount -gt 0)
{
# Copy subkeys recursively
$SubKeyNames = $Key.GetSubKeyNames()
$newEntries = @()
foreach($subkey in $SubKeyNames)
{
$newEntry = Join-Path -Path $entry -ChildPath $subkey
$newEntries += ,$newEntry
}
$RegistryPolicies += Read-RegistryPolicies -Entries $newEntries -Division $Division
}
}
else
{
$Tokens = $entry.Split('\')
$Property = $Tokens[-1]
$ParentKey = $Tokens[0..($Tokens.Count-2)] -join '\'
$NoSuchKeyOrProperty = $false
if (IsRegistryKey -Path $ParentKey -Hive $Hive)
{
# $entry is a property.
# [key;value;type;size;data]
$Key = $Hive.OpenSubKey($ParentKey)
if ($Key.GetValueNames() -icontains $Property)
{
$info = Get-RegKeyInfo -RegKey $Key -ValueName $Property
$rp = [GPRegistryPolicy]::new($ParentKey, $Property, $info.Type, $info.Size, $info.Data)
$RegistryPolicies += $rp
}
else
{
$NoSuchKeyOrProperty = $true
}
}
else
{
$NoSuchKeyOrProperty = $true
}
if ( $NoSuchKeyOrProperty -and @('Continue', 'SilentlyContinue', 'Ignore' ) -inotcontains $ErrorActionPreference)
{
# $entry points to a key/property that doesn't exist.
$NoSuchKeyOrProperty = $true
Fail -ErrorMessage ($LocalizedData.InvalidPath -f $entry)
}
}
}
return $RegistryPolicies
}
<#
.SYNOPSIS
Creates a .pol file entry byte array from a GPRegistryPolicy instance.
.DESCRIPTION
Creates a .pol file entry byte array from a GPRegistryPolicy instance. This entry can be written
in a .pol file later.
.PARAMETER RegistryPolicy
Specifies the registry policy entry.
#>
Function Create-RegistrySettingsEntry
{
[OutputType([Array])]
param (
[Parameter(Mandatory = $true)]
[alias("RP")]
[GPRegistryPolicy]
$RegistryPolicy
)
# Entry format: [key;value;type;size;data]
[Byte[]] $Entry = @()
$Entry += [System.Text.Encoding]::Unicode.GetBytes('[') # Openning bracket
$Entry += [System.Text.Encoding]::Unicode.GetBytes($RP.KeyName + "`0")
$Entry += [System.Text.Encoding]::Unicode.GetBytes(';') # semicolon as delimiter
$Entry += [System.Text.Encoding]::Unicode.GetBytes($RP.ValueName + "`0")
$Entry += [System.Text.Encoding]::Unicode.GetBytes(';') # semicolon as delimiter
$Entry += [System.BitConverter]::GetBytes([Int32]$RP.ValueType)
$Entry += [System.Text.Encoding]::Unicode.GetBytes(';') # semicolon as delimiter
#Assert $type ($LocalizedData.InternalError -f $key)
# Get data bytes then compute byte size based on data and type
switch ($RP.ValueType)
{
{ @([RegType]::REG_SZ, [RegType]::REG_EXPAND_SZ, [RegType]::REG_MULTI_SZ) -contains $_ }
{
$dataBytes = [System.Text.Encoding]::Unicode.GetBytes($RP.ValueData + "`0")
$dataSize = $dataBytes.Count
}
([RegType]::REG_BINARY)
{
$dataBytes = [System.Text.Encoding]::Unicode.GetBytes($RP.ValueData)
$dataSize = $dataBytes.Count
}
([RegType]::REG_DWORD)
{
$dataBytes = [System.BitConverter]::GetBytes([Int32]$RP.ValueData)
$dataSize = 4
}
([RegType]::REG_QWORD)
{
$dataBytes = [System.BitConverter]::GetBytes([Int64]$RP.ValueData)
$dataSize = 8
}
default
{
$dataBytes = [System.Text.Encoding]::Unicode.GetBytes("")
$dataSize = 0
}
}
#Assert $type ($LocalizedData.InternalError -f $key)
$Entry += [System.BitConverter]::GetBytes($dataSize)
$Entry += [System.Text.Encoding]::Unicode.GetBytes(';') # semicolon as delimiter
#Assert $type ($LocalizedData.InternalError -f $key)
$Entry += $dataBytes
$Entry += [System.Text.Encoding]::Unicode.GetBytes(']') # Closing bracket
return $Entry
}
<#
.SYNOPSIS
Appends an array of registry policy entries to a file.
.DESCRIPTION
Appends an array of registry policy entries to a file.
.PARAMETER RegistryPolicies
An array of registry policy entries.
.PARAMETER Path
Path to a file (.pol extension)
#>
Function Append-RegistryPolicies
{
param (
[Parameter(Mandatory = $true)]
[GPRegistryPolicy[]]
$RegistryPolicies,
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]
$Path
)
foreach ($rp in $RegistryPolicies)
{
[Byte[]] $Entry = Create-RegistrySettingsEntry -RegistryPolicy $rp
$Entry | Add-Content -Path $Path -Encoding Byte
}
}
Function Assert
{
param (
[Parameter(Mandatory)]
$Condition,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$ErrorMessage
)
if (!$Condition)
{
Fail -ErrorMessage $ErrorMessage;
}
}
Function Fail
{
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$ErrorMessage
)
throw $ErrorMessage
}
<#
.SYNOPSIS
Creates a file and initializes it with Group Policy Registry file format signature.
.DESCRIPTION
Creates a file and initializes it with Group Policy Registry file format signature.
.PARAMETER Path
Path to a file (.pol extension)
#>
Function Create-GPRegistryPolicyFile
{
param (
[Parameter(Mandatory)]
$Path
)
$null = Remove-Item -Path $Path -Force -Verbose -ErrorAction SilentlyContinue
New-Item -Path $Path -Force -Verbose -ErrorAction Stop | Out-Null
[System.BitConverter]::GetBytes($script:REGFILE_SIGNATURE) | Add-Content -Path $Path -Encoding Byte
[System.BitConverter]::GetBytes($script:REGISTRY_FILE_VERSION) | Add-Content -Path $Path -Encoding Byte
}
<#
.SYNOPSIS
Returns the type, size and data values of a given registry key.
.DESCRIPTION
Returns the type, size and data values of a given registry key.
.PARAMETER RegKey
Registry Key
.PARAMETER ValueName
The name of the Value under the given registry key
#>
Function Get-RegKeyInfo
{
param (
[Parameter(Mandatory = $true)]
[Microsoft.Win32.RegistryKey]
$RegKey,
[Parameter(Mandatory = $true)]
[AllowEmptyString()]
[string]
$ValueName
)
switch ($RegKey.GetValueKind($ValueName))
{
"String" {
$Type = $RegKey.GetValueKind($ValueName)
$Data = $RegKey.GetValue($ValueName)
$Size = $Data.Length
}
"ExpandString" {
$Type = $RegKey.GetValueKind($ValueName)
$Data = $RegKey.GetValue($ValueName,$null,[Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
$Size = $Data.Length
}
"Binary" {
$Type = $RegKey.GetValueKind($ValueName)
$value = $RegKey.GetValue($ValueName)
$Data = [System.Text.Encoding]::Unicode.GetString($value)
$Size = $Data.Count
}
"DWord" {
$Type = $RegKey.GetValueKind($ValueName)
$Data = $RegKey.GetValue($ValueName)
$Size = 4
}
"MultiString" {
$Type = $RegKey.GetValueKind($ValueName)
$Data = ($RegKey.GetValue($ValueName) -join "`0") + "`0"
$Size = $Data.Length
}
"QWord" {
$Type = $RegKey.GetValueKind($ValueName)
$Data = $RegKey.GetValue($ValueName)
$Size = 8
}
default {
$Type = $null
$Data = $null
$Size = 0
}
}
return @{
'Type' = $Type;
'Size' = $Size;
'Data' = $Data;
}
}
Function IsRegistryKey
{
param (
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]
$Path,
[Microsoft.Win32.RegistryKey]
$Hive = [Microsoft.Win32.Registry]::LocalMachine
)
$key = $Hive.OpenSubKey($Path)
if ($key)
{
if ($PSVersionTable.PSEdition -ieq 'Core')
{
$key.Flush()
$key.Dispose()
}
else
{
$key.Close()
}
return $true
}
else
{
return $false
}
}
Function Convert-StringToInt
{
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[System.Object[]]
$ValueString
)
if ($ValueString.Length -le 4)
{
[int32] $result = 0
}
elseif ($ValueString.Length -le 8)
{
[int64] $result = 0
}
else
{
Fail -ErrorMessage $LocalizedData.InvalidIntegerSize
}
for ($i = $ValueString.Length - 1 ; $i -ge 0 ; $i -= 1)
{
$result = $result -shl 8
$result = $result + ([int][char]$ValueString[$i])
}
return $result
}
@Chris-titsus010
Copy link

Line 175 & 208 seems like a typo, WinHttPSettings instead of WinHttpSettings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment