Skip to content

Instantly share code, notes, and snippets.

@dhondta
Last active Aug 13, 2022
Embed
What would you like to do?
Tinyscript steganography tool based on base32/64 padding

Paddinganograph

This Tinyscript-based tool allows to unhide data hidden in base32/base64 strings. It can take a PNG or JPG in input to retrieve an EXIF value as the input data.

This can be installed using:

$ pip install tinyscript
$ wget https://gist.githubusercontent.com/dhondta/90a07d9d106775b0cd29bb51ffe15954/raw/paddinganograph.py && chmod +x paddinganograph.py && sudo mv paddinganograph.py /usr/bin/paddinganograph
$ paddinganograph -e base64 -f Comment -s . < test.jpg
[...]
$ paddinganograph -e base64 -f Comment -s . < test.jpg | paddinganograph -e base32
[...]

This tool is especially useful in the use cases hereafter.

Retrieve hidden data from an image using Base32/64 padding

Select the "Comment" field, split its value with the separator "." and extract hidden data:

$ paddinganograph -s "." -f "Comment" < image.jpg
[...]
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import math
from tinyscript import *
__author__ = "Alexandre D'Hondt"
__version__ = "1.5"
__copyright__ = ("A. D'Hondt", 2019)
__license__ = "gpl-3.0"
__reference__ = "https://inshallhack.org/paddinganography/"
__examples__ = ["-s . -f \"Comment\" < image.jpg > base32.enc", "-e base32 < base32.enc"]
__docformat__ = "md"
__doc__ = """
*Paddinganograph* allows to unhide data hidden in base32/base64 strings. It can take a PNG or JPG in input to retrieve an EXIF value as the input data.
"""
SCRIPTNAME_FORMAT = "none"
DEF_BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
DEF_BASE32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
def exif(raw_data, key):
t = ts.TempPath().tempfile().name
with open(str(t), 'wb') as f:
f.write(raw_data)
logger.debug("Getting EXIF data...")
exif = subprocess.check_output(["exiftool", str(t)])
exif = codecs.decode(exif, "utf-8")
exif = {l.split(':', 1)[0].strip(): l.split(':', 1)[1].strip() for l in exif.split('\n') if l.strip() != ""}
return exif if not key else exif[key]
def unhide(encoded, encoding="base64", charset=None, sep=".", pad="=", n_pad=8):
try:
charset = (charset or globals()["DEF_{}".format(encoding.upper())]).strip()
except NameError:
raise ValueError("Bad encoding")
logger.debug("Unhidding data...")
bits = b("")
for token in b(encoded).split(b(sep)):
bits += unhide_bits(token.strip(), charset, pad, n_pad) or b("")
return b("").join(ts.bin2str(bits[i:i+8]) for i in range(0, len(bits), 8))
def unhide_bits(encoded, charset, pad="=", n_pad=8):
def __gcd(a,b):
while b > 0:
a, b = b, a % b
return a
padding = encoded.count(b(pad))
n_repr = int(math.ceil(math.log(len(charset), 2)))
w_len = n_repr * n_pad / __gcd(n_repr, n_pad)
n_char = int(math.ceil(float(w_len) / n_repr))
if encoded == "" or len(encoded) % n_char != 0 or padding == 0:
return
unused = {n: int(w_len - n * n_repr) % n_pad for n in range(n_char)}
b_val = bin(b(charset).index(encoded.rstrip(b(pad))[-1]))[2:].zfill(n_repr)
return b(b_val[-unused[padding]:])
if __name__ == '__main__':
parser.add_argument("-c", "--charset", help="characters set")
parser.add_argument("-e", "--encoding", choices=["base32", "base64"], default="base64", help="character encoding")
parser.add_argument("-f", "--exif-field", dest="exif", default="Comment", help="EXIF metadata field to be selected")
parser.add_argument("-p", "--padding-char", default="=", help="padding character")
parser.add_argument("-s", "--separator", default="\n", help="base-encoded token separator")
initialize()
data = b("").join(l for l in ts.stdin_pipe())
if data.startswith(b("\x89PNG")) or data.startswith(b("\xff\xd8\xff\xe0")):
try:
data = exif(data, args.exif)
except KeyError:
logger.error("No EXIF field '%s'" % args.exif)
sys.exit(1)
print(ensure_str(unhide(data, args.encoding, args.charset, args.separator)))
@penthium2
Copy link

penthium2 commented Apr 20, 2021

