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

Feature/cookies addressbook conversations full (#19)

Add `address_book` report
Add `conversations_full` report 
Add `cookies` report
Add `all` placeholder report
Alberto Güerere 7 éve
szülő
commit
615575ce8b

+ 113 - 0
package-lock.json

@@ -35,6 +35,11 @@
       "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=",
       "dev": true
     },
+    "async": {
+      "version": "0.2.10",
+      "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+      "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
+    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -79,6 +84,43 @@
       "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.26.tgz",
       "integrity": "sha1-OvFnL6Ytry1eyvrPblqg0l4Cwcg="
     },
+    "binary-cookies": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/binary-cookies/-/binary-cookies-0.1.1.tgz",
+      "integrity": "sha1-ltRFJ0bFOij+wv8K/WEMgfrjGeI=",
+      "requires": {
+        "argparse": "0.1.16",
+        "async": "0.2.10",
+        "bufferpack": "0.0.6",
+        "colors": "0.6.2",
+        "path": "0.4.10",
+        "underscore": "1.4.4",
+        "winston": "0.6.2"
+      },
+      "dependencies": {
+        "argparse": {
+          "version": "0.1.16",
+          "resolved": "https://registry.npmjs.org/argparse/-/argparse-0.1.16.tgz",
+          "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=",
+          "requires": {
+            "underscore": "1.7.0",
+            "underscore.string": "2.4.0"
+          },
+          "dependencies": {
+            "underscore": {
+              "version": "1.7.0",
+              "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
+              "integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk="
+            }
+          }
+        },
+        "colors": {
+          "version": "0.6.2",
+          "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
+          "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w="
+        }
+      }
+    },
     "bind-obj-methods": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/bind-obj-methods/-/bind-obj-methods-1.0.0.tgz",
@@ -118,6 +160,11 @@
         "concat-map": "0.0.1"
       }
     },
+    "bufferpack": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/bufferpack/-/bufferpack-0.0.6.tgz",
+      "integrity": "sha1-+z2HOKDh5OA7z/mfmnX57Bip1z4="
+    },
     "caseless": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
@@ -241,6 +288,11 @@
         "boom": "2.10.1"
       }
     },
+    "cycle": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz",
+      "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI="
+    },
     "dashdash": {
       "version": "1.14.1",
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -317,6 +369,11 @@
       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
       "dev": true
     },
+    "eyes": {
+      "version": "0.1.8",
+      "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
+      "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A="
+    },
     "flat": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz",
@@ -2402,6 +2459,11 @@
         "own-or": "1.0.0"
       }
     },
+    "path": {
+      "version": "0.4.10",
+      "resolved": "https://registry.npmjs.org/path/-/path-0.4.10.tgz",
+      "integrity": "sha1-Iv7ye3zW6vMPsT/AJ4AelW5RjvE="
+    },
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -2422,6 +2484,11 @@
         "pinkie": "2.0.4"
       }
     },
+    "pkginfo": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.2.3.tgz",
+      "integrity": "sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg="
+    },
     "plist": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/plist/-/plist-2.1.0.tgz",
@@ -3400,6 +3467,11 @@
         }
       }
     },
+    "stack-trace": {
+      "version": "0.0.10",
+      "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+      "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
+    },
     "stack-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz",
@@ -3569,6 +3641,16 @@
       "dev": true,
       "optional": true
     },
+    "underscore": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz",
+      "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ="
+    },
+    "underscore.string": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.4.0.tgz",
+      "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs="
+    },
     "unicode-length": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/unicode-length/-/unicode-length-1.0.3.tgz",
@@ -3636,6 +3718,37 @@
         "isexe": "2.0.0"
       }
     },
