scripts directory #2
This commit is contained in:
parent
05e330fcae
commit
fce5c5c606
83
scripts/check_for_native_calls.py
Normal file
83
scripts/check_for_native_calls.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Check for native library calls and return a stacktrace
|
||||||
|
import sys
|
||||||
|
import frida
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(m, _data):
|
||||||
|
if m['type'] == 'send':
|
||||||
|
print(m['payload'])
|
||||||
|
else:
|
||||||
|
if m['type'] == 'error':
|
||||||
|
pprint(m)
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
jscode = """
|
||||||
|
Java.perform(function() {
|
||||||
|
|
||||||
|
var SystemDef = Java.use('java.lang.System');
|
||||||
|
|
||||||
|
var RuntimeDef = Java.use('java.lang.Runtime');
|
||||||
|
|
||||||
|
var exceptionClass = Java.use('java.lang.Exception');
|
||||||
|
|
||||||
|
var SystemLoad_1 = SystemDef.load.overload('java.lang.String');
|
||||||
|
|
||||||
|
var SystemLoad_2 = SystemDef.loadLibrary.overload('java.lang.String');
|
||||||
|
|
||||||
|
var RuntimeLoad_1 = RuntimeDef.load.overload('java.lang.String');
|
||||||
|
|
||||||
|
var RuntimeLoad_2 = RuntimeDef.loadLibrary.overload('java.lang.String');
|
||||||
|
|
||||||
|
var ThreadDef = Java.use('java.lang.Thread');
|
||||||
|
|
||||||
|
var ThreadObj = ThreadDef.$new();
|
||||||
|
|
||||||
|
SystemLoad_1.implementation = function(library) {
|
||||||
|
send("[1] Loading dynamic library => " + library);
|
||||||
|
stackTrace();
|
||||||
|
return SystemLoad_1.call(this, library);
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemLoad_2.implementation = function(library) {
|
||||||
|
send("[2] Loading dynamic library => " + library);
|
||||||
|
stackTrace();
|
||||||
|
SystemLoad_2.call(this, library);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLoad_1.implementation = function(library) {
|
||||||
|
send("[3] Loading dynamic library => " + library);
|
||||||
|
stackTrace();
|
||||||
|
RuntimeLoad_1.call(this, library);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeLoad_2.implementation = function(library) {
|
||||||
|
send("[4] Loading dynamic library => " + library);
|
||||||
|
stackTrace();
|
||||||
|
RuntimeLoad_2.call(this, library);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stackTrace() {
|
||||||
|
var stack = ThreadObj.currentThread().getStackTrace();
|
||||||
|
for (var i = 0; i < stack.length; i++) {
|
||||||
|
send(i + " => " + stack[i].toString());
|
||||||
|
}
|
||||||
|
send("--------------------------------------------------------------------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
APP = 'com.app'
|
||||||
|
device = frida.get_usb_device()
|
||||||
|
pid = device.spawn([APP])
|
||||||
|
session = device.attach(pid)
|
||||||
|
script = session.create_script(jscode)
|
||||||
|
print("[*] Intercepting [{}]".format(pid))
|
||||||
|
script.on('message', on_message)
|
||||||
|
script.load()
|
||||||
|
device.resume(APP)
|
||||||
|
sys.stdin.read()
|
119
scripts/dump_dynamically_created_files.py
Normal file
119
scripts/dump_dynamically_created_files.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import frida
|
||||||
|
|
||||||
|
APP_NAME = 'com.package.name'
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(message, _ignored_data):
|
||||||
|
if message['type'] == 'send':
|
||||||
|
if type(message['payload']) == dict:
|
||||||
|
os.makedirs(os.path.dirname('./dump/{}/'.format(APP_NAME)), exist_ok=True) # create sub folder if not exist
|
||||||
|
with open('./dump/{}/{}'.format(APP_NAME, message['payload']['file']), 'w') as d:
|
||||||
|
for element in message['payload']['content']:
|
||||||
|
d.write(chr(element % 256))
|
||||||
|
d.close()
|
||||||
|
print('[*] Successfully dumped to {0}'.format(message['payload']['file']))
|
||||||
|
else:
|
||||||
|
print('[*] {0}'.format(message['payload'].encode('utf-8')))
|
||||||
|
else:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
|
||||||
|
js_code = """
|
||||||
|
Java.perform(function() {
|
||||||
|
var openedfile = "";
|
||||||
|
var data = {
|
||||||
|
"file": "",
|
||||||
|
"content": []
|
||||||
|
};
|
||||||
|
var isOpen = false;
|
||||||
|
var index = 0;
|
||||||
|
|
||||||
|
var fos = Java.use('java.io.FileOutputStream');
|
||||||
|
|
||||||
|
var fos_construct_2 = fos.$init.overload('java.lang.String');
|
||||||
|
var fos_construct_3 = fos.$init.overload('java.io.File');
|
||||||
|
var fos_construct_4 = fos.$init.overload('java.lang.String', 'boolean');
|
||||||
|
var fos_construct_5 = fos.$init.overload('java.io.File', 'boolean');
|
||||||
|
|
||||||
|
var fos_write_1 = fos.write.overload('[B', 'int', 'int');
|
||||||
|
|
||||||
|
var fos_close = fos.close;
|
||||||
|
|
||||||
|
function dump(data) {
|
||||||
|
send("Got " + data["content"].length + " bytes!");
|
||||||
|
var tmp_name = openedfile.split("/");
|
||||||
|
tmp_name = tmp_name[tmp_name.length - 1];
|
||||||
|
data["file"] = tmp_name;
|
||||||
|
send(data);
|
||||||
|
data["content"] = [];
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fos_construct_2.implementation = function(file) {
|
||||||
|
var filename = file;
|
||||||
|
if (openedfile != filename) {
|
||||||
|
openedfile = filename;
|
||||||
|
send("File opened for write " + filename);
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
return fos_construct_2.call(this, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fos_construct_3.implementation = function(file) {
|
||||||
|
var filename = file.getAbsolutePath();
|
||||||
|
if (openedfile != filename) {
|
||||||
|
openedfile = filename;
|
||||||
|
send("File opened for write " + filename);
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
return fos_construct_3.call(this, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fos_construct_4.implementation = function(file, true_false) {
|
||||||
|
var filename = file;
|
||||||
|
if (openedfile != filename) {
|
||||||
|
openedfile = filename;
|
||||||
|
send("File opened for write " + filename);
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
return fos_construct_4.call(this, file, true_false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fos_construct_5.implementation = function(file, true_false) {
|
||||||
|
var filename = file.getAbsolutePath();
|
||||||
|
if (openedfile != filename) {
|
||||||
|
openedfile = filename;
|
||||||
|
send("File opened for write " + filename);
|
||||||
|
isOpen = true;
|
||||||
|
}
|
||||||
|
return fos_construct_5.call(this, file, true_false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fos_write_1.implementation = function(arr, offset, length) {
|
||||||
|
var i = 0;
|
||||||
|
for (i = offset; i < length; i = i + 1) {
|
||||||
|
data["content"][index] = arr[i];
|
||||||
|
index = index + 1;
|
||||||
|
}
|
||||||
|
return fos_write_1.call(this, arr, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
fos_close.implementation = function() {
|
||||||
|
dump(data);
|
||||||
|
return fos_close.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
|
||||||
|
device = frida.get_usb_device()
|
||||||
|
pid = device.spawn([APP_NAME])
|
||||||
|
session = device.attach(pid)
|
||||||
|
script = session.create_script(js_code)
|
||||||
|
print("[*] Intercepting [{}]".format(pid))
|
||||||
|
script.on('message', on_message)
|
||||||
|
script.load()
|
||||||
|
device.resume(APP_NAME)
|
||||||
|
sys.stdin.read()
|
75
scripts/exec_shell_cmd.py
Normal file
75
scripts/exec_shell_cmd.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Execute shell command
|
||||||
|
For example, list directory contents:
|
||||||
|
def ls(folder):
|
||||||
|
cmd = Shell(['/bin/sh', '-c', 'ls -la ' + folder], None)
|
||||||
|
cmd.exec()
|
||||||
|
for chunk in cmd.output:
|
||||||
|
print(chunk.strip().decode())
|
||||||
|
"""
|
||||||
|
import frida
|
||||||
|
from frida.application import Reactor
|
||||||
|
import threading
|
||||||
|
import click
|
||||||
|
|
||||||
|
|
||||||
|
class Shell(object):
|
||||||
|
def __init__(self, argv, env):
|
||||||
|
self._stop_requested = threading.Event()
|
||||||
|
self._reactor = Reactor(run_until_return=lambda reactor: self._stop_requested.wait())
|
||||||
|
|
||||||
|
self._device = frida.get_usb_device()
|
||||||
|
self._sessions = set()
|
||||||
|
|
||||||
|
self._device.on("child-added", lambda child: self._reactor.schedule(lambda: self._on_child_added(child)))
|
||||||
|
self._device.on("child-removed", lambda child: self._reactor.schedule(lambda: self._on_child_removed(child)))
|
||||||
|
self._device.on("output", lambda pid, fd, data: self._reactor.schedule(lambda: self._on_output(pid, fd, data)))
|
||||||
|
|
||||||
|
self.argv = argv
|
||||||
|
self.env = env
|
||||||
|
self.output = [] # stdout will pushed into array
|
||||||
|
|
||||||
|
def exec(self):
|
||||||
|
self._reactor.schedule(lambda: self._start())
|
||||||
|
self._reactor.run()
|
||||||
|
|
||||||
|
def _start(self):
|
||||||
|
click.secho("✔ spawn(argv={})".format(self.argv), fg='green', dim=True)
|
||||||
|
pid = self._device.spawn(self.argv, env=self.env, stdio='pipe')
|
||||||
|
self._instrument(pid)
|
||||||
|
|
||||||
|
def _stop_if_idle(self):
|
||||||
|
if len(self._sessions) == 0:
|
||||||
|
self._stop_requested.set()
|
||||||
|
|
||||||
|
def _instrument(self, pid):
|
||||||
|
click.secho("✔ attach(pid={})".format(pid), fg='green', dim=True)
|
||||||
|
session = self._device.attach(pid)
|
||||||
|
session.on("detached", lambda reason: self._reactor.schedule(lambda: self._on_detached(pid, session, reason)))
|
||||||
|
click.secho("✔ enable_child_gating()", fg='green', dim=True)
|
||||||
|
session.enable_child_gating()
|
||||||
|
# print("✔ resume(pid={})".format(pid))
|
||||||
|
self._device.resume(pid)
|
||||||
|
self._sessions.add(session)
|
||||||
|
|
||||||
|
def _on_child_added(self, child):
|
||||||
|
click.secho("⚡ child_added: {}".format(child), fg='green', dim=True)
|
||||||
|
self._instrument(child.pid)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _on_child_removed(child):
|
||||||
|
click.secho("⚡ child_removed: {}".format(child), fg='green', dim=True)
|
||||||
|
|
||||||
|
def _on_output(self, pid, fd, data):
|
||||||
|
# if len(data) > 0:
|
||||||
|
# print("⚡ output: pid={}, fd={}, data={}".format(pid, fd, repr(data)))
|
||||||
|
self.output.append(data)
|
||||||
|
|
||||||
|
def _on_detached(self, pid, session, reason):
|
||||||
|
click.secho("⚡ detached: pid={}, reason='{}'".format(pid, reason), fg='green', dim=True)
|
||||||
|
self._sessions.remove(session)
|
||||||
|
self._reactor.schedule(self._stop_if_idle, delay=0.5)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _on_message(pid, message):
|
||||||
|
click.secho("⚡ message: pid={}, payload={}".format(pid, message), fg='green', dim=True)
|
73
scripts/how_to_access_inner_class_static_field.md
Normal file
73
scripts/how_to_access_inner_class_static_field.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
### How to access inner class static field
|
||||||
|
```
|
||||||
|
package tech.yusi.fridademo;
|
||||||
|
|
||||||
|
public class Jingdong {
|
||||||
|
private int intResult;
|
||||||
|
|
||||||
|
private final static class a {
|
||||||
|
final static Jingdong a = new Jingdong();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Jingdong() {
|
||||||
|
intResult = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Jingdong a() {
|
||||||
|
return a.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int a(int arg0, int arg1) {
|
||||||
|
return arg0 + arg1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String a(String arg0, String arg1) {
|
||||||
|
return arg0 + arg1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import frida,sys
|
||||||
|
|
||||||
|
rdev = frida.get_remote_device()
|
||||||
|
session = rdev.attach("tech.yusi.fridademo")
|
||||||
|
|
||||||
|
def on_message(message ,data):
|
||||||
|
if message['type'] == 'send':
|
||||||
|
print(message['payload'])
|
||||||
|
elif message['type'] == 'error':
|
||||||
|
print(message['stack'])
|
||||||
|
else:
|
||||||
|
print(message)
|
||||||
|
|
||||||
|
jscode = """
|
||||||
|
send(Java.available);
|
||||||
|
Java.perform(function () {
|
||||||
|
var JingdongA = Java.use("tech.yusi.fridademo.Jingdong$a");
|
||||||
|
var Jingdong = JingdongA.a;
|
||||||
|
send(Jingdong.fieldType);
|
||||||
|
|
||||||
|
var JingdongInstance = Jingdong.value;
|
||||||
|
var ret = JingdongInstance.a("G8", "4tar");
|
||||||
|
send(ret);
|
||||||
|
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
|
||||||
|
script = session.create_script(jscode)
|
||||||
|
script.on("message" , on_message)
|
||||||
|
script.load()
|
||||||
|
|
||||||
|
try:
|
||||||
|
sys.stdin.read()
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
session.detach()
|
||||||
|
sys.exit(0)
|
||||||
|
```
|
||||||
|
|
20
scripts/install_frida_server.sh
Normal file
20
scripts/install_frida_server.sh
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Download latest frida-server, extract, push & run on android device/emulator
|
||||||
|
# adb 1.0.32, jq 1.5, xz 5.1, wget 1.17.1
|
||||||
|
# sudo apt install wget jq xz
|
||||||
|
|
||||||
|
# PARCH = phone architecture
|
||||||
|
# if oneliner [[ == "armeabi-v7a" ]] is a dirty fix because frida's release for armeabi-v7a is just "arm"
|
||||||
|
|
||||||
|
# TODO fix adb root which does not work on phones, only emulators, use `adb shell su` instead
|
||||||
|
|
||||||
|
PARCH=`adb shell getprop ro.product.cpu.abi`;\
|
||||||
|
[[ "${PARCH}" == "armeabi-v7a" ]] && PARCH="arm";\
|
||||||
|
wget -q -O - https://api.github.com/repos/frida/frida/releases \
|
||||||
|
| jq '.[0] | .assets[] | select(.browser_download_url | match("server(.*?)android-'${PARCH}'*\\.xz")).browser_download_url' \
|
||||||
|
| xargs wget -q --show-progress $1 \
|
||||||
|
&& unxz frida-server* \
|
||||||
|
&& adb root \
|
||||||
|
&& adb push frida-server* /data/local/tmp/frida-server \
|
||||||
|
&& adb shell "chmod 755 /data/local/tmp/frida-server" \
|
||||||
|
&& adb shell "/data/local/tmp/frida-server &"
|
23
scripts/log_string_builders_and_string_compare.js
Normal file
23
scripts/log_string_builders_and_string_compare.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
Java.perform(function() {
|
||||||
|
// string compare
|
||||||
|
var str = Java.use('java.lang.String'), objectClass = 'java.lang.Object';
|
||||||
|
str.equals.overload(objectClass).implementation = function(obj) {
|
||||||
|
var response = str.equals.overload(objectClass).call(this, obj);
|
||||||
|
if (obj) {
|
||||||
|
if (obj.toString().length > 5) {
|
||||||
|
send(str.toString.call(this) + ' == ' + obj.toString() + ' ? ' + response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
// log AbstractStringBuilder.toString()
|
||||||
|
['java.lang.StringBuilder', 'java.lang.StringBuffer'].forEach(function(clazz, i) {
|
||||||
|
console.log('[?] ' + i + ' = ' + clazz);
|
||||||
|
var func = 'toString';
|
||||||
|
Java.use(clazz)[func].implementation = function() {
|
||||||
|
var ret = this[func]();
|
||||||
|
send('[' + i + '] ' + ret);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
105
scripts/objc_ssl_unppining_helper.js
Normal file
105
scripts/objc_ssl_unppining_helper.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* By http://github.com/LotemBY *
|
||||||
|
|
||||||
|
This is a frida script for unpinning and reversing of ObjC applications.
|
||||||
|
Intercept method's which match regex.
|
||||||
|
|
||||||
|
You may change the following regex arrays to match your needs:
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The list of regexs for the moudle name
|
||||||
|
var moduleKeyWords = [/.*/]; // (It is not recommended to search all the moudles)
|
||||||
|
|
||||||
|
// The list of regexs for the method name
|
||||||
|
var methodKeyWords = [/cert/i, /trust/i, /ssl/i, /verify/i, /509/];
|
||||||
|
|
||||||
|
// The list of regexs for the method to override their return value with "1"
|
||||||
|
var overrideKeyWords = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
To run this script with frida on iPhone, follow these steps:
|
||||||
|
1. Make sure the iPhone is jailbreaked
|
||||||
|
2. Download the frida server from Cydia (package: re.frida.server)
|
||||||
|
3. Connect the iPhone to your computer with USB and open the application
|
||||||
|
4. Type in console "frida-ps -U" to get the list of running proccess on the iPhone, and find the proccess name of your app
|
||||||
|
5. Type in console "frida -U <APP PROCCESS NAME> -l <PATH TO THIS SCRIPT>" to run this script
|
||||||
|
6. Now you should use the app to trigger some of the intercepted methods
|
||||||
|
*/
|
||||||
|
var onCompleteCallback = function (retval) {};
|
||||||
|
setImmediate(function () {
|
||||||
|
if (!ObjC.available) {
|
||||||
|
console.log("[-] Objective-C Runtime is not available!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("=======================================================\n");
|
||||||
|
console.log("[*] Searching methods...");
|
||||||
|
|
||||||
|
var moduleUsed = false;
|
||||||
|
|
||||||
|
Process.enumerateModules({
|
||||||
|
onMatch: function(module) {
|
||||||
|
|
||||||
|
if (!matchesRegex(moduleKeyWords, module.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
moduleUsed = false;
|
||||||
|
Module.enumerateSymbols(module.name, {
|
||||||
|
onMatch: function(exp) {
|
||||||
|
if (matchesRegex(methodKeyWords, exp.name)) {
|
||||||
|
if (!moduleUsed) {
|
||||||
|
console.log("[*] In module \"" + module.name + "\"");
|
||||||
|
moduleUsed = true;
|
||||||
|
}
|
||||||
|
console.log("\t[*] Matching method: \"" + exp.name + "\", Address: " + Module.findExportByName(module.name, exp.name));
|
||||||
|
|
||||||
|
if (intercept(module.name, exp.name)) {
|
||||||
|
console.log("\t\t[+] Now intercepting " + exp.name);
|
||||||
|
} else {
|
||||||
|
console.log("\t\t[-] Could not intercept " + exp.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onComplete: onCompleteCallback
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onComplete: onCompleteCallback
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[*] Completed!");
|
||||||
|
console.log("=======================================================\n\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Return if 'str' match any of the regexs in the array 'regexList'
|
||||||
|
function matchesRegex(regexList, str) {
|
||||||
|
regexList.forEach(function(el) {
|
||||||
|
if (str.search(el) != -1)
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to intercept a method by moudle name and function name.
|
||||||
|
// Return 'true' on success and 'false' on failor.
|
||||||
|
function intercept(module, func) {
|
||||||
|
try {
|
||||||
|
Interceptor.attach(Module.findExportByName(module, func), {
|
||||||
|
onEnter: function(args) {
|
||||||
|
console.log("[*] Method CALL:\t\"" + func + "\" called!");
|
||||||
|
},
|
||||||
|
onLeave: function (retval) {
|
||||||
|
console.log("[*] Method RETURN:\t\"" + func + "\" (return value: " + retval + ")");
|
||||||
|
|
||||||
|
if (matchesRegex(overrideKeyWords, func)) {
|
||||||
|
console.log("[!] CHANGED RETURN VALUE of method:\t\"" + func + "\" (new value: " + 1 + ")");
|
||||||
|
retval.replace(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
164
scripts/print_native_method_arguments.py
Normal file
164
scripts/print_native_method_arguments.py
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
def on_message(m, _data):
|
||||||
|
if m['type'] == 'send':
|
||||||
|
print(m['payload'])
|
||||||
|
elif m['type'] == 'error':
|
||||||
|
print(m)
|
||||||
|
|
||||||
|
|
||||||
|
def switch(argument_key, idx):
|
||||||
|
"""
|
||||||
|
c/c++ variable type to javascript reader switch implementation
|
||||||
|
# TODO handle other arguments, [long, longlong..]
|
||||||
|
:param argument_key: variable type
|
||||||
|
:param idx: index in symbols array
|
||||||
|
:return: javascript to read the type of variable
|
||||||
|
"""
|
||||||
|
argument_key = argument_key.replace(' ', '')
|
||||||
|
return '%d: %s' % (idx, {
|
||||||
|
'int': 'args[%d].toInt32(),',
|
||||||
|
'unsignedint': 'args[%d].toInt32(),',
|
||||||
|
'std::string': 'Memory.readUtf8String(Memory.readPointer(args[%d])),',
|
||||||
|
'bool': 'Boolean(args[%d]),'
|
||||||
|
}[argument_key] % idx)
|
||||||
|
|
||||||
|
|
||||||
|
def list_symbols_from_object_files(module_id):
|
||||||
|
import subprocess
|
||||||
|
return subprocess.getoutput('nm --demangle --dynamic %s' % module_id)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_nm_output(nm_stdout, symbols):
|
||||||
|
for line in nm_stdout.splitlines():
|
||||||
|
split = line.split()
|
||||||
|
open_parenthesis_idx = line.find('(')
|
||||||
|
raw_arguments = [] if open_parenthesis_idx == -1 else line[open_parenthesis_idx + 1:-1]
|
||||||
|
if len(raw_arguments) > 0: # ignore methods without arguments
|
||||||
|
raw_argument_list = raw_arguments.split(',')
|
||||||
|
symbols.append({
|
||||||
|
'address': split[0],
|
||||||
|
'type': split[1], # @see Symbol Type Table
|
||||||
|
'name': split[2][:split[2].find('(')], # method name
|
||||||
|
'args': raw_argument_list
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def get_js_script(method, module_id):
|
||||||
|
js_script = """
|
||||||
|
var moduleName = "{{moduleName}}", nativeFuncAddr = {{methodAddress}};
|
||||||
|
Interceptor.attach(Module.findExportByName(null, "dlopen"), {
|
||||||
|
onEnter: function(args) {
|
||||||
|
this.lib = Memory.readUtf8String(args[0]);
|
||||||
|
console.log("[*] dlopen called with: " + this.lib);
|
||||||
|
},
|
||||||
|
onLeave: function(retval) {
|
||||||
|
if (this.lib.endsWith(moduleName)) {
|
||||||
|
Interceptor.attach(Module.findBaseAddress(moduleName).add(nativeFuncAddr), {
|
||||||
|
onEnter: function(args) {
|
||||||
|
console.log("[*] hook invoked", JSON.stringify({{arguments}}, null, '\t'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
replace_map = {
|
||||||
|
'{{moduleName}}': module_id,
|
||||||
|
'{{methodAddress}}': '0x' + method['address'],
|
||||||
|
'{{arguments}}': '{' + ''.join([switch(method['args'][i], i + 1) for i in range(len(method['args']))]) + '}'
|
||||||
|
}
|
||||||
|
for k, v in replace_map.items():
|
||||||
|
js_script = js_script.replace(k, v)
|
||||||
|
print('[+] JS Script:\n', js_script)
|
||||||
|
return js_script
|
||||||
|
|
||||||
|
|
||||||
|
def main(app_id, module_id, method):
|
||||||
|
"""
|
||||||
|
$ python3.x+ script.py --method SomeClass::someMethod --app com.company.app --module libfoo.so
|
||||||
|
:param app_id: application identifier / bundle id
|
||||||
|
:param module_id: shared object identifier / known suffix, will iterate loaded modules (@see dlopen)
|
||||||
|
:param method: method/symbol name
|
||||||
|
:return: hook native method and print arguments when invoked
|
||||||
|
"""
|
||||||
|
# TODO extract all app's modules via `adb shell -c 'ls -lR /data/app/' + app_if + '*' | grep "\.so"`
|
||||||
|
|
||||||
|
nm_stdout = list_symbols_from_object_files(module_id)
|
||||||
|
|
||||||
|
symbols = []
|
||||||
|
parse_nm_output(nm_stdout, symbols)
|
||||||
|
|
||||||
|
selection_idx = None
|
||||||
|
for idx, symbol in enumerate(symbols):
|
||||||
|
if method is None: # if --method flag is not passed
|
||||||
|
print("%4d) %s (%d)" % (idx, symbol['name'], len(symbol['args'])))
|
||||||
|
elif method == symbol['name']:
|
||||||
|
selection_idx = idx
|
||||||
|
break
|
||||||
|
if selection_idx is None:
|
||||||
|
if method is None:
|
||||||
|
selection_idx = input("Enter symbol number: ")
|
||||||
|
else:
|
||||||
|
print('[+] Method not found, remove method flag to get list of methods to select from, `nm` stdout:')
|
||||||
|
print(nm_stdout)
|
||||||
|
exit(2)
|
||||||
|
|
||||||
|
method = symbols[int(selection_idx)]
|
||||||
|
print('[+] Selected method: %s' % method['name'])
|
||||||
|
print('[+] Method arguments: %s' % method['args'])
|
||||||
|
|
||||||
|
from frida import get_usb_device
|
||||||
|
device = get_usb_device()
|
||||||
|
pid = device.spawn([app_id])
|
||||||
|
session = device.attach(pid)
|
||||||
|
script = session.create_script(get_js_script(method, module_id))
|
||||||
|
script.on('message', on_message)
|
||||||
|
script.load()
|
||||||
|
device.resume(app_id)
|
||||||
|
# keep hook alive
|
||||||
|
from sys import stdin
|
||||||
|
stdin.read()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument('--app', help='app identifier "com.company.app"')
|
||||||
|
parser.add_argument('--module', help='loaded module name "libfoo.2.so"')
|
||||||
|
parser.add_argument('--method', help='method name "SomeClass::someMethod", if empty it will print select-list')
|
||||||
|
args = parser.parse_args()
|
||||||
|
main(args.app, args.module, args.method)
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Symbol Type Table:
|
||||||
|
"A" The symbol's value is absolute, and will not be changed by further linking.
|
||||||
|
"B" The symbol is in the uninitialized data section (known as BSS).
|
||||||
|
"C" The symbol is common. Common symbols are uninitialized data.
|
||||||
|
When linking, multiple common symbols may appear with the same name.
|
||||||
|
If the symbol is defined anywhere, the common symbols are treated as undefined references.
|
||||||
|
"D" The symbol is in the initialized data section.
|
||||||
|
"G" The symbol is in an initialized data section for small objects.
|
||||||
|
Some object file formats permit more efficient access to small data objects, such as a global int variable as
|
||||||
|
opposed to a large global array.
|
||||||
|
"I" The symbol is an indirect reference to another symbol.
|
||||||
|
This is a GNU extension to the a.out object file format which is rarely used.
|
||||||
|
"N" The symbol is a debugging symbol.
|
||||||
|
"R" The symbol is in a read only data section.
|
||||||
|
"S" The symbol is in an uninitialized data section for small objects.
|
||||||
|
"T" The symbol is in the text (code) section.
|
||||||
|
"U" The symbol is undefined.
|
||||||
|
"V" The symbol is a weak object. When a weak defined symbol is linked with a normal defined symbol,
|
||||||
|
the normal defined symbol is used with no error.
|
||||||
|
When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes
|
||||||
|
zero with no error.
|
||||||
|
"W" The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
|
||||||
|
When a weak defined symbol is linked with a normal defined symbol,
|
||||||
|
the normal defined symbol is used with no error.
|
||||||
|
When a weak undefined symbol is linked and the symbol is not defined, the value of the symbol is determined
|
||||||
|
in a system-specific manner without error.
|
||||||
|
On some systems, uppercase indicates that a default value has been specified.
|
||||||
|
"-" The symbol is a stabs symbol in an a.out object file.
|
||||||
|
In this case, the next values printed are the stabs other field, the stabs desc field, and the stab type.
|
||||||
|
Stabs symbols are used to hold debugging information.
|
||||||
|
"?" The symbol type is unknown, or object file format specific.
|
||||||
|
"""
|
Loading…
Reference in New Issue
Block a user