I've an issue :

  File "./paddinganograph.py", line 83, in <module>
    print(unhide(data, args.encoding, args.charset, args.separator))
  File "./paddinganograph.py", line 47, in unhide
    for token in encoded.split(b(sep)):
TypeError: must be str or None, not bytes

command :
./paddinganograph.py -s "." -f "Comment" < ./fic.jpg

@dhondta
Copy link
Author

dhondta commented Apr 23, 2021

@penthium2 Fixed. Thanks for the heads up.

@WiseLife42
Copy link

WiseLife42 commented Feb 25, 2022

J'ai une erreur même après installé tinyscript :

Traceback (most recent call last):
  File "paddinganograph.py", line 4, in <module>
    from tinyscript import *
ImportError: No module named tinyscript

Une fois que j'ai modifié :

#!/usr/bin/env python

en

#!/usr/bin/env python3

ça fonctionne ! :)

Si tu peux modifier; ça sera sympa, sinon je dois ajouter un truc du style "sed -i '1 s/$/3/g'" paddinganograph.py dans mon script

@neoxeo
Copy link

neoxeo commented May 14, 2022

Hi,

I have an error with this script with all png files I have tested :
python paddinganograph.py -s "." -f "Comment" < ./TheWonderfulWizard.png

Traceback (most recent call last):
File "/home/userxxx/ctf_tools/Stegano/scripts/paddinganograph.py", line 73, in
data = exif(data, args.exif)
File "/home/userxxx/ctf_tools/Stegano/scripts/paddinganograph.py", line 33, in exif
return exif if not key else exif[key]
KeyError: 'Comment'

Thank you for your help

@dhondta
Copy link
Author

dhondta commented May 15, 2022

Hi @neoxeo !
Fixed. Thank you very much for mentioning this.
Please note however that the error occurs because there is no EXIF field Comment (as set by default ; you can choose the field name with the -f/--exif-field option) in your target image. A priori, this is a normal behavior. Anyway, I caught the exception and added a clean logged error when it occurs.

@neoxeo
Copy link

neoxeo commented May 15, 2022

Hi @dhondta,

Thank you very much for your quickly reply and correction/explanations

@dhondta
Copy link
Author

dhondta commented Jun 10, 2022

Hi @Rexillios !
This tool relies on exiftool which can be simply installed with sudo apt install exiftool.

@Rexillios
Copy link

Rexillios commented Jun 10, 2022

Hi @Rexillios ! This tool relies on exiftool which can be simply installed with sudo apt install exiftool.

Ups sorry for that.
Your tool is crazy thanks !

@Tom-Weber
Copy link

Tom-Weber commented Aug 4, 2022

Hello, j'ai une erreur lors du lancement du script, python full à jour et tinyscript installé juste avant.
Une idée de comment résoudre ce truc ? Installer une ancienne version de python ? Ou possible faire une update pour que le script fonctionne avec la dernière version de markdown.util ?

Traceback (most recent call last):
  File "/home/kali/Documents/root-me/Steganographie/paddinganograph.py", line 4, in <module>
    from tinyscript import *

  File "/usr/local/lib/python3.10/dist-packages/tinyscript/__init__.py", line 13, in <module>
    from .features import *

  File "/usr/local/lib/python3.10/dist-packages/tinyscript/features/__init__.py", line 5, in <module>
    from .handlers import *

  File "/usr/local/lib/python3.10/dist-packages/tinyscript/features/handlers.py", line 9, in <module>
    from ..helpers.constants import WINDOWS

  File "/usr/local/lib/python3.10/dist-packages/tinyscript/helpers/__init__.py", line 26, in <module>
    from .text import *

  File "/usr/local/lib/python3.10/dist-packages/tinyscript/helpers/text.py", line 6, in <module>
    import mdv

  File "/usr/local/lib/python3.10/dist-packages/mdv/__init__.py", line 3, in <module>
    from .markdownviewer import run, main

  File "/usr/local/lib/python3.10/dist-packages/mdv/markdownviewer.py", line 160, in <module>
    from markdown.util import etree

ImportError: cannot import name 'etree' from 'markdown.util' (/usr/lib/python3/dist-packages/markdown/util.py)```

Merci d'avance pour l'aide :)

@dhondta
Copy link
Author

dhondta commented Aug 10, 2022

@Tom-Weber
Merci d'avoir rapporté ce problème. Cette erreur provient de MarkdownViewer (mdv). Dans la dernière version de Tinyscript, au lieu de charger mdv sous Python2 et mdv3 (une version alternative qui fixe la compatibilité) sous Python3, j'ai rétabli un seul import de mdv (et plus mdv3 car cette compatibilité semble avoir été fixée récemment dans le paquet de MarkdownViewer.
Par conséquent, si pip install --upgrade mdv et pip install --upgrade tinyscript ne résolvent pas le problème, c'est que le problème de compatibilité n'est toujours pas fixé.
Peux-tu essayer ceci et tester de ton côté, stp ?

@Cyber-Broccoli
Copy link

Cyber-Broccoli commented Aug 10, 2022

Personnelement pour l'erreur markdown j'ai pu résoudre en rétrogradant la version pour installer markdown-3.3.7 via pip install markdown==3.3.7
En revanche quand je fais un test sur mon fichier j'ai l'erreur suivante:

#python3 paddinography.py -s "." -f "Comment" < ch15.jpg                                                   
Traceback (most recent call last):
  File "/home/kali/Desktop/Rootme/ch15/paddinography.py", line 83, in <module>
    data = exif(data, args.exif)
  File "/home/kali/Desktop/Rootme/ch15/paddinography.py", line 29, in exif
    t = TempPath().tempfile().name
NameError: name 'TempPath' is not defined

(Il y a bien un "Comment" EXIF dans mon fichier)
Merci pour ton aide.

EDIT : j'ai cette erreur si je call le script paddinograph.py mais si j'appelle le bin tout fonctionne depuis ton upgrade du code :)

@Tom-Weber
Copy link

Tom-Weber commented Aug 12, 2022

@Tom-Weber Merci d'avoir rapporté ce problème. Cette erreur provient de MarkdownViewer (mdv). Dans la dernière version de Tinyscript, au lieu de charger mdv sous Python2 et mdv3 (une version alternative qui fixe la compatibilité) sous Python3, j'ai rétabli un seul import de mdv (et plus mdv3 car cette compatibilité semble avoir été fixée récemment dans le paquet de MarkdownViewer. Par conséquent, si pip install --upgrade mdv et pip install --upgrade tinyscript ne résolvent pas le problème, c'est que le problème de compatibilité n'est toujours pas fixé. Peux-tu essayer ceci et tester de ton côté, stp ?

Après pip install --upgrade mdv et pip install --upgrade tinyscript toutes les dépendances étaient déjà ok + modules à jour.
J'ai relancé le script, même erreur : ImportError: cannot import name 'etree' from 'markdown.util' (/usr/lib/python3/dist-packages/markdown/util.py)

Personnelement pour l'erreur markdown j'ai pu résoudre en rétrogradant la version pour installer markdown-3.3.7 via pip install markdown==3.3.7 En revanche quand je fais un test sur mon fichier j'ai l'erreur suivante:

#python3 paddinography.py -s "." -f "Comment" < ch15.jpg                                                   
Traceback (most recent call last):
  File "/home/kali/Desktop/Rootme/ch15/paddinography.py", line 83, in <module>
    data = exif(data, args.exif)
  File "/home/kali/Desktop/Rootme/ch15/paddinography.py", line 29, in exif
    t = TempPath().tempfile().name
NameError: name 'TempPath' is not defined

(Il y a bien un "Comment" EXIF dans mon fichier) Merci pour ton aide.

EDIT : j'ai cette erreur si je call le script paddinograph.py mais si j'appelle le bin tout fonctionne depuis ton upgrade du code :)

Après pip install markdown==3.3.7, tout est ok ! Merci à vous deux pour votre aide :)

Il ne serait pas plus simple d'avoir un projet python avec les dépendance utilisées ? @dhondta Cela pourrait éviter toutes les erreurs concernant les version !

Merci d'avance !

@dhondta
Copy link
Author

dhondta commented Aug 13, 2022

Salut @Tom-Weber !
Merci pour ces explications.
Après pip3 install --upgrade markdown, j'ai :

$ pip show markdown
Name: Markdown
Version: 3.4.1
Summary: Python implementation of Markdown.
...

Et j'obtiens bien l'erreur que tu as rapportée :

$ paddinganograph --help
Traceback (most recent call last):
[...]
ImportError: cannot import name 'etree' from 'markdown.util' (/home/morfal/.local/lib/python3.8/site-packages/markdown/util.py)

Le problème vient bien de mdv qui n'est pas compatible avec markdown>=3.4 apparemment.
Je peux épingler markdown==3.3.7 temporairement pour résoudre le problème.
Merci en tout cas.

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