pinfinder开源下载_pinfinder.go

// Copyright (c) 2017, Gareth Watts

// All rights reserved.

//

// Redistribution and use in source and binary forms, with or without

// modification, are permitted provided that the following conditions are met:

// * Redistributions of source code must retain the above copyright

// notice, this list of conditions and the following disclaimer.

// * Redistributions in binary form must reproduce the above copyright

// notice, this list of conditions and the following disclaimer in the

// documentation and/or other materials provided with the distribution.

// * Neither the name of the nor the

// names of its contributors may be used to endorse or promote products

// derived from this software without specific prior written permission.

//

// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND

// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED

// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE

// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY

// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES

// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;

// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND

// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT

// (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 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)

// To regenerate licenses.go:

// 1) go get github.com/gwatts/embedfiles

// 2) go generate

//go:generate embedfiles -filename licenses.go -var licenses LICENSE*

package main

import (

"bufio"

"bytes"

"crypto/sha1"

"encoding/base64"

"errors"

"flag"

"fmt"

"html/template"

"io"

"os"

"os/user"

"path"

"path/filepath"

"runtime"

"sort"

"strconv"

"strings"

"sync"

"syscall"

"time"

"github.com/DHowett/go-plist"

"github.com/gwatts/ios/keychain"

"github.com/howeyc/gopass"

"golang.org/x/crypto/pbkdf2"

)

const (

maxPIN = 10000

version = "1.7.1"

restrictionsPlistName = "398bc9c2aeeab4cb0c12ada0f52eea12cf14f40b"

msgIsEncrypted = "backup is encrypted"

msgEncryptionDisabled = "encrypted backups not supported"

msgNoPasscode = "none"

msgIncorrectPassword = "incorrect encryption password"

msgNoPassword = "need encryption password"

msgKeychainLoadFailed = "failed to read keychain"

msgEncryptedNeeded = "need encrypted backup"

)

var (

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

showLicense = flag.Bool("license", false, "Display license information")

diag = flag.Bool("diag", false, "Generate a diagnostic pinfinder-debug.zip file")

)

func isDir(p string) bool {

s, err := os.Stat(p)

if err != nil {

return false

}

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)

}

}

func appendIfDir(dirs []string, dir string) []string {

if isDir(dir) {

return append(dirs, dir)

}

return dirs

}

// figure out where iTunes keeps its backups on the current OS

func findSyncDirs() (dirs []string, err error) {

usr, err := user.Current()

if err != nil {

return nil, fmt.Errorf("failed to get information about current user: %s", err)

}

switch runtime.GOOS {

case "darwin":

dir := filepath.Join(usr.HomeDir, "Library", "Application Support", "MobileSync", "Backup")

dirs = appendIfDir(dirs, dir)

case "windows":

// this seems to be correct for all versions of Windows.. Tested on XP and Windows 8

dir := filepath.Join(os.Getenv("APPDATA"), "Apple Computer", "MobileSync", "Backup")

dirs = appendIfDir(dirs, dir)

dir = filepath.Join(os.Getenv("USERPROFILE"), "Apple", "MobileSync", "Backup")

dirs = appendIfDir(dirs, dir)

default:

return nil, errors.New("could not detect backup directory for this operating system; pass explicitly")

}

return dirs, nil

}

func parsePlist(fn string, target interface{}) error {

f, err := os.Open(fn)

if err != nil {

return err

}

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}}

RestrictionPath: {{.RestrictionsPath}}

IsEncrypted: {{.Manifest.IsEncrypted}}

Key: {{.Restrictions.Key}}

Salt: {{.Restrictions.Salt}}

LastBackup: {{.Info.LastBackup}}

DisplayName: {{.Info.DisplayName}}

ProductName: {{.Info.ProductName}}

ProductType: {{.Info.ProductType}}

ProductVersion: {{.Info.ProductVersion}}

