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

Commit

Permalink
Find and print pin for all backups
Browse files Browse the repository at this point in the history
Some people have multiple devices backed up - Instead of just finding
the passcode for the most recent backup, switch to examining all backups and
printing out any restriction passcode that happens to be set for each
  • Loading branch information
gwatts committed Nov 28, 2015
1 parent 0613669 commit a379489
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 118 deletions.
29 changes: 20 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Restrictions Pin Finder
# iOS Restrictions Pin Finder

[![Build Status](https://travis-ci.org/gwatts/pinfinder.svg?branch=master)](https://travis-ci.org/gwatts/pinfinder)

pinfinder is a small application which attempts to to find the restrictions PIN/passcode
for an iOS device by brute force examination of its iTunes backup.

It was written after the PIN was forgotten for a kid's device and wiping it
It was written after the PIN was forgotten for a kid's iPod Touch and wiping it
would of been more work than writing this little program.

**NOTE**: This program will **not** help you unlock a locked device - It can only help recover the restrictoins
Expand All @@ -23,6 +23,9 @@ Operating-specifc instructions are below. In most cases, simply running the pro
OS specific security restrictions) should deliver the right result. Take a look at the Troubleshooting
section if you run into issues.

By defalut, it will print out the passcode for all devices it can find an unencrypted backup for, dispalying
the most recently backed up first.

### Windows

1. Backup the device using iTunes on a desktop computer.
Expand Down Expand Up @@ -60,15 +63,23 @@ Download, extract and run the binary.

```
$ ./pinfinder
Searching backup at /Users/johndoe/Library/Application\ Support/MobileSync/Backup/9afaaa65041cb570cd393b710f392c8220f2f20e
Finding PIN... FOUND!
PIN number is: 1234 (found in 761.7ms)
PIN Finder 1.3.0
http://github.com/gwatts/pinfinder
IOS DEVICE BACKUP TIME RESTRICTIONS PASSCODE
John Doe’s iPad Mini Nov 25, 2015 01:39 PM PST 1234
John Doe's iPhone 6 Nov 25, 2015 12:15 PM PST 3456
John Doe's iPhone 5S Sep 19, 2014 03:57 PM PDT No passcode found
```


## Troubleshooting

If you have multiple devices or backups, you can pass the exact path to the backup folder to
pinfinder, rather than have it try to find it by itself:
By default the program will look for the restrictions passcode for every device that has been
backed up, and return results of the most recently backed up first.

You can also specify the backup directory explicitly on the command line to examine the backup
for a single device:

On Mac it will be in the home directory as /Library/Application Support/MobileSync/Backup/<something>
eg.
Expand All @@ -89,8 +100,8 @@ Use whatever directory is the latest as the argument to pinfinder:
$ pinfinder /Users/johndoe/Library/Application\ Support/MobileSync/Backup/51957b68226dbc9f59cb5797532afd906ba0a1f8
```

The program will find the plist containing the hashed version of the PIN and will then find
the PIN that matches that hash (which can then be used with your device).
The program will find the plist containing the hashed version of the passcode and will then find
the passcode that matches that hash (which can then be used with your device).
It shouldn't take more than a few seconds to run.

If the program fails to find the passcode for your device, and you're sure it's searching the right
Expand Down
194 changes: 96 additions & 98 deletions pinfinder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// iOS Restrictions PIN Finder
// iOS Restrictions Passcode Finder
//
// This program will examine an iTunes backup folder for an iOS device and attempt
// to find the PIN used for restricting permissions on the device (NOT the unlock PIN)
Expand All @@ -35,7 +35,6 @@ import (
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/xml"
"errors"
"flag"
"fmt"
Expand All @@ -45,6 +44,7 @@ import (
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"time"
Expand All @@ -53,14 +53,20 @@ import (
)

const (
maxPIN = 10000
version = "1.2.1"
maxPIN = 10000
version = "1.3.0"
restrictionsPlistName = "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b"
)

var (
noPause = flag.Bool("nopause", false, "Set to true to prevent the program pausing for input on completion")
)

type plist struct {
Dict plistDict `xml:"dict"`
path string
}

func isDir(p string) bool {
s, err := os.Stat(p)
if err != nil {
Expand All @@ -69,6 +75,15 @@ func isDir(p string) bool {
return s.IsDir()
}

func dumpFile(fn string) {
if f, err := os.Open(fn); err != nil {
fmt.Printf("Failed to open %s: %s\n", fn, err)
} else {
defer f.Close()
io.Copy(os.Stdout, f)
}
}

// figure out where iTunes keeps its backups on the current OS
func findSyncDir() (string, error) {
usr, err := user.Current()
Expand All @@ -95,96 +110,61 @@ func findSyncDir() (string, error) {
return dir, nil
}

// Fidn the latest backup folder
func findLatestBackup(backupDir string) (string, error) {
d, err := os.Open(backupDir)
if err != nil {
return "", fmt.Errorf("failed to open directory %q: %s", backupDir, err)
}
files, err := d.Readdir(10000)
if err != nil {
return "", fmt.Errorf("failed to read directory %q: %s", backupDir, err)
}
var newest string
var lastMT time.Time

for _, fi := range files {
if mt := fi.ModTime(); mt.After(lastMT) {
lastMT = mt
newest = fi.Name()
}
}
if newest != "" {
return filepath.Join(backupDir, newest), nil
}
return "", errors.New("no backup directories found in " + backupDir)
type backup struct {
path string
info plist
restrictions plist
}

type plist struct {
Path string
Keys []string `xml:"dict>key"`
Data []string `xml:"dict>data"`
}
type backups []*backup

func (p *plist) DumpTo(w io.Writer) error {
f, err := os.Open(p.Path)
if err != nil {
return fmt.Errorf("failed to dump plist data: %s", err)
}
defer f.Close()
io.Copy(w, f)
return nil
func (b backups) Len() int { return len(b) }
func (b backups) Less(i, j int) bool {
di, dj := b[i].info.Dict["Last Backup Date"].Value, b[j].info.Dict["Last Backup Date"].Value
return di < dj
}
func (b backups) Swap(i, j int) { b[i], b[j] = b[j], b[i] }

func loadPlist(fn string) (*plist, error) {
var p plist
f, err := os.Open(fn)
func loadBackups(syncDir string) (backups backups, err error) {
// loop over all directories and see whether they contain an Info.plist
d, err := os.Open(syncDir)
if err != nil {
return nil, err
}
defer f.Close()
if err := xml.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
p.Path = fn
return &p, nil
}

func findRestrictions(fpath string) (*plist, error) {
d, err := os.Open(fpath)
if err != nil {
return nil, fmt.Errorf("failed to open directory %q: %s", fpath, err)
return nil, fmt.Errorf("failed to open directory %q: %s", syncDir, err)
}
defer d.Close()
fl, err := d.Readdir(-1)
if err != nil {
return nil, fmt.Errorf("failed to read directory %q: %s", fpath, err)
return nil, fmt.Errorf("failed to read directory %q: %s", syncDir, err)
}
c := 0
for _, fi := range fl {
if !fi.Mode().IsRegular() {
continue
}
if size := fi.Size(); size < 300 || size > 500 {
if !fi.Mode().IsDir() {
continue
}
if pl, err := loadPlist(path.Join(fpath, fi.Name())); err == nil {
c++
if len(pl.Keys) == 2 && len(pl.Data) == 2 && pl.Keys[0] == "RestrictionsPasswordKey" {
return pl, nil
}
path := filepath.Join(syncDir, fi.Name())
if b, err := loadBackup(path); err == nil {
backups = append(backups, b)
}
}
if c == 0 {
return nil, errors.New("no plist files; are you sure you have the right backup directory?")
sort.Sort(sort.Reverse(backups))
return backups, nil
}

func loadBackup(backupDir string) (*backup, error) {
var b backup
if err := loadXML(filepath.Join(backupDir, "Info.plist"), &b.info); err != nil {
return nil, fmt.Errorf("%s is not a backup directory", backupDir)
}
return nil, errors.New("could not find parental restricitons plist file - " +
"Are you sure parental restrictions were turned on when this backup was taken?")
b.info.path = filepath.Join(backupDir, "Info.plist")
if err := loadXML(filepath.Join(backupDir, restrictionsPlistName), &b.restrictions); err == nil {
b.restrictions.path = filepath.Join(backupDir, restrictionsPlistName)
}
b.path = backupDir
return &b, nil
}

func parseRestrictions(pl *plist) (pw, salt []byte) {
pw, _ = base64.StdEncoding.DecodeString(strings.TrimSpace(pl.Data[0]))
salt, _ = base64.StdEncoding.DecodeString(strings.TrimSpace(pl.Data[1]))
func (b *backup) parseRestrictions() (pw, salt []byte) {
pw, _ = base64.StdEncoding.DecodeString(strings.TrimSpace(b.restrictions.Dict["RestrictionsPasswordKey"].Value))
salt, _ = base64.StdEncoding.DecodeString(strings.TrimSpace(b.restrictions.Dict["RestrictionsPasswordSalt"].Value))
return pw, salt
}

Expand Down Expand Up @@ -262,10 +242,12 @@ func init() {
}

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

fmt.Println("PIN Finder", version)
fmt.Println("http://github.com/gwatts/pinfinder")

flag.Parse()

Expand All @@ -277,40 +259,56 @@ func main() {
fmt.Println(err.Error)
usage()
}
backupDir, err = findLatestBackup(syncDir)
allBackups, err = loadBackups(syncDir)
if err != nil {
exit(101, true, err.Error())
}

case 1:
backupDir = args[0]
b, err := loadBackup(args[0])
if err != nil {
exit(101, true, err.Error())
}
allBackups = backups{b}

default:
exit(102, true, "Too many arguments")
}

if !isDir(backupDir) {
exit(103, true, "Directory not found: %s", backupDir)
}

fmt.Println("Searching backup at", backupDir)
pl, err := findRestrictions(backupDir)
if err != nil {
exit(104, false, err.Error())
fmt.Println()
fmt.Printf("%-40.40s %-25s %s\n", "IOS DEVICE", "BACKUP TIME", "RESTRICTIONS PASSCODE")
failed := make(backups, 0)
for _, b := range allBackups {
info := b.info.Dict
var backupTime string
if t, err := time.Parse(time.RFC3339, info["Last Backup Date"].Value); err != nil {
backupTime = info["Last Backup Date"].Value
} else {
backupTime = t.In(time.Local).Format("Jan _2, 2006 03:04 PM MST")
}
fmt.Printf("%-40.40s %s ", info["Display Name"].Value, backupTime)
if b.restrictions.Dict != nil {
key, salt := b.parseRestrictions()
pin, err := findPIN(key, salt)
if err != nil {
fmt.Println("Failed to find passcode")
failed = append(failed, b)
} else {
fmt.Println(pin)
}
} else {
fmt.Println("No passcode found")
}
}

key, salt := parseRestrictions(pl)

fmt.Print("Finding PIN...")
startTime := time.Now()
pin, err := findPIN(key, salt)
if err != nil {
// Failed to break the PIN; dump the plist data for debugging purposes
fmt.Fprintln(os.Stderr, err.Error()+"\n")
fmt.Fprintln(os.Stderr, "Source data file: ", pl.Path)
pl.DumpTo(os.Stderr)
exit(105, false, "")
fmt.Println()
for _, b := range failed {
fmt.Printf("Failed to find PIN for backup %s\nPlease file a bug report at https://github.com/gwatts/pinfinder/issues\n", b.path)
for _, key := range []string{"Product Name", "Product Type", "Product Version"} {
fmt.Printf("%-20s: %s\n", key, b.info.Dict[key].Value)
}
dumpFile(b.restrictions.path)
fmt.Println()
}
fmt.Printf(" FOUND!\nPIN number is: %s (found in %s)\n", pin, time.Since(startTime))
exit(0, false, "")
}
Loading

0 comments on commit a379489

Please sign in to comment.