Account-Verification and Browser OTP Services on Corporate Assets

WindowsmacOSNetwork

IOC Hunt low DPRK IT Worker Fraud

The services an operation uses to phone-verify accounts in bulk, hold VoIP numbers for personas, and generate multi-factor codes in a browser so a whole team can share one second factor.

What this hunt looks for

The operation manual in Hayden McKenzie's second report names the services behind its accounts: sms-man.com and sms-activate.org for bulk phone verification, mailforspam.com as a throwaway Gmail secondary address, slynumber.com and hushed.com for persona numbers, and ssn-check.org for validating Social Security numbers. His fourth report shows SlyNumber used by a working cell.

The manual also names 2fa.live for browser TOTP generation, so multi-factor secrets are shared across a team rather than bound to a device. It is the service most likely to reach an employer's network after hire, because the employer's own prompt is what the code answers. The others belong mostly to the pre-hire stage.

Technique
Establish Accounts (MITRE ATT&CK T1585) and Multi-Factor Authentication interception or sharing (T1111) in support of fraudulently obtained employment (T1078).
Data sources
DNS or proxy logs, EDR network 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.

TypeValueNote
Domain 2fa.live Browser-based TOTP generation, so a multi-factor secret can be shared across a team (HM2).
Domain sms-man.com SMS verification service for phone-verifying accounts at scale (HM2).
Domain sms-activate.org SMS verification service for phone-verifying accounts at scale (HM2).
Domain mailforspam.com Temporary email, used as the secondary verification address on new Gmail accounts (HM2).
Domain slynumber.com VoIP numbers used to satisfy phone verification on persona mailboxes and profiles (HM2, HM4).
Domain hushed.com VoIP numbers named beside SlyNumber as the replacement for Skype and Google Voice (HM2).
Domain ssn-check.org Public Social Security number validation service used on stolen or borrowed identities (HM2).

Logic

Every query below implements these steps, so the same hunt returns the same result in each product.

  1. Collect: DNS lookups and web connections from Windows and macOS endpoints, plus DNS server or proxy logs
  2. Match: name is 2fa.live, or a subdomain of it, the browser one-time-code generator
  3. Match: or name is, or is a subdomain of, one of six pre-hire services (SMS verification, disposable mail and numbers, SSN check)
  4. Report: sources with lookup counts and the distinct services reached, most services first

Queries

Defender / Sentinel KQL

Written for this site, from indicators reported by Hayden McKenzie

A source that reaches 2fa.live, or several of the listed services, is worth checking against the issued-machine inventory; macOS hosts rely on the DnsEvents branch where Defender omits the URL.

// Parent = the last two labels of the host, so any subdomain of a listed domain matches.
union
  (DeviceNetworkEvents
    | where Timestamp > ago(90d) and isnotempty(RemoteUrl)
    | project Timestamp, Source = DeviceName, Name = RemoteUrl),
  (DnsEvents
    | where TimeGenerated > ago(90d)
    | project Timestamp = TimeGenerated, Source = ClientIP, Name)
| extend Parent = extract(@"^(?:[a-z][a-z0-9+.-]*://)?(?:[^/:?#]*\.)?([^./:?#]+\.[^./:?#]+)\.?(?:[:/?#]|$)", 1, tolower(Name))
| where Parent in~ (
    // optional: the six pre-hire services are lower fidelity; delete this line and the next
    "sms-man.com", "sms-activate.org", "mailforspam.com", "slynumber.com", "hushed.com", "ssn-check.org",
    "2fa.live")
| summarize Lookups = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), Domains = make_set(Parent) by Source
| order by array_length(Domains) desc, Lookups desc
Lower-fidelity parts (1)
  • the six pre-hire services (sms-man.com, sms-activate.org, mailforspam.com, slynumber.com, hushed.com, ssn-check.org) — disposable numbers and addresses have ordinary privacy uses and most of this activity precedes hire on the operation's own machines. Remove: delete the // optional: comment line and the line of six domains beneath it, leaving only "2fa.live"

Splunk SPL

Written for this site, from indicators reported by Hayden McKenzie

The same match over DNS logs, grouped by client so a source reaching several services sorts first.

```Placeholder: index=dns carries the CIM DNS field query; for proxy data use the host field your proxy extracts.```
index=dns earliest=-90d
| eval query=lower(query)
| rex field=query "(?<parent>[^.]+\.[^.]+)\.?$"
| where parent IN (
    ```optional: the six pre-hire services are lower fidelity; delete this line and the next```
    "sms-man.com", "sms-activate.org", "mailforspam.com", "slynumber.com", "hushed.com", "ssn-check.org",
    "2fa.live")
| stats count AS lookups min(_time) AS first_seen max(_time) AS last_seen dc(parent) AS services values(parent) AS domains BY src
| convert ctime(first_seen) ctime(last_seen)
| sort - services, - lookups
Lower-fidelity parts (1)
  • the six pre-hire services (sms-man.com, sms-activate.org, mailforspam.com, slynumber.com, hushed.com, ssn-check.org) — disposable numbers and addresses have ordinary privacy uses and most of this activity precedes hire on the operation's own machines. Remove: delete the // optional: comment line and the line of six domains beneath it, leaving only "2fa.live"

Sigma

Written for this site, from indicators reported by Hayden McKenzie

The same match as an endpoint DNS rule that applies to Windows and macOS DNS telemetry.

title: DNS Lookup of an Account-Verification or Browser One-Time-Code Service
id: 0e1399c7-1224-4248-b93c-03deeedd0f48
status: experimental
description: >
  Detects lookups of services named in DPRK IT-worker operation material for
  bulk phone verification, disposable numbers and email, SSN validation, and
  browser-based TOTP generation that lets a team share one second factor.
