Compare commits

...

21 Commits
3.x ... master

Author SHA1 Message Date
Levent Duivel 9a3ce60629
Windows-compatible version.
Requires 7z to be present in PATH
2021-08-06 01:43:33 +05:00
Alone_Monkey 56e99b2138
Merge pull request #122 from alex1704/master
fix name 'KeyFileName' is not defined error
2020-06-01 12:22:21 +08:00
alex1704 9e75f6bca3 name 'KeyFileName' is not defined error 2020-06-01 01:04:26 +03:00
Alone_Monkey 56737ae57c
Merge pull request #121 from alex1704/master
Allow to specify SSH private key file path in options
2020-05-31 10:06:13 +08:00
alex1704 8b5b5332fd Allow to specify SSH private key file path in options 2020-05-31 01:41:51 +03:00
Alone_Monkey 850a38efd2
Merge pull request #98 from xiofee/master
Fix: py3 can't concat str to bytes
2020-03-08 09:16:32 +08:00
xiofee c5eff3b063 fix py3 str 2020-02-08 02:05:39 +08:00
Alone_Monkey 64b58d1c3e
Merge pull request #96 from fishso/master
fix python3 show error :'str' object has no attribute 'decode'
2020-01-15 19:04:45 +08:00
yujunfeng 9850158401 fix python3 show error :'str' object has no attribute 'decode' 2020-01-15 15:32:40 +08:00
Alone_Monkey 2363a00550
Merge pull request #91 from Hamz-a/master
added optional arguments for parsing SSH host, port, username, password
2019-12-16 15:33:10 +08:00
Hamza 2405c99d17 added optional arguments for parsing SSH host, port, username, password 2019-12-15 23:04:22 +01:00
Alone_Monkey 8a47ae4ff8
Merge pull request #88 from everettjf/master
support both python 2.x and 3.x
2019-11-16 11:10:35 +08:00
everettjf 35a749f270 support both python 2.x and 3.x 2019-11-16 11:00:56 +08:00
AloneMonkey f88043a222 [bugfix] dlopen parameter problem. 2019-07-24 10:59:06 +08:00
AloneMonkey 160a6e7419 [bugfix] fix for frida 12.5 2019-05-09 20:11:57 +08:00
AloneMonkey a39c11e38c [feature] auto load all frameworks and dylibs. 2019-05-02 23:26:09 +08:00
Alone_Monkey b2581409b1
change frida to frida-tools 2018-11-10 17:17:51 +08:00
AloneMonkey a9b9c9fbf5 fix bug 2018-07-14 19:26:29 +08:00
Alone_Monkey 187446d3c1
Update README.md 2018-07-13 16:51:05 +08:00
Alone_Monkey 71499f4d91
Compatible with previous versions 2018-07-13 16:41:48 +08:00
Alone_Monkey e53e81ecc3
change type tether to usb 2018-07-13 09:27:12 +08:00
4 changed files with 160 additions and 59 deletions

View File

