Privates/FileHandlers.psm1
|
# FileHandlers.psm1 # supplementary function function Test-ExcelMacros { param ( [string]$FilePath ) $result = $false # Macros check $excel = New-Object -ComObject Excel.Application $excel.DisplayAlerts = $false $excel.Visible = $false try { $workbook = $excel.Workbooks.Open($FilePath, $false, $true) if ($workbook.HasVBProject) { $result = $true } } catch { $result = $_ } finally { if ($workbook) { $workbook.Close($false) } $excel.Quit() [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null [GC]::Collect() [GC]::WaitForPendingFinalizers() } return $result } function Check-SysvolAcl { param ( [string]$FilePath, [string[]]$TrustGroups, [string[]]$PermissionTrust ) $results = @() try { $acl = Get-Acl -Path $FilePath #-ErrorAction Stop } catch { #Write-Warning "Impossible de lire les ACL de : $fichier" return } $acl.Access | Where-Object { $_.IsInherited -eq $false -and $Trustgroups -notcontains $_.IdentityReference.Value -and $_.FileSystemRights -notin $PermissionTrust } | ForEach-Object { $msg = "$($_.IdentityReference.Value) has '$($_.FileSystemRights)'" $results += [pscustomobject]@{ FilePath = $FilePath Pattern = 'WrongACL' Reason = $msg } } return $results } #region FonctionDetect function Get-OthersContent { param ( [string]$filepath, [string[]]$patterns ) $results = @() try { $findmatch = Select-String -Path $filepath -Pattern $patterns -AllMatches foreach ($match in $findmatch) { $patternMatch = $match.pattern -replace '\\b','' [string]$word= $match.line.Replace($patternMatch,'') if (-not([string]::IsNullOrEmpty(($word -replace '^[\s=:"]+').Trim()))){ switch ($patternMatch) { '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' { $patternMatch = 'IPv4' } '[a-fA-F0-9]{32}' { $patternMatch = 'MD5' } '[a-fA-F0-9]{40}' { $patternMatch = 'SHA-1' } '[a-fA-F0-9]{64}' { $patternMatch = 'SHA-256' } '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}' { $patternMatch = 'UPN' } '/user:\S+\\\S+' { $patternMatch = 'Net Credentials' } default { switch -Wildcard ($patternMatch) { 'net *' { $patternMatch = 'Commande Net User' } default { $patternMatch = $patternMatch } } } } $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $patternMatch Reason = $match.Line } $results += $result } } } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } return $results } function Get-DefaultGPOContent { param ( [string]$filepath, [string[]]$patterns ) $PatternGPO = @( 'MinimumPasswordAge', 'MaximumPasswordAge', 'MinimumPasswordLength', 'PasswordComplexity', 'PasswordHistorySize', 'RequireLogonToChangePassword', 'ClearTextPassword', 'RefusePasswordChange', 'EnablePlainTextPassword', 'disablepasswordchange', 'maximumpasswordage', 'LimitBlankPasswordUse' ) $results = @() try { $findmatch = Select-String -Path $filepath -Pattern $patterns -AllMatches foreach ($match in $findmatch) { $patternMatch = $match.pattern -replace '\\b','' [string]$word= $match.line.Replace($patternMatch,'') $firstToken = (($match.Line -split '\\')[-1] -split '=' | Select-Object -First 1).trim() if ($PatternGPO -contains $firstToken) { $value = ($match.Line -split '=', 2)[1].Trim() if ($value) { $valueClean = $value -replace '[^\d-]', '' if (-not [int]::TryParse($valueClean, [ref]$null)) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = "WrongParameters" Reason = $match.Line } $results += $result } } } elseif (-not([string]::IsNullOrEmpty(($word -replace '^[\s=:"]+').Trim()))){ switch ($patternMatch) { '(?:[0-9]{1,3}\.){3}[0-9]{1,3}' { $patternMatch = 'IPv4' } '[a-fA-F0-9]{32}' { $patternMatch = 'MD5' } '[a-fA-F0-9]{40}' { $patternMatch = 'SHA-1' } '[a-fA-F0-9]{64}' { $patternMatch = 'SHA-256' } '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}' { $patternMatch = 'UPN' } default { switch -Wildcard ($patternMatch) { 'net *' { $patternMatch = 'Commande Net User' } default { $patternMatch = $patternMatch } } } } $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $patternMatch Reason = $match.Line } $results += $result } } } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } return $results } function Get-DocxContent { param ( [string]$filepath, [string[]]$patterns ) $results = @() Import-Module PSWriteOffice try { $document = Get-OfficeWord -FilePath $filepath -ReadOnly $n = 0 foreach ($pattern in $patterns) { $pattern = $pattern -replace '\\b', '' $findtext = $document.Find($pattern) | Select-Object text -First 2 if ($findtext) { foreach ($text in $findtext.text) { if ($n -le '1') { $n++ $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = $text } $results += $result } elseif ($n -gt '1') { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results += $result break } } } } Close-OfficeWord -Document $document } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_ } $results = $result } $results } function Get-XlsxContent { param ( [string]$filepath, [string[]]$patterns ) $results = @() Import-Module PSWriteOffice try { $excel = Get-OfficeExcel -FilePath $filepath $n = 0 foreach ($pattern in $patterns) { $pattern = $pattern -replace '\\b', '' [string]$findtext = $excel.Search($pattern, [System.Globalization.CompareOptions]::IgnoreCase, $false) | Select-Object value -First 2 if ($findtext) { if ($n -le '2') { foreach ($text in $findtext) { $n++ $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = $text } $results += $result } } else { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results += $result break } } } $excel.Dispose() } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_ } $results = $result } $results } function Get-DocContent { param ( [string]$filepath, [string[]]$patterns, [string]$wordinstalled ) $results = @() if ($wordinstalled -eq $false) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'requires_check' Reason = 'Word is not installed' } $results += $result } else { $wordApp = New-Object -ComObject Word.Application $wordApp.Visible = $false try { $document = $wordApp.Documents.Open($filepath, [ref]$null, [ref]$true) $n = 0 foreach ($pattern in $patterns) { $pattern = $pattern -replace '\\b', '' $find = $document.Content.Find | Select-Object -First 1 $find.Text = $pattern $find.Forward = $true $find.Wrap = 1 # wdFindContinue $find.Execute() | Out-Null if ($find.Found) { if ($n -le '2') { $n++ $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results = $result } else { break } } } $document.Close([ref]$false) [System.Runtime.Interopservices.Marshal]::ReleaseComObject($document) | Out-Null } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } finally { $wordApp.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($wordApp) | Out-Null [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } } $results } function Get-XlsContent { param ( [string]$filePath, [string[]]$patterns, [string]$excelInstalled ) $results = @() if ($excelInstalled -eq $false) { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'requires_check' Reason = 'Excel is not installed' } return $result } else { $value = Test-ExcelMacros -FilePath $filePath if ($value -eq $true ) { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = "Macros detected" Reason = "Files contenant macros, need manualy check" } return $result } $excelApp = New-Object -ComObject Excel.Application $excelApp.Visible = $false try { $workbook = $excelApp.Workbooks.Open($filePath, [ref]$null, [ref]$true) $n = 0 foreach ($pattern in $patterns) { $pattern = $pattern -replace '\\b', '' foreach ($sheet in $workbook.Sheets) { $cells = $sheet.Cells.Find($pattern) if ($null -ne $cells) { if ($n -le '2') { $n++ $result = [PSCustomObject]@{ FilePath = $filePath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results = $result } else { break } } } } $workbook.Close([ref]$false) [System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null } catch { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } finally { $excelApp.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($excelApp) | Out-Null [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } } $results } function Get-PPTContent { param ( [string]$filepath, [string[]]$patterns, [string]$wordinstalled ) $results = @() if ($wordinstalled -eq $false) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'requires_check' Reason = 'Office is not installed' } $results += $result } else { try { $MSPPT = New-Object -ComObject powerpoint.application $PRES = $MSPPT.Presentations.Open($filepath, $true, $true, $false) $n = 0 foreach($Slide in $PRES.Slides) { foreach ($Shape in $Slide.Shapes) { if ($Shape.HasTextFrame -eq "-1") { $text = $Shape.TextFrame.TextRange.Text foreach ($pattern in $patterns) { $pattern = $pattern -replace '\\b', '' if ($text -match $pattern) { if ($n -le '2') { $n++ $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results = $result } else { break } } } } } } $MSPPT.PresentationClose [System.Runtime.Interopservices.Marshal]::ReleaseComObject($MSPPT) | Out-Null } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } finally { $MSPPT.Quit() [System.Runtime.Interopservices.Marshal]::ReleaseComObject($MSPPT) | Out-Null [System.GC]::Collect() [System.GC]::WaitForPendingFinalizers() } } $results } function Get-OdsContent { param ( [string]$filePath, [string[]]$patterns ) $results = @() # Open ODS file Add-Type -AssemblyName System.IO.Compression.FileSystem $zip = [System.IO.Compression.ZipFile]::OpenRead($filePath) # Extract contenant $contentXmlEntry = $zip.Entries | Where-Object { $_.FullName -eq "content.xml" } $reader = [System.IO.StreamReader]::new($contentXmlEntry.Open()) $contentXml = $reader.ReadToEnd() $reader.Close() $zip.Dispose() # Analys file XML [xml]$xmlContent = $contentXml # Read text XML $textContent = $xmlContent.'document-content'.InnerText $n = 0 foreach ($pattern in $patterns) { $pattern = $pattern -replace '\\b', '' if ($textContent -match $pattern) { if ($n -le '2') { $n++ $result = [PSCustomObject]@{ FilePath = $filePath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results = $result } } } $results } function Get-Pdfcontentold { param ( [string]$filepath, [string[]]$patterns ) $results = @() Import-Module PSWritePDF try { $text = (Convert-PDFToText -FilePath $filepath) -join "`n" $n = 0 foreach ($pattern in $passwordPatterns) { if ($text -match $pattern) { if ($n -le '2') { $n++ $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = "at least " + $n + " characters found" } $results = $result } else { break } } } } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } $results } function Get-Pdfcontent { param ( [string]$filepath, [string[]]$patterns ) $results = @() Import-Module PSWritePDF try { $text = (Convert-PDFToText -FilePath $filepath) -join "`n" $n = 0 foreach ($pattern in $patterns) { $cleanPattern = $pattern -replace '\\b', '' if ($text -match $pattern) { if ($n -le '2') { $n++ $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $cleanPattern Reason = "at least " + $n + " characters found" } $results += $result } else { break } } } } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } $results } function Get-OthersxmlContent { param ( [string]$filepath, [string[]]$patterns, [int]$MaxContextLength = 40 # Number of characters to display after the word ) $results = @() $maxMatchesPerLine = 2 # Limit to 2 occurrences per pattern per line try { # Read the file line by line $lines = Get-Content -Path $filepath foreach ($line in $lines) { foreach ($pattern in $patterns) { # Find all matches for the current pattern (limited to 2) $valueMatches = [regex]::Matches($line, $pattern) | Select-Object -First $maxMatchesPerLine | ForEach-Object { $_.Value } # Identify the type of detected pattern $patternMatch = switch ($pattern) { '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' { 'IPv4' } '\b[a-fA-F0-9]{32}\b' { 'MD5' } '\b[a-fA-F0-9]{40}\b' { 'SHA-1' } '\b[a-fA-F0-9]{64}\b' { 'SHA-256' } '\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' { 'UPN' } '\b[Pp]assword\s*[:=]\s*\S+' { 'Password' } default { $pattern } } # Store each found match foreach ($valueMatch in $valueMatches) { if (-not [string]::IsNullOrEmpty($valueMatch)) { # Find the exact position of the word in the line $startIndex = $line.IndexOf($valueMatch) $endIndex = [Math]::Min($startIndex + $MaxContextLength, $line.Length) # Extract the found word + 40 characters after $reason = $line.Substring($startIndex, $endIndex - $startIndex).Trim() # Add the result $results += [PSCustomObject]@{ FilePath = $filepath Pattern = $patternMatch Reason = $reason # Found word + 40 characters after } } } } } } catch { $results += [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } } return $results } function Get-Xmlcontent { param ( [string]$filepath, [string[]]$patterns ) $results = @() $xmlContent = Get-Content -Path $filepath -Raw if ($xmlContent -match 'cpassword') { $xml = [xml]$xmlContent # Check for cpassword $cpasswords = @() if ($xml.Groups.User) { $cpasswords = $xml | Select-Xml "/Groups/User/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} } elseif ($xml.NTServices.NTService) { $cpasswords = $xml | Select-Xml "/NTServices/NTService/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} } elseif ($xml.ScheduledTasks.Task) { $cpasswords = $xml | Select-Xml "/ScheduledTasks/Task/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} } elseif ($xml.DataSources.DataSource) { $cpasswords = $xml | Select-Xml "/DataSources/DataSource/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} } elseif ($xml.Printers.SharedPrinter) { $cpasswords = $xml | Select-Xml "/Printers/SharedPrinter/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} } elseif ($xml.Drives.Drive) { $cpasswords = $xml | Select-Xml "/Drives/Drive/Properties/@cpassword" | Select-Object -Expand Node | ForEach-Object {$_.Value} } if ($cpasswords) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'cpassword' Reason = $cpasswords } $results += $result } } elseif ($xmlContent -match 'DefaultUserName') { $xml = [xml]$xmlContent # Check for AutoLogon $userName = "" $password = "" foreach ($registry in $xml.RegistrySettings.Registry) { if ($registry.Properties.name -eq "DefaultUserName") { $userName = $registry.Properties.value } if ($registry.Properties.name -eq "DefaultPassword") { $password = $registry.Properties.value } } $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'AutoLogon' Reason = "$userName : $password" } $results += $result } else { $results= Get-OthersxmlContent -filepath $filepath -patterns $patterns } $results } function Get-Xlsmcontent { param ( [string]$filepath ) $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Macros detected ' Reason = 'Files contenant macros, need check' } return $result } function Get-Executablescontent { param ( [string]$filepath ) $results = @() if ($filepath -notlike '*.jar') { $signature = Get-AuthenticodeSignature -FilePath $filepath if ($signature.Status -ne 'Valid') { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = "NotSigned" Reason = 'File is Not Signed' } $results = $result } } else { $isSigned = $false try { $zip = [System.IO.Compression.ZipFile]::OpenRead($filepath) foreach ($entry in $zip.Entries) { if ($entry.FullName -like "META-INF/*.SF") { $isSigned = $true break } } $zip.Dispose() } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'error' Reason = $_.Exception.Message } $results = $result } if (!$isSigned) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = "NotSigned" Reason = "Jar file not signed" } $results = $result } } $results } function Get-Zipprotectedbypass { param ( [string]$filepath, [string]$zipinstalled ) $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.') if ($extension -eq "zip" ) { try { $zip = [System.IO.Compression.ZipFile]::OpenRead($filepath) foreach ($entry in $zip.Entries) { $stream = $entry.Open() [byte[]]$buffer = New-Object byte[] 10 $stream.Read($buffer, 0, $buffer.Length) | Out-Null $stream.Close() break } $zip.Dispose() return $null } catch { if ($_.Exception.Message -match "(Read|Block|Password|Encrypted)") { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Zip protected' Reason = 'File protected by password' } return $result } else { Write-Host "Erreur inattendue : $_" $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Zip check error' Reason = 'Error to read zip : $_' } return $result } } } elseif ($zipinstalled) { $sevenZipPath = $zipinstalled + "7z.exe" $output = & "$sevenZipPath" t "$filepath" -pBadPasswordConf 2>&1 if ($output -match "Wrong password") { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Zip protected' Reason = 'File protected by password' } return $result } } } function Get-Requiredcheckcontent { param ( [string]$filepath ) $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'check required' Reason = 'Binary does not match' } return $result } function Get-CertifContent { param ( [string]$filePath ) $results = @() try { # Try to load certificat with class .NET X509Certificate2 $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $cert.Import($filePath) if (!$cert.Thumbprint) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Certificate Empty' Reason = 'Certificate without Thumbprint' } $results = $result } if ($cert.PrivateKey) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Certificate private key' Reason = 'certificate with exportable private key' } $results += $result } $cert.Dispose() } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Protected Certificate' Reason = ($_.Exception.Message).split(":").split("`n")[1].Trim() } $results = $result } return $results } function Get-P7bCertContent { param ( [string]$filePath ) $results = @() try { # Create certificate collection contenant P7B ou P7C $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($filePath) # browse all certificate present in P7B/P7C foreach ($cert in $certCollection) { if (!$cert.Thumbprint) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Certificate Empty' Reason = 'Certificate without Thumbprint' } $results += $result } if ($cert.PrivateKey) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Certificate private key' Reason = 'certificate with exportable private key' } $results += $result } $cert.Dispose() } } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Protected Certificate' Reason = $_.Exception.Message } $results = $result } return $results } function Get-HiddenFilesInImage { param ( [string]$filePath ) $fileInfo = Get-Item $filePath $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2) # Skip file more than 4 Mo if ($fileSizeMB -gt 4) { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Large size' Reason = "File ignored: (size: $fileSizeMB MB)" } return $result } try { # read binary and convert file to Hexa $fileBytes = [System.IO.File]::ReadAllBytes($filePath) $fileHex = [BitConverter]::ToString($fileBytes) -replace '-' $magicNumbers = [ordered]@{ "MSI" = "D0CF11E0A1B11AE1" # MSI or office "RAR" = "526172211A0700"; # RAR file "ZIP" = "504B0304"; # ZIP file "7z" = "377ABCAF271C"; # 7z file "mimikatz" = "6D696D696B61747A" "printf" = "7072696E7466" "invoke-" = "696E766F6B652D" ".Net.WebClient" = "2E4E65742E576562436C69656E74" "EXE" = "4D5A"; # EXE file (MZ header) } foreach ($key in $magicNumbers.Keys) { $magicNumber = $magicNumbers[$key] $currentIndex = 0 if ($fileHex -match $magicNumber) { if ($key -eq "EXE") { while ($fileHex.IndexOf($magicNumber, $currentIndex) -ne -1) { $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex) # Extract the part after the Magic Number (limited to 400 bytes after) $remainingHex = $fileHex.Substring($startIndex + $magicNumber.Length, [Math]::Min(200 * 2, $fileHex.Length - ($startIndex + $magicNumber.Length))) # Check for the presence of the special string "0000004000" in this range if ($remainingHex -match "0000004000") { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Suspicious Image' Reason = "EXE file with '0000004000' string detected" } return $result } # Continue the search from the next index $currentIndex = $startIndex + $magicNumber.Length } } elseif ($key -eq "ZIP") { # Find the index where the ZIP file starts $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex) / 2 # Divide by 2 because each hexadecimal represents 2 characters # Extract the ZIP bytes starting from this index $zipBytes = $fileBytes[$startIndex..($fileBytes.Length - 1)] $tempDir = [System.IO.Path]::GetTempPath() $fileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($filePath) $tempZipPath = [System.IO.Path]::Combine($tempDir, "$fileNameWithoutExtension.zip") [System.IO.File]::WriteAllBytes($tempZipPath, $zipBytes) # Read the ZIP file and list the files Add-Type -AssemblyName System.IO.Compression.FileSystem $zip = [System.IO.Compression.ZipFile]::OpenRead($tempZipPath) # List the files inside the ZIP $fileNames = $zip.Entries | Select-Object -ExpandProperty FullName $fileNames | ForEach-Object { Write-Host "File in ZIP: $_" } $zip.Dispose() # Delete the temporary file Remove-Item $tempZipPath # Return the result $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "ZIP detected in pictures. Containing: $($fileNames -join ', ')" } return $result } else { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Suspicious Image' Reason = "File $key detected in the image" } return $result } } } } catch { $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Error' Reason = "Details : $_" } return $result } } function Get-HiddenFilesSpecificInImage { param ( [string]$filePath ) $fileInfo = Get-Item $filePath $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2) # Skip file more than 4 Mo if ($fileSizeMB -gt 4) { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Large size' Reason = "File ignored: (size: $fileSizeMB MB)" } return $result } function Extractzip-fromfile { # Extract ZIP and list files inside it $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex) / 2 # Divide by 2 for hex chars $zipBytes = $fileBytes[$startIndex..($fileBytes.Length - 1)] $tempDir = [System.IO.Path]::GetTempPath() $fileNameWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($filePath) $tempZipPath = [System.IO.Path]::Combine($tempDir, "$fileNameWithoutExtension.zip") [System.IO.File]::WriteAllBytes($tempZipPath, $zipBytes) # Read the ZIP file and list the files inside it Add-Type -AssemblyName System.IO.Compression.FileSystem $zip = [System.IO.Compression.ZipFile]::OpenRead($tempZipPath) $fileNames = $zip.Entries | Select-Object -ExpandProperty FullName $fileNames | ForEach-Object { Write-Host "File in ZIP: $_" } $zip.Dispose() Remove-Item $tempZipPath $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "ZIP detected in pictures. Containing: $($fileNames -join ', ')" } return $result } try { # Read binary and convert file to Hex $fileBytes = [System.IO.File]::ReadAllBytes($filePath) $fileHex = [BitConverter]::ToString($fileBytes) -replace '-' $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.').ToLower() # Magic numbers for different file types $magicNumbers = [ordered]@{ "MSI" = "D0CF11E0A1B11AE1" "RAR" = "526172211A0700" "ZIP" = "504B0304" "7z" = "377ABCAF271C" "EXE" = "4D5A" } switch ($extension) { {$_ -in "jpg","jpeg"} { $endoffile = "FFD9"} 'png' { $endoffile = "0000000049454E44AE426082"} 'gif' { $endoffile = "3B"} } foreach ($key in $magicNumbers.Keys) { $magicNumber = $magicNumbers[$key] $currentIndex = 0 $truecheck = $endoffile + $magicNumber if (($fileHex -match $truecheck) -and ($fileHex.Substring($fileHex.Length -$endoffile.Length, $endoffile.Length) -ne $endoffile)) { if ($key -eq 'zip') { return Extractzip-fromfile } else { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "$key file found in image with unexpected binary ending" } return $result } } } foreach ($key in $magicNumbers.Keys) { $magicNumber = $magicNumbers[$key] $currentIndex = 0 if ($fileHex -match $magicNumber) { if ($key -eq "EXE") { while ($fileHex.IndexOf($magicNumber, $currentIndex) -ne -1) { $startIndex = $fileHex.IndexOf($magicNumber, $currentIndex) # Extract the part after the Magic Number (up to 200 bytes) $remainingHex = $fileHex.Substring($startIndex + $magicNumber.Length, [Math]::Min(200 * 2, $fileHex.Length - ($startIndex + $magicNumber.Length))) # Check if the string "0000004000" appears in the remaining data if ($remainingHex -match "0000004000") { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "EXE file with '0000004000' string detected" } return $result } # Continue searching from the next index $currentIndex = $startIndex + $magicNumber.Length } } elseif ($key -eq "ZIP") { return Extractzip-fromfile } else { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "File $key detected in the image" } return $result } } } if ($fileHex.Substring($fileHex.Length -$endoffile.Length, $endoffile.Length) -ne $endoffile) { $Keywords = [ordered]@{ "EXE file" = "4558452066696C65" "printf" = "7072696E7466" "fopen" = "666F70656E" "malloc" = "6D616C6C6F63" "strcpy" = "737472637079" "system" = "73797374656D" "socket" = "736F636B6574" "class" = "636C617373" } # Search other motifs in the last 2000 characters $searchHex = $fileHex.Substring([Math]::Max(0, $fileHex.Length - 2000)) foreach ($word in $Keywords.Keys) { if ($searchHex -match $Keywords[$word]) { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "Image probably modified, found argument : $word" } return $result } } $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Suspicious Image' Reason = "Image probably modified, not correctyl end binary match" } return $result } } catch { $result = [PSCustomObject]@{ FilePath = $filePath Pattern = 'Error' Reason = "Details: $_" } return $result } } function Get-checkfilesize { param ( [string]$filePath ) $fileInfo = Get-Item $filePath $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2) $result = [PSCustomObject]@{ FilePath = $filepath Pattern = 'Large size' Reason = "Size is so much, file ignored: (size: $fileSizeMB MB)" } return $result } #endregion FonctionDetect # Add other functions for different file types... function Get-CompressedFileType { param ( [string]$filePath, [object[]]$detectedType ) # More check fot DOCX, XLSX, ODT, ODS ou JAR PPTX zip if ($detectedType -contains "docx" -or $detectedType -contains "jar") { try { Add-Type -AssemblyName System.IO.Compression.FileSystem $zip = [System.IO.Compression.ZipFile]::OpenRead($filePath) $fileNames = $zip.Entries | Select-Object -ExpandProperty FullName if ($fileNames -contains "word/document.xml") { $detectedType = "docx" } elseif ($fileNames -contains "xl/vbaProject.bin") { $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.') if ($extension -in "xlsm","xlam") { $detectedType = $extension } } elseif ($fileNames -contains "ppt/presentation.xml") { $detectedType = "pptx" } elseif ($fileNames -contains "visio/document.xml") { $detectedType = "vsdx" } elseif ($fileNames -contains "xl/workbook.xml") { $detectedType = "xlsx" } elseif ($fileNames -contains "content.xml") { $mimetypeEntry = $zip.Entries | Where-Object { $_.FullName -eq "mimetype" } if ($mimetypeEntry -ne $null) { $reader = [System.IO.StreamReader]::new($mimetypeEntry.Open()) $mimetype = $reader.ReadToEnd() $reader.Close() switch ($mimetype) { "application/vnd.oasis.opendocument.spreadsheet" { $detectedType = "ods" } "application/vnd.oasis.opendocument.text" { $detectedType = "odt" } "application/vnd.oasis.opendocument.presentation" { $detectedType = "odp" } "application/vnd.oasis.opendocument.text-template" { $detectedType = "ott" } } } } elseif ($fileNames -contains "META-INF/MANIFEST.MF") { $detectedType = "jar" } else { $detectedType = "others" } $zip.Dispose() } catch { $detectedType = "requires_check" } } elseif ($detectedType -contains "doc") { $a = [System.IO.File]::ReadAllBytes($filePath) $content = [System.Text.Encoding]::ASCII.GetString($a) if ($content.Contains("Word.Document")) { $detectedType = "doc" } elseif ($content.Contains("MSI") -or $content.Contains("Installer")) { $detectedType = "msi" } elseif ($content.Contains("Excel") ) { $detectedType = "xls" } elseif ($content.Contains("PowerPoint")) { $detectedType = "ppt" } elseif ($content.Contains("Microsoft Visio")) { $detectedType = "vsd" } else { #check for db files $offsetBytes = [System.IO.File]::ReadAllBytes($filePath)[1024..1050] $offsetAscii = [System.Text.Encoding]::ASCII.GetString($offsetBytes).Trim() $filteredAscii = ($offsetAscii -split '').Where{ $_ -match '[A-Za-z ]' } -join '' if ($filteredAscii -replace '\s+', ' ' -eq "Root Entry") { $detectedType = "db" } else { $detectedType = "others" } } } return $detectedType } function Get-FileType { param ( [string]$filePath, [int]$Maxfilesize, [int]$MaxBinarysize, [object]$jsonContent ) $detectedType = "others" $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.') $fileHeaderHex = [System.IO.File]::ReadAllBytes($filePath)[0..20] | ForEach-Object { "{0:X2}" -f $_ } $fileHeaderHex = ($fileHeaderHex -join '').Trim() if ($fileHeaderHex.Length -eq 0) { return "empty" } $matchFound = $false foreach ($entry in $jsonContent.magic_numbers) { if ($matchFound) { break } foreach ($expectedMagic in $entry.magic) { if ($matchFound) { break } if ($expectedMagic.Length -le $fileHeaderHex.Length) { $difference = Compare-Object -ReferenceObject $expectedMagic -DifferenceObject ($fileHeaderHex.Substring(0, $expectedMagic.Length)) -SyncWindow 0 if ($difference.Count -eq 0) { if ($entry.offset) { foreach ($offsetItem in $entry.offset) { $offsetPosition = $expectedOffsetValue = $null $offsetPosition = $offsetItem[0] $expectedOffsetValue = $offsetItem[1] $lastpostition = $offsetPosition + ($expectedOffsetValue.Length/2)-1 $CustomfileHeaderHex = [System.IO.File]::ReadAllBytes($filePath)[$offsetPosition..$lastpostition] | ForEach-Object { "{0:X2}" -f $_ } $specificBytes = ($CustomfileHeaderHex -join '').Trim() if ($specificBytes -eq $expectedOffsetValue) { if ($extension -notin $entry.extensions) { $detectedType = 'requires_check' } else { $detectedType = $extension } $matchFound = $true break } } } else { if ($extension -notin $entry.extensions) { $detectedType = 'requires_check' } else { if ($entry.extensions -contains "docx" -or $entry.extensions -contains "doc") { $detectedType = Get-CompressedFileType -filePath $filePath -detectedType $entry.extensions } if ($detectedType -eq "others" -and $extension -in $entry.extensions) { $detectedType = $extension } elseif ($detectedType -ne $extension -and $detectedType -ne "others") { $detectedType = 'requires_check' } } $matchFound = $true break } } } } } if ($detectedType -eq 'others') { if ($extension -in $jsonContent.magic_numbers.extensions) { $detectedType = 'requires_check' } elseif ($filePath -like '*\MACHINE\Microsoft\Windows NT\SecEdit\GptTmpl.inf') { $detectedType = "DefaultGPO" } else { $firstLine = Get-Content -Path $filePath -TotalCount 1 if ($firstLine -match '^<\?xml') { $detectedType = "xml" } } } # Check the file size to avoid slowing down analyze files larger than 10 MB, along with installation file extensions over 50 MB if ($detectedType -ne 'requires_check' ) { $fileInfo = Get-Item $filePath $fileSizeMB = [math]::Round($fileInfo.Length / 1MB, 2) switch ($extension) { {$_ -in "exe","msi","dll","msu","cab","zip","rar"} { if ($fileSizeMB -gt $MaxBinarysize) { return "bigsize" } } default { if ($fileSizeMB -gt $Maxfilesize) { return "bigsize" } } } } return $detectedType } #region Codehtml #endregion Codehtml # SIG # Begin signature block # MIItjQYJKoZIhvcNAQcCoIItfjCCLXoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDd94ffdy/FQYKj # 7Qeh8B2PWlsPzjVSw+UxqNyi53NM66CCEtUwggXJMIIEsaADAgECAhAbtY8lKt8j # AEkoya49fu0nMA0GCSqGSIb3DQEBDAUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK # ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy # dGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5l # dHdvcmsgQ0EwHhcNMjEwNTMxMDY0MzA2WhcNMjkwOTE3MDY0MzA2WjCBgDELMAkG # A1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAl # BgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb # Q2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEAvfl4+ObVgAxknYYblmRnPyI6HnUBfe/7XGeMycxca6mR5rlC # 5SBLm9qbe7mZXdmbgEvXhEArJ9PoujC7Pgkap0mV7ytAJMKXx6fumyXvqAoAl4Va # qp3cKcniNQfrcE1K1sGzVrihQTib0fsxf4/gX+GxPw+OFklg1waNGPmqJhCrKtPQ # 0WeNG0a+RzDVLnLRxWPa52N5RH5LYySJhi40PylMUosqp8DikSiJucBb+R3Z5yet # /5oCl8HGUJKbAiy9qbk0WQq/hEr/3/6zn+vZnuCYI+yma3cWKtvMrTscpIfcRnNe # GWJoRVfkkIJCu0LW8GHgwaM9ZqNd9BjuiMmNF0UpmTJ1AjHuKSbIawLmtWJFfzcV # WiNoidQ+3k4nsPBADLxNF8tNorMe0AZa3faTz1d1mfX6hhpneLO/lv403L3nUlbl # s+V1e9dBkQXcXWnjlQ1DufyDljmVe2yAWk8TcsbXfSl6RLpSpCrVQUYJIP4ioLZb # MI28iQzV13D4h1L92u+sUS4Hs07+0AnacO+Y+lbmbdu1V0vc5SwlFcieLnhO+Nqc # noYsylfzGuXIkosagpZ6w7xQEmnYDlpGizrrJvojybawgb5CAKT41v4wLsfSRvbl # jnX98sy50IdbzAYQYLuDNbdeZ95H7JlI8aShFf6tjGKOOVVPORa5sWOd/7cCAwEA # AaOCAT4wggE6MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLahVDkCw6A/joq8 # +tT4HKbROg79MB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1Ud # DwEB/wQEAwIBBjAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vY3JsLmNlcnR1bS5w # bC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgGCCsGAQUFBzABhhxodHRwOi8v # c3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUFBzAChiVodHRwOi8vcmVwb3Np # dG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMDkGA1UdIAQyMDAwLgYEVR0gADAmMCQG # CCsGAQUFBwIBFhhodHRwOi8vd3d3LmNlcnR1bS5wbC9DUFMwDQYJKoZIhvcNAQEM # BQADggEBAFHCoVgWIhCL/IYx1MIy01z4S6Ivaj5N+KsIHu3V6PrnCA3st8YeDrJ1 # BXqxC/rXdGoABh+kzqrya33YEcARCNQOTWHFOqj6seHjmOriY/1B9ZN9DbxdkjuR # mmW60F9MvkyNaAMQFtXx0ASKhTP5N+dbLiZpQjy6zbzUeulNndrnQ/tjUoCFBMQl # lVXwfqefAcVbKPjgzoZwpic7Ofs4LphTZSJ1Ldf23SIikZbr3WjtP6MZl9M7JYjs # NhI9qX7OAo0FmpKnJ25FspxihjcNpDOO16hO0EoXQ0zF8ads0h5YbBRRfopUofbv # n3l6XYGaFpAP4bvxSgD5+d2+7arszgowggZHMIIEL6ADAgECAhA12OBytW+cTayv # VHUpRhwLMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhB # c3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNp # Z25pbmcgMjAyMSBDQTAeFw0yNTExMTYxMTAwMTlaFw0yNjExMTYxMTAwMThaMG0x # CzAJBgNVBAYTAkZSMQ8wDQYDVQQHDAZUb3Vsb24xHjAcBgNVBAoMFU9wZW4gU291 # cmNlIERldmVsb3BlcjEtMCsGA1UEAwwkT3BlbiBTb3VyY2UgRGV2ZWxvcGVyLCBE # QUtIQU1BIE1FSERJMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAp6Ku # m/VmkWCqAaF/3zHh9f1FuJYY2ozbXOu7mo1/Q8i1c0fE0TXpkZXLY2GZbfpj9BmH # AAFM0IhOsPR2vdxq3jOUJUb9TICneFor6YaPpySsXR3WSE7X42kgpkkmPELovm1Y # hwSzhJ4a+E+NWL/MU8h5JpmGVlqPJ02/ZTlMj5kcpIQtq8hoQMcUEDkGFt9IcamE # 1yN4IHkBA5nm4jJPaos0IuS77t805992JSGWhxBxWARH+2vyltv8Rmq1pZV1lE6n # JgrWT7Ichjw2X/A+OP68ooTzQwCIpzXb4UuUcwHEfrmP3HGMQJoj//SNC4QPMao+ # 3Z8zbevl73E3d6Kfvra1S+pWM2Ze5YCsIqAd98GUHgi5E6GiG8FQq/+d6msL7l8B # UASCqXlcAKIjRNMHp8BrUaaW6HS9Kpc+3O3t/LUmK6X3FFiW8QsWoh4K+7YSpopa # CQbNXmEI4xftctwBOJrEU2oqRnYiwchfjqBNlrGwVGPK1rmM0iTt5KiLTus7AgMB # AAGjggF4MIIBdDAMBgNVHRMBAf8EAjAAMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6 # Ly9jY3NjYTIwMjEuY3JsLmNlcnR1bS5wbC9jY3NjYTIwMjEuY3JsMHMGCCsGAQUF # BwEBBGcwZTAsBggrBgEFBQcwAYYgaHR0cDovL2Njc2NhMjAyMS5vY3NwLWNlcnR1 # bS5jb20wNQYIKwYBBQUHMAKGKWh0dHA6Ly9yZXBvc2l0b3J5LmNlcnR1bS5wbC9j # Y3NjYTIwMjEuY2VyMB8GA1UdIwQYMBaAFN10XUwA23ufoHTKsW73PMAywHDNMB0G # A1UdDgQWBBSXTmfHi9BD9GDRwk5/doNtKHBXYzBLBgNVHSAERDBCMAgGBmeBDAEE # ATA2BgsqhGgBhvZ3AgUBBDAnMCUGCCsGAQUFBwIBFhlodHRwczovL3d3dy5jZXJ0 # dW0ucGwvQ1BTMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA4GA1UdDwEB/wQEAwIHgDAN # BgkqhkiG9w0BAQsFAAOCAgEAe+khGqwUUkFYuFRsrvenX2/a+PIt2Tu9d3VoW6Or # MX3YLpe7S2CgFkXwEi2Siq5KiD1labP9jsh/3G1ZQwwlnPv8dB7ocl/nOrQ9OZex # GVE1r7IO6VYVa5F7XuJ/KadKLEbQSs1BpBVhESo1ZYr6w9NCLuO9q2Sh3H5MktET # D6sB+g1TFOYMdwYl8eAawgI2kGPe3dRQSoumP0mHkm3x5SIwRCW+08md5uyzCIui # 85WmcNPtM1QCqjkSpfdFGYPsnf/BO9NATpZkqFxhXwa9+PqseX+mofCIL49guCXG # kU4RpeRHcUie14oYkxvBw7VUO4MT6wYbS2C3j2nyoAV4XqqNMfrhZIBJG5haj2RB # V46bMJ+DsW6hxlm3lIlCaJT2pLbbk79OP+Bk0HIdC9mAbKzcqaZpBpn4+ljrcx7/ # X7OHv4XTCCDWwlZbaogy4Wci6TiSjjfpfXK5N/eJTEEh2w4qoYTTrR61ptkVnTUT # vGRfPnVtS/3aOm2v4UahtOc/ygcL0A/J85r1e6CEeOaTm9eJbHoNdwNIYaZ81VlX # /V/MoJgFCtioYOKiTf2Rdq7XrEEHLU2YGwCqJyKYz9tz10yXBcMW6/+gX+PGqAYz # eKg5jbKLdi9lVrKspQUXAPHdcl6VJMXy799J0lbsQeJNgBVy6HWxOWvdLBGX3hPE # 3aYwgga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6EprPqOTANBgkqhkiG9w0BAQwF # ADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVz # IFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEk # MCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMB4XDTIxMDUxOTA1 # MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFz # c2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2ln # bmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnSPP # BDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4mSAfgLKTvggv24/rWiVGzGxT # 9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck6+hI1shsylP4JyLvmxwLHtSw # orV9wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xqbNTIPsnWtw3E7DmDoUmDQiYi # /ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KNqfcYf7Z4/iZgkBJ+UFNDcc6z # okZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSlbfsq7BiqljjaCun+RJgTgFRC # tsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYqVWQ4x6cM7/G0yswg1ElLlJj6 # NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAExzd4prHwYjUXTeZIlVXqj+ea # YqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962WZayx7ACFf5ydJpoEowSP07Y # aBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiYPIAHlezf4muJqxqIns/kqld6 # JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36E/ENVv8urPS0yOnpG4tIoBGx # VCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPECAwEAAaOCAVUwggFRMA8GA1Ud # EwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23ufoHTKsW73PMAywHDNMB8GA1Ud # IwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjATBgNV # HSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmNl # cnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEBBGAwXjAoBggrBgEFBQcwAYYc # aHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAyBggrBgEFBQcwAoYmaHR0cDov # L3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5jZXIwOQYDVR0gBDIwMDAuBgRV # HSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkq # hkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP89DttyZqGYn2XRDhJkL6P+/T0 # IPZyxfxiXumYlARMgwRzLRUStJl490L94C9LGF3vjzzH8Jq3iR74BRlkO18J3zId # mCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO0eqeLNhLVsgw6/zOfImNlARK # n1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2mbJCKKtl2s42g9KUJHEIiLni # 9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA3ahe8WlxVWkxyrQLjH8ItI17 # RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhpn8zJ+6MTyE6YoEBSRVrs0zFF # IHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSouCReZwSLo8WdrDlPXtR0gicD # nytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na3PR7nxYVlPu1JPoRZCbH86gc # 96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1eEJfo2qyrBNPvF7wuAyQfiFX # LwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+tKyEVAotnyU9vyEVOaIYMk3I # eBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmvO1nm+/80nLy5r0AZvCQxaQ4x # ghoOMIIaCgIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0 # YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNpZ25pbmcgMjAy # MSBDQQIQNdjgcrVvnE2sr1R1KUYcCzANBglghkgBZQMEAgEFAKB8MBAGCisGAQQB # gjcCAQwxAjAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC # AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCAqMWJLQUAe3hjOOSd/ # k+fWEFejJDxY5rI9dmQoOm368TANBgkqhkiG9w0BAQEFAASCAYBOC2jqi44xnSyj # 2931W9Rwsg+W8UM0qlxvNZx2Bmbsr/GEWzLsQcDqvi/0qBtY9PftTS1KoiYPhd74 # JnmSwgeGInKUaqmOPEdOidCjY/g0fKqRrswUI/zKc5UVxEvqle6iTH9ahJ8CT9U4 # YkdnCuOvqLim9cJFRd/qP+ULHyRenjOjzOftV9jHuH4wQkdJeyIjhX//qxVmXz/k # wL/zmrngpEwgKr2PoNW0uZZOXsHyLTck2NwF60Hb2853LvxI2YEk4zqgIL+/JGeG # Wc3JnSH3G9luyqCOU3nBr34iX2B4SLDV3nXdxEpj73HpKzRNFR/ywOeRZDtHC+d2 # k9/zrDTecab1NNBqMTAlOLaKnbrhzyeWwpMb1VF5E6UIi9s6iLfxjRN6pOx9yOwY # 2X27WNy0ULLmJwtc8cbAwwEa2RBUA+XWjiFaqRolauU2Cxy9CNdmP5CwhpfAAatI # E2PLYDAQ3yr6a9zL4IEgc5smiW/v/FYinL0bmNeCRDEx4kFiIuGhghd3MIIXcwYK # KwYBBAGCNwMDATGCF2MwghdfBgkqhkiG9w0BBwKgghdQMIIXTAIBAzEPMA0GCWCG # SAFlAwQCAQUAMHgGCyqGSIb3DQEJEAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTAN # BglghkgBZQMEAgEFAAQgcZYAUQOPtpqppbNp1s5oHXJtbXvKP8aVhsHJ5z79rSkC # EQC2hOsWTPN9eIOLj2+ED/M0GA8yMDI2MTAxNjExMDAyOFqgghM6MIIG7TCCBNWg # AwIBAgIQCE/cM09+RU7bww+P+ZIYNTANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0 # IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex # MB4XDTI2MDgwNTAwMDAwMFoXDTM3MTEwNDIzNTk1OVowYzELMAkGA1UEBhMCVVMx # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy # NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjYgMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBALZ7pvLJ/s1K+NSbTGWz/TjGMPh8CQ6RucZC # Lv5anHzWJjF/NWJrFIhy24fcpKXlgRiky4WAawDfU3YP0BMxt9l3Dm5oCG5Z69Aq # EN1kgHg2epx+l+lZBcmJCcN0ASURML5uFIS80sZsDwO3BSkUxDjLJhBI+qiZP3ai # xAC/qEGLjsBNlLol9VZ7pfGEXiMlneJIC5/YKuizVzNFKZZEeoy/0B8Zm+nzKBgS # WG52lCO1w+nCg6XpCtklTJXeIg283hw7TmmsZXR+SMbjbrEOvZ3fP2VxIgeR28Y9 # 0ZStd3F9VuA5RVynb/whITPAo9b75Zr4Ta6Mj3URm26QZYMn/FnbuTegcoRcFEZ9 # FOqM5T6MTdtr/n74lIT/ug0eeOzmZ6QTFg33otX+bFRsIolvykE1jive4PuESaT8 # zzVeFWDAMDtozNgLctkGD1ZjkEyZtJrLl5ya0m5doH/ScpaZCZVl6pNUOCybMc/k # xC6EAmSJY24L0yYKD1Nkddsnb/ItVKi/2nXpQNMu1PT5prW83vV8d67WowuUs0Hd # Y4H8AMLGvdL/WHEj3ZnqMqAQQP9u3Ai9t+5eQ02GDwy0ODjdzi0xlp70W+ow63/0 # ++YDEX1M0iwgUHwbrJvfpklkZQvw3+kv3vUPItdwroczk9icflf55W1zOEKAcJVA # IXpcMCU9AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQUyWOK # MC7USvtulPPm40B+9ezN4jAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL # TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG # AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQCNxTphHp1SCt+ZrAmAfn0oQLFr0mLywSLaDXQIENoyKqxrFbJblzCV # P/pkXmwXOdrOpWygLzlT12os5ipDCy35RBCg2UMeApEtrfGhz45F4Wt4WGdNdIbR # Wt3YTYJmpR+b7lr4d7Uwn+H600u4D7RnOGf8Wj4UNgAdZkfHhHv1mx9EVh71SJel # cEN/oORSjXzdjfw1iZH9d8Nh/thn6hH23d+VsPAr6GAYyzSA02nXD1nYLI7Ijmiv # +xLCiYC41DSFYL3GhTiy0PxpawPtGRyaBVGzq+UiTfM8pD7KVyF5aQyWP4KhVGUU # Tnmm/RlYJoW3TiXA/+t0YcT2oRVBm3JETjajHug2AL+v5jhtKVnd3D0rbHXEu27o # +Q8p4sEWPMqKDB+qbceb6T/6WcwTwXmQ9lOCLLYcsQeSWmvKqzpAec9etE14jOQA # zLKWdE3w/TCaKtLRaRT7LCkRYVnhA2D73FLje1O5b3HR5eHs0NzU/+xX7NbEdcof # y0W3Wdwd1XOqtlpg/JgwtKfZM5dqO94lbUveOiJBI+xZEbGRsMNbXmMREUTgu+Oc # a7Y73MPWcslIx2VhkSKSXjDbD6rgg39H5Mh7QfieAIjWagkJNt68Yfim6cjEzVSi # LSeZfdkr5dtFPTW6jATlWJdYeeDRGCyatf8R1hSjzSvdN8yWQPT9gzCCBrQwggSc # oAMCAQICEA3HrFcF/yGZLkBDIgw6SYYwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTI1 # MDUwNzAwMDAwMFoXDTM4MDExNDIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNV # BAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0 # IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBALR4MdMKmEFyvjxGwBysddujRmh0tFEXnU2t # jQ2UtZmWgyxU7UNqEY81FzJsQqr5G7A6c+Gh/qm8Xi4aPCOo2N8S9SLrC6Kbltqn # 7SWCWgzbNfiR+2fkHUiljNOqnIVD/gG3SYDEAd4dg2dDGpeZGKe+42DFUF0mR/vt # La4+gKPsYfwEu7EEbkC9+0F2w4QJLVSTEG8yAR2CQWIM1iI5PHg62IVwxKSpO0Xa # F9DPfNBKS7Zazch8NF5vp7eaZ2CVNxpqumzTCNSOxm+SAWSuIr21Qomb+zzQWKhx # KTVVgtmUPAW35xUUFREmDrMxSNlr/NsJyUXzdtFUUt4aS4CEeIY8y9IaaGBpPNXK # FifinT7zL2gdFpBP9qh8SdLnEut/GcalNeJQ55IuwnKCgs+nrpuQNfVmUB5KlCX3 # ZA4x5HHKS+rqBvKWxdCyQEEGcbLe1b8Aw4wJkhU1JrPsFfxW1gaou30yZ46t4Y9F # 20HHfIY4/6vHespYMQmUiote8ladjS/nJ0+k6MvqzfpzPDOy5y6gqztiT96Fv/9b # H7mQyogxG9QEPHrPV6/7umw052AkyiLA6tQbZl1KhBtTasySkuJDpsZGKdlsjg4u # 70EwgWbVRSX1Wd4+zoFpp4Ra+MlKM2baoD6x0VR4RjSpWM8o5a6D8bpfm4CLKczs # G7ZrIGNTAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW # BBTvb1NK6eQGfHrK4pBW9i/USezLTjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/ # 57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYI # KwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9j # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1Ud # IAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEA # F877FoAc/gc9EXZxML2+C8i1NKZ/zdCHxYgaMH9Pw5tcBnPw6O6FTGNpoV2V4wzS # UGvI9NAzaoQk97frPBtIj+ZLzdp+yXdhOP4hCFATuNT+ReOPK0mCefSG+tXqGpYZ # 3essBS3q8nL2UwM+NMvEuBd/2vmdYxDCvwzJv2sRUoKEfJ+nN57mQfQXwcAEGCvR # R2qKtntujB71WPYAgwPyWLKu6RnaID/B0ba2H3LUiwDRAXx1Neq9ydOal95CHfmT # nM4I+ZI2rVQfjXQA1WSjjf4J2a7jLzWGNqNX+DF0SQzHU0pTi4dBwp9nEC8EAqox # W6q17r0z0noDjs6+BFo+z7bKSBwZXTRNivYuve3L2oiKNqetRHdqfMTCW/NmKLJ9 # M+MtucVGyOxiDf06VXxyKkOirv6o02OoXN4bFzK0vlNMsvhlqgF2puE6FndlENSm # E+9JGYxOGLS/D284NHNboDGcmWXfwXRy4kbu4QFhOm0xJuF2EZAOk5eCkhSxZON3 # rGlHqhpB/8MluDezooIs8CVnrpHMiD2wL40mm53+/j7tFaxYKIqL0Q4ssd8xHZnI # n/7GELH3IdvG2XlM9q7WP/UwgOkw/HQtyRN62JK4S1C8uw3PdBunvAZapsiI5YKd # vlarEvf8EA+8hcpSM9LHJmyrxaFtoza2zNaQ9k+5t1wwggWNMIIEdaADAgECAhAO # mxiO+dAt5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUw # EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x # JDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEw # MDAwMDBaFw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT # GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprN # rnsbhA3EMB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVy # r2iTcMKyunWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4 # IWGbNOsFxl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13j # rclPXuU15zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4Q # kXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQn # vKFPObURWBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu # 5tTvkpI6nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/ # 8tWMcCxBYKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQp # JYls5Q5SUUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFf # xCBRa2+xq4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGj # ggE6MIIBNjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/ # 57qYrhwPTzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8B # Af8EBAMCAYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6 # oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElE # Um9vdENBLmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEB # AHCgv0NcVec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0a # FPQTSnovLbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNE # m0Mh65ZyoUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZq # aVSwuKFWjuyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCs # WKAOQGPFmCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9Fc # rBjDTZ9ztwGpn1eqXijiuZQxggN8MIIDeAIBATB9MGkxCzAJBgNVBAYTAlVTMRcw # FQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3Rl # ZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAhP3DNP # fkVO28MPj/mSGDUwDQYJYIZIAWUDBAIBBQCggdEwGgYJKoZIhvcNAQkDMQ0GCyqG # SIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNjEwMTYxMTAwMjhaMCsGCyqGSIb3 # DQEJEAIMMRwwGjAYMBYEFFHZq9oDSXPYT0JmrKSCSOazacQ5MC8GCSqGSIb3DQEJ # BDEiBCC0UZVRV6eTYJrip1oXK9/iGeDhjLvSnLMiT+DTEb6s/jA3BgsqhkiG9w0B # CRACLzEoMCYwJDAiBCAtoJ2n9BMfn+cttsXm6cllZ1WvBD8ep0LMDSEg4UHr/DAN # BgkqhkiG9w0BAQEFAASCAgCuNHCLIf6bTOFs5/fZD5wWHFvnDxQLkPovrqwotcWa # /NmYoevzdfLKqpnmmPyWbZw1UQcvJxUhvdGhJ1ff/+lk6rWNAMyK3JprOJRas6Ki # NcWuZYNdNhAWOnQ0YQWpEIv1aP2yHlEVJdBDW1b6dbMdxbCXQvWSMty/RYD18FMG # P3435EQfmsZyMdwaRj+tIVgaP3JYGFpNTkY3G2CV0ON+r9j1O1pzxFrt5kIO+Qhb # GUFQllbmK9ZoyuZ8WCrfw6BAo1cin3V8IpMWT5F7ihn/zEtHtda+xKj1UCrOkYqt # ozubdJlv5Fkl7g8slAyUZdll6waU+QJBXsJ+qKGpkoOpHyyYaZsC/rvkYGMflbih # aKiZWi9XI4/qOqvgLJf8aIpdx09dNk+pd/NGETzdHO5zRJ6hWMdHLyAZmgZr9ukU # i5VVr3ayC85DM+xYRcJZAyTI8NSy0ik/gWxMw+r6vPh4/gzmo5QAoSbzfjfbuMtU # gyhmUnpoo/w5Z25dNyCnprqrUtPSS0AY2U0ESyZs9fgOpr8VjGWchGUtuHHyeGMY # vf6t9+evHJ8ZIqrtzKaIEP+8C6e4qe5TrMSlEqpdNwDh8p5WYVENxqfAomnLPp6H # JO3SkzlqP5mjVcLQjmB/TCqR6a1tu0Eud8E4HJWXJZ7jVEG6byil1OGicYOVSOwx # vg== # SIG # End signature block |