+    "winston": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/winston/-/winston-0.6.2.tgz",
+      "integrity": "sha1-QUT+JYbNwZphK/jANVkBMskGS9I=",
+      "requires": {
+        "async": "0.1.22",
+        "colors": "0.6.2",
+        "cycle": "1.0.3",
+        "eyes": "0.1.8",
+        "pkginfo": "0.2.3",
+        "request": "2.9.203",
+        "stack-trace": "0.0.10"
+      },
+      "dependencies": {
+        "async": {
+          "version": "0.1.22",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.1.22.tgz",
+          "integrity": "sha1-D8GqoIig4+8Ovi2IMbqw3PiEUGE="
+        },
+        "colors": {
+          "version": "0.6.2",
+          "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
+          "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w="
+        },
+        "request": {
+          "version": "2.9.203",
+          "resolved": "https://registry.npmjs.org/request/-/request-2.9.203.tgz",
+          "integrity": "sha1-bBcRpUB/uUoRQhlWPkQUW8v0cjo="
+        }
+      }
+    },
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
   "author": "@richinfante",
   "license": "MIT",
   "dependencies": {
+    "binary-cookies": "^0.1.1",
     "bplist-parser": "^0.1.1",
     "chalk": "^1.1.3",
     "commander": "^2.12.2",

+ 6 - 1
tools/index.js

@@ -11,6 +11,8 @@ var reportTypes = {
   'apps': require('./reports/apps'),
   'calls': require('./reports/calls'),
   'conversations': require('./reports/conversations'),
+  'conversations_full': require('./reports/conversations_full'),
+  'cookies': require('./reports/cookies'),
   'list': require('./reports/list'),
   'manifest': require('./reports/manifest'),
   'messages': require('./reports/messages'),
@@ -20,7 +22,10 @@ var reportTypes = {
   'voicemail-files': require('./reports/voicemail-files'),
   'voicemail': require('./reports/voicemail'),
   'webhistory': require('./reports/webhistory'),
-  'wifi': require('./reports/wifi')
+  'calls_statistics': require('./reports/calls_statistics'),
+  'wifi': require('./reports/wifi'),
+  'all': require('./reports/all'),
+  'address_book': require('./reports/address_book')
 }
 
 var formatters = {

+ 38 - 0
tools/reports/address_book.js

@@ -0,0 +1,38 @@
+module.exports.name = 'address_book'
+module.exports.description = 'List all address book records contained in the backup.'
+
+// Specify this reporter requires a backup. 
+// The second parameter to func() is now a backup instead of the path to one.
+module.exports.requiresBackup = true
+
+// Specify this reporter supports the promises API for allowing chaining of reports.
+module.exports.usesPromises = true
+
+module.exports.func = function (program, backup, resolve, reject) {
+  backup.getAddressBook()
+  .then((items) => {
+
+    // Use the configured formatter to print the rows.
+    const result = program.formatter.format(items, {
+      // Color formatting?
+      program: program,
+
+      // Columns to be displayed in human-readable printouts.
+      // Some formatters, like raw or CSV, ignore these.
+      columns: {
+        'ID': el => el.ROWID,
+        'First': el => el.First ? el.First.substring(0,10) + '' : '',
+        'Last': el => el.Last ? el.Last.substring(0,10) + '' : '',
+        'Organization': el => el.organization ? el.organization.substring(0,10) + '' : '',
+        'Phone Work': el => el.phone_work ? el.phone_work.substring(0,14) + '' : '',
+        'Phone Mobile': el => el.phone_mobile ? el.phone_mobile.substring(0,14) + '' : '',
+        'Phone Home': el => el.phone_home ? el.phone_home.substring(0,14) + '' : '',
+        'Email': el => el.email ? el.email.substring(0,28) + '' : ''
+      }
+    })
+
+    // If using promises, we must call resolve()
+    resolve(result)
+  })
+  .catch(reject)
+}

+ 2 - 0
tools/reports/all.js

@@ -0,0 +1,2 @@
+module.exports.name = 'all'
+module.exports.description = 'Runs all reports at once'

+ 50 - 0
tools/reports/calls_statistics.js

@@ -0,0 +1,50 @@
+module.exports.name = 'calls_statistics'
+module.exports.description = 'Get statistics about all calls'
+
+// Specify this reporter requires a backup. 
+// The second parameter to func() is now a backup instead of the path to one.
+module.exports.requiresBackup = true
+
+// Specify this reporter supports the promises API for allowing chaining of reports.
+module.exports.usesPromises = true
+
+// You can also provide an array of functions instead of using `module.exports.func`.
+// These functions *should* be independent ranges to ensure reliable execution
+module.exports.functions = {
+  '>=9.0': function(program,backup,resolve,reject) {
+    // This function would be called for iOS 10+
+    backup.getCallsStatistics()
+    .then((items) => {
+      
+      var result = program.formatter.format(Object.entries(items[0]), {
+        program: program,
+        columns: {
+          'Key': el => el[0] + '',
+          'Value': el => el[1] + ''
+        }
+      })
+
+      resolve(result)
+
+    })
+    .catch(reject)
+  }, 
+  '>=1.0,<9.0': function(program,backup,resolve,reject) {
+    // This function would be called for all iOS 9.
+    backup.getCallsStatisticsiOS7()
+    .then((items) => {
+
+      var result = program.formatter.format(items, {
+        program: program,
+        columns: {
+          'Key': el => el.key + '',
+          'Value': el => el.value + ''
+        }
+      })
+
+      resolve(result)
+
+    })
+    .catch(reject)
+  }
+}

+ 60 - 0
tools/reports/conversations_full.js

@@ -0,0 +1,60 @@
+module.exports.name = 'conversations_full'
+module.exports.description = 'List all SMS and iMessage conversations and their messages (dump only)'
+
+// Specify this reporter requires a backup. 
+// The second parameter to func() is now a backup instead of the path to one.
+module.exports.requiresBackup = true
+
+// Specify this reporter supports the promises API for allowing chaining of reports.
+module.exports.usesPromises = true
+
+module.exports.func = async function (program, backup, resolve, reject) {
+  //if (program.dump) {
+    //return new Promise(async (resolve, reject) => {
+    let conversations = await backup.getConversations();
+    for (let el of conversations) {
+      el.messages = await backup.getMessages(el.ROWID, true);
+    }
+
+      // Use the configured formatter to print the rows.
+    const result = program.formatter.format(conversations, {
+      // Color formatting?
+      program: program,
+
+      // Columns to be displayed in human-readable printouts.
+      // Some formatters, like raw or CSV, ignore these.
+      columns: {
+        'ID': el => el.ROWID,
+        'Date': el => el.XFORMATTEDDATESTRING || '??',
+        'Service': el => el.service_name + '',
+        'Chat Name': el => el.chat_identifier + '',
+        'Display Name': el => el.display_name + '',
+      }
+    })
+      
+    resolve(conversations);
+    //});
+  /*} 
+  else {
+    backup.getConversations(program.dump)
+      .then((items) => {
+        items = items.map(el => [
+          el.ROWID + '',
+          chalk.gray(el.XFORMATTEDDATESTRING || '??'),
+          el.service_name + '', 
+          el.chat_identifier + '',
+          el.display_name + ''
+        ])
+
+        items = [['ID', 'DATE', 'Service', 'Chat Name', 'Display Name'], ['-', '-', '-', '-', '-'], ...items]
+        items = normalizeCols(items).map(el => el.join(' | ')).join('\n')
+
+        if (!program.color) { items = stripAnsi(items) }
+
+        console.log(items)
+      })
+      .catch((e) => {
+        console.log('[!] Encountered an Error:', e)
+      })
+  }*/
+}

+ 31 - 0
tools/reports/cookies.js

@@ -0,0 +1,31 @@
+module.exports.name = 'cookies'
+module.exports.description = 'List all iOS cookies'
+
+// Specify this reporter requires a backup. 
+// The second parameter to func() is now a backup instead of the path to one.
+module.exports.requiresBackup = true
+
+// Specify this reporter supports the promises API for allowing chaining of reports.
+module.exports.usesPromises = true
+
+module.exports.func = function (program, backup, resolve, reject) {
+
+  backup.getCookies()
+  .then((items) => {
+    // Use the configured formatter to print the rows.
+    const result = program.formatter.format(items, {
+      // Color formatting?
+      program: program,
+
+      // Columns to be displayed in human-readable printouts.
+      // Some formatters, like raw or CSV, ignore these.
+      columns: {
+        'domain': el => el.domain,
+        'url': el => el.cookie.url
+      }
+    })
+
+    resolve(result)
+  })
+  .catch(reject)
+}

+ 49 - 53
tools/reports/manifest.js

@@ -19,68 +19,64 @@ module.exports.supportedVersions = '>=10.0'
 
 module.exports.func = function (program, backup, resolve, reject) {
 
-    backup.getFileManifest()
-    .then((items) => {
-      if (program.dump) {
-        console.log(JSON.stringify(items, null, 4))
-        return
-      }
+  backup.getFileManifest()
+  .then((items) => {
 
-      // Extract items for analysis on-disk.
-      if (program.extract) {
-        for (var item of items) {
-          // Filter by the domain.
-          // Simple "Contains" Search
-          if (program.filter === 'all' || (program.filter && item.domain.indexOf(program.filter) > -1)) {
-            // Do nothing, we'll process later.
-          } else {
-            // Skip to the next iteration of the loop.
-            console.log(chalk.yellow('skipped'), item.relativePath)
-            continue
-          }
+    // Extract items for analysis on-disk.
+    if (program.extract) {
+      for (var item of items) {
+        // Filter by the domain.
+        // Simple "Contains" Search
+        if (program.filter === 'all' || (program.filter && item.domain.indexOf(program.filter) > -1)) {
+          // Do nothing, we'll process later.
+        } else {
+          // Skip to the next iteration of the loop.
+          console.log(chalk.yellow('skipped'), item.relativePath)
+          continue
+        }
 
-          try {
-            var sourceFile = backup.getFileName(item.fileID)
-            var stat = fs.lstatSync(sourceFile)
+        try {
+          var sourceFile = backup.getFileName(item.fileID)
+          var stat = fs.lstatSync(sourceFile)
 
-            // Only process files that exist.
-            if (stat.isFile() && fs.existsSync(sourceFile)) {
-              console.log(chalk.green('export'), item.relativePath)
+          // Only process files that exist.
+          if (stat.isFile() && fs.existsSync(sourceFile)) {
+            console.log(chalk.green('export'), item.relativePath)
 
-                // Calculate the output dir.
-              var outDir = path.join(program.extract, item.domain, item.relativePath)
+              // Calculate the output dir.
+            var outDir = path.join(program.extract, item.domain, item.relativePath)
 
-                // Create the directory and copy
-              fs.ensureDirSync(path.dirname(outDir))
-              fs.copySync(sourceFile, outDir)
+              // Create the directory and copy
+            fs.ensureDirSync(path.dirname(outDir))
+            fs.copySync(sourceFile, outDir)
 
-                // Save output info to the data item.
-              item.output_dir = outDir
-            } else if (stat.isDirectory()) {
-              // Do nothing..
-            } else {
-              console.log(chalk.blue('not found'), item.relativePath)
-            }
-          } catch (e) {
-            console.log(chalk.red('fail'), item.relativePath, e.toString())
+              // Save output info to the data item.
+            item.output_dir = outDir
+          } else if (stat.isDirectory()) {
+            // Do nothing..
+          } else {
+            console.log(chalk.blue('not found'), item.relativePath)
           }
+        } catch (e) {
+          console.log(chalk.red('fail'), item.relativePath, e.toString())
         }
+      }
 
-        resolve(result)
-      } else {
+      resolve(result)
+    } else {
 
-        var result = program.formatter.format(items, {
-          program: program,
-          columns: {
-            'ID': el => el.fileID,
-            'Domain/Path': el => el.domain + ': ' + el.relativePath
-          }
-        })
+      var result = program.formatter.format(items, {
+        program: program,
+        columns: {
+          'ID': el => el.fileID,
+          'Domain/Path': el => el.domain + ': ' + el.relativePath
+        }
+      })
 
-        resolve(result)
-      }
-    })
-    .catch((e) => {
-        console.log('[!] Encountered an Error:', e)
-    })
+      resolve(result)
+    }
+  })
+  .catch((e) => {
+      console.log('[!] Encountered an Error:', e)
+  })
 }

+ 28 - 29
tools/reports/voicemail-files.js

@@ -16,36 +16,35 @@ module.exports.usesPromises = true
 
 module.exports.func = function (program, backup, resolve, reject) {
 
-  backup.getVoicemailFileList()
-    .then((list) => {
-
-      // Extract to the specified location
-      if (program.extract) {
-        for (var item of list) {
-          try {
-            var outDir = path.join(program.extract, path.basename(item.relativePath))
-            fs.ensureDirSync(path.dirname(outDir))
-            fs.createReadStream(backup.getFileName(item.fileID)).pipe(fs.createWriteStream(outDir))
-            item.output_dir = outDir
-          } catch (e) {
-            console.log(`Couldn't Export: ${item.relativePath}`, e)
-          }
+backup.getVoicemailFileList()
+  .then((list) => {
+
+    // Extract to the specified location
+    if (program.extract) {
+      for (var item of list) {
+        try {
+          var outDir = path.join(program.extract, path.basename(item.relativePath))
+          fs.ensureDirSync(path.dirname(outDir))
+          fs.createReadStream(backup.getFileName(item.fileID)).pipe(fs.createWriteStream(outDir))
+          item.output_dir = outDir
+        } catch (e) {
+          console.log(`Couldn't Export: ${item.relativePath}`, e)
         }
       }
-
-      // Generate report.
-      var result = program.formatter.format(list, {
-        program: program,
-        columns: {
-          'ID': el => el.fileID,
-          'Path': el => el.relativePath,
-          'Export Path': el => el.output_dir || '<not exported>'
-        }
-      })
-
-      resolve(result)
-    })
-    .catch((e) => {
-      console.log('[!] Encountered an Error:', e)
+    }
+    // Generate report.
+    var result = program.formatter.format(list, {
+      program: program,
+      columns: {
+        'ID': el => el.fileID,
+        'Path': el => el.relativePath,
+        'Export Path': el => el.output_dir || '<not exported>'
+      }
     })
+
+    resolve(result)
+  })
+  .catch((e) => {
+    console.log('[!] Encountered an Error:', e)
+  })
 }

+ 132 - 0
tools/util/iphone_backup.js

@@ -4,12 +4,15 @@ const bplist = require('bplist-parser')
 const fs = require('fs')
 const plist = require('plist')
 const mac_address_parse = require('./mac_address_parse')
+const cookieParser = require('binary-cookies')()
 const tz_offset = 5
 
 const databases = {
   SMS: '3d0d7e5fb2ce288813306e4d4636395e047a3d28',
+  AddressBook: '31bb7ba8914766d4ba40d6dfb6113c8b614be442',
   Contacts: '31bb7ba8914766d4ba40d6dfb6113c8b614be442',
   Calendar: '2041457d5fe04d39d0ab481178355df6781e6858',
+  'Cookies.binarycookies': '69b1865768101bacde5b77ccc44445cea9ce1261',
   Reminders: '2041457d5fe04d39d0ab481178355df6781e6858',
   Notes: 'ca3bc056d4da0bbf88b5fb3be254f3b7147e639c',
   Notes2: '4f98687d8ab0d6d1a371110e6b7300f6e465bef2',
@@ -391,6 +394,57 @@ class iPhoneBackup {
     })
   }
 
+  getCallsStatisticsiOS7 () {
+    return new Promise((resolve, reject) => {
+      var messagedb = this.getDatabase(databases.Calls)
+      messagedb.all(`SELECT * from _SqliteDatabaseProperties`, async function (err, rows) {
+        if (err) reject(err)
+        resolve(rows)
+      })
+    })
+  }
+
+  getCallsStatistics () {
+    return new Promise((resolve, reject) => {
+      var messagedb = this.getDatabase(databases.Calls2)
+      messagedb.all(`SELECT * from ZCALLDBPROPERTIES`, async function (err, rows) {
+        if (err) reject(err)
+        resolve(rows)
+      })
+    })
+  }
+
+  getCallsList () {
+    if (parseInt(this.manifest.Lockdown.BuildVersion) <= 13) {
+      // Legacy iOS 9 support
+      // May work for earlier but I haven't tested it
+      return this.getCallsListiOS7()
+    } else {
+      return this.getCallsList()
+    }
+  }
+
+  getCallsListiOS7 () {
+    return new Promise((resolve, reject) => {
+      var messagedb = this.getDatabase(databases.Calls)
+      messagedb.all(`SELECT 
+        ROWID as Z_PK, 
+        datetime(date, 'unixepoch') AS XFORMATTEDDATESTRING, 
+        answered as ZANSWERED,
+        duration as ZDURATION,
+        address as ZADDRESS,
+        country_code as ZISO_COUNTRY_CODE, 
+        country_code as ZISO_COUNTRY_CODE, 
+        * from call ORDER BY date ASC`, async function (err, rows) {
+        if (err) reject(err)
+
+        resolve(rows)
+      })
+    })
+  }
+
+
+
   getCallsList () {
     return new Promise((resolve, reject) => {
       var messagedb = this.getDatabase(databases.Calls2)
@@ -441,6 +495,84 @@ class iPhoneBackup {
       }
     })
   }
+
+  getCookies () {
+    return new Promise((resolve, reject) => {
+      const self = this
+      var manifestdb = this.getDatabase('Manifest.db', true)
+      manifestdb.all(`SELECT fileID,domain,relativePath from FILES where relativePath like 'Library/Cookies/Cookies.binarycookies'`, async function (err, rows) {
+        if (err) reject(err)
+        let cookiesResult = [];
+        const iterateElements = (elements, index, callback) => {
+          if (index == elements.length)
+            return callback();
+          // do parse call with element
+          var ele = elements[index];
+          cookieParser.parse(self.getFileName(ele.fileID), function(err, cookies) {
+            //console.log(ele.domain, ':', cookies)
+            
+            if (err) {
+              cookiesResult.push({
+                domain: ele.domain,
+                error: err
+              })
+            } else {
+              cookies.forEach(cookie => {
+                cookie.url = cookie.url.replace(/\0/g, '')
+                cookie.name = cookie.name.replace(/\0/g, '')
+                cookie.path = cookie.path.replace(/\0/g, '')
+                cookie.value = cookie.value.replace(/\0/g, '')
+                cookiesResult.push({
+                  domain: ele.domain,
+                  cookie: cookie
+                })
+              });
+            }
+            iterateElements(elements, index+1, callback);
+          })
+        }
+        iterateElements(rows, 0, () => {
+          resolve(cookiesResult)
+        })
+      })
+    })
+  }
+
+  getAddressBook () {
+    return new Promise((resolve, reject) => {
+      var addressbookdb = this.getDatabase(databases.AddressBook);
+      try {
+        const query = `
+        select ABPerson.ROWID
+            , ABPerson.first
+            , ABPerson.middle
+            , ABPerson.last
+            , ABPerson.Organization as organization
+            , ABPerson.Department as department
+            , ABPerson.Birthday as birthday
+            , ABPerson.JobTitle as jobtitle
+
+            , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Work>!$_')) as phone_work
+            , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Mobile>!$_')) as phone_mobile
+            , (select value from ABMultiValue where property = 3 and record_id = ABPerson.ROWID and label = (select ROWID from ABMultiValueLabel where value = '_$!<Home>!$_')) as phone_home
+
+            , (select value from ABMultiValue where property = 4 and record_id = ABPerson.ROWID) as email
+            
+            , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'street')) as address
+            , (select value from ABMultiValueEntry where parent_id in (select ROWID from ABMultiValue where record_id = ABPerson.ROWID) and key = (select ROWID from ABMultiValueEntryKey where lower(value) = 'city')) as city
+          from ABPerson
+        order by ABPerson.ROWID
+        `
+        addressbookdb.all(query, async function (err, rows) {
+          if (err) reject(err)
+  
+          resolve(rows)
+        })
+      } catch (e) {
+        reject(e)
+      }
+    })
+  }
 }
 
 module.exports.availableBackups = function () {