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 decoding encrypted backups
Browse files Browse the repository at this point in the history
  • Loading branch information
gwatts committed Dec 27, 2017
1 parent 8a2c52f commit 6b86647
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 47 deletions.
4 changes: 2 additions & 2 deletions debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func addFileToZip(zf *zip.Writer, path, fn string) error {

// buildDebug constructs a .zip file containing debugging information in the given target
// directory. If targetDir is empty then it will use the user's home or desktop directory.
func buildDebug(targetDir string, backupResult string, allBackups backups) (fn string, err error) {
func buildDebug(targetDir string, backupResult string, allBackups *backups) (fn string, err error) {
if targetDir == "" {
targetDir, err = getDefaultDir()
if err != nil {
Expand All @@ -100,7 +100,7 @@ func buildDebug(targetDir string, backupResult string, allBackups backups) (fn s
return "", err
}

for _, backup := range allBackups {
for _, backup := range allBackups.backups {
if err := addBackupInfoToZip(zf, backup); err != nil {
return "", err
}
Expand Down
11 changes: 11 additions & 0 deletions decrypt-disable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// +build nodecrypt

package main

var (
decryptEnabled = false
)

func decrypt(backupDir string, b *backup) {
b.Status = msgEncryptionDisabled
}
51 changes: 51 additions & 0 deletions decrypt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// +build !nodecrypt

package main

import (
"bytes"

plist "github.com/DHowett/go-plist"
iosbackup "github.com/gwatts/ios/backup"
)

var (
decryptEnabled = true
)

func decrypt(backupDir string, b *backup) {
pw := getpw()
if pw == "" {
b.Status = msgIsEncrypted
return
}
encbw, err := iosbackup.Open(backupDir)
if err != nil {
b.Status = "Failed to open backup: " + err.Error()
return
}
if err := encbw.SetPassword(pw); err != nil {
b.Status = msgIncorrectPassword
return
}
if err := encbw.Load(); err != nil {
b.Status = err.Error()
return
}
rec := encbw.RecordById(restrictionsPlistName)
if rec == nil {
b.Status = msgNoPassword
return
}
data, err := encbw.ReadFile(*rec)
if err != nil {
b.Status = msgIncorrectPassword
return
}
buf := bytes.NewReader(data)
if err := plist.NewDecoder(buf).Decode(&b.Restrictions); err != nil {
b.Status = msgIncorrectPassword
return
}

}
2 changes: 1 addition & 1 deletion licenses.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

// Generated by github.com/gwatts/embedfiles
// embedfiles -filename licenses.go -var licenses LICENSE*
// at Tue Apr 19 15:22:21 PDT 2016
// at Fri Dec 22 17:24:17 PST 2017

import (
"bytes"
Expand Down
133 changes: 89 additions & 44 deletions pinfinder.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2016, Gareth Watts
// Copyright (c) 2017, Gareth Watts
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
Expand Down Expand Up @@ -29,7 +29,7 @@
// to find the PIN used for restricting permissions on the device (NOT the unlock PIN)

// To regenerate licenses.go:
// 1) go get github.com/gwatts/emebdfiles
// 1) go get github.com/gwatts/embedfiles
// 2) go generate

//go:generate embedfiles -filename licenses.go -var licenses LICENSE*
Expand All @@ -40,6 +40,7 @@ import (
"bufio"
"bytes"
"crypto/sha1"
"encoding/base64"
"errors"
"flag"
"fmt"
Expand All @@ -55,17 +56,20 @@ import (
"time"

"github.com/DHowett/go-plist"

"github.com/howeyc/gopass"
"golang.org/x/crypto/pbkdf2"
)

const (
maxPIN = 10000
version = "1.5.1.dev2"
version = "1.6.0"
restrictionsPlistName = "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b"

msgIsEncrypted = "backup is encrypted"
msgNoPasscode = "no passcode stored"
msgIsEncrypted = "backup is encrypted"
msgEncryptionDisabled = "encrypted backups not supported"
msgNoPasscode = "no passcode stored"
msgIncorrectPassword = "incorrect encryption password"
msgNoPassword = "need encryption password"
)

var (
Expand Down Expand Up @@ -126,6 +130,14 @@ func parsePlist(fn string, target interface{}) error {
return plist.NewDecoder(f).Decode(target)
}

func fileExists(fn string) bool {
fi, err := os.Stat(fn)
if err != nil {
return false
}
return fi.Mode().IsRegular()
}

var backupInfoTpl = template.Must(template.New("backup").Parse(`
Path: {{.Path}}
Status: {{.Status}}
Expand Down Expand Up @@ -183,15 +195,19 @@ func (b *backup) isEncrypted() bool {
}
}

type backups []*backup
type backups struct {
encrypted bool
backups []*backup
}

func (b backups) Len() int { return len(b) }
func (b backups) Len() int { return len(b.backups) }
func (b backups) Less(i, j int) bool {
return b[i].Info.LastBackup.Before(b[j].Info.LastBackup)
return b.backups[i].Info.LastBackup.Before(b.backups[j].Info.LastBackup)
}
func (b backups) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func (b backups) Swap(i, j int) { b.backups[i], b.backups[j] = b.backups[j], b.backups[i] }

func loadBackups(syncDir string) (backups backups, err error) {
func loadBackups(syncDir string) (bs *backups, err error) {
bs = new(backups)
// loop over all directories and see whether they contain an Info.plist
d, err := os.Open(syncDir)
if err != nil {
Expand All @@ -208,11 +224,14 @@ func loadBackups(syncDir string) (backups backups, err error) {
}
path := filepath.Join(syncDir, fi.Name())
if b := loadBackup(path); b != nil {
backups = append(backups, b)
bs.backups = append(bs.backups, b)
if b.isEncrypted() {
bs.encrypted = true
}
}
}
sort.Sort(sort.Reverse(backups))
return backups, nil
sort.Sort(sort.Reverse(bs))
return bs, nil
}

func loadBackup(backupDir string) *backup {
Expand All @@ -233,21 +252,44 @@ func loadBackup(backupDir string) *backup {
b.RestrictionsPath = filepath.Join(backupDir, restrictionsPlistName[:2], restrictionsPlistName)
}

if err := parsePlist(b.RestrictionsPath, &b.Restrictions); os.IsNotExist(err) {
b.Path = backupDir
if !fileExists(b.RestrictionsPath) {
b.Status = msgNoPasscode
return &b
}

} else if err != nil {
if b.isEncrypted() {
b.Status = msgIsEncrypted
} else {
b.Status = err.Error()
}
if b.isEncrypted() {
decrypt(backupDir, &b)
return &b
}

if err := parsePlist(b.RestrictionsPath, &b.Restrictions); err != nil {
b.Status = err.Error()
}

b.Path = backupDir
return &b
}

var prompted bool
var cachepw string

func getpw() string {
if prompted {
return cachepw
}
prompted = true
fmt.Println("\nSome backups are encrypted; passcode recovery requires the")
fmt.Println("encryption password used with iTunes. Press return to skip.\n")
fmt.Print("Enter iTunes Encryption Password: ")
pw, _ := gopass.GetPasswdMasked()
fmt.Println("")
cachepw = string(pw)
if cachepw != "" {
fmt.Println("Decryption may take a few minutes...")
}
return cachepw
}

type swg struct{ sync.WaitGroup }

func (wg *swg) WaitChan() chan struct{} {
Expand Down Expand Up @@ -292,7 +334,7 @@ func findPIN(key, salt []byte) (string, error) {

select {
case <-wg.WaitChan():
return "", errors.New("failed to calculate PIN number")
return "", errors.New("failed to calculate PIN")
case pin := <-found:
return pin, nil
}
Expand Down Expand Up @@ -336,21 +378,21 @@ func displayLicense() {
fmt.Println()
}

func generateReport(f io.Writer, includeDirName bool, allBackups backups) {
func generateReport(f io.Writer, includeDirName bool, allBackups *backups) {
if includeDirName {
fmt.Fprintf(f, "%-70s", "BACKUP DIR")
}
fmt.Fprintf(f, "%-40.40s %-25s %s\n", "IOS DEVICE", "BACKUP TIME", "RESTRICTIONS PASSCODE")
failed := make(backups, 0)
fmt.Fprintf(f, "%-40.40s %-7.7s %-25s %s\n", "IOS DEVICE", "IOS", "BACKUP TIME", "RESTRICTIONS PASSCODE")
failed := make([]*backup, 0)

foundEncrypted := false
for _, b := range allBackups {
for _, b := range allBackups.backups {
info := b.Info
if includeDirName {
fmt.Fprintf(f, "%-70s", filepath.Base(b.Path))
}
fmt.Fprintf(f, "%-40.40s %s ",
fmt.Fprintf(f, "%-40.40s %-7.7s %s ",
info.DisplayName,
info.ProductVersion,
info.LastBackup.In(time.Local).Format("Jan _2, 2006 03:04 PM MST"))

if len(b.Restrictions.Key) > 0 {
Expand All @@ -363,38 +405,39 @@ func generateReport(f io.Writer, includeDirName bool, allBackups backups) {
}
} else {
fmt.Fprintln(f, b.Status)
if b.Status == msgIsEncrypted {
foundEncrypted = true
}
}
}

if foundEncrypted {
fmt.Fprintln(f, "")
fmt.Fprintln(f, "NOTE: Some backups had passcodes that are encrypted.")
fmt.Fprintln(f, "Select the device in iTunes, uncheck the \"Encrypt iPhone backup\" checkbox, ")
fmt.Fprintln(f, "re-sync and then run pinfinder again.")
}

fmt.Fprintln(f)
for _, b := range failed {
fmt.Fprintf(f, "Failed to find PIN for backup %s\nPlease file a bug report at https://github.com/gwatts/pinfinder/issues\n", b.Path)
fmt.Fprintf(f, "%-20s: %s\n", "Product Name", b.Info.ProductName)
fmt.Fprintf(f, "%-20s: %s\n", "Product Type", b.Info.ProductType)
fmt.Fprintf(f, "%-20s: %s\n", "Product Version", b.Info.ProductVersion)
fmt.Fprintf(f, "%-20s: %s\n", "Salt", base64.StdEncoding.EncodeToString(b.Restrictions.Salt))
fmt.Fprintf(f, "%-20s: %s\n", "Key", base64.StdEncoding.EncodeToString(b.Restrictions.Key))

dumpFile(b.RestrictionsPath)
fmt.Fprintln(f, "")
}
}

func donate() {
fmt.Println("| DID PINFINDER SAVE THE DAY?")
fmt.Println("| Please consider donating a few dollars to say thanks!")
fmt.Println("| https://pinfinder.net/donate")
fmt.Println("")
}

var syncDir string

func main() {
var syncDir string
var err error
var allBackups backups
var allBackups *backups

fmt.Println("PIN Finder", version)
fmt.Println("http://github.com/gwatts/pinfinder")
fmt.Println("iOS Restrictions Passcode Finder")
fmt.Println("https://pinfinder.net\n")

flag.Parse()

Expand All @@ -410,18 +453,19 @@ func main() {
if err != nil {
exit(101, true, err.Error())
}
fmt.Println("Sync Directory:", syncDir)
fmt.Println("Scanning backups...")
allBackups, err = loadBackups(syncDir)
if err != nil {
exit(101, true, err.Error())
}
fmt.Println("Sync Directory:", syncDir)

case 1:
b := loadBackup(args[0])
if b == nil {
exit(101, true, "Invalid backup directory")
}
allBackups = backups{b}
allBackups = &backups{encrypted: b.isEncrypted(), backups: []*backup{b}}

default:
exit(102, true, "Too many arguments")
Expand All @@ -442,5 +486,6 @@ func main() {
}

generateReport(os.Stdout, false, allBackups)
donate()
exit(0, false, "")
}

0 comments on commit 6b86647

Please sign in to comment.