From 635d0986cff8b2191d8c66a0fbb5c31019268edf Mon Sep 17 00:00:00 2001 From: AloneMonkey Date: Fri, 1 Dec 2017 01:20:02 +0800 Subject: [PATCH] version 1.0 --- README.md | 48 ++++++++ app.js | 31 ++++++ dump.js | 323 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ dump.py | 129 ++++++++++++++++++++++ 4 files changed, 531 insertions(+) create mode 100644 app.js create mode 100644 dump.js create mode 100755 dump.py diff --git a/README.md b/README.md index eb9d45f..1a04ed4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,50 @@ # frida-ios-dump pull decrypted ipa from jailbreak device + +### Usage + +## 1. install [frida](http://www.frida.re/) on device and mac + +## 2. iproxy 2222 22 + +## 3. ./dump.py 微信 + +``` +➜ frida-ios-dump ./dump.py 微信 +open target app...... +start dump target app...... +start dump /var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/WeChat +WeChat 100% 68MB 11.4MB/s 00:05 +start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/WCDB.framework/WCDB +WCDB 100% 2555KB 11.0MB/s 00:00 +start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/MMCommon.framework/MMCommon +MMCommon 100% 979KB 10.6MB/s 00:00 +start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/MultiMedia.framework/MultiMedia +MultiMedia 100% 6801KB 11.1MB/s 00:00 +start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/mars.framework/mars +mars 100% 7462KB 11.1MB/s 00:00 +AppIcon60x60@2x.png 100% 2253 230.9KB/s 00:00 +AppIcon60x60@3x.png 100% 4334 834.8KB/s 00:00 +AppIcon76x76@2x~ipad.png 100% 2659 620.6KB/s 00:00 +AppIcon76x76~ipad.png 100% 1523 358.0KB/s 00:00 +AppIcon83.5x83.5@2x~ipad.png 100% 2725 568.9KB/s 00:00 +Assets.car 100% 10MB 11.1MB/s 00:00 +....... +AppIntentVocabulary.plist 100% 197 52.9KB/s 00:00 +AppIntentVocabulary.plist 100% 167 43.9KB/s 00:00 +AppIntentVocabulary.plist 100% 187 50.2KB/s 00:00 +InfoPlist.strings 100% 1720 416.4KB/s 00:00 +TipsPressTalk@2x.png 100% 14KB 2.2MB/s 00:00 +mm.strings 100% 404KB 10.2MB/s 00:00 +network_setting.html 100% 1695 450.4KB/s 00:00 +InfoPlist.strings 100% 1822 454.1KB/s 00:00 +mm.strings 100% 409KB 10.2MB/s 00:00 +network_setting.html 100% 1819 477.5KB/s 00:00 +InfoPlist.strings 100% 1814 466.8KB/s 00:00 +mm.strings 100% 409KB 10.3MB/s 00:00 +network_setting.html 100% 1819 404.9KB/s 00:00 +``` + +congratulations!!! You've got a decrypted ipa file. + +Drag to [MonkeyDev](https://github.com/AloneMonkey/MonkeyDev), Happy hacking! \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..ce12626 --- /dev/null +++ b/app.js @@ -0,0 +1,31 @@ +//by: AloneMonkey + +const LSApplicationWorkspace = ObjC.classes.LSApplicationWorkspace; + +function openApplication(appid){ + const workspace = LSApplicationWorkspace.defaultWorkspace(); + return workspace.openApplicationWithBundleID_(appid); +} + +function getbundleid(name){ + const workspace = LSApplicationWorkspace.defaultWorkspace(); + const apps = workspace.allApplications(); + var result; + for(var index = 0; index < apps.count(); index++){ + var proxy = apps.objectAtIndex_(index); + if(proxy.localizedName().toString() == name){ + return proxy.bundleIdentifier().toString(); + } + } + return "" +}; + +function handleMessage(message) { + const bundleid = getbundleid(message); + if(bundleid.length > 0){ + openApplication(bundleid); + } + send({opened: "ok"}); +} + +recv(handleMessage); \ No newline at end of file diff --git a/dump.js b/dump.js new file mode 100644 index 0000000..4fcf4d7 --- /dev/null +++ b/dump.js @@ -0,0 +1,323 @@ +var O_RDONLY = 0; +var O_WRONLY = 1; +var O_RDWR = 2; +var O_CREAT = 512; + +var SEEK_SET = 0; +var SEEK_CUR = 1; +var SEEK_END = 2; + +function allocStr(str) { + return Memory.allocUtf8String(str); +} + +function putStr(addr, str) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.writeUtf8String(addr, str); +} + +function getByteArr(addr, l) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.readByteArray(addr, l); +} + +function getU8(addr) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.readU8(addr); +} + +function putU8(addr, n) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.writeU8(addr, n); +} + +function getU16(addr) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.readU16(addr); +} + +function putU16(addr, n) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.writeU16(addr, n); +} + +function getU32(addr) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.readU32(addr); +} + +function putU32(addr, n) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.writeU32(addr, n); +} + +function getU64(addr) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.readU64(addr); +} + +function putU64(addr, n) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.writeU64(addr, n); +} + +function getPt(addr) { + if (typeof addr == "number") { + addr = ptr(addr); + } + return Memory.readPointer(addr); +} + +function putPt(addr, n) { + if (typeof addr == "number") { + addr = ptr(addr); + } + if (typeof n == "number") { + n = ptr(n); + } + return Memory.writePointer(addr, n); +} + +function malloc(size) { + return Memory.alloc(size); +} + +function getExportFunction(type, name, ret, args) { + var nptr; + nptr = Module.findExportByName(null, name); + if (nptr === null) { + console.log("cannot find " + name); + return null; + } else { + if (type === "f") { + var funclet = new NativeFunction(nptr, ret, args); + if (typeof funclet === "undefined") { + console.log("parse error " + name); + return null; + } + return funclet; + } else if (type === "d") { + var datalet = Memory.readPointer(nptr); + if (typeof datalet === "undefined") { + console.log("parse error " + name); + return null; + } + return datalet; + } + } +} + +NSSearchPathForDirectoriesInDomains = getExportFunction("f", "NSSearchPathForDirectoriesInDomains", "pointer", ["int", "int", "int"]); +wrapper_open = getExportFunction("f", "open", "int", ["pointer", "int", "int"]); +read = getExportFunction("f", "read", "int", ["int", "pointer", "int"]); +write = getExportFunction("f", "write", "int", ["int", "pointer", "int"]); +lseek = getExportFunction("f", "lseek", "int64", ["int", "int64", "int"]); +close = getExportFunction("f", "close", "int", ["int"]); +remove = getExportFunction("f", "remove", "int", ["pointer"]); + +function getDocumentDir() { + var NSDocumentDirectory = 9; + var NSUserDomainMask = 1; + var npdirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, 1); + return ObjC.Object(npdirs).objectAtIndex_(0).toString(); +} + +function open(pathname, flags, mode) { + if (typeof pathname == "string") { + pathname = allocStr(pathname); + } + return wrapper_open(pathname, flags, mode); +} + +var modules = null; +function getAllAppModules() { + if (modules == null) { + modules = new Array(); + var tmpmods = Process.enumerateModulesSync(); + for (var i = 0; i < tmpmods.length; i++) { + if (tmpmods[i].path.indexOf(".app") != -1) { + modules.push(tmpmods[i]); + } + } + } + return modules; +} + +var FAT_MAGIC = 0xcafebabe; +var FAT_CIGAM = 0xbebafeca; +var MH_MAGIC = 0xfeedface; +var MH_CIGAM = 0xcefaedfe; +var MH_MAGIC_64 = 0xfeedfacf; +var MH_CIGAM_64 = 0xcffaedfe; +var LC_SEGMENT = 0x1; +var LC_SEGMENT_64 = 0x19; +var LC_ENCRYPTION_INFO = 0x21; +var LC_ENCRYPTION_INFO_64 = 0x2C; + +function pad(str, n) { + return Array(n-str.length+1).join("0")+str; +} + +function swap32(value) { + value = pad(value.toString(16),8) + var result = ""; + for(var i = 0; i < value.length; i=i+2){ + result += value.charAt(value.length - i - 2); + result += value.charAt(value.length - i - 1); + } + return parseInt(result,16) +} + +function dumpModule(name) { + if (modules == null) { + modules = getAllAppModules(); + } + var targetmod = null; + for (var i = 0; i < modules.length; i++) { + if (modules[i].path.indexOf(name) != -1) { + targetmod = modules[i]; + break; + } + } + if (targetmod == null) { + console.log("Cannot find module"); + return; + } + var modbase = modules[i].base; + var modsize = modules[i].size; + var newmodname = modules[i].name; + var newmodpath = getDocumentDir() + "/" + newmodname; + var oldmodpath = modules[i].path; + + remove(allocStr(newmodpath)); + + var fmodule = open(newmodpath, O_CREAT | O_RDWR, 0); + var foldmodule = open(oldmodpath, O_RDONLY, 0); + + if (fmodule == -1 || foldmodule == -1) { + console.log("Cannot open file" + newmodpath); + return; + } + + var is64bit = false; + var size_of_mach_header = 0; + var magic = getU32(modbase); + var cur_cpu_type = getU32(modbase.add(4)); + var cur_cpu_subtype = getU32(modbase.add(8)); + if (magic == MH_MAGIC || magic == MH_CIGAM) { + is64bit = false; + size_of_mach_header = 28; + }else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) { + is64bit = true; + size_of_mach_header = 32; + } + + var BUFSIZE = 4096; + var buffer = malloc(BUFSIZE); + + read(foldmodule, buffer, BUFSIZE); + + var fileoffset = 0; + var filesize = 0; + magic = getU32(buffer); + if(magic == FAT_CIGAM || magic == FAT_MAGIC){ + var off = 4; + var archs = swap32(getU32(buffer.add(off))); + for (var i = 0; i < archs; i++) { + var cputype = swap32(getU32(buffer.add(off + 4))); + var cpusubtype = swap32(getU32(buffer.add(off + 8))); + if(cur_cpu_type == cputype && cur_cpu_subtype == cpusubtype){ + fileoffset = swap32(getU32(buffer.add(off + 12))); + filesize = swap32(getU32(buffer.add(off + 16))); + break; + } + off += 20; + } + + if(fileoffset == 0 || filesize == 0) + return; + + lseek(fmodule, 0, SEEK_SET); + lseek(foldmodule, fileoffset, SEEK_SET); + for(var i = 0; i < parseInt(filesize / BUFSIZE); i++) { + read(foldmodule, buffer, BUFSIZE); + write(fmodule, buffer, BUFSIZE); + } + if(filesize % BUFSIZE){ + read(foldmodule, buffer, filesize % BUFSIZE); + write(fmodule, buffer, filesize % BUFSIZE); + } + }else{ + var readLen = 0; + lseek(foldmodule, 0, SEEK_SET); + lseek(fmodule, 0, SEEK_SET); + while(readLen = read(foldmodule, buffer, BUFSIZE)) { + write(fmodule, buffer, readLen); + } + } + + var ncmds = getU32(modbase.add(16)); + var off = size_of_mach_header; + var offset_cryptid = -1; + var crypt_off = 0; + var crypt_size = 0; + var segments = []; + for (var i = 0; i < ncmds; i++) { + var cmd = getU32(modbase.add(off)); + var cmdsize = getU32(modbase.add(off + 4)); + if (cmd == LC_ENCRYPTION_INFO || cmd == LC_ENCRYPTION_INFO_64) { + offset_cryptid = off + 16; + crypt_off = getU32(modbase.add(off + 8)); + crypt_size = getU32(modbase.add(off + 12)); + } + off += cmdsize; + } + + if (offset_cryptid != -1) { + var tpbuf = malloc(8); + putU64(tpbuf, 0); + lseek(fmodule, offset_cryptid, SEEK_SET); + write(fmodule, tpbuf, 4); + lseek(fmodule, crypt_off, SEEK_SET); + write(fmodule, modbase.add(crypt_off), crypt_size); + } + + close(fmodule); + close(foldmodule); + return newmodpath +} + +function handleMessage(message) { + //start dump + modules = getAllAppModules(); + for (var i = 0; i < modules.length; i++) { + console.log("start dump " + modules[i].path); + result = dumpModule(modules[i].path); + send({ dump: result, path: modules[i].path}); + } + send({app: ObjC.classes.NSBundle.mainBundle().bundlePath().toString()}); + send({done: "ok"}); + recv(handleMessage); +} + +recv(handleMessage); \ No newline at end of file diff --git a/dump.py b/dump.py new file mode 100755 index 0000000..2bedc92 --- /dev/null +++ b/dump.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#Author : AloneMonkey +#blog: www.alonemonkey.com + +import sys +import codecs +import frida +import threading +import os +import shutil + +DUMP_JS = './dump.js' +APP_JS = './app.js' +OUTPUT = "Payload" +file_dict = {} + +opened = threading.Event() +finished = threading.Event() + +global session + +def get_usb_iphone(): + dManager = frida.get_device_manager(); + changed = threading.Event() + def on_changed(): + changed.set() + dManager.on('changed',on_changed) + + device = None + while device is None: + devices = [dev for dev in dManager.enumerate_devices() if dev.type == 'tether'] + if len(devices) == 0: + print 'Waiting for usb device...' + changed.wait() + else: + device = devices[0] + + dManager.off('changed',on_changed) + + return device + +def gen_ipa(target): + app_name = file_dict["app"] + for key, value in file_dict.items(): + if key != "app": + shutil.move(target+"/"+key, target + "/" + app_name + "/" + value); + (shotname,extension) = os.path.splitext(app_name) + os.system("zip -qr %s.ipa ./Payload" % shotname); + os.system("rm -rf ./Payload"); + +def on_message(message,data): + if message.has_key('payload'): + payload = message['payload'] + if payload.has_key("opened"): + opened.set(); + if payload.has_key("dump"): + orign_path = payload["path"] + dumppath = payload["dump"] + os.system(u''.join(("scp -P 2222 root@localhost:", dumppath, u" ./" + OUTPUT + u"/")).encode('utf-8').strip()) + os.system(u''.join(("chmod 655 ", u'./' + OUTPUT + u'/', os.path.basename(dumppath))).encode('utf-8').strip()) + index = orign_path.find(".app/") + file_dict[os.path.basename(dumppath)] = orign_path[index+5:] + if payload.has_key("app"): + apppath = payload["app"] + os.system(u''.join(("scp -r -P 2222 root@localhost:", apppath, u" ./" + OUTPUT + u"/")).encode('utf-8').strip()) + os.system(u''.join(("chmod 755 ", u'./' + OUTPUT + u'/', os.path.basename(apppath))).encode('utf-8').strip()) + file_dict["app"] = os.path.basename(apppath) + if payload.has_key("done"): + gen_ipa(os.getcwd()+"/"+OUTPUT) + finished.set(); + +def loadJsFile(session, filename): + source = '' + with codecs.open(filename,'r','utf-8') as f: + source = source + f.read(); + script = session.create_script(source); + script.on("message",on_message) + script.load() + return script + +def ClearAndQuit(session): + if session: + session.detach() + sys.exit(0) + +def createDir(path): + path = path.strip() + path = path.rstrip("\\") + if not os.path.exists(path): + os.makedirs(path) + else: + print path + u" is existed!"; + +def main(target): + global session + session = None + device = get_usb_iphone(); + #open app + name = u'SpringBoard'; + print "open target app......" + session = device.attach(name); + script = loadJsFile(session, APP_JS); + name = target.decode('utf8'); + script.post(name); + opened.wait(); + session.detach(); + createDir(os.getcwd()+"/"+OUTPUT) + print "start dump target app......" + session = device.attach(name); + script = loadJsFile(session, DUMP_JS); + script.post("dump"); + finished.wait(); + ClearAndQuit(session); + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.exit(0) + else: + try: + main(sys.argv[1]) + except KeyboardInterrupt: + print 2 + if session: + session.detach() + sys.exit() + except: + pass \ No newline at end of file