From a864ff6ff22780b9f0f5753c988e796b8cf8a4fd Mon Sep 17 00:00:00 2001 From: Gareth Watts Date: Fri, 23 Sep 2016 23:24:56 -0700 Subject: [PATCH] Add support for iOS 10 iOS 10 made a change to the way backup files are laid out on disk; previously there were hundreds of files in a single directory.. the updated layout stores files in a sub-directory named using the first two characters of the filename. --- README.md | 2 +- pinfinder.go | 10 +++++-- pinfinder_test.go | 69 +++++++++++++++++++++++++++++++---------------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 6379559..9851fe6 100644 --- a/README.md +++ b/README.md @@ -146,4 +146,4 @@ https://nbalkota.wordpress.com/2014/04/05/recover-your-forgotten-ios-7-restricti ## Other Notes -Last tested with iOS 8 through 9.3 on OS X 10.10, 10.11, Windows XP and Windows 8 with iTunes 12.3 +Last tested with iOS 8 through 10.0.2 on OS X 10.10, 10.11, Windows XP and Windows 8 with iTunes 12.5 diff --git a/pinfinder.go b/pinfinder.go index 77e9b07..e405381 100644 --- a/pinfinder.go +++ b/pinfinder.go @@ -60,7 +60,7 @@ import ( const ( maxPIN = 10000 - version = "1.4.0" + version = "1.5.0" restrictionsPlistName = "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b" msgIsEncrypted = "backup is encrypted" @@ -193,7 +193,6 @@ func loadBackups(syncDir string) (backups backups, err error) { func loadBackup(backupDir string) *backup { var b backup - b.restrictionsPath = filepath.Join(backupDir, restrictionsPlistName) if err := parsePlist(filepath.Join(backupDir, "Info.plist"), &b.info); err != nil { return nil // no Info.plist == invalid backup dir @@ -203,6 +202,13 @@ func loadBackup(backupDir string) *backup { return nil // no Manifest.plist == invaild backup dir } + b.restrictionsPath = filepath.Join(backupDir, restrictionsPlistName) + if _, err := os.Stat(b.restrictionsPath); err != nil { + // iOS 10 moved backup files into sub-folders beginning with + // the first 2 letters of the filename. + b.restrictionsPath = filepath.Join(backupDir, restrictionsPlistName[:2], restrictionsPlistName) + } + if err := parsePlist(b.restrictionsPath, &b.restrictions); os.IsNotExist(err) { b.status = msgNoPasscode diff --git a/pinfinder_test.go b/pinfinder_test.go index 33e92f7..a29197f 100644 --- a/pinfinder_test.go +++ b/pinfinder_test.go @@ -70,11 +70,14 @@ func setupDataDir() string { b3path := filepath.Join(tmp, "nobackup") b4path := filepath.Join(tmp, "encbackup") b5path := filepath.Join(tmp, "encnopcbackup") + b6path := filepath.Join(tmp, "ios10backup") os.Mkdir(b1path, 0777) os.Mkdir(b2path, 0777) os.Mkdir(b3path, 0777) os.Mkdir(b4path, 0777) os.Mkdir(b5path, 0777) + os.Mkdir(b6path, 0777) + os.Mkdir(filepath.Join(b6path, "39"), 0777) ioutil.WriteFile( filepath.Join(b1path, "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b"), @@ -138,6 +141,20 @@ func setupDataDir() string { mkManifest(true), 0644) + // b6 contains a passcode with iOS 10 file layout + ioutil.WriteFile( + filepath.Join(b6path, "39", "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b"), + []byte(pinData), + 0644) + ioutil.WriteFile( + filepath.Join(b6path, "Info.plist"), + mkInfo("2016-09-23T21:39:29Z", "ios10 device"), + 0644) + ioutil.WriteFile( + filepath.Join(b6path, "Manifest.plist"), + mkManifest(false), + 0644) + return tmp } @@ -167,28 +184,32 @@ func TestLoadBackups(t *testing.T) { if err != nil { t.Fatal("loadBackups failed", err) } - if len(b) != 4 { + if len(b) != 5 { t.Fatal("Incorrect backup count", len(b)) } + // Should of been sorted into reverse time order - if devname := b[0].info.DisplayName; devname != "device two" { - t.Errorf("First entry is not device two, got %q", devname) + if devname := b[0].info.DisplayName; devname != "ios10 device" { + t.Errorf("First entry is not ios10 device got %q", devname) + } + if devname := b[1].info.DisplayName; devname != "device two" { + t.Errorf("Second entry is not device two, got %q", devname) } - if devname := b[1].info.DisplayName; devname != "device one" { - t.Errorf("Second entry is not device one, got %q", devname) + if devname := b[2].info.DisplayName; devname != "device one" { + t.Errorf("Third entry is not device one, got %q", devname) } - if devname := b[2].info.DisplayName; devname != "device three" { - t.Errorf("Second entry is not device wthree, got %q", devname) + if devname := b[3].info.DisplayName; devname != "device three" { + t.Errorf("Fourth entry is not device wthree, got %q", devname) } - if !b[2].isEncrypted() { + if !b[3].isEncrypted() { t.Error("device three not marked as encrypted") } - if status := b[2].status; status != msgIsEncrypted { + if status := b[3].status; status != msgIsEncrypted { t.Error("device three does not have correct status: ", status) } - if status := b[3].status; status != msgNoPasscode { + if status := b[4].status; status != msgNoPasscode { t.Error("device four does not have correct status", status) } } @@ -197,22 +218,24 @@ func TestParseRestriction(t *testing.T) { tmpDir := setupDataDir() defer os.RemoveAll(tmpDir) - path := filepath.Join(tmpDir, "backup1") - b := loadBackup(path) - if b == nil { - t.Fatal("Failed to load backup") - } + for _, base := range []string{"backup1", "ios10backup"} { + path := filepath.Join(tmpDir, base) + b := loadBackup(path) + if b == nil { + t.Fatal("Failed to load backup") + } - key := b.restrictions.Key - salt := b.restrictions.Salt + key := b.restrictions.Key + salt := b.restrictions.Salt - if !bytes.Equal(key, dataKey) { - t.Error("key doesn't match") - } - if !bytes.Equal(salt, dataSalt) { - t.Error("salt doesn't match") + if !bytes.Equal(key, dataKey) { + t.Error("key doesn't match") + } + if !bytes.Equal(salt, dataSalt) { + t.Error("salt doesn't match") + } + fmt.Printf("key: %#v\nsalt: %#v\n", key, salt) } - fmt.Printf("key: %#v\nsalt: %#v\n", key, salt) } func TestFindPINOK(t *testing.T) {