Forráskód Böngészése

Add support for newer iOS Notes
Improve date output formatting

Rich Infante 7 éve
szülő
commit
090d49374a
5 módosított fájl, 117 hozzáadás és 36 törlés
  1. 4 0
      Readme.md
  2. 1 1
      package.json
  3. 65 9
      tools/index.js
  4. 43 23
      tools/util/iphone_backup.js
  5. 4 3
      tools/util/normalize.js

+ 4 - 0
Readme.md

@@ -44,6 +44,7 @@ UDID="0c1bc52c50016933679b0980ccff3680e5831162"
 - Current types:
     - `apps`: List all installed bundle ids and groups.
     - `notes`: List all notes data
+    - `oldnotes`: List old notes data that may have been retained from around iOS 8ish
     - `webhistory`: List recent web history
     - `photolocations`: List photo locations (lat/lng) and timestamp.
     - `manifest`: List all files in the backup manifest and what they are.
@@ -61,6 +62,9 @@ ibackuptool -b $UDID --report photolocations
 
 # List iOS Notes
 ibackuptool -b $UDID --report notes
+
+# List iOS Notes from old database that may exist
+ibackuptool -b $UDID --report oldnotes
 ```
 
 ### Messages Access

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "ibackuptool",
-  "version": "2.0.0",
+  "version": "2.0.1",
   "description": "Read Messages and info from iOS Backups",
   "main": "tools/index.js",
   "scripts": {

+ 65 - 9
tools/index.js

@@ -101,7 +101,7 @@ if(program.list) {
 
         var items = items.map(el => [ 
             el.ROWID + '', 
-            chalk.gray(el.date ? el.date.toLocaleString() : '??'),
+            chalk.gray(el.XFORMATTEDDATESTRING || '??'),
             el.chat_identifier + '',
             el.display_name + ''
         ])
@@ -113,6 +113,9 @@ if(program.list) {
 
         console.log(items)
     })
+    .catch((e) => {
+        console.log('[!] Encountered an Error:', e)
+    })
 } else if(program.messages) {
     if(!program.backup) {
         console.log('use -b or --backup <id> to specify backup.')
@@ -128,17 +131,20 @@ if(program.list) {
         if(program.dump) return 
 
         items = items.map(el => [
-            chalk.gray(el.date ? el.date.toLocaleString() : ''),
-            chalk.blue(el.sender + ': '),
+            chalk.gray(el.XFORMATTEDDATESTRING + ''),
+            chalk.blue(el.x_sender + ''),
             el.text || ''
         ])
 
-        items = normalizeCols(items).map(el => el.join(' | ')).join('\n')
+        items = normalizeCols(items, 2).map(el => el.join(' | ')).join('\n')
 
         if(!program.color) { items = stripAnsi(items) }
 
         console.log(items)
     })
+    .catch((e) => {
+        console.log('[!] Encountered an Error:', e)
+    })
 } else if(program.report) {
     ///
     /// APPS REPORT
@@ -168,7 +174,7 @@ if(program.list) {
 
         console.log(`Apps installed inside backup: ${backup.id}`)
         console.log(apps.map(el => '- ' + el).join('\n'))
-    } else if(program.report == 'notes') {
+    } else if(program.report == 'oldnotes') {
         if(!program.backup) {
             console.log('use -b or --backup <id> to specify backup.')
             process.exit(1)
@@ -176,7 +182,7 @@ if(program.list) {
 
         // Grab the backup
         var backup = iPhoneBackup.fromID(program.backup, base)
-        backup.getNotes(program.dump)
+        backup.getOldNotes(program.dump)
             .then((items) => {
                 // Dump if needed
                 if(program.dump) {
@@ -185,7 +191,7 @@ if(program.list) {
                 }
                 
                 // Otherwise, format table
-                items = items.map(el => [el.ZMODIFICATIONDATE + '', (el.Z_PK + ''), (el.ZTITLE + '').substring(0, 128)])
+                items = items.map(el => [el.XFORMATTEDDATESTRING + '', (el.Z_PK + ''), (el.ZTITLE + '').substring(0, 128)])
                 items = [['Modified', 'ID', 'Title'], ['-', '-', '-'], ...items]
                 items = normalizeCols(items).map(el => el.join(' | ')).join('\n')
                 
@@ -193,6 +199,42 @@ if(program.list) {
 
                 console.log(items)
             })
+            .catch((e) => {
+                console.log('[!] Encountered an Error:', e)
+            })
+    } else if(program.report == 'notes') {
+        if(!program.backup) {
+            console.log('use -b or --backup <id> to specify backup.')
+            process.exit(1)
+        }
+
+        // Grab the backup
+        var backup = iPhoneBackup.fromID(program.backup, base)
+        backup.getNotes(program.dump)
+            .then((items) => {
+                // Dump if needed
+                if(program.dump) {
+                    console.log(JSON.stringify(items, null, 4))
+                    return
+                }
+                
+                // Otherwise, format table
+                items = items.map(el => [
+                    (el.XFORMATTEDDATESTRING || el.XFORMATTEDDATESTRING1 )+ '', 
+                    (el.Z_PK + ''), 
+                    (el.ZTITLE2+ '').trim().substring(0, 128), 
+                    (el.ZTITLE1+ '').trim() || ''
+                ])
+                items = [['Modified', 'ID', 'Title2', 'Title1'], ['-', '-', '-', '-'], ...items]
+                items = normalizeCols(items, 3).map(el => el.join(' | ')).join('\n')
+                
+                if(!program.color) { items = stripAnsi(items) }
+
+                console.log(items)
+            })
+            .catch((e) => {
+                console.log('[!] Encountered an Error:', e)
+            })
     }  else if(program.report == 'webhistory') {
         if(!program.backup) {
             console.log('use -b or --backup <id> to specify backup.')
@@ -210,7 +252,7 @@ if(program.list) {
                 }
 
                 var items = history.map(el => [
-                    el.visit_time + '' || '',
+                    el.XFORMATTEDDATESTRING + '' || '',
                     new URL(el.url || '').origin || '',
                     (el.title || '').substring(0, 64)
                 ])
@@ -222,6 +264,9 @@ if(program.list) {
 
                 console.log(items)
             })
+            .catch((e) => {
+                console.log('[!] Encountered an Error:', e)
+            })
     } else if(program.report == 'photolocations') {
         if(!program.backup) {
             console.log('use -b or --backup <id> to specify backup.')
@@ -240,7 +285,7 @@ if(program.list) {
                 }
 
                 var items = history.map(el => [
-                    el.ZDATECREATED + '' || '',
+                    el.XFORMATTEDDATESTRING + '' || '',
                     el.ZLATITUDE + '' || '',
                     el.ZLONGITUDE  + '' || '',
                     el.ZFILENAME + '' || ''
@@ -253,6 +298,9 @@ if(program.list) {
 
                 console.log(items)
             })
+            .catch((e) => {
+                console.log('[!] Encountered an Error:', e)
+            })
     }  else if(program.report == 'manifest') {
         if(!program.backup) {
             console.log('use -b or --backup <id> to specify backup.')
@@ -282,6 +330,14 @@ if(program.list) {
 
                 console.log(items)
             })
+            .catch((e) => {
+                console.log('[!] Encountered an Error:', e)
+            })
+    } else {
+        console.log('')
+        console.log('  [!] Unknown Option type:', program.report)
+        console.log('')
+        program.outputHelp()
     }
 } else {
     program.outputHelp()

+ 43 - 23
tools/util/iphone_backup.js

@@ -11,6 +11,7 @@ const databases = {
   Calendar: '2041457d5fe04d39d0ab481178355df6781e6858',
   Reminders: '2041457d5fe04d39d0ab481178355df6781e6858',
   Notes: 'ca3bc056d4da0bbf88b5fb3be254f3b7147e639c',
+  Notes2: '4f98687d8ab0d6d1a371110e6b7300f6e465bef2',
   Calls: '2b2b0084a1bc3a5ac8c27afdf14afb42c61a19ca',
   Locations: '4096c9ec676f2847dc283405900e284a7c815836',
   WebHistory: 'e74113c185fd8297e140cfcf9c99436c5cc06b57',
@@ -118,7 +119,8 @@ class iPhoneBackup {
       messagedb.all(`
         SELECT 
           message.*,
