{"id":297,"date":"2026-05-12T09:00:00","date_gmt":"2026-05-12T09:00:00","guid":{"rendered":"http:\/\/justruss.tech\/index.php\/2025\/05\/21\/velociraptor-process-injection-investigation-full-multi-layer-enrichment\/"},"modified":"2026-05-15T10:34:55","modified_gmt":"2026-05-15T10:34:55","slug":"velociraptor-process-injection-investigation-full-multi-layer-enrichment","status":"publish","type":"post","link":"https:\/\/justruss.tech\/index.php\/2026\/05\/12\/velociraptor-process-injection-investigation-full-multi-layer-enrichment\/","title":{"rendered":"Velociraptor Process Injection Hunting: Full Investigation Enrichment"},"content":{"rendered":"<h3>Enrichment layer 1: correlating VAD findings with Sysmon process creation history<\/h3>\n<p>Finding an anonymous executable region in a process is a strong indicator, but it answers only part of the question. You know something is in the memory of process X. You do not yet know how it got there, when it appeared, or what chain of events preceded it. Windows event logs and Sysmon telemetry fill that gap, giving you a timeline of everything that happened to that process before and after the injection.<\/p>\n<p>The first enrichment step is to take the PID and process name from your Velociraptor findings and pull the full process creation chain from Sysmon Event ID 1. This tells you what created the process you are investigating, what command line was used, and how far back the process tree goes. An injected svchost.exe that was spawned by services.exe with normal arguments is a very different finding from an injected svchost.exe that was spawned by a PowerShell process with an encoded command argument.<\/p>\n<pre>-- VQL: Enrich suspicious process with its creation history from Sysmon EventLog\n-- Replace TargetPid with the PID from your injection hunt results\n\nLET target_pid = 4892 -- replace with suspicious PID from hunt results\n\n-- Step 1: Get the full ancestry chain for the suspicious process\nSELECT\n ProcessId AS PID,\n ParentProcessId AS ParentPID,\n Name AS ProcessName,\n CommandLine,\n User,\n CreateTime,\n Exe AS ExecutablePath,\n -- Hash the executable to check reputation\n hash(path=Exe, hashselect=\"SHA256\") AS ExecutableHash\n\nFROM pslist()\nWHERE ProcessId = target_pid\n OR ProcessId IN (\n -- Also pull the parent and grandparent\n SELECT ParentProcessId FROM pslist() WHERE ProcessId = target_pid\n )<\/pre>\n<pre>-- VQL: Pull Sysmon Event ID 1 records for process creation context\n-- This gives you the full command line and parent details at process start time\n-- even if the process has been running for hours\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n EventData.ProcessId AS PID,\n EventData.ParentProcessId AS ParentPID,\n EventData.Image AS ProcessImage,\n EventData.CommandLine AS CommandLine,\n EventData.ParentImage AS ParentImage,\n EventData.ParentCommandLine AS ParentCommandLine,\n EventData.User AS RunningAsUser,\n EventData.IntegrityLevel AS IntegrityLevel,\n EventData.Hashes AS Hashes,\n -- Current directory at time of launch is often revealing\n EventData.CurrentDirectory AS WorkingDirectory\n\nFROM watch_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.EventID.Value = 1\n AND (\n EventData.ProcessId = format(format=\"%d\", args=target_pid)\n OR EventData.ParentProcessId = format(format=\"%d\", args=target_pid)\n )\nORDER BY EventTime ASC<\/pre>\n<p>Pay careful attention to the <code>IntegrityLevel<\/code> field in the Sysmon Event ID 1 record. A process running at High or System integrity that was spawned from a process at Medium integrity indicates a privilege escalation occurred somewhere in the chain. A process running as SYSTEM that was spawned from a user-context process through an unusual parent (WmiPrvSE.exe, msdtc.exe, or similar) indicates lateral movement or exploitation of a privileged service.<\/p>\n<p>The <code>Hashes<\/code> field in Sysmon Event ID 1 contains the SHA256 of the executable at the time it launched. Take that hash and check it against threat intelligence immediately. If the binary has been renamed to disguise itself, the hash will still identify the real malware family even if the filename looks legitimate.<\/p>\n<h3>Enrichment layer 2: memory image acquisition and Volatility cross-analysis<\/h3>\n<p>Velociraptor can acquire a full memory image from a live remote system and make it available for offline analysis with Volatility. This is the bridge between the live query capabilities of Velociraptor and the deep structural analysis capabilities of Volatility. Where Velociraptor gives you fleet-wide fast triage, Volatility gives you the forensic depth to answer definitively what the injected code is, what it has been doing, and what it has communicated with.<\/p>\n<pre>-- VQL: Acquire full memory image from the target machine\n-- Run this as a client artefact against the specific host that showed injection\n\nSELECT * FROM Artifact.Windows.Memory.Acquisition(\n -- Output path on the target machine (temporary staging)\n destination=\"C:\/Windows\/Temp\/mem_acquisition.raw\",\n -- Also acquire the pagefile for complete coverage\n also_upload_pagefile=TRUE\n)<\/pre>\n<p>Once the acquisition completes, Velociraptor uploads the image to the server and you can download it for Volatility analysis. The acquisition typically takes 2-10 minutes depending on RAM size. Do not wait for it to complete before starting other enrichment steps the Velociraptor VAD queries already captured the volatile data you need for triage, so the memory image is for deep analysis that can run in parallel.<\/p>\n<pre># Volatility 3 analysis workflow against the acquired image\n# Replace memory.raw with your downloaded image filename\n\n# Step 1: Confirm the image profile and get baseline info\nvol -f memory.raw windows.info\n\n# Step 2: Cross-reference the suspicious PID from Velociraptor\n# Get the full process details including PPID chain\nvol -f memory.raw windows.pstree | grep -A2 -B2 \"4892\"\n\n# Step 3: Run malfind against the specific suspicious process\n# malfind finds executable regions with no disk backing - same concept as our VQL\nvol -f memory.raw windows.malfind --pid 4892\n\n# Example malfind output for a Cobalt Strike injected process:\n# Process: svchost.exe Pid: 4892 Address: 0x1d0000\n# Vad Tag: VadS Protection: PAGE_EXECUTE_READWRITE\n# Hexdump:\n# 4d 5a 90 00 03 00 00 00 MZ......\n# This confirms the Velociraptor VAD finding with an independent second source\n\n# Step 4: Dump the suspicious region for further analysis\nvol -f memory.raw windows.dumpfiles --virtaddr 0x1d0000 --pid 4892 -o \/tmp\/dumped\/\n\n# Step 5: Analyse the dumped region\nfile \/tmp\/dumped\/file.0x4892.0x1d0000.img\nstrings -a -n 8 \/tmp\/dumped\/file.0x4892.0x1d0000.img | grep -iE \"(http|https|192\\.|10\\.|beacon|sleep|checkin)\"\nsha256sum \/tmp\/dumped\/file.0x4892.0x1d0000.img<\/pre>\n<pre># Step 6: Extract network connections from memory for C2 identification\nvol -f memory.raw windows.netscan | grep \"4892\"\n\n# Sample output:\n# TCPv4 192.168.1.105 54321 198.51.100.45 443 ESTABLISHED 4892 svchost.exe\n# This is the C2 connection - 198.51.100.45:443 is the attacker server\n\n# Step 7: Check for credential material in the injected process\n# If the injection target was lsass.exe or if the injected code accessed lsass\nvol -f memory.raw windows.lsadump | head -50\n\n# Step 8: Extract the full module list to find reflectively loaded components\nvol -f memory.raw windows.dlllist --pid 4892\n# Cross-reference against what Velociraptor reported in the modules() query\n# Any discrepancy = something was loaded that the OS does not officially track<\/pre>\n<h3>Enrichment layer 3: correlating with Windows event logs for timeline reconstruction<\/h3>\n<p>The memory analysis and Velociraptor VAD findings tell you what is in memory now. The Windows event logs tell you the story of how it got there. Building a complete timeline from event logs around the injection event turns an isolated finding into a full attack narrative that supports incident response decisions.<\/p>\n<pre>-- VQL: Pull all security-relevant events for the suspicious process\n-- and the 30 minutes before and after it was created\n-- This builds a timeline around the injection event\n\nLET process_create_time = timestamp(string=\"2024-04-02T22:14:00Z\") -- from Sysmon Event 1\nLET window_start = process_create_time - 1800 -- 30 minutes before\nLET window_end = process_create_time + 1800 -- 30 minutes after\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n System.EventID.Value AS EventID,\n -- Map event IDs to human-readable names\n switch(\n condition=System.EventID.Value = 4624, then=\"Logon Success\",\n condition=System.EventID.Value = 4625, then=\"Logon Failure\",\n condition=System.EventID.Value = 4648, then=\"Explicit Credentials Used\",\n condition=System.EventID.Value = 4688, then=\"Process Created\",\n condition=System.EventID.Value = 4689, then=\"Process Terminated\",\n condition=System.EventID.Value = 4698, then=\"Scheduled Task Created\",\n condition=System.EventID.Value = 7045, then=\"Service Installed\",\n condition=System.EventID.Value = 4720, then=\"User Account Created\",\n condition=System.EventID.Value = 4732, then=\"Member Added to Local Group\",\n else=format(format=\"Event %d\", args=System.EventID.Value)\n ) AS EventName,\n -- Pull the most relevant fields\n get(item=EventData, field=\"SubjectUserName\") AS Actor,\n get(item=EventData, field=\"TargetUserName\") AS Target,\n get(item=EventData, field=\"ProcessName\") AS ProcessName,\n get(item=EventData, field=\"CommandLine\") AS CommandLine,\n get(item=EventData, field=\"IpAddress\") AS SourceIP,\n get(item=EventData, field=\"WorkstationName\") AS SourceHost\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Security.evtx\"\n)\nWHERE System.TimeCreated.SystemTime &gt;= window_start\n AND System.TimeCreated.SystemTime &lt;= window_end\n AND System.EventID.Value IN (4624, 4625, 4648, 4688, 4689, 4698, 7045, 4720, 4732)\nORDER BY EventTime ASC<\/pre>\n<pre>-- VQL: Sysmon timeline for the injection window\n-- Covers network connections, file operations, registry changes, and pipe creation\n-- All correlated to the same time window around the injection\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n System.EventID.Value AS SysmonEventID,\n switch(\n condition=System.EventID.Value = 1, then=\"Process Created\",\n condition=System.EventID.Value = 3, then=\"Network Connection\",\n condition=System.EventID.Value = 5, then=\"Process Terminated\",\n condition=System.EventID.Value = 6, then=\"Driver Loaded\",\n condition=System.EventID.Value = 7, then=\"Image Loaded\",\n condition=System.EventID.Value = 8, then=\"CreateRemoteThread\",\n condition=System.EventID.Value = 10, then=\"ProcessAccess (LSASS\/injection)\",\n condition=System.EventID.Value = 11, then=\"File Created\",\n condition=System.EventID.Value = 12, then=\"Registry Key Created\/Deleted\",\n condition=System.EventID.Value = 13, then=\"Registry Value Set\",\n condition=System.EventID.Value = 17, then=\"Pipe Created\",\n condition=System.EventID.Value = 18, then=\"Pipe Connected\",\n condition=System.EventID.Value = 22, then=\"DNS Query\",\n condition=System.EventID.Value = 25, then=\"Process Tampering\",\n else=format(format=\"Sysmon %d\", args=System.EventID.Value)\n ) AS EventType,\n get(item=EventData, field=\"Image\") AS ProcessImage,\n get(item=EventData, field=\"TargetImage\") AS TargetProcess,\n get(item=EventData, field=\"DestinationIp\") AS DestIP,\n get(item=EventData, field=\"DestinationPort\") AS DestPort,\n get(item=EventData, field=\"QueryName\") AS DNSQuery,\n get(item=EventData, field=\"TargetFilename\") AS FilePath,\n get(item=EventData, field=\"TargetObject\") AS RegistryKey,\n get(item=EventData, field=\"PipeName\") AS PipeName,\n get(item=EventData, field=\"GrantedAccess\") AS AccessMask\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.TimeCreated.SystemTime &gt;= window_start\n AND System.TimeCreated.SystemTime &lt;= window_end\nORDER BY EventTime ASC<\/pre>\n<p>When you view this combined timeline, you are looking for the moment the injection happened and everything that preceded it. Work backwards from the injection event. What network connections were made immediately before? What processes were created? Was there a suspicious DNS query 30 seconds before the injected process started? Was there a logon event from an unusual source IP around the same time?<\/p>\n<p>Common patterns you will see in real injection chains: a macro-enabled Office document spawning PowerShell (Event ID 1 with ParentImage=winword.exe and Image=powershell.exe), followed immediately by a DNS query for a domain that resolves to a known hosting provider (Event ID 22), followed by a network connection outbound on port 443 (Event ID 3), followed by a CreateRemoteThread event (Event ID 8) into a separate process. That four-event sequence, compressed into 10-30 seconds, is the macro-to-shellcode-to-injected-process chain in plain text.<\/p>\n<h3>Enrichment layer 4: filesystem artefacts Prefetch, Amcache, and MFT<\/h3>\n<p>After establishing what is in memory and correlating with event logs, the filesystem artefacts provide a third independent source of evidence that can confirm or add detail to the picture. Prefetch files tell you what ran and when, Amcache provides execution hashes for tools that have since been deleted, and the MFT timestamps tell you when files appeared and changed on disk.<\/p>\n<pre>-- VQL: Pull Prefetch execution history for processes related to the injection\n-- Prefetch files survive even after the executable is deleted\n\nSELECT\n Name AS PrefetchFile,\n -- Extract process name from prefetch filename (format: PROCESSNAME-HASH.pf)\n regex_transform(\n source=Name,\n map={\"^(.+)-[A-F0-9]{8}\\\\.pf$\": \"$1\"}\n ) AS ExecutableName,\n Mtime AS LastExecutionTime,\n Ctime AS FirstSeen,\n -- Read the prefetch binary for run count and timestamps\n -- (basic metadata only without full parsing)\n Size AS FileSize\n\nFROM glob(\n globs=\"C:\/Windows\/Prefetch\/*.pf\"\n)\n-- Filter to processes related to the injection investigation\nWHERE ExecutableName IN (\n \"POWERSHELL\",\n \"CMD\",\n \"WSCRIPT\",\n \"CSCRIPT\",\n \"MSHTA\",\n \"REGSVR32\",\n \"RUNDLL32\",\n -- Add the specific process name from your injection findings\n \"SVCHOST\" -- example\n)\nORDER BY LastExecutionTime DESC<\/pre>\n<pre>-- VQL: Amcache analysis for executed files with hash attribution\n-- Amcache persists SHA1 hashes of files that have executed\n-- even if those files have since been deleted\n\nSELECT\n BinProductVersion AS FileVersion,\n BinFileDescription AS FileDescription,\n FileId AS SHA1Hash,\n LongPathHash AS FilePath,\n LinkDate AS CompileTimestamp,\n -- The write time on the Amcache key approximates first execution time\n Modified AS ApproxFirstExecution\n\nFROM read_reg_key(\n globs=\"HKEY_LOCAL_MACHINE\/SYSTEM\/CurrentControlSet\/Control\/Session Manager\/AppCompatCache\"\n)\n\n-- Alternatively, parse the Amcache.hve directly for richer data\n-- VQL to invoke AmcacheParser via shell\nSELECT * FROM execve(argv=[\n \"C:\/tools\/AmcacheParser.exe\",\n \"-f\", \"C:\/Windows\/AppCompat\/Programs\/Amcache.hve\",\n \"--csv\", \"C:\/Windows\/Temp\/amcache_output\",\n \"-q\"\n])\nWHERE ReturnCode = 0<\/pre>\n<pre>-- VQL: MFT timeline analysis around the injection window\n-- The MFT records precise timestamps for every file creation, modification,\n-- and access revealing dropped tools, staged payloads, and cleanup attempts\n\nSELECT\n FullPath AS FilePath,\n Name AS FileName,\n -- NTFS records four timestamps per file (MACB)\n SI_Mtime AS Modified,\n SI_Atime AS Accessed,\n SI_Ctime AS MetadataChanged,\n SI_Btime AS Created,\n FileSize,\n IsDir,\n -- Flag executables and scripts specifically\n if(condition=Name =~ \"(?i)\\\\.(exe|dll|ps1|vbs|js|bat|cmd|hta|scr)$\",\n then=\"EXECUTABLE\", else=\"DATA\") AS FileType\n\nFROM parse_mft(\n -- Direct MFT access requires admin privileges\n filename=\"C:\/$MFT\",\n accessor=\"ntfs\"\n)\nWHERE\n -- Focus on the 2-hour window around the injection\n SI_Btime &gt;= window_start\n AND SI_Btime &lt;= window_end\n -- Focus on likely staging and tool-drop locations\n AND (\n FullPath =~ \"(?i)(\\\\Temp\\\\|\\\\AppData\\\\|\\\\ProgramData\\\\|\\\\Public\\\\)\"\n OR FullPath =~ \"(?i)(\\\\Downloads\\\\|\\\\Desktop\\\\)\"\n )\n AND NOT IsDir\nORDER BY SI_Btime ASC<\/pre>\n<p>The MFT timeline is particularly valuable for finding files that were created, used briefly, and deleted a pattern that is invisible in most log sources but leaves a permanent MFT record. Attackers who drop a tool, run it, and delete it often miss the fact that the MFT entry persists long after the file is gone. Cross-reference MFT creation timestamps with Prefetch execution timestamps for the same filename: if a file was created at 22:14:31 and Prefetch shows it ran at 22:14:35 with a single run count, it was a one-shot tool that was deleted after use. That four-second gap between file creation and first execution is the attacker staging and immediately running a tool.<\/p>\n<h3>Enrichment layer 5: network telemetry correlation<\/h3>\n<p>The injected process almost always makes network connections that is usually the whole point. Correlating the memory and event log findings with network telemetry from Zeek or your firewall gives you the C2 infrastructure details that complete the attack picture. You now know not just that a machine is compromised, but exactly where it is communicating with and what that communication looks like.<\/p>\n<pre>-- VQL: Pull all network connections made by the suspicious process\n-- Cross-reference with Sysmon network events and live connections\n\n-- Current network connections (live state)\nSELECT\n Pid,\n Name AS ProcessName,\n LocalAddress,\n LocalPort,\n RemoteAddress,\n RemotePort,\n Status AS ConnectionState,\n -- Flag obviously suspicious destinations\n if(condition=RemotePort IN (4444, 8080, 8443, 1337, 31337),\n then=\"COMMON_C2_PORT\", else=\"CHECK\") AS PortFlag\n\nFROM netstat()\nWHERE Pid = target_pid\n\nUNION\n\n-- Historical connections from Sysmon Event ID 3\nSELECT\n get(item=EventData, field=\"ProcessId\") AS Pid,\n get(item=EventData, field=\"Image\") AS ProcessName,\n get(item=EventData, field=\"SourceIp\") AS LocalAddress,\n get(item=EventData, field=\"SourcePort\") AS LocalPort,\n get(item=EventData, field=\"DestinationIp\") AS RemoteAddress,\n get(item=EventData, field=\"DestinationPort\") AS RemotePort,\n \"HISTORICAL\" AS ConnectionState,\n get(item=EventData, field=\"UtcTime\") AS EventTime\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.EventID.Value = 3\n AND get(item=EventData, field=\"ProcessId\") = format(format=\"%d\", args=target_pid)\nORDER BY EventTime DESC<\/pre>\n<pre>-- VQL: DNS query history for the suspicious process\n-- Sysmon Event ID 22 captures DNS queries with the requesting process\n-- This gives you the domain names the injected code tried to resolve\n\nSELECT\n System.TimeCreated.SystemTime AS QueryTime,\n get(item=EventData, field=\"QueryName\") AS Domain,\n get(item=EventData, field=\"QueryResults\") AS ResolvedIPs,\n get(item=EventData, field=\"Image\") AS RequestingProcess,\n -- Flag high-risk domain patterns\n if(condition=get(item=EventData, field=\"QueryName\") =~\n \"(?i)(\\.tk$|\\.xyz$|\\.top$|bit\\.ly|tinyurl|ngrok\\.io|pastebin)\",\n then=\"SUSPICIOUS_TLD\", else=\"CHECK\") AS DomainFlag\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.EventID.Value = 22\n AND get(item=EventData, field=\"Image\") =~ \"svchost\" -- match process from findings\n AND System.TimeCreated.SystemTime &gt;= window_start\nORDER BY QueryTime ASC<\/pre>\n<pre># Zeek correlation: match the C2 IP from memory analysis against network logs\n# Run this on your Zeek sensor after identifying the C2 IP from Volatility netscan\n\nC2_IP=\"198.51.100.45\"\nWINDOW_START=\"2024-04-02T22:00:00\"\nWINDOW_END=\"2024-04-02T23:00:00\"\n\n# All connections to the C2 IP in the incident window\ncat \/opt\/zeek\/logs\/current\/conn.log | \\\n python3 -c \"\nimport json, sys\nfrom datetime import datetime\n\nc2_ip = '$C2_IP'\nfor line in sys.stdin:\n try:\n rec = json.loads(line)\n if rec.get('id.resp_h') == c2_ip or rec.get('id.orig_h') == c2_ip:\n print(json.dumps({\n 'time': rec.get('ts'),\n 'src': rec.get('id.orig_h'),\n 'src_port': rec.get('id.orig_p'),\n 'dst': rec.get('id.resp_h'),\n 'dst_port': rec.get('id.resp_p'),\n 'duration': rec.get('duration'),\n 'bytes_out': rec.get('orig_bytes'),\n 'bytes_in': rec.get('resp_bytes'),\n 'protocol': rec.get('proto'),\n 'state': rec.get('conn_state')\n }, indent=2))\n except:\n pass\n\"\n\n# TLS session details if C2 uses HTTPS\ncat \/opt\/zeek\/logs\/current\/ssl.log | \\\n python3 -c \"\nimport json, sys\nfor line in sys.stdin:\n try:\n rec = json.loads(line)\n if rec.get('id.resp_h') == '$C2_IP':\n print(f\\\"JA3: {rec.get('ja3','none')} | SNI: {rec.get('server_name','none')} | Cert: {rec.get('subject','none')}\\\")\n except:\n pass\n\"<\/pre>\n<p>The combination of the destination IP from <code>netscan<\/code>, the JA3 fingerprint from Zeek&#8217;s ssl.log, and the DNS query history from Sysmon Event ID 22 gives you a complete C2 profile. Submit the JA3 hash to threat intelligence platforms if it matches a known Cobalt Strike or Meterpreter default, that narrows the toolset significantly. Take the destination IP and check it against passive DNS to find other domains that have resolved to the same infrastructure. Those other domains may be used in other campaigns targeting your sector.<\/p>\n<h3>Enrichment layer 6: lateral movement indicators<\/h3>\n<p>A compromised machine is rarely the end of the story. Once an attacker has injected code and established C2, the next phase is almost always lateral movement. Looking for signs that the injected process was used as a pivot point or that the credentials it accessed were used elsewhere rounds out the investigation and determines scope.<\/p>\n<pre>-- VQL: Detect lateral movement from the compromised host\n-- Look for authentication attempts to other systems originating from this host\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n get(item=EventData, field=\"TargetUserName\") AS AccountUsed,\n get(item=EventData, field=\"TargetServerName\") AS TargetSystem,\n get(item=EventData, field=\"LogonType\") AS LogonType,\n switch(\n condition=get(item=EventData, field=\"LogonType\") = \"3\",\n then=\"Network (SMB\/WMI\/DCOM)\",\n condition=get(item=EventData, field=\"LogonType\") = \"10\",\n then=\"Remote Interactive (RDP)\",\n condition=get(item=EventData, field=\"LogonType\") = \"9\",\n then=\"NewCredentials (runas \/netonly)\",\n else=format(format=\"Type %v\", args=get(item=EventData, field=\"LogonType\"))\n ) AS LogonDescription,\n get(item=EventData, field=\"SubjectUserName\") AS InitiatingAccount,\n -- LogonProcessName reveals the mechanism\n get(item=EventData, field=\"LogonProcessName\") AS AuthMechanism\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Security.evtx\"\n)\nWHERE System.EventID.Value = 4648 -- Explicit credential use (common in lateral movement)\n OR System.EventID.Value = 4624 -- Successful logon\n AND System.TimeCreated.SystemTime &gt;= window_start\n AND get(item=EventData, field=\"LogonType\") IN (\"3\", \"9\", \"10\") -- Remote logon types\nORDER BY EventTime ASC<\/pre>\n<pre>-- VQL: Look for PsExec, WMI exec, and other lateral movement tool signatures\n-- These leave characteristic artefacts even when the tools themselves are gone\n\n-- PsExec leaves a service named PSEXESVC\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n get(item=EventData, field=\"ServiceName\") AS ServiceName,\n get(item=EventData, field=\"ImagePath\") AS ServiceBinary,\n get(item=EventData, field=\"ServiceAccount\") AS RunAsAccount,\n \"SERVICE_INSTALL\" AS Indicator\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/System.evtx\"\n)\nWHERE System.EventID.Value = 7045\n AND System.TimeCreated.SystemTime &gt;= window_start\n\nUNION\n\n-- WMI remote execution leaves Event 4688 with WmiPrvSE.exe as parent\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n get(item=EventData, field=\"NewProcessName\") AS ServiceName,\n get(item=EventData, field=\"CommandLine\") AS ServiceBinary,\n get(item=EventData, field=\"SubjectUserName\") AS RunAsAccount,\n \"WMI_EXEC\" AS Indicator\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Security.evtx\"\n)\nWHERE System.EventID.Value = 4688\n AND get(item=EventData, field=\"ParentProcessName\") =~ \"WmiPrvSE\"\n AND System.TimeCreated.SystemTime &gt;= window_start\n\nORDER BY EventTime ASC<\/pre>\n<h3>Putting it all together: the complete investigation playbook<\/h3>\n<p>Running all of these queries individually is manageable during a focused investigation, but having them organised into a structured playbook makes the process repeatable and ensures nothing gets missed under pressure. The following VQL artefact wraps the entire multi-layer investigation into a single deployable workflow.<\/p>\n<pre>-- Complete investigation artefact definition\n-- Save as Custom.Investigate.ProcessInjection.yaml\n\nname: Custom.Investigate.ProcessInjection\ndescription: |\n Complete multi-layer investigation for a suspected process injection finding.\n Correlates Velociraptor VAD findings with:\n - Process ancestry and creation context (Sysmon Event 1)\n - Security event timeline (logons, privilege use, process creation)\n - Sysmon event timeline (network, files, registry, pipes)\n - Filesystem artefacts (Prefetch, MFT)\n - Current and historical network connections\n - Lateral movement indicators\n\n Run this against a specific host after the initial hunting query\n identifies a suspicious PID.\n\nauthor: justruss\ntype: CLIENT\nparameters:\n - name: SuspiciousPid\n description: The PID identified as suspicious by the injection hunt\n type: int\n default: \"0\"\n - name: InvestigationWindowMinutes\n description: Minutes before and after process creation to include in timeline\n type: int\n default: \"60\"\n\nsources:\n - name: ProcessAncestry\n description: Full process creation chain for the suspicious process\n query: |\n SELECT\n ProcessId AS PID,\n ParentProcessId AS ParentPID,\n Name AS ProcessName,\n CommandLine,\n Username AS RunningAs,\n CreateTime,\n Exe AS BinaryPath,\n hash(path=Exe, hashselect=\"SHA256\") AS BinarySHA256,\n hash(path=Exe, hashselect=\"SHA1\") AS BinarySHA1\n FROM pslist()\n WHERE ProcessId = SuspiciousPid\n OR ProcessId IN (\n SELECT ParentProcessId FROM pslist()\n WHERE ProcessId = SuspiciousPid\n )\n\n - name: VADMemoryRegions\n description: All executable VAD regions for the suspicious process\n query: |\n SELECT\n Pid,\n Name AS ProcessName,\n VAD.Start AS RegionStart,\n format(format=\"0x%016X\", args=VAD.Start) AS RegionStartHex,\n VAD.End AS RegionEnd,\n VAD.Protection AS Protection,\n VAD.Type AS VADType,\n VAD.FileObject AS BackingFile,\n format(format=\"%d KB\", args=[(VAD.End - VAD.Start) \/ 1024]) AS RegionSizeKB,\n entropy(string=read_file(\n accessor=\"process\",\n filename=format(format=\"\/%d\/%d\", args=[Pid, VAD.Start]),\n length=4096\n )) AS EntropyScore,\n if(condition=read_file(\n accessor=\"process\",\n filename=format(format=\"\/%d\/%d\", args=[Pid, VAD.Start]),\n length=2\n ) = \"MZ\",\n then=\"YES\", else=\"NO\") AS HasPEHeader,\n if(condition=VAD.Protection = \"EXECUTE_READ_WRITE\",\n then=\"CRITICAL\", else=\"HIGH\") AS RiskLevel\n FROM foreach(\n row={SELECT Pid, Name FROM pslist() WHERE Pid = SuspiciousPid},\n query={\n SELECT Pid, Name, VAD FROM vad(pid=Pid)\n WHERE VAD.Protection =~ \"EXECUTE\"\n AND VAD.Type = \"PRIVATE\"\n AND (VAD.FileObject = NULL OR VAD.FileObject = \"\")\n AND (VAD.End - VAD.Start) &gt;= 4096\n }\n )\n ORDER BY EntropyScore DESC\n\n - name: ActiveThreads\n description: Threads currently executing in suspicious memory regions\n query: |\n LET anon_vads = SELECT Pid, VAD.Start AS VStart, VAD.End AS VEnd\n FROM foreach(\n row={SELECT Pid FROM pslist() WHERE Pid = SuspiciousPid},\n query={SELECT Pid, VAD FROM vad(pid=Pid)\n WHERE VAD.Protection =~ \"EXECUTE\"\n AND VAD.Type = \"PRIVATE\"\n AND (VAD.FileObject = NULL OR VAD.FileObject = \"\")\n AND (VAD.End - VAD.Start) &gt;= 4096}\n )\n\n SELECT\n t.Pid, t.Name AS ProcessName, t.ThreadId,\n format(format=\"0x%016X\", args=t.StartAddress) AS StartAddress,\n format(format=\"0x%016X\", args=v.VStart) AS VADStart,\n \"THREAD EXECUTING IN INJECTED REGION\" AS Finding,\n \"CRITICAL\" AS Severity\n FROM foreach(\n row={SELECT Pid, Name FROM pslist() WHERE Pid = SuspiciousPid},\n query={SELECT Pid, Name, ThreadId, StartAddress FROM threads(pid=Pid)\n WHERE StartAddress &gt; 0x1000}\n ) AS t\n JOIN anon_vads AS v ON (\n t.Pid = v.Pid\n AND t.StartAddress &gt;= v.VStart\n AND t.StartAddress &lt; v.VEnd\n )\n\n - name: NetworkConnections\n description: Current and recent network connections from suspicious process\n query: |\n SELECT\n Pid,\n Name AS ProcessName,\n LocalAddress,\n LocalPort,\n RemoteAddress,\n RemotePort,\n Status,\n now() AS CheckTime\n FROM netstat()\n WHERE Pid = SuspiciousPid\n\n - name: RecentFileSystem\n description: Files created or modified around the injection window\n query: |\n LET proc_info = SELECT CreateTime FROM pslist() WHERE Pid = SuspiciousPid\n LET proc_time = proc_info[0].CreateTime\n LET win_start = proc_time - InvestigationWindowMinutes * 60\n LET win_end = proc_time + InvestigationWindowMinutes * 60\n\n SELECT\n FullPath AS FilePath,\n Name AS FileName,\n Mtime AS LastModified,\n Ctime AS Created,\n Size AS FileSizeBytes,\n if(condition=Name =~ \"(?i)\\\\.(exe|dll|ps1|vbs|js|bat|cmd|hta)$\",\n then=\"EXECUTABLE\", else=\"DATA\") AS FileType\n FROM glob(globs=[\n \"C:\/Users\/*\/AppData\/Local\/Temp\/**\",\n \"C:\/Users\/*\/AppData\/Roaming\/**10\",\n \"C:\/Windows\/Temp\/**\",\n \"C:\/ProgramData\/**5\"\n ])\n WHERE Mtime &gt;= win_start\n AND Mtime &lt;= win_end\n AND NOT IsDir\n ORDER BY Mtime ASC\n\n - name: PrefetchEvidence\n description: Prefetch execution records for related processes\n query: |\n SELECT\n Name AS PrefetchFile,\n Mtime AS LastExecutionTime,\n Ctime AS PrefetchCreated,\n Size\n FROM glob(globs=\"C:\/Windows\/Prefetch\/*.pf\")\n ORDER BY Mtime DESC\n LIMIT 50<\/pre>\n<h3>Reading the complete investigation picture<\/h3>\n<p>When all six enrichment layers are combined, what you end up with is not just a list of suspicious memory regions it is a complete narrative of an intrusion. Walk through a realistic example of how to read the combined output.<\/p>\n<p>The VAD query returns a 512KB RWX anonymous region in svchost.exe (PID 4892) with entropy of 7.3 and an MZ header. That is your starting point: injected PE file, currently in memory, high probability of being active malware.<\/p>\n<p>The process ancestry query shows svchost.exe was created at 22:14:35 by services.exe, which is normal. But the command line is unusual it was launched with <code>-k netsvcs -p -s Schedule<\/code> rather than the typical short form. Cross-referencing with Sysmon Event ID 1 for the same PID shows that 22 seconds earlier at 22:14:13, a PowerShell process (PID 3842) with a base64-encoded command ran and terminated. PID 3842&#8217;s parent was winword.exe. That is the delivery chain: Office macro to PowerShell to shellcode injection into svchost.<\/p>\n<p>The Sysmon timeline around 22:14:00 shows Event ID 22 (DNS query) at 22:14:08 for a domain that did not exist in the previous 30 days of DNS cache, followed by Event ID 3 (network connection) at 22:14:12 to a residential-range IP on port 443. Then Event ID 8 (CreateRemoteThread) at 22:14:34, source PID 3842, target PID 4892 this is the exact moment of injection. The PowerShell process injected into svchost.<\/p>\n<p>The MFT query shows a file named <code>update.ps1<\/code> created in <code>C:\\Users\\jsmith\\AppData\\Local\\Temp\\<\/code> at 22:14:09 with a size of 4,847 bytes, and then a modification time identical to its creation time. It was written once and never modified a staging file. Prefetch confirms it ran at 22:14:11 with a run count of 1. The file is gone from disk but both the MFT entry and Prefetch prove its existence and execution.<\/p>\n<p>The network connection query shows svchost.exe (PID 4892) has an established outbound connection to 203.0.113.45:443 that has been open for 47 minutes. Zeek&#8217;s ssl.log shows the JA3 hash for that connection is a0e9f5d64349fb13191bc781f81f42e1 the documented Cobalt Strike default JA3 hash.<\/p>\n<p>The lateral movement query shows that at 22:31:06 17 minutes after injection Event ID 4648 fired showing <code>jsmith<\/code>&#8216;s credentials used to authenticate to <code>FINSERVER01<\/code> via a network logon. The scope just expanded from one workstation to a second host.<\/p>\n<p>That is a complete incident in six data sources, stitched together through correlated timestamps and process identifiers. The initial Velociraptor VAD query found a thread. The enrichment queries built an entire story around it.<\/p>\n<h3>Enrichment layer 1: correlating VAD findings with Sysmon process creation history<\/h3>\n<p>Finding an anonymous executable region in a process is a strong indicator, but it answers only part of the question. You know something is in the memory of process X. You do not yet know how it got there, when it appeared, or what chain of events preceded it. Windows event logs and Sysmon telemetry fill that gap, giving you a timeline of everything that happened to that process before and after the injection.<\/p>\n<p>The first enrichment step is to take the PID and process name from your Velociraptor findings and pull the full process creation chain from Sysmon Event ID 1. This tells you what created the process you are investigating, what command line was used, and how far back the process tree goes. An injected svchost.exe that was spawned by services.exe with normal arguments is a very different finding from an injected svchost.exe that was spawned by a PowerShell process with an encoded command argument.<\/p>\n<pre>-- VQL: Enrich suspicious process with its creation history from Sysmon EventLog\n-- Replace TargetPid with the PID from your injection hunt results\n\nLET target_pid = 4892 -- replace with suspicious PID from hunt results\n\n-- Step 1: Get the full ancestry chain for the suspicious process\nSELECT\n ProcessId AS PID,\n ParentProcessId AS ParentPID,\n Name AS ProcessName,\n CommandLine,\n User,\n CreateTime,\n Exe AS ExecutablePath,\n -- Hash the executable to check reputation\n hash(path=Exe, hashselect=\"SHA256\") AS ExecutableHash\n\nFROM pslist()\nWHERE ProcessId = target_pid\n OR ProcessId IN (\n -- Also pull the parent and grandparent\n SELECT ParentProcessId FROM pslist() WHERE ProcessId = target_pid\n )<\/pre>\n<pre>-- VQL: Pull Sysmon Event ID 1 records for process creation context\n-- This gives you the full command line and parent details at process start time\n-- even if the process has been running for hours\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n EventData.ProcessId AS PID,\n EventData.ParentProcessId AS ParentPID,\n EventData.Image AS ProcessImage,\n EventData.CommandLine AS CommandLine,\n EventData.ParentImage AS ParentImage,\n EventData.ParentCommandLine AS ParentCommandLine,\n EventData.User AS RunningAsUser,\n EventData.IntegrityLevel AS IntegrityLevel,\n EventData.Hashes AS Hashes,\n -- Current directory at time of launch is often revealing\n EventData.CurrentDirectory AS WorkingDirectory\n\nFROM watch_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.EventID.Value = 1\n AND (\n EventData.ProcessId = format(format=\"%d\", args=target_pid)\n OR EventData.ParentProcessId = format(format=\"%d\", args=target_pid)\n )\nORDER BY EventTime ASC<\/pre>\n<p>Pay careful attention to the <code>IntegrityLevel<\/code> field in the Sysmon Event ID 1 record. A process running at High or System integrity that was spawned from a process at Medium integrity indicates a privilege escalation occurred somewhere in the chain. A process running as SYSTEM that was spawned from a user-context process through an unusual parent (WmiPrvSE.exe, msdtc.exe, or similar) indicates lateral movement or exploitation of a privileged service.<\/p>\n<p>The <code>Hashes<\/code> field in Sysmon Event ID 1 contains the SHA256 of the executable at the time it launched. Take that hash and check it against threat intelligence immediately. If the binary has been renamed to disguise itself, the hash will still identify the real malware family even if the filename looks legitimate.<\/p>\n<h3>Enrichment layer 2: memory image acquisition and Volatility cross-analysis<\/h3>\n<p>Velociraptor can acquire a full memory image from a live remote system and make it available for offline analysis with Volatility. This is the bridge between the live query capabilities of Velociraptor and the deep structural analysis capabilities of Volatility. Where Velociraptor gives you fleet-wide fast triage, Volatility gives you the forensic depth to answer definitively what the injected code is, what it has been doing, and what it has communicated with.<\/p>\n<pre>-- VQL: Acquire full memory image from the target machine\n-- Run this as a client artefact against the specific host that showed injection\n\nSELECT * FROM Artifact.Windows.Memory.Acquisition(\n -- Output path on the target machine (temporary staging)\n destination=\"C:\/Windows\/Temp\/mem_acquisition.raw\",\n -- Also acquire the pagefile for complete coverage\n also_upload_pagefile=TRUE\n)<\/pre>\n<p>Once the acquisition completes, Velociraptor uploads the image to the server and you can download it for Volatility analysis. The acquisition typically takes 2-10 minutes depending on RAM size. Do not wait for it to complete before starting other enrichment steps, the Velociraptor VAD queries already captured the volatile data you need for triage, so the memory image is for deep analysis that can run in parallel.<\/p>\n<pre># Volatility 3 analysis workflow against the acquired image\n# Replace memory.raw with your downloaded image filename\n\n# Step 1: Confirm the image profile and get baseline info\nvol -f memory.raw windows.info\n\n# Step 2: Cross-reference the suspicious PID from Velociraptor\n# Get the full process details including PPID chain\nvol -f memory.raw windows.pstree | grep -A2 -B2 \"4892\"\n\n# Step 3: Run malfind against the specific suspicious process\n# malfind finds executable regions with no disk backing - same concept as our VQL\nvol -f memory.raw windows.malfind --pid 4892\n\n# Example malfind output for a Cobalt Strike injected process:\n# Process: svchost.exe Pid: 4892 Address: 0x1d0000\n# Vad Tag: VadS Protection: PAGE_EXECUTE_READWRITE\n# Hexdump:\n# 4d 5a 90 00 03 00 00 00 MZ......\n# This confirms the Velociraptor VAD finding with an independent second source\n\n# Step 4: Dump the suspicious region for further analysis\nvol -f memory.raw windows.dumpfiles --virtaddr 0x1d0000 --pid 4892 -o \/tmp\/dumped\/\n\n# Step 5: Analyse the dumped region\nfile \/tmp\/dumped\/file.0x4892.0x1d0000.img\nstrings -a -n 8 \/tmp\/dumped\/file.0x4892.0x1d0000.img | grep -iE \"(http|https|192\\.|10\\.|beacon|sleep|checkin)\"\nsha256sum \/tmp\/dumped\/file.0x4892.0x1d0000.img<\/pre>\n<pre># Step 6: Extract network connections from memory for C2 identification\nvol -f memory.raw windows.netscan | grep \"4892\"\n\n# Sample output:\n# TCPv4 192.168.1.105 54321 198.51.100.45 443 ESTABLISHED 4892 svchost.exe\n# This is the C2 connection - 198.51.100.45:443 is the attacker server\n\n# Step 7: Check for credential material in the injected process\n# If the injection target was lsass.exe or if the injected code accessed lsass\nvol -f memory.raw windows.lsadump | head -50\n\n# Step 8: Extract the full module list to find reflectively loaded components\nvol -f memory.raw windows.dlllist --pid 4892\n# Cross-reference against what Velociraptor reported in the modules() query\n# Any discrepancy = something was loaded that the OS does not officially track<\/pre>\n<h3>Enrichment layer 3: correlating with Windows event logs for timeline reconstruction<\/h3>\n<p>The memory analysis and Velociraptor VAD findings tell you what is in memory now. The Windows event logs tell you the story of how it got there. Building a complete timeline from event logs around the injection event turns an isolated finding into a full attack narrative that supports incident response decisions.<\/p>\n<pre>-- VQL: Pull all security-relevant events for the suspicious process\n-- and the 30 minutes before and after it was created\n-- This builds a timeline around the injection event\n\nLET process_create_time = timestamp(string=\"2024-04-02T22:14:00Z\") -- from Sysmon Event 1\nLET window_start = process_create_time - 1800 -- 30 minutes before\nLET window_end = process_create_time + 1800 -- 30 minutes after\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n System.EventID.Value AS EventID,\n -- Map event IDs to human-readable names\n switch(\n condition=System.EventID.Value = 4624, then=\"Logon Success\",\n condition=System.EventID.Value = 4625, then=\"Logon Failure\",\n condition=System.EventID.Value = 4648, then=\"Explicit Credentials Used\",\n condition=System.EventID.Value = 4688, then=\"Process Created\",\n condition=System.EventID.Value = 4689, then=\"Process Terminated\",\n condition=System.EventID.Value = 4698, then=\"Scheduled Task Created\",\n condition=System.EventID.Value = 7045, then=\"Service Installed\",\n condition=System.EventID.Value = 4720, then=\"User Account Created\",\n condition=System.EventID.Value = 4732, then=\"Member Added to Local Group\",\n else=format(format=\"Event %d\", args=System.EventID.Value)\n ) AS EventName,\n -- Pull the most relevant fields\n get(item=EventData, field=\"SubjectUserName\") AS Actor,\n get(item=EventData, field=\"TargetUserName\") AS Target,\n get(item=EventData, field=\"ProcessName\") AS ProcessName,\n get(item=EventData, field=\"CommandLine\") AS CommandLine,\n get(item=EventData, field=\"IpAddress\") AS SourceIP,\n get(item=EventData, field=\"WorkstationName\") AS SourceHost\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Security.evtx\"\n)\nWHERE System.TimeCreated.SystemTime &gt;= window_start\n AND System.TimeCreated.SystemTime &lt;= window_end\n AND System.EventID.Value IN (4624, 4625, 4648, 4688, 4689, 4698, 7045, 4720, 4732)\nORDER BY EventTime ASC<\/pre>\n<pre>-- VQL: Sysmon timeline for the injection window\n-- Covers network connections, file operations, registry changes, and pipe creation\n-- All correlated to the same time window around the injection\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n System.EventID.Value AS SysmonEventID,\n switch(\n condition=System.EventID.Value = 1, then=\"Process Created\",\n condition=System.EventID.Value = 3, then=\"Network Connection\",\n condition=System.EventID.Value = 5, then=\"Process Terminated\",\n condition=System.EventID.Value = 6, then=\"Driver Loaded\",\n condition=System.EventID.Value = 7, then=\"Image Loaded\",\n condition=System.EventID.Value = 8, then=\"CreateRemoteThread\",\n condition=System.EventID.Value = 10, then=\"ProcessAccess (LSASS\/injection)\",\n condition=System.EventID.Value = 11, then=\"File Created\",\n condition=System.EventID.Value = 12, then=\"Registry Key Created\/Deleted\",\n condition=System.EventID.Value = 13, then=\"Registry Value Set\",\n condition=System.EventID.Value = 17, then=\"Pipe Created\",\n condition=System.EventID.Value = 18, then=\"Pipe Connected\",\n condition=System.EventID.Value = 22, then=\"DNS Query\",\n condition=System.EventID.Value = 25, then=\"Process Tampering\",\n else=format(format=\"Sysmon %d\", args=System.EventID.Value)\n ) AS EventType,\n get(item=EventData, field=\"Image\") AS ProcessImage,\n get(item=EventData, field=\"TargetImage\") AS TargetProcess,\n get(item=EventData, field=\"DestinationIp\") AS DestIP,\n get(item=EventData, field=\"DestinationPort\") AS DestPort,\n get(item=EventData, field=\"QueryName\") AS DNSQuery,\n get(item=EventData, field=\"TargetFilename\") AS FilePath,\n get(item=EventData, field=\"TargetObject\") AS RegistryKey,\n get(item=EventData, field=\"PipeName\") AS PipeName,\n get(item=EventData, field=\"GrantedAccess\") AS AccessMask\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.TimeCreated.SystemTime &gt;= window_start\n AND System.TimeCreated.SystemTime &lt;= window_end\nORDER BY EventTime ASC<\/pre>\n<p>When you view this combined timeline, you are looking for the moment the injection happened and everything that preceded it. Work backwards from the injection event. What network connections were made immediately before? What processes were created? Was there a suspicious DNS query 30 seconds before the injected process started? Was there a logon event from an unusual source IP around the same time?<\/p>\n<p>Common patterns you will see in real injection chains: a macro-enabled Office document spawning PowerShell (Event ID 1 with ParentImage=winword.exe and Image=powershell.exe), followed immediately by a DNS query for a domain that resolves to a known hosting provider (Event ID 22), followed by a network connection outbound on port 443 (Event ID 3), followed by a CreateRemoteThread event (Event ID 8) into a separate process. That four-event sequence, compressed into 10-30 seconds, is the macro-to-shellcode-to-injected-process chain in plain text.<\/p>\n<h3>Enrichment layer 4: filesystem artefacts, Prefetch, Amcache, and MFT<\/h3>\n<p>After establishing what is in memory and correlating with event logs, the filesystem artefacts provide a third independent source of evidence that can confirm or add detail to the picture. Prefetch files tell you what ran and when, Amcache provides execution hashes for tools that have since been deleted, and the MFT timestamps tell you when files appeared and changed on disk.<\/p>\n<pre>-- VQL: Pull Prefetch execution history for processes related to the injection\n-- Prefetch files survive even after the executable is deleted\n\nSELECT\n Name AS PrefetchFile,\n -- Extract process name from prefetch filename (format: PROCESSNAME-HASH.pf)\n regex_transform(\n source=Name,\n map={\"^(.+)-[A-F0-9]{8}\\\\.pf$\": \"$1\"}\n ) AS ExecutableName,\n Mtime AS LastExecutionTime,\n Ctime AS FirstSeen,\n -- Read the prefetch binary for run count and timestamps\n -- (basic metadata only without full parsing)\n Size AS FileSize\n\nFROM glob(\n globs=\"C:\/Windows\/Prefetch\/*.pf\"\n)\n-- Filter to processes related to the injection investigation\nWHERE ExecutableName IN (\n \"POWERSHELL\",\n \"CMD\",\n \"WSCRIPT\",\n \"CSCRIPT\",\n \"MSHTA\",\n \"REGSVR32\",\n \"RUNDLL32\",\n -- Add the specific process name from your injection findings\n \"SVCHOST\" -- example\n)\nORDER BY LastExecutionTime DESC<\/pre>\n<pre>-- VQL: Amcache analysis for executed files with hash attribution\n-- Amcache persists SHA1 hashes of files that have executed\n-- even if those files have since been deleted\n\nSELECT\n BinProductVersion AS FileVersion,\n BinFileDescription AS FileDescription,\n FileId AS SHA1Hash,\n LongPathHash AS FilePath,\n LinkDate AS CompileTimestamp,\n -- The write time on the Amcache key approximates first execution time\n Modified AS ApproxFirstExecution\n\nFROM read_reg_key(\n globs=\"HKEY_LOCAL_MACHINE\/SYSTEM\/CurrentControlSet\/Control\/Session Manager\/AppCompatCache\"\n)\n\n-- Alternatively, parse the Amcache.hve directly for richer data\n-- VQL to invoke AmcacheParser via shell\nSELECT * FROM execve(argv=[\n \"C:\/tools\/AmcacheParser.exe\",\n \"-f\", \"C:\/Windows\/AppCompat\/Programs\/Amcache.hve\",\n \"--csv\", \"C:\/Windows\/Temp\/amcache_output\",\n \"-q\"\n])\nWHERE ReturnCode = 0<\/pre>\n<pre>-- VQL: MFT timeline analysis around the injection window\n-- The MFT records precise timestamps for every file creation, modification,\n-- and access -- revealing dropped tools, staged payloads, and cleanup attempts\n\nSELECT\n FullPath AS FilePath,\n Name AS FileName,\n -- NTFS records four timestamps per file (MACB)\n SI_Mtime AS Modified,\n SI_Atime AS Accessed,\n SI_Ctime AS MetadataChanged,\n SI_Btime AS Created,\n FileSize,\n IsDir,\n -- Flag executables and scripts specifically\n if(condition=Name =~ \"(?i)\\\\.(exe|dll|ps1|vbs|js|bat|cmd|hta|scr)$\",\n then=\"EXECUTABLE\", else=\"DATA\") AS FileType\n\nFROM parse_mft(\n -- Direct MFT access requires admin privileges\n filename=\"C:\/$MFT\",\n accessor=\"ntfs\"\n)\nWHERE\n -- Focus on the 2-hour window around the injection\n SI_Btime &gt;= window_start\n AND SI_Btime &lt;= window_end\n -- Focus on likely staging and tool-drop locations\n AND (\n FullPath =~ \"(?i)(\\\\Temp\\\\|\\\\AppData\\\\|\\\\ProgramData\\\\|\\\\Public\\\\)\"\n OR FullPath =~ \"(?i)(\\\\Downloads\\\\|\\\\Desktop\\\\)\"\n )\n AND NOT IsDir\nORDER BY SI_Btime ASC<\/pre>\n<p>The MFT timeline is particularly valuable for finding files that were created, used briefly, and deleted, a pattern that is invisible in most log sources but leaves a permanent MFT record. Attackers who drop a tool, run it, and delete it often miss the fact that the MFT entry persists long after the file is gone. Cross-reference MFT creation timestamps with Prefetch execution timestamps for the same filename: if a file was created at 22:14:31 and Prefetch shows it ran at 22:14:35 with a single run count, it was a one-shot tool that was deleted after use. That four-second gap between file creation and first execution is the attacker staging and immediately running a tool.<\/p>\n<h3>Enrichment layer 5: network telemetry correlation<\/h3>\n<p>The injected process almost always makes network connections, that is usually the whole point. Correlating the memory and event log findings with network telemetry from Zeek or your firewall gives you the C2 infrastructure details that complete the attack picture. You now know not just that a machine is compromised, but exactly where it is communicating with and what that communication looks like.<\/p>\n<pre>-- VQL: Pull all network connections made by the suspicious process\n-- Cross-reference with Sysmon network events and live connections\n\n-- Current network connections (live state)\nSELECT\n Pid,\n Name AS ProcessName,\n LocalAddress,\n LocalPort,\n RemoteAddress,\n RemotePort,\n Status AS ConnectionState,\n -- Flag obviously suspicious destinations\n if(condition=RemotePort IN (4444, 8080, 8443, 1337, 31337),\n then=\"COMMON_C2_PORT\", else=\"CHECK\") AS PortFlag\n\nFROM netstat()\nWHERE Pid = target_pid\n\nUNION\n\n-- Historical connections from Sysmon Event ID 3\nSELECT\n get(item=EventData, field=\"ProcessId\") AS Pid,\n get(item=EventData, field=\"Image\") AS ProcessName,\n get(item=EventData, field=\"SourceIp\") AS LocalAddress,\n get(item=EventData, field=\"SourcePort\") AS LocalPort,\n get(item=EventData, field=\"DestinationIp\") AS RemoteAddress,\n get(item=EventData, field=\"DestinationPort\") AS RemotePort,\n \"HISTORICAL\" AS ConnectionState,\n get(item=EventData, field=\"UtcTime\") AS EventTime\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.EventID.Value = 3\n AND get(item=EventData, field=\"ProcessId\") = format(format=\"%d\", args=target_pid)\nORDER BY EventTime DESC<\/pre>\n<pre>-- VQL: DNS query history for the suspicious process\n-- Sysmon Event ID 22 captures DNS queries with the requesting process\n-- This gives you the domain names the injected code tried to resolve\n\nSELECT\n System.TimeCreated.SystemTime AS QueryTime,\n get(item=EventData, field=\"QueryName\") AS Domain,\n get(item=EventData, field=\"QueryResults\") AS ResolvedIPs,\n get(item=EventData, field=\"Image\") AS RequestingProcess,\n -- Flag high-risk domain patterns\n if(condition=get(item=EventData, field=\"QueryName\") =~\n \"(?i)(\\.tk$|\\.xyz$|\\.top$|bit\\.ly|tinyurl|ngrok\\.io|pastebin)\",\n then=\"SUSPICIOUS_TLD\", else=\"CHECK\") AS DomainFlag\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Microsoft-Windows-Sysmon%4Operational.evtx\"\n)\nWHERE System.EventID.Value = 22\n AND get(item=EventData, field=\"Image\") =~ \"svchost\" -- match process from findings\n AND System.TimeCreated.SystemTime &gt;= window_start\nORDER BY QueryTime ASC<\/pre>\n<pre># Zeek correlation: match the C2 IP from memory analysis against network logs\n# Run this on your Zeek sensor after identifying the C2 IP from Volatility netscan\n\nC2_IP=\"198.51.100.45\"\nWINDOW_START=\"2024-04-02T22:00:00\"\nWINDOW_END=\"2024-04-02T23:00:00\"\n\n# All connections to the C2 IP in the incident window\ncat \/opt\/zeek\/logs\/current\/conn.log | \\\n python3 -c \"\nimport json, sys\nfrom datetime import datetime\n\nc2_ip = '$C2_IP'\nfor line in sys.stdin:\n try:\n rec = json.loads(line)\n if rec.get('id.resp_h') == c2_ip or rec.get('id.orig_h') == c2_ip:\n print(json.dumps({\n 'time': rec.get('ts'),\n 'src': rec.get('id.orig_h'),\n 'src_port': rec.get('id.orig_p'),\n 'dst': rec.get('id.resp_h'),\n 'dst_port': rec.get('id.resp_p'),\n 'duration': rec.get('duration'),\n 'bytes_out': rec.get('orig_bytes'),\n 'bytes_in': rec.get('resp_bytes'),\n 'protocol': rec.get('proto'),\n 'state': rec.get('conn_state')\n }, indent=2))\n except:\n pass\n\"\n\n# TLS session details if C2 uses HTTPS\ncat \/opt\/zeek\/logs\/current\/ssl.log | \\\n python3 -c \"\nimport json, sys\nfor line in sys.stdin:\n try:\n rec = json.loads(line)\n if rec.get('id.resp_h') == '$C2_IP':\n print(f\\\"JA3: {rec.get('ja3','none')} | SNI: {rec.get('server_name','none')} | Cert: {rec.get('subject','none')}\\\")\n except:\n pass\n\"<\/pre>\n<p>The combination of the destination IP from <code>netscan<\/code>, the JA3 fingerprint from Zeek&#8217;s ssl.log, and the DNS query history from Sysmon Event ID 22 gives you a complete C2 profile. Submit the JA3 hash to threat intelligence platforms, if it matches a known Cobalt Strike or Meterpreter default, that narrows the toolset significantly. Take the destination IP and check it against passive DNS to find other domains that have resolved to the same infrastructure. Those other domains may be used in other campaigns targeting your sector.<\/p>\n<h3>Enrichment layer 6: lateral movement indicators<\/h3>\n<p>A compromised machine is rarely the end of the story. Once an attacker has injected code and established C2, the next phase is almost always lateral movement. Looking for signs that the injected process was used as a pivot point, or that the credentials it accessed were used elsewhere, rounds out the investigation and determines scope.<\/p>\n<pre>-- VQL: Detect lateral movement from the compromised host\n-- Look for authentication attempts to other systems originating from this host\n\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n get(item=EventData, field=\"TargetUserName\") AS AccountUsed,\n get(item=EventData, field=\"TargetServerName\") AS TargetSystem,\n get(item=EventData, field=\"LogonType\") AS LogonType,\n switch(\n condition=get(item=EventData, field=\"LogonType\") = \"3\",\n then=\"Network (SMB\/WMI\/DCOM)\",\n condition=get(item=EventData, field=\"LogonType\") = \"10\",\n then=\"Remote Interactive (RDP)\",\n condition=get(item=EventData, field=\"LogonType\") = \"9\",\n then=\"NewCredentials (runas \/netonly)\",\n else=format(format=\"Type %v\", args=get(item=EventData, field=\"LogonType\"))\n ) AS LogonDescription,\n get(item=EventData, field=\"SubjectUserName\") AS InitiatingAccount,\n -- LogonProcessName reveals the mechanism\n get(item=EventData, field=\"LogonProcessName\") AS AuthMechanism\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Security.evtx\"\n)\nWHERE System.EventID.Value = 4648 -- Explicit credential use (common in lateral movement)\n OR System.EventID.Value = 4624 -- Successful logon\n AND System.TimeCreated.SystemTime &gt;= window_start\n AND get(item=EventData, field=\"LogonType\") IN (\"3\", \"9\", \"10\") -- Remote logon types\nORDER BY EventTime ASC<\/pre>\n<pre>-- VQL: Look for PsExec, WMI exec, and other lateral movement tool signatures\n-- These leave characteristic artefacts even when the tools themselves are gone\n\n-- PsExec leaves a service named PSEXESVC\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n get(item=EventData, field=\"ServiceName\") AS ServiceName,\n get(item=EventData, field=\"ImagePath\") AS ServiceBinary,\n get(item=EventData, field=\"ServiceAccount\") AS RunAsAccount,\n \"SERVICE_INSTALL\" AS Indicator\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/System.evtx\"\n)\nWHERE System.EventID.Value = 7045\n AND System.TimeCreated.SystemTime &gt;= window_start\n\nUNION\n\n-- WMI remote execution leaves Event 4688 with WmiPrvSE.exe as parent\nSELECT\n System.TimeCreated.SystemTime AS EventTime,\n get(item=EventData, field=\"NewProcessName\") AS ServiceName,\n get(item=EventData, field=\"CommandLine\") AS ServiceBinary,\n get(item=EventData, field=\"SubjectUserName\") AS RunAsAccount,\n \"WMI_EXEC\" AS Indicator\n\nFROM parse_evtx(\n filename=\"C:\/Windows\/System32\/winevt\/Logs\/Security.evtx\"\n)\nWHERE System.EventID.Value = 4688\n AND get(item=EventData, field=\"ParentProcessName\") =~ \"WmiPrvSE\"\n AND System.TimeCreated.SystemTime &gt;= window_start\n\nORDER BY EventTime ASC<\/pre>\n<h3>Putting it all together: the complete investigation playbook<\/h3>\n<p>Running all of these queries individually is manageable during a focused investigation, but having them organised into a structured playbook makes the process repeatable and ensures nothing gets missed under pressure. The following VQL artefact wraps the entire multi-layer investigation into a single deployable workflow.<\/p>\n<pre>-- Complete investigation artefact definition\n-- Save as Custom.Investigate.ProcessInjection.yaml\n\nname: Custom.Investigate.ProcessInjection\ndescription: |\n Complete multi-layer investigation for a suspected process injection finding.\n Correlates Velociraptor VAD findings with:\n - Process ancestry and creation context (Sysmon Event 1)\n - Security event timeline (logons, privilege use, process creation)\n - Sysmon event timeline (network, files, registry, pipes)\n - Filesystem artefacts (Prefetch, MFT)\n - Current and historical network connections\n - Lateral movement indicators\n\n Run this against a specific host after the initial hunting query\n identifies a suspicious PID.\n\nauthor: justruss\ntype: CLIENT\nparameters:\n - name: SuspiciousPid\n description: The PID identified as suspicious by the injection hunt\n type: int\n default: \"0\"\n - name: InvestigationWindowMinutes\n description: Minutes before and after process creation to include in timeline\n type: int\n default: \"60\"\n\nsources:\n - name: ProcessAncestry\n description: Full process creation chain for the suspicious process\n query: |\n SELECT\n ProcessId AS PID,\n ParentProcessId AS ParentPID,\n Name AS ProcessName,\n CommandLine,\n Username AS RunningAs,\n CreateTime,\n Exe AS BinaryPath,\n hash(path=Exe, hashselect=\"SHA256\") AS BinarySHA256,\n hash(path=Exe, hashselect=\"SHA1\") AS BinarySHA1\n FROM pslist()\n WHERE ProcessId = SuspiciousPid\n OR ProcessId IN (\n SELECT ParentProcessId FROM pslist()\n WHERE ProcessId = SuspiciousPid\n )\n\n - name: VADMemoryRegions\n description: All executable VAD regions for the suspicious process\n query: |\n SELECT\n Pid,\n Name AS ProcessName,\n VAD.Start AS RegionStart,\n format(format=\"0x%016X\", args=VAD.Start) AS RegionStartHex,\n VAD.End AS RegionEnd,\n VAD.Protection AS Protection,\n VAD.Type AS VADType,\n VAD.FileObject AS BackingFile,\n format(format=\"%d KB\", args=[(VAD.End - VAD.Start) \/ 1024]) AS RegionSizeKB,\n entropy(string=read_file(\n accessor=\"process\",\n filename=format(format=\"\/%d\/%d\", args=[Pid, VAD.Start]),\n length=4096\n )) AS EntropyScore,\n if(condition=read_file(\n accessor=\"process\",\n filename=format(format=\"\/%d\/%d\", args=[Pid, VAD.Start]),\n length=2\n ) = \"MZ\",\n then=\"YES\", else=\"NO\") AS HasPEHeader,\n if(condition=VAD.Protection = \"EXECUTE_READ_WRITE\",\n then=\"CRITICAL\", else=\"HIGH\") AS RiskLevel\n FROM foreach(\n row={SELECT Pid, Name FROM pslist() WHERE Pid = SuspiciousPid},\n query={\n SELECT Pid, Name, VAD FROM vad(pid=Pid)\n WHERE VAD.Protection =~ \"EXECUTE\"\n AND VAD.Type = \"PRIVATE\"\n AND (VAD.FileObject = NULL OR VAD.FileObject = \"\")\n AND (VAD.End - VAD.Start) &gt;= 4096\n }\n )\n ORDER BY EntropyScore DESC\n\n - name: ActiveThreads\n description: Threads currently executing in suspicious memory regions\n query: |\n LET anon_vads = SELECT Pid, VAD.Start AS VStart, VAD.End AS VEnd\n FROM foreach(\n row={SELECT Pid FROM pslist() WHERE Pid = SuspiciousPid},\n query={SELECT Pid, VAD FROM vad(pid=Pid)\n WHERE VAD.Protection =~ \"EXECUTE\"\n AND VAD.Type = \"PRIVATE\"\n AND (VAD.FileObject = NULL OR VAD.FileObject = \"\")\n AND (VAD.End - VAD.Start) &gt;= 4096}\n )\n\n SELECT\n t.Pid, t.Name AS ProcessName, t.ThreadId,\n format(format=\"0x%016X\", args=t.StartAddress) AS StartAddress,\n format(format=\"0x%016X\", args=v.VStart) AS VADStart,\n \"THREAD EXECUTING IN INJECTED REGION\" AS Finding,\n \"CRITICAL\" AS Severity\n FROM foreach(\n row={SELECT Pid, Name FROM pslist() WHERE Pid = SuspiciousPid},\n query={SELECT Pid, Name, ThreadId, StartAddress FROM threads(pid=Pid)\n WHERE StartAddress &gt; 0x1000}\n ) AS t\n JOIN anon_vads AS v ON (\n t.Pid = v.Pid\n AND t.StartAddress &gt;= v.VStart\n AND t.StartAddress &lt; v.VEnd\n )\n\n - name: NetworkConnections\n description: Current and recent network connections from suspicious process\n query: |\n SELECT\n Pid,\n Name AS ProcessName,\n LocalAddress,\n LocalPort,\n RemoteAddress,\n RemotePort,\n Status,\n now() AS CheckTime\n FROM netstat()\n WHERE Pid = SuspiciousPid\n\n - name: RecentFileSystem\n description: Files created or modified around the injection window\n query: |\n LET proc_info = SELECT CreateTime FROM pslist() WHERE Pid = SuspiciousPid\n LET proc_time = proc_info[0].CreateTime\n LET win_start = proc_time - InvestigationWindowMinutes * 60\n LET win_end = proc_time + InvestigationWindowMinutes * 60\n\n SELECT\n FullPath AS FilePath,\n Name AS FileName,\n Mtime AS LastModified,\n Ctime AS Created,\n Size AS FileSizeBytes,\n if(condition=Name =~ \"(?i)\\\\.(exe|dll|ps1|vbs|js|bat|cmd|hta)$\",\n then=\"EXECUTABLE\", else=\"DATA\") AS FileType\n FROM glob(globs=[\n \"C:\/Users\/*\/AppData\/Local\/Temp\/**\",\n \"C:\/Users\/*\/AppData\/Roaming\/**10\",\n \"C:\/Windows\/Temp\/**\",\n \"C:\/ProgramData\/**5\"\n ])\n WHERE Mtime &gt;= win_start\n AND Mtime &lt;= win_end\n AND NOT IsDir\n ORDER BY Mtime ASC\n\n - name: PrefetchEvidence\n description: Prefetch execution records for related processes\n query: |\n SELECT\n Name AS PrefetchFile,\n Mtime AS LastExecutionTime,\n Ctime AS PrefetchCreated,\n Size\n FROM glob(globs=\"C:\/Windows\/Prefetch\/*.pf\")\n ORDER BY Mtime DESC\n LIMIT 50<\/pre>\n<h3>Reading the complete investigation picture<\/h3>\n<p>When all six enrichment layers are combined, what you end up with is not just a list of suspicious memory regions, it is a complete narrative of an intrusion. Walk through a realistic example of how to read the combined output.<\/p>\n<p>The VAD query returns a 512KB RWX anonymous region in svchost.exe (PID 4892) with entropy of 7.3 and an MZ header. That is your starting point: injected PE file, currently in memory, high probability of being active malware.<\/p>\n<p>The process ancestry query shows svchost.exe was created at 22:14:35 by services.exe, which is normal. But the command line is unusual, it was launched with <code>-k netsvcs -p -s Schedule<\/code> rather than the typical short form. Cross-referencing with Sysmon Event ID 1 for the same PID shows that 22 seconds earlier at 22:14:13, a PowerShell process (PID 3842) with a base64-encoded command ran and terminated. PID 3842&#8217;s parent was winword.exe. That is the delivery chain: Office macro to PowerShell to shellcode injection into svchost.<\/p>\n<p>The Sysmon timeline around 22:14:00 shows Event ID 22 (DNS query) at 22:14:08 for a domain that did not exist in the previous 30 days of DNS cache, followed by Event ID 3 (network connection) at 22:14:12 to a residential-range IP on port 443. Then Event ID 8 (CreateRemoteThread) at 22:14:34, source PID 3842, target PID 4892, this is the exact moment of injection. The PowerShell process injected into svchost.<\/p>\n<p>The MFT query shows a file named <code>update.ps1<\/code> created in <code>C:\\Users\\jsmith\\AppData\\Local\\Temp\\<\/code> at 22:14:09 with a size of 4,847 bytes, and then a modification time identical to its creation time. It was written once and never modified, a staging file. Prefetch confirms it ran at 22:14:11 with a run count of 1. The file is gone from disk but both the MFT entry and Prefetch prove its existence and execution.<\/p>\n<p>The network connection query shows svchost.exe (PID 4892) has an established outbound connection to 203.0.113.45:443 that has been open for 47 minutes. Zeek&#8217;s ssl.log shows the JA3 hash for that connection is a0e9f5d64349fb13191bc781f81f42e1, the documented Cobalt Strike default JA3 hash.<\/p>\n<p>The lateral movement query shows that at 22:31:06, 17 minutes after injection, Event ID 4648 fired showing <code>jsmith<\/code>&#8216;s credentials used to authenticate to <code>FINSERVER01<\/code> via a network logon. The scope just expanded from one workstation to a second host.<\/p>\n<p>That is a complete incident in six data sources, stitched together through correlated timestamps and process identifiers. The initial Velociraptor VAD query found a thread. The enrichment queries built an entire story around it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Enriching Velociraptor injection findings by cross-referencing memory images, Windows event logs, filesystem artefacts, Zeek network telemetry, and lateral movement indicators into a complete investigation narrative.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-297","post","type-post","status-publish","format-standard","hentry","category-dfir"],"_links":{"self":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/297","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/comments?post=297"}],"version-history":[{"count":4,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/297\/revisions"}],"predecessor-version":[{"id":348,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/posts\/297\/revisions\/348"}],"wp:attachment":[{"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/media?parent=297"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/categories?post=297"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/justruss.tech\/index.php\/wp-json\/wp\/v2\/tags?post=297"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}