How To: Analyze Web Browser Extensions for Possible Malware & Other Malicious Activity

Analyze Web Browser Extensions for Possible Malware & Other Malicious Activity

Browser extensions are extremely useful since they can expand web browsers like Google Chrome and Mozilla Firefox beyond their built-in features. However, we don't always know who's behind a browser add-on or what it's doing beyond what's advertised. That's where ExtAnalysis comes into play.

ExtAnalysis will unpack an extension so that we can see what's really going on inside. To start using it, you just need to use either Chrome or Firefox, as well as an extension you want to investigate for possible malicious background activities. We'll be examining a Firefox extension from a computer science student to see how a more amateurish add-on will leak its hidden intentions.

Requirements

  • Chrome (or Brave) or Firefox
  • Installed or uninstalled extension from Chrome Web Store or Firefox Addons
  • Linux or Windows PC (or macOS for uninstalled extensions only)

ExtAnalysis Features

ExtAnalysis is somewhat similar to Jupyter Notebook in that it's some Python code that will run, create a user interface, and open in a browser window. It's also a really interactive experience that lets us easily use the tool without relying on a particular piece of hardware or an operating system. It's cross-platform, easy to install, and straightforward to use.

There are a lot of things that ExtAnalysis can do, such as VirusTotal scans, RetireJS Vulnerability scans for JavaScript files, view and edit HTML, JSON, JavaScript, and CSS files, but we're just going to give a general overview of what can be done from a simple scan of an extension.

Step 1: Install ExtAnalysis

We'll be going over the install and use of ExtAnalysis on Kali Linux. To install it, just clone it from its GitHub repository using git clone.

~$ git clone https://github.com/Tuhinshubhra/ExtAnalysis.git

Cloning into 'ExtAnalysis'...
remote: Enumerating objects: 30, done.
remote: Counting objects: 100% (30/30), done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 638 (delta 7), reused 15 (delta 6), pack-reused 608
Receiving objects: 100% (638/638), 10.28 MiB | 4.95 MiB/s, done.
Resolving deltas: 100% (221/221), done.

Next, change into its directory with cd.

~$ cd ExtAnalysis
~/ExtAnalysis$

The, if you want, you can list its contents with ls to see what's there.

~/ExtAnalysis$ ls

CHANGELOG        db              LICENSE           settings.json
core             Dockerfile      plugins           static
CREDITS          extanalysis.py  README.md         templates
current_version  frontend        requirements.txt

We'll need to install the requirements.txt file to get ExtAnalysis working, so use pip3 install -r to get the job done. And that's it for the installation.

~/ExtAnalysis$ pip3 install -r requirements.txt

Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: flask in /usr/lib/python3/dist-packages (from -r requirements.txt (line 1)) (1.1.2)
Collecting python-whois
  Downloading python-whois-0.7.3.tar.gz (91 kB)
     |████████████████████████████████| 91 kB 968 kB/s
Collecting futures
  Downloading futures-3.1.1-py3-none-any.whl (2.8 kB)
Requirement already satisfied: requests in /usr/lib/python3/dist-packages (from -r requirements.txt (line 4)) (2.23.0)
Requirement already satisfied: maxminddb in /usr/lib/python3/dist-packages (from -r requirements.txt (line 5)) (1.4.1)
Requirement already satisfied: Flask-WTF in /usr/lib/python3/dist-packages (from -r requirements.txt (line 6)) (0.14.3)
Requirement already satisfied: future in /usr/lib/python3/dist-packages (from python-whois->-r requirements.txt (line 2)) (0.18.2)
Building wheels for collected packages: python-whois
  Building wheel for python-whois (setup.py) ... done
  Created wheel for python-whois: filename=python_whois-0.7.3-py3-none-any.whl size=87701 sha256=55c0fc88867dd7568ca3ce381f919cc1cd4233a9a6f963717ed76e0880606ed8
  Stored in directory: /home/kali/.cache/pip/wheels/7d/0b/b2/50bf00862456cf788d83cb6525be163d8bf753ca7968d8d50d