-          handle.id as sender_name
+          handle.id as sender_name,
+          datetime(date / 1000000000 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING
         FROM chat_message_join 
         INNER JOIN message 
           ON message.rowid = chat_message_join.message_id 
@@ -129,35 +131,22 @@ class iPhoneBackup {
       if(err) return reject(err)
 
        chats = chats || []
-        var offset = new Date('2001-01-01 00:00:00').getTime()
-        
         if(dumpAll) console.log(JSON.stringify(chats, null, 4))
 
+        // Compute the user's name
         for(var i in chats) {
           var el = chats[i]
-          
-          // Some of the dates are of much larger precision
-          if(el.date > 100000000000000000) {
-            el.date = el.date / 1000000000
-          }
-
-          var date = new Date(offset + el.date * 1000 - tz_offset * 60 * 60 * 1000)
-          var text = el.text
-          var sender = el.is_from_me ? 'Me' : el.sender_name
+          el.x_sender = el.is_from_me ? 'Me' : el.sender_name
 
           if(!el.is_from_me) {
             var contact = await backup.getName(el.sender_name)
 
             if(contact) {
-              sender = `${contact.name} <${contact.query}>`
+              el.x_sender = `${contact.name} <${contact.query}>`
             }
           }
-
-          chats[i] = { sender, text, date }
         }
 
-        
-
         resolve(chats)
       })
     })
