Changed argument parsing to argparse, adding an output naming option.

Replaced the use of command-line scp to a paramiko to avoid multiple
logins.
This commit is contained in:
Cameron Lowell Palmer 2018-02-05 11:41:19 +01:00 committed by Cameron Lowell Palmer
parent 90c17279d2
commit f606152240
3 changed files with 149 additions and 76 deletions

View File

@ -7,6 +7,8 @@ Pull a decrypted IPA from a jailbroken device
2. Run usbmuxd/iproxy SSH forwarding over USB (Default 2222 -> 22)
3. Run ./dump.py `Display name` or `Bundle identifier`
For SSH/SCP make sure you have your public key added to the target device's ~/.ssh/authorized_keys file.
```
./dump.py Aftenposten
Start the target app Aftenposten

165
dump.py
View File

@ -11,9 +11,15 @@ import threading
import os
import shutil
import time
import getopt
import argparse
import tempfile
import subprocess
import re
import paramiko
from paramiko import SSHClient
from scp import SCPClient
import traceback
reload(sys)
sys.setdefaultencoding('utf8')
@ -22,8 +28,10 @@ script_dir = os.path.dirname(os.path.realpath(__file__))
DUMP_JS = os.path.join(script_dir, 'dump.js')
SSH_USER_HOST='root@localhost:'
SSH_PORT=2222
User = 'root'
Password = 'alpine'
Host = 'localhost'
Port = 2222
TEMP_DIR = tempfile.gettempdir()
PAYLOAD_DIR = 'Payload'
@ -33,41 +41,33 @@ file_dict = {}
finished = threading.Event()
# show usage
def usage():
print '-------------------------frida-ios-dump(by AloneMonkey v2.0)----------------------------'
print '\t%-20s\t%s' % ('-h,--help', 'Show help menu.')
print '\t%-20s\t%s' % ('name', 'Decrypt the application with the specified display name or bundle identifier. ps: ./dump.py 微信')
print '\t%-20s\t%s' % ('-l', 'List the installed apps.')
def get_usb_iphone():
dManager = frida.get_device_manager()
device_manager = frida.get_device_manager()
changed = threading.Event()
def on_changed():
changed.set()
dManager.on('changed', on_changed)
device_manager.on('changed', on_changed)
device = None
while device is None:
devices = [dev for dev in dManager.enumerate_devices() if dev.type == 'tether']
devices = [dev for dev in device_manager.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)
device_manager.off('changed', on_changed)
return device
def generate_ipa(path, display_name, bundle_identifier):
ipa_filename = display_name.replace(' ', '\\ ') + '.ipa'
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']
@ -89,42 +89,49 @@ def generate_ipa(path, display_name, bundle_identifier):
def on_message(message, data):
global name
if message.has_key('payload'):
if 'payload' in message:
payload = message['payload']
if payload.has_key('opened'):
if 'opened' in payload:
name = payload['opened']
if payload.has_key('dump'):
if 'dump' in payload:
origin_path = payload['path']
dump_path = payload['dump']
scp_from = SSH_USER_HOST + dump_path.replace(' ', '\ ')
scp_from = dump_path.replace(' ', '\ ')
scp_to = PAYLOAD_PATH + u'/'
scp_args = ('scp', '-P {}'.format(SSH_PORT), scp_from, scp_to)
subprocess.check_call(scp_args)
with SCPClient(ssh.get_transport()) 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)
subprocess.check_call(chmod_args)
try:
subprocess.check_call(chmod_args)
except subprocess.CalledProcessError as err:
print err
index = origin_path.find('.app/')
file_dict[os.path.basename(dump_path)] = origin_path[index + 5:]
if payload.has_key('app'):
if 'app' in payload:
app_path = payload['app']
scp_from = SSH_USER_HOST + app_path.replace(' ', '\ ')
scp_from = app_path.replace(' ', '\ ')
scp_to = PAYLOAD_PATH + u'/'
scp_args = ('scp', '-r', '-P {}'.format(SSH_PORT), scp_from, scp_to)
subprocess.check_call(scp_args)
with SCPClient(ssh.get_transport()) 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)
subprocess.check_call(chmod_args)
try:
subprocess.check_call(chmod_args)
except subprocess.CalledProcessError as err:
print err
file_dict['app'] = os.path.basename(app_path)
if payload.has_key('done'):
if 'done' in payload:
finished.set()
@ -145,10 +152,10 @@ def compare_applications(a, b):
def cmp_to_key(mycmp):
'Convert a cmp= function into a key= function'
"""Convert a cmp= function into a key= function"""
class K:
def __init__(self, obj, *args):
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
@ -200,11 +207,11 @@ def list_applications(device):
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 app in sorted(applications, key=cmp_to_key(compare_applications)):
if app.pid == 0:
print line_format % ('-', app.name, app.identifier)
for application in sorted(applications, key=cmp_to_key(compare_applications)):
if application.pid == 0:
print line_format % ('-', application.name, application.identifier)
else:
print line_format % (app.pid, app.name, app.identifier)
print line_format % (application.pid, application.name, application.identifier)
def load_js_file(session, filename):
@ -224,12 +231,16 @@ def create_dir(path):
if os.path.exists(path):
print 'Removing {}'.format(path)
shutil.rmtree(path)
os.makedirs(path)
try:
os.makedirs(path)
except os.error as err:
print err
def open_target_app(device, name_or_bundleid):
print 'Start the target app {}'.format(name_or_bundleid)
pid = -1
display_name = ''
bundle_identifier = ''
for application in get_applications(device):
@ -240,15 +251,14 @@ def open_target_app(device, name_or_bundleid):
try:
pid = device.spawn([bundle_identifier])
device.resume(pid)
create_dir(PAYLOAD_PATH)
time.sleep(1)
except Exception as e:
print e
return (pid, display_name, bundle_identifier)
return pid, display_name, bundle_identifier
def start_dump(device, pid, display_name, bundle_identifier):
def start_dump(device, pid, ipa_name):
print 'Dumping {} to {}'.format(display_name, TEMP_DIR)
session = device.attach(pid)
@ -256,51 +266,54 @@ def start_dump(device, pid, display_name, bundle_identifier):
script.post('dump')
finished.wait()
generate_ipa(PAYLOAD_PATH, display_name, bundle_identifier)
generate_ipa(PAYLOAD_PATH, ipa_name)
if session:
session.detach()
def check_args():
if len(sys.argv) < 2:
usage()
return 1
try:
opts, args = getopt.getopt(sys.argv[1:], 'hl', ['help'])
except getopt.GetoptError:
usage()
return 2
for opt, value in opts:
if opt in ('-h', '--help'):
usage()
if opt in '-l':
device = get_usb_iphone()
list_applications(device)
if len(opts) == 0:
name_or_bundleid = ' '.join(sys.argv[1:])
device = get_usb_iphone()
print "Device {}".format(device)
(pid, display_name, bundle_identifier) = open_target_app(device, name_or_bundleid)
start_dump(device, pid, display_name, bundle_identifier)
return 0
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('target', nargs='?', help='Bundle identifier or display name of the target app')
args = parser.parse_args()
exit_code = 0
try:
exit_code = check_args()
except:
exit_code = 1
ssh = None
device = get_usb_iphone()
if args.list_applications:
list_applications(device)
else:
print "Device {}".format(device)
name_or_bundleid = args.target
output_ipa = args.output_ipa
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(Host, port=Port, username=User, password=Password)
create_dir(PAYLOAD_PATH)
(pid, display_name, bundle_identifier) = open_target_app(device, name_or_bundleid)
if output_ipa is None:
output_ipa = display_name
output_ipa = re.sub('\.ipa$', '', output_ipa)
start_dump(device, pid, output_ipa)
except paramiko.AuthenticationException as e:
print e
exit_code = 1
except Exception as e:
print('*** Caught exception: %s: %s' % (e.__class__, e))
traceback.print_exc()
exit_code = 1
if ssh:
ssh.close()
if os.path.exists(PAYLOAD_PATH):
print 'Deleting ' + PAYLOAD_PATH
shutil.rmtree(PAYLOAD_PATH)
sys.exit(exit_code)

58
process.sh Executable file
View File

@ -0,0 +1,58 @@
#!/bin/bash
ORIGINAL_IPA_FOLDER="${HOME}/Music/iTunes/iTunes Media/Mobile Applications"
DECRYPTED_IPA_FOLDER="${HOME}/Hacking/Decrypted Apps"
IOS_DEPLOY="/usr/local/bin/ios-deploy"
PLISTBUDDY="/usr/libexec/PlistBuddy"
FRIDA_DUMP_PY="./dump.py"
# CFBundleIdentifier in Info.plist to uninstall
ORIGINAL_IPAS=()
while IFS= read -r -d $'\0'; do
ORIGINAL_IPA=$( echo "${REPLY}" )
ORIGINAL_IPAS+=( "${ORIGINAL_IPA}" )
done < <(find "${ORIGINAL_IPA_FOLDER}" -type f -name '*.ipa' -print0)
echo "Decrypting ${#ORIGINAL_IPAS[@]} apps"
APP_COUNTER=0
for ORIGINAL_IPA in "${ORIGINAL_IPAS[@]}"; do
APP_COUNTER=$((APP_COUNTER + 1))
echo "#### Decryting app number: ${APP_COUNTER}"
APP_BUNDLE_ID=""
ESCAPED_IPA_PATH=$( echo "${ORIGINAL_IPA}" | sed 's/ /\\ /g' | sed 's/&/\\&/g' )
IPA_NAME=$( basename "${ESCAPED_IPA_PATH}" )
# Make a temp file for the iTunesMetadata.plist
ITUNESMETADATA_TMP_FILE=$( mktemp "/tmp/iTunesMetadata.plist.XXXXXX" ) || exit 1
# Unzip iTunesMetadata.plist to temp file
UNZIP_CMD="unzip -p ${ESCAPED_IPA_PATH} iTunesMetadata.plist > ${ITUNESMETADATA_TMP_FILE}"
echo "${UNZIP_CMD}"
eval "${UNZIP_CMD}" || exit 1
# Extract softwareVersionBundleId from plist file
APP_BUNDLE_ID=$( "${PLISTBUDDY}" -c 'Print softwareVersionBundleId' "${ITUNESMETADATA_TMP_FILE}" )
echo "App bundle identifier: ${APP_BUNDLE_ID}"
rm "${ITUNESMETADATA_TMP_FILE}"
# Install the app
IOS_DEPLOY_INSTALL="${IOS_DEPLOY} -W --bundle ${ESCAPED_IPA_PATH}"
echo "${IOS_DEPLOY_INSTALL}"
eval "${IOS_DEPLOY_INSTALL}"
if [ $? -eq 0 ]; then
sleep 1
echo "Dumping ${APP_BUNDLE_ID}"
FRIDA_DUMP_CMD="${FRIDA_DUMP_PY} --output ${IPA_NAME} ${APP_BUNDLE_ID}"
echo "${FRIDA_DUMP_CMD}"
eval "${FRIDA_DUMP_CMD}"
fi
# Uninstall the app
IOS_DEPLOY_UNINSTALL="${IOS_DEPLOY} -W --uninstall_only --bundle_id ${APP_BUNDLE_ID}"
echo "${IOS_DEPLOY_UNINSTALL}"
eval "${IOS_DEPLOY_UNINSTALL}"
done