Successfully built python-whois
Installing collected packages: python-whois, futures
Successfully installed futures-3.1.1 python-whois-0.7.3
WARNING: You are using pip version 20.2.2; however, version 20.2.3 is available.
You should consider upgrading via the '/usr/bin/python3 -m pip install --upgrade pip' command.

Step 2: Run ExtAnalysis

To use ExtAnalysis, we just need to be in the correct folder, call Python, and run the tool with extanalysis.py. So let's do that to open up the web interface.

~/ExtAnalysis$ python3 extanalysis.py

     _____     _   _____         _         _
    |   __|_ _| |_|  _  |___ ___| |_ _ ___|_|___
    |   __|_'_|  _|     |   | .'| | | |_ -| |_ -|
    |_____|_,_|_| |__|__|_|_|__,|_|_  |___|_|___|
    => Browser Extension Analysis |___| Framework
    => Version 1.0.4 By r3dhax0r

[i] Created empty reports file
[!] Virustotal api was not specified... Files won't be scanned
[i] Creating lab directory: /home/kali/ExtAnalysis/lab

[~] Starting ExtAnalysis at: http://127.0.0.1:13337

 * Serving Flask app "ExtAnalysis - Browser Extension Analysis Toolkit" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
Sandbox: seccomp sandbox violation: pid 1625, tid 1625, syscall 315, args 1625 140296477792640 56 0 10 140296477792640.
Sandbox: seccomp sandbox violation: pid 1647, tid 1647, syscall 315, args 1647 139671689964608 56 0 10 139671689964608.
[i] Accessed Main page

After a few moments, it'll open up your web browser to a webpage at 127.0.0.1:13337. As you can see from the very start, the web app has a very nice UI that's simple to navigate.

Best of all, anything you do in the web app will show in your terminal if you want to see the information that way. But you can also see things printed to the terminal directly on the web app lower on the page.

Step 3: Find an Extension to Analyze

As an example, we're using the PCC RMP Integration extension, which is available as a Chrome extension and a Firefox extension.

This particular extension is written by a computer science student at Pasadena City College. It's designed to overlay the RateMyProfessor score of different teachers onto the college's course catalog. That way, people signing up for classes at the school won't end up with a professor that has a horrible score.

Step 4: Analyze the Extension

From the web interface, there are multiple ways you can go about analyzing an extension, which you can navigate to using the tabs at the top of the page.

  • A link from the Chrome Web Store
  • A link from the Firefox Addon catalog
  • A locally installed extension in Chrome, Brave, or Firefox
  • An uploadable extension in .crx, .xpi, or .zip format

For our example, we're going to use the first option, where we simply give ExtAnalysis a link to a Chrome extension in the Chrome Web Store. This is a good option whether you're doing so for Chrome, Brave, or Firefox since it'll let you investigate an add-on before you actually install it.

Enter the link into the field, then hit "Download & Analyze." A pop-up will appear asking you to save the extension as a custom name, so enter anything you want here where you can find it later. Hit "Download & Analyze" again after naming it.

In seconds, it should tell you that the extension was analyzed and that the report was saved under a unique ID. To view the results, click "View Analysis."

Step 5: Read the Analysis (Basic Info & Files)

This is where things get fun. Right away, we can see that an "unknown" person created our PCC RMP extension. That alone may make you not want to install the add-on.

Other things we can see right away is the type of extension, as well as the number of permissions it requires, unique domains, extracted URLs, and external JaveScript. For the latter, there's none in our example, but if there were a lot, that might be a suspicious sign indicating it's a coin miner or something else you might not want to install. You'll also see the manifest.json file's contents.

In the "Files" tab, there's a wiggly Files & URL Graph that you can interact with to see which files and URLs are linked to each other. There's also a Statistics section and a View Source Code of Files section.

