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'); } // Extract the manifest $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); // === // 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"));}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")); } }