From 2df519e5a513505dfe336bc5f98c5eb3a20ec366 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:28:33 +0100 Subject: [PATCH 01/11] Support fractions of a second when unmarshalling https://go-review.googlesource.com/c/go/+/108355/ --- encoding/asn1/asn1.go | 2 +- encoding/asn1/asn1_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/encoding/asn1/asn1.go b/encoding/asn1/asn1.go index 78c0d1a..b3f3286 100644 --- a/encoding/asn1/asn1.go +++ b/encoding/asn1/asn1.go @@ -342,7 +342,7 @@ func parseUTCTime(bytes []byte) (ret time.Time, err error) { // parseGeneralizedTime parses the GeneralizedTime from the given byte slice // and returns the resulting time. func parseGeneralizedTime(bytes []byte) (ret time.Time, err error) { - const formatStr = "20060102150405Z0700" + const formatStr = "20060102150405.999999999Z0700" s := string(bytes) if ret, err = time.Parse(formatStr, s); err != nil { diff --git a/encoding/asn1/asn1_test.go b/encoding/asn1/asn1_test.go index e0e8331..6cea311 100644 --- a/encoding/asn1/asn1_test.go +++ b/encoding/asn1/asn1_test.go @@ -317,6 +317,10 @@ func TestUTCTime(t *testing.T) { var generalizedTimeTestData = []timeTest{ {"20100102030405Z", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.UTC)}, {"20100102030405", false, time.Time{}}, + {"20100102030405.123456Z", true, time.Date(2010, 01, 02, 03, 04, 05, 123456e3, time.UTC)}, + {"20100102030405.123456", false, time.Time{}}, + {"20100102030405.Z", false, time.Time{}}, + {"20100102030405.", false, time.Time{}}, {"20100102030405+0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", 6*60*60+7*60))}, {"20100102030405-0607", true, time.Date(2010, 01, 02, 03, 04, 05, 0, time.FixedZone("", -6*60*60-7*60))}, /* These are invalid times. However, the time package normalises times From 12e8fe30e8b9d51d96434f5bedfc9435c9c97b94 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:37:17 +0100 Subject: [PATCH 02/11] Support fractions of a second when marshalling --- encoding/asn1/marshal.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/encoding/asn1/marshal.go b/encoding/asn1/marshal.go index 6e85858..db339b3 100644 --- a/encoding/asn1/marshal.go +++ b/encoding/asn1/marshal.go @@ -370,6 +370,14 @@ func marshalTimeCommon(out *forkableWriter, t time.Time) (err error) { return } + if t.Nanosecond() > 0 { + out.WriteByte('.') + date := fmt.Sprintf("%s", t) + for _, digit := range date[20:len(date)-10] { + out.WriteByte(byte(digit)) + } + } + _, offset := t.Zone() switch { From 7f390bf2fdd287305b1ce4f13d73287f6fecb541 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:38:13 +0100 Subject: [PATCH 03/11] Ensure TagGeneralizedTime tags are marshalled --- encoding/asn1/marshal.go | 12 ++++++------ encoding/asn1/marshal_test.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/encoding/asn1/marshal.go b/encoding/asn1/marshal.go index db339b3..2786260 100644 --- a/encoding/asn1/marshal.go +++ b/encoding/asn1/marshal.go @@ -422,11 +422,11 @@ func marshalBody(out *forkableWriter, value reflect.Value, params fieldParameter return nil case timeType: t := value.Interface().(time.Time) - if params.timeType == TagGeneralizedTime || outsideUTCRange(t) { + // if params.timeType == TagGeneralizedTime || outsideUTCRange(t) { return marshalGeneralizedTime(out, t) - } else { - return marshalUTCTime(out, t) - } + // } else { + // return marshalUTCTime(out, t) + // } case bitStringType: return marshalBitString(out, value.Interface().(BitString)) case objectIdentifierType: @@ -592,9 +592,9 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) tag = params.stringType } case TagUTCTime: - if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) { + // if params.timeType == TagGeneralizedTime || outsideUTCRange(v.Interface().(time.Time)) { tag = TagGeneralizedTime - } + // } } if params.set { diff --git a/encoding/asn1/marshal_test.go b/encoding/asn1/marshal_test.go index cdca8aa..8c3427f 100644 --- a/encoding/asn1/marshal_test.go +++ b/encoding/asn1/marshal_test.go @@ -102,9 +102,9 @@ var marshalTests = []marshalTest{ {explicitTagTest{64}, "3005a503020140"}, {flagTest{true}, "30028000"}, {flagTest{false}, "3000"}, - {time.Unix(0, 0).UTC(), "170d3730303130313030303030305a"}, - {time.Unix(1258325776, 0).UTC(), "170d3039313131353232353631365a"}, - {time.Unix(1258325776, 0).In(PST), "17113039313131353134353631362d30383030"}, + {time.Unix(0, 0).UTC(), "180f31393730303130313030303030305a"}, + {time.Unix(1258325776, 0).UTC(), "180f32303039313131353232353631365a"}, + {time.Unix(1258325776, 0).In(PST), "181332303039313131353134353631362d30383030"}, {farFuture(), "180f32313030303430353132303130315a"}, {generalizedTimeTest{time.Unix(1258325776, 0).UTC()}, "3011180f32303039313131353232353631365a"}, {BitString{[]byte{0x80}, 1}, "03020780"}, From 026b9daca37e8c4c85c0ebf12fc0241da4c93f12 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:34:50 +0100 Subject: [PATCH 04/11] Ensure TagUTF8String tags are marshalled --- encoding/asn1/marshal.go | 5 +++++ encoding/asn1/marshal_test.go | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/encoding/asn1/marshal.go b/encoding/asn1/marshal.go index 2786260..fc1db6a 100644 --- a/encoding/asn1/marshal.go +++ b/encoding/asn1/marshal.go @@ -624,6 +624,11 @@ func marshalField(out *forkableWriter, v reflect.Value, params fieldParameters) class = ClassContextSpecific } + // Replace TagPrintableString with TagUTF8String tags for idempotency + if (tag == 19) { + tag = 12 + } + err = marshalTagAndLength(tags, tagAndLength{class, tag, bodyLen, isCompound}) if err != nil { return diff --git a/encoding/asn1/marshal_test.go b/encoding/asn1/marshal_test.go index 8c3427f..989a375 100644 --- a/encoding/asn1/marshal_test.go +++ b/encoding/asn1/marshal_test.go @@ -112,14 +112,14 @@ var marshalTests = []marshalTest{ {ObjectIdentifier([]int{1, 2, 3, 4}), "06032a0304"}, {ObjectIdentifier([]int{1, 2, 840, 133549, 1, 1, 5}), "06092a864888932d010105"}, {ObjectIdentifier([]int{2, 100, 3}), "0603813403"}, - {"test", "130474657374"}, + {"test", "0c0474657374"}, { "" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 127 times 'x' - "137f" + + "0c7f" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + @@ -131,7 +131,7 @@ var marshalTests = []marshalTest{ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", // This is 128 times 'x' - "138180" + + "0c8180" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + "7878787878787878787878787878787878787878787878787878787878787878" + @@ -139,14 +139,14 @@ var marshalTests = []marshalTest{ }, {ia5StringTest{"test"}, "3006160474657374"}, {optionalRawValueTest{}, "3000"}, - {printableStringTest{"test"}, "3006130474657374"}, - {printableStringTest{"test*"}, "30071305746573742a"}, + {printableStringTest{"test"}, "30060c0474657374"}, + {printableStringTest{"test*"}, "30070c05746573742a"}, {rawContentsStruct{nil, 64}, "3003020140"}, {rawContentsStruct{[]byte{0x30, 3, 1, 2, 3}, 64}, "3003010203"}, {RawValue{Tag: 1, Class: 2, IsCompound: false, Bytes: []byte{1, 2, 3}}, "8103010203"}, {testSET([]int{10}), "310302010a"}, {omitEmptyTest{[]string{}}, "3000"}, - {omitEmptyTest{[]string{"1"}}, "30053003130131"}, + {omitEmptyTest{[]string{"1"}}, "300530030c0131"}, {"Σ", "0c02cea3"}, {defaultTest{0}, "3003020100"}, {defaultTest{1}, "3000"}, From 10fb8acffcd30203926ad7b966ece4c6233f50a3 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:38:57 +0100 Subject: [PATCH 05/11] Support zero nonces when sealing --- crypto/gcm/gcm.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/crypto/gcm/gcm.go b/crypto/gcm/gcm.go index fcb51a6..d570545 100644 --- a/crypto/gcm/gcm.go +++ b/crypto/gcm/gcm.go @@ -78,16 +78,17 @@ func (*gcm) Overhead() int { } func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != gcmNonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - ret, out := sliceForAppend(dst, len(plaintext)+gcmTagSize) // See GCM spec, section 7.1. var counter, tagMask [gcmBlockSize]byte - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 + if len(nonce) != gcmNonceSize { + // counter is all zeros for apple's blank iv + // The python code seems to do the hash inside g.auth for non-12 byte IVs + } else { + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } g.cipher.Encrypt(tagMask[:], counter[:]) gcmInc32(&counter) From a27a12fa40abbf6bdd594bfc51f78ee868a65ba8 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:40:07 +0100 Subject: [PATCH 06/11] Rename key variable to avoid reassignment --- cmd/irestore/irestore.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/irestore/irestore.go b/cmd/irestore/irestore.go index ba78c29..216ed80 100644 --- a/cmd/irestore/irestore.go +++ b/cmd/irestore/irestore.go @@ -139,13 +139,13 @@ func dumpKeyGroup(db *backup.MobileBackup, group []KCEntry) []interface{} { continue } - key := aeswrap.Unwrap(ckey, wkey) - if key == nil { + aesKey := aeswrap.Unwrap(ckey, wkey) + if aesKey == nil { fmt.Println("unwrap failed for class", class) continue } // Create a gcm cipher - c, err := aes.NewCipher(key) + c, err := aes.NewCipher(aesKey) if err != nil { log.Panic(err) } From 6ba94b2da95c4a21a2b98b3c639647845c310363 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:42:51 +0100 Subject: [PATCH 07/11] Add fields to keys dump to support idempotency --- cmd/irestore/irestore.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/irestore/irestore.go b/cmd/irestore/irestore.go index 216ed80..3c14f04 100644 --- a/cmd/irestore/irestore.go +++ b/cmd/irestore/irestore.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "encoding/json" "time" + "reflect" "bytes" "fmt" @@ -106,6 +107,8 @@ func parseRecord(data []byte) map[string]interface{} { ioutil.WriteFile("failed.bin", data, 0644) } // must(err) + keys := make([]string, 0, len(v)) + types := make([]string, 0, len(v)) for _, entry := range v { // Time values come through as nil, so we try again with a "DateEntry" structure. if entry.Value == nil { @@ -117,7 +120,12 @@ func parseRecord(data []byte) map[string]interface{} { } rval[entry.Key] = entry.Value + keys = append(keys, entry.Key) + types = append(types, reflect.TypeOf(entry.Value).String()) } + + rval["_fieldOrder"] = strings.Join(keys, ",") + rval["_fieldTypes"] = strings.Join(types, ",") return rval } @@ -155,7 +163,14 @@ func dumpKeyGroup(db *backup.MobileBackup, group []KCEntry) []interface{} { } plain, err := gcm.Open(nil, nil, edata, nil) must(err) + record := parseRecord(plain) + record["_class"] = class + record["_version"] = version + record["_wkey"] = wkey + record["_length"] = l + record["_ref"] = key.Ref + rval = append(rval, record) default: panic(fmt.Sprintf("Unhandled keychain blob version %d", version)) From bf430ae007aea8d8509ec5194f9a54d195d7c824 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:44:45 +0100 Subject: [PATCH 08/11] Add support for encrypting JSON keys to base64 --- cmd/irestore/irestore.go | 124 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/cmd/irestore/irestore.go b/cmd/irestore/irestore.go index 3c14f04..695a567 100644 --- a/cmd/irestore/irestore.go +++ b/cmd/irestore/irestore.go @@ -3,6 +3,7 @@ package main import ( "encoding/binary" "encoding/json" + "encoding/base64" "time" "reflect" @@ -211,6 +212,125 @@ func dumpkeys(db *backup.MobileBackup, outfile string) { } } +func unparseRecord(record map[string]interface{}) []byte { + var v EntrySET + + keys := strings.Split(fmt.Sprint(record["_fieldOrder"]), ",") + types := strings.Split(fmt.Sprint(record["_fieldTypes"]), ",") + + for index, key := range keys { + if (strings.HasPrefix(key, "_")) { + continue + } + + var entry Entry + entry.Key = key + + switch types[index] { + case "int64": + entry.Value = int(record[key].(float64)) + case "string": + entry.Value = record[key].(string) + case "time.Time": + const formatStr = "2006-01-02T15:04:05.999999999Z" + t, _ := time.Parse(formatStr, record[key].(string)) + entry.Value = t + default: + value, _ := base64.StdEncoding.DecodeString(record[key].(string)) + entry.Value = value + } + + v = append(v, entry) + } + + entries, err := asn1.Marshal(v) + if err != nil { + fmt.Println("Error marshaling record:", err) + } + + return entries +} + +func encryptKeyGroup(db *backup.MobileBackup, group interface {}, class string) []KCEntry { + var rval []KCEntry + + if (group == nil) { + return rval + } + + for _, record := range group.([]interface{}) { + var entry KCEntry + + recordObject := record.(map[string]interface{}) + + ckey := db.Keybag.GetClassKey(uint32(recordObject["_class"].(float64))) + wkey, _ := base64.StdEncoding.DecodeString(recordObject["_wkey"].(string)) + key := aeswrap.Unwrap(ckey, wkey) + + c, err := aes.NewCipher(key) + must(err) + + gcm, err := gcm.NewGCM(c) + must(err) + + unparsed := unparseRecord(recordObject) + + nonce := []byte{} + ciphertext := gcm.Seal(nil, nonce, unparsed, nil) + + data := make([]byte, 12) + le.PutUint32(data, uint32(recordObject["_version"].(float64))) + le.PutUint32(data[4:], uint32(recordObject["_class"].(float64))) + le.PutUint32(data[8:], uint32(recordObject["_length"].(float64))) + data = append(data, wkey...) + data = append(data, ciphertext...) + + entry.Data = data + ref, _ := base64.StdEncoding.DecodeString(recordObject["_ref"].(string)) + entry.Ref = ref + + rval = append(rval, entry) + } + + return rval +} + +func encryptkeys(db *backup.MobileBackup, keys string, outfile string) { + jsonFile, err := os.Open(keys) + must(err) + + defer jsonFile.Close() + + jsonBytes, _ := ioutil.ReadAll(jsonFile) + jsonMap := make(map[string](interface{})) + json.Unmarshal([]byte(jsonBytes), &jsonMap) + + path := os.ExpandEnv(outfile) + plistFile, err := os.Create(path) + must(err) + + defer plistFile.Close() + + emptyPlist := []byte{98, 112, 108, 105, 115, 116, 48, 48, 208, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9} + _, err = plistFile.Write(emptyPlist) + must(err) + + var v Keychain + err = plist.Unmarshal(plistFile, v) + must(err) + + v.General = encryptKeyGroup(db, jsonMap["General"], "genp") + v.Internet = encryptKeyGroup(db, jsonMap["Internet"], "inet") + v.Certs = encryptKeyGroup(db, jsonMap["Certs"], "cert") + v.Keys = encryptKeyGroup(db, jsonMap["Keys"], "keys") + + out, err := plist.Marshal(v) + must(err) + + err = ioutil.WriteFile(path, out, 0644) + must(err) +} + func restore(db *backup.MobileBackup, domain string, dest string) { var err error var total int64 @@ -334,6 +454,10 @@ func main() { out = os.Args[3] } dumpkeys(db, out) + case "encryptkeys": + if len(os.Args) > 4 { + encryptkeys(db, os.Args[3], os.Args[4]) + } default: help() } From a94046429c7bdbde064c9f313abfffcf629bc828 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Mon, 3 Jan 2022 20:26:02 +0100 Subject: [PATCH 09/11] Update README with encryptkeys command --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 46d2ec4..c87c423 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Usage: ls [domain] restore domain dest dumpkeys [outputfile] + encryptkeys [inputfile] [outputfile] apps ``` @@ -30,6 +31,8 @@ The `restore` command will restore the files in a domain into a directory tree. The `dumpkeys` command will dump the readable portions of the keychain to json. +The `encryptkeys` command will encrypt the dumped portions of the keychain back to a property list. + The `apps` command will list the installed apps. _Changes to the database format in recent iOS releases:_ From 8c082461d212cf73ffdd6a7cf21c8bc3b634ea93 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Sun, 9 Jan 2022 16:43:17 +0100 Subject: [PATCH 10/11] Add encryptkeys command to help --- cmd/irestore/irestore.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/irestore/irestore.go b/cmd/irestore/irestore.go index 695a567..d02a32c 100644 --- a/cmd/irestore/irestore.go +++ b/cmd/irestore/irestore.go @@ -425,6 +425,7 @@ func main() { ls [domain] restore domain dest dumpkeys [outputfile] + encryptkeys [inputfile] [outputfile] apps `) } From ce292b8afc0c5b526191669c4a02e91d66227ed9 Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Sun, 9 Jan 2022 16:44:21 +0100 Subject: [PATCH 11/11] =?UTF-8?q?Fix=20=E2=80=9CPrintln=20arg=20list=20end?= =?UTF-8?q?s=20with=20redundant=20newline=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/irestore/irestore.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/irestore/irestore.go b/cmd/irestore/irestore.go index d02a32c..c30b5f5 100644 --- a/cmd/irestore/irestore.go +++ b/cmd/irestore/irestore.go @@ -426,8 +426,7 @@ func main() { restore domain dest dumpkeys [outputfile] encryptkeys [inputfile] [outputfile] - apps -`) + apps`) } var cmd string