tcsteg_v1


SUBMITTED BY: Guest

DATE: Feb. 9, 2013, 11:51 p.m.

FORMAT: Python

SIZE: 10.6 kB

HITS: 1479

  1. #!/usr/bin/env python
  2. """
  3. tcsteg -- TrueCrypt real steganography tool
  4. version 1.0 (2011-02-24)
  5. by Martin J. Fiedler <martin.fiedler@gmx.net>
  6. This software is published under the terms of KeyJ's Research License,
  7. version 0.2. Usage of this software is subject to the following conditions:
  8. 0. There's no warranty whatsoever. The author(s) of this software can not
  9. be held liable for any damages that occur when using this software.
  10. 1. This software may be used freely for both non-commercial and commercial
  11. purposes.
  12. 2. This software may be redistributed freely as long as no fees are charged
  13. for the distribution and this license information is included.
  14. 3. This software may be modified freely except for this license information,
  15. which must not be changed in any way.
  16. 4. If anything other than configuration, indentation or comments have been
  17. altered in the code, the original author(s) must receive a copy of the
  18. modified code.
  19. To generate a steganographic TrueCrypt/QuickTime hybrid using the script, do the following:
  20. Find a good candidate QuickTime or MP4 file to use as a disguise, let’s say SomeVideo.mp4. The file should be encoded in a very efficient way so that an increase in filesize is believable in relation to the length and quality of the video. Estimate how much larger the file may become without becoming suspicious.
  21. Use TrueCrypt’s Volume Creating Wizard to create a new hidden(!) TrueCrypt volume.
  22. Use the name of the final hybrid file as the container file name, e.g. InnocentLookingVideo.mp4.
  23. As the outer volume size, enter the estimated maximum enlargment from step 1.
  24. Don’t bother entering a good password for the outer volume, it will be destroyed anyway.
  25. Use the maximum possible size for the hidden volume. Enter the size in KB instead of MB and do a bit of number guessing – the »Next« button in the wizard is disabled when the size is too large. Find the maximum size where the button is still clickable. (Technically, you could enter lower values, but why should you? Every byte left to the outer volume is a wasted byte!)
  26. Use your real ultra-secret password or keyfile for the hidden volume.
  27. Do not mount the outer volume! You will likely destroy the hidden volume otherwise.
  28. Use the script:
  29. python tcsteg.py SomeVideo.mp4 InnocentLookingVideo.mp4
  30. This will modify the TrueCrypt container file in-place. It might still take a while to process, since the disguise file is basically copied over into the container file.
  31. If everything worked, you will now have a file that
  32. looks like a video file in every way
  33. can be played as a video file using normal video player applications
  34. can still be mounted in TrueCrypt as a hidden volume
  35. is very hard to detect as a hybrid file
  36. """
  37. import sys, os, array
  38. def r_u32(s, offs=0):
  39. return (ord(s[offs]) << 24) | (ord(s[offs+1]) << 16) | (ord(s[offs+2]) << 8) | ord(s[offs+3])
  40. def w_u32(i):
  41. return chr((i >> 24) & 0xFF) + chr((i >> 16) & 0xFF) + chr((i >> 8) & 0xFF) + chr(i & 0xFF)
  42. LITTLE_ENDIAN = array.array('h', "\x00\x01").tolist()[0] - 1
  43. def copy_block(f_src, f_dest, size):
  44. BLOCKSIZE = 65536
  45. while size > 0:
  46. bytes = size
  47. if bytes > BLOCKSIZE:
  48. bytes = BLOCKSIZE
  49. block = f_src.read(bytes)
  50. if not block:
  51. break
  52. f_dest.write(block)
  53. size -= len(block)
  54. return size
  55. class ProcessingError(RuntimeError):
  56. pass
  57. ################################################################################
  58. class QTFileAtom:
  59. def __init__(self, atom_or_file=None, offset=0, size=0):
  60. self.offset = offset
  61. if isinstance(atom_or_file, basestring):
  62. self.atom = atom_or_file
  63. self.size = size
  64. else:
  65. atom_or_file.seek(offset)
  66. s = atom_or_file.read(8)
  67. if len(s) < 8:
  68. raise EOFError("end of file reached")
  69. self.size = r_u32(s)
  70. self.atom = s[4:]
  71. if not self.size:
  72. atom_or_file.seek(0, 2)
  73. self.size = atom_or_file.tell() - offset
  74. elif self.size == 1:
  75. raise ValueError("64-bit files are not supported")
  76. self.end = self.offset + self.size
  77. def read(self, f):
  78. f.seek(self.offset)
  79. data = f.read(self.size)
  80. if len(data) != self.size:
  81. raise IOError("unexpected end of file")
  82. return data
  83. def copy(self, f_src, f_dest, payload_only=False):
  84. offset = self.offset
  85. size = self.size
  86. if payload_only:
  87. offset += 8
  88. size -= 8
  89. f_src.seek(offset)
  90. if copy_block(f_src, f_dest, size):
  91. raise IOError("unexpected end of file")
  92. def copy_moov(self, f_src, f_dest, offset_adjust=0):
  93. moov = self.read(f_src)
  94. stco_pos = 0
  95. while True:
  96. stco_pos = moov.find("stco\0\0\0\0", stco_pos + 5) - 4
  97. if stco_pos <= 0:
  98. break
  99. stco_size = r_u32(moov, stco_pos)
  100. stco_count = r_u32(moov, stco_pos + 12)
  101. if stco_size < (stco_count * 4 + 16):
  102. continue # invalid stco size, maybe a false positive
  103. start = stco_pos + 16
  104. end = start + stco_count * 4
  105. data = array.array('I', moov[start:end])
  106. if LITTLE_ENDIAN: data.byteswap()
  107. try:
  108. data = array.array('I', [x + offset_adjust for x in data])
  109. except OverflowError:
  110. continue # invalid offset, maybe a false positive
  111. if LITTLE_ENDIAN: data.byteswap()
  112. moov = moov[:start] + data.tostring() + moov[end:]
  113. f_dest.write(moov)
  114. def __repr__(self):
  115. return "QTFileAtom(%r, %d, %d)" % (self.atom, self.offset, self.size)
  116. def AnalyzeQT(f):
  117. relevant_atoms = ('ftyp', 'moov', 'mdat')
  118. atoms = {}
  119. offset = 0
  120. while True:
  121. try:
  122. atom = QTFileAtom(f, offset)
  123. except EOFError:
  124. break
  125. if atom.atom in relevant_atoms:
  126. if atom.atom in atoms:
  127. raise ValueError("duplicate %r atom" % atom.atom)
  128. atoms[atom.atom] = atom
  129. elif not(atom.atom in ('free', 'wide', 'uuid')):
  130. print >>sys.stderr, "WARNING: unknown atom %r, ignoring" % atom.atom
  131. offset = atom.end
  132. try:
  133. return tuple([atoms[a] for a in relevant_atoms])
  134. except KeyError:
  135. raise ValueError("missing '%s' atom" % atom)
  136. def TCSteg_Embed_QT(f_src, f_dest):
  137. "QuickTime / ISO MPEG-4 ### mov,qt,mp4,m4v,m4a"
  138. try:
  139. ftyp, moov, mdat = AnalyzeQT(f_src)
  140. except (IOError, ValueError), e:
  141. print >>sys.stderr, "Error reading the source file:", e
  142. return 1
  143. if ftyp.size > (65536 - 8):
  144. raise ProcessingError("'ftyp' atom too long")
  145. # copy main data
  146. f_dest.seek(0, 2)
  147. eof_pos = f_dest.tell() - 131072
  148. if eof_pos <= 131072:
  149. raise ProcessingError("TrueCrypt file too small")
  150. f_dest.seek(eof_pos)
  151. if (eof_pos + mdat.size - 8 + moov.size) >= (2L**32):
  152. raise ProcessingError("files too large (must be less than 4 GiB)")
  153. mdat.copy(f_src, f_dest, payload_only=True)
  154. mdat_end = f_dest.tell()
  155. moov.copy_moov(f_src, f_dest, eof_pos - mdat.offset - 8)
  156. # re-generate first 64 KiB
  157. head = ftyp.read(f_src) + "\0\0\0\x08free"
  158. head += w_u32(mdat_end - len(head)) + "mdat"
  159. f_dest.seek(0)
  160. f_dest.write(head)
  161. remain = 65536 - len(head)
  162. if remain >= 0:
  163. f_src.seek(mdat.offset + 8)
  164. f_dest.write(f_src.read(remain))
  165. return 0
  166. ################################################################################
  167. def __get_fmts():
  168. return [item for name, item in globals().iteritems() if name.lower().startswith('tcsteg_embed_')]
  169. FORMATS = __get_fmts()
  170. if __name__ == "__main__":
  171. try:
  172. f_src = sys.argv[1]
  173. f_dest = sys.argv[2]
  174. except IndexError:
  175. print "Usage:", sys.argv[0], "<INPUT> <OUTPUT>"
  176. print "Embeds a file into a TrueCrypt container so that both are still readable."
  177. print
  178. print "<INPUT> is a file in one of the following formats:"
  179. fmtlist = []
  180. for fmt in FORMATS:
  181. name, exts = fmt.__doc__.split("###", 1)
  182. exts = exts.strip().lower().split(',')
  183. exts.sort()
  184. fmtlist.append((name.strip(), exts))
  185. maxlen = max([len(name) for name, exts in fmtlist])
  186. for name, exts in fmtlist:
  187. print " %s (%s)" % (name.ljust(maxlen), ", ".join(["*."+ext for ext in exts]))
  188. print
  189. print "<OUTPUT> is a TrueCrypt container containing a hidden volume. The file will be"
  190. print "modified in-place so that it seems like a copy of the input file that can be"
  191. print "opened in an appropriate viewer/player. However, the hidden TrueCtype volume"
  192. print "will also be preserved and can be used."
  193. print
  194. main = None
  195. ext = os.path.splitext(f_src)[-1][1:].lower()
  196. for fmt in FORMATS:
  197. if ext in fmt.__doc__.split("###", 1)[-1].strip().lower().split(','):
  198. main = fmt
  199. if not main:
  200. print >>sys.stderr, "Error: input file format is not supported"
  201. sys.exit(1)
  202. try:
  203. f_src = open(f_src, "rb")
  204. except IOError, e:
  205. print >>sys.stderr, "Error opening the input file:", e
  206. sys.exit(1)
  207. try:
  208. f_dest = open(f_dest, "r+b")
  209. except IOError, e:
  210. print >>sys.stderr, "Error opening the output file:", e
  211. sys.exit(1)
  212. try:
  213. sys.exit(main(f_src, f_dest))
  214. except ProcessingError, e:
  215. print >>sys.stderr, "Error:", e
  216. sys.exit(1)

comments powered by Disqus