@@ -168,7 +157,7 @@ class iPhoneBackup {
     return new Promise((resolve, reject) => {
       var messagedb = this.getDatabase(databases.SMS)
 
-      messagedb.all('SELECT * FROM chat', async function (err, rows) {
+      messagedb.all(`SELECT *, datetime(last_read_message_timestamp / 1000000000 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM chat ORDER BY last_read_message_timestamp ASC`, async function (err, rows) {
         if(err) return reject(err)
         rows = rows || []
 
@@ -215,22 +204,53 @@ class iPhoneBackup {
     })
   }
 
-  getNotes() {
+  getOldNotes() {
     return new Promise((resolve, reject) => {
       var messagedb = this.getDatabase(databases.Notes)
-      messagedb.all('SELECT * from ZNOTE LEFT JOIN ZNOTEBODY ON ZBODY = ZNOTEBODY.Z_PK', async function (err, rows) {
+      messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from ZNOTE LEFT JOIN ZNOTEBODY ON ZBODY = ZNOTEBODY.Z_PK`, async function (err, rows) {
         if (err) reject(err)
         
         resolve(rows)
       })
-    
     })
   }
 
+  getNewNotesLegacyiOS9() {
+    return new Promise((resolve, reject) => {
+      var messagedb = this.getDatabase(databases.Notes2)
+      messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM ZICCLOUDSYNCINGOBJECT`, async function (err, rows) {
+        if(err) reject(err)
+
+        resolve(rows)
+      })
+    })
+  }
+
+  getNewNotesiOS10_iOS11() {
+    return new Promise((resolve, reject) => {
+      var messagedb = this.getDatabase(databases.Notes2)
+      messagedb.all(`SELECT *, datetime(ZCREATIONDATE + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING, datetime(ZCREATIONDATE1 + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING1 FROM ZICCLOUDSYNCINGOBJECT`, async function (err, rows) {
+        if(err) reject(err)
+
+        resolve(rows)
+      })
+    })
+  }
+
+  getNotes() {
+    if(parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
+      // Legacy iOS 9 support
+      // May work for earlier but I haven't tested it
+      return this.getNewNotesLegacyiOS9()
+    } else {
+      return this.getNewNotesiOS10_iOS11()
+    }
+  }
+
   getWebHistory() {
     return new Promise((resolve, reject) => {
       var messagedb = this.getDatabase(databases.WebHistory)
-      messagedb.all('SELECT * from history_visits LEFT JOIN history_items ON history_items.ROWID = history_visits.history_item', async function (err, rows) {
+      messagedb.all(`SELECT *, datetime(visit_time + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING from history_visits LEFT JOIN history_items ON history_items.ROWID = history_visits.history_item`, async function (err, rows) {
         if(err) reject(err)
 
         resolve(rows)
@@ -242,7 +262,7 @@ class iPhoneBackup {
   getPhotoLocationHistory() {
     return new Promise((resolve, reject) => {
       var messagedb = this.getDatabase(databases.Photos)
-      messagedb.all('SELECT ZDATECREATED, ZLATITUDE, ZLONGITUDE, ZFILENAME FROM ZGENERICASSET ORDER BY ZDATECREATED ASC', async function (err, rows) {
+      messagedb.all(`SELECT ZDATECREATED, ZLATITUDE, ZLONGITUDE, ZFILENAME, datetime(ZDATECREATED + 978307200, 'unixepoch') AS XFORMATTEDDATESTRING FROM ZGENERICASSET ORDER BY ZDATECREATED ASC`, async function (err, rows) {
         if(err) reject(err)
 
         resolve(rows)

+ 4 - 3
tools/util/normalize.js

@@ -1,6 +1,6 @@
 const stripAnsi = require('strip-ansi')
 
-module.exports = function normalizeOutput(rows) {
+module.exports = function normalizeOutput(rows, max) {
     function padEnd(string, maxLength, fillString) {
         while(stripAnsi(string).length < maxLength) {
             string = string + fillString;
@@ -10,9 +10,10 @@ module.exports = function normalizeOutput(rows) {
     }
 
     var widths = []
+    max = max || rows[0].length
 
     for(var i = 0; i < rows.length; i++) {
-        for(var j = 0; j < rows[i].length; j++) {
+        for(var j = 0; j < rows[i].length && j < max; j++) {
             if(!widths[j] || widths[j] < stripAnsi(rows[i][j]).length) {
                 widths[j] = stripAnsi(rows[i][j]).length
             }
@@ -20,7 +21,7 @@ module.exports = function normalizeOutput(rows) {
     }
 
     for(var i = 0; i < rows.length; i++) {
-        for(var j = 0; j < rows[i].length; j++) {
+        for(var j = 0; j < rows[i].length && j < max; j++) {
             if(rows[i][j] == '-') {
                 rows[i][j] = padEnd(rows[i][j], widths[j], '-')
             } else {