AI in SecOps Success Kit

About This Scenario

Every artifact on this page was generated from a simulated APT29 phishing campaign against a fictional medium-sized company. The logs are synthetic but structurally authentic — built by combining real attack technique recordings from the Mordor dataset with a procedurally generated org topology. No real infrastructure was targeted.


Phase 1 · Use AI Chatbot

Use AI to interpret raw logs and explain why an alert matters

Tool: ChatGPT · Claude · Gemini (all free)

How to use this section

Below are four real log events from our simulated attack, one per stage of the kill chain. Expand each one, copy the raw log, paste it into any free AI tool, then use the prompt template at the bottom to get an instant analyst-grade explanation.

1

Initial Access / Delivery

T1197 – BITS Jobs

The attacker used Windows' built-in BITSAdmin utility to silently download a malicious PowerShell script from a remote URL. Because BITS is a legitimate Windows service, many security tools overlook it — but the destination URL and dropped filename are strong red flags.

What to look for

Raw Sysmon Log Event

{
  "kill_chain_step": 1,
  "EventID": 1,
  "SourceName": "Microsoft-Windows-Sysmon",
  "Hostname": "SALES-LAP-01.holodeck.corp",
  "User": "CORP\\SYSTEM",
  "Image": "C:\\Windows\\System32\\bitsadmin.exe",
  "CommandLine": "bitsadmin.exe  /transfer /Download /priority Foreground https://raw.githubusercontent.com/redcanaryco/atomic-red-team/master/atomics/T1197/T1197.md C:\\Users\\wardog\\AppData\\Local\\Temp\\bitsadmin1_flag.ps1",
  "ParentImage": "C:\\Windows\\System32\\cmd.exe",
  "ParentCommandLine": "\"C:\\windows\\system32\\cmd.exe\" ",
  "Hashes": "SHA1=282DA9EE622F01CC63352E53FDC3D4A75CEEB6FD,MD5=A23A7A6B6F8E1A5D913EA119F5F2ED1A,SHA256=EAAE8536D554D0E86D8540A8B34DB2649BD884843F389495D0B6E91636C6CF54,IMPHASH=B0A3CFF8CFDE112945189719F82F9EA9",
  "@timestamp": "2026-01-14T00:00:02.876000+00:00"
}

2

Execution / C2 Establishment

T1059.001 – PowerShell

A PowerShell HTTP listener was spun up, creating an inbound C2 channel. The process masquerades as a legitimate svchost BITS service invocation, but the parent-child relationship and timing betray it.

What to look for

Raw Sysmon Log Event

{
  "kill_chain_step": 2,
  "EventID": 1,
  "SourceName": "Microsoft-Windows-Sysmon",
  "Hostname": "SALES-LAP-01.holodeck.corp",
  "User": "CORP\\SYSTEM",
  "Image": "C:\\Windows\\System32\\svchost.exe",
  "CommandLine": "C:\\windows\\System32\\svchost.exe -k netsvcs -p -s BITS",
  "ParentImage": "C:\\Windows\\System32\\services.exe",
  "ParentCommandLine": "C:\\windows\\system32\\services.exe",
  "@timestamp": "2026-01-14T00:00:02.919000+00:00"
}

3

Discovery

T1592 – Seatbelt Enumeration

Seatbelt.exe, a well-known post-exploitation reconnaissance tool, was executed with the '-group=user' flag to enumerate local users, groups, and privilege details. This is standard attacker tradecraft before attempting lateral movement.

What to look for

Raw Sysmon Log Event

{
  "kill_chain_step": 3,
  "EventID": 1,
  "SourceName": "Microsoft-Windows-Sysmon",
  "Hostname": "SALES-LAP-02.holodeck.corp",
  "User": "CORP\\SYSTEM",
  "Image": "C:\\Users\\wardog\\Desktop\\Seatbelt.exe",
  "CommandLine": "Seatbelt.exe  -group=user",
  "ParentImage": "C:\\Windows\\System32\\cmd.exe",
  "@timestamp": "2026-01-14T02:00:03.041000+00:00"
}

4

Credential Access

T1003.001 – LSASS Memory

Outflank-Dumpert.exe directly accessed lsass.exe memory using syscalls to bypass common AV hooks. This extracts cleartext credentials and NTLM hashes, enabling lateral movement across the network.

What to look for

Raw Sysmon Log Event

