Skip to content

Instantly share code, notes, and snippets.

@ypetya
Last active April 26, 2018 16:18
Show Gist options
  • Select an option

  • Save ypetya/78764180b960743143c5db884fa8d84b to your computer and use it in GitHub Desktop.

Select an option

Save ypetya/78764180b960743143c5db884fa8d84b to your computer and use it in GitHub Desktop.
Steganography

Polyvinyl

Steganography encoded javascript encoder / decoder project. Create an image containing your javascript code. Generate a scriptloader and embed it onto your site.

An experiment to

  • encode javascript to image data
  • run image data as javascript
var VERSION = 2;
function Drawer(canvas) {
this.canvas = canvas;
this.width = canvas.width;
this.height = canvas.height;
this.ctx = canvas.getContext("2d");
this.imageData = this.ctx.createImageData(1, 1);
}
Drawer.prototype.putPixel = function (x, y, r, g, b, a) {
this.imageData.data[0] = r;
this.imageData.data[1] = g;
this.imageData.data[2] = b;
this.imageData.data[3] = a;
this.ctx.putImageData(this.imageData, x, y);
}
Drawer.prototype.pushPixel = function (a, r, g, b) {
this.determineNextPixel();
// Defaults
r = r === undefined ? rnd() : r;
g = g === undefined ? rnd() : g;
b = b === undefined ? rnd() : b;
a = a === undefined ? 255 : a;
this.putPixel(this.x, this.y, r, g, b, a);
}
Drawer.prototype.encode = function (array, resize) {
var self = this, len = array.length;
resize && this.resize(len);
console.log('Encoding length:', len, this.canvas.width, this.canvas.height);
if (len >= this.width * this.height) throw 'Too large input!';
this.pushPixel(VERSION);
array.forEach(function (d) {
self.pushPixel(d);
});
this.pushPixel(0);
}
Drawer.prototype.determineNextPixel = function () {
if (this.x === undefined) {
this.x = this.y = 0;
} else {
this.x++;
if (this.x >= this.width) {
this.x = 0;
this.y++;
}
}
}
Drawer.prototype.resize = function (newSize) {
var dim = Math.ceil(Math.sqrt(newSize));
this.width = this.height = this.canvas.width = this.canvas.height = dim;
this.ctx = this.canvas.getContext("2d");
this.imageData = this.ctx.createImageData(1, 1);
}
function rnd() {
return Math.floor(Math.random() * 80 + 80);
}
Drawer.prototype.toString = function () {
var str = Drawer.prototype.constructor.toString() + '\n';
for (var i in Drawer.prototype) {
str += 'Drawer.prototype.' + i + ' = ' + Drawer.prototype[i].toString() + '\n';
}
return str;
}
var ImageMangler = function (canvas) {
this.canvas = canvas;
this.reset();
}
ImageMangler.prototype.isEmpty = function () {
return this.data.length === 0;
}
ImageMangler.prototype.reset = function () {
this.drawer = new Drawer(this.canvas);
this.data = [];
}
ImageMangler.prototype.append = function (fn) {
this.data = this.data.concat(convertFnToImageData(fn));
return this;
}
ImageMangler.prototype.draw = function () {
this.drawer.encode(this.data, false);
this.reset();
}
function convertFnToImageData(func) {
var returnData = [],
input = func.toString().split('');
returnData = input.map(function (c) {
return c.charCodeAt(0);
});
return returnData;
}
function ImageUploader(onImageLoadedCb) {
this.onImageLoadedCb = onImageLoadedCb;
// Create <input type="file" id="file" accept="image/*">
this.input = document.createElement('INPUT');
this.input.type = 'file';
this.input.id = 'file';
this.input.accept = 'image/*';
document.body.appendChild(this.input);
this.input.addEventListener('change', this.onFileInputChange.bind(this));
}
ImageUploader.prototype.onFileInputChange = function () {
var file = this.input.files[0];
var reader = new FileReader();
reader.addEventListener('load', this.onFileLoad.bind(this));
reader.readAsDataURL(file);
}
ImageUploader.prototype.onFileLoad = function (e) {
this.img = getImage();
this.img.addEventListener('load', this.onImageLoad.bind(this));
this.img.src = e.target.result;
}
ImageUploader.prototype.onImageLoad = function () {
this.onImageLoadedCb(this.img);
}
function getImage() {
var img = document.getElementById('image');
if (!img) {
img = document.createElement('IMG');
img.id = 'image';
img.style = 'display:none';
document.body.appendChild(img);
}
return img;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="Drawer.js"></script>
<script src="Runner.js"></script>
<script src="ImageUploader.js"></script>
<script src="ImageMangler.js"></script>
<script src="script.js"></script>
</head>
<body>
<h1>polyvinyl</h1>
<h2>Javascript to image encoder</h2>
Canvas:
<canvas id="myCanvas" style="border:1px solid #d3d3d3;" width="500" height="500"></canvas>
<hr/>
<p>Check console...</p>
</body>
</html>
var VERSION = 2;
function Runner(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.reset();
}
Runner.prototype.reset = function () {
this.x = this.y = undefined;
this.width = this.canvas.width;
this.height = this.canvas.height;
}
Runner.prototype.decode = function () {
var dataLength,
stringData = '';
this.reset();
if (this.popPixel() === VERSION) {
this.popPixel();
var chr;
while ((chr = this.popPixel()) > 0) stringData += String.fromCharCode(chr);
}
return stringData;
}
Runner.prototype.popPixel = function () {
this.determineNextPixel();
this.data = this.ctx.getImageData(this.x, this.y, 1, 1).data;
return this.data[3];
}
Runner.prototype.determineNextPixel = function () {
if (this.x === undefined) {
this.x = this.y = 0;
} else {
this.x++;
if (this.x >= this.width) {
this.x = 0;
this.y++;
}
}
}
Runner.prototype.toString = function () {
var str = Runner.prototype.constructor.toString() + '\n';
for (var i in Runner.prototype) {
str += 'Runner.prototype.' + i + ' = ' + Runner.prototype[i].toString() + '\n';
}
return str;
}
// Code goes here
window.addEventListener('load', onLoad);
function onLoad(e) {
var canvas = document.getElementById('myCanvas'),
tamperer = new ImageMangler(canvas),
runner = new Runner(canvas),
uploader = new ImageUploader(uploaded);
// example payload:
tamperer.append(tamperer).append(runner).append(uploader);
console.log('init done');
function uploaded(image) {
//console.log('New image selected!', image);
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
console.log('Decoded message:', runner.decode());
if (!tamperer.isEmpty()) {
setTimeout(tamperer.draw.bind(tamperer), 5000);
}
}
}
/* Styles go here */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment