Skip to content

Instantly share code, notes, and snippets.

@dhondta
Last active February 11, 2024 10:29
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dhondta/30abb35bb8ee86109d17437b11a1477a to your computer and use it in GitHub Desktop.
Save dhondta/30abb35bb8ee86109d17437b11a1477a to your computer and use it in GitHub Desktop.
Tinyscript steganography tool implementing the Pixel Indicator Technique

StegoPIT

This Tinyscript-based tool allows to apply steganography based on PIT (Pixel Indicator Technique) in order to retrieve hidden data from an image.

$ pip install tinyscript
$ tsm install stegopit

This tool is especially useful in the use cases hereafter.

Retrieve hidden data from an image using PIT

$ stegopit -v -i G image.png -w secret.txt
12:34:56 [DEBUG] Image size: 1000x1000
12:34:56 [DEBUG] RMS:        8296
12:34:56 [DEBUG] N other:    IC=G
12:34:56 [DEBUG] Channels:   GBR
12:34:56 [INFO] Hidden data:
[...]
#!/usr/bin/python3
# -*- coding: UTF-8 -*-
from PIL import Image
from tinyscript import *
__author__ = "Alexandre D'Hondt"
__version__ = "1.4"
__copyright__ = ("A. D'Hondt", 2019)
__license__ = "gpl-3.0"
__examples__ = ["-v -i G test.png"]
__doc__ = """
*StegoPIT* allows to apply steganography based on PIT (Pixel Indicator Technique) in order to retrieve hidden data from an image.
"""
BANNER_FONT = "standard"
BANNER_STYLE = {'fgcolor': "lolcat"}
SCRIPTNAME_FORMAT = "none"
get2lsb = lambda n: ["00", "01", "10", "11"][n % 4]
parity = lambda i: ts.int2bin(int(i)).count("1") % 2
is_prime = lambda n: all(n % i for i in range(2, int(n**0.5)+1)) and n > 1
class PIT(object):
""" Base on the following paper:
https://pdfs.semanticscholar.org/7105/e33b5887ed26c7f470df7fdd0928c636242c.pdf
"""
def __init__(self, filepath):
if not os.path.exists(filepath):
raise ValueError("File does not exist")
self.__filepath = filepath
self.__obj = Image.open(filepath)
logger.debug("Image size: {}x{}".format(*self.__obj.size))
def __read_len(self):
pixels = self.__obj.load()
t = []
for i in range(3):
t.append(ts.int2bin(pixels[i, 0][0]))
t.append(ts.int2bin(pixels[i, 0][1]))
t.append(ts.int2bin(pixels[i, 0][2]))
t.pop() # remove the 9th octet
self.__rms = ts.bin2int("".join(t))
logger.debug("RMS: {}".format(self.__rms))
def __select_sequence(self, ic=None):
l = self.__rms
if (ic is None and l % 2 == 0) or ic == "R": # if even: R..
self.__seq = "R" + ["BG", "GB"][parity(l)]
logger.debug("N even: IC=R")
elif (ic is None and is_prime(l)) or ic == "B": # if prime: B..
self.__seq = "B" + ["GR", "RG"][parity(l)]
logger.debug("N prime: IC=B")
else: # else: G..
self.__seq = "G" + ["BR", "RB"][parity(l)]
logger.debug("N other: IC=G")
logger.debug("Channels: {}".format(self.__seq))
def extract(self, ic=None):
# 1. extract length of hidden message from 8B (/8 pixels ?) of first row
# 2. copy into RMS (Remaining Message Size)
self.__read_len()
# 3. start from second row, select indicator channel from RGB channels
self.__select_sequence(ic)
# 4. check 2 LSB's of indicator channel from RGB channels
# case 00 => next pixel
# case 01 => extract 2 LSB's from ch2, RMS -= 2, next pixel
# case 10 => extract 2 LSB's from ch1, RMS -= 2, next pixel
# case 11 => extract 2 LSB's from ch1 & ch2, RMS -= 4, next pixel
# if RMS > 0, goto 4.
data = ""
w, h = self.__obj.size
pixels = self.__obj.load()
i = w
ic, c1, c2 = map(lambda x: "RGB".index(x), self.__seq)
while self.__rms > 0:
x, y = i % w, i // w
if y >= h:
break
pixel = self.__obj.getpixel((x, y))
lsb = pixel[ic] % 4
if lsb == 1:
data += get2lsb(pixel[c2])
self.__rms -= 2
elif lsb == 2:
data += get2lsb(pixel[c1])
self.__rms -= 2
elif lsb == 3:
data += get2lsb(pixel[c1])
data += get2lsb(pixel[c2])
self.__rms -= 4
i += 1
self.data = ts.bin2str(data)
return self
def hide(self, data):
bin_data = ts.str2bin(data)
bin_len = ts.int2bin(len(bin_data))
return self
def write(self, filename=None):
if filename is None:
filename = os.path.basename(self.__filepath)
filename, _ = os.path.splitext(filename)
filename = "{}-secret.txt".format(filename)
with open(filename, 'wb') as f:
f.write(b(self.data))
return self
if __name__ == "__main__":
parser.add_argument("image", type=ts.file_exists, help="image file path")
parser.add_argument("-i", "--ic", choices=list("RGB"), help="force IC")
parser.add_argument("-w", "--write", help="write to a file")
initialize(noargs_action="demo")
p = PIT(args.image)
p.extract(args.ic)
if args.write:
p.write(args.write)
logger.info("Hidden data:\n" + p.data)
#TODO: implement hiding data (using PIT.hide(data))
@neoxeo
Copy link

neoxeo commented May 14, 2022

Hi,

First of all, I want to thank you for all your tools. One of them (stegolsb) saved me in a chall where I couldn't find any solution.

I encounter a problem with stegopit, I have this error all the time regardless of the file passed:

                                          ___| |_ ___  __ _  ___  _ __ (_) |_
                                         / __| __/ _ \/ _` |/ _ \| '_ \| | __|
                                         \__ \ ||  __/ (_| | (_) | |_) | | |_
                                         |___/\__\___|\__, |\___/| .__/|_|\__|
                                                      |___/      |_|



09:23:28 [DEBUG] Image size: 800x674
09:23:28 [DEBUG] RMS:        4054732982757109567
09:23:28 [DEBUG] N other:    IC=G
09:23:28 [DEBUG] Channels:   GBR
Traceback (most recent call last):
  File "/home/userxxx/ctf_tools/Stegano/scripts/stegopit.py", line 119, in <module>
    p.extract(args.ic)
  File "/home/userxxx/ctf_tools/Stegano/scripts/stegopit.py", line 82, in extract
    pixel = self.__obj.getpixel((x, y))
  File "/usr/lib/python3/dist-packages/PIL/Image.py", line 1411, in getpixel
    return self.im.getpixel(xy)
IndexError: image index out of range

Thank you for your help and have a good day !

@dhondta
Copy link
Author

dhondta commented May 15, 2022

Hi @neoxeo !
This is fixed. Thank you very much for mentioning the issue ! ;-)

@neoxeo
Copy link

neoxeo commented May 15, 2022

Thank you very much for your quickly correction

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment