Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Operation: Multiple Steganography Operations #625

Merged
merged 5 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/core/config/Categories.json
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@
"Remove EXIF",
"Extract EXIF",
"Split Colour Channels",
"Extract RGBA",
"View Bit Plane",
"Randomize Colour Palette",
"Extract LSB",
"Rotate Image",
"Resize Image",
"Blur Image",
Expand Down
9 changes: 9 additions & 0 deletions src/core/lib/Delim.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,12 @@ export const JOIN_DELIM_OPTIONS = [
{name: "Nothing (join chars)", value: ""}
];

/*
RGBA list delimiters.
*/
export const RGBA_DELIM_OPTIONS = [
{name: "Comma", value: ","},
{name: "Space", value: " "},
{name: "CRLF", value: "\\r\\n"},
{name: "Line Feed", value: "\n"}
];
114 changes: 114 additions & 0 deletions src/core/operations/ExtractLSB.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/

import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils";
import { isImage } from "../lib/FileType";
import jimp from "jimp";

/**
* Extract LSB operation
*/
class ExtractLSB extends Operation {

/**
* ExtractLSB constructor
*/
constructor() {
super();

this.name = "Extract LSB";
this.module = "Image";
this.description = "Extracts the Least Significant Bit data from each pixel in an image. This is a common way to hide data in Steganography.";
this.infoURL = "https://en.wikipedia.org/wiki/Bit_numbering#Least_significant_bit_in_digital_steganography";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.args = [
{
name: "Colour Pattern #1",
type: "option",
value: COLOUR_OPTIONS,
},
{
name: "Colour Pattern #2",
type: "option",
value: ["", ...COLOUR_OPTIONS],
},
{
name: "Colour Pattern #3",
type: "option",
value: ["", ...COLOUR_OPTIONS],
},
{
name: "Colour Pattern #4",
type: "option",
value: ["", ...COLOUR_OPTIONS],
},
{
name: "Pixel Order",
type: "option",
value: ["Row", "Column"],
},
{
name: "Bit",
type: "number",
value: 0
}
];
}

/**
* @param {File} input
* @param {Object[]} args
* @returns {File}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");

const bit = 7 - args.pop(),
pixelOrder = args.pop(),
colours = args.filter(option => option !== "").map(option => COLOUR_OPTIONS.indexOf(option)),
parsedImage = await jimp.read(Buffer.from(input)),
width = parsedImage.bitmap.width,
height = parsedImage.bitmap.height,
rgba = parsedImage.bitmap.data;

if (bit < 0 || bit > 7) {
throw new OperationError("Error: Bit argument must be between 0 and 7");
}

let i, combinedBinary = "";

if (pixelOrder === "Row") {
for (i = 0; i < rgba.length; i += 4) {
for (const colour of colours) {
combinedBinary += Utils.bin(rgba[i + colour])[bit];
}
}
} else {
let rowWidth;
const pixelWidth = width * 4;
for (let col = 0; col < width; col++) {
for (let row = 0; row < height; row++) {
rowWidth = row * pixelWidth;
for (const colour of colours) {
i = rowWidth + (col + colour * 4);
combinedBinary += Utils.bin(rgba[i])[bit];
}
}
}
}

return Utils.convertToByteArray(combinedBinary, "binary");

}

}

const COLOUR_OPTIONS = ["R", "G", "B", "A"];

export default ExtractLSB;
65 changes: 65 additions & 0 deletions src/core/operations/ExtractRGBA.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/

import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { isImage } from "../lib/FileType";
import jimp from "jimp";

import {RGBA_DELIM_OPTIONS} from "../lib/Delim.mjs";

/**
* Extract RGBA operation
*/
class ExtractRGBA extends Operation {

/**
* ExtractRGBA constructor
*/
constructor() {
super();

this.name = "Extract RGBA";
this.module = "Image";
this.description = "Extracts each pixel's RGBA value in an image. These are sometimes used in Steganography to hide text or data.";
this.infoURL = "https://en.wikipedia.org/wiki/RGBA_color_space";
this.inputType = "byteArray";
this.outputType = "string";
this.args = [
{
name: "Delimiter",
type: "editableOption",
value: RGBA_DELIM_OPTIONS
},
{
name: "Include Alpha",
type: "boolean",
value: true
}
];
}

/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {string}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");

const delimiter = args[0],
includeAlpha = args[1],
parsedImage = await jimp.read(Buffer.from(input));

let bitmap = parsedImage.bitmap.data;
bitmap = includeAlpha ? bitmap : bitmap.filter((val, idx) => idx % 4 !== 3);

return bitmap.join(delimiter);
}

}

export default ExtractRGBA;
84 changes: 84 additions & 0 deletions src/core/operations/RandomizeColourPalette.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/

import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import Utils from "../Utils";
import PseudoRandomNumberGenerator from "./PseudoRandomNumberGenerator.mjs";
import { isImage } from "../lib/FileType";
import { runHash } from "../lib/Hash.mjs";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";

/**
* Randomize Colour Palette operation
*/
class RandomizeColourPalette extends Operation {

/**
* RandomizeColourPalette constructor
*/
constructor() {
super();

this.name = "Randomize Colour Palette";
this.module = "Image";
this.description = "Randomize's each colour in an image's colour palette. This can often reveal text or symbols that were previously a very similar colour to their surroundings.";
this.infoURL = "https://en.wikipedia.org/wiki/Indexed_color";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.presentType = "html";
this.args = [
{
name: "Seed",
type: "string",
value: ""
}
];
}

/**
* @param {byteArray} input
* @param {Object[]} args
* @returns {byteArray}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");

const seed = args[0] || (new PseudoRandomNumberGenerator()).run("", [5, "Hex"]),
parsedImage = await jimp.read(Buffer.from(input)),
width = parsedImage.bitmap.width,
height = parsedImage.bitmap.height;

let rgbString, rgbHash, rgbHex;

parsedImage.scan(0, 0, width, height, function(x, y, idx) {
rgbString = this.bitmap.data.slice(idx, idx+3).join(".");
rgbHash = runHash("md5", Utils.strToArrayBuffer(seed + rgbString));
rgbHex = rgbHash.substr(0, 6) + "ff";
parsedImage.setPixelColor(parseInt(rgbHex, 16), x, y);
});

const imageBuffer = await parsedImage.getBufferAsync(jimp.AUTO);

return Array.from(imageBuffer);
}

/**
* Displays the extracted data as an image for web apps.
* @param {byteArray} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
const type = isImage(data);

return `<img src="data:${type};base64,${toBase64(data)}">`;
}

}

export default RandomizeColourPalette;
108 changes: 108 additions & 0 deletions src/core/operations/ViewBitPlane.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @author Ge0rg3 [georgeomnet+cyberchef@gmail.com]
* @copyright Crown Copyright 2019
* @license Apache-2.0
*/

import Operation from "../Operation";
import OperationError from "../errors/OperationError";
import Utils from "../Utils";
import { isImage } from "../lib/FileType";
import { toBase64 } from "../lib/Base64";
import jimp from "jimp";

/**
* View Bit Plane operation
*/
class ViewBitPlane extends Operation {

/**
* ViewBitPlane constructor
*/
constructor() {
super();

this.name = "View Bit Plane";
this.module = "Image";
this.description = "Extracts and displays a bit plane of any given image. These show only a single bit from each pixel, and so are often used to hide messages in Steganography.";
this.infoURL = "https://wikipedia.org/wiki/Bit_plane";
this.inputType = "byteArray";
this.outputType = "byteArray";
this.presentType = "html";
this.args = [
{
name: "Colour",
type: "option",
value: COLOUR_OPTIONS
},
{
name: "Bit",
type: "number",
value: 0
}
];
}

/**
* @param {File} input
* @param {Object[]} args
* @returns {File}
*/
async run(input, args) {
if (!isImage(input)) throw new OperationError("Please enter a valid image file.");

const [colour, bit] = args,
parsedImage = await jimp.read(Buffer.from(input)),
width = parsedImage.bitmap.width,
height = parsedImage.bitmap.height,
colourIndex = COLOUR_OPTIONS.indexOf(colour),
bitIndex = 7-bit;

if (bit < 0 || bit > 7) {
throw new OperationError("Error: Bit argument must be between 0 and 7");
}


let pixel, bin, newPixelValue;

parsedImage.scan(0, 0, width, height, function(x, y, idx) {
pixel = this.bitmap.data[idx + colourIndex];
bin = Utils.bin(pixel);
newPixelValue = 255;

if (bin.charAt(bitIndex) === "1") newPixelValue = 0;

for (let i=0; i < 3; i++) {
this.bitmap.data[idx + i] = newPixelValue;
}
this.bitmap.data[idx + 3] = 255;

});

const imageBuffer = await parsedImage.getBufferAsync(jimp.AUTO);

return Array.from(imageBuffer);
}

/**
* Displays the extracted data as an image for web apps.
* @param {byteArray} data
* @returns {html}
*/
present(data) {
if (!data.length) return "";
const type = isImage(data);

return `<img src="data:${type};base64,${toBase64(data)}">`;
}

}

const COLOUR_OPTIONS = [
"Red",
"Green",
"Blue",
"Alpha"
];

export default ViewBitPlane;
Loading