BeaverTail to InvisibleFerret: The Contagious Interview Malware Chain
WindowsmacOS
TTP Hunt high BeaverTailContagious InterviewInvisibleFerret
The JavaScript stealer Contagious Interview delivers and the Python backdoor it installs, hunted as one chain: hidden .pyp, .npl and .n2 paths, the /pdown C2 layout, ports 1224, 3000 and 3306, silent AnyDesk and Telegram exfiltration.
What this hunt looks for
BeaverTail is the obfuscated JavaScript infostealer Contagious Interview embeds in npm packages, take-home repositories and trojanised conferencing apps. It enumerates wallet browser extensions and steals browser credentials, then fetches a Python distribution from its controller and installs InvisibleFerret, a cross-platform backdoor with main, payload, browser and AnyDesk modules.
- BeaverTail requests /pdown on port 1224 and unpacks Python into a hidden .pyp folder, or p.zi in the temporary directory
- it writes the loader as .npl in the home directory and runs it
- components arrive from /payload, /bow and /adc into .n2, with a helper DLL saved as store.node.
Later reporting extends this: Unit 42's Qt-compiled variant ends python.exe with taskkill, GitLab records port 3000 and .npc and .myvars, Google's EtherHiding report adds a JavaScript variant beaconing on 3306 to Telegram, and Trend Micro reports InvisibleFerret recompiled with Cython into .pyd and .so modules. Python running from .pyp is the strongest signal.
- Technique
- Obfuscated JavaScript infostealer (MITRE ATT&CK T1027) targeting browser cryptocurrency-wallet extensions (T1005, T1217), ingress of a Python runtime and backdoor over an application-layer channel (T1105, T1071.001), Python execution from a hidden profile folder (T1059.006), remote access through silently installed AnyDesk (T1219.002) and exfiltration over Telegram (T1567). MITRE ATT&CK Software S1246 and S1245.
- Data sources
- EDR network telemetry, File scanning, Proxy logs, EDR process telemetry
- Last updated
- 2026-10-04
Indicators
The atomic values this hunt matches on, listed separately from the queries so they can be lifted into tooling that speaks none of the query languages below. Every value here is reported by one of the sources cited at the foot of this page.
| Type | Value | Note |
|---|---|---|
| File path | %HOMEPATH%\.pyp\ (or %TEMP%\p.zi) |
Where BeaverTail stores the Python runtime it fetches from /pdown (Unit 42, 2023). |
| File path | ~/.npl |
InvisibleFerret loader, saved under the user's home directory and executed with the downloaded Python (Unit 42, 2023). |
| File path | ~/.n2/pay, ~/.n2/bow, ~/.n2/adc |
InvisibleFerret components fetched from /payload, /bow and /adc (Unit 42, 2023). |
| File path | %USERPROFILE%\store.node |
Helper DLL fetched from /node/<version> (Unit 42, 2023). |
| File path | ~/.npc, ~/.myvars, ~/.linvidia, .vscode/argv.exe, p8.zi |
September 2025 variant: entry point, password-exfiltration file, Linux payload, a renamed 7-Zip, and a password-protected archive whose password is ppp (GitLab). |
| Port | 1224 |
Command-and-control port for the whole chain since 2023 (Unit 42); 95.164.17.24:1224 (Datadog) and 23.227.202.244:1224 (Talos) are observed hosts. |
| Port | 3000 |
Controller port in the September 2025 variant, at 172.86.93.139 (GitLab). |
| Port | 3306 |
Beacon port of the JavaScript InvisibleFerret variant delivered through EtherHiding (Google Threat Intelligence Group). |
| URI path | /keys, /uploads, /node/<version>, /pdown, /client/<campaign_id>, /payload/<campaign_id>, /bow/<campaign_id>, /adc/<campaign_id> |
The controller's directory layout (Unit 42, 2023); Datadog observed /client/3/<campaign id> with campaign identifiers 726 and 525. |
| npm package | passports-js, bcrypts-js, blockscan-api |
Packages carrying BeaverTail in Datadog's reporting. |
| Browser extension ID | nkbihfbeogaeaoehlefnkodbefgpgknn |
MetaMask for Chrome, among the wallet extensions BeaverTail enumerates; Unit 42's October 2024 report lists thirteen. |
| Behaviour | AnyDesk installed silently by a Python process |
The AnyDesk module installs the legitimate client to provide hands-on-keyboard access (Unit 42, Datadog). |
| Behaviour | taskkill or killall against python.exe before installation |
The Qt-compiled BeaverTail ends competing Python processes first (Unit 42, 2024). |
| Domain | nvidiasdk.fly[.]dev, businesshire[.]top |
Payload host and recruiting lure site of the September 2025 variant; requests carry a numeric user-agent (203 Windows, 204 macOS, 208 Linux) as an execution guardrail (GitLab). |
| Behaviour | Exfiltration to a private Telegram chat |
JavaScript InvisibleFerret variant (Google Threat Intelligence Group). |
Logic
Every query below implements these steps, so the same hunt returns the same result in each product.
- Collect: process, file and network events on Windows and macOS endpoints, plus the npm package inventory
- Match: command line names .pyp, .npl, .n2 or .npc, or a .npl, .npc, .myvars or .linvidia file, or a file under .pyp or .n2, is written
- Match: or Python starts AnyDesk with --silent or --install, or an npm package named passports-js, bcrypts-js or blockscan-api is present
- Match: or, lower fidelity: node or python reaches port 1224, 3000 or 3306 or api.telegram.org, a lure host, or taskkill ends python
- Report: devices with the signal kinds hit, most kinds first
Queries
Defender / Sentinel KQL
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupAny process or file signal in a hidden .pyp, .npl or .n2 location is near-conclusive, and a device with several signal kinds should be isolated first.
union
// optional: lower fidelity, ports 3000 and 3306 are common development and database ports and node or python legitimately reaches Telegram; delete this line and the branch below it
(DeviceNetworkEvents
| where Timestamp > ago(30d)
| where (InitiatingProcessFileName in~ ("node.exe", "node", "python.exe", "pythonw.exe", "python", "python3") and ((RemotePort in (1224, 3000, 3306) and not(ipv4_is_private(RemoteIP))) or RemoteUrl has "api.telegram.org"))
or RemoteUrl has_any ("nvidiasdk.fly.dev", "businesshire.top")
| project Timestamp, DeviceId, DeviceName, Signal = "network", Detail = strcat(InitiatingProcessFileName, " -> ", RemoteIP, ":", RemotePort, " ", RemoteUrl)),
(DeviceFileEvents
| where Timestamp > ago(30d)
| where FileName in~ (".npl", ".npc", ".myvars", ".linvidia")
or FolderPath matches regex @"(?i)[\\/](\.(pyp|n2)|node_modules[\\/](passports-js|bcrypts-js|blockscan-api))([\\/]|$)"
// optional: lower fidelity, generic file names that can occur outside the campaign; delete this line and the next
or FileName in~ ("store.node", "p.zi", "p8.zi") or (FileName =~ "argv.exe" and FolderPath contains ".vscode")
| project Timestamp, DeviceId, DeviceName, Signal = "file", Detail = strcat(FolderPath, "/", FileName)),
(DeviceProcessEvents
| where Timestamp > ago(30d)
| where FolderPath matches regex @"(?i)[\\/]\.(pyp|n2)[\\/]" or ProcessCommandLine matches regex @"(?i)[\\/]\.((pyp|n2)[\\/]|npl|npc)"
or (InitiatingProcessFileName in~ ("python.exe", "pythonw.exe", "python", "python3") and ((FileName in~ ("AnyDesk.exe", "AnyDesk") and ProcessCommandLine has_any ("--silent", "--install")) or ProcessCommandLine contains "AnyDesk.app"))
// optional: lower fidelity, administrators also end Python jobs with taskkill or killall; delete this line and the next
or (FileName in~ ("taskkill.exe", "taskkill", "killall", "pkill") and ProcessCommandLine contains "python")
| project Timestamp, DeviceId, DeviceName, Signal = "process", Detail = ProcessCommandLine)
| summarize Hits = count(), Signals = make_set(Signal), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), Details = make_set(Detail, 5) by DeviceId, DeviceName
| order by array_length(Signals) desc, LastSeen desc
Lower-fidelity parts (3)
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the // optional: comment line and the (DeviceNetworkEvents ...) branch beneath it
- the store.node, p.zi, p8.zi and .vscode argv.exe file names — generic names that can occur outside the campaign. Remove: delete the // optional: comment line and the line beneath it
- the taskkill, killall or pkill against python match — administrators and developers also end Python jobs. Remove: delete the // optional: comment line and the line beneath it
Splunk SPL
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupThe same process and file signals over the Endpoint data model, with the network and proxy branches over EDR and proxy indexes.
```Placeholders: the Endpoint data model carries process and file events from Windows and macOS; index=network is an EDR or flow source with process_name; index=proxy is the proxy log.```
| tstats count max(_time) AS _time FROM datamodel=Endpoint.Processes WHERE earliest=-30d
(Processes.process="*/.pyp/*" OR Processes.process="*\\.pyp\\*" OR Processes.process="*/.n2/*" OR Processes.process="*\\.n2\\*" OR Processes.process="*/.npl*" OR Processes.process="*\\.npl*" OR Processes.process="*/.npc*" OR Processes.process="*\\.npc*"
OR (Processes.parent_process_name IN ("python.exe", "pythonw.exe", "python", "python3") AND ((Processes.process_name IN ("AnyDesk.exe","AnyDesk") AND (Processes.process="*--silent*" OR Processes.process="*--install*")) OR Processes.process="*AnyDesk.app*"))
```optional: lower fidelity, administrators also end Python jobs with taskkill or killall; delete this line and the next```
OR (Processes.process_name IN ("taskkill.exe","taskkill","killall","pkill") AND Processes.process="*python*")
)
BY Processes.dest, Processes.process
| rename Processes.dest AS host, Processes.process AS detail
| eval signal="process"
| append
[| tstats count max(_time) AS _time FROM datamodel=Endpoint.Filesystem WHERE earliest=-30d
(Filesystem.file_name IN (".npl",".npc",".myvars",".linvidia") OR Filesystem.file_path="*/.pyp/*" OR Filesystem.file_path="*\\.pyp\\*" OR Filesystem.file_path="*/.n2/*" OR Filesystem.file_path="*\\.n2\\*" OR Filesystem.file_path="*node_modules*passports-js*" OR Filesystem.file_path="*node_modules*bcrypts-js*" OR Filesystem.file_path="*node_modules*blockscan-api*"
```optional: lower fidelity, generic file names that can occur outside the campaign; delete this line and the next```
OR Filesystem.file_name IN ("store.node","p.zi","p8.zi") OR (Filesystem.file_name="argv.exe" AND Filesystem.file_path="*.vscode*")
)
BY Filesystem.dest, Filesystem.file_path
| rename Filesystem.dest AS host, Filesystem.file_path AS detail
| eval signal="file"]
```optional: lower fidelity, ports 3000 and 3306 are common development and database ports and node or python legitimately reaches Telegram; delete this line and the two append blocks below it```
| append
[search index=network earliest=-30d process_name IN ("node.exe", "node", "python.exe", "pythonw.exe", "python", "python3") (dest_port IN (1224, 3000, 3306) OR url="*api.telegram.org*")
| where NOT cidrmatch("10.0.0.0/8", dest_ip) AND NOT cidrmatch("172.16.0.0/12", dest_ip) AND NOT cidrmatch("192.168.0.0/16", dest_ip)
| stats count max(_time) AS _time BY dest, process_name, dest_ip, dest_port
| eval host=dest, detail=process_name." -> ".dest_ip.":".dest_port, signal="network"]
| append
[search index=proxy earliest=-30d dest_port IN (1224, 3000, 3306)
| where match(url, "^https?://[^/]+:[0-9]+/(pdown|keys|uploads|node/[^/]+|client/[0-9]+|payload/[0-9]+|bow/[0-9]+|adc/[0-9]+)")
| stats count max(_time) AS _time BY src, url
| eval host=src, detail=url, signal="network"]
| stats count AS hits min(_time) AS first_seen max(_time) AS last_seen values(signal) AS signals values(detail) AS details BY host
| eval signal_count=mvcount(signals)
| convert ctime(first_seen) ctime(last_seen)
| sort - signal_count, - last_seen
Lower-fidelity parts (3)
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the optional comment line and the two append blocks beneath it
- the store.node, p.zi, p8.zi and .vscode argv.exe file names — generic names that can occur outside the campaign. Remove: delete the optional comment line and the line beneath it
- the taskkill, killall or pkill against python match — administrators and developers also end Python jobs. Remove: delete the optional comment line and the line beneath it
Sigma
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupFour rules that apply to Windows and macOS process, file, network and proxy telemetry.
title: BeaverTail or InvisibleFerret Hidden Path, Silent AnyDesk Install or Python Termination
id: f6c5b4a7-9acd-4b8f-8eb6-7cd3a0cf6b06
status: experimental
description: >
Detects the BeaverTail to InvisibleFerret handoff Unit 42 and GitLab
documented: a process running from or naming the hidden .pyp, .npl, .n2 and
.npc paths, and a Python process silently installing AnyDesk, on Windows
and macOS.
references:
- https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/
- https://securitylabs.datadoghq.com/articles/tenacious-pungsan-dprk-threat-actor-contagious-interview/
- https://gitlab-com.gitlab.io/gl-security/security-tech-notes/threat-intelligence-tech-notes/north-korean-malware-sept-2025/
logsource:
category: process_creation
detection:
selection_image:
Image|contains:
- '\.pyp\'
- '/.pyp/'
selection_cmdline:
CommandLine|contains:
- '\.pyp\'
- '\.npl'
- '\.n2\'
- '\.npc'
- '/.pyp/'
- '/.npl'
- '/.n2/'
- '/.npc'
selection_anydesk_silent:
ParentImage|endswith:
- '\python.exe'
- '\pythonw.exe'
- '/python'
- '/python3'
Image|endswith:
- '\AnyDesk.exe'
- '/AnyDesk'
CommandLine|contains:
- '--silent'
- '--install'
selection_anydesk_app:
ParentImage|endswith:
- '\python.exe'
- '\pythonw.exe'
- '/python'
- '/python3'
CommandLine|contains: 'AnyDesk.app'
# optional: lower fidelity, administrators also end Python jobs with taskkill or killall; delete this line and the selection below it
selection_kill:
Image|endswith:
- '\taskkill.exe'
- '/killall'
- '/pkill'
CommandLine|contains: 'python'
condition: 1 of selection_*
falsepositives:
- IT-managed silent AnyDesk deployment started by a Python-based tool (selection_anydesk_*), and administrators ending Python jobs (selection_kill)
level: high
---
title: BeaverTail or InvisibleFerret File Written
id: f6c5b4a7-9acd-4b8f-8eb6-7cd3a0cf6b07
status: experimental
description: >
Detects the files the chain writes: the hidden .npl, .npc, .myvars and
.linvidia files, anything under .pyp or .n2, the helper and archive names,
and the three npm packages Datadog reports as carrying BeaverTail.
references:
- https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/
- https://securitylabs.datadoghq.com/articles/tenacious-pungsan-dprk-threat-actor-contagious-interview/
logsource:
category: file_event
detection:
selection_name:
TargetFilename|endswith:
- '\.npl'
- '\.npc'
- '\.myvars'
- '\.linvidia'
- '/.npl'
- '/.npc'
- '/.myvars'
- '/.linvidia'
selection_dir:
TargetFilename|contains:
- '\.pyp\'
- '\.n2\'
- '/.pyp/'
- '/.n2/'
- '/node_modules/passports-js/'
- '/node_modules/bcrypts-js/'
- '/node_modules/blockscan-api/'
- '\node_modules\passports-js\'
- '\node_modules\bcrypts-js\'
- '\node_modules\blockscan-api\'
# optional: lower fidelity, generic file names that can occur outside the campaign; delete this line and the selection below it
selection_generic:
TargetFilename|endswith:
- '\store.node'
- '/store.node'
- '\p.zi'
- '/p.zi'
- '\p8.zi'
- '/p8.zi'
- '.vscode\argv.exe'
- '.vscode/argv.exe'
condition: 1 of selection_*
level: high
---
# optional: lower fidelity, ports 3000 and 3306 are common development and database ports and node or python legitimately reaches Telegram; delete this line and every document below it
title: Interpreter Connecting to a BeaverTail Port or Lure Host
id: f6c5b4a7-9acd-4b8f-8eb6-7cd3a0cf6b08
status: experimental
description: >
Detects node or Python connecting to the BeaverTail command-and-control
ports 1224, 3000 and 3306, restricted to public addresses by the exclusions.
references:
- https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/
- https://cloud.google.com/blog/topics/threat-intelligence/dprk-adopts-etherhiding
logsource:
category: network_connection
detection:
selection:
Image|endswith:
- '\node.exe'
- '\python.exe'
- '\pythonw.exe'
- '/node'
- '/python'
- '/python3'
DestinationPort:
- 1224
- 3000
- 3306
filter_private:
DestinationIp|cidr:
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '127.0.0.0/8'
condition: selection and not filter_private
level: medium
---
title: Request for the BeaverTail Controller Directory Layout
id: f6c5b4a7-9acd-4b8f-8eb6-7cd3a0cf6b09
status: experimental
description: >
Detects proxy requests for the controller paths Unit 42 and Datadog report
(/pdown, /keys, /uploads, /node, /client, /payload, /bow, /adc) on the
documented ports.
references:
- https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/
- https://securitylabs.datadoghq.com/articles/tenacious-pungsan-dprk-threat-actor-contagious-interview/
logsource:
category: proxy
detection:
selection:
c-uri|re: '^https?://[^/]+:(1224|3000|3306)/(pdown|keys|uploads|node/[^/]+|client/[0-9]+|payload/[0-9]+|bow/[0-9]+|adc/[0-9]+)'
condition: selection
level: high
Lower-fidelity parts (3)
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the # optional: comment line and the two documents beneath it
- the store.node, p.zi, p8.zi and .vscode argv.exe file names — generic names that can occur outside the campaign. Remove: delete the # optional: comment line and the selection_generic block beneath it
- the taskkill, killall or pkill against python match — administrators and developers also end Python jobs. Remove: delete the # optional: comment line and the selection_kill block beneath it
CrowdStrike LogScale
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupThe process, AnyDesk and DNS signals over Falcon events; Falcon file and port signals are not used, so staged files surface through the process signals.
(#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ (ImageFileName=/[\\\/]\.(pyp|n2)[\\\/]/i or CommandLine=/[\\\/]\.((pyp|n2)[\\\/]|npl|npc)/i))
or (#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ ParentBaseFileName=/^pythonw?(\.exe|3)?$/i FileName=/^AnyDesk(\.exe)?$/i CommandLine=/--(silent|install)/i)
// optional: lower fidelity, administrators also end Python jobs with taskkill or killall; delete this line and the next
or (#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ FileName=/^(taskkill\.exe|killall|pkill)$/i CommandLine=/python/i)
// optional: lower fidelity, ports 3000 and 3306 are common development and database ports and node or python legitimately reaches Telegram; delete this line and the next
or (#event_simpleName=DnsRequest DomainName=/(^|\.)(nvidiasdk\.fly\.dev|businesshire\.top)$/i)
| groupBy([aid, ComputerName], function=[count(as=Hits), count(#event_simpleName, distinct=true, as=SignalKinds), collect([#event_simpleName]), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen)])
| sort(SignalKinds, order=desc)
Lower-fidelity parts (2)
- the taskkill, killall or pkill against python match — administrators and developers also end Python jobs. Remove: delete the // optional: comment line and the line beneath it
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the // optional: comment line and the line beneath it
Elastic
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupThe same process, file, AnyDesk, npm and network signals over Elastic Defend events, counted per host.
FROM logs-endpoint.events.*
| EVAL cmd = TO_LOWER(process.command_line), exe = TO_LOWER(process.executable), path = TO_LOWER(file.path), fname = TO_LOWER(file.name), name = TO_LOWER(COALESCE(dns.question.name, destination.domain))
| WHERE exe RLIKE ".*[\\\\/]\\.(pyp|n2)[\\\\/].*"
OR cmd RLIKE ".*[\\\\/]\\.((pyp|n2)[\\\\/]|npl|npc).*"
OR (TO_LOWER(process.parent.name) IN ("python.exe", "pythonw.exe", "python", "python3") AND ((TO_LOWER(process.name) IN ("anydesk.exe", "anydesk") AND cmd RLIKE ".*--(silent|install).*") OR cmd RLIKE ".*anydesk\\.app.*"))
OR fname IN (".npl", ".npc", ".myvars", ".linvidia")
OR path RLIKE ".*[\\\\/](\\.(pyp|n2)|node_modules[\\\\/](passports-js|bcrypts-js|blockscan-api))[\\\\/].*"
OR fname IN ("store.node", "p.zi", "p8.zi")
OR (fname == "argv.exe" AND path RLIKE ".*\\.vscode.*")
OR (TO_LOWER(process.name) IN ("taskkill.exe", "taskkill", "killall", "pkill") AND cmd RLIKE ".*python.*")
OR (TO_LOWER(process.name) IN ("node.exe", "node", "python.exe", "pythonw.exe", "python", "python3") AND ((destination.port IN (1224, 3000, 3306) AND NOT CIDR_MATCH(destination.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8")) OR name == "api.telegram.org"))
OR name RLIKE "(.*\\.)?(nvidiasdk\\.fly\\.dev|businesshire\\.top)"
| EVAL signal = CASE(file.path IS NOT NULL, "file", process.command_line IS NOT NULL, "process", "network")
| STATS hits = COUNT(*), signal_kinds = COUNT_DISTINCT(signal), signals = VALUES(signal), last_seen = MAX(@timestamp) BY host.name
| SORT signal_kinds DESC, last_seen DESC
Lower-fidelity parts (3)
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the last two OR lines beginning (TO_LOWER(process.name) IN ("node.exe" and name RLIKE
- the store.node, p.zi, p8.zi and .vscode argv.exe file names — generic names that can occur outside the campaign. Remove: delete the two OR lines beginning fname IN ("store.node" and (fname == "argv.exe"
- the taskkill, killall or pkill against python match — administrators and developers also end Python jobs. Remove: delete the OR line beginning (TO_LOWER(process.name) IN ("taskkill.exe"
Cortex XQL
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupThe same process, AnyDesk, file, npm and network signals over Cortex events.
dataset = xdr_data
| alter host = lowercase(coalesce(dns_query_name, action_external_hostname, ""))
| filter (event_type = ENUM.PROCESS and (lowercase(action_process_image_path) ~= "[^a-z0-9][.](pyp|n2)[^a-z0-9]" or lowercase(action_process_image_command_line) ~= "[^a-z0-9][.]((pyp|n2)[^a-z0-9]|npl|npc)"))
or (event_type = ENUM.PROCESS and lowercase(actor_process_image_name) in ("python.exe", "pythonw.exe", "python", "python3") and ((lowercase(action_process_image_name) in ("anydesk.exe", "anydesk") and lowercase(action_process_image_command_line) ~= "--(silent|install)") or lowercase(action_process_image_command_line) ~= "anydesk[.]app"))
// optional: lower fidelity, administrators also end Python jobs with taskkill or killall; delete this line and the next
or (event_type = ENUM.PROCESS and lowercase(action_process_image_name) in ("taskkill.exe", "taskkill", "killall", "pkill") and lowercase(action_process_image_command_line) ~= "python")
or (event_type = ENUM.FILE and (lowercase(action_file_name) in (".npl", ".npc", ".myvars", ".linvidia") or lowercase(action_file_path) ~= "[^a-z0-9]([.](pyp|n2)|node_modules[^a-z0-9](passports-js|bcrypts-js|blockscan-api))[^a-z0-9]"))
// optional: lower fidelity, generic file names that can occur outside the campaign; delete this line and the next
or (event_type = ENUM.FILE and (lowercase(action_file_name) in ("store.node", "p.zi", "p8.zi") or (lowercase(action_file_name) = "argv.exe" and lowercase(action_file_path) ~= "[.]vscode")))
// optional: lower fidelity, ports 3000 and 3306 are common development and database ports and node or python legitimately reaches Telegram; delete this line and the next
or (event_type = ENUM.NETWORK and ((lowercase(actor_process_image_name) in ("node.exe", "node", "python.exe", "pythonw.exe", "python", "python3") and (action_remote_port in (1224, 3000, 3306) or host = "api.telegram.org")) or host ~= "(^|[.])(nvidiasdk[.]fly[.]dev|businesshire[.]top)$"))
| comp count() as hits, count_distinct(event_type) as signal_kinds, max(_time) as last_seen by agent_hostname
| sort desc signal_kinds, desc last_seen
Lower-fidelity parts (3)
- the taskkill, killall or pkill against python match — administrators and developers also end Python jobs. Remove: delete the // optional: comment line and the line beneath it
- the store.node, p.zi, p8.zi and .vscode argv.exe file names — generic names that can occur outside the campaign. Remove: delete the // optional: comment line and the line beneath it
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the // optional: comment line and the line beneath it
osquery
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupA point-in-time check of running processes, hidden home-directory entries, npm packages and interpreter sockets on the machine in hand.
SELECT 'process' AS signal, name AS item, path || ' ' || cmdline AS detail
FROM processes
WHERE lower(path) LIKE '%/.pyp/%' OR lower(path) LIKE '%\.pyp\%' OR lower(path) LIKE '%/.n2/%' OR lower(path) LIKE '%\.n2\%'
OR lower(cmdline) LIKE '%/.npl%' OR lower(cmdline) LIKE '%\.npl%' OR lower(cmdline) LIKE '%/.npc%' OR lower(cmdline) LIKE '%\.npc%'
UNION ALL
SELECT 'file', f.filename, f.path
FROM users u JOIN file f ON f.directory = u.directory
WHERE f.filename IN ('.npl', '.npc', '.myvars', '.n2', '.pyp', '.linvidia', 'store.node')
UNION ALL
SELECT 'npm package', name, path
FROM npm_packages
WHERE name IN ('passports-js', 'bcrypts-js', 'blockscan-api')
-- optional: lower fidelity, ports 3000 and 3306 are common development and database ports; delete this line and the five below it
UNION ALL
SELECT 'network', p.name, s.remote_address || ':' || s.remote_port
FROM process_open_sockets s JOIN processes p ON p.pid = s.pid
WHERE s.remote_port IN (1224, 3000, 3306) AND lower(p.name) IN ('node', 'node.exe', 'python', 'python3', 'python.exe', 'pythonw.exe')
AND s.remote_address NOT LIKE '10.%' AND s.remote_address NOT LIKE '192.168.%' AND s.remote_address NOT LIKE '127.%' AND s.remote_address NOT LIKE '172.16.%';
Lower-fidelity parts (1)
- the network branch (ports 1224, 3000 and 3306, api.telegram.org, nvidiasdk.fly.dev, businesshire.top) — 3000 and 3306 are ordinary development and database ports, node and python legitimately reach Telegram, and the hosts rotate. Remove: delete the -- optional: comment line and the UNION ALL, SELECT, FROM and two WHERE lines beneath it, ending at the semicolon
YARA
Written for this site, from indicators reported by Unit 42, Datadog Security Labs, GitLab, Google Threat Intelligence GroupHeuristic content rules for a BeaverTail npm payload and an InvisibleFerret Python module; Trend Micro reports 2026 builds compiled to .pyd and .so, which a Python-source rule will not match.
rule Suspected_BeaverTail_NPM_Payload
{
meta:
description = "Heuristic match for BeaverTail-style JS infostealer packaged inside an npm module (postinstall payload, wallet-extension enumeration, hardcoded C2 port pattern reported by Unit 42 and Datadog)"
reference = "https://unit42.paloaltonetworks.com/north-korean-threat-actors-lure-tech-job-seekers-as-fake-recruiters/"
confidence = "medium"
strings:
$wallet1 = "nkbihfbeogaeaoehlefnkodbefgpgknn" ascii // MetaMask extension ID, commonly targeted
$postinstall = "postinstall" ascii
// optional: lower fidelity, ordinary minifiers also emit _0x and the port pattern is generic; delete this line, the $obf line and the final 1 of term
$exfil = /https?:\/\/[a-z0-9.\-]+:12[0-9]{2}\// ascii
$obf = "_0x" ascii
condition:
filesize < 5MB and
$postinstall and $wallet1 and 1 of ($exfil, $obf)
}
rule Suspected_InvisibleFerret_Python_Payload
{
meta:
description = "Heuristic match for InvisibleFerret-style Python backdoor modules (main/payload/browser/AnyDesk) and its reported C2 URI structure"
reference = "https://securitylabs.datadoghq.com/articles/tenacious-pungsan-dprk-threat-actor-contagious-interview/"
confidence = "medium"
strings:
$mod1 = "browser_module" ascii
$mod2 = "anydesk_module" ascii nocase
$uri1 = "/pdown" ascii
$uri2 = "/uploads" ascii
$py = "import base64" ascii
condition:
filesize < 3MB and $py and 1 of ($mod1, $mod2) and 1 of ($uri1, $uri2)
}
Lower-fidelity parts (1)
- the _0x obfuscation prefix and the :12xx exfiltration URL pattern — ordinary minifiers also emit _0x and the port pattern is generic. Remove: in Suspected_BeaverTail_NPM_Payload delete the // optional: comment, the $exfil line, the $obf line and the trailing and 1 of ($exfil, $obf) term of the condition
False positives and tuning
The hidden-folder queries are close to zero-noise: no legitimate Python distribution installs into .pyp and nothing benign writes .npl or .n2 into a home directory.
The port queries are noisy: 3000 is the default for countless development servers and 3306 for MySQL, so they are restricted to script interpreters and public addresses, and 1224 will go stale as hosts rotate.
IT-managed silent AnyDesk deployment matches the silent-install signal, so exclude known RMM or configuration-management parent processes. The taskkill and killall query also matches administrators ending Python jobs.
The YARA rules and URI-path strings are heuristic: tune against your own corpus before blocking, since ordinary minifiers also emit the _0x obfuscation prefix.
Sources
- Hacking Employers and Seeking Employment: Two Job-Related Campaigns Bear Hallmarks of North Korean Threat Actors — Unit 42 (Palo Alto Networks), 2023-11-21
- Contagious Interview: DPRK Threat Actors Lure Tech Industry Job Seekers to Install New Variants of BeaverTail and InvisibleFerret Malware — Unit 42 (Palo Alto Networks), 2024-10-09
- Tenacious Pungsan: A DPRK Threat Actor Linked to Contagious Interview — Datadog Security Labs, 2024-10-24
- BeaverTail, Software S1246 — The MITRE Corporation (MITRE ATT&CK), 2025-10-24
- BeaverTail and OtterCookie evolve with a new Javascript module — Cisco Talos, 2025-10-16
- InvisibleFerret, Software S1245 — The MITRE Corporation (MITRE ATT&CK), 2025-10-24
- North Korean Malware, September 2025 — GitLab Threat Intelligence, 2025-09-30
- DPRK Adopts EtherHiding: Nation-State Malware Hiding on Blockchains — Google Threat Intelligence Group, 2025-10-16
- Void Dokkaebi InvisibleFerret malware analysis (Cython-compiled variant) — Trend Micro, 2026