Merge pull request #4 from palmerc/master
Updates you might be interested in
This commit is contained in:
commit
6abc0cf79c
|
@ -1 +1,5 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
||||||
|
*.ipa
|
||||||
|
|
||||||
|
frida/
|
||||||
|
|
71
README.md
71
README.md
|
@ -1,50 +1,39 @@
|
||||||
# frida-ios-dump
|
# frida-ios-dump
|
||||||
pull decrypted ipa from jailbreak device
|
Pull a decrypted IPA from a jailbroken device
|
||||||
|
|
||||||
### Usage
|
## Usage
|
||||||
|
|
||||||
## 1. install [frida](http://www.frida.re/) on device and mac
|
1. Install [frida](http://www.frida.re/) on device and mac
|
||||||
|
2. Run usbmuxd/iproxy SSH forwarding over USB (Default 2222 -> 22)
|
||||||
|
3. Run ./dump.py `Display name` or `Bundle identifier`
|
||||||
|
|
||||||
## 2. iproxy 2222 22
|
For SSH/SCP make sure you have your public key added to the target device's ~/.ssh/authorized_keys file.
|
||||||
|
|
||||||
## 3. ./dump.py 微信
|
|
||||||
|
|
||||||
```
|
```
|
||||||
➜ frida-ios-dump ./dump.py 微信
|
./dump.py Aftenposten
|
||||||
open target app......
|
Start the target app Aftenposten
|
||||||
start dump target app......
|
Dumping Aftenposten to /var/folders/wn/9v1hs8ds6nv_xj7g95zxyl140000gn/T
|
||||||
start dump /var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/WeChat
|
start dump /var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/AftenpostenApp
|
||||||
WeChat 100% 68MB 11.4MB/s 00:05
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/AFNetworking.framework/AFNetworking
|
||||||
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/WCDB.framework/WCDB
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/ATInternet_iOS_ObjC_SDK.framework/ATInternet_iOS_ObjC_SDK
|
||||||
WCDB 100% 2555KB 11.0MB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/SPTEventCollector.framework/SPTEventCollector
|
||||||
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/MMCommon.framework/MMCommon
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/SPiDSDK.framework/SPiDSDK
|
||||||
MMCommon 100% 979KB 10.6MB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftCore.dylib
|
||||||
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/MultiMedia.framework/MultiMedia
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftCoreData.dylib
|
||||||
MultiMedia 100% 6801KB 11.1MB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftCoreGraphics.dylib
|
||||||
start dump /private/var/containers/Bundle/Application/6665AA28-68CC-4845-8610-7010E96061C6/WeChat.app/Frameworks/mars.framework/mars
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftCoreImage.dylib
|
||||||
mars 100% 7462KB 11.1MB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftCoreLocation.dylib
|
||||||
AppIcon60x60@2x.png 100% 2253 230.9KB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftDarwin.dylib
|
||||||
AppIcon60x60@3x.png 100% 4334 834.8KB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftDispatch.dylib
|
||||||
AppIcon76x76@2x~ipad.png 100% 2659 620.6KB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftFoundation.dylib
|
||||||
AppIcon76x76~ipad.png 100% 1523 358.0KB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftObjectiveC.dylib
|
||||||
AppIcon83.5x83.5@2x~ipad.png 100% 2725 568.9KB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftQuartzCore.dylib
|
||||||
Assets.car 100% 10MB 11.1MB/s 00:00
|
start dump /private/var/containers/Bundle/Application/66423A80-0AFE-471C-BC9B-B571107D3C27/AftenpostenApp.app/Frameworks/libswiftUIKit.dylib
|
||||||
.......
|
Generating Aftenposten.ipa
|
||||||
AppIntentVocabulary.plist 100% 197 52.9KB/s 00:00
|
|
||||||
AppIntentVocabulary.plist 100% 167 43.9KB/s 00:00
|
Done.
|
||||||
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.
|
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!
|
||||||
|
|
52
app.js
52
app.js
|
@ -1,52 +0,0 @@
|
||||||
//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() && proxy.localizedName().toString() == name){
|
|
||||||
return proxy.bundleIdentifier().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
};
|
|
||||||
|
|
||||||
function getdisplayname(bundleid){
|
|
||||||
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.bundleIdentifier() && proxy.bundleIdentifier().toString() == bundleid){
|
|
||||||
return proxy.localizedName().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMessage(message) {
|
|
||||||
var bundleid;
|
|
||||||
var displayname;
|
|
||||||
if(message['name']){
|
|
||||||
displayname = message['name']
|
|
||||||
bundleid = getbundleid(displayname);
|
|
||||||
}else if(message['bundleid']){
|
|
||||||
bundleid = message['bundleid']
|
|
||||||
displayname = getdisplayname(bundleid);
|
|
||||||
}
|
|
||||||
if(bundleid.length > 0){
|
|
||||||
openApplication(bundleid);
|
|
||||||
}
|
|
||||||
send({ opened: displayname });
|
|
||||||
}
|
|
||||||
|
|
||||||
recv(handleMessage);
|
|
14
dump.js
14
dump.js
|
@ -175,9 +175,9 @@ var LC_SEGMENT_64 = 0x19;
|
||||||
var LC_ENCRYPTION_INFO = 0x21;
|
var LC_ENCRYPTION_INFO = 0x21;
|
||||||
var LC_ENCRYPTION_INFO_64 = 0x2C;
|
var LC_ENCRYPTION_INFO_64 = 0x2C;
|
||||||
|
|
||||||
function pad(str, n) {
|
function pad(str, n) {
|
||||||
return Array(n-str.length+1).join("0")+str;
|
return Array(n-str.length+1).join("0")+str;
|
||||||
}
|
}
|
||||||
|
|
||||||
function swap32(value) {
|
function swap32(value) {
|
||||||
value = pad(value.toString(16),8)
|
value = pad(value.toString(16),8)
|
||||||
|
@ -214,7 +214,7 @@ function dumpModule(name) {
|
||||||
if(!access(allocStr(newmodpath),0)){
|
if(!access(allocStr(newmodpath),0)){
|
||||||
remove(allocStr(newmodpath));
|
remove(allocStr(newmodpath));
|
||||||
}
|
}
|
||||||
|
|
||||||
var fmodule = open(newmodpath, O_CREAT | O_RDWR, 0);
|
var fmodule = open(newmodpath, O_CREAT | O_RDWR, 0);
|
||||||
var foldmodule = open(oldmodpath, O_RDONLY, 0);
|
var foldmodule = open(oldmodpath, O_RDONLY, 0);
|
||||||
|
|
||||||
|
@ -288,7 +288,7 @@ function dumpModule(name) {
|
||||||
var segments = [];
|
var segments = [];
|
||||||
for (var i = 0; i < ncmds; i++) {
|
for (var i = 0; i < ncmds; i++) {
|
||||||
var cmd = getU32(modbase.add(off));
|
var cmd = getU32(modbase.add(off));
|
||||||
var cmdsize = getU32(modbase.add(off + 4));
|
var cmdsize = getU32(modbase.add(off + 4));
|
||||||
if (cmd == LC_ENCRYPTION_INFO || cmd == LC_ENCRYPTION_INFO_64) {
|
if (cmd == LC_ENCRYPTION_INFO || cmd == LC_ENCRYPTION_INFO_64) {
|
||||||
offset_cryptid = off + 16;
|
offset_cryptid = off + 16;
|
||||||
crypt_off = getU32(modbase.add(off + 8));
|
crypt_off = getU32(modbase.add(off + 8));
|
||||||
|
@ -305,11 +305,11 @@ function dumpModule(name) {
|
||||||
lseek(fmodule, crypt_off, SEEK_SET);
|
lseek(fmodule, crypt_off, SEEK_SET);
|
||||||
write(fmodule, modbase.add(crypt_off), crypt_size);
|
write(fmodule, modbase.add(crypt_off), crypt_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fmodule);
|
close(fmodule);
|
||||||
close(foldmodule);
|
close(foldmodule);
|
||||||
return newmodpath
|
return newmodpath
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessage(message) {
|
function handleMessage(message) {
|
||||||
//start dump
|
//start dump
|
||||||
|
|
483
dump.py
483
dump.py
|
@ -1,272 +1,317 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
#Author : AloneMonkey
|
# Author : AloneMonkey
|
||||||
#blog: www.alonemonkey.com
|
# blog: www.alonemonkey.com
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import codecs
|
import codecs
|
||||||
import frida
|
import frida
|
||||||
import threading
|
import threading
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
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)
|
reload(sys)
|
||||||
sys.setdefaultencoding('utf8')
|
sys.setdefaultencoding('utf8')
|
||||||
|
|
||||||
DUMP_JS = './dump.js'
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
APP_JS = './app.js'
|
|
||||||
OUTPUT = "Payload"
|
DUMP_JS = os.path.join(script_dir, 'dump.js')
|
||||||
|
|
||||||
|
User = 'root'
|
||||||
|
Password = 'alpine'
|
||||||
|
Host = 'localhost'
|
||||||
|
Port = 2222
|
||||||
|
|
||||||
|
TEMP_DIR = tempfile.gettempdir()
|
||||||
|
PAYLOAD_DIR = 'Payload'
|
||||||
|
PAYLOAD_PATH = os.path.join(TEMP_DIR, PAYLOAD_DIR)
|
||||||
file_dict = {}
|
file_dict = {}
|
||||||
|
|
||||||
opened = threading.Event()
|
|
||||||
finished = threading.Event()
|
finished = threading.Event()
|
||||||
|
|
||||||
global session
|
|
||||||
global name
|
|
||||||
|
|
||||||
#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' % ('displayname','Decrypt the application of the specified display name. ps: ./dump.py 微信');
|
|
||||||
print '\t%-20s\t%s' % ('-l','List the app has been installed.');
|
|
||||||
print '\t%-20s\t%s' % ('-b bundleid','Decrypt the application of the specified bundleid. ps: ./dump.py com.tencent.xin');
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
def get_usb_iphone():
|
def get_usb_iphone():
|
||||||
dManager = frida.get_device_manager();
|
device_manager = frida.get_device_manager()
|
||||||
changed = threading.Event()
|
changed = threading.Event()
|
||||||
def on_changed():
|
|
||||||
changed.set()
|
|
||||||
dManager.on('changed',on_changed)
|
|
||||||
|
|
||||||
device = None
|
def on_changed():
|
||||||
while device is None:
|
changed.set()
|
||||||
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)
|
device_manager.on('changed', on_changed)
|
||||||
|
|
||||||
return device
|
|
||||||
|
|
||||||
def gen_ipa(target):
|
device = None
|
||||||
try:
|
while device is None:
|
||||||
app_name = file_dict["app"]
|
devices = [dev for dev in device_manager.enumerate_devices() if dev.type == 'tether']
|
||||||
for key, value in file_dict.items():
|
if len(devices) == 0:
|
||||||
if key != "app":
|
print 'Waiting for USB device...'
|
||||||
shutil.move(target +"/"+ key, target + "/" + app_name + "/" + value);
|
changed.wait()
|
||||||
(shotname,extension) = os.path.splitext(app_name)
|
else:
|
||||||
os.system(u''.join(("zip -qr ", name.replace(" ", "\\ "), ".ipa ./Payload")).encode('utf-8').strip());
|
device = devices[0]
|
||||||
os.system("rm -rf ./Payload");
|
|
||||||
except Exception as e:
|
device_manager.off('changed', on_changed)
|
||||||
print e
|
|
||||||
finished.set();
|
return device
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ipa(path, display_name):
|
||||||
|
ipa_filename = display_name + '.ipa'
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
target_dir = './' + PAYLOAD_DIR
|
||||||
|
zip_args = ('zip', '-qr', os.path.join(os.getcwd(), ipa_filename), target_dir)
|
||||||
|
subprocess.check_call(zip_args, cwd=TEMP_DIR)
|
||||||
|
shutil.rmtree(PAYLOAD_PATH)
|
||||||
|
print
|
||||||
|
except Exception as e:
|
||||||
|
print e
|
||||||
|
finished.set()
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(message, data):
|
||||||
|
global name
|
||||||
|
if 'payload' in message:
|
||||||
|
payload = message['payload']
|
||||||
|
if 'opened' in payload:
|
||||||
|
name = payload['opened']
|
||||||
|
|
||||||
|
if 'dump' in payload:
|
||||||
|
origin_path = payload['path']
|
||||||
|
dump_path = payload['dump']
|
||||||
|
|
||||||
|
scp_from = dump_path
|
||||||
|
scp_to = PAYLOAD_PATH + u'/'
|
||||||
|
|
||||||
|
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)
|
||||||
|
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 'app' in payload:
|
||||||
|
app_path = payload['app']
|
||||||
|
|
||||||
|
scp_from = app_path
|
||||||
|
scp_to = PAYLOAD_PATH + u'/'
|
||||||
|
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)
|
||||||
|
try:
|
||||||
|
subprocess.check_call(chmod_args)
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
print err
|
||||||
|
|
||||||
|
file_dict['app'] = os.path.basename(app_path)
|
||||||
|
|
||||||
|
if 'done' in payload:
|
||||||
|
finished.set()
|
||||||
|
|
||||||
def on_message(message,data):
|
|
||||||
global name;
|
|
||||||
if message.has_key('payload'):
|
|
||||||
payload = message['payload']
|
|
||||||
if payload.has_key("opened"):
|
|
||||||
name = payload["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.replace(" ", "\\\\\ "), u" ./" + OUTPUT + u"/")).encode('utf-8').strip())
|
|
||||||
os.system(u''.join(("chmod 655 ", u'./' + OUTPUT + u'/', os.path.basename(dumppath.replace(" ", "\\ ")))).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.replace(" ", "\\\\\ "), u" ./" + OUTPUT + u"/")).encode('utf-8').strip())
|
|
||||||
os.system(u''.join(("chmod 755 ", u'./' + OUTPUT + u'/', os.path.basename(apppath.replace(" ", "\\ ")))).encode('utf-8').strip())
|
|
||||||
file_dict["app"] = os.path.basename(apppath)
|
|
||||||
if payload.has_key("done"):
|
|
||||||
gen_ipa(os.getcwd()+"/"+OUTPUT)
|
|
||||||
finished.set();
|
|
||||||
|
|
||||||
def compare_applications(a, b):
|
def compare_applications(a, b):
|
||||||
a_is_running = a.pid != 0
|
a_is_running = a.pid != 0
|
||||||
b_is_running = b.pid != 0
|
b_is_running = b.pid != 0
|
||||||
if a_is_running == b_is_running:
|
if a_is_running == b_is_running:
|
||||||
if a.name > b.name:
|
if a.name > b.name:
|
||||||
return 1
|
return 1
|
||||||
elif a.name < b.name:
|
elif a.name < b.name:
|
||||||
return -1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
elif a_is_running:
|
|
||||||
return -1
|
return -1
|
||||||
else:
|
else:
|
||||||
return 1
|
return 0
|
||||||
|
elif a_is_running:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def cmp_to_key(mycmp):
|
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):
|
|
||||||
self.obj = obj
|
|
||||||
def __lt__(self, other):
|
|
||||||
return mycmp(self.obj, other.obj) < 0
|
|
||||||
def __gt__(self, other):
|
|
||||||
return mycmp(self.obj, other.obj) > 0
|
|
||||||
def __eq__(self, other):
|
|
||||||
return mycmp(self.obj, other.obj) == 0
|
|
||||||
def __le__(self, other):
|
|
||||||
return mycmp(self.obj, other.obj) <= 0
|
|
||||||
def __ge__(self, other):
|
|
||||||
return mycmp(self.obj, other.obj) >= 0
|
|
||||||
def __ne__(self, other):
|
|
||||||
return mycmp(self.obj, other.obj) != 0
|
|
||||||
return K
|
|
||||||
|
|
||||||
def get_applications():
|
class K:
|
||||||
device = get_usb_iphone()
|
def __init__(self, obj):
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
try:
|
def __lt__(self, other):
|
||||||
applications = device.enumerate_applications()
|
return mycmp(self.obj, other.obj) < 0
|
||||||
except Exception as e:
|
|
||||||
print "Failed to enumerate applications: %s" % e
|
|
||||||
exit(1)
|
|
||||||
return
|
|
||||||
|
|
||||||
return applications
|
def __gt__(self, other):
|
||||||
|
return mycmp(self.obj, other.obj) > 0
|
||||||
|
|
||||||
def list_applications():
|
def __eq__(self, other):
|
||||||
applications = get_applications()
|
return mycmp(self.obj, other.obj) == 0
|
||||||
|
|
||||||
if len(applications) > 0:
|
def __le__(self, other):
|
||||||
pid_column_width = max(map(lambda app: len("%d" % app.pid), applications))
|
return mycmp(self.obj, other.obj) <= 0
|
||||||
name_column_width = max(map(lambda app: len(app.name), applications))
|
|
||||||
identifier_column_width = max(map(lambda app: len(app.identifier), applications))
|
|
||||||
else:
|
|
||||||
pid_column_width = 0
|
|
||||||
name_column_width = 0
|
|
||||||
identifier_column_width = 0
|
|
||||||
|
|
||||||
header_format = "%" + str(pid_column_width) + "s " + "%-" + str(name_column_width) + "s " + "%-" + str(identifier_column_width) + "s"
|
def __ge__(self, other):
|
||||||
print header_format % ("PID", "Name", "Identifier")
|
return mycmp(self.obj, other.obj) >= 0
|
||||||
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)
|
|
||||||
else:
|
|
||||||
print line_format % (app.pid, app.name, app.identifier)
|
|
||||||
|
|
||||||
def get_pid_by_bundleid(bundleid):
|
def __ne__(self, other):
|
||||||
applications = get_applications()
|
return mycmp(self.obj, other.obj) != 0
|
||||||
|
|
||||||
return [app.pid for app in applications if app.identifier == bundleid][0]
|
return K
|
||||||
|
|
||||||
def find_target_app(isbundleid, value):
|
|
||||||
applications = get_applications()
|
def get_applications(device):
|
||||||
|
try:
|
||||||
|
applications = device.enumerate_applications()
|
||||||
|
except Exception as e:
|
||||||
|
print 'Failed to enumerate applications: %s' % e
|
||||||
|
return
|
||||||
|
|
||||||
|
return applications
|
||||||
|
|
||||||
|
|
||||||
|
def list_applications(device):
|
||||||
|
applications = get_applications(device)
|
||||||
|
|
||||||
|
if len(applications) > 0:
|
||||||
|
pid_column_width = max(map(lambda app: len('{}'.format(app.pid)), applications))
|
||||||
|
name_column_width = max(map(lambda app: len(app.name), applications))
|
||||||
|
identifier_column_width = max(map(lambda app: len(app.identifier), applications))
|
||||||
|
else:
|
||||||
|
pid_column_width = 0
|
||||||
|
name_column_width = 0
|
||||||
|
identifier_column_width = 0
|
||||||
|
|
||||||
|
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 * '-')
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
print line_format % (application.pid, application.name, application.identifier)
|
||||||
|
|
||||||
if not isbundleid:
|
|
||||||
return [app for app in applications if app.name == value]
|
|
||||||
else:
|
|
||||||
return [app for app in applications if app.identifier == value]
|
|
||||||
|
|
||||||
def load_js_file(session, filename):
|
def load_js_file(session, filename):
|
||||||
source = ''
|
source = ''
|
||||||
with codecs.open(filename,'r','utf-8') as f:
|
with codecs.open(filename, 'r', 'utf-8') as f:
|
||||||
source = source + f.read();
|
source = source + f.read()
|
||||||
script = session.create_script(source);
|
script = session.create_script(source)
|
||||||
script.on("message",on_message)
|
script.on('message', on_message)
|
||||||
script.load()
|
script.load()
|
||||||
return script
|
|
||||||
|
return script
|
||||||
|
|
||||||
def clear_and_quit(session):
|
|
||||||
if session:
|
|
||||||
session.detach()
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
def create_dir(path):
|
def create_dir(path):
|
||||||
path = path.strip()
|
path = path.strip()
|
||||||
path = path.rstrip("\\")
|
path = path.rstrip('\\')
|
||||||
if not os.path.exists(path):
|
if os.path.exists(path):
|
||||||
os.makedirs(path)
|
shutil.rmtree(path)
|
||||||
else:
|
try:
|
||||||
print path + u" is existed!";
|
os.makedirs(path)
|
||||||
|
except os.error as err:
|
||||||
|
print err
|
||||||
|
|
||||||
def open_target_app(isbundleid, value):
|
|
||||||
global session;
|
|
||||||
device = get_usb_iphone();
|
|
||||||
name = u'SpringBoard';
|
|
||||||
print "open target app......"
|
|
||||||
session = device.attach(name);
|
|
||||||
script = load_js_file(session, APP_JS);
|
|
||||||
if not isbundleid:
|
|
||||||
script.post({'name': value})
|
|
||||||
else:
|
|
||||||
script.post({'bundleid': value})
|
|
||||||
opened.wait();
|
|
||||||
session.detach();
|
|
||||||
create_dir(os.getcwd()+"/"+OUTPUT)
|
|
||||||
print 'Waiting for the application to open......'
|
|
||||||
time.sleep(5);
|
|
||||||
|
|
||||||
def start_dump(target):
|
def open_target_app(device, name_or_bundleid):
|
||||||
global session;
|
print 'Start the target app {}'.format(name_or_bundleid)
|
||||||
print "start dump target app......"
|
|
||||||
device = get_usb_iphone();
|
|
||||||
session = device.attach(target);
|
|
||||||
script = load_js_file(session, DUMP_JS);
|
|
||||||
script.post("dump");
|
|
||||||
finished.wait();
|
|
||||||
clear_and_quit(session);
|
|
||||||
|
|
||||||
def dump_by_display_name(display_name):
|
pid = -1
|
||||||
open_target_app( 0, display_name)
|
display_name = ''
|
||||||
start_dump(display_name);
|
bundle_identifier = ''
|
||||||
|
for application in get_applications(device):
|
||||||
|
if name_or_bundleid == application.identifier or name_or_bundleid == application.name:
|
||||||
|
display_name = application.name
|
||||||
|
bundle_identifier = application.identifier
|
||||||
|
|
||||||
def dump_by_bundleid(bundleid):
|
try:
|
||||||
open_target_app( 1, bundleid)
|
pid = device.spawn([bundle_identifier])
|
||||||
start_dump(get_pid_by_bundleid(bundleid));
|
device.resume(pid)
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception as e:
|
||||||
|
print e
|
||||||
|
|
||||||
def check_args():
|
return pid, display_name, bundle_identifier
|
||||||
if len(sys.argv) < 2:
|
|
||||||
usage()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts,args = getopt.getopt(sys.argv[1:],"hlb:",["help"]);
|
|
||||||
except getopt.GetoptError:
|
|
||||||
usage()
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
for opt,value in opts:
|
def start_dump(device, pid, ipa_name):
|
||||||
if opt in ("-h","--help"):
|
print 'Dumping {} to {}'.format(display_name, TEMP_DIR)
|
||||||
usage()
|
|
||||||
if opt in ("-l"):
|
|
||||||
list_applications()
|
|
||||||
if opt in ("-b"):
|
|
||||||
if not find_target_app(1, value):
|
|
||||||
print "can not find target app '%s'" % value
|
|
||||||
else:
|
|
||||||
dump_by_bundleid(value)
|
|
||||||
|
|
||||||
if len(opts) == 0 and len(sys.argv) == 2:
|
session = device.attach(pid)
|
||||||
name = sys.argv[1].decode('utf8');
|
script = load_js_file(session, DUMP_JS)
|
||||||
if not find_target_app(0, name):
|
script.post('dump')
|
||||||
print "can not find target app '%s'" % name
|
finished.wait()
|
||||||
else:
|
|
||||||
dump_by_display_name(name)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
generate_ipa(PAYLOAD_PATH, ipa_name)
|
||||||
try:
|
|
||||||
check_args()
|
if session:
|
||||||
pass
|
session.detach()
|
||||||
except KeyboardInterrupt:
|
|
||||||
if session:
|
|
||||||
session.detach()
|
if __name__ == '__main__':
|
||||||
sys.exit()
|
parser = argparse.ArgumentParser(description='frida-ios-dump (by AloneMonkey v2.0)')
|
||||||
except:
|
parser.add_argument('-l', '--list', dest='list_applications', action='store_true', help='List the installed apps')
|
||||||
pass
|
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
|
||||||
|
ssh = None
|
||||||
|
device = get_usb_iphone()
|
||||||
|
if args.list_applications:
|
||||||
|
list_applications(device)
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
if pid > 0:
|
||||||
|
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):
|
||||||
|
shutil.rmtree(PAYLOAD_PATH)
|
||||||
|
|
||||||
|
sys.exit(exit_code)
|
|
@ -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
|
Loading…
Reference in New Issue