references:
  - https://haydenmckenzie.com/research/dprk-it-worker-operation-part-two
  - https://haydenmckenzie.com/research/dprk-it-worker-cell-part-one
logsource:
  category: dns_query
detection:
  selection_otp_exact:
    QueryName: '2fa.live'
  selection_otp_sub:
    QueryName|endswith: '.2fa.live'
  # optional: the six pre-hire services are lower fidelity; delete this line and the two selections below it
  selection_prehire_exact:
    QueryName:
      - 'sms-man.com'
      - 'sms-activate.org'
      - 'mailforspam.com'
      - 'slynumber.com'
      - 'hushed.com'
      - 'ssn-check.org'
  selection_prehire_sub:
    QueryName|endswith:
      - '.sms-man.com'
      - '.sms-activate.org'
      - '.mailforspam.com'
      - '.slynumber.com'
      - '.hushed.com'
      - '.ssn-check.org'
  condition: 1 of selection_*
falsepositives:
  - Privacy-minded staff using a second number or a disposable address for
    personal sign-ups
level: medium
Lower-fidelity parts (1)
  • the six pre-hire services (sms-man.com, sms-activate.org, mailforspam.com, slynumber.com, hushed.com, ssn-check.org) — disposable numbers and addresses have ordinary privacy uses and most of this activity precedes hire on the operation's own machines. Remove: delete the # optional: comment line and the selection_prehire_exact and selection_prehire_sub blocks beneath it

CrowdStrike LogScale

Written for this site, from indicators reported by Hayden McKenzie

The same match over Falcon DnsRequest events on Windows and macOS.

#event_simpleName=DnsRequest
| lower(DomainName, as=DomainName)
| regex("(?<parent>[^.]+\\.[^.]+)\\.?$", field=DomainName)
| in(parent, values=[
    // optional: the six pre-hire services are lower fidelity; delete this line and the next
    "sms-man.com", "sms-activate.org", "mailforspam.com", "slynumber.com", "hushed.com", "ssn-check.org",
    "2fa.live"])
| groupBy([ComputerName], function=[count(as=Lookups), min(@timestamp, as=FirstSeen), max(@timestamp, as=LastSeen), count(parent, distinct=true, as=Services), collect([parent])])
| sort(Services, order=desc)
Lower-fidelity parts (1)
  • the six pre-hire services (sms-man.com, sms-activate.org, mailforspam.com, slynumber.com, hushed.com, ssn-check.org) — disposable numbers and addresses have ordinary privacy uses and most of this activity precedes hire on the operation's own machines. Remove: delete the // optional: comment line and the line of six domains beneath it, leaving only "2fa.live"

Elastic

Written for this site, from indicators reported by Hayden McKenzie

The same match over ECS DNS and network events, grouped by host.

FROM logs-*
| EVAL name = TO_LOWER(COALESCE(dns.question.name, destination.domain))
| WHERE name RLIKE "(.*\\.)?(2fa\\.live|sms-man\\.com|sms-activate\\.org|mailforspam\\.com|slynumber\\.com|hushed\\.com|ssn-check\\.org)\\.?"
| EVAL parent = REPLACE(name, "^(.*\\.)?([^.]+\\.[^.]+)\\.?$", "$2")
| STATS lookups = COUNT(*), services = COUNT_DISTINCT(parent), domains = VALUES(parent), first_seen = MIN(@timestamp), last_seen = MAX(@timestamp) BY host.name
| SORT services DESC, lookups DESC
Lower-fidelity parts (1)
  • the six pre-hire services (sms-man.com, sms-activate.org, mailforspam.com, slynumber.com, hushed.com, ssn-check.org) — disposable numbers and addresses have ordinary privacy uses and most of this activity precedes hire on the operation's own machines. Remove: in the RLIKE pattern, delete the six pre-hire names and their pipes, leaving (.*\\.)?(2fa\\.live)\\.?

Cortex XQL

Written for this site, from indicators reported by Hayden McKenzie

The same match over Cortex network events carrying a DNS name.

dataset = xdr_data
| filter event_type = ENUM.NETWORK and (dns_query_name != null or action_external_hostname != null)
| alter host = lowercase(coalesce(dns_query_name, action_external_hostname))
| alter parent = arrayindex(regextract(host, "([^.]+[.][^.]+)[.]?$"), 0)
| filter parent in (
    // optional: the six pre-hire services are lower fidelity; delete this line and the next
    "sms-man.com", "sms-activate.org", "mailforspam.com", "slynumber.com", "hushed.com", "ssn-check.org",
    "2fa.live")
| comp count() as lookups, count_distinct(parent) as services, min(_time) as first_seen by agent_hostname
| sort desc services, desc lookups
Lower-fidelity parts (1)
  • the six pre-hire services (sms-man.com, sms-activate.org, mailforspam.com, slynumber.com, hushed.com, ssn-check.org) — disposable numbers and addresses have ordinary privacy uses and most of this activity precedes hire on the operation's own machines. Remove: delete the // optional: comment line and the line of six domains beneath it, leaving only "2fa.live"

False positives and tuning

Disposable numbers and throwaway addresses have privacy uses, and browser TOTP sites serve people who never set up an authenticator app. A single lookup means little.

The stronger result is 2fa.live reached as the same account completes a multi-factor prompt, or several of these services reached from one issued machine. Most activity precedes hire, on the operation's own machines.

Sources

Tuning required before use This hunt is a starting point. Adapt the queries to your schema, validate them against your baseline and tune them for the false positives noted above. Indicators go stale as operators rotate hosts.