@ -1,10 +1,11 @@
# frida-ios-dump # frida-ios-dump
Pull a decrypted IPA from a jailbroken device Pull a decrypted IPA from a jailbroken device
## Usage ## Usage
1. Install [frida](http://www.frida.re/) on device 1. Install [frida](http://www.frida.re/) on device
2. `sudo pip install -r requirements.txt --upgrade` (Python 2.7) 2. `sudo pip install -r requirements.txt --upgrade`
3. Run usbmuxd/iproxy SSH forwarding over USB (Default 2222 -> 22). e.g. `iproxy 2222 22` 3. Run usbmuxd/iproxy SSH forwarding over USB (Default 2222 -> 22). e.g. `iproxy 2222 22`
4. Run ./dump.py `Display name` or `Bundle identifier` 4. Run ./dump.py `Display name` or `Bundle identifier`
@ -39,6 +40,11 @@ Congratulations!!! You've got a decrypted IPA file.
Drag to [MonkeyDev](https://github.com/AloneMonkey/MonkeyDev), Happy hacking! Drag to [MonkeyDev](https://github.com/AloneMonkey/MonkeyDev), Happy hacking!
## Support
Python 2.x and 3.x
### issues ### issues
If the following error occurs: If the following error occurs:

95
dump.js
View File

@ -129,14 +129,15 @@ function getExportFunction(type, name, ret, args) {
} }
} }
NSSearchPathForDirectoriesInDomains = getExportFunction("f", "NSSearchPathForDirectoriesInDomains", "pointer", ["int", "int", "int"]); var NSSearchPathForDirectoriesInDomains = getExportFunction("f", "NSSearchPathForDirectoriesInDomains", "pointer", ["int", "int", "int"]);
wrapper_open = getExportFunction("f", "open", "int", ["pointer", "int", "int"]); var wrapper_open = getExportFunction("f", "open", "int", ["pointer", "int", "int"]);
read = getExportFunction("f", "read", "int", ["int", "pointer", "int"]); var read = getExportFunction("f", "read", "int", ["int", "pointer", "int"]);
write = getExportFunction("f", "write", "int", ["int", "pointer", "int"]); var write = getExportFunction("f", "write", "int", ["int", "pointer", "int"]);
lseek = getExportFunction("f", "lseek", "int64", ["int", "int64", "int"]); var lseek = getExportFunction("f", "lseek", "int64", ["int", "int64", "int"]);
close = getExportFunction("f", "close", "int", ["int"]); var close = getExportFunction("f", "close", "int", ["int"]);
remove = getExportFunction("f", "remove", "int", ["pointer"]); var remove = getExportFunction("f", "remove", "int", ["pointer"]);
access = getExportFunction("f", "access", "int", ["pointer", "int"]); var access = getExportFunction("f", "access", "int", ["pointer", "int"]);
var dlopen = getExportFunction("f", "dlopen", "pointer", ["pointer", "int"]);
function getDocumentDir() { function getDocumentDir() {
var NSDocumentDirectory = 9; var NSDocumentDirectory = 9;
@ -154,13 +155,11 @@ function open(pathname, flags, mode) {
var modules = null; var modules = null;
function getAllAppModules() { function getAllAppModules() {
if (modules == null) { modules = new Array();
modules = new Array(); var tmpmods = Process.enumerateModulesSync();
var tmpmods = Process.enumerateModulesSync(); for (var i = 0; i < tmpmods.length; i++) {
for (var i = 0; i < tmpmods.length; i++) { if (tmpmods[i].path.indexOf(".app") != -1) {
if (tmpmods[i].path.indexOf(".app") != -1) { modules.push(tmpmods[i]);
modules.push(tmpmods[i]);
}
} }
} }
return modules; return modules;
@ -314,15 +313,75 @@ function dumpModule(name) {
return newmodpath return newmodpath
} }
function loadAllDynamicLibrary(app_path) {
var defaultManager = ObjC.classes.NSFileManager.defaultManager();
var errorPtr = Memory.alloc(Process.pointerSize);
Memory.writePointer(errorPtr, NULL);
var filenames = defaultManager.contentsOfDirectoryAtPath_error_(app_path, errorPtr);
for (var i = 0, l = filenames.count(); i < l; i++) {
var file_name = filenames.objectAtIndex_(i);
var file_path = app_path.stringByAppendingPathComponent_(file_name);
if (file_name.hasSuffix_(".framework")) {
var bundle = ObjC.classes.NSBundle.bundleWithPath_(file_path);
if (bundle.isLoaded()) {
console.log("[frida-ios-dump]: " + file_name + " has been loaded. ");
} else {
if (bundle.load()) {
console.log("[frida-ios-dump]: Load " + file_name + " success. ");
} else {
console.log("[frida-ios-dump]: Load " + file_name + " failed. ");
}
}
} else if (file_name.hasSuffix_(".bundle") ||
file_name.hasSuffix_(".momd") ||
file_name.hasSuffix_(".strings") ||
file_name.hasSuffix_(".appex") ||
file_name.hasSuffix_(".app") ||
file_name.hasSuffix_(".lproj") ||
file_name.hasSuffix_(".storyboardc")) {
continue;
} else {
var isDirPtr = Memory.alloc(Process.pointerSize);
Memory.writePointer(isDirPtr,NULL);
defaultManager.fileExistsAtPath_isDirectory_(file_path, isDirPtr);
if (Memory.readPointer(isDirPtr) == 1) {
loadAllDynamicLibrary(file_path);
} else {
if (file_name.hasSuffix_(".dylib")) {
var is_loaded = 0;
for (var j = 0; j < modules.length; j++) {
if (modules[j].path.indexOf(file_name) != -1) {
is_loaded = 1;
console.log("[frida-ios-dump]: " + file_name + " has been dlopen.");
break;
}
}
if (!is_loaded) {
if (dlopen(allocStr(file_path.UTF8String()), 9)) {
console.log("[frida-ios-dump]: dlopen " + file_name + " success. ");
} else {
console.log("[frida-ios-dump]: dlopen " + file_name + " failed. ");
}
}
}
}
}
}
}
function handleMessage(message) { function handleMessage(message) {
//start dump modules = getAllAppModules();
var app_path = ObjC.classes.NSBundle.mainBundle().bundlePath();
loadAllDynamicLibrary(app_path);
// start dump
modules = getAllAppModules(); modules = getAllAppModules();
for (var i = 0; i < modules.length; i++) { for (var i = 0; i < modules.length; i++) {
console.log("start dump " + modules[i].path); console.log("start dump " + modules[i].path);
result = dumpModule(modules[i].path); var result = dumpModule(modules[i].path);
send({ dump: result, path: modules[i].path}); send({ dump: result, path: modules[i].path});
} }
send({app: ObjC.classes.NSBundle.mainBundle().bundlePath().toString()}); send({app: app_path.toString()});
send({done: "ok"}); send({done: "ok"});
recv(handleMessage); recv(handleMessage);
} }

114
dump.py
View File

@ -4,26 +4,28 @@
# Author : AloneMonkey # Author : AloneMonkey
# blog: www.alonemonkey.com # blog: www.alonemonkey.com
from __future__ import print_function
from __future__ import unicode_literals
import sys import sys
import codecs import codecs
import frida import frida
import threading import threading
import os import os
import shutil
import time import time
import argparse import argparse
import tempfile import tempfile
import subprocess import subprocess
import re import re
import paramiko import paramiko
from paramiko import SSHClient from paramiko import SSHClient
from scp import SCPClient from scp import SCPClient
from tqdm import tqdm from tqdm import tqdm
import traceback import traceback
reload(sys) IS_PY2 = sys.version_info[0] < 3
sys.setdefaultencoding('utf8') if IS_PY2:
reload(sys)
sys.setdefaultencoding('utf8')
script_dir = os.path.dirname(os.path.realpath(__file__)) script_dir = os.path.dirname(os.path.realpath(__file__))
@ -33,6 +35,7 @@ User = 'root'
Password = 'alpine' Password = 'alpine'
Host = 'localhost' Host = 'localhost'
Port = 2222 Port = 2222
KeyFileName = None
TEMP_DIR = tempfile.gettempdir() TEMP_DIR = tempfile.gettempdir()
PAYLOAD_DIR = 'Payload' PAYLOAD_DIR = 'Payload'
@ -43,6 +46,9 @@ finished = threading.Event()
def get_usb_iphone(): def get_usb_iphone():
Type = 'usb'
if int(frida.__version__.split('.')[0]) < 12:
Type = 'tether'
device_manager = frida.get_device_manager() device_manager = frida.get_device_manager()
changed = threading.Event() changed = threading.Event()
@ -53,9 +59,9 @@ def get_usb_iphone():
device = None device = None
while device is None: while device is None:
devices = [dev for dev in device_manager.enumerate_devices() if dev.type == 'tether'] devices = [dev for dev in device_manager.enumerate_devices() if dev.type == Type]
if len(devices) == 0: if len(devices) == 0:
print 'Waiting for USB device...' print('Waiting for USB device...')
changed.wait() changed.wait()
else: else:
device = devices[0] device = devices[0]
@ -68,23 +74,25 @@ def get_usb_iphone():
def generate_ipa(path, display_name): def generate_ipa(path, display_name):
ipa_filename = display_name + '.ipa' ipa_filename = display_name + '.ipa'
print 'Generating "{}"'.format(ipa_filename) print('Generating "{}"'.format(ipa_filename))
try: try:
app_name = file_dict['app'] app_name = file_dict['app']
for key, value in file_dict.items(): for key, value in file_dict.items():
from_dir = os.path.join(path, key) from_dir = os.path.join(path, key)
to_dir = os.path.join(path, app_name, value) to_dir = os.path.join(path, app_name, value)
if key != 'app': if key != 'app':
shutil.move(from_dir, to_dir) try:
os.rename(from_dir, to_dir)
except FileExistsError:
os.remove(to_dir)
os.rename(from_dir, to_dir)
target_dir = './' + PAYLOAD_DIR target_dir = './' + PAYLOAD_DIR
zip_args = ('zip', '-qr', os.path.join(os.getcwd(), ipa_filename), target_dir) zip_args = ("7z", "a", "-r", f"{os.getcwd()}/{ipa_filename}.zip", "-w", f"{target_dir}", "-mem=AES256")
subprocess.check_call(zip_args, cwd=TEMP_DIR) subprocess.check_call(zip_args, cwd=TEMP_DIR)
shutil.rmtree(PAYLOAD_PATH) os.rename(f"{os.getcwd()}/{ipa_filename}.zip", f"{os.getcwd()}/{ipa_filename}")
print os.system('rmdir /S /Q "{}"'.format(PAYLOAD_PATH))
except Exception as e: except Exception as e:
print e print(f"{type(e)}: {e}")
finished.set() finished.set()
def on_message(message, data): def on_message(message, data):
@ -92,7 +100,11 @@ def on_message(message, data):
last_sent = [0] last_sent = [0]
def progress(filename, size, sent): def progress(filename, size, sent):
t.desc = os.path.basename(filename) baseName = os.path.basename(filename)
if IS_PY2 or isinstance(baseName, bytes):
t.desc = baseName.decode("utf-8")
else:
t.desc = baseName
t.total = size t.total = size
t.update(sent - last_sent[0]) t.update(sent - last_sent[0])
last_sent[0] = 0 if size == sent else sent last_sent[0] = 0 if size == sent else sent
@ -104,17 +116,19 @@ def on_message(message, data):
dump_path = payload['dump'] dump_path = payload['dump']
scp_from = dump_path scp_from = dump_path
scp_to = PAYLOAD_PATH + u'/' scp_to = PAYLOAD_PATH + '/'
with SCPClient(ssh.get_transport(), progress = progress, socket_timeout = 60) as scp: with SCPClient(ssh.get_transport(), progress = progress, socket_timeout = 60) as scp:
scp.get(scp_from, scp_to) scp.get(scp_from, scp_to)
chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(dump_path)) chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(dump_path))
chmod_args = ('chmod', '655', chmod_dir) chmod_args = ('chmod', '655', chmod_dir)
"""
try: try:
subprocess.check_call(chmod_args) subprocess.check_call(chmod_args)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
print err print(err)
"""
index = origin_path.find('.app/') index = origin_path.find('.app/')
file_dict[os.path.basename(dump_path)] = origin_path[index + 5:] file_dict[os.path.basename(dump_path)] = origin_path[index + 5:]
@ -123,17 +137,18 @@ def on_message(message, data):
app_path = payload['app'] app_path = payload['app']
scp_from = app_path scp_from = app_path
scp_to = PAYLOAD_PATH + u'/' scp_to = PAYLOAD_PATH + '/'
with SCPClient(ssh.get_transport(), progress = progress, socket_timeout = 60) as scp: with SCPClient(ssh.get_transport(), progress = progress, socket_timeout = 60) as scp:
scp.get(scp_from, scp_to, recursive=True) scp.get(scp_from, scp_to, recursive=True)
chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(app_path)) chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(app_path))
chmod_args = ('chmod', '755', chmod_dir) chmod_args = ('chmod', '755', chmod_dir)
"""
try: try:
subprocess.check_call(chmod_args) subprocess.check_call(chmod_args)
except subprocess.CalledProcessError as err: except subprocess.CalledProcessError as err:
print err print(err)
"""
file_dict['app'] = os.path.basename(app_path) file_dict['app'] = os.path.basename(app_path)
if 'done' in payload: if 'done' in payload:
@ -188,8 +203,7 @@ def get_applications(device):
try: try:
applications = device.enumerate_applications() applications = device.enumerate_applications()
except Exception as e: except Exception as e:
print 'Failed to enumerate applications: %s' % e sys.exit('Failed to enumerate applications: %s' % e)
return
return applications return applications
@ -208,15 +222,15 @@ def list_applications(device):
header_format = '%' + str(pid_column_width) + 's ' + '%-' + str(name_column_width) + 's ' + '%-' + str( header_format = '%' + str(pid_column_width) + 's ' + '%-' + str(name_column_width) + 's ' + '%-' + str(
identifier_column_width) + 's' identifier_column_width) + 's'
print header_format % ('PID', 'Name', 'Identifier') print(header_format % ('PID', 'Name', 'Identifier'))
print '%s %s %s' % (pid_column_width * '-', name_column_width * '-', identifier_column_width * '-') print('%s %s %s' % (pid_column_width * '-', name_column_width * '-', identifier_column_width * '-'))
line_format = '%' + str(pid_column_width) + 's ' + '%-' + str(name_column_width) + 's ' + '%-' + str( line_format = '%' + str(pid_column_width) + 's ' + '%-' + str(name_column_width) + 's ' + '%-' + str(
identifier_column_width) + 's' identifier_column_width) + 's'
for application in sorted(applications, key=cmp_to_key(compare_applications)): for application in sorted(applications, key=cmp_to_key(compare_applications)):
if application.pid == 0: if application.pid == 0:
print line_format % ('-', application.name, application.identifier) print(line_format % ('-', application.name, application.identifier))
else: else:
print line_format % (application.pid, application.name, application.identifier) print(line_format % (application.pid, application.name, application.identifier))
def load_js_file(session, filename): def load_js_file(session, filename):
@ -234,15 +248,15 @@ def create_dir(path):
path = path.strip() path = path.strip()
path = path.rstrip('\\') path = path.rstrip('\\')
if os.path.exists(path): if os.path.exists(path):
shutil.rmtree(path) os.system('rmdir /S /Q "{}"'.format(path))
try: try:
os.makedirs(path) os.makedirs(path)
except os.error as err: except os.error as err:
print err print(err)
def open_target_app(device, name_or_bundleid): def open_target_app(device, name_or_bundleid):
print 'Start the target app {}'.format(name_or_bundleid) print('Start the target app {}'.format(name_or_bundleid))
pid = '' pid = ''
session = None session = None
@ -262,13 +276,13 @@ def open_target_app(device, name_or_bundleid):
else: else:
session = device.attach(pid) session = device.attach(pid)
except Exception as e: except Exception as e:
print e print(e)
return session, display_name, bundle_identifier return session, display_name, bundle_identifier
def start_dump(session, ipa_name): def start_dump(session, ipa_name):
print 'Dumping {} to {}'.format(display_name, TEMP_DIR) print('Dumping {} to {}'.format(display_name, TEMP_DIR))
script = load_js_file(session, DUMP_JS) script = load_js_file(session, DUMP_JS)
script.post('dump') script.post('dump')
@ -284,25 +298,45 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description='frida-ios-dump (by AloneMonkey v2.0)') parser = argparse.ArgumentParser(description='frida-ios-dump (by AloneMonkey v2.0)')
parser.add_argument('-l', '--list', dest='list_applications', action='store_true', help='List the installed apps') parser.add_argument('-l', '--list', dest='list_applications', action='store_true', help='List the installed apps')
parser.add_argument('-o', '--output', dest='output_ipa', help='Specify name of the decrypted IPA') parser.add_argument('-o', '--output', dest='output_ipa', help='Specify name of the decrypted IPA')
parser.add_argument('-H', '--host', dest='ssh_host', help='Specify SSH hostname')
parser.add_argument('-p', '--port', dest='ssh_port', help='Specify SSH port')
parser.add_argument('-u', '--user', dest='ssh_user', help='Specify SSH username')
parser.add_argument('-P', '--password', dest='ssh_password', help='Specify SSH password')
parser.add_argument('-K', '--key_filename', dest='ssh_key_filename', help='Specify SSH private key file path')
parser.add_argument('target', nargs='?', help='Bundle identifier or display name of the target app') parser.add_argument('target', nargs='?', help='Bundle identifier or display name of the target app')
args = parser.parse_args() args = parser.parse_args()
exit_code = 0 exit_code = 0
ssh = None ssh = None
if not len(sys.argv[1:]):
parser.print_help()
sys.exit(exit_code)
device = get_usb_iphone() device = get_usb_iphone()
if len(sys.argv[1:]) == 0: if args.list_applications:
parser.print_help()
elif args.list_applications:
list_applications(device) list_applications(device)
else: else:
name_or_bundleid = args.target name_or_bundleid = args.target
output_ipa = args.output_ipa output_ipa = args.output_ipa
# update ssh args
if args.ssh_host:
Host = args.ssh_host
if args.ssh_port:
Port = int(args.ssh_port)
if args.ssh_user:
User = args.ssh_user
if args.ssh_password:
Password = args.ssh_password
if args.ssh_key_filename:
KeyFileName = args.ssh_key_filename
try: try:
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(Host, port=Port, username=User, password=Password) ssh.connect(Host, port=Port, username=User, password=Password, key_filename=KeyFileName)
create_dir(PAYLOAD_PATH) create_dir(PAYLOAD_PATH)
(session, display_name, bundle_identifier) = open_target_app(device, name_or_bundleid) (session, display_name, bundle_identifier) = open_target_app(device, name_or_bundleid)
@ -312,10 +346,12 @@ if __name__ == '__main__':
if session: if session:
start_dump(session, output_ipa) start_dump(session, output_ipa)
except paramiko.ssh_exception.NoValidConnectionsError as e: except paramiko.ssh_exception.NoValidConnectionsError as e:
print e print(e)
print('Try specifying -H/--hostname and/or -p/--port')
exit_code = 1 exit_code = 1
except paramiko.AuthenticationException as e: except paramiko.AuthenticationException as e:
print e print(e)
print('Try specifying -u/--username and/or -P/--password')
exit_code = 1 exit_code = 1
except Exception as e: except Exception as e:
print('*** Caught exception: %s: %s' % (e.__class__, e)) print('*** Caught exception: %s: %s' % (e.__class__, e))
@ -326,6 +362,6 @@ if __name__ == '__main__':
ssh.close() ssh.close()
if os.path.exists(PAYLOAD_PATH): if os.path.exists(PAYLOAD_PATH):
shutil.rmtree(PAYLOAD_PATH) os.system('rmdir /S /Q "{}"'.format(PAYLOAD_PATH))
sys.exit(exit_code) sys.exit(exit_code)

View File

@ -4,7 +4,7 @@ cffi
colorama colorama
cryptography cryptography
enum34 enum34
frida frida-tools
idna idna
ipaddress ipaddress
paramiko paramiko