Skip to content

Instantly share code, notes, and snippets.

@whutch
Last active February 12, 2018 23:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save whutch/8b9bfe57a6b29f326132c772dbef6e69 to your computer and use it in GitHub Desktop.
Save whutch/8b9bfe57a6b29f326132c772dbef6e69 to your computer and use it in GitHub Desktop.
Hide/find messages hidden in text indentation.
#!/usr/bin/env python
"""Hide/find messages hidden in text indentation."""
import argparse
from collections import deque
import os
from os.path import exists
# Command line parsing stuff.
PARSER = argparse.ArgumentParser(description="Hide/find messages hidden in text indentation.")
PARSER.add_argument("in_file", metavar="IN_FILE", help="The file to hide/find messages in")
PARSER.add_argument("message", metavar="MESSAGE", nargs="?", help="The message to hide in the file")
PARSER.add_argument("-o", metavar="OUT_FILE", dest="out_file",
help="An output path for modified files, defaults to appending"
" \"-hidden\" to IN_FILE")
PARSER.add_argument("-t", "--tab-width", metavar="N", default=4, type=int,
help="The number of spaces per indentation level in file")
ARGS = PARSER.parse_args()
BITS = (128, 64, 32, 16, 8, 4, 2, 1)
def tabify_line(line):
"""Convert space indentation to tabs."""
new_line = ""
index = 0
spaces = 0
tabs = 0
while line[index] in (" ", "\t"):
if line[index] == " ":
spaces += 1
if spaces >= ARGS.tab_width:
new_line += "\t"
tabs += 1
spaces = 0
else:
spaces = 0 # If there were spaces ahead of this tab,
# we want to strip them.
new_line += line[index]
tabs += 1
index += 1
if spaces:
new_line += " " * spaces
new_line += line[index:]
return new_line, tabs
def prep_file(in_file):
"""Convert a file to tabs."""
prepped_file = in_file + "-tmp"
total_tabs = 0
with open(in_file) as in_fd, open(prepped_file, "w") as out_fd:
for line in in_fd.readlines():
new_line, tabs = tabify_line(line)
out_fd.write(new_line)
total_tabs += tabs
return prepped_file, total_tabs
def hide_message_in_file(in_file, out_file, message):
"""Hide a message in a file's indentation."""
prepped_file, tabs = prep_file(in_file)
if tabs < len(message) * 8:
os.remove(in_file + "-tmp")
raise ValueError("message too long for amount of indentation in file")
bit_queue = deque()
for character in message:
ordinal = ord(character)
if ordinal > 255:
raise ValueError("character cannot be hidden: " + character)
for bit in BITS:
bit_queue.append(int(bool(ordinal & bit)))
with open(prepped_file) as in_fd, open(out_file, "w") as out_fd:
for line in in_fd.readlines():
new_line = ""
index = 0
while bit_queue and line[index] in (" ", "\t"):
if line[index] == "\t":
bit = bit_queue.popleft()
new_line += (" " * (bit + 1)) + "\t"
else:
new_line += " "
index += 1
new_line += line[index:]
out_fd.write(new_line)
os.remove(prepped_file)
def show_message_in_file(in_file):
"""Find a message hidden in a file's indentation."""
found_bits = []
with open(in_file) as in_fd:
for line in in_fd.readlines():
index = 0
spaces = 0
while line[index] in (" ", "\t"):
if line[index] == " ":
spaces += 1
else:
if spaces and spaces < 3:
found_bits.append(spaces - 1)
spaces = 0
index += 1
if len(found_bits) < 8:
print("No message found.")
return
message = ""
while found_bits:
if len(found_bits) < 8:
print("WARNING: number of bits found not multiple of 8")
break
byte = found_bits[:8]
found_bits = found_bits[8:]
ordinal = 0
for index, bit in enumerate(byte):
if bit:
ordinal += BITS[index]
message += chr(ordinal)
print("Found message:", message)
def start():
"""The main entrypoint for this script."""
if not exists(ARGS.in_file):
raise FileNotFoundError(ARGS.in_file)
if ARGS.message:
out_file = ARGS.out_file or ARGS.in_file + "-hidden"
hide_message_in_file(ARGS.in_file, out_file, ARGS.message)
else:
show_message_in_file(ARGS.in_file)
if __name__ == "__main__":
start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment