index.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #!/usr/bin/env node
  2. const chalk = require('chalk')
  3. const fs = require('fs')
  4. const program = require('commander')
  5. const path = require('path')
  6. const { URL } = require('url')
  7. const stripAnsi = require('strip-ansi')
  8. const iPhoneBackup = require('./util/iphone_backup.js').iPhoneBackup
  9. const normalizeCols = require('./util/normalize.js')
  10. var base = path.join(process.env.HOME, '/Library/Application Support/MobileSync/Backup/')
  11. program
  12. .version('2.0.0')
  13. .option('-l, --list', 'List Backups')
  14. .option('-c, --conversations', 'List Conversations')
  15. .option('-m, --messages <conversation_id>', 'List Conversations')
  16. .option('-r, --report <report_type>', 'Report types: apps, notes, webhistory, photolocations, manifest')
  17. .option(`-d, --dir <directory>`, `Backup Directory (default: ${base})`)
  18. .option(`-u, --device <device>`, 'Device UUID')
  19. .option(`-b, --backup <backup>`, 'Backup ID')
  20. .option(`-v, --verbose`, 'Verbose debugging output')
  21. .option(`-x, --no-color`, 'Disable colorized output')
  22. .option('-z, --dump', 'Dump a ton of raw JSON formatted data instead of formatted output')
  23. program.on('--help', function(){
  24. console.log('')
  25. console.log("If you're interested to know how this works, check out my post:")
  26. console.log("https://www.richinfante.com/2017/3/16/reverse-engineering-the-ios-backup")
  27. console.log('')
  28. })
  29. program.parse(process.argv);
  30. if(!process.stdout.isTTY) { program.color = false }
  31. base = program.dir || base
  32. if(program.verbose) console.log('Using source:', base)
  33. if(program.list) {
  34. var items = fs.readdirSync(base, { encoding: 'utf8' })
  35. .filter(el => (el.length == 40))
  36. .map(file => iPhoneBackup.fromID(file, base))
  37. // Possibly dump output
  38. if(program.dump) {
  39. console.log(JSON.stringify(items, null, 4))
  40. return
  41. }
  42. items = items.map(el => {
  43. return {
  44. encrypted: el.manifest ? el.manifest.IsEncrypted
  45. ? chalk.green('encrypted')
  46. : chalk.red('not encrypted')
  47. : 'unknown encryption',
  48. device_name: el.manifest ? el.manifest.Lockdown.DeviceName : 'Unknown Device',
  49. device_id: el.id,
  50. serial: el.manifest.Lockdown.SerialNumber,
  51. iOSVersion: el.manifest.Lockdown.ProductVersion + '(' + el.manifest.Lockdown.BuildVersion + ')',
  52. backupVersion: el.status ? el.status.Version : '?',
  53. date: el.status ? new Date(el.status.Date).toLocaleString() : ''
  54. }})
  55. .map(el => [
  56. chalk.gray(el.device_id),
  57. el.encrypted,
  58. el.date,
  59. el.device_name,
  60. el.serial,
  61. el.iOSVersion,
  62. el.backupVersion
  63. ])
  64. items = [
  65. ['UDID', 'Encryption', 'Date', 'Device Name', 'Serial #', 'iOS Version', 'Backup Version'],
  66. ['-','-','-','-','-','-','-'],
  67. ...items
  68. ]
  69. items = normalizeCols(items)
  70. items = items.map(el => el.join(' | ')).join('\n')
  71. if(!program.color) { items = stripAnsi(items) }
  72. console.log('BACKUPS LIST')
  73. console.log(items)
  74. } else if (program.conversations) {
  75. if(!program.backup) {
  76. console.log('use -b or --backup <id> to specify backup.')
  77. process.exit(1)
  78. }
  79. // Grab the backup
  80. var backup = iPhoneBackup.fromID(program.backup, base)
  81. backup.getConversations(program.dump)
  82. .then((items) => {
  83. if(program.dump) return
  84. var items = items.map(el => [
  85. el.ROWID + '',
  86. chalk.gray(el.date ? el.date.toLocaleString() : '??'),
  87. el.chat_identifier + '',
  88. el.display_name + ''
  89. ])
  90. items = [['ID', 'DATE', 'Chat Name', 'Display Name'], ['-', '-', '-', '-',], ...items]
  91. items = normalizeCols(items).map(el => el.join(' | ')).join('\n')
  92. if(!program.color) { items = stripAnsi(items) }
  93. console.log(items)
  94. })
  95. } else if(program.messages) {
  96. if(!program.backup) {
  97. console.log('use -b or --backup <id> to specify backup.')
  98. process.exit(1)
  99. }
  100. // Grab the backup
  101. var backup = iPhoneBackup.fromID(program.backup, base)
  102. backup.getMessages(program.messages, program.dump)
  103. .then((items) => {
  104. if(program.dump) return
  105. items = items.map(el => [
  106. chalk.gray(el.date ? el.date.toLocaleString() : ''),
  107. chalk.blue(el.sender + ': '),
  108. el.text || ''
  109. ])
  110. items = normalizeCols(items).map(el => el.join(' | ')).join('\n')
  111. if(!program.color) { items = stripAnsi(items) }
  112. console.log(items)
  113. })
  114. } else if(program.report) {
  115. ///
  116. /// APPS REPORT
  117. ///
  118. if(program.report == 'apps') {
  119. if(!program.backup) {
  120. console.log('use -b or --backup <id> to specify backup.')
  121. process.exit(1)
  122. }
  123. // Grab the backup
  124. var backup = iPhoneBackup.fromID(program.backup, base)
  125. if (!backup.manifest) return {}
  126. // Possibly dump output
  127. if(program.dump) {
  128. console.log(JSON.stringify(backup.manifest, null, 4))
  129. return
  130. }
  131. // Enumerate the apps in the backup
  132. var apps = []
  133. for (var key in backup.manifest.Applications) {
  134. apps.push(key)
  135. }
  136. console.log(`Apps installed inside backup: ${backup.id}`)
  137. console.log(apps.map(el => '- ' + el).join('\n'))
  138. } else if(program.report == 'notes') {
  139. if(!program.backup) {
  140. console.log('use -b or --backup <id> to specify backup.')
  141. process.exit(1)
  142. }
  143. // Grab the backup
  144. var backup = iPhoneBackup.fromID(program.backup, base)
  145. backup.getNotes(program.dump)
  146. .then((items) => {
  147. // Dump if needed
  148. if(program.dump) {
  149. console.log(JSON.stringify(items, null, 4))
  150. return
  151. }
  152. // Otherwise, format table
  153. items = items.map(el => [el.ZMODIFICATIONDATE + '', (el.Z_PK + ''), (el.ZTITLE + '').substring(0, 128)])
  154. items = [['Modified', 'ID', 'Title'], ['-', '-', '-'], ...items]
  155. items = normalizeCols(items).map(el => el.join(' | ')).join('\n')
  156. if(!program.color) { items = stripAnsi(items) }
  157. console.log(items)
  158. })
  159. } else if(program.report == 'webhistory') {
  160. if(!program.backup) {
  161. console.log('use -b or --backup <id> to specify backup.')
  162. process.exit(1)
  163. }
  164. // Grab the backup
  165. var backup = iPhoneBackup.fromID(program.backup, base)
  166. backup.getWebHistory(program.dump)
  167. .then((history) => {
  168. if(program.dump) {
  169. console.log(JSON.stringify(history, null, 4))
  170. return
  171. }
  172. var items = history.map(el => [
  173. el.visit_time + '' || '',
  174. new URL(el.url || '').origin || '',
  175. (el.title || '').substring(0, 64)
  176. ])
  177. items = [['Time', 'URL', 'Title'], ['-', '-', '-'], ...items]
  178. items = normalizeCols(items).map(el => el.join(' | ').replace(/\n/g, '')).join('\n')
  179. if(!program.color) { items = stripAnsi(items) }
  180. console.log(items)
  181. })
  182. } else if(program.report == 'photolocations') {
  183. if(!program.backup) {
  184. console.log('use -b or --backup <id> to specify backup.')
  185. process.exit(1)
  186. }
  187. // Grab the backup
  188. var backup = iPhoneBackup.fromID(program.backup, base)
  189. backup.getPhotoLocationHistory(program.dump)
  190. .then((history) => {
  191. if(program.dump) {
  192. console.log(JSON.stringify(history, null, 4))
  193. return
  194. }
  195. var items = history.map(el => [
  196. el.ZDATECREATED + '' || '',
  197. el.ZLATITUDE + '' || '',
  198. el.ZLONGITUDE + '' || '',
  199. el.ZFILENAME + '' || ''
  200. ])
  201. items = [['Time', 'Latitude', 'Longitude', 'Photo Name'], ['-', '-', '-'], ...items]
  202. items = normalizeCols(items).map(el => el.join(' | ').replace(/\n/g, '')).join('\n')
  203. if(!program.color) { items = stripAnsi(items) }
  204. console.log(items)
  205. })
  206. } else if(program.report == 'manifest') {
  207. if(!program.backup) {
  208. console.log('use -b or --backup <id> to specify backup.')
  209. process.exit(1)
  210. }
  211. // Grab the backup
  212. var backup = iPhoneBackup.fromID(program.backup, base)
  213. backup.getFileManifest()
  214. .then((items) => {
  215. if(program.dump) {
  216. console.log(JSON.stringify(items, null, 4))
  217. return
  218. }
  219. var items = items.map(el => [
  220. el.fileID + '',
  221. el.relativePath + ''
  222. ])
  223. items = [['ID', 'Path'], ['-', '-'], ...items]
  224. items = normalizeCols(items).map(el => el.join(' | ').replace(/\n/g, '')).join('\n')
  225. if(!program.color) { items = stripAnsi(items) }
  226. console.log(items)
  227. })
  228. }
  229. } else {
  230. program.outputHelp()
  231. }