Skip to content

Commit 69f5d5c

Browse files
committed
feat: add ability to ipsw mount encrypted DMGs with --key flag OR --lookup them from TheAppleWiki 🇦🇷
1 parent f67a300 commit 69f5d5c

File tree

5 files changed

+108
-6
lines changed

5 files changed

+108
-6
lines changed

api/server/routes/mount/mount.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func AddRoutes(rg *gin.RouterGroup, pemDB string) {
7474
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid dmg type: must be app, sys, or fs"})
7575
return
7676
}
77-
ctx, err := mount.DmgInIPSW(ipswPath, dmgType, pemDbPath)
77+
ctx, err := mount.DmgInIPSW(ipswPath, dmgType, pemDbPath, nil)
7878
if err != nil {
7979
if errors.Unwrap(err) == info.ErrorCryptexNotFound {
8080
c.AbortWithError(http.StatusNotFound, err)

cmd/ipsw/cmd/mount.go

+53-3
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,22 @@ import (
2626
"os"
2727
"os/signal"
2828
"path/filepath"
29+
"regexp"
30+
"strings"
2931
"syscall"
3032

3133
"github.com/apex/log"
3234
"github.com/blacktop/ipsw/internal/commands/mount"
35+
"github.com/blacktop/ipsw/internal/download"
3336
"github.com/blacktop/ipsw/internal/utils"
3437
"github.com/spf13/cobra"
3538
)
3639

3740
func init() {
3841
rootCmd.AddCommand(mountCmd)
3942

43+
mountCmd.Flags().StringP("key", "k", "", "DMG key")
44+
mountCmd.Flags().Bool("lookup", false, "Lookup DMG keys on theapplewiki.com")
4045
mountCmd.Flags().String("pem-db", "", "AEA pem DB JSON file")
4146
}
4247

@@ -54,15 +59,60 @@ var mountCmd = &cobra.Command{
5459
}
5560
return []string{"ipsw"}, cobra.ShellCompDirectiveFilterFileExt
5661
},
57-
RunE: func(cmd *cobra.Command, args []string) error {
58-
62+
RunE: func(cmd *cobra.Command, args []string) (err error) {
63+
// set log level
5964
if Verbose {
6065
log.SetLevel(log.DebugLevel)
6166
}
6267

68+
// flags
69+
key, _ := cmd.Flags().GetString("key")
70+
lookupKeys, _ := cmd.Flags().GetBool("lookup")
6371
pemDB, _ := cmd.Flags().GetString("pem-db")
72+
// validate flags
73+
if len(key) > 0 && lookupKeys {
74+
return fmt.Errorf("cannot use --key AND --lookup flags together")
75+
}
76+
77+
var keys any
78+
if lookupKeys {
79+
var (
80+
device string
81+
version string
82+
build string
83+
)
84+
re := regexp.MustCompile(`^.*/(?P<device>.+)_(?P<version>.+)_(?P<build>.+)_(?i)Restore\.ipsw$`)
85+
if re.MatchString(args[1]) {
86+
matches := re.FindStringSubmatch(args[1])
87+
if len(matches) < 4 {
88+
return fmt.Errorf("failed to parse IPSW filename: %s", args[1])
89+
}
90+
device = matches[1]
91+
version = matches[2]
92+
build = matches[3]
93+
} else {
94+
return fmt.Errorf("failed to parse IPSW filename: %s", args[1])
95+
}
96+
if device == "" || build == "" {
97+
return fmt.Errorf("device or build information is missing from IPSW filename (required for key lookup)")
98+
}
99+
log.Info("Downloading Keys...")
100+
wikiKeys, err := download.GetWikiFirmwareKeys(&download.WikiConfig{
101+
Keys: true,
102+
Device: strings.Replace(device, "ip", "iP", 1),
103+
Version: version,
104+
Build: strings.ToUpper(build),
105+
// Beta: viper.GetBool("download.key.beta"),
106+
}, "", false)
107+
if err != nil {
108+
return fmt.Errorf("failed querying theapplewiki.com: %v", err)
109+
}
110+
keys = wikiKeys
111+
} else if len(key) > 0 {
112+
keys = key
113+
}
64114

65-
mctx, err := mount.DmgInIPSW(args[1], args[0], pemDB)
115+
mctx, err := mount.DmgInIPSW(args[1], args[0], pemDB, keys)
66116
if err != nil {
67117
return fmt.Errorf("failed to mount %s DMG: %v", args[0], err)
68118
}

internal/commands/dsc/dsc.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ func GetUserAgent(f *dyld.File, sysVer *plist.SystemVersion) (string, error) {
774774
}
775775

776776
func OpenFromIPSW(ipswPath, pemDB string, driverKit, all bool) (*mount.Context, []*dyld.File, error) {
777-
ctx, err := mount.DmgInIPSW(ipswPath, "sys", pemDB)
777+
ctx, err := mount.DmgInIPSW(ipswPath, "sys", pemDB, nil)
778778
if err != nil {
779779
return nil, nil, fmt.Errorf("failed to mount IPSW: %v", err)
780780
}

internal/commands/mount/mount.go

+37-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"time"
1212

1313
"github.com/apex/log"
14+
"github.com/blacktop/go-apfs/pkg/disk/dmg"
15+
"github.com/blacktop/ipsw/internal/download"
16+
"github.com/blacktop/ipsw/internal/magic"
1417
"github.com/blacktop/ipsw/internal/utils"
1518
"github.com/blacktop/ipsw/pkg/aea"
1619
"github.com/blacktop/ipsw/pkg/info"
@@ -42,7 +45,7 @@ func (c Context) Unmount() error {
4245
}
4346

4447
// DmgInIPSW will mount a DMG from an IPSW
45-
func DmgInIPSW(path, typ, pemDbPath string) (*Context, error) {
48+
func DmgInIPSW(path, typ, pemDbPath string, keys any) (*Context, error) {
4649
ipswPath := filepath.Clean(path)
4750

4851
i, err := info.Parse(ipswPath)
@@ -110,6 +113,39 @@ func DmgInIPSW(path, typ, pemDbPath string) (*Context, error) {
110113
return nil, fmt.Errorf("failed to parse AEA encrypted DMG: %v", err)
111114
}
112115
}
116+
if isEncrypted, err := magic.IsEncryptedDMG(extractedDMG); err != nil {
117+
return nil, fmt.Errorf("failed to check if DMG is encrypted: %v", err)
118+
} else if isEncrypted {
119+
var key string
120+
switch v := keys.(type) {
121+
case string:
122+
key = v
123+
case map[string]download.WikiFWKeys:
124+
if len(v) == 0 {
125+
return nil, fmt.Errorf("DMG is encrypted, please provide a key")
126+
}
127+
for _, wk := range v {
128+
if strings.EqualFold(wk.Filename[0], filepath.Base(extractedDMG)) {
129+
key = wk.Key[0]
130+
break
131+
}
132+
}
133+
if key == "" {
134+
return nil, fmt.Errorf("key not found for DMG '%s' in theapplewiki lookup results", extractedDMG)
135+
}
136+
}
137+
log.Info("Decrypting DMG...")
138+
if dmg, err := dmg.Open(extractedDMG, &dmg.Config{
139+
Key: key,
140+
}); err != nil {
141+
return nil, fmt.Errorf("failed to open DMG '%s': %v", extractedDMG, err)
142+
} else {
143+
defer dmg.Close()
144+
if err := os.Rename(dmg.DecryptedTemp(), extractedDMG); err != nil {
145+
return nil, fmt.Errorf("failed to overwrite encrypted DMG with the decrypted one: %v", err)
146+
}
147+
}
148+
}
113149

114150
mp, am, err := utils.MountDMG(extractedDMG)
115151
if err != nil {

internal/utils/macos.go

+16
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,22 @@ func Mount(image, mountPoint string) error {
488488
return nil
489489
}
490490

491+
func MountEncrypted(image, mountPoint, password string) error {
492+
if runtime.GOOS == "darwin" {
493+
cmd := exec.Command("/usr/bin/hdiutil", "attach", "-noverify", "-mountpoint", mountPoint, "-stdinpass", image)
494+
cmd.Stdin = strings.NewReader(password)
495+
out, err := cmd.CombinedOutput()
496+
if err != nil {
497+
if strings.Contains(string(out), "hdiutil: mount failed - Resource busy") {
498+
return ErrMountResourceBusy
499+
}
500+
return fmt.Errorf("%v: %s", err, out)
501+
}
502+
return nil
503+
}
504+
return fmt.Errorf("only supported on macOS")
505+
}
506+
491507
func IsAlreadyMounted(image, mountPoint string) (string, bool, error) {
492508
if runtime.GOOS == "darwin" {
493509
info, err := MountInfo()

0 commit comments

Comments
 (0)