scripts directory
This commit is contained in:
parent
b7ad44fc54
commit
05e330fcae
@ -8,7 +8,7 @@
|
|||||||
- [Hook JNI by address](#hook-jni-by-address)
|
- [Hook JNI by address](#hook-jni-by-address)
|
||||||
- [Print all runtime strings & Stacktrace](#print-runtime-strings)
|
- [Print all runtime strings & Stacktrace](#print-runtime-strings)
|
||||||
- [Find iOS application UUID](#find-ios-application-uuid)
|
- [Find iOS application UUID](#find-ios-application-uuid)
|
||||||
- [Execute shell command](https://github.com/iddoeldor/frida-snippets/blob/master/exec_shell_cmd.py)
|
- [Execute shell command](https://github.com/iddoeldor/frida-snippets/blob/master/scripts/exec_shell_cmd.py)
|
||||||
- [TODO list](#todos)
|
- [TODO list](#todos)
|
||||||
|
|
||||||
#### Enumerate loaded classes
|
#### Enumerate loaded classes
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
# 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()
|
|
@ -1,119 +0,0 @@
|
|||||||
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()
|
|
@ -1,75 +0,0 @@
|
|||||||
"""
|
|
||||||
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)
|
|
@ -1,73 +0,0 @@
|
|||||||
### 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)
|
|
||||||
```
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
#!/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 &"
|
|
@ -1,23 +0,0 @@
|
|||||||
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;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
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