Last active
August 1, 2024 19:50
-
-
Save jborean93/6c1f1b3130f2675f1618da56633eb1fa to your computer and use it in GitHub Desktop.
Logs Wireshark compatible TLS keys like the SSLKEYLOGFILE env var
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Requires -Module PSDetour | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory)] | |
[string] | |
$LogPath | |
) | |
$LogPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($LogPath) | |
$session = New-PSDetourSession -ProcessId lsass -ErrorAction Stop | |
try { | |
Invoke-Command -Session $session -ScriptBlock { | |
Function Get-SecretKey { | |
param([Parameter(Mandatory)][IntPtr]$KeyPtr) | |
$dddbStructPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($KeyPtr) | |
$ssl3StructPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($dddbStructPtr, 0x10) | |
$uuurStructPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($ssl3StructPtr, 0x20) | |
$mskyStructPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($uuurStructPtr, 0x10) | |
$secretLength = [System.Runtime.InteropServices.Marshal]::ReadInt32($mskyStructPtr, 0x10) | |
$secretPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($mskyStructPtr, 0x18) | |
$secret = [byte[]]::new($secretLength) | |
[System.Runtime.InteropServices.Marshal]::Copy($secretPtr, $secret, 0, $secret.Length) | |
, $secret | |
} | |
$tlsLog = [System.IO.File]::Open($using:LogPath, "Append", "Write", "Read") | |
$tlsWriter = [System.IO.StreamWriter]::new($tlsLog) | |
$tlsWriter.AutoFlush = $true | |
$state = @{ | |
GetSecretKeyFunc = ${function:Get-SecretKey}.ToString() | |
ClientRandoms = [Hashtable]::Synchronized(@{}) | |
Stages = [Hashtable]::Synchronized(@{}) | |
TlsWriter = $tlsWriter | |
} | |
$ncrypt = @{ DllName = 'ncrypt.dll' } | |
Start-PSDetour -State $state -Hook @( | |
New-PSDetourHook @ncrypt -MethodName SslHashHandshake -Action { | |
<# | |
.DESCRIPTION | |
Called for TLS 1.3 sessions and TLS 1.2 with RFC 7627 session hashing. | |
It is used to capture the client_random value in the TLS ClientHello | |
message for future reference in the other hooked functions. | |
#> | |
[OutputType([int])] | |
param ( | |
[IntPtr]$SslProvider, | |
[IntPtr]$HandshakeHash, | |
[IntPtr]$InputBytes, | |
[int]$InputLength, | |
[int]$Flags | |
) | |
# This buffer is the TLS packets, we are interested in the ClientHello | |
# which is msgType 1 and version 0x0303 (TLS 1.2) | |
$data = [byte[]]::new($InputLength) | |
[System.Runtime.InteropServices.Marshal]::Copy($InputBytes, $data, 0, $data.Length) | |
$msgType = $data[0] | |
$version = if ($msgType -eq 1) { | |
[System.BitConverter]::ToUInt16($data, 4) | |
} | |
if ($msgType -eq 1 -and $version -eq 0x0303) { | |
$crandom = [byte[]]::new(32) | |
[System.Buffer]::BlockCopy($data, 6, $crandom, 0, $crandom.Length) | |
$tid = [System.Threading.Thread]::CurrentThread.ManagedThreadId | |
$this.State.ClientRandoms[$tid] = $crandom | |
} | |
$this.Invoke($SslProvider, $HandshakeHash, $InputBytes, $InputLength, $Flags) | |
} | |
New-PSDetourHook @ncrypt -MethodName SslExpandTrafficKeys -Action { | |
<# | |
.DESCRIPTION | |
Called for TLS 1.3 sessions twice. | |
First is for the handshake traffic key and second for the first | |
handshake traffic secret. | |
The function is undocumented but Param4 is the client secret and Param5 | |
is the server secret. | |
#> | |
[OutputType([int])] | |
param ( | |
[IntPtr]$Param1, | |
[IntPtr]$Param2, | |
[IntPtr]$Param3, | |
[IntPtr]$Param4, | |
[IntPtr]$Param5 | |
) | |
$res = $this.Invoke($Param1, $Param2, $Param3, $Param4, $Param5) | |
$null = New-Item -Path Function:\Get-SecretKey -Value ([ScriptBlock]::Create( | |
$this.State.GetSecretKeyFunc)) | |
$tid = [System.Threading.Thread]::CurrentThread.ManagedThreadId | |
if ($this.State.Stages[$tid]) { | |
$suffix = 'TRAFFIC_SECRET_0' | |
} | |
else { | |
$suffix = 'HANDSHAKE_TRAFFIC_SECRET' | |
$this.State.Stages[$tid] = $true | |
} | |
$clientSecret = Get-SecretKey -KeyPtr $Param4 | |
$serverSecret = Get-SecretKey -KeyPtr $Param5 | |
$cr = $this.State.ClientRandoms[$tid] | |
($this.State.TlsWriter).WriteLine('CLIENT_{0} {1} {2}', $suffix, | |
[System.Convert]::ToHexString($cr), | |
[System.Convert]::ToHexString($clientSecret)) | |
($this.State.TlsWriter).WriteLine('SERVER_{0} {1} {2}', $suffix, | |
[System.Convert]::ToHexString($cr), | |
[System.Convert]::ToHexString($serverSecret)) | |
$res | |
} | |
New-PSDetourHook @ncrypt -MethodName SslExpandExporterMasterKey -Action { | |
<# | |
.DESCRIPTION | |
Called for TLS 1.3 sessions. | |
Gets the exporter secret through the undocumented function. | |
#> | |
[OutputType([int])] | |
param ( | |
[IntPtr]$Param1, | |
[IntPtr]$Param2, | |
[IntPtr]$Param3, | |
[IntPtr]$Param4 | |
) | |
$res = $this.Invoke($Param1, $Param2, $Param3, $Param4) | |
$null = New-Item -Path Function:\Get-SecretKey -Value ([ScriptBlock]::Create( | |
$this.State.GetSecretKeyFunc)) | |
$tid = [System.Threading.Thread]::CurrentThread.ManagedThreadId | |
$secret = Get-SecretKey -KeyPtr $Param4 | |
$cr = $this.State.ClientRandoms[$tid] | |
($this.State.TlsWriter).WriteLine('EXPORTER_SECRET {0} {1}', | |
[System.Convert]::ToHexString($cr), | |
[System.Convert]::ToHexString($secret)) | |
$res | |
} | |
New-PSDetourHook @ncrypt -MethodName SslGenerateSessionKeys -Action { | |
<# | |
.DESCRIPTION | |
Called for TLS 1.2 sessions. | |
Gets the master secret key for TLS 1.2 sessions. | |
#> | |
[OutputType([int])] | |
param ( | |
[IntPtr]$SslProvider, | |
[IntPtr]$MasterKey, | |
[IntPtr]$ReadKey, | |
[IntPtr]$WriteKey, | |
[IntPtr]$ParameterList, | |
[int]$Flags | |
) | |
<# | |
typedef struct _NCryptBufferDesc { | |
ULONG ulVersion; | |
ULONG cBuffers; | |
PNCryptBuffer pBuffers; | |
} NCryptBufferDesc, *PNCryptBufferDesc; | |
typedef struct _NCryptBuffer { | |
ULONG cbBuffer; | |
ULONG BufferType; | |
PVOID pvBuffer; | |
} NCryptBuffer, *PNCryptBuffer; | |
#> | |
$bufferCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($ParameterList, 4) | |
$bufferPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($ParameterList, 8) | |
for ($i = 0; $i -lt $bufferCount; $i++) { | |
$bufferSize = [System.Runtime.InteropServices.Marshal]::ReadInt32($bufferPtr) | |
$bufferType = [System.Runtime.InteropServices.Marshal]::ReadInt32($bufferPtr, 4) | |
$bufferValuePtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($bufferPtr, 8) | |
if ($bufferType -eq 20) { | |
# NCRYPTBUFFER_SSL_CLIENT_RANDOM | |
$cr = [byte[]]::new($bufferSize) | |
[System.Runtime.InteropServices.Marshal]::Copy($bufferValuePtr, $cr, 0, $cr.Length) | |
break | |
} | |
$bufferPtr = [IntPtr]::Add($bufferPtr, 8 + ([IntPtr]::Size)) | |
} | |
# The client_random should always be in the ParameterList but the | |
# fallback is still used. | |
if (-not $cr) { | |
$cr = ($this.State.ClientRandoms)[([System.Threading.Thread]::CurrentThread.ManagedThreadId)] | |
} | |
$ssl5StructPtr = [System.Runtime.InteropServices.Marshal]::ReadIntPtr($MasterKey, 0x10) | |
$secretPtr = [IntPtr]::Add($ssl5StructPtr, | |
([IntPtr]::Size -eq 8 ? 28 : 20)) | |
$secret = [byte[]]::new(48) | |
[System.Runtime.InteropServices.Marshal]::Copy($secretPtr, $secret, 0, $secret.Length) | |
($this.State.TlsWriter).WriteLine('CLIENT_RANDOM {0} {1}', | |
[System.Convert]::ToHexString($cr), | |
[System.Convert]::ToHexString($secret)) | |
$this.Invoke($SslProvider, $MasterKey, $ReadKey, $WriteKey, $ParameterList, $Flags) | |
} | |
) | |
Write-Host "Press any key to stop TLS logging..." | |
$null = $host.UI.RawUI.ReadKey() | |
Stop-PSDetour | |
$tlsWriter.Dispose() | |
$tlsLog.Dispose() | |
} | |
} | |
finally { | |
$session | Remove-PSSession | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks to https://b.poc.fun/decrypting-schannel-tls-part-1/ for providing all the hard work on what to hook and how.