To view the source code for any item in the list, just click on "View Source" next to it. It'll pop up in a new tab in your browser. Below is what appears for the pcc-rmp.js file. Needless to say, this can show you a lot of what's going on behind the scenes, and the comments can help you understand how and why the tool was developed, and how advanced they are at building extensions.

On the webpage itself, there are options to "Beautify Selected Code" and "Beautify Full Code" to give you different views of the source.

// TODO: Add a "Why am I getting this?" link to professors with more than one professor
//  explain that it is difficult to distinguish professors with the same name
//  instead of making a guess and potentially providing an incorrect rating,
//  we have chosen to link to the search page instead
//  oftentimes, there will be duplicate professors
//  This is a greater hassle because the reviews will be segmented
//  You can help by reporting duplicate professors on RateMyProfessor
/**
 * Professor object
 * @typedef {Object} Professor
 * @property {string} name The professor's name
 * @property {string} requestURL The URL to access the professor's information
 * @property {Array} elementArray Contains all elements in the page associated with the professor
 * @property {number} score The professor's RateMyProfessor score
 * @property {number} scoreCount The professor's amount of ratings
 * @property {number} id The professor's RateMyProfessor ID
 * @property {string} department The professor's department
 * @property {string} RMP_URL URL to the professor's RateMyProfessor page
 * @property {string} error Error type
 */
class Professor {
  /**
   * Sets professor's name and formats the request URL
   * @constructor
   * @param  {string} name Professor's name
   */
  constructor(name) {
    this.name = name;
    this.requestURL = formatRequestURL(name);
    this.elementArray = [];
  }

  /**
   * Saves data to Professor object, then sets the elements
   * <p> TODO: Should this function also set the elements? </p>
   * @param  {Array} data Contains professor's data
   */
  loadData(data) {
    //console.log("loading data for " + this.name);
    this.score = data[0];
    this.scoreCount = data[1];
    this.id = data[2];
    this.department = data[3];
    this.RMP_URL = "https://www.ratemyprofessors.com/ShowRatings.jsp?tid=" + this.id.toString();
    setElements(this);
  }

  /**
   * Saves error
   * @param  {string} errorMsg
   */
  error(errorMsg) {
    this.error = errorMsg;
    setError(this);
  }
}

/**
 * Adds error message to all the Professor object's elements
 * @param  {Professor} prof
 */
function setError(prof) {
  var br = document.createElement("br");
  var profError = document.createElement("span");
  profError.textContent = prof.error;
  profError.style.fontSize = "10px";

  for(var i = 0; i < prof.elementArray.length; i++) {
    prof.elementArray[i].appendChild(br.cloneNode(true));
    prof.elementArray[i].appendChild(profError.cloneNode(true));
  }
}

/**
 * Sets all Professor object's elements to reflect the professor's RMP rating
 * <ul>
 *  <li> Changes background color of element according to RMP score </li>
 *  <li> Displays score next to professor's name </li>
 *  <li> Displays amount of ratings below professor's name </li>
 *  <li> Adds link to professor's RateMyProfessor page </li>
 * </ul>
 * @param  {Professor} prof
 */
function setElements(prof) {
  //console.log("Setting elements for " + prof.name);

  if(prof.score === null) {
    //console.log("Error on " + prof.name + ", You should not be seeing this message.");
    return;
  }

  var br = document.createElement("br");
  var profLink = document.createElement("a");
  profLink.href = prof.RMP_URL;
  profLink.target = "_blank";
  profLink.style.fontSize = "10px";
  profLink.textContent = prof.scoreCount + " Ratings";

  var profText = prof.name + " (" + prof.score.toFixed(1) + ")";

  var color;
  if(prof.score < 3) {
    color = "#ff9999";
  } else if (prof.score < 4){
    color = "#ffff99";
  } else if (prof.score < 4.5) {
    color = "#bbff99";
  } else {
    color = "#99ff99";
  }

  for(var i = 0; i < prof.elementArray.length; i++) {
    prof.elementArray[i].style.backgroundColor = color;
    prof.elementArray[i].textContent = profText;
    prof.elementArray[i].title = prof.department;
    prof.elementArray[i].appendChild(br.cloneNode(true));
    prof.elementArray[i].appendChild(profLink.cloneNode(true));
  }
}

