Skip to content
This repository has been archived by the owner on Nov 9, 2024. It is now read-only.

Commit

Permalink
Add support for iOS 10
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gwatts committed Sep 24, 2016
1 parent 891acd7 commit a864ff6
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 26 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 8 additions & 2 deletions pinfinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ import (

const (
maxPIN = 10000
version = "1.4.0"
version = "1.5.0"
restrictionsPlistName = "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b"

msgIsEncrypted = "backup is encrypted"
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
69 changes: 46 additions & 23 deletions pinfinder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
}
}
Expand All @@ -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) {
Expand Down

0 comments on commit a864ff6

Please sign in to comment.