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 # MIItjAYJKoZIhvcNAQcCoIItfTCCLXkCAQExDzANBglghkgBZQMEAgEFADB5Bgor # 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 # ghoNMIIaCQIBATBqMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0 # 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/FYinL0bmNeCRDEx4kFiIuGhghd2MIIXcgYK # KwYBBAGCNwMDATGCF2IwghdeBgkqhkiG9w0BBwKgghdPMIIXSwIBAzEPMA0GCWCG # SAFlAwQCAQUAMHcGCyqGSIb3DQEJEAEEoGgEZjBkAgEBBglghkgBhv1sBwEwMTAN # BglghkgBZQMEAgEFAAQgcZYAUQOPtpqppbNp1s5oHXJtbXvKP8aVhsHJ5z79rSkC # EDvjp7f5N1kBJ0EAsVP1xpUYDzIwMjYwOTI5MjEwNDA2WqCCEzowggbtMIIE1aAD # AgECAhAIT9wzT35FTtvDD4/5khg1MA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYT # AlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQg # VHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEw # HhcNMjYwODA1MDAwMDAwWhcNMzcxMTA0MjM1OTU5WjBjMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFNIQTI1 # NiBSU0E0MDk2IFRpbWVzdGFtcCBSZXNwb25kZXIgMjAyNiAxMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAtnum8sn+zUr41JtMZbP9OMYw+HwJDpG5xkIu # /lqcfNYmMX81YmsUiHLbh9ykpeWBGKTLhYBrAN9Tdg/QEzG32XcObmgIblnr0CoQ # 3WSAeDZ6nH6X6VkFyYkJw3QBJREwvm4UhLzSxmwPA7cFKRTEOMsmEEj6qJk/dqLE # AL+oQYuOwE2UuiX1Vnul8YReIyWd4kgLn9gq6LNXM0UplkR6jL/QHxmb6fMoGBJY # bnaUI7XD6cKDpekK2SVMld4iDbzeHDtOaaxldH5IxuNusQ69nd8/ZXEiB5Hbxj3R # lK13cX1W4DlFXKdv/CEhM8Cj1vvlmvhNroyPdRGbbpBlgyf8Wdu5N6ByhFwURn0U # 6ozlPoxN22v+fviUhP+6DR547OZnpBMWDfei1f5sVGwiiW/KQTWOK97g+4RJpPzP # NV4VYMAwO2jM2Aty2QYPVmOQTJm0msuXnJrSbl2gf9JylpkJlWXqk1Q4LJsxz+TE # LoQCZIljbgvTJgoPU2R12ydv8i1UqL/adelA0y7U9Pmmtbze9Xx3rtajC5SzQd1j # gfwAwsa90v9YcSPdmeoyoBBA/27cCL237l5DTYYPDLQ4ON3OLTGWnvRb6jDrf/T7 # 5gMRfUzSLCBQfBusm9+mSWRlC/Df6S/e9Q8i13CuhzOT2Jx+V/nlbXM4QoBwlUAh # elwwJT0CAwEAAaOCAZUwggGRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBTJY4ow # LtRK+26U8+bjQH717M3iMB8GA1UdIwQYMBaAFO9vU0rp5AZ8esrikFb2L9RJ7MtO # MA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCBlQYIKwYB # BQUHAQEEgYgwgYUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv # bTBdBggrBgEFBQcwAoZRaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD # ZXJ0VHJ1c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIwMjVDQTEuY3J0 # MF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL # BQADggIBAI3FOmEenVIK35msCYB+fShAsWvSYvLBItoNdAgQ2jIqrGsVsluXMJU/ # +mRebBc52s6lbKAvOVPXaizmKkMLLflEEKDZQx4CkS2t8aHPjkXha3hYZ010htFa # 3dhNgmalH5vuWvh3tTCf4frTS7gPtGc4Z/xaPhQ2AB1mR8eEe/WbH0RWHvVIl6Vw # Q3+g5FKNfN2N/DWJkf13w2H+2GfqEfbd35Ww8CvoYBjLNIDTadcPWdgsjsiOaK/7 # EsKJgLjUNIVgvcaFOLLQ/GlrA+0ZHJoFUbOr5SJN8zykPspXIXlpDJY/gqFUZRRO # eab9GVgmhbdOJcD/63RhxPahFUGbckRONqMe6DYAv6/mOG0pWd3cPStsdcS7buj5 # DyniwRY8yooMH6ptx5vpP/pZzBPBeZD2U4IsthyxB5Jaa8qrOkB5z160TXiM5ADM # spZ0TfD9MJoq0tFpFPssKRFhWeEDYPvcUuN7U7lvcdHl4ezQ3NT/7Ffs1sR1yh/L # RbdZ3B3Vc6q2WmD8mDC0p9kzl2o73iVtS946IkEj7FkRsZGww1teYxERROC745xr # tjvcw9ZyyUjHZWGRIpJeMNsPquCDf0fkyHtB+J4AiNZqCQk23rxh+KbpyMTNVKIt # J5l92Svl20U9NbqMBOVYl1h54NEYLJq1/xHWFKPNK903zJZA9P2DMIIGtDCCBJyg # AwIBAgIQDcesVwX/IZkuQEMiDDpJhjANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjUw # NTA3MDAwMDAwWhcNMzgwMTE0MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UE # ChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQg # VGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAtHgx0wqYQXK+PEbAHKx126NGaHS0URedTa2N # DZS1mZaDLFTtQ2oRjzUXMmxCqvkbsDpz4aH+qbxeLho8I6jY3xL1IusLopuW2qft # JYJaDNs1+JH7Z+QdSKWM06qchUP+AbdJgMQB3h2DZ0Mal5kYp77jYMVQXSZH++0t # rj6Ao+xh/AS7sQRuQL37QXbDhAktVJMQbzIBHYJBYgzWIjk8eDrYhXDEpKk7RdoX # 0M980EpLtlrNyHw0Xm+nt5pnYJU3Gmq6bNMI1I7Gb5IBZK4ivbVCiZv7PNBYqHEp # NVWC2ZQ8BbfnFRQVESYOszFI2Wv82wnJRfN20VRS3hpLgIR4hjzL0hpoYGk81coW # J+KdPvMvaB0WkE/2qHxJ0ucS638ZxqU14lDnki7CcoKCz6eum5A19WZQHkqUJfdk # DjHkccpL6uoG8pbF0LJAQQZxst7VvwDDjAmSFTUms+wV/FbWBqi7fTJnjq3hj0Xb # Qcd8hjj/q8d6ylgxCZSKi17yVp2NL+cnT6Toy+rN+nM8M7LnLqCrO2JP3oW//1sf # uZDKiDEb1AQ8es9Xr/u6bDTnYCTKIsDq1BtmXUqEG1NqzJKS4kOmxkYp2WyODi7v # QTCBZtVFJfVZ3j7OgWmnhFr4yUozZtqgPrHRVHhGNKlYzyjlroPxul+bgIspzOwb # tmsgY1MCAwEAAaOCAV0wggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYE # FO9vU0rp5AZ8esrikFb2L9RJ7MtOMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/n # upiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB3Bggr # BgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNv # bTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lD # ZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2Ny # bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0g # BBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQAX # zvsWgBz+Bz0RdnEwvb4LyLU0pn/N0IfFiBowf0/Dm1wGc/Do7oVMY2mhXZXjDNJQ # a8j00DNqhCT3t+s8G0iP5kvN2n7Jd2E4/iEIUBO41P5F448rSYJ59Ib61eoalhnd # 6ywFLerycvZTAz40y8S4F3/a+Z1jEMK/DMm/axFSgoR8n6c3nuZB9BfBwAQYK9FH # aoq2e26MHvVY9gCDA/JYsq7pGdogP8HRtrYfctSLANEBfHU16r3J05qX3kId+ZOc # zgj5kjatVB+NdADVZKON/gnZruMvNYY2o1f4MXRJDMdTSlOLh0HCn2cQLwQCqjFb # qrXuvTPSegOOzr4EWj7PtspIHBldNE2K9i697cvaiIo2p61Ed2p8xMJb82Yosn0z # 4y25xUbI7GIN/TpVfHIqQ6Ku/qjTY6hc3hsXMrS+U0yy+GWqAXam4ToWd2UQ1KYT # 70kZjE4YtL8Pbzg0c1ugMZyZZd/BdHLiRu7hAWE6bTEm4XYRkA6Tl4KSFLFk43es # aUeqGkH/wyW4N7OigizwJWeukcyIPbAvjSabnf7+Pu0VrFgoiovRDiyx3zEdmcif # /sYQsfch28bZeUz2rtY/9TCA6TD8dC3JE3rYkrhLULy7Dc90G6e8BlqmyIjlgp2+ # VqsS9/wQD7yFylIz0scmbKvFoW2jNrbM1pD2T7m3XDCCBY0wggR1oAMCAQICEA6b # GI750C3n79tQ4ghAGFowDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTAT # BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEk # MCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAw # MDAwMFoXDTMxMTEwOTIzNTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp # Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMY # RGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A # MIICCgKCAgEAv+aQc2jeu+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2u # exuEDcQwH/MbpDgW61bGl20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKv # aJNwwrK6dZlqczKU0RBEEC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/gh # YZs06wXGXuxbGrzryc/NrDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOt # yU9e5TXnMcvak17cjo+A2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCR # cKtVgkEy19sEcypukQF8IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8 # oU85tRFYF/ckXEaPZPfBaYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m # 1O+SkjqePdwA5EUlibaaRBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y # 1YxwLEFgqrFjGESVGnZifvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkl # iWzlDlJRR3S+Jqy2QXXeeqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/E # IFFrb7GrhotPwtZFX50g/KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOC # ATowggE2MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/n # upiuHA9PMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB # /wQEAwIBhjB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw # LmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNl # cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqg # OKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURS # b290Q0EuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEA # cKC/Q1xV5zhfoKN0Gz22Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU # 9BNKei8ttzjv9P+Aufih9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0Sb # QyHrlnKhSLSZy51PpwYDE3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1Rmpp # VLC4oVaO7KTVPeix3P0c2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxY # oA5AY8WYIsGyWfVVa88nq2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0Vys # GMNNn3O3AamfV6peKOK5lDGCA3wwggN4AgEBMH0waTELMAkGA1UEBhMCVVMxFzAV # BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVk # IEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMQIQCE/cM09+ # RU7bww+P+ZIYNTANBglghkgBZQMEAgEFAKCB0TAaBgkqhkiG9w0BCQMxDQYLKoZI # hvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDkyOTIxMDQwNlowKwYLKoZIhvcN # AQkQAgwxHDAaMBgwFgQUUdmr2gNJc9hPQmaspIJI5rNpxDkwLwYJKoZIhvcNAQkE # MSIEILIOfpSqDKjTtCZpz8nVTgsQfQE2PJNBZRnlQZNXZZ5MMDcGCyqGSIb3DQEJ # EAIvMSgwJjAkMCIEIC2gnaf0Ex+f5y22xebpyWVnVa8EPx6nQswNISDhQev8MA0G # CSqGSIb3DQEBAQUABIICADCghAyZeePUevICoy4AGB0PDTSQGdu1GhP5tsm9pxd+ # /6HMfJnHa2z8I0lejYXI51evM+Jui+lzn9aIejnNN0Ft29Xu7FmjJ6e3/jZuhp85 # zNSkFhor5x4oruAV3tbOvAuPPimLQnSmbJnlgTnoLRYEwe/ePljORNkmR0ceBNx/ # 2CXJgXnTaNtj9cZDqZK+rvde5wx3BgQHj13zdDdmFINfBYaRK5WIflHWERESmezI # M85UNwlUTQVJU2F7xEACax1DfGEG60mSMxSjtPxtHHCIH4QLc0b1eWwDW2b4wK+f # R1cHVmmQv2eiUxm8sQSeGnwb+P+tyDspoxXLRmjBhvJD9+D89eQBtqHls4fICx6F # Q7Qyrl45wZ7fYeCagWfgOz615A4AaVGpu2HCtVw/6hbt9R7ASfIYE7rt/T03DDTN # mwMC6zTaHVE8AcN0pCFgt53SAUprktcoaIgiNRtdhoA1k5oSBQEe2xaIei3YhPjd # zx7SDnehbItD9iHInk+eTfl1tpprEnPlSDl/mJANaISWRd5gV2PUIJqr9x15Gxi9 # U99cZqx1YECaDPdxRv/TciVcqeViseyoASD11tPg1gZyvINTd1FPEWgN61+7We17 # UMXCQSFUWTOJwAsoNehWpIV9rbj7OAk5mxEantCTqYbVioXeiZ0EouND17alN5/o # SIG # End signature block |