/**
 * Creates a URL to access RMP data for a specified professor
 * @param  {string} profName The professor's name
 * @returns {string} URL to access RMP data
 */
function formatRequestURL(profName) {
  let requestURL = "https://pcc-rmp.com/solr/rmp/select/?solrformat=true&rows=20&wt=json&q=PROFNAME+AND+schoolid_s:2649";

  profName = profName.replace(/\s/g, "+");
  requestURL = requestURL.replace(/PROFNAME/g, profName);
  return requestURL;
}

/**
 * Scrapes page for elements that contain a professor's name
 * @returns {Array} Elements containing a professor's name
 */
function getProfElements() {
  let profElements = [];
  var selector = "td.default1[nowrap][valign=top], td.default2[nowrap][valign=top]";
  var selectedElements = document.querySelectorAll(selector);
  var re = /\d|<font\scolor="#000000">|Online|^.$|^[A-Z\s\.]+$|Needs Assignment|^Th$|^Su$|Staff|ABILITY\sFIRST\s-\sKinneola/g;

  for(var i = 0; i < selectedElements.length; i++) {
    let elementText = selectedElements[i].textContent;
    let matches = elementText.match(re);
    if(matches === null) {
    profElements.push(selectedElements[i]);
    }
  }
  return profElements;
}

/**
 * Searches an array of Professor objects for a specified name
 * <ol>
 *  <li> If the name exists, return the Professor object with that name </li>
 *  <li> Otherwise, create a new Professor object with that name, add it to the Professor array,
 *  and return that Professor </li>
 * </ul>
 * @param  {Array.<Professor>} profsArray An array of Professor objects
 * @param  {string} profName The professor to search for
 * @returns {Professor} A Professor object
 */
function searchProfs(profsArray, profName) {
  for(var i = 0; i < profsArray.length; i++) {
    if(profsArray[i].name == profName) {
      return profsArray[i];
    }
  }

  profsArray.push(new Professor(profName));
  return profsArray[profsArray.length - 1];
}

/**
 * Searches for all unique professors
 * @returns {Array.<Professor>} An array containing a Professor object for all professors on the page
 */
function getUniqueProfs() {
  let profElements = getProfElements();
  let uniqueProfs = [];

  for(var i = 0; i < profElements.length; i++) {
    let profName = profElements[i].textContent;
    searchProfs(uniqueProfs, profName).elementArray.push(profElements[i]);
  }

  return uniqueProfs;
}

/**
 * Grabs RateMyProfessor data and gives it to the Professor object
 * <p> TODO: This function does too much </p>
 * @param  {Professor} prof
 */
function getProfData(prof) {
  var request = new XMLHttpRequest();
  request.open("GET", prof.requestURL, true);

  request.onload = function() {
    if (this.status >= 200 && this.status < 400) {
      var data = JSON.parse(this.response);
      var profData = extractProfData(data);
      if(profData == -1) {
        //console.log("No professors found matching: " + prof.name);
        prof.error("No professors found");
      } else if(profData == -2) {
        //console.log("More than one professor found matching: " + prof.name);
        prof.error("More than one professor found");
      } else if (profData == -3){
        //console.log("Zero ratings found for " + prof.name);
        prof.error("No ratings found");
      } else {
        prof.loadData(profData);
        //console.log(prof.name + ": " + prof.score + " " + prof.scoreCount);
      }
    } else {
      //console.log("Server error: " + this.status);
    }
  };

  request.onerror = function() {
    //console.log("Could not reach server.");
  };

  request.send();
}

