SOCLIFE
SOCLIFE

Evidence-led security analysis

Published knowledge

Search SOC//LIFE

HuntsHUNT-005

Hypothesis-led threat hunting

Hunt for Smart-Contract C2 Resolution Across Endpoints

Builds an environment-wide blockchain RPC baseline, hunts rare runtime and browser RPC use, correlates resolver traffic with follow-on destinations, and scopes source-reported EtherHiding artifacts.

Question

Hunt goal

Endpoints or browsers in the environment may be contacting public blockchain RPC infrastructure to resolve attacker-controlled next-stage domains, payloads, or C2 configuration.

Why this hunt

2026 reporting shows EtherHiding used by compromised websites and malware loaders across multiple EVM chains. The public RPC service is legitimate, but the contract output can be attacker-controlled and can rotate without redeploying the initial loader.

Data sources

Where to look

  • NetworkEndpoint network telemetry with destination, initiating process, device, user, port, and stable initiating-process identity.
  • ProcessEndpoint process telemetry with process identity, command line, parent context, user, and hashes.

Search path

Hunt steps

Each search broadens the view from patient zero to related users, devices, infrastructure, and follow-on activity.
  1. Step 1First search

    Inventory blockchain RPC usage across endpoints

    Finding

    The first search establishes which endpoints and processes have normal blockchain RPC activity and which populations have no established Web3 baseline.

    RPC infrastructure is legitimate shared infrastructure, so the hunt begins with environment context rather than IOC blocking.

    View query
    Q-01First search

    Inventory blockchain RPC usage across endpoints

    What this checks

    Establish which devices, users, and processes contact public blockchain RPC providers so unexpected RPC behavior can be separated from legitimate Web3 usage.

    KQL
    DeviceNetworkEvents
        | where Timestamp >= ago(30d)
        | where isnotempty(RemoteUrl)
        | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
        | summarize
            FirstSeen=min(Timestamp),
            LastSeen=max(Timestamp),
            RpcEvents=count(),
            RpcProviders=make_set(RemoteUrl, 50),
            Processes=make_set(InitiatingProcessFileName, 30),
            Users=make_set(InitiatingProcessAccountUpn, 30)
            by DeviceId, DeviceName
        | order by RpcEvents asc, LastSeen desc
    SPL
    index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype>
    earliest=-30d
    | eval
        device=coalesce(device, dest, host, DeviceName),
        user=lower(coalesce(user, AccountUpn, InitiatingProcessAccountUpn)),
        process_name=lower(coalesce(process_name, InitiatingProcessFileName)),
        remote_domain=lower(coalesce(remote_domain, RemoteUrl, dest_host))
    | where match(
        remote_domain,
        "(?i)(nodies\.app|tenderly\.co|1rpc\.io|drpc\.org|publicnode\.com|ankr\.com|quiknode\.pro|blastapi\.io)$"
    )
    | stats
        min(_time) as first_seen
        max(_time) as last_seen
        count as rpc_events
        values(remote_domain) as rpc_providers
        values(process_name) as processes
        values(user) as users
        by device
    | convert ctime(first_seen) ctime(last_seen)
    | sort rpc_events - last_seen
    What to look for

    A baseline that distinguishes expected developer/wallet/monitoring populations from ordinary endpoints with rare RPC use.

    Technical details
    Tested signal

    Environment-wide RPC usage grouped by device and initiating process.

    Assumptions
    • The RPC provider category is maintained as a defensive service list, not a malicious IOC list.
    Data requirements and relevant fields
    network

    Endpoint network telemetry with destination, initiating process, device, user, port, and stable initiating-process identity.

    • Timestamp
    • DeviceId
    • DeviceName
    • RemoteIP
    • RemotePort
    • RemoteUrl
    • Protocol
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    • InitiatingProcessAccountUpn
    KQL schema

    Uses documented Microsoft Defender XDR DeviceNetworkEvents/DeviceProcessEvents fields. Validate Defender for Endpoint coverage and local retention.

    SPL schema

    Replace index/sourcetype placeholders and normalize endpoint process/network fields to the local data source.

    Limitations
    • The provider list is not exhaustive and public services can change.

    KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local endpoint/network field mapping.

  2. Step 2Pivot

    Find rare runtime-driven RPC access

    Finding

    Runtime-driven RPC activity on ordinary endpoints identifies higher-fidelity candidates similar to loader behavior reported by Microsoft and other researchers.

    Python, Node, PowerShell, and native runtimes need owner/path/context validation before they are treated as malicious.

    View query
    Q-02Pivot

    Find rare runtime-driven RPC access

    What this checks

    Search ordinary endpoints for first-seen blockchain RPC connections initiated by scripting or runtime processes associated with loader behavior.

    KQL
    let current_window = 7d;
        let baseline_window = 30d;
        let runtime_processes = dynamic(['python.exe', 'pythonw.exe', 'node.exe', 'powershell.exe', 'pwsh.exe', 'mshta.exe', 'wscript.exe', 'cscript.exe', 'rundll32.exe', 'curl.exe']);
        let history =
            DeviceNetworkEvents
            | where Timestamp between (ago(baseline_window) .. ago(current_window))
            | where isnotempty(RemoteUrl)
            | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
            | summarize HistoricalRpc=count() by DeviceId;
        DeviceNetworkEvents
        | where Timestamp >= ago(current_window)
        | where isnotempty(RemoteUrl)
        | where InitiatingProcessFileName in~ (runtime_processes)
        | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
        | join kind=leftouter history on DeviceId
        | extend HistoricalRpc=coalesce(HistoricalRpc,0)
        | where HistoricalRpc == 0
        | project Timestamp, DeviceId, DeviceName, InitiatingProcessAccountUpn, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessUniqueId, RemoteUrl, RemoteIP, RemotePort
        | order by Timestamp asc
    SPL
    index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype>
    earliest=-30d
    | eval
        device=coalesce(device, dest, host, DeviceName),
        user=lower(coalesce(user, AccountUpn, InitiatingProcessAccountUpn)),
        process_name=lower(coalesce(process_name, InitiatingProcessFileName)),
        process_command_line=coalesce(process_command_line, InitiatingProcessCommandLine),
        process_uid=coalesce(process_uid, ProcessUniqueId, InitiatingProcessUniqueId),
        remote_domain=lower(coalesce(remote_domain, RemoteUrl, dest_host)),
        is_current=if(_time>=relative_time(now(),"-7d"),1,0),
        is_rpc=if(match(remote_domain,"(?i)(nodies\.app|tenderly\.co|1rpc\.io|drpc\.org|publicnode\.com|ankr\.com|quiknode\.pro|blastapi\.io)$"),1,0),
        is_runtime=if(process_name IN ("python.exe","pythonw.exe","node.exe","powershell.exe","pwsh.exe","mshta.exe","wscript.exe","cscript.exe","rundll32.exe","curl.exe"),1,0)
    | eventstats count(eval(is_rpc=1 AND is_current=0)) as historical_rpc by device
    | where is_current=1 AND is_rpc=1 AND is_runtime=1 AND historical_rpc=0
    | fields _time device user process_name process_command_line process_uid remote_domain RemoteIP RemotePort
    | sort 0 _time
    What to look for

    Runtime-driven RPC connections on endpoints outside approved Web3 populations.

    Technical details
    Tested signal

    Rare RPC access from Python, Node, PowerShell, script hosts, rundll32, or curl.

    Assumptions
    • The thirty-day inventory from Q-01 identifies endpoints with expected RPC usage.
    Data requirements and relevant fields
    network

    Endpoint network telemetry with destination, initiating process, device, user, port, and stable initiating-process identity.

    • Timestamp
    • DeviceId
    • DeviceName
    • RemoteIP
    • RemotePort
    • RemoteUrl
    • Protocol
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    • InitiatingProcessAccountUpn
    KQL schema

    Uses documented Microsoft Defender XDR DeviceNetworkEvents/DeviceProcessEvents fields. Validate Defender for Endpoint coverage and local retention.

    SPL schema

    Replace index/sourcetype placeholders and normalize endpoint process/network fields to the local data source.

    Limitations
    • Legitimate automation can use the same runtimes and requires owner/path/schedule validation.

    KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local endpoint/network field mapping.

  3. Step 3Pivot

    Find first-seen browser RPC activity

    Finding

    First-seen browser RPC connections identify endpoints that may have visited a compromised site using client-side EtherHiding.

    Browser results are intentionally lower confidence and require preceding-site/referrer or follow-on evidence.

    View query
    Q-03Pivot

    Find first-seen browser RPC activity

    What this checks

    Cover the browser-delivery variant by finding browsers that contact public RPC providers on devices with no historical RPC activity.

    KQL
    let current_window = 7d;
        let baseline_window = 30d;
        let browsers = dynamic(["msedge.exe","chrome.exe","firefox.exe","brave.exe","opera.exe"]);
        let history =
            DeviceNetworkEvents
            | where Timestamp between (ago(baseline_window) .. ago(current_window))
            | where isnotempty(RemoteUrl)
            | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
            | summarize HistoricalRpc=count() by DeviceId;
        DeviceNetworkEvents
        | where Timestamp >= ago(current_window)
        | where InitiatingProcessFileName in~ (browsers)
        | where isnotempty(RemoteUrl)
        | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
        | join kind=leftouter history on DeviceId
        | extend HistoricalRpc=coalesce(HistoricalRpc,0)
        | where HistoricalRpc == 0
        | project Timestamp, DeviceId, DeviceName, InitiatingProcessAccountUpn, InitiatingProcessFileName, InitiatingProcessUniqueId, RemoteUrl, RemoteIP, RemotePort
        | order by Timestamp asc
    SPL
    index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype>
    earliest=-30d
    | eval
        device=coalesce(device, dest, host, DeviceName),
        user=lower(coalesce(user, AccountUpn, InitiatingProcessAccountUpn)),
        process_name=lower(coalesce(process_name, InitiatingProcessFileName)),
        process_uid=coalesce(process_uid, ProcessUniqueId, InitiatingProcessUniqueId),
        remote_domain=lower(coalesce(remote_domain, RemoteUrl, dest_host)),
        is_current=if(_time>=relative_time(now(),"-7d"),1,0),
        is_rpc=if(match(remote_domain,"(?i)(nodies\.app|tenderly\.co|1rpc\.io|drpc\.org|publicnode\.com|ankr\.com|quiknode\.pro|blastapi\.io)$"),1,0),
        is_browser=if(process_name IN ("msedge.exe","chrome.exe","firefox.exe","brave.exe","opera.exe"),1,0)
    | eventstats count(eval(is_rpc=1 AND is_current=0)) as historical_rpc by device
    | where is_current=1 AND is_rpc=1 AND is_browser=1 AND historical_rpc=0
    | fields _time device user process_name process_uid remote_domain RemoteIP RemotePort
    | sort 0 _time
    What to look for

    Ordinary user endpoints where a browser unexpectedly contacts an RPC provider, suitable for correlation with compromised-site and follow-on web activity.

    Technical details
    Tested signal

    Browser-origin RPC activity on a device without an established Web3 baseline.

    Assumptions
    • Browser processes and device-level RPC history are retained in endpoint network telemetry.
    Data requirements and relevant fields
    network

    Endpoint network telemetry with destination, initiating process, device, user, port, and stable initiating-process identity.

    • Timestamp
    • DeviceId
    • DeviceName
    • RemoteIP
    • RemotePort
    • RemoteUrl
    • Protocol
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    • InitiatingProcessAccountUpn
    KQL schema

    Uses documented Microsoft Defender XDR DeviceNetworkEvents/DeviceProcessEvents fields. Validate Defender for Endpoint coverage and local retention.

    SPL schema

    Replace index/sourcetype placeholders and normalize endpoint process/network fields to the local data source.

    Limitations
    • Browser RPC access can be legitimate on Web3 sites and is lower fidelity than runtime-driven access.
    • DeviceNetworkEvents does not expose the page referrer or JSON-RPC request body.

    KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local endpoint/network field mapping.

  4. Step 4Pivot

    Correlate RPC access with the next external destination

    Finding

    Same-process RPC-to-follow-on sequences approximate the resolver pattern even when the contract address or returned domain is unknown.

    This behavior remains useful when the actor rotates the smart contract output.

    View query
    Q-04Pivot

    Correlate RPC access with the next external destination

    What this checks

    For both browsers and runtimes, find a same-process transition from a public RPC service to a separate external destination within five minutes.

    KQL
    let current_window = 1d;
    let baseline_window = 30d;
    let recent_rpc =
        DeviceNetworkEvents
        | where Timestamp >= ago(current_window)
        | where isnotempty(RemoteUrl)
        | where isnotempty(InitiatingProcessUniqueId)
        | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
        | project
            RpcTime=Timestamp,
            DeviceId,
            DeviceName,
            AccountUpn=InitiatingProcessAccountUpn,
            ProcessName=InitiatingProcessFileName,
            ProcessCommandLine=InitiatingProcessCommandLine,
            ProcessUniqueId=InitiatingProcessUniqueId,
            RpcUrl=tolower(RemoteUrl),
            RpcIP=RemoteIP,
            RpcPort=RemotePort;
    
    let prior_rpc =
        DeviceNetworkEvents
        | where Timestamp between (ago(baseline_window) .. ago(current_window))
        | where isnotempty(RemoteUrl)
        | where (
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    )
        | summarize PriorRpcEvents=count() by DeviceId;
    
    let follow_on =
        DeviceNetworkEvents
        | where Timestamp >= ago(current_window)
        | where isnotempty(RemoteUrl)
        | where isnotempty(InitiatingProcessUniqueId)
        | where not((
        RemoteUrl endswith "nodies.app"
        or RemoteUrl endswith "tenderly.co"
        or RemoteUrl endswith "1rpc.io"
        or RemoteUrl endswith "drpc.org"
        or RemoteUrl endswith "publicnode.com"
        or RemoteUrl endswith "ankr.com"
        or RemoteUrl endswith "quiknode.pro"
        or RemoteUrl endswith "blastapi.io"
    ))
        | project
            FollowTime=Timestamp,
            DeviceId,
            ProcessUniqueId=InitiatingProcessUniqueId,
            FollowUrl=tolower(RemoteUrl),
            FollowIP=RemoteIP,
            FollowPort=RemotePort;
    
    recent_rpc
    | join kind=leftouter prior_rpc on DeviceId
    | extend PriorRpcEvents=coalesce(PriorRpcEvents, 0)
    | where PriorRpcEvents == 0
    | join kind=inner follow_on on DeviceId, ProcessUniqueId
    | where FollowTime between (RpcTime .. RpcTime + 5m)
    | where FollowUrl != RpcUrl
    | extend TimeDelta=FollowTime-RpcTime
    | summarize arg_min(TimeDelta, *) by DeviceId, ProcessUniqueId, RpcTime
    | project
        RpcTime,
        FollowTime,
        TimeDelta,
        DeviceName,
        AccountUpn,
        ProcessName,
        ProcessCommandLine,
        RpcUrl,
        RpcIP,
        RpcPort,
        FollowUrl,
        FollowIP,
        FollowPort,
        PriorRpcEvents
    | order by RpcTime desc
    SPL
    index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype>
    earliest=-30d
    | eval
        device=coalesce(device, dest, host, DeviceName),
        user=lower(coalesce(user, AccountUpn, InitiatingProcessAccountUpn)),
        process_name=lower(coalesce(process_name, InitiatingProcessFileName)),
        process_command_line=coalesce(process_command_line, InitiatingProcessCommandLine),
        process_uid=coalesce(process_uid, ProcessUniqueId, InitiatingProcessUniqueId),
        remote_domain=lower(coalesce(remote_domain, RemoteUrl, dest_host)),
        remote_ip=coalesce(remote_ip, RemoteIP, dest_ip),
        remote_port=coalesce(remote_port, RemotePort, dest_port)
    | where isnotnull(device) AND isnotnull(process_uid) AND isnotnull(remote_domain)
    | eval
        is_rpc=if(match(
            remote_domain,
            "(?i)(nodies\.app|tenderly\.co|1rpc\.io|drpc\.org|publicnode\.com|ankr\.com|quiknode\.pro|blastapi\.io)$"
        ),1,0),
        is_current=if(_time>=relative_time(now(),"-1d"),1,0)
    | eventstats
        count(eval(is_rpc=1 AND is_current=0)) as prior_rpc_events
        by device
    | sort 0 device process_uid _time
    | streamstats current=f
        last(eval(if(is_rpc=1 AND is_current=1 ,_time,null()))) as rpc_time
        last(eval(if(is_rpc=1 AND is_current=1 ,remote_domain,null()))) as rpc_domain
        last(eval(if(is_rpc=1 AND is_current=1 ,remote_ip,null()))) as rpc_ip
        last(eval(if(is_rpc=1 AND is_current=1 ,process_name,null()))) as rpc_process
        last(eval(if(is_rpc=1 AND is_current=1 ,process_command_line,null()))) as rpc_command
        by device process_uid
    | where
        is_current=1
        AND is_rpc=0
        AND prior_rpc_events=0
        AND isnotnull(rpc_time)
        AND _time>=rpc_time
        AND _time<=rpc_time+300
    | eval delta_seconds=_time-rpc_time
    | table
        rpc_time _time delta_seconds device user process_uid
        rpc_process rpc_command rpc_domain rpc_ip
        remote_domain remote_ip remote_port prior_rpc_events
    | sort - rpc_time
    What to look for

    Resolver-like sequences where an RPC connection is immediately followed by a different destination from the same process.

    Technical details
    Tested signal

    RPC request followed by same-process non-RPC network activity.

    Assumptions
    • InitiatingProcessUniqueId is consistently populated in endpoint network telemetry.
    Data requirements and relevant fields
    network

    Endpoint network telemetry with destination, initiating process, device, user, port, and stable initiating-process identity.

    • Timestamp
    • DeviceId
    • DeviceName
    • RemoteIP
    • RemotePort
    • RemoteUrl
    • Protocol
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    • InitiatingProcessAccountUpn
    KQL schema

    Uses documented Microsoft Defender XDR DeviceNetworkEvents/DeviceProcessEvents fields. Validate Defender for Endpoint coverage and local retention.

    SPL schema

    Replace index/sourcetype placeholders and normalize endpoint process/network fields to the local data source.

    Limitations
    • Busy browsers can contact many unrelated destinations inside five minutes, so browser results require stronger surrounding context.

    KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local endpoint/network field mapping.

  5. Step 5Pivot

    Sweep source-reported contract and second-stage artifacts

    Finding

    Source-scoped IOC matches can connect current telemetry to Cribl's documented Polygon activity without making those indicators a universal requirement.

    A negative result does not clear the EtherHiding hypothesis because providers, contracts, chains, and second-stage domains can change.

    View query
    Q-05Pivot

    Sweep source-reported contract and second-stage artifacts

    What this checks

    Search for the Polygon contract, method selector, and malicious second-stage domain from Cribl's August 2026 investigation.

    KQL
    let reported_contract = "0x0C7Cb01C83203aC0a50Abc3a9AFF3c9Ca727eF55";
    let reported_selector = "b68d1809";
    let reported_second_stage = "thu-ipad-03.cfd";
    union
    (
        DeviceNetworkEvents
        | where Timestamp >= ago(30d)
        | where RemoteUrl =~ reported_second_stage
        | project
            Timestamp,
            DeviceName,
            ArtifactType="Network",
            ArtifactValue=RemoteUrl,
            ProcessName=InitiatingProcessFileName,
            ProcessCommandLine=InitiatingProcessCommandLine,
            AccountUpn=InitiatingProcessAccountUpn
    ),
    (
        DeviceProcessEvents
        | where Timestamp >= ago(30d)
        | where ProcessCommandLine has reported_contract
            or ProcessCommandLine has reported_selector
        | project
            Timestamp,
            DeviceName,
            ArtifactType="Process",
            ArtifactValue=ProcessCommandLine,
            ProcessName=FileName,
            ProcessCommandLine,
            AccountUpn
    )
    | order by Timestamp asc
    SPL
    (
        index=<endpoint_network_index> sourcetype=<endpoint_network_events_sourcetype> earliest=-30d
        "thu-ipad-03.cfd"
    )
    OR
    (
        index=<endpoint_process_index> sourcetype=<process_events_sourcetype> earliest=-30d
        ("0x0C7Cb01C83203aC0a50Abc3a9AFF3c9Ca727eF55" OR "b68d1809")
    )
    | eval
        device=coalesce(device, dest, host, DeviceName),
        user=lower(coalesce(user, AccountUpn, InitiatingProcessAccountUpn)),
        process_name=coalesce(process_name, FileName, InitiatingProcessFileName),
        process_command_line=coalesce(process_command_line, ProcessCommandLine, InitiatingProcessCommandLine),
        artifact=coalesce(RemoteUrl, remote_domain, process_command_line)
    | fields _time device user process_name process_command_line artifact
    | sort 0 _time
    What to look for

    Additional devices or processes referencing the same source-reported EtherHiding artifacts.

    Technical details
    Tested signal

    Source-scoped artifacts appear in process or network telemetry.

    Assumptions
    • Source indicators are used after behavior-first candidate discovery or for retrospective scoping.
    Data requirements and relevant fields
    network

    Endpoint network telemetry with destination, initiating process, device, user, port, and stable initiating-process identity.

    • Timestamp
    • DeviceId
    • DeviceName
    • RemoteIP
    • RemotePort
    • RemoteUrl
    • Protocol
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    • InitiatingProcessAccountUpn
    process

    Endpoint process telemetry with process identity, command line, parent context, user, and hashes.

    • Timestamp
    • DeviceId
    • DeviceName
    • FileName
    • FolderPath
    • ProcessId
    • ProcessUniqueId
    • ProcessCommandLine
    • AccountUpn
    • SHA1
    • SHA256
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    KQL schema

    Uses documented Microsoft Defender XDR DeviceNetworkEvents/DeviceProcessEvents fields. Validate Defender for Endpoint coverage and local retention.

    SPL schema

    Replace index/sourcetype placeholders and normalize endpoint process/network fields to the local data source.

    Limitations
    • The source values are not universal EtherHiding indicators and can rotate or disappear.

    KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local endpoint/network field mapping.

  6. Step 6Pivot

    Inspect candidate process ancestry

    Finding

    Process ancestry distinguishes approved Web3 tooling from user-driven or malware loader execution that precedes blockchain resolution.

    Suspicious ancestry is the point to expand into persistence, credential-access, and post-compromise telemetry.

    View query
    Q-06Pivot

    Inspect candidate process ancestry

    What this checks

    Review process ancestry for runtime-driven RPC candidates to determine how the script or loader was launched.

    KQL
    let target_device_id = "<device_id>";
    let target_process_uid = "<process_unique_id>";
    DeviceProcessEvents
    | where Timestamp >= ago(7d)
    | where DeviceId == target_device_id
    | where ProcessUniqueId == target_process_uid
    | project
        Timestamp,
        DeviceName,
        AccountUpn,
        FileName,
        FolderPath,
        ProcessCommandLine,
        ProcessId,
        ProcessUniqueId,
        SHA1,
        SHA256,
        InitiatingProcessFileName,
        InitiatingProcessCommandLine,
        InitiatingProcessId,
        InitiatingProcessUniqueId
    | order by Timestamp asc
    SPL
    index=<endpoint_process_index> sourcetype=<process_events_sourcetype>
    earliest=-7d
    | eval
        device_id=coalesce(device_id, DeviceId),
        process_uid=coalesce(process_uid, ProcessUniqueId),
        user=lower(coalesce(user, AccountUpn)),
        process_name=coalesce(process_name, FileName),
        process_path=coalesce(process_path, FolderPath),
        process_command_line=coalesce(process_command_line, ProcessCommandLine),
        parent_process_name=coalesce(parent_process_name, InitiatingProcessFileName),
        parent_command_line=coalesce(parent_command_line, InitiatingProcessCommandLine),
        sha1=coalesce(sha1, SHA1),
        sha256=coalesce(sha256, SHA256)
    | where device_id="<device_id>" AND process_uid="<process_unique_id>"
    | fields _time device_id user process_name process_path process_command_line process_uid parent_process_name parent_command_line sha1 sha256
    | sort 0 _time
    What to look for

    A suspicious ancestry such as browser/user-driven execution, unexpected user-writable path, obfuscated command line, or loader chain preceding RPC access.

    Technical details
    Tested signal

    Candidate Python, Node, PowerShell, script-host, or native runtime process with parent/command-line context.

    Assumptions
    • Candidate device and process identifiers are known from earlier hunt steps.
    Data requirements and relevant fields
    process

    Endpoint process telemetry with process identity, command line, parent context, user, and hashes.

    • Timestamp
    • DeviceId
    • DeviceName
    • FileName
    • FolderPath
    • ProcessId
    • ProcessUniqueId
    • ProcessCommandLine
    • AccountUpn
    • SHA1
    • SHA256
    • InitiatingProcessFileName
    • InitiatingProcessCommandLine
    • InitiatingProcessId
    • InitiatingProcessUniqueId
    KQL schema

    Uses documented Microsoft Defender XDR DeviceNetworkEvents/DeviceProcessEvents fields. Validate Defender for Endpoint coverage and local retention.

    SPL schema

    Replace index/sourcetype placeholders and normalize endpoint process/network fields to the local data source.

    Limitations
    • Legitimate developer toolchains can have complex runtime ancestry and need repository/owner context.

    KQL uses Microsoft Defender XDR endpoint telemetry. SPL is a normalized raw-event scaffold and requires local endpoint/network field mapping.