{
  "kill_chain_step": 4,
  "EventID": 10,
  "SourceName": "Microsoft-Windows-Sysmon",
  "Hostname": "SALES-LAP-01.holodeck.corp",
  "SourceImage": "C:\\windows\\system32\\svchost.exe",
  "TargetImage": "C:\\windows\\system32\\lsass.exe",
  "GrantedAccess": "0x1000",
  "CallTrace": "C:\\windows\\SYSTEM32\\ntdll.dll+9c584|C:\\windows\\System32\\KERNELBASE.dll+6af15|c:\\windows\\system32\\lsm.dll+ff97|C:\\windows\\System32\\RPCRT4.dll+76a63|C:\\windows\\System32\\RPCRT4.dll+da036|C:\\windows\\System32\\RPCRT4.dll+37b7c|C:\\windows\\System32\\RPCRT4.dll+549f8|C:\\windows\\System32\\RPCRT4.dll+2c9b1|C:\\windows\\System32\\RPCRT4.dll+2c26b|C:\\windows\\System32\\RPCRT4.dll+1a8cf|C:\\windows\\System32\\RPCRT4.dll+19d7a|C:\\windows\\System32\\RPCRT4.dll+19361|C:\\windows\\System32\\RPCRT4.dll+18dce|C:\\windows\\System32\\RPCRT4.dll+16a05|C:\\windows\\SYSTEM32\\ntdll.dll+333ed|C:\\windows\\SYSTEM32\\ntdll.dll+34142|C:\\windows\\System32\\KERNEL32.DLL+17c24|C:\\windows\\SYSTEM32\\ntdll.dll+6cea1",
  "@timestamp": "2026-01-14T00:00:02.913000+00:00"
}

Phase 2 · Use AI in Automation

Wire AI into your alert pipeline — from a single webhook to full triage

Tool: Python · FastAPI · LiteLLM

The pattern

Once you've confirmed AI can explain a single alert, the next move is automation: Alert → Webhook → AI Analysis → Ticket. The handler below exposes a POST /triage endpoint. Point your SIEM at it and every alert gets classified, MITRE-mapped, and returned as structured JSON ready for ticket creation — or wire any no-code tool (N8N, Zapier, Make) to call it.

Workflow

🚨SIEM Alert

⚡Webhook

🤖AI Analysis

🎫Create Ticket

Use AI in Automation – Webhook Triage Handler

Drop this in front of your SIEM. It exposes a POST /triage endpoint: your SIEM fires a webhook, the handler calls an LLM with structured output, and returns ticket-ready JSON you can forward to Jira, ServiceNow, or Slack. Wire any no-code tool (N8N, Zapier, Make) to this endpoint instead of calling the LLM directly — the structured output guarantees consistent fields every time.

Prompt Template

from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import List, Literal
from litellm import completion
import uvicorn

app = FastAPI()

class TriageResult(BaseModel):
    severity: Literal["critical", "high", "medium", "low"]
    summary: str = Field(description="One sentence: what happened and why it matters")
    mitre_technique: str = Field(description="Best-fit ATT&CK ID + name, e.g. T1197 – BITS Jobs")
    next_steps: List[str] = Field(description="Exactly 3 concrete analyst actions")
    escalate: bool = Field(description="True if a human should act within 30 minutes")
    confidence: int = Field(description="1-10. How certain is this classification.")

class AlertPayload(BaseModel):
    alert: dict  # raw alert from your SIEM
    model: str = "claude-haiku-4-5-20251001"  # cheap model is fine for triage

@app.post("/triage", response_model=TriageResult)
def triage(payload: AlertPayload):
    import json
    alert_text = json.dumps(payload.alert, indent=2)

resp = completion(
        model=payload.model,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user",   "content": f"Alert data:\n{alert_text}"},
        ],
        response_format=TriageResult,
    )
    return TriageResult.model_validate_json(resp.choices[0].message.content)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Phase 3 · Build your own AI Agent

Watch Claude hunt through real logs — then replicate it with your own data

Tool: Python · LiteLLM

What you're looking at

This is an actual AI threat hunt — Claude Opus reasoning through the same APT29 simulation, writing SQL queries against a log database, and discovering the attack step-by-step. Each turn shows the reasoning, the query, and what was found. Green turns are moments a new piece of evidence was discovered.

1

The analyst (Claude) starts the same way a human would: get oriented. How many events are there? What time range? What hosts? This is good tradecraft — don't assume, measure first.

SELECT MIN("@timestamp") as earliest, MAX("@timestamp") as latest, COUNT(*) as total_events, COUNT(DISTINCT "host") as hosts FROM logs

2

The first query that hits gold. By grouping events by EventID and timestamp range, Claude immediately sees which event types are present and when activity clusters. This single query gives a map of the battlefield.