/**
 * Reads JSON data from argument
 * <ol>
 *  <li> If there are 0 or more than 1 professors found,
 *  return a number representing the error </li>
 *  <li> Otherwise return an array containing the pertinent information </li>
 * </ul>
 * @param  {JSON} responseData
 * @returns {number|Array} Error code or professor information
 */
function extractProfData(responseData) {
  if(responseData.response.numFound == 0) {
    return -1;
  } else if(responseData.response.numFound > 1) {
    return -2;
  } else if(responseData.response.docs[0].total_number_of_ratings_i == 0) {
    return -3;
  }
  var profScore = responseData.response.docs[0].averageratingscore_rf;
  var profScoreCount = responseData.response.docs[0].total_number_of_ratings_i;
  var profID = responseData.response.docs[0].pk_id;
  var profDepartment = responseData.response.docs[0].teacherdepartment_s;
  //console.log("Found data: " + profScore + " " + profScoreCount + " " + profID);
  return [profScore, profScoreCount, profID, profDepartment];
}

//console.log("Starting PCC-RMP...");
var uniqueProfs = getUniqueProfs();

for(var i = 0; i < uniqueProfs.length; i++) {
  //console.log("Trying to access RMP for " + uniqueProfs[i].name);
  getProfData(uniqueProfs[i]);
}

Step 6: Read the Analysis (Permissions)

There's also a "Permissions" tab you can view to see if there are any suspicious-looking things the extensions "needs" to function. Items to be wary of are camera, microphone, location, and other device permissions.

Step 7: Read the Analysis (URLs & Domains)

The "URLs & Domains" tab will show you just that. Here, you can see some of the fascinating stuff that has been pulled already, such as the domains the tool is calling. Our tool is calling ratemyprofessor.com, which makes sense, and the tool's website and a Google server, none of which looks out of the ordinary. Each link has an option to request information from "Whois," "VT Report," and "GEO IP Lookup."

You can also see the Extracted URLs from Files, which can help you quickly determine if the extension is calling a domain that you don't think it needs to be calling. Suspicious activity will really pop out here. In our case, it looks harmless. Each link has an option to request information from "Whois," "Source," and "HTTP Headers."

A Whois report can help you determine if the owner of the domain is someone sketchy. The VirusTotal report will help you determine if its scanner has caught anything.

There's also a section for External JavaScript Files which could also highlight some nefarious activities that the extension has under the rug.

Step 8: Read the Analysis (Gathered Intels)

Lastly, there's the "Gathered Intels" tab. This shows you the extracted IP addresses, Bitcoin addresses, email addresses, and comments. The bitcoin one can help you determine if something might be going on that shouldn't be, as bitcoin addresses are common in code whenever someone is trying to mine or lift cryptocurrency. The email addresses can show off suspicious looking ones that may look offputting. The comments can help you see things said by the author, which can help shed light on why the tool was made.

There's also Base64 encoded strings that are extracted, if any, that you can analyze.

ExtAnalysis Is a Great Tool for Awareness

ExtAnalysis is a great tool to dig into pretty much any browser extension to see if any hidden activities are going on behind the scenes — either before or after you install the add-on. It's great to just get nosy with extensions, discover malware, and learn how others build extensions if you need some inspiration for your own.

Just updated your iPhone to iOS 18? You'll find a ton of hot new features for some of your most-used Apple apps. Dive in and see for yourself:

Cover image and screenshots by Retia/Null Byte

1 Comment

i am stuck is step 2..pls help

(my pc name) MINGW64 ~/ExtAnalysis (master)
$ python3 extanalysis.py
bash: /c/Users/(my pc name)/AppData/Local/Microsoft/WindowsApps/python3: Permission denied

Share Your Thoughts

  • Hot
  • Latest