Thereat Hunting
2022-01-25
Threat Hunting Simulator -- Technical Documentation
1. Overview
The Threat Hunting Simulator is TryHackMe's interactive training platform for developing proactive threat hunting skills. Unlike the SOC Simulator (which focuses on reactive alert triage), Threat Hunting places users in a proactive investigator role: they receive a hypothesis and threat intelligence briefing, then must independently hunt through SIEM logs, reconstruct a full attack chain using the MITRE ATT&CK framework, write adversary step descriptions, and generate an AI-powered threat report.
The system provides VMs (SIEM + analyst workstation), evaluates the user's timeline against expected attack stages, uses GPT-4o to assess the quality of adversary descriptions, and generates an overall performance report.
2. High-Level Architecture
3. Directory Structure
Backend (API)
apps/api/app/api/v2/src/
├── routes/threat-hunting/
│ ├── index.ts # Main router
│ ├── runs.ts # Run lifecycle routes
│ ├── timeline.ts # Timeline stage CRUD routes
│ ├── content.ts # CMS content routes
│ ├── vms.ts # VM management routes
│ └── public/ # Unauthenticated routes
├── controllers/threat-hunting/
│ ├── runs.ts # Run request handlers
│ ├── timeline.ts # Timeline stage handlers
│ ├── content.ts # Content handlers
│ ├── stats.ts # Stats handlers
│ ├── leaderboard.ts # Leaderboard handlers
│ └── vms.ts # VM handlers
├── services/threat-hunting/
│ ├── manage-runs.ts # Run init, scoring, pause/resume
│ ├── vms.ts # VM deployment (wraps soc-sim)
│ ├── runs.ts # Run CRUD operations
│ ├── report.ts # AI report generation (GPT-4o)
│ ├── content.ts # Scenario content retrieval
│ ├── run-access.ts # Access control
│ ├── run-public-summary.ts # Public sharing
│ ├── stats.ts # Statistics
│ └── leaderboard.ts # Leaderboard logic
├── models/threat-hunting/
│ └── run.ts # ThreatHuntingRun Mongoose model
├── common/
│ ├── interfaces/threat-hunting/ # TypeScript interfaces
│ ├── enums/threat-hunting/ # Status enums
│ └── constants/mitre/ # MITRE ATT&CK tactics & techniques
└── services/openai/
└── index.ts # Shared OpenAI service (GPT-4o)
Frontend
apps/frontend/src/features/threat-hunting/
├── threat-hunting.tsx # Root component
├── threat-hunting.slice.ts # Core RTK Query API slice
├── threat-hunting.types.ts # TypeScript types
├── threat-intel/ # Hypothesis & threat intelligence
├── timeline/ # Attack chain timeline builder
├── threat-report/ # AI-generated threat report
├── summary/ # Run summary & scoring
├── scenario-onboarding/ # Onboarding flow
├── scenario-overview/ # Scenario preview
├── my-computer/ # Analyst VM iframe
├── my-notes/ # Draggable notes editor
├── documentation/ # Scenario documentation
├── guide-pages/ # Help guides
├── landing-sections/
│ ├── threat-hunting-home/ # Landing page
│ ├── threat-hunting-scenarios/ # Scenario listing
│ ├── threat-hunting-stats/ # User statistics
│ └── threat-hunting-leaderboard/ # Leaderboard
└── components/
├── navigation/ # Top navigation bar
├── notes-editor/ # Draggable note-taking
├── vms-frame/ # VM iframe wrapper
├── timeline-card/ # Timeline stage cards
├── tactic-select/ # MITRE tactic picker
├── mitre-accuracy/ # MITRE accuracy visualization
└── machine-expiring-notification/# VM expiry warnings
4. Data Model
ThreatHuntingRun (threat_hunting_runs)
| Field | Type | Description |
|---|---|---|
scenario | String | Sanity CMS scenario ID |
title | String | Scenario display name |
user | ObjectId (User) | Run owner |
status | Enum | starting, ready, completed, terminated, abandoned, paused |
startedAt / endedAt | Date | Run time boundaries |
pausedAt / resumedAt | Date | Pause/resume timestamps |
actualRuntime | Number | Total active time (seconds) |
hasInvestigationStarted | Boolean | Whether user clicked "Start Investigation" |
hypothesis | String | User's threat hypothesis |
vms.siem | { url, instance } | SIEM VM connection |
vms.analyst | { url, instance } | Analyst VM connection |
siemTool | Enum | splunk, elastic, sentinel |
users | String[] | Selectable user entities in scenario |
assets | String[] | Selectable asset entities in scenario |
notes | String | User's investigation notes |
timelineStages | Array | User-created attack chain stages |
meta.hypothesisOutcome | String | Expected hypothesis outcome (ground truth) |
meta.hypothesisOutcomePoints | Number | Points for correct hypothesis classification |
meta.cmsTimelineStages | Array | Snapshot of expected stages from CMS |
report | Embedded | Evaluation results (see below) |
overallRunEvaluation | String | AI-generated overall feedback |
pointsAwarded | Number | Total points earned |
success | Boolean | Pass/fail (>=80% threshold) |
isErrored | Boolean | VM deployment failure |
Timeline Stage (user-created, embedded in run)
| Field | Type | Description |
|---|---|---|
title | String | Stage name |
description | String | Adversary step description (free-text) |
timestamp | Date | When the attack step occurred |
tactic | String | MITRE ATT&CK Tactic ID |
technique | String | MITRE ATT&CK Technique ID |
user | String | Compromised user entity |
asset | String | Compromised asset |
ioc | String | Indicators of Compromise |
siemUrlLink | String | SIEM query URL as evidence |
order | Number | Stage position in chain |
evaluation | String | AI-generated feedback (post-submission) |
*PointsAwarded | Number | Points per category (7 fields) |
CMS Timeline Stage (ground truth, snapshotted into meta)
| Field | Type | Description |
|---|---|---|
timelineStartTime / timelineEndTime | Date | Expected timestamp window |
timelinePoints | Number | Points for correct timestamp |
tactic / tacticPoints | String/Number | Expected tactic + points |
technique / techniquePoints | String/Number | Expected technique + points |
expectedUser / userPoints | String/Number | Expected user + points |
expectedAsset / assetPoints | String/Number | Expected asset + points |
iocs | Array<{value, points, type}> | Expected IOCs with individual points |
adversaryStepDescriptionEvaluationCriteria | String | Criteria for AI evaluation |
adversaryStepDescriptionPoints | Number | Max points for description |
Report (embedded in run)
| Field | Type |
|---|---|
hypothesisOutcomeClassification | String |
hypothesisOutcomePointsAwarded | Number |
threatReportEvaluation | String (markdown) |
techniquesAndTactics | { percentageIdentified, tactic[], technique[] } |
compromisedAssets | { percentageIdentified, assets[] } |
iocs | { percentageIdentified, network[], host[] } |
5. Run Lifecycle
Key Difference from SOC-Sim
Threat Hunting has an explicit investigation start step (hasInvestigationStarted). After VMs are ready, the user reviews the scenario overview and threat intelligence before clicking "Start Investigation." In SOC-Sim, investigation begins immediately when the run is ready.
6. VM Creation & Deployment
Shared Infrastructure with SOC-Sim
Threat Hunting reuses the SOC-Sim VM infrastructure directly:
threat-hunting/vms.ts imports from soc-sim/vms.ts:
├── deployVMsWithRetry() # Core EC2 deployment + retry logic
├── insertSOCSimInstance # (aliased as insertThreatHuntingInstance)
├── insertMockInstance # Local dev mock
├── extendVmTime # 1-hour extension logic
└── validateSimulationStarted # Prevent duplicate VMs
| Aspect | SOC-Sim | Threat Hunting |
|---|---|---|
| Cost Center | CostCenter.SOC_SIM | CostCenter.THREAT_HUNTER |
| Health Check Job | soc-sim-health-check | threat-hunting-health-check |
| Post-deploy | Import logs + alerts | 25s wait only (logs pre-loaded) |
| Log ingestion | EventBridge scheduled | None -- logs are pre-loaded on the SIEM VM |
| Alert insertion | From CMS into MongoDB | None -- no alert queue |
The critical difference: Threat Hunting VMs come with pre-loaded logs on the SIEM. There is no EventBridge log scheduling or alert insertion. The user proactively hunts through existing data rather than reacting to incoming alerts.
7. The Timeline: Attack Chain Building
Timeline API Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /threat-hunting/timeline | Get all stages for active run |
GET | /threat-hunting/timeline/stage | Get single stage |
POST | /threat-hunting/timeline/stage | Create new stage |
PATCH | /threat-hunting/timeline/stage | Update existing stage |
DELETE | /threat-hunting/timeline/stage | Delete stage |
POST | /threat-hunting/timeline/stage/reorder | Reorder stages |
The frontend uses @dnd-kit for drag-and-drop reordering of timeline stages.
8. AI Integration (GPT-4o)
AI Function 1: Adversary Step Description Evaluation
When: Called for each timeline stage during submitFindings()
Purpose: Evaluates the quality of the user's free-text adversary step description against CMS-defined criteria
Model: GPT-4o via OpenAIService.chatGptCompletionAPI()
System prompt (summarized):
"You are a Senior Threat Hunter. Evaluate the adversary step description strictly based on the given 'Criteria'. Calculate a totalScore. Write feedback as a senior SOC analyst speaking to a Junior Threat Hunter. Do not reveal evaluation criteria. Limit to 1000 characters."
Input:
- User's adversary step description
- Selected user & asset
- Evaluation criteria from CMS (defines what to look for and max scores)
Output (Zod-validated):
{ summary: string, totalScore: number }AI Function 2: Threat Report Generation
When: User explicitly triggers via "Generate Threat Report" button Purpose: Creates a markdown threat case report from the full attack chain
System prompt (summarized):
"You are a Senior Threat Hunter. Generate a precise threat case report in markdown from attack chain stages. Validate data completeness. Reject single-stage chains. Limit to 15 sentences."
Input: All timeline stages with title, description, timestamp, tactic/technique names, user, asset, IOC, SIEM URL
Output: Markdown-formatted threat report
AI Function 3: Overall Run Evaluation
When: After scoring completes (async) Purpose: Generates manager-style overall performance feedback
System prompt (summarized):
"You are a Senior Threat Hunter. Provide overall feedback on the user's performance based on individual stage evaluations. 2-3 sentences focusing on improvement areas. Address user as 'Junior Threat Hunter'."
Input: All stage evaluations (AI-generated summaries from Function 1)
Output: 2-3 sentence overall feedback
AI Configuration
const openAiOptions = {
temperature: 0.7,
top_p: 0.95,
n: 1,
stream: false,
max_tokens: 1000,
presence_penalty: 0,
frequency_penalty: 0,
};All three functions use structured output (Zod schema validation) via OpenAI's chatGptCompletionAPI, which includes:
- Up to 3 retries with exponential backoff
- Policy violation detection
- Rate limit / quota handling
- Error capture via Sentry
9. Scoring & Evaluation
Scoring Categories (per stage)
| Category | Evaluation Method | Points Source |
|---|---|---|
| Timestamp | Is stage.timestamp within [timelineStartTime, timelineEndTime]? | cmsStage.timelinePoints |
| Tactic | Exact match: stage.tactic === cmsStage.tactic | cmsStage.tacticPoints |
| Technique | Exact match: stage.technique === cmsStage.technique | cmsStage.techniquePoints |
| User | Exact match: stage.user === cmsStage.expectedUser | cmsStage.userPoints |
| Asset | Exact match: stage.asset === cmsStage.expectedAsset | cmsStage.assetPoints |
| IOC | Substring match (case-insensitive) per IOC entry | cmsStage.iocs[].points |
| Description | AI-evaluated against CMS criteria | Up to cmsStage.adversaryStepDescriptionPoints |
IOC Matching Detail
const calculateIocPoints = (stageIoc: string, cmsIocs: Array<{ value: string; points: number }>) =>
cmsIocs.reduce((acc, ioc) => {
const normalizedStageIoc = stageIoc.replace(/\\/g, '');
const normalizedValue = ioc.value.replace(/\\/g, '');
if (normalizedStageIoc.toLowerCase().includes(normalizedValue.toLowerCase()))
acc.iocPointsAwarded += ioc.points;
acc.totalIocPoints += ioc.points;
return acc;
}, { iocPointsAwarded: 0, totalIocPoints: 0 });IOCs are matched via case-insensitive substring inclusion -- the user's IOC field must contain the expected IOC value somewhere in the text.
Final Score Calculation
totalScenarioPoints = sum(all CMS stage points) + sum(all IOC points) + hypothesisOutcomePoints
totalAwarded = sum(all awarded stage points) + hypothesisOutcomePointsAwarded
success = (totalAwarded / totalScenarioPoints) * 100 >= 80
Report Breakdown Generation
After scoring, the system generates detailed breakdowns stored in run.report:
| Breakdown | What it shows |
|---|---|
techniquesAndTactics | % of tactics/techniques correctly identified, per-stage detail |
compromisedAssets | % of assets identified, list with identified flag |
iocs | % of IOCs identified, split into network vs host categories |
10. Frontend User Flow
11. API Endpoints Summary
Run Management
| Method | Endpoint | Description |
|---|---|---|
POST | /threat-hunting/runs | Create new run |
GET | /threat-hunting/runs/active | Get active run data |
POST | /threat-hunting/runs/start | Start investigation (sets flag) |
POST | /threat-hunting/runs/pause | Pause active run |
POST | /threat-hunting/runs/resume | Resume paused run |
POST | /threat-hunting/runs/terminate | Terminate run |
POST | /threat-hunting/runs/hypothesis-outcome | Set hypothesis classification |
POST | /threat-hunting/runs/threat-report | Generate AI threat report |
POST | /threat-hunting/runs/submit-findings | Submit & evaluate (triggers scoring + AI) |
GET | /threat-hunting/runs/summary | Get run summary |
GET | /threat-hunting/runs/summary/overall-evaluation | Get AI overall evaluation |
GET | /threat-hunting/runs/shared-links | Get public sharing links |
PATCH | /threat-hunting/runs/notes | Update investigation notes |
Timeline
| Method | Endpoint | Description |
|---|---|---|
GET | /threat-hunting/timeline | Get all stages |
GET | /threat-hunting/timeline/stage | Get single stage |
POST | /threat-hunting/timeline/stage | Create stage |
PATCH | /threat-hunting/timeline/stage | Update stage |
DELETE | /threat-hunting/timeline/stage | Delete stage |
POST | /threat-hunting/timeline/stage/reorder | Reorder stages |
Content & Reference
| Method | Endpoint | Description |
|---|---|---|
GET | /threat-hunting/content/scenarios | List scenarios |
GET | /threat-hunting/content/scenario | Scenario details |
GET | /threat-hunting/content/tactics | MITRE ATT&CK tactics |
GET | /threat-hunting/content/techniques | MITRE ATT&CK techniques |
VMs
| Method | Endpoint | Description |
|---|---|---|
POST | /threat-hunting/vms/extend | Extend VM by 1 hour |
POST | /threat-hunting/vms/refresh-url | Refresh Guacamole URL |
Company / Management Dashboard
| Method | Endpoint | Description |
|---|---|---|
POST | /companies/threat-hunting/assignment | Create assignment |
GET | /companies/threat-hunting/stats | Company stats |
GET | /companies/threat-hunting/users | Users with runs |
GET | /companies/threat-hunting/runs | User runs |
GET | /companies/threat-hunting/average-stats | Average stats |
GET | /companies/threat-hunting/success-rate-progression | Success rate over time |
GET | /companies/threat-hunting/tactic-and-technique-accuracy-rate | MITRE accuracy |
12. SOC-Sim vs Threat Hunting: Architecture Comparison
| Dimension | SOC Simulator | Threat Hunting |
|---|---|---|
| User Role | Reactive SOC Analyst | Proactive Threat Hunter |
| Core Task | Triage incoming alerts | Reconstruct attack chain |
| Logs | Ingested over time via EventBridge | Pre-loaded on VM |
| Alerts | System-generated, timed release | None -- user finds evidence |
| Data Structure | Flat alert list | Ordered timeline stages |
| MITRE ATT&CK | Alert types only | Full tactic + technique per stage |
| AI Usage | Writeup evaluation, overall eval | Description eval, threat report, overall eval |
| AI Model | GPT-4o | GPT-4o |
| Pass Threshold | All TPs resolved correctly | >=80% of total points |
| Scoring | Classification + escalation + evaluation | 7 categories + hypothesis |
| Multiplayer | Supported | Not supported |
| Certification | Integrated | Not integrated |
| Cost Center | SOC_SIM | THREAT_HUNTER |
| VM Infra | Own deployment code | Reuses SOC-Sim deployment code |
| WebSocket | Real-time alert events | Not used |
13. Feature Flags (GrowthBook)
| Flag | Type | Purpose |
|---|---|---|
soc-sim-vms-deploy | Boolean | Shared with SOC-Sim; enables real VM deployment |
vms-on-new-browser | Boolean | Open VMs in new browser tab vs iframe |
threat-hunting-siem-tool-modal | Boolean | SIEM tool selection UI |
guac-common-js | Boolean | Guacamole client implementation toggle |
guacamole-v2 | Boolean | Guacamole V2 infrastructure |
cell-feature-access | Boolean | Cell-based feature gating |
14. Infrastructure Summary
Key characteristics:
- No EventBridge / Lambda -- unlike SOC-Sim, logs are pre-loaded on VMs
- No Azure Sentinel path -- simplified; uses same constant but Sentinel VMs work differently
- VM lifetime: 3 hours default, extendable by 1 hour (same constraints as SOC-Sim)
- Health check polling: Every 3 seconds via Agenda.js until VMs respond or 30-minute timeout
- 25-second post-deployment wait (vs 18s in SOC-Sim) for VM readiness before marking READY