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
Pull a decrypted IPA from a jailbroken device
## Usage
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`
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!
## Support
Python 2.x and 3.x
### issues
If the following error occurs:

83
dump.js
View File

@ -129,14 +129,15 @@ function getExportFunction(type, name, ret, args) {
}
}
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"]);
access = getExportFunction("f", "access", "int", ["pointer", "int"]);
var NSSearchPathForDirectoriesInDomains = getExportFunction("f", "NSSearchPathForDirectoriesInDomains", "pointer", ["int", "int", "int"]);
var wrapper_open = getExportFunction("f", "open", "int", ["pointer", "int", "int"]);
var read = getExportFunction("f", "read", "int", ["int", "pointer", "int"]);
var write = getExportFunction("f", "write", "int", ["int", "pointer", "int"]);
var lseek = getExportFunction("f", "lseek", "int64", ["int", "int64", "int"]);
var close = getExportFunction("f", "close", "int", ["int"]);
var remove = getExportFunction("f", "remove", "int", ["pointer"]);
var access = getExportFunction("f", "access", "int", ["pointer", "int"]);
var dlopen = getExportFunction("f", "dlopen", "pointer", ["pointer", "int"]);
function getDocumentDir() {
var NSDocumentDirectory = 9;
@ -154,7 +155,6 @@ function 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++) {
@ -162,7 +162,6 @@ function getAllAppModules() {
modules.push(tmpmods[i]);
}
}
}
return modules;
}
@ -314,15 +313,75 @@ function dumpModule(name) {
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) {
modules = getAllAppModules();
var app_path = ObjC.classes.NSBundle.mainBundle().bundlePath();
loadAllDynamicLibrary(app_path);
// start dump
modules = getAllAppModules();
for (var i = 0; i < modules.length; i++) {
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({app: ObjC.classes.NSBundle.mainBundle().bundlePath().toString()});
send({app: app_path.toString()});
send({done: "ok"});
recv(handleMessage);
}

108
dump.py
View File

@ -4,24 +4,26 @@
# Author : AloneMonkey
# blog: www.alonemonkey.com
from __future__ import print_function
from __future__ import unicode_literals
import sys
import codecs
import frida
import threading
import os
import shutil
import time
import argparse
import tempfile
import subprocess
import re
import paramiko
from paramiko import SSHClient
from scp import SCPClient
from tqdm import tqdm
import traceback
IS_PY2 = sys.version_info[0] < 3
if IS_PY2:
reload(sys)
sys.setdefaultencoding('utf8')
@ -33,6 +35,7 @@ User = 'root'
Password = 'alpine'
Host = 'localhost'
Port = 2222
KeyFileName = None
TEMP_DIR = tempfile.gettempdir()
PAYLOAD_DIR = 'Payload'
@ -43,6 +46,9 @@ finished = threading.Event()
def get_usb_iphone():
Type = 'usb'
if int(frida.__version__.split('.')[0]) < 12:
Type = 'tether'
device_manager = frida.get_device_manager()
changed = threading.Event()
@ -53,9 +59,9 @@ def get_usb_iphone():
device = 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:
print 'Waiting for USB device...'
print('Waiting for USB device...')
changed.wait()
else:
device = devices[0]
@ -68,23 +74,25 @@ def get_usb_iphone():
def generate_ipa(path, display_name):
ipa_filename = display_name + '.ipa'
print 'Generating "{}"'.format(ipa_filename)
print('Generating "{}"'.format(ipa_filename))
try:
app_name = file_dict['app']
for key, value in file_dict.items():
from_dir = os.path.join(path, key)
to_dir = os.path.join(path, app_name, value)
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
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)
shutil.rmtree(PAYLOAD_PATH)
print
os.rename(f"{os.getcwd()}/{ipa_filename}.zip", f"{os.getcwd()}/{ipa_filename}")
os.system('rmdir /S /Q "{}"'.format(PAYLOAD_PATH))
except Exception as e:
print e
print(f"{type(e)}: {e}")
finished.set()
def on_message(message, data):
@ -92,7 +100,11 @@ def on_message(message, data):
last_sent = [0]
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.update(sent - last_sent[0])
last_sent[0] = 0 if size == sent else sent
@ -104,17 +116,19 @@ def on_message(message, data):
dump_path = payload['dump']
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:
scp.get(scp_from, scp_to)
chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(dump_path))
chmod_args = ('chmod', '655', chmod_dir)
"""
try:
subprocess.check_call(chmod_args)
except subprocess.CalledProcessError as err:
print err
print(err)
"""
index = origin_path.find('.app/')
file_dict[os.path.basename(dump_path)] = origin_path[index + 5:]
@ -123,17 +137,18 @@ def on_message(message, data):
app_path = payload['app']
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:
scp.get(scp_from, scp_to, recursive=True)
chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(app_path))
chmod_args = ('chmod', '755', chmod_dir)
"""
try:
subprocess.check_call(chmod_args)
except subprocess.CalledProcessError as err:
print err
print(err)
"""
file_dict['app'] = os.path.basename(app_path)
if 'done' in payload:
@ -188,8 +203,7 @@ def get_applications(device):
try:
applications = device.enumerate_applications()
except Exception as e:
print 'Failed to enumerate applications: %s' % e
return
sys.exit('Failed to enumerate applications: %s' % e)
return applications
@ -208,15 +222,15 @@ def list_applications(device):
header_format = '%' + str(pid_column_width) + 's ' + '%-' + str(name_column_width) + 's ' + '%-' + str(
identifier_column_width) + 's'
print header_format % ('PID', 'Name', 'Identifier')
print '%s %s %s' % (pid_column_width * '-', name_column_width * '-', identifier_column_width * '-')
print(header_format % ('PID', 'Name', 'Identifier'))
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(
identifier_column_width) + 's'
for application in sorted(applications, key=cmp_to_key(compare_applications)):
if application.pid == 0:
print line_format % ('-', application.name, application.identifier)
print(line_format % ('-', application.name, application.identifier))
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):
@ -234,15 +248,15 @@ def create_dir(path):
path = path.strip()
path = path.rstrip('\\')
if os.path.exists(path):
shutil.rmtree(path)
os.system('rmdir /S /Q "{}"'.format(path))
try:
os.makedirs(path)
except os.error as err:
print err
print(err)
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 = ''
session = None
@ -262,13 +276,13 @@ def open_target_app(device, name_or_bundleid):
else:
session = device.attach(pid)
except Exception as e:
print e
print(e)
return session, display_name, bundle_identifier
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.post('dump')
@ -284,25 +298,45 @@ if __name__ == '__main__':
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('-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')
args = parser.parse_args()
exit_code = 0
ssh = None
if not len(sys.argv[1:]):
parser.print_help()
sys.exit(exit_code)
device = get_usb_iphone()
if len(sys.argv[1:]) == 0:
parser.print_help()
elif args.list_applications:
if args.list_applications:
list_applications(device)
else:
name_or_bundleid = args.target
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:
ssh = paramiko.SSHClient()
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)
(session, display_name, bundle_identifier) = open_target_app(device, name_or_bundleid)
@ -312,10 +346,12 @@ if __name__ == '__main__':
if session:
start_dump(session, output_ipa)
except paramiko.ssh_exception.NoValidConnectionsError as e:
print e
print(e)
print('Try specifying -H/--hostname and/or -p/--port')
exit_code = 1
except paramiko.AuthenticationException as e:
print e
print(e)
print('Try specifying -u/--username and/or -P/--password')
exit_code = 1
except Exception as e:
print('*** Caught exception: %s: %s' % (e.__class__, e))
@ -326,6 +362,6 @@ if __name__ == '__main__':
ssh.close()
if os.path.exists(PAYLOAD_PATH):
shutil.rmtree(PAYLOAD_PATH)
os.system('rmdir /S /Q "{}"'.format(PAYLOAD_PATH))
sys.exit(exit_code)

View File

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