SELECT "EventID", COUNT(*) as cnt, MIN("@timestamp") as earliest, MAX("@timestamp") as latest FROM logs WHERE "@timestamp" != '' GROUP BY "EventID" ORDER BY cnt DESC LIMIT 20

3

Log clearing (EventID 1102) is a classic attacker cleanup move — they're trying to hide their tracks. Claude finds it early and flags the exact hosts and users involved.

SELECT "@timestamp", "EventID", "SourceName", "Channel", "SubjectUserName", "SubjectDomainName", "Message" FROM logs WHERE "EventID" IN ('1102', '104') ORDER BY "@timestamp"

4

Now Claude goes hunting for the delivery mechanism. It searches for suspicious command-line patterns across all process creation events.

SELECT "@timestamp", "Image", "CommandLine", "ParentImage", "ParentCommandLine", "User" FROM logs WHERE "EventID" = '1' AND ("CommandLine" LIKE '%powershell%' OR "CommandLine" LIKE '%cmd%' OR "CommandLine" LIKE '%whoami%' OR "CommandLine" LIKE '%net %' OR "CommandLine" LIKE '%mimikatz%' OR "CommandLine" LIKE '%invoke%' OR "CommandLine" LIKE '%download%' OR "CommandLine" LIKE '%encode%' OR "CommandLine" LIKE '%base64%' OR "CommandLine" LIKE '%bypass%') ORDER BY "@timestamp" LIMIT 10

5

The credential theft is exposed. Outflank-Dumpert.exe opened lsass.exe with PROCESS_VM_READ access — the signature of a memory dump. Claude recognizes the tool name and access mask immediately.

SELECT "@timestamp", "SourceImage", "TargetImage", "GrantedAccess", "CallTrace" FROM logs WHERE "EventID" = '10' AND "TargetImage" LIKE '%lsass%' ORDER BY "@timestamp" LIMIT 10

Build your own AI Agent – The ReAct Loop

This is the actual agent loop powering the hunt walkthrough above. The key insight: force structured output so the model can't hallucinate a tool call — it must emit valid JSON with a reasoning trace, a tool name, and the right arguments. Copy this, point it at your log database, and the model will drive its own investigation.

Prompt Template

from litellm import completion
from pydantic import BaseModel, Field
from typing import Optional, List

class HunterAction(BaseModel):
    reasoning: str = Field(
        description="Step-by-step thinking: what did I find, what does it mean,
                    what should I query next? Never leave this empty."
    )
    tool: str = Field(
        description="One of: 'run_sql' | 'submit_flags' | 'give_up'"
    )
    sql_query: Optional[str] = Field(
        None, description="Required when tool='run_sql'"
    )
    suspicious_timestamps: Optional[List[str]] = Field(
        None, description="ISO timestamps of malicious events. Required when tool='submit_flags'"
    )

SYSTEM_PROMPT = """
You are an expert threat hunter. A log database contains evidence of a
security breach. Your job: find every malicious event by writing SQL queries.

Database: table 'logs' with columns:
  @timestamp, EventID, SourceName, Hostname, User,
  Image, CommandLine, ParentImage, TargetImage,
  DestinationIp, DestinationPort, TargetFilename

AVAILABLE TOOLS:
  run_sql           - Execute a SQL SELECT. Costs one turn.
  submit_flags      - Record the @timestamp of each malicious event you found.
  give_up           - End the hunt.

Be efficient. You have 20 queries. Start broad (event counts, time ranges),
then narrow toward suspicious activity.
"""

def run_hunt(db_conn, model="claude-opus-4-6"):
    history = [{"role": "system", "content": SYSTEM_PROMPT}]
    obs = "Begin the investigation. The log database is ready."

for turn in range(20):
        history.append({"role": "user", "content": obs})
        resp = completion(
            model=model,
            messages=history,
            response_format=HunterAction,
        )
        action = HunterAction.model_validate_json(
            resp.choices[0].message.content
        )
        history.append({"role": "assistant", "content": resp.choices[0].message.content})

print(f"[Turn {turn+1}] tool={{action.tool}}")
        print(f"  Reasoning: {{action.reasoning[:120]}}...")

if action.tool == "run_sql":
            rows = db_conn.execute(action.sql_query).fetchall()
            obs = f"Query returned {{len(rows)}} rows:\n{{rows[:10]}}"

elif action.tool == "submit_flags":
            print(f"  Flagged {{len(action.suspicious_timestamps)}} events")
            obs = f"Recorded {{len(action.suspicious_timestamps)}} timestamps."

elif action.tool == "give_up":
            print("  Agent gave up.")
            break

return history