Skip to content

Instantly share code, notes, and snippets.

@dhondta

dhondta/README.md

Last active Aug 6, 2020
Embed
What would you like to do?
Tinyscript steganography tool implementing the Least Significant Bit algorithm

StegoLSB

This can be installed using:

$ pip install tinyscript
$ wget https://gist.githubusercontent.com/dhondta/d2151c82dcd9a610a7380df1c6a0272c/raw/stegolsb.py && chmod +x stegolsb.py && sudo mv stegolsb.py /usr/bin/stegolsb

This tool is especially useful in the use cases hereafter.

Extract hidden data from an image using LSB stegano

$ stegolsb -v extract test.png --column-step 2 --rows 1 --cols 128
12:34:56 [DEBUG] Image size: 225x225
12:34:56 [DEBUG] Bits [0], channels RGB, column step 2, row step 1
12:34:56 [INFO] Hidden data:
[...]

Bruteforce LSB stegano parameters to recover hidden data from an image

This will display readable strings recovered using bruteforced paramters.

$ stegolsb bruteforce test.png
12:34:56 [INFO] [...]
#! /usr/bin/python3
# -*- coding: UTF-8 -*-
from PIL import Image
from tinyscript import *
__author__ = "Alexandre D'Hondt"
__version__ = "1.0"
__copyright__ = "A. D'Hondt"
__license__ = "agpl-3.0"
__doc__ = """
This tool allows to apply steganography based on LSB (Least Significant Bit)
in order to retrieve hidden data from an image.
"""
__examples__ = [
"-v extract -b 0 test.png",
"extract test.png --cols 128 --rows 1 --column-step 2",
"-w secret.txt bruteforce test.png",
]
class LSB(object):
def __init__(self, image, secret=None):
self.__image = image
self.__secret = secret
self.__obj = Image.open(image)
logger.debug("Image size: {}x{}".format(*self.__obj.size))
def bruteforce(self, bits=False, channels=False, nchars=16, maxstep=10):
i = self.__obj
for ch in (ts.bruteforce(3, "RGB", repeat=False) if channels else \
["RGB"]):
for bi in (ts.bruteforce(8, range(8), repeat=False) if bits else \
[(0, )]):
for y_s in range(1, maxstep + 1):
for x_s in range(1, maxstep + 1):
self.extract(bi, ch, colstep=x_s, rowstep=y_s)
for s in ts.strings(self.data, nchars):
logger.info(s)
if self.__secret:
self.write(content=s)
def extract(self, bits=(0, ), channels="RGB", cols=None, rows=None,
coloffset=0, rowoffset=0, colstep=1, rowstep=1):
logger.debug("Bits {}, channels {}, column step {}, row step {}"
.format(list(bits), channels, colstep, rowstep))
i = self.__obj
cols = cols or i.size[0]
rows = rows or i.size[1]
data = ""
for y in range(rowoffset, rows, max(1, rowstep)):
for x in range(coloffset, cols, max(1, colstep)):
pixel = {k: v for k, v in zip("RGB", i.getpixel((x, y)))}
for c in channels.upper():
B = ts.int2bin(pixel[c])[::-1]
for b in bits:
data += B[b]
self.data = ts.bin2str(data)
return self
def hide(self, data):
bin_data = ts.str2bin(data)
#TODO: implement hiding data
bin_len = ts.int2bin(len(bin_data))
return self
def write(self, filename=None, content=None):
filename = self.__secret or filename
if filename is None:
filename = os.path.basename(self.__image)
filename, _ = os.path.splitext(filename)
filename = "{}-secret.txt".format(filename)
with open(filename, 'ab') as f:
f.write(b(content or self.data))
return self
if __name__ == "__main__":
parser.add_argument("-w", "--write", help="write data to a file")
subparsers = parser.add_subparsers(help="commands", dest="command")
extract = subparsers.add_parser('extract',
help="manually extract hidden data")
bruteforce = subparsers.add_parser('bruteforce', help="bruteforce "
"parameters for extracting hidden data")
extract.add_argument("image", type=ts.file_exists, help="image path")
extract.add_argument("-b", "--bits", type=ts.pos_ints, default="0",
help="bits to be considered, starging from the LSB")
extract.add_argument("-c", "--channels", default="RGB",
help="channels to be considered")
extract.add_argument("--cols", type=ts.pos_int,
help="number of image columns to be considered")
extract.add_argument("--column-offset", dest="coloffset", type=ts.pos_int,
default=0, help="column offset for searching for data")
extract.add_argument("--column-step", dest="colstep", type=ts.pos_int,
default=1, help="step number for iterating columns")
extract.add_argument("--rows", type=ts.pos_int,
help="number of image rows to be considered")
extract.add_argument("--row-offset", dest="rowoffset", type=ts.pos_int,
default=0, help="row offset for searching for data")
extract.add_argument("--row-step", dest="rowstep", type=ts.pos_int,
default=1, help="step number for iterating rows")
bruteforce.add_argument("image", type=ts.file_exists, help="image path")
bruteforce.add_argument("-b", "--bits", action="store_true",
help="bruteforce the bits positions",
note="if false, only the LSB is considered")
bruteforce.add_argument("-c", "--channels", action="store_true",
help="bruteforce the color channels",
note="if false, RGB are considered")
bruteforce.add_argument("-n", "--nchars", type=ts.pos_int, default=16,
help="minimal length for readable strings")
bruteforce.add_argument("-s", "--max-step", type=ts.pos_int, default=10,
help="maximum bit step to be considered",
note="e.g. 3 will lookup every 3 bits in the "
"LSB-collected data")
initialize(noargs_action="demo")
p = LSB(args.image, args.write)
if args.command == "bruteforce":
p.bruteforce(args.bits, args.channels, args.nchars, args.max_step)
elif args.command == "extract":
p.extract(args.bits, args.channels, args.cols, args.rows,
args.coloffset, args.rowoffset, args.colstep, args.rowstep)
logger.info("Hidden data:\n" + p.data)
if args.write:
p.write()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment