# FileHandlers.psm1 #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' } 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', '' [string]$findtext = $document.Find($pattern) | Select-Object text -First 2 if ($findtext) { if ($n -le '1') { foreach ($text in $findtext) { $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' } $results += $result } else { $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-Pdfcontent { 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-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 { $xml = [xml]$xmlContent foreach ($pattern in $patterns) { $findmatches = $xmlContent | Select-String -Pattern $pattern foreach ($match in $findmatches) { switch ($pattern) { '\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b' { $pattern = 'IPv4' } '\b[a-fA-F0-9]{32}\b' { $pattern = 'MD5' } '\b[a-fA-F0-9]{40}\b' { $pattern = 'SHA-1' } '\b[a-fA-F0-9]{64}\b' { $pattern = 'SHA-256' } default { switch -Wildcard ($pattern) { 'net *' { $pattern = 'Commande Net User' } default { $pattern = $pattern } } } } $result = [PSCustomObject]@{ FilePath = $filepath Pattern = $pattern Reason = $match.Matches.Value } $results += $result } } } $results } 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-CertifsContent { 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 "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 } } #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/workbook.xml") { $detectedType = "xlsx" } elseif ($fileNames -contains "ppt/presentation.xml") { $detectedType = "pptx" } elseif ($fileNames -contains "visio/document.xml") { $detectedType = "vsdx" } 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, [object]$jsonContent ) $detectedType = "others" $extension = [System.IO.Path]::GetExtension($filePath).TrimStart('.') $fileHeaderHex = [System.IO.File]::ReadAllBytes($filePath)[0..16] | 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' -and $extension -in $jsonContent.magic_numbers.extensions) { $detectedType = 'requires_check' } if ($detectedType -eq "others") { $firstLine = Get-Content -Path $filePath -TotalCount 1 if ($firstLine -match '^<\?xml') { $detectedType = "xml" } } return $detectedType }