From f41973a69bb05c20c8c128349a87807c8399e15e Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Tue, 15 Aug 2017 17:37:12 +0200 Subject: [PATCH] implement PDTCRYPT header scanning MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit First phase: collect all possible header start locations. Adds a CLI subcommand “scan” to crypto.py for analyzing files. --- deltatar/crypto.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 71 insertions(+), 1 deletions(-) diff --git a/deltatar/crypto.py b/deltatar/crypto.py index 25cf8d9..bef3ced 100755 --- a/deltatar/crypto.py +++ b/deltatar/crypto.py @@ -132,6 +132,7 @@ import bisect import ctypes import io from functools import reduce, partial +import mmap import os import struct import sys @@ -508,6 +509,40 @@ def iv_fmt (iv): ############################################################################### +## restoration +############################################################################### + +class Location (object): + n = 0 + offset = 0 + +def restore_loc_fmt (loc): + return "%d off:%d" \ + % (loc.n, loc.offset) + +def locate_hdr_candidates (fd): + """ + Walk over instances of the magic string in the payload, collecting their + positions. If the offset of the first found instance is not zero, the file + begins with leading garbage. + + :return: The list of offsets in the file. + """ + cands = [] + + mm = mmap.mmap(fd, 0, mmap.MAP_SHARED, mmap.PROT_READ) + pos = 0 + while True: + pos = mm.find (PDTCRYPT_HDR_MAGIC, pos) + if pos == -1: + break + cands.append (pos) + pos += 1 + + return cands + + +############################################################################### ## passthrough / null encryption ############################################################################### @@ -1236,10 +1271,12 @@ _testing_set_PDTCRYPT_MAX_OBJ_SIZE = \ PDTCRYPT_SUB_PROCESS = 0 PDTCRYPT_SUB_SCRYPT = 1 +PDTCRYPT_SUB_SCAN = 2 PDTCRYPT_SUB = \ { "process" : PDTCRYPT_SUB_PROCESS - , "scrypt" : PDTCRYPT_SUB_SCRYPT } + , "scrypt" : PDTCRYPT_SUB_SCRYPT + , "scan" : PDTCRYPT_SUB_SCAN } PDTCRYPT_SECRET_PW = 0 PDTCRYPT_SECRET_KEY = 1 @@ -1565,6 +1602,30 @@ def mode_scrypt (pw, ins=None, nacl=None, fmt=PDTCRYPT_SCRYPT_INTRANATOR): print (out) +def mode_scan (pw, fname, nacl=None): + """ + Dissect a binary file, looking for PDTCRYPT headers and objects. + """ + try: + fd = os.open (fname, os.O_RDONLY) + except FileNotFoundError: + noise ("PDT: failed to open %s readonly" % fname) + noise ("") + usage (err=True) + + try: + if PDTCRYPT_VERBOSE is True: + noise ("PDT: scan for potential sync points") + cands = locate_hdr_candidates (fd) + if len (cands) == 0: + noise ("PDT: scan complete: input does not contain potential PDT " + "headers; giving up.") + return -1 + if PDTCRYPT_VERBOSE is True: + noise ("PDT: scan complete: found %d candidates" % len (cands)) + finally: + os.close (fd) + def usage (err=False): out = print @@ -1694,6 +1755,8 @@ def parse_argv (argv): noise ("PDT: scrypt output format “%s”" % scrypt_format) else: bail ("ERROR: unexpected positional argument “%s”" % arg) + elif subcommand == PDTCRYPT_SUB_SCAN: + pass if secret is None: if PDTCRYPT_VERBOSE is True: @@ -1715,6 +1778,13 @@ def parse_argv (argv): elif mode & PDTCRYPT_DECRYPT: bail ("ERROR: encryption requested but no password given") + if subcommand == PDTCRYPT_SUB_SCAN: + if insspec is None: + bail ("ERROR: please supply an input file for scanning") + if insspec == '-': + bail ("ERROR: input must be seekable; please specify a file") + return True, partial (mode_scan, secret [1].encode (), insspec, nacl) + if subcommand == PDTCRYPT_SUB_SCRYPT: if secret [0] == PDTCRYPT_SECRET_KEY: bail ("ERROR: scrypt mode requires a password") -- 1.7.1