diff --git a/.gitignore b/.gitignore index 576f0db..2a996b1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ *~ __pycache__/ *.DS_Store - +vinetto.egg-info +.vscode +build \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3695d61..351870a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes to this project are documented in this file. + +## [0.9.9] - 2022-01-31 (RELEASED) + +### Changed + +- Updated all copyright notices and file versions +- Stanardized on Python 3--removed all Python 2 compatability code +- Minor function and class callout fixups +- ESEDB library + - Looks for a system ESEDB Python library before importing the Vinetto supplied version + - Stanardized the supplied ESEDB Python library on Python 3 + - Latest Python ESEDB library based on [libesedb](https://github.com/libyal/libesedb) commit 3326953 +- Added invert option (-i, --invert) to invert colors (negatives) for Type 1 images ## [0.9.8] - 2020-06-25 (DEVELOPEMENT) diff --git a/ReadMe.md b/ReadMe.md index 057838b..9ce75f5 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -5,15 +5,17 @@ these files. Based on the original Vinetto by Michel Roukine. This is a much needed update to the last original Vinetto (version 0.7). -This version should be compatible with Python 2 and 3. It should work on +This version should be compatible with Python 3. It should work on Linux, Mac, and Windows. Testing has currently been limited to Linux. +NOTE: Python 2 compatible code has been removed since version 0.9.9. + ## Project Overview 1. **Context** : Older Windows systems (98, ME, 2000, XP, and Server 2003) can store a thumb cache containing thumbnails and metadata of image files found in the directories of its FAT32 or NTFS filesystems. Newer Windows systems -(Vista, 7, 8, 10, other related Editions, and Server versions) use a unified +(Vista, 7, 8, 10, 11, other related Editions, and Server versions) use a unified thumb cache system for each user. 1. For older OS systems, thumbnails and associated metadata are stored in Thumbs.db files in each directory. Thumbs.db files are undocumented OLE @@ -83,11 +85,13 @@ liveCD like FCCU GNU/Linux Forensic Boot CD. ## Requirements -1. Python-2.3 or later including standard libraries. +1. Python 3.7 or later including standard libraries. + +2. Pillow 9.0.0 or later. Based on PIL (Python Imaging Library). Used to attempt +correct reconstitution of Type 1 thumbnails (see Limitations below). -2. PIL or Pillow. PIL (Python Imaging Library) 1.1.5 or later. Pillow is used -by the maintainer. PIL is used to attempt correct reconstitution of Type 1 -thumbnails (see Limitations below). +3. PyESEDB. The author suppiles a late model version, but the program checks for a +system installed version first. If not found, it uses the supplied version. ## Limitations @@ -116,14 +120,17 @@ times to fix the file. Vinetto has been tested on a modern Linux distribution. The code has been modified to use common Python packages and methods not specific to the Linux OS. Therefore, it should operate on BSD deriviatives, such as Darwin(R)(TM), -and Windows(R)(TM) OSes as well. YMMV. +and Windows(R)(TM) OSes as well. However, the supplied ESEDB Python library is +compiled on a Linux system. Compile and/or install the ESEDB Python lib for +your OS. YMMV. ## Usage Overview: ``` - usage: vinetto [-h] [-e EDBFILE] [-H] [-m [{f,d,r,a}]] [--md5] [--nomd5] - [-o DIR] [-q] [-s] [-U] [-v] [--version] - [infile] + Vinetto: Version 0.9.9 + usage: vinetto [-h] [-e EDBFILE] [-H] [-i] [-m [{f,d,r,a}]] [--md5] [--nomd5] + [-o DIR] [-q] [-s] [-U] [-v] [--version] + [infile] Vinetto.py - The Thumbnail File Parser @@ -139,6 +146,9 @@ and Windows(R)(TM) OSes as well. YMMV. NOTE: -e without an INFILE explores EDBFILE extracted data NOTE: Automatic mode will attempt to use ESEDB without -e -H, --htmlrep write html report to DIR (requires option -o) + -i, --invert Color invert Type 1 images. Some test Thumbs.db files showed + color negative images. If your Type 1 files need color inverting, + use this option. -m [{f,d,r,a}], --mode [{f,d,r,a}] operating mode: "f", "d", "r", or "a" where "f" indicates single file processing (default) @@ -206,7 +216,7 @@ and Windows(R)(TM) OSes as well. YMMV. Warnings are warning messages indicating processing issues Info are information messages indicating processing states - --- Vinetto.py 0.9.8 --- + --- Vinetto.py 0.9.9 --- Based on the original Vinetto by Michel Roukine Author: Keven L. Ates Vinetto.py is open source software diff --git a/src/vinetto/config.py b/src/vinetto/config.py index 1fc0587..f71ebae 100644 --- a/src/vinetto/config.py +++ b/src/vinetto/config.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -29,7 +29,7 @@ file_major = "0" file_minor = "1" -file_micro = "8" +file_micro = "9" OS_WIN_ESEDB_VISTA = "ProgramData/" diff --git a/src/vinetto/data/HtmlReportTemplate.html b/src/vinetto/data/HtmlReportTemplate.html index 4ac56a2..66b3cb2 100644 --- a/src/vinetto/data/HtmlReportTemplate.html +++ b/src/vinetto/data/HtmlReportTemplate.html @@ -6,7 +6,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. diff --git a/src/vinetto/error.py b/src/vinetto/error.py index e761a8d..ccb6118 100644 --- a/src/vinetto/error.py +++ b/src/vinetto/error.py @@ -6,7 +6,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -30,7 +30,8 @@ file_major = "0" file_minor = "1" -file_micro = "0" +file_micro = "1" + """ Vinetto Errors are categorized by the return exit codes. diff --git a/src/vinetto/esedb.py b/src/vinetto/esedb.py index 36b69de..7c4ebe6 100644 --- a/src/vinetto/esedb.py +++ b/src/vinetto/esedb.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -25,28 +25,20 @@ ----------------------------------------------------------------------------- """ -from __future__ import print_function file_major = "0" file_minor = "1" -file_micro = "7" +file_micro = "8" import sys from struct import unpack from binascii import hexlify, unhexlify -try: - import vinetto.config as config - import vinetto.utils as utils - import vinetto.error as verror - bLib3 = True -except ImportError: - import config - import utils - import error as verror - bLib3 = False +import vinetto.config as config +import vinetto.utils as utils +import vinetto.error as verror ############################################################################### @@ -140,16 +132,23 @@ def __init__(self): self.iCol[key] = None def prepare(self): + bEDBFileGood = False try: - if (bLib3): - from vinetto.lib import pyesedb - else: - from lib import pyesedb + import pyesedb + sys.stdout.write(" Info: Imported system pyesedb library.") bEDBFileGood = True except: - # Hard Error! The "pyesedb" library is installed locally with Vinetto, - # so missing "pyesedb" library is bad! - raise verror.InstallError(" Error (Install): Cannot import local library pyesedb") + sys.stdout.write(" Warning: Cannot import system pyesedb library!") + # Error! The "pyesedb" library is supposed to be installed locally with Vinetto, + try: + from vinetto.lib import pyesedb + sys.stdout.write(" Info: Imported Vinetto's pyesedb library.") + bEDBFileGood = True + except: + # Error! The "pyesedb" library is not found anywhere. + sys.stdout.write(" Warning: Cannot import Vinetto's pyesedb library!") + # A missing "pyesedb" library is bad! + raise verror.InstallError(" Error (Install): Cannot import a pyesedb library!") pyesedb_ver = pyesedb.get_version() if (config.ARGS.verbose > 0): @@ -269,7 +268,7 @@ def processRecord(self, recordESEDB, strKey): def load(self): if (self.iCol["TCID"] == None): if (config.ARGS.verbose >= 0): - sys.stderr.write(" Warning: No ESEDB Image column %s available\n" % ESEDB_ICOL_NAMES["TCID"][0]) + sys.stderr.write(" Warning: No ESEDB Image column %s available\n" % utils.ESEDB_ICOL_NAMES["TCID"][0]) self.table = None self.edbFile.close() self.edbFile = False @@ -277,9 +276,9 @@ def load(self): if (self.iCol["MIME"] == None and self.iCol["CTYPE"] == None and self.iCol["ITT"] == None): if (config.ARGS.verbose >= 0): sys.stderr.write(" Warning: No ESEDB Image columns %s available\n" % - (ESEDB_ICOL_NAMES["MIME"][0] + ", " + - ESEDB_ICOL_NAMES["CTYPE"][0] + ", or " + - ESEDB_ICOL_NAMES["ITT"][0])) + (utils.ESEDB_ICOL_NAMES["MIME"][0] + ", " + + utils.ESEDB_ICOL_NAMES["CTYPE"][0] + ", or " + + utils.ESEDB_ICOL_NAMES["ITT"][0])) self.table = None self.edbFile.close() self.edbFile = False @@ -476,10 +475,7 @@ def examine(self): import re import readline - try: - funcInput = raw_input - except NameError: - funcInput = input + funcInput = input def prompt(strMessage, strErrorMessage, isValid): # Prompt for input given a message and return that value after verifying the input. diff --git a/src/vinetto/lib/pyesedb.cpython-37m-x86_64-linux-gnu.so b/src/vinetto/lib/pyesedb.cpython-37m-x86_64-linux-gnu.so deleted file mode 100755 index 9aed53b..0000000 Binary files a/src/vinetto/lib/pyesedb.cpython-37m-x86_64-linux-gnu.so and /dev/null differ diff --git a/src/vinetto/lib/pyesedb.so b/src/vinetto/lib/pyesedb.so index db0bd2c..a92f015 100755 Binary files a/src/vinetto/lib/pyesedb.so and b/src/vinetto/lib/pyesedb.so differ diff --git a/src/vinetto/processor.py b/src/vinetto/processor.py index 82e5fde..9b75563 100644 --- a/src/vinetto/processor.py +++ b/src/vinetto/processor.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -25,34 +25,24 @@ ----------------------------------------------------------------------------- """ -from __future__ import print_function file_major = "0" file_minor = "1" -file_micro = "9" +file_micro = "10" import sys import os import fnmatch -try: - import vinetto.config as config - import vinetto.report as report - import vinetto.thumbOLE as thumbOLE - import vinetto.thumbCMMM as thumbCMMM - import vinetto.thumbIMMM as thumbIMMM - import vinetto.utils as utils - import vinetto.error as verror -except ImportError: - import config - import report - import thumbOLE - import thumbCMMM - import thumbIMMM - import utils - import error as verror +import vinetto.config as config +import vinetto.report as report +import vinetto.thumbOLE as thumbOLE +import vinetto.thumbCMMM as thumbCMMM +import vinetto.thumbIMMM as thumbIMMM +import vinetto.utils as utils +import vinetto.error as verror ############################################################################### @@ -95,14 +85,8 @@ def processThumbFile(self, infile, filenames = None): # Get MD5 of file... if (config.ARGS.md5force) or ((not config.ARGS.md5never) and (dictHead["FileSize"] < (1024 ** 2) * 512)): - try: - # Python >= 2.5 - from hashlib import md5 - dictHead["MD5"] = md5(fileThumbsDB.read()).hexdigest() - except: - # Python < 2.5 - import md5 - dictHead["MD5"] = md5.new(fileThumbsDB.read()).hexdigest() + from hashlib import md5 + dictHead["MD5"] = md5( fileThumbsDB.read() ).hexdigest() del md5 # ----------------------------------------------------------------------------- @@ -189,7 +173,7 @@ def processDirectory(self, thumbDir, filenames = None): # TODO: to check existing image file names against stored thumbnail IDs for thumbFile in tc_files: - processThumbFile(thumbFile, filenames) + self.processThumbFile(thumbFile, filenames) return @@ -197,7 +181,7 @@ def processDirectory(self, thumbDir, filenames = None): def processRecursiveDirectory(self): # Walk the directories from given directory recursively down... for dirpath, dirnames, filenames in os.walk(config.ARGS.infile): - processDirectory(dirpath, filenames) + self.processDirectory(dirpath, filenames) return @@ -225,7 +209,7 @@ def processFileSystem(self): if (config.ARGS.verbose >= 0): sys.stderr.write(" Warning: Skipping %s - does not contain %s\n" % (entryUserDir.path, config.OS_WIN_THUMBCACHE_DIR)) else: - processDirectory(userThumbsDir) + self.processDirectory(userThumbsDir) # XP # ============================================================ @@ -237,13 +221,13 @@ def processFileSystem(self): for entryUserDir in iterDirs: if not entryUserDir.is_dir(): continue - processDirectory(entryUserDir) + self.processDirectory(entryUserDir) # Other / Unidentified # ============================================================ else: if (config.ARGS.verbose > 0): sys.stderr.write(" Info: FS - Generic partition, processing all subdirectories (recursive operating mode)\n") - processDirectory(config.ARGS.infile) + self.processDirectory(config.ARGS.infile) return diff --git a/src/vinetto/report.py b/src/vinetto/report.py index 16c1295..c1ce0ff 100644 --- a/src/vinetto/report.py +++ b/src/vinetto/report.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -29,23 +29,17 @@ file_major = "0" file_minor = "4" -file_micro = "9" +file_micro = "10" from time import time from os.path import dirname, basename, abspath, getmtime from pkg_resources import resource_filename -try: - import vinetto.version as version - import vinetto.config as config - import vinetto.error as verror - import vinetto.utils as utils -except ImportError: - import version - import config - import error as verror - import utils +import vinetto.version as version +import vinetto.config as config +import vinetto.error as verror +import vinetto.utils as utils HTTP_HEADER = [] diff --git a/src/vinetto/tdb_catalog.py b/src/vinetto/tdb_catalog.py index 6d881b1..c884ed9 100644 --- a/src/vinetto/tdb_catalog.py +++ b/src/vinetto/tdb_catalog.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -29,13 +29,11 @@ file_major = "0" file_minor = "1" -file_micro = "4" +file_micro = "5" -try: - from collections.abc import MutableMapping - unicode = str -except ImportError: - from collections import MutableMapping +from collections.abc import MutableMapping + +unicode = str ############################################################################### diff --git a/src/vinetto/tdb_streams.py b/src/vinetto/tdb_streams.py index b300ddf..13d7cd8 100644 --- a/src/vinetto/tdb_streams.py +++ b/src/vinetto/tdb_streams.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -29,15 +29,15 @@ file_major = "0" file_minor = "1" -file_micro = "4" +file_micro = "5" -try: - from collections.abc import MutableMapping - import vinetto.config as config - unicode = str -except ImportError: - from collections import MutableMapping - import config + +import sys + +from collections.abc import MutableMapping +import vinetto.config as config + +unicode = str @@ -176,7 +176,9 @@ def getFileName(self, key, strExt): try: iVal = int(strIndexFileName[iMark + 1: ]) except ValueError: - raise Value("Stream invalid: Stream names must be extended with _# where # is an integer! Offender: %s" % strIndexFileName) + tb = sys.exc_info()[2] + strError = "Invalid Stream: Stream names must be extended with _# where # is an integer! Offender: {}".format(strIndexFileName) + raise ValueError(strError).with_traceback(tb) strComputedFileName = strIndexFileName[ :iMark + 1] + str(iVal + 1) # Add or append to self -- see __setitem__()... diff --git a/src/vinetto/thumbCMMM.py b/src/vinetto/thumbCMMM.py index 3c89486..0fe9445 100644 --- a/src/vinetto/thumbCMMM.py +++ b/src/vinetto/thumbCMMM.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -25,7 +25,6 @@ ----------------------------------------------------------------------------- """ -from __future__ import print_function file_major = "0" @@ -37,18 +36,11 @@ from io import StringIO from struct import unpack -try: - import vinetto.config as config - import vinetto.esedb as esedb - import vinetto.tdb_catalog as tdb_catalog - import vinetto.tdb_streams as tdb_streams - import vinetto.utils as utils -except ImportError: - import config - import esedb - import tdb_catalog - import tdb_streams - import utils +import vinetto.config as config +import vinetto.esedb as esedb +import vinetto.tdb_catalog as tdb_catalog +import vinetto.tdb_streams as tdb_streams +import vinetto.utils as utils def printHead(dictCMMMMeta): @@ -238,7 +230,7 @@ def process(infile, fileThumbsDB, iThumbsDBSize): # Setup symbolic link to filename... if (config.ARGS.symlinks): # ...implies config.ARGS.outdir strTarget = config.THUMBS_SUBDIR + "/" + strCleanFileName + "." + strExt - setSymlink(strTarget, config.ARGS.outdir + strFileName) + utils.setSymlink(strTarget, config.ARGS.outdir + strFileName) fileURL = open(config.ARGS.outdir + config.THUMBS_FILE_SYMS, "a+") fileURL.write(strTarget + " => " + strFileName + "\n") diff --git a/src/vinetto/thumbIMMM.py b/src/vinetto/thumbIMMM.py index 01d92ee..9369d6d 100644 --- a/src/vinetto/thumbIMMM.py +++ b/src/vinetto/thumbIMMM.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -25,27 +25,20 @@ ----------------------------------------------------------------------------- """ -from __future__ import print_function file_major = "0" file_minor = "1" -file_micro = "8" +file_micro = "9" import sys from struct import unpack -try: - import vinetto.config as config -# import vinetto.tdb_catalog as tdb_catalog - import vinetto.tdb_streams as tdb_streams - import vinetto.utils as utils -except ImportError: - import config -# import tdb_catalog - import tdb_streams - import utils +import vinetto.config as config +#import vinetto.tdb_catalog as tdb_catalog +import vinetto.tdb_streams as tdb_streams +import vinetto.utils as utils def printHead(dictIMMMMeta, iFileSize): @@ -325,5 +318,3 @@ def process(infile, fileThumbsDB, iThumbsDBSize, iInitialOffset = 0): if (config.ARGS.symlinks): # ...implies config.ARGS.outdir strSubDir = config.THUMBS_SUBDIR config.HTTP_REPORT.flush(astrStats, strSubDir) - - diff --git a/src/vinetto/thumbOLE.py b/src/vinetto/thumbOLE.py index 8db1516..5dd8906 100644 --- a/src/vinetto/thumbOLE.py +++ b/src/vinetto/thumbOLE.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -25,36 +25,27 @@ ----------------------------------------------------------------------------- """ -from __future__ import print_function file_major = "0" file_minor = "1" -file_micro = "10" +file_micro = "11" import sys import os import errno -from io import StringIO +from io import BytesIO from struct import unpack -from binascii import hexlify, unhexlify +from binascii import hexlify from pkg_resources import resource_filename -try: - import vinetto.config as config - import vinetto.esedb as esedb - import vinetto.tdb_catalog as tdb_catalog - import vinetto.tdb_streams as tdb_streams - import vinetto.utils as utils - import vinetto.error as verror -except ImportError: - import config - import esedb - import tdb_catalog - import tdb_streams - import utils - import error as verror +import vinetto.config as config +import vinetto.esedb as esedb +import vinetto.tdb_catalog as tdb_catalog +import vinetto.tdb_streams as tdb_streams +import vinetto.utils as utils +import vinetto.error as verror def preparePILOutput(): @@ -67,7 +58,7 @@ def preparePILOutput(): # Initializing PIL library for Type 1 image extraction... config.THUMBS_TYPE_OLE_PIL = False # ...attempting to load PIL.. try: - from PIL import Image + from PIL import Image, ImageOps config.THUMBS_TYPE_OLE_PIL = True # ...loaded PIL if (config.ARGS.verbose > 0): sys.stderr.write(" Info: Imported PIL for possible Type 1 exports\n") @@ -83,7 +74,7 @@ def preparePILOutput(): except: # Hard Error! The header, quantization, and huffman data files are installed # locally with Vinetto, so missing missing files are bad! - raise verror.InstallError(" Error (Install): Cannot load PIL support data files") + raise verror.InstallError(" Error: Cannot load PIL support data files!") return @@ -149,13 +140,12 @@ def printCache(strName, dictOLECache): def process(infile, fileThumbsDB, iThumbsDBSize): preparePILOutput() + from PIL import Image, ImageOps if (config.ARGS.verbose >= 0): if (iThumbsDBSize % 512 ) != 0: sys.stderr.write(" Warning: Length of %s == %d not multiple 512\n" % (infile, iThumbsDBSize)) - tDB_endian = "<" # Little Endian - # Structure: # -------------------- # The CFBF file consists of a 512-Byte header record followed by a number of @@ -176,6 +166,8 @@ def process(infile, fileThumbsDB, iThumbsDBSize): # * Stream Sector – contains arbitrary file data # * Range Lock Sector – contains the byte-range locking area of a large file + tDB_endian = "<" # Little Endian + fileThumbsDB.seek(8) # ...skip magic bytes # File Signature: 0xD0CF11E0A1B11AE1 for current version tDB_CLSID = str(hexlify( fileThumbsDB.read(16) ))[2:-1] # CLSID tDB_revisionNo = unpack(tDB_endian+"H", fileThumbsDB.read(2))[0] # Minor Version @@ -184,8 +176,9 @@ def process(infile, fileThumbsDB, iThumbsDBSize): tDB_endianOrder = fileThumbsDB.read(2) # 0xFFFE OR 0xFEFF # Byte Order, 0xFFFE (Intel) if (tDB_endianOrder == bytearray(b"\xff\xfe")): tDB_endian = ">" # Big Endian - #elif (tDB_endianOrder == bytearray(b"\xfe\xff")): - # tDB_endian = "<" + # Otherwise, it's Little Endian: + # (tDB_endianOrder == bytearray(b"\xfe\xff")) + # which was initialized above. tDB_SectorSize = unpack(tDB_endian+"H", fileThumbsDB.read(2))[0] # Sector Shift tDB_SectorSizeMini = unpack(tDB_endian+"H", fileThumbsDB.read(2))[0] # Mini Sector Shift @@ -450,6 +443,12 @@ def process(infile, fileThumbsDB, iThumbsDBSize): fileImg = open(config.ARGS.outdir + strFileName, "wb") fileImg.write(bstrStreamData[headOffset:]) fileImg.close() + + if (config.ARGS.verbose > 0): + print(" File Info: ---------------------------------------") + print(" Type: 2 (Full JPEG)") + print(" Name: %s" % strFileName) + else: # Not extracting... tdbStreams[keyStreamName] = config.LIST_PLACEHOLDER @@ -461,17 +460,68 @@ def process(infile, fileThumbsDB, iThumbsDBSize): if (config.ARGS.outdir != None and config.THUMBS_TYPE_OLE_PIL): strFileName = tdbStreams.getFileName(keyStreamName, strExt) + # DEBUG + #imageRaw = open(config.ARGS.outdir + strFileName + ".bin", "wb") + #imageRaw.write(bstrStreamData) + #imageRaw.close() + # -------------------------------------------------------------------------------- # Construct thumbnail image from standard blocks and stored image data... + # -------------------------------------------------------------------------------- + # + # [ 0: 8] Marker [0C 00 00 00 : 01 00 00 00] + # [ 8:12] Size of File 1 (SF1) from [12] to End Of File (little-endian) + # [12:16] Marker [01 00 00 00] + # [16:20] Size of File 2 (SF2) from [28] to End Of File (little-endian) + # [20:24] Frame Samples per Line (little-endian) + # [24:28] Frame Line Count (little-endian) + # [28:30] SOI [FF D8] + # [30:32] SOF [FF C0] (8 + 3*FCC Bytes) + # [32:34] Frame Length (FL) + # [34] Frame Precision + # [35:37] Frame Line Count + # [37:39] Frame Samples per Line + # [39] Frame Component Count (FCC: 3 Bytes Each) + # [40] FC1: Component ID + # [41H] FC1: Horiz Sample Factor: (bstrStreamData[41] >> 4) & 15 + # [41L] FC1: Vert Sample Factor: bstrStreamData[41] & 15 + # [42] FC1: Quantization Table Selector + # ... FC2-FCN + # [32+FL:34+FL] SOS [FF DA] + # [32+FL:34+FL] SOS [FF DA] + # [12+SF1-2:12+SF1-1] EOI [FF D9] + + iFileSize1 = int.from_bytes(bstrStreamData[ 8:12], 'little') + iFileSize2 = int.from_bytes(bstrStreamData[16:20], 'little') + iFileDiff = iFileSize1 - iFileSize2 + iFrameIndex = 30 + iFrameSize = int.from_bytes(bstrStreamData[32:34], 'big') + iScanIndex = iFrameIndex + 2 + iFrameSize + bstrImage = ( config.THUMBS_TYPE_OLE_PIL_TYPE1_HEADER[:20] + - config.THUMBS_TYPE_OLE_PIL_TYPE1_QUANTIZE + bstrStreamData[30:52] + - config.THUMBS_TYPE_OLE_PIL_TYPE1_HUFFMAN + bstrStreamData[52:] ) + config.THUMBS_TYPE_OLE_PIL_TYPE1_QUANTIZE + bstrStreamData[iFrameIndex:iScanIndex] + + config.THUMBS_TYPE_OLE_PIL_TYPE1_HUFFMAN + bstrStreamData[iScanIndex:] ) - image = Image.open(StringIO.StringIO(bstrImage)) - #r, g, b, a = image.split() - #image = Image.merge("RGB", (r, g, b)) + image = Image.open( BytesIO( bstrImage ) ) image = image.transpose(Image.FLIP_TOP_BOTTOM) - image.save(config.ARGS.outdir + strFileName, "JPEG", quality=100) + r, g, b, a = image.split() + imageRGB = Image.merge("RGB", (r, g, b)) + + if (config.ARGS.invert): + imageRGB = ImageOps.invert(imageRGB) + imageRGB.save(config.ARGS.outdir + strFileName, "JPEG", quality=100) + + if (config.ARGS.verbose > 0): + print(" File Info: ---------------------------------------") + print(" Type: 1 (JPEG Fragment)") + print(" Name: %s" % strFileName) + print(" Size 1: %d Bytes" % iFileSize1) + print(" Size 2: %d Bytes" % iFileSize2) + print(" Diff: %d Bytes == 16? %s" % (iFileDiff, (iFileDiff == 16))) + print(" Frame Start: Byte %d" % iFrameIndex) + print(" Frame Size: %d" % iFrameSize) + print(" Scan Start: Byte %d" % iScanIndex) + else: # Cannot extract (PIL not found) or not extracting... tdbStreams[keyStreamName] = config.LIST_PLACEHOLDER else: diff --git a/src/vinetto/utils.py b/src/vinetto/utils.py index be0fb26..7ac0198 100644 --- a/src/vinetto/utils.py +++ b/src/vinetto/utils.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -29,7 +29,7 @@ file_major = "0" file_minor = "4" -file_micro = "0" +file_micro = "2" from sys import version_info as py_version_info @@ -44,7 +44,6 @@ import config import error as verror - def convertWinToPyTime(iFileTime_Win32): # Convert Win32 timestamp to Python timestamp... SECS_BETWEEN_EPOCHS = 11644473600 @@ -70,14 +69,8 @@ def getFormattedWinToPyTimeUTC(iFileTime_Win32): def cleanFileName(strFileName): strInChars = "\\/:*?\"<>|" strOutChars = "_________" - try: - # Python >= 3 - dictTransTab = str.maketrans(strInChars, strOutChars) - return strFileName.translate(dictTransTab) - except: - # Python < 3 - dictTransTab = {ord(c): ord(t) for c, t in zip(unicode(strInChars), unicode(strOutChars))} - return strFileName.translate(dictTransTab) + dictTransTab = str.maketrans(strInChars, strOutChars) + return strFileName.translate(dictTransTab) def getEncoding(): @@ -90,18 +83,12 @@ def getEncoding(): #def reencodeBytes(bytesString): # # Convert bytes encoded as utf-16-le to the global encoding... -# if (py_version_info[0] < 3): -# return unicode(bytesString, "utf-16-le").encode(getEncoding(), "replace") -# else: -# return str(bytesString, "utf-16-le").encode(getEncoding(), "replace") +# return str(bytesString, "utf-16-le").encode(getEncoding(), "replace") def decodeBytes(byteString): # Convert bytes encoded as utf-16-le to standard unicode... - if (py_version_info[0] < 3): - return unicode(str(byteString), "utf-16-le") - else: - return str(byteString, "utf-16-le") + return str(byteString, "utf-16-le") def prepareSymLink(): @@ -127,5 +114,3 @@ def setSymlink(strTarget, strLink): else: raise verror.LinkError(" Error (Symlink): Cannot create symlink " + strLink + " to file " + strTarget) return - - diff --git a/src/vinetto/version.py b/src/vinetto/version.py index 09f50d1..0b10409 100644 --- a/src/vinetto/version.py +++ b/src/vinetto/version.py @@ -5,7 +5,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -29,7 +29,7 @@ major = "0" minor = "9" -micro = "8" +micro = "9" maintainer = (("Keven L. Ates", "atescomp@gmail.com"), ) author = ("Keven L. Ates", "atescomp@gmail.com") diff --git a/src/vinetto/vinetto.py b/src/vinetto/vinetto.py index 3848d12..972f18e 100755 --- a/src/vinetto/vinetto.py +++ b/src/vinetto/vinetto.py @@ -6,7 +6,7 @@ Vinetto : a forensics tool to examine Thumb Database files Copyright (C) 2005, 2006 by Michel Roukine - Copyright (C) 2019-2020 by Keven L. Ates + Copyright (C) 2019-2022 by Keven L. Ates This file is part of Vinetto. @@ -30,7 +30,7 @@ file_major = "0" file_minor = "1" -file_micro = "10" +file_micro = "12" import sys @@ -39,20 +39,12 @@ import argparse import signal -try: - import vinetto.version as version - import vinetto.config as config - import vinetto.error as verror - import vinetto.processor as processor - import vinetto.esedb as esedb - import vinetto.utils as utils -except ImportError: - import version - import config - import error as verror - import processor - import esedb - import utils +import vinetto.version as version +import vinetto.config as config +import vinetto.error as verror +import vinetto.processor as processor +import vinetto.esedb as esedb +import vinetto.utils as utils def getArgs(): # Return arguments passed to vinetto on the command line... @@ -121,6 +113,10 @@ def getArgs(): "NOTE: Automatic mode will attempt to use ESEDB without -e")) parser.add_argument("-H", "--htmlrep", action="store_true", dest="htmlrep", help=("write html report to DIR (requires option -o)")) + parser.add_argument("-i", "--invert", action="store_true", dest="invert", + help=("Color invert Type 1 images. Some test Thumbs.db files showed\n" + + "color negative images. If your Type 1 files need color inverting,\n" + + "use this option.\n")) parser.add_argument("-m", "--mode", nargs="?", dest="mode", choices=["f", "d", "r", "a"], default="f", const="f", help=("operating mode: \"f\", \"d\", \"r\", or \"a\"\n" + @@ -316,6 +312,11 @@ def main(): # signal.signal(signal.SIGTERM, signal_handler) # signal.signal(signal.SIGQUIT, signal_handler) + sys.stdout.write( "Vinetto: Version {}\n".format(version.STR_VERSION) ) + if ( sys.version_info < (3, 0) ): + sys.stdout.write( "Vinetto (version {}) requires Python 3!\n".format(version.STR_VERSION) ) + sys.exit(1) + config.ARGS = getArgs() try: