All pastes #611996 Raw Edit

Anonymous

public php v1 · immutable
·published 2007-07-10 04:12 UTC
rendered paste body
#!/usr/bin/php<?php/** * iPhone Backup Extraction Utility * * @author Carlos Averett <caverett@corecodec.net> * @category Utility * @filesource * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License * @package interface * @uses Console_Getopt * @uses Console_Color * @version $Id$ (Used by SVN to automatically stamp files with their version #) * @copyright 2007, Carlos Averett */ // Pearrequire_once ('Pear/PEAR.php');require_once ('Pear/Console/Getargs.php');require_once ('Pear/Console/Color.php'); // Register command line options$config = array (    'extract' => array (        'short' => 'e',        'min' => 0,        'max' => 0,        'desc' => 'Extract files from [indir]'    ),    'compress' => array (        'short' => 'c',        'min' => 0,        'max' => 0,        'desc' => 'Compress files from [indir] into [outdir]'    ),    'indir' => array (        'short' => 'i',        'min' => 1,        'max' => 1,        'desc' => 'Specify the input directory for compressing or extracting',    ),    'outdir' => array (        'short' => 'o',        'min' => 1,        'max' => 1,        'desc' => 'Specify the output directory for compressing or extracting',    ),    'license' => array (        'short' => 'l',        'max' => 0,        'desc' => 'Display the license for this program'    ),    'obscure' => array (        'short' => 'b',        'max' => 0,        'desc' => 'Obscure identifying information in output.'    ),    'help' => array (        'short' => 'h',        'max' => 0,        'desc' => 'Display this help'    ));// =====// Handle license seperately// =====foreach ($_SERVER['argv'] as $arg) {    if ($arg == '-l' || $arg == '--license') {        // Dump a copy of the LGPL to stdin        exit (file_get_contents ('LICENSE'));    }}$cmd = $_SERVER['argv'][0];$args =& Console_Getargs::factory ($config);$header = Console_Color::convert (    "%riPhone Extraction Utility%n\n" .     "%r=========================%n\n" .    "Example: %p$cmd%n %g--extract%n %g--outdir=%n%b\"out\"%n \\\n" .     "    %g--indir=%n%b\"~/Library/Application Support/MobileSync/Backup/*\"%n \\\n" .    "\n%rOptions:%n\n");if (PEAR::isError($args)) {    if ($args->getCode() === CONSOLE_GETARGS_ERROR_USER) {        echo Console_Getargs::getHelp($config, $header, $args->getMessage()) . "\n";    } else if ($args->getCode() === CONSOLE_GETARGS_HELP) {        echo Console_Getargs::getHelp($config, $header)."\n";    }    exit ();}// Sanity checking$indir = $args->getValue ('indir');$outdir = $args->getValue ('outdir');$compress = $args->getValue ('compress');$extract = $args->getValue ('extract');$obscure = $args->getValue ('obscure');$noError = false;if (!is_dir ($indir)) {    usage_error ('Invalid indir.');} else if (!is_dir ($outdir)) {    usage_error ('Invalid indir.');} else if ((!$compress && !$extract) || ($compress && $extract)) {    usage_error ('You must either select compression or extraction.');} else if ($indir == $outdir) {    usage_error ('Indir and outdir must differ.');} else {     $noerror = true;}if (!$noerror) {    exit();}// =====// Meat of the program// =====// Make sure we always have the directory with a '/' on the end$indir = dirname ($indir . '/removeme.txt') . '/';$outdir = dirname ($outdir . '/removeme.txt') . '/';if ($extract) {    if (!file_exists ($indir . 'Manifest.plist')) {        fatal_error ('Unable to read Manifest');    } else if (!file_exists ($indir . 'Info.plist')) {        fatal_error ('Unable to read Info.plist');    }        // ===    // Info.plist    // ===    $info_file = $outdir . 'Info.plist';    file_write ($info_file, file_get_contents ($indir . 'Info.plist'));    $info = file_get_contents ($info_file);        $regex = '/<string>(.*)<\/string>/smiU';    $matches = array ();    preg_match_all ($regex, $info, $matches);    $matches = $matches[1];    if (count ($matches) != 6) {        fatal_error ('Invalid Info.plist');    }        $devicename = trim ($matches[1]);    $serial = obscure (trim ($matches[2]));    $deviceid = obscure (trim ($matches[3]));    $regex = '/<date>(.*)<\/date>/smiU';    $matches = array ();    preg_match_all ($regex, $info, $matches);    $matches = $matches[1];    if (count ($matches) != 1) {        fatal_error ('Invalid Info.plist');    }        $lastbackup = strtotime(trim(ereg_replace ('[TZ]', ' ', $matches[0])));    $backup_date = date ('F j, Y, g:i a', $lastbackup);      echo (Console_Color::convert (        "%rSuccessfully opened backup files.%n\n" .        "%r=================================%n\n" .        "Device Name: %b$devicename%n\n" .        "Device Serial: %b$serial%n\n" .        "Device ID: %b$deviceid%n\n" .        "Last Backup: %b$backup_date%n\n\n"));            // ===    // Manifest.plist    // ===    $manifest = file_get_contents ($indir . 'Manifest.plist');    $regex = '/<data>(.*)<\/data>/smiU';    $matches = array ();    preg_match_all ($regex, $manifest, $matches);    $matches = $matches[1];        if (count ($matches) != 3) {       fatal_error ('Invalid Manifest.plist');    }    $authdata = trim ($matches[0]);    $authsignature = trim ($matches[1]);    $data = trim ($matches[2]);    // Verify AuthData    if (base64_decode ($authdata) != 'Forty Two') {        fatal_error ('Invalid Manifest.plist (AuthData)');    }        // Verify AuthSignature    if (base64_sign ($data) != $authsignature) {        fatal_error ('Invalid Manifest.plist (AuthSignature)');    }        // Extract the internal manifest    $manifest = base64_decode ($data);    if (!$manifest) {        fatal_error ('Invalid Manifest.plist (Data)');    }    $manifest_file = $outdir . 'Manifest.xml';    file_write ($manifest_file, $manifest);    bpl2xml ($manifest_file);        $inner_manifest = file_get_contents ($manifest_file);    $regex = '/<key>Files<\/key>[^<]+<dict>(.*)<\/dict>[^<]+<key>Version<\/key>/smiU';    $matches = array();    preg_match_all ($regex, $inner_manifest, $matches);    if (count ($matches[1]) != 1) {        fatal_error ('Invalid Manifest');    }        $inner_manifest = $matches[1][0];        // Pull out the items in the inner manifest    $regex = '/<key>(.*)<\/key>[^<]+<dict>(.*)<\/dict>/smiU';    $matches = array();    preg_match_all ($regex, $inner_manifest, $matches);        $keys = $matches[1];    $values = $matches[2];    if (!count ($matches[1])) {        fatal_error ('Manifest contains no files');    } else if ( count ($keys) != count ($values) ) {        fatal_error ('Malformed inner manifest.');    }        $num_files = count ($keys);    echo (Console_Color::convert ("%rManifest contains $num_files files.\n%n"));    $tmpdir = $outdir . substr (md5 (uniqid (rand(), true)), 0, 8);    mkdir ($tmpdir);    $tmpdir .= '/';        for ($i = 0; $i < count ($keys); $i++) {        // Hallelujah time, we can finally extract!        $backup_file = $keys[$i] . '.mdbackup';                // Copy this file over to the temporary dir        if (!file_exists ($indir . $backup_file)) {            echo (Console_Color::convert ("%rFile $backup_file is missing.%n\n"));            continue;        }                            file_write ($tmpdir . $backup_file, file_get_contents ($indir . $backup_file));        // Verify the file's signature                bpl2xml ($tmpdir . $backup_file);                // Extract the file itself        $pref_file = file_get_contents ($tmpdir . $backup_file);        $regex = '/<key>Path<\/key>[^<]+<string>(.*)<\/string>/smiU';        $matches = array ();        preg_match_all ($    }    }function obscure (&$input) {    global $obscure;    if ($obscure) {        $len = strlen ($input);        if ($len <= 7) {            return 'XXXX';        } else {            return substr ($input, 0, 3) . str_repeat ('X', $len - 6) . substr ($input, -3);        }    } else {        return $input;    }}function bpl2xml ($filename) {    $res = `plutil -convert xml1 $filename`;}function file_write ($filename, $filedata) {    $fp = fopen ($filename, 'w');    if (!$fp) {        fatal_error ('Unable to open ' . $filename . ' for writing');    }    fputs ($fp, $filedata);    fclose ($fp);}function base64_sign ($indata) {    return base64_encode (pack ('H*', sha1 (base64_decode ($indata))));   }function fatal_error ($message) {    exit (Console_Color::convert ("%r$message%n\n"));}function usage_error ($message) {    global $args, $config, $header, $first_error;    if (!$first_error) {        echo Console_Getargs::getHelp($config, $header, Console_Color::convert ("%r$message%n\n"));    } else {        echo (Console_Color::convert ("%r$message%n\n"));    }   }