TBRES.ps1
# This file contains functions to read and decrypt TBRES files # Parses TBRES files # Nov 18 2021 Function Parse-TBRES { [cmdletbinding()] param( [parameter(Mandatory=$true,ValueFromPipeline)] [byte[]]$Data ) Process { # Strip the null terminator, convert to string and parse json. $json = [text.encoding]::Unicode.GetString($Data,0,$Data.Length).TrimEnd(0x00) | ConvertFrom-Json # Get the encrypted content $txtEncrypted = $json.TBDataStoreObject.ObjectData.SystemDefinedProperties.ResponseBytes.Value # Convert B64 to byte array $binEncrypted = Convert-B64ToByteArray -B64 $txtEncrypted # If protected, decrypt with DPAPI if($json.TBDataStoreObject.ObjectData.SystemDefinedProperties.ResponseBytes.IsProtected) { $binDecrypted = [Security.Cryptography.ProtectedData]::Unprotect($binEncrypted,$null,'CurrentUser') } else { $binDecrypted = $binEncrypted } # Parse the expiration time $fileTimeUtc = [BitConverter]::ToUInt64((Convert-B64ToByteArray $json.TBDataStoreObject.ObjectData.SystemDefinedProperties.Expiration.Value),0) $expires = [datetime]::FromFileTimeUtc($fileTimeUtc) if((Get-Date).ToUniversalTime() -ge $expires) { Write-Warning "Token is expired" return } return Parse-TBRESResponseBytes -Data $binDecrypted } } # Parses ResponseBytes TBRES files # Nov 18 2021 Function Parse-TBRESResponseBytes { [cmdletbinding()] param( [parameter(Mandatory=$true,ValueFromPipeline)] [byte[]]$Data ) Begin { } Process { # Parses version number from TBRES response bytes # Nov 20 2021 Function Parse-TBRESVersion { [cmdletbinding()] param( [parameter(Mandatory=$true,ValueFromPipeline)] [byte[]]$Data, [parameter(Mandatory=$true,ValueFromPipeline)] [ref]$Position, [parameter(Mandatory=$false,ValueFromPipeline)] [int[]]$ExpectedVersions = @(1,2) ) Process { $p = $Position.Value $version = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 if($ExpectedVersions -notcontains $version) { Throw "Invalid version $version, expected one of $($ExpectedVersions -join ',')" } $Position.Value = $p } } # Parses key-value pairs from decrypted TBRES response bytes # Nov 20 2021 Function Parse-TBRESKeyValue { [cmdletbinding()] param( [parameter(Mandatory=$true,ValueFromPipeline)] [byte[]]$Data, [parameter(Mandatory=$true,ValueFromPipeline)] [ref]$Position ) Process { $p = $Position.Value $keyType = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 if($keyType -ne 0x0c) { Throw "Invalid key type $keyType" } $keyLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 $binKey = $Data[$p..($p + $keyLength -1)]; $p += $keyLength $key = [text.encoding]::UTF8.GetString($binKey) $valueType = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 switch($valueType) { 0x0C # String { $valueLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 $value = [text.encoding]::UTF8.GetString($Data,$p,$valueLength); $p += $valueLength break } 0x04 # UInt 32 { $value = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 break } 0x05 # UInt 32 { $value = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 break } 0x06 # Timestamp { $timestamp = [BitConverter]::ToUInt64($Data[($p + 7)..$p],0); $p += 8 $value = [datetime]::FromFileTimeUtc($timestamp) break } 0x07 # UInt 64 { $value = [BitConverter]::ToUInt64($Data[($p + 7)..$p],0); $p += 8 break } 0x0D # Guid { $value = [guid][byte[]]$Data[$p..($p + 15)]; $p += 16 break } 1025 # Content identifier? { # This is the second content "identifier" if($binKey.Length -eq 1 -and $binKey[0] -gt 1) { Write-Verbose "Content identifier $($binKey[0]), getting the next Key-Value." # Read the size $length = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 # Parse version Parse-TBRESVersion -Data $Data -Position ([ref]$p) # Get the next value $next = Parse-TBRESKeyValue -Data $Data -Position ([ref]$p) $key = $next.Key $value = $next.Value break } break } default { Write-Verbose "Unknown value type $valueType" $value = $valueType break } } $Position.Value = $p return [PSCustomObject][ordered]@{ "Key" = $key "Value" = $value } } } # Parses elements from decrypted TBRES response bytes content # Nov 20 2021 Function Parse-TBRESElement { [cmdletbinding()] param( [parameter(Mandatory=$true,ValueFromPipeline)] [byte[]]$Data, [parameter(Mandatory=$true,ValueFromPipeline)] [ref]$Position, [parameter(Mandatory=$false,ValueFromPipeline)] [PSCustomObject]$Element ) Process { $p = $Position.Value $value = $null # Parse element & length if(!$Element) { $element = Parse-TBRESKeyValue -Data $Data ([ref]$p) } Write-Debug $element $elementLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 if($element.Key -eq "WTRes_Error") { Write-Verbose "WTRes_Error file, skipping.." return $null } elseif($element.Key -eq "WTRes_Token") { Write-Verbose "Parsing WTRes_Token" # We already read the length so adjust $p -= 4 # Parse status $status = Parse-TBRESKeyValue -Data $Data ([ref]$p) if($status.Value -ne 0) { Write-Warning "WTRes_Token status $($status.Value)" } $value = $element.Value } # Parse WTRes_PropertyBag and WTRes_Account else { $propertyBagStart = $p Write-Verbose "Parsing $($element.Key), $elementLength bytes" # Parse version Parse-TBRESVersion -Data $Data -Position ([ref]$p) $properties = [ordered]@{} While($p -lt ( $propertyBagStart + $elementLength)) { $property = Parse-TBRESKeyValue -Data $Data ([ref]$p) if($property.Key -eq "WA_Properties" -or $property.Key -eq "WA_Provier") { $property.Value = Parse-TBRESElement -Data $Data ([ref]$p) -Element $property } $properties[$property.Key] = $property.Value } $value = [PSCustomObject]$properties } $Position.Value = $p return [PSCustomObject][ordered]@{ "Key" = $element.Key "Value" = $value } } } $p = 0 # Parse version Parse-TBRESVersion -Data $Data -Position ([ref]$p) # Parse expiration timestamp and responses guid $expiration = (Parse-TBRESKeyValue -Data $Data ([ref]$p)).value $responses = (Parse-TBRESKeyValue -Data $Data ([ref]$p)).value # Total response content length $responseLen = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 # Parse version Parse-TBRESVersion -Data $Data -Position ([ref]$p) # It seems that sometimes the content have multiple "entries" # These start with the following key-value pair: # First: Key = 0x01 and Value = 1025 # Second: Key = 0x01 and Value = 1025 # These are handled in Parse-TBRESKeyValue function $unk = Parse-TBRESKeyValue -Data $Data ([ref]$p) # # Content # # Content length $contentLength = [BitConverter]::ToUInt32($Data[($p + 3)..$p],0); $p += 4 $contentStart = $p # Parse version Parse-TBRESVersion -Data $Data -Position ([ref]$p) # Return value $properties = [ordered]@{} while($p -le ($contentStart + $contentLength)) { try { $element = Parse-TBRESElement -Data $Data -Position ([ref]$p) if($element -eq $null) { return $null } $properties[$element.Key] = $element.Value } catch { Write-Verbose "Got exception: $($_.Exception.Message)" break } } return [PSCustomObject]$properties } } |