Blast radius

Wider-compromise pivots

  • Recover the preceding website/referrer for browser candidates from proxy or browser telemetry.
  • Search DNS/proxy telemetry for the same RPC providers on unmanaged or non-EDR assets.
  • Hunt additional EVM chains and providers when one chain-specific pattern is confirmed.
  • Inspect source-controlled scripts, scheduled tasks, and user-writable runtime directories on confirmed endpoints.
  • Search service-worker registrations and modified website content when the organization manages the compromised web property.
  • Track smart-contract output changes through threat-intelligence or blockchain-analysis workflows outside the endpoint detection itself.
  • Expand confirmed cases into credential-access and remote-control behavior based on the delivered malware family.

Evidence threshold

What would increase confidence

  • RPC usage is first-seen for the device or user population.
  • The initiating process is an unexpected scripting/runtime executable.
  • The same process reaches a new external destination immediately after the RPC request.
  • The process ancestry shows user-driven, browser-adjacent, or loader behavior.
  • A source-reported contract, selector, or second-stage domain appears.
  • The follow-on destination is malicious or low reputation.
  • Persistence, credential access, or remote-control behavior follows.
  • No approved Web3 owner, application, project, or deployment explains the activity.

Conclusion

Result and next action

The hunt demonstrates a practical route from environment-wide RPC baselining to rare runtime/browser use, resolver-to-follow-on correlation, source IOC scoping, and process ancestry without treating legitimate blockchain infrastructure as inherently malicious.

  • Isolate endpoints where resolver behavior is tied to confirmed malware.
  • Block confirmed malicious follow-on domains and infrastructure.
  • Do not globally block legitimate RPC providers without a business and risk decision.
  • Collect the initiating script/binary and preserve process/network evidence.
  • Scope the same behavior across browser and runtime populations.
  • Review persistence, credential access, and remote-control artifacts for confirmed devices.
  • Where business need is absent, restrict public blockchain RPC access through proxy/network policy.
  • Add new source-reported contracts and outputs to retrospective hunting without replacing the behavior hunt.

The hunt starts by learning where blockchain RPC use is normal. It then separates higher-confidence runtime-driven candidates from lower-confidence browser candidates, correlates RPC access with the next same-process destination, and uses source-reported contracts and domains only as retrospective pivots.

That order matters. EtherHiding is specifically useful to attackers because the public service can stay constant while the smart-contract state and off-chain destination change.

A strong candidate should progress into incident response only after the RPC behavior is connected to suspicious execution, a malicious follow-on destination, source-backed contract evidence, or post-compromise activity.

Context

ATT&CK and limits

Behavior mapping

MITRE ATT&CK

This mapping describes the valid-account behavior examined by the Hunt. It does not prove token theft, attribution, or technique-wide coverage.

Review boundary

Sources and limits

The conclusion stays bounded to the stated scope and available logs.