summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Weiss2018-10-24 22:48:32 -0700
committerMaximilian Weiss2018-10-24 22:48:32 -0700
commit7c313eb86cccf726c9ce1843ecd7f64d30fb19d2 (patch)
tree532dbea9816996295e4ff218fe739d278c4917ef
downloadaur-7c313eb86cccf726c9ce1843ecd7f64d30fb19d2.tar.gz
Initial Upload
-rw-r--r--.SRCINFO22
-rw-r--r--PKGBUILD26
-rwxr-xr-xvcsteg.py368
3 files changed, 416 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..38cfd78c8c1e
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,22 @@
+pkgbase = vcsteg
+ pkgdesc = Real Steganography with TrueCrypt
+ pkgver = 2.0
+ pkgrel = 1
+ url = http://keyj.emphy.de/real-steganography-with-truecrypt/
+ arch = any
+ license = custom:FOSS
+ depends = python2
+ provides = vcsteg
+ conflicts = tcsteg
+ conflicts = tcsteg2
+ conflicts = tcsteg.py
+ conflicts = tcsteg2.py
+ conflicts = vcsteg
+ conflicts = vcsteg2
+ conflicts = vcsteg.py
+ conflicts = vcsteg2.py
+ source = vcsteg.py
+ sha256sums = 19130a705f2337dccd5f9633576d3f616b56fa47b909a2ac6005796eec82f7a7
+
+pkgname = vcsteg
+
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..79dbc8bba70e
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,26 @@
+# Maintainer: Maximilian Weiss <$(echo "bWF4QG1heHdlaXNzLmlv" | base64 -d)>
+# Contributor: Martin Fiedler <$(echo "a2V5akBlbXBoeS5kZQ==" | base64 -d)>
+# Contributor: Vladimir Ivanov <$(echo "dmxhZGltaXJpdmFub3Y4MTVAZ21haWwuY29t" | base64 -d)>
+
+pkgname=vcsteg
+pkgver=2.0
+pkgrel=1
+pkgdesc='Real Steganography with TrueCrypt'
+arch=('any')
+url='http://keyj.emphy.de/real-steganography-with-truecrypt/'
+# See python file for license
+license=('custom:FOSS')
+
+depends=('python2')
+
+provides=('vcsteg')
+conflicts=('tcsteg' 'tcsteg2' 'tcsteg.py' 'tcsteg2.py' 'vcsteg' 'vcsteg2' 'vcsteg.py' 'vcsteg2.py')
+
+source=('vcsteg.py')
+sha256sums=('19130a705f2337dccd5f9633576d3f616b56fa47b909a2ac6005796eec82f7a7')
+
+package() {
+ cd "$srcdir/"
+ install -Dm755 "vcsteg.py" "$pkgdir/usr/bin/vcsteg"
+}
+
diff --git a/vcsteg.py b/vcsteg.py
new file mode 100755
index 000000000000..7a1a335ae0d2
--- /dev/null
+++ b/vcsteg.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python2
+"""
+vcsteg2 -- VeraCrypt real steganography tool
+version 2.0 (2012-02-18)
+by Vladimir Ivanov <vladimirivanov815@gmail.com>
+and Martin J. Fiedler <martin.fiedler@gmx.net>
+
+see: http://keyj.emphy.de/real-steganography-with-truecrypt
+
+This software is published under the terms of KeyJ's Research License,
+version 0.2. Usage of this software is subject to the following conditions:
+0. There's no warranty whatsoever. The author(s) of this software can not
+ be held liable for any damages that occur when using this software.
+1. This software may be used freely for both non-commercial and commercial
+ purposes.
+2. This software may be redistributed freely as long as no fees are charged
+ for the distribution and this license information is included.
+3. This software may be modified freely except for this license information,
+ which must not be changed in any way.
+4. If anything other than configuration, indentation or comments have been
+ altered in the code, the original author(s) must receive a copy of the
+ modified code.
+
+Version history
+===============
+
+2.0 (Vladimir Ivanov, speed optimizations by Martin Fiedler)
+- now supports files over 4 GiB
+- erases duplicate encoder signature
+- auto-renames VeraCrypt container
+- supports 3gp videos
+- function allowing post-embed password change
+
+1.0 (Martin Fiedler)
+- initial release
+"""
+import sys, os, struct
+
+MAX_BUFFER_SIZE = 67108864 # 64 MiB
+TC_HEADER_SIZE = 65536 # 64 KiB
+MAX_INT32 = 4294967295
+MAX_INT64 = 18446744073709551615L
+
+class ProcessingError(RuntimeError):
+ pass
+
+################################################################################
+
+class Atom(object):
+ def __init__(self, f_src, name, start, header_size, size, mother):
+ self.f_src = f_src
+ self.name = name
+ self.start = start
+ self.size = size
+ self.header_size = header_size
+ self.mother = mother
+ self.childs = []
+ self.contents = None
+
+ def setBodySize(self, bodySize):
+ oldBodySize = self.size - self.header_size
+ bodyDiff = bodySize - oldBodySize
+ hDiff = 0
+ if bodySize <= MAX_INT32:
+ if self.header_size != 8:
+ self.header_size = 8
+ hDiff = -8
+ else:
+ if self.header_size != 16:
+ self.header_size = 16
+ hDiff = 8
+ self.size = self.header_size + bodySize
+ if self.mother:
+ oldParentBodySize = self.mother.size - self.mother.header_size
+ self.mother.setBodySize(oldParentBodySize + hDiff + bodyDiff)
+ def writeHeader(self, f_dest):
+ if self.size >= MAX_INT32 and self.header_size == 8:
+ raise ProcessingError("Atom size too large for compact header")
+ # compact
+ if self.size <= MAX_INT32 and self.header_size == 8:
+ f_dest.write(struct.pack(">I4s", self.size, self.name))
+ # extended
+ else:
+ f_dest.write(struct.pack(">I4sQ", 1, self.name, self.size))
+ return self.size - self.header_size
+
+ def writePayload(self, f_dest):
+ if self.childs:
+ for atom in self.childs:
+ atom.write(f_dest)
+ else:
+ dataBuffer = None
+ bodySize = self.size - self.header_size
+ if self.f_src:
+ self.f_src.seek(self.start + self.header_size)
+ percent_i = 0
+ percent_f = 0.0
+ if bodySize > MAX_BUFFER_SIZE:
+ percent_incr = 100.0 * MAX_BUFFER_SIZE / bodySize
+ else:
+ percent_incr = 0.0
+ while bodySize > 0:
+ if bodySize > MAX_BUFFER_SIZE:
+ dataBuffer = self.f_src.read(MAX_BUFFER_SIZE)
+ else:
+ dataBuffer = self.f_src.read(bodySize)
+ f_dest.write(dataBuffer)
+ bodySize -= MAX_BUFFER_SIZE
+ percent_f += percent_incr
+ percent_i_new = min(100, int(percent_f))
+ if percent_i_new > percent_i:
+ percent_i = percent_i_new
+ sys.stderr.write("%3d%% done\r" % percent_i)
+ sys.stderr.flush()
+ elif self.contents:
+ if bodySize == len(self.contents):
+ f_dest.write(self.contents)
+ else:
+ raise ProcessingError("Atom content size does not equal body size")
+ else:
+ if bodySize > 0:
+ f_dest.seek(bodySize - 1, 1)
+ byte = f_dest.read(1)
+ if not byte:
+ f_dest.write("\0")
+ else:
+ f_dest.seek(-1, 1)
+ f_dest.write(byte)
+
+ def write(self, f_dest):
+ self.writeHeader(f_dest)
+ self.writePayload(f_dest)
+
+################################################################################
+
+def AnalyseFile(f):
+ atoms = None
+ try:
+ atoms = parseAtoms(f, 0, os.fstat(f.fileno()).st_size, None)
+ except Exception, e:
+ raise ProcessingError("Parse Error: " + str(e))
+ return atoms
+
+def parseAtoms(f, start, end, mother):
+ offset = start
+ atomSize = None
+ atomHeaderSize = None
+ comrades = []
+ try:
+ while offset < end:
+ f.seek(offset)
+ atomSize = struct.unpack(">I", f.read(4))[0]
+ atomType = struct.unpack(">4s", f.read(4))[0]
+ if atomSize == 1:
+ atomSize = struct.unpack(">Q", f.read(8))[0]
+ atomHeaderSize = 16 # Extended
+ else:
+ atomHeaderSize = 8 # Compact
+ if atomSize == 0:
+ atomSize = end - offset
+ if start + atomSize > end:
+ raise ProcessingError("Invalid size for atom '" + atomType + "' @ " + hex(offset))
+ atom = Atom(f, atomType, offset, atomHeaderSize, atomSize, mother)
+ if mother:
+ mother.childs.append(atom)
+ comrades.append(atom)
+ if atomType in ["moov","trak","mdia","minf","stbl"]:
+ atom.childs = parseAtoms(f, offset + atomHeaderSize, offset + atomSize, atom)
+ offset = offset + atomSize
+ except struct.error, e:
+ raise ProcessingError("Atom header must be multiples 4 or 8 near " + hex(offset))
+ except Exception, e:
+ raise ProcessingError(str(e))
+ return comrades
+
+def findAtom(atoms, name):
+ aList = []
+ for a in atoms:
+ if a.name == name:
+ aList.append(a)
+ aList = aList + findAtom(a.childs, name)
+ return aList
+
+def printAtoms(atoms, l=0):
+ for a in atoms:
+ print "%s %s %ld @ 0x%lx" % (" "*l, a.name, a.size, a.start)
+ printAtoms(a.childs,l+1)
+
+def adjustSampleOffsets(atoms, offset):
+ sampleAtoms = findAtom(atoms, "stco") + findAtom(atoms, "co64")
+ if len(sampleAtoms) == 0:
+ raise ProcessingError("Could not find any 'stco' or 'co64' atoms")
+ for sAtom in sampleAtoms:
+ sAtom.f_src.seek(sAtom.start + sAtom.header_size)
+ verFlags, count = struct.unpack(">II", sAtom.f_src.read(8))
+ if sAtom.name == "stco":
+ sampleOffsets = struct.unpack('>' + 'I' * count, sAtom.f_src.read(count * 4))
+ elif sAtom.name == "co64":
+ sampleOffsets = struct.unpack('>' + 'Q' * count, sAtom.f_src.read(count * 8))
+ sampleOffsets = [x + offset for x in sampleOffsets]
+ # Does the atom need to support 64-bit values?
+ if max(sampleOffsets) > MAX_INT32 and sAtom.name == "stco":
+ sAtom.name = "co64"
+ sAtom.contents = struct.pack(">II", verFlags, count)
+ if sAtom.name == "stco":
+ sAtom.contents += struct.pack('>' + 'I' * count, *sampleOffsets)
+ elif sAtom.name == "co64":
+ sAtom.contents += struct.pack('>' + 'Q' * count, *sampleOffsets)
+ if (sAtom.size - sAtom.header_size) != len(sAtom.contents):
+ sAtom.setBodySize(len(sAtom.contents))
+ sAtom.f_src = None
+ return min(sampleOffsets)
+
+def TCSteg_Embed(atoms, tcFile):
+ ftyp = findAtom(atoms, "ftyp")
+ mdat = findAtom(atoms, "mdat")
+ moov = findAtom(atoms, "moov")
+ if len(ftyp) != 1 or len(mdat) != 1 or len(moov) != 1:
+ printAtoms(atoms)
+ raise ProcessingError("One of each type required to embed: ['ftyp','mdat','moov']\nWe do not support this.")
+ ftyp = ftyp[0]
+ mdat = mdat[0]
+ moov = moov[0]
+ tcFileSize = os.fstat(tcFile.fileno()).st_size
+ tcPreservedSize = tcFileSize - (TC_HEADER_SIZE * 3)
+ tcStartHeaderVolBackup = tcFileSize - (TC_HEADER_SIZE * 2)
+ mdatRealBodySize = mdat.size - mdat.header_size
+ mdatEndMarker = tcFileSize - (TC_HEADER_SIZE * 2) + (mdatRealBodySize)
+ mdatNewSize = mdatEndMarker - ftyp.size
+ tcFile.seek(0)
+ if ftyp.size + 16 > TC_HEADER_SIZE:
+ raise ProcessingError("'ftyp' atom + 'mdat' headers too long")
+ ftyp.write(tcFile)
+ tempH = mdat.header_size
+ tempL = mdat.size
+ if mdatNewSize <= MAX_INT32:
+ Atom(None, "free", None, 8, 8, None).write(tcFile)
+ mdatNewSize = mdatNewSize - 8
+ mdat.size = mdatNewSize
+ mdat.header_size = 8
+ mdat.writeHeader(tcFile)
+ else:
+ mdat.size = mdatNewSize
+ mdat.header_size = 16
+ mdat.writeHeader(tcFile)
+ mdat.header_size = tempH
+ mdat.size = tempL
+
+ # re-generate first 64 KiB
+ voidRegionSize = TC_HEADER_SIZE - tcFile.tell()
+ mdat.f_src.seek(mdat.start + mdat.header_size)
+ tcFile.write(mdat.f_src.read(voidRegionSize))
+
+ # start header volume backups. Last 128 KiB of tc_file
+ tcFile.seek(tcStartHeaderVolBackup)
+
+ # Mark the position of the real mdat sample start
+ mdatOffset = tcFile.tell() - (mdat.start + mdat.header_size)
+ mdat.writePayload(tcFile)
+ if tcFile.tell() != mdatEndMarker:
+ raise ProcessingError("Wrote more mdat than we should have")
+
+ # fix mdat shift by offsetting to each sample chunk
+ print "Fixing up hybrid file ..."
+ firstSample = adjustSampleOffsets(atoms, mdatOffset)
+
+ # Destory duplicate encoder signature before first sample.
+ tcFile.seek(tcStartHeaderVolBackup)
+ tcFile.write(os.urandom(min(firstSample - tcStartHeaderVolBackup, TC_HEADER_SIZE)))
+ tcFile.seek(mdatEndMarker)
+ moov.write(tcFile)
+
+def Pass_Helper(video_path):
+ f = None
+ try:
+ f = open(video_path, "rb+")
+ last = AnalyseFile(f)[-1]
+ if last.name == "skip":
+ print "Removing padding 'skip' atom"
+ f.truncate(last.start)
+ print "Removal completed successfully"
+ else:
+ print "Preparing hybrid file for password change ... "
+ f.seek(0, 2)
+ Atom(None, "skip", None, 8, 8 + TC_HEADER_SIZE * 2, None).write(f)
+ print "Complete. Now change the VeraCrypt password"
+ except IndexError:
+ pass
+ except IOError:
+ print >>sys.stderr, "Error opening file '"+video_path+"'"
+ except Exception, e:
+ print >>sys.stderr, str(e)
+ if f:
+ f.close()
+
+################################################################################
+
+if __name__ == "__main__":
+ supported_formats = ["mov","qt","mp4","m4v","m4a","3gp"]
+ if len(sys.argv) < 3:
+ pname = sys.argv[0].split(os.sep)[-1]
+ print "too few arguments"
+ print "Usage (1):", pname, "<MP4 Video> <VeraCrypt Container>"
+ print "Embeds a file into a VeraCrypt container so that both are still readable."
+ print
+ print "<MP4 Video> is a file in one of the following formats:"
+ print " QuickTime / ISO MPEG-4 (%s)" % (", ".join(["*." + fmt for fmt in supported_formats]))
+ print
+ print "<VeraCrypt Container> is a VeraCrypt hidden volume. The file will be"
+ print "modified in-place so that it seems like a copy of the input file that can be"
+ print "opened in an appropriate viewer/player. However, the hidden VeraCrypt volume"
+ print "will also be preserved and can be used."
+ print
+ print
+ print "Usage (2):", pname, "-p <Hybrid File>"
+ print "<Hybrid File> is a file that is both VeraCrypt container and a video."
+ print "This file will be modified in-place to make it possible to change the VeraCrypt"
+ print "password. After changing the password, this command should be run again to"
+ print "remove that (detectable and hence insecure) modification!"
+ print
+ print
+ sys.exit(2)
+
+ if sys.argv[1] == "-p":
+ Pass_Helper(sys.argv[2])
+ sys.exit(0)
+ video_path = sys.argv[1]
+ tc_path = sys.argv[2]
+ video_file = None
+ tc_file = None
+ tcSize = 0
+ try:
+ video_file = open(video_path, "rb")
+ except IOError, e:
+ print >>sys.stderr, "Error opening file '"+video_path+"'"
+ sys.exit(1)
+ try:
+ tc_file = open(tc_path, "rb+")
+ tcSize = os.path.getsize(tc_path)
+ except IOError, e:
+ print >>sys.stderr, "Error opening file '"+tc_path+"'"
+ sys.exit(1)
+ try:
+ video_ext = os.path.splitext(video_path)[1].lstrip(".")
+ if video_ext in supported_formats:
+ print "Parsing video ..."
+ atoms = AnalyseFile(video_file)
+ print "Embedding ... be patient"
+ TCSteg_Embed(atoms, tc_file)
+ tc_file.close()
+ if not tc_path.endswith("." + video_ext):
+ if not os.path.exists(tc_path + "." + video_ext):
+ new_tc_path = tc_path + "." + video_ext
+ os.rename(tc_path, new_tc_path)
+ tc_path = new_tc_path
+ print "Hybrid file '%s' was created successfully." % tc_path
+ print
+ print "Everything OK. Try mounting the file in VeraCrypt and playing the video."
+ else:
+ print >>sys.stderr, "Error: input video format is not supported"
+ except (ProcessingError, IOError), e:
+ print >>sys.stderr, "ERROR:", e
+ tc_file.truncate(tcSize)
+ finally:
+ video_file.close()
+ tc_file.close()
+
+