diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 89b93b87..188d6783 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -378,6 +378,8 @@ "Remove EXIF", "Extract EXIF", "Split Colour Channels", + "View Bit Plane", + "Extract LSB", "Rotate Image", "Resize Image", "Blur Image", diff --git a/src/core/operations/ExtractLSB.mjs b/src/core/operations/ExtractLSB.mjs new file mode 100644 index 00000000..cd6128d3 --- /dev/null +++ b/src/core/operations/ExtractLSB.mjs @@ -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; diff --git a/src/core/operations/ViewBitPlane.mjs b/src/core/operations/ViewBitPlane.mjs new file mode 100644 index 00000000..5c9c59c9 --- /dev/null +++ b/src/core/operations/ViewBitPlane.mjs @@ -0,0 +1,107 @@ +/** + * @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"); + } + + parsedImage.rgba(true); + + 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 < 4; i++) { + this.bitmap.data[idx + i] = newPixelValue; + } + }); + + 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 ``; + } + +} + +const COLOUR_OPTIONS = [ + "Red", + "Green", + "Blue", + "Alpha" +]; + +export default ViewBitPlane;