`))

type backup struct {

Path string

Status string

RestrictionsPath string

UsesScreenTime bool

Info struct {

LastBackup time.Time `plist:"Last Backup Date"`

DisplayName string `plist:"Display Name"`

ProductName string `plist:"Product Name"`

ProductType string `plist:"Product Type"`

ProductVersion string `plist:"Product Version"`

}

Manifest struct {

IsEncrypted interface{} `plist:"IsEncrypted"`

}

Restrictions struct {

Key []byte `plist:"RestrictionsPasswordKey"`

Salt []byte `plist:"RestrictionsPasswordSalt"`

}

Keychain *keychain.Keychain

}

func (b *backup) debugInfo() string {

var buf bytes.Buffer

backupInfoTpl.Execute(&buf, b)

return buf.String()

}

func (b *backup) isEncrypted() bool {

switch v := b.Manifest.IsEncrypted.(type) {

case int:

return v != 0

case uint64:

return v != 0

case bool:

return v

case nil:

return false

default:

return false

}

}

func (b *backup) isIOS12() bool {

return majorVersion(b.Info.ProductVersion) >= 12

}

type backups struct {

encrypted bool

backups []*backup

}

func (b backups) Len() int { return len(b.backups) }

func (b backups) Less(i, j int) bool {

return b.backups[i].Info.LastBackup.Before(b.backups[j].Info.LastBackup)

}

func (b backups) Swap(i, j int) { b.backups[i], b.backups[j] = b.backups[j], b.backups[i] }

func (b *backups) loadBackups(syncDir string) error {

// loop over all directories and see whether they contain an Info.plist

d, err := os.Open(syncDir)

if err != nil {

if err := isBadMacPerms(err); err != nil {

return err

}

return fmt.Errorf("failed to open directory %q: %s", syncDir, err)

}

defer d.Close()

fl, err := d.Readdir(-1)

if err != nil {

return fmt.Errorf("failed to read directory %q: %s", syncDir, err)

}

for _, fi := range fl {

if !fi.Mode().IsDir() {

continue

}

path := filepath.Join(syncDir, fi.Name())

if backup, _ := loadBackup(path); backup != nil {

b.backups = append(b.backups, backup)

if backup.isEncrypted() {

b.encrypted = true

}

}

}

sort.Sort(sort.Reverse(b))

return nil

}

func majorVersion(v string) int {

parts := strings.Split(v, ".")

if len(parts) < 1 {

return 0

}

maj, err := strconv.Atoi(parts[0])

if err != nil {

return 0

}

return maj

}

func loadBackup(backupDir string) (*backup, error) {

var b backup

if err := parsePlist(filepath.Join(backupDir, "Info.plist"), &b.Info); err != nil {

return nil, err // no Info.plist == invalid backup dir

}

if err := parsePlist(filepath.Join(backupDir, "Manifest.plist"), &b.Manifest); err != nil {

return nil, err // no Manifest.plist == invaild backup dir

}

b.Path = backupDir

switch {

case b.isIOS12():

if !b.isEncrypted() {

b.Status = msgEncryptedNeeded

return &b, nil

}

decrypt(backupDir, &b)

default:

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 !fileExists(b.RestrictionsPath) {

b.Status = msgNoPasscode

return &b, nil

}

if b.isEncrypted() {

decrypt(backupDir, &b)

return &b, nil

}

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

b.Status = err.Error()

}

}

return &b, nil

}

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.")

fmt.Print("\nEnter 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{} {

c := make(chan struct{}, 1)

go func() {

wg.Wait()

c

}()

return c

}

// use all available cores to brute force the PIN

func findPIN(key, salt []byte) (string, error) {

found := make(chan string)

var wg swg

var start, end int

perCPU := maxPIN / runtime.NumCPU()

for i := 0; i < runtime.NumCPU(); i++ {

wg.Add(1)

if i == runtime.NumCPU()-1 {

end = maxPIN

} else {

end += perCPU

}

go func(start, end int) {

for j := start; j < end; j++ {

guess := fmt.Sprintf("%04d", j)

k := pbkdf2.Key([]byte(guess), salt, 1000, len(key), sha1.New)

if bytes.Equal(k, key) {

found

return

}

}

wg.Done()

}(start, end)

start += perCPU

}

select {

case

return "", errors.New("failed to calculate PIN")

case pin :=

return pin, nil

}

}

func findPINFromKeychain(b *backup) (string, error) {

items := b.Keychain.General.FindByKeyMatch(keychain.KService, "ParentalControls")

if len(items) == 0 {

return "", fmt.Errorf("none")

}

code, ok := items[0][keychain.KData].([]byte)

if !ok {

return "", fmt.Errorf(msgNoPasscode)

}

return string(code), nil

}

func exit(status int, addUsage bool, errfmt string, a ...interface{}) {

if errfmt != "" {

fmt.Fprintf(os.Stderr, errfmt+"\n", a...)

}

if addUsage {

usage()

}

if !*noPause {

fmt.Printf("Press Enter to exit")

bufio.NewReader(os.Stdin).ReadBytes('\n')

}

os.Exit(status)

}

var badMacPerms = errors.New("mac full disk access required")

func isBadMacPerms(err error) error {

if runtime.GOOS != "darwin" {

return nil

}

perr, ok := err.(*os.PathError)

if !ok || perr.Err != syscall.EPERM {

return nil

}

return badMacPerms

}

func exitBadMacPerms() {

fmt.Fprintln(os.Stderr, "\nOperation not permitted: Full Disk Access Required")

fmt.Fprintln(os.Stderr, "Please grant \"Full Disk Access\" to Terminal to run pinfinder")

fmt.Fprintln(os.Stderr, "See https://pinfinder.net/mac.html for help")

os.Exit(130)

}

func usage() {

fmt.Fprintln(os.Stderr, "Usage:", path.Base(os.Args[0]), " [flags] []")

flag.PrintDefaults()

}

func init() {

flag.Usage = usage

}

func displayLicense() {

fmt.Println("LICENSE INFORMATION")

fmt.Println("-------------------")

fmt.Println()

for _, fn := range licenses.Filenames() {

fmt.Println(fn)

fmt.Println()

f, _ := licenses.Open(fn)

io.Copy(os.Stdout, f)

fmt.Println()

fmt.Println()

}

fmt.Println()

}

func generateReport(f io.Writer, includeDirName bool, allBackups *backups) {

if includeDirName {

fmt.Fprintf(f, "%-70s", "BACKUP DIR")

}

fmt.Fprintf(f, "%-35.35s %-7.7s %-25s %s\n", "IOS DEVICE", "IOS", "BACKUP TIME", "RESTRICTIONS PASSCODE")

failed := make([]*backup, 0)

for _, b := range allBackups.backups {

info := b.Info

if includeDirName {

fmt.Fprintf(f, "%-70s", filepath.Base(b.Path))

}

fmt.Fprintf(f, "%-35.35s %-7.7s %s ",

info.DisplayName,

info.ProductVersion,

info.LastBackup.In(time.Local).Format("Jan _2, 2006 03:04 PM MST"))

if b.UsesScreenTime {

pin, err := findPINFromKeychain(b)

if err != nil {

fmt.Fprintln(f, err.Error())

} else {

fmt.Fprintln(f, pin)

}

} else if len(b.Restrictions.Key) > 0 {

pin, err := findPIN(b.Restrictions.Key, b.Restrictions.Salt)

if err != nil {

fmt.Fprintln(f, "Failed to find passcode")

failed = append(failed, b)

} else {

fmt.Fprintln(f, pin)

}

} else {

fmt.Fprintln(f, b.Status)

}

}

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() {

allBackups := new(backups)

fmt.Println("PIN Finder", version)

fmt.Println("iOS Restrictions Passcode Finder")

fmt.Println("https://pinfinder.net")

fmt.Println()

flag.Parse()

if *showLicense {

displayLicense()

return

}

args := flag.Args()

switch len(args) {

case 0:

syncDirs, err := findSyncDirs()

if err != nil {

exit(101, true, err.Error())

}

fmt.Println("Sync Directories:", syncDirs)

fmt.Println("Scanning backups...")

for _, syncDir := range syncDirs {

if err := allBackups.loadBackups(syncDir); err != nil {

if isBadMacPerms(err); err != nil {

exitBadMacPerms()

}

exit(101, true, err.Error())

}

}

case 1:

b, err := loadBackup(args[0])

if err != nil {

if isBadMacPerms(err); err != nil {

exitBadMacPerms()

}

exit(101, true, "Invalid backup directory")

}

allBackups = &backups{encrypted: b.isEncrypted(), backups: []*backup{b}}

default:

exit(102, true, "Too many arguments")

}

fmt.Println()

if *diag {

var buf bytes.Buffer

fmt.Println("Generating backup diagnostic report; may take a couple of minutes..")

generateReport(io.MultiWriter(os.Stdout, &buf), true, allBackups)

if fn, err := buildDebug("", buf.String(), allBackups); err != nil {

exit(110, false, err.Error())

} else {

fmt.Println("Generated diagnostic report file stored at", fn)

exit(0, false, "")

}

}

generateReport(os.Stdout, false, allBackups)

donate()

exit(0, false, "")

}

一键复制

编辑

Web IDE

原始数据

按行查看

历史

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值