start dynamic analysis
This commit is contained in:
parent
81c85763fd
commit
7713f3247a
6 changed files with 1868 additions and 0 deletions
1
frida/.gitignore
vendored
Normal file
1
frida/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
__pycache__
|
||||
3
frida/README.md
Normal file
3
frida/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Theseus Data Collector
|
||||
|
||||
Collect runtime information about reflection operation done by en application to feed them to the patcher.
|
||||
1655
frida/poetry.lock
generated
Normal file
1655
frida/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
23
frida/pyproject.toml
Normal file
23
frida/pyproject.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[project]
|
||||
name = "theseus-frida"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "Jean-Marie Mineau",email = "jean-marie.mineau@inria.fr"}
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13,<4.0.0"
|
||||
dependencies = [
|
||||
"frida (>=16.6.6,<17.0.0)",
|
||||
"frida-tools (>=13.6.1,<14.0.0)",
|
||||
"androguard (>=4.1.2,<5.0.0)"
|
||||
]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
collect-reflection-data = 'theseus_frida.__init__:main'
|
||||
|
||||
124
frida/theseus_frida/__init__.py
Normal file
124
frida/theseus_frida/__init__.py
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import frida # type: ignore
|
||||
from androguard.core.apk import get_apkid # type: ignore
|
||||
|
||||
FRIDA_SCRIPT = Path(__file__).parent / "hook.js"
|
||||
|
||||
|
||||
# Define handler to event generated by the scripts
|
||||
def on_message(message, data):
|
||||
if message["type"] == "error":
|
||||
print(f"[error] {message['description']}")
|
||||
print(message["stack"])
|
||||
elif message["type"] == "send" and message["payload"]["type"] == "invoke":
|
||||
handle_invoke_data(message["payload"]["data"])
|
||||
elif message["type"] == "send" and message["payload"]["type"] == "class-new-inst":
|
||||
handle_class_new_inst_data(message["payload"]["data"])
|
||||
elif message["type"] == "send" and message["payload"]["type"] == "cnstr-new-isnt":
|
||||
handle_cnstr_new_inst_data(message["payload"]["data"])
|
||||
else:
|
||||
print("[on_message] message:", message)
|
||||
|
||||
|
||||
def get_ty(java_name: str) -> str:
|
||||
"""Return the android name from the java name of a class / type"""
|
||||
# TODO: array
|
||||
# TODO: scalar
|
||||
if java_name == "V": # tmp stub
|
||||
return "V"
|
||||
return f"L{java_name.replace('.', '/')};"
|
||||
|
||||
|
||||
def get_method_id(method_data) -> str:
|
||||
"""Get a method descriptor from the different elements collected from the methods."""
|
||||
name = method_data["name"]
|
||||
ret = get_ty(method_data["ret"])
|
||||
cls = get_ty(method_data["class"])
|
||||
args = "".join(map(get_ty, method_data["args"]))
|
||||
return f"{cls}->{name}({args}){ret}"
|
||||
|
||||
|
||||
def handle_invoke_data(data):
|
||||
method = get_method_id(data["method"])
|
||||
caller_method = "?" # get_method_id(data["caller_method"])
|
||||
addr = data["addr"]
|
||||
print("Method.Invoke:")
|
||||
print(f" called: {method}")
|
||||
print(f" by: {caller_method}")
|
||||
print(f" at: 0x{addr:08x}")
|
||||
|
||||
|
||||
def handle_class_new_inst_data(data):
|
||||
constructor = get_method_id(data["constructor"])
|
||||
caller_method = "?" # get_method_id(data["caller_method"])
|
||||
addr = data["addr"]
|
||||
print("Class.NewInstance:")
|
||||
print(f" called: {constructor}")
|
||||
print(f" by: {caller_method}")
|
||||
print(f" at: 0x{addr:08x}")
|
||||
|
||||
|
||||
def handle_cnstr_new_inst_data(data):
|
||||
constructor = get_method_id(data["constructor"])
|
||||
caller_method = "?" # get_method_id(data["caller_method"])
|
||||
addr = data["addr"]
|
||||
print("Constructor.newInstance:")
|
||||
print(f" called: {constructor}")
|
||||
print(f" by: {caller_method}")
|
||||
print(f" at: 0x{addr:08x}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="Android Theseus project",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-a", "--apk", required=True, help="Target application", type=Path
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--device",
|
||||
default="",
|
||||
help="The android device to connect to, eg: 'emulator-5554'",
|
||||
type=str,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
env = dict(os.environ)
|
||||
|
||||
if args.device != "":
|
||||
device = frida.get_device(args.device)
|
||||
env["ANDROID_SERIAL"] = args.device
|
||||
else:
|
||||
device = frida.get_usb_device()
|
||||
|
||||
app = get_apkid(args.apk)[0]
|
||||
|
||||
if device.enumerate_applications([app]):
|
||||
# Uninstall the APK if it already exist
|
||||
subprocess.run(["adb", "uninstall", app], env=env)
|
||||
subprocess.run(["adb", "install", str(args.apk.absolute())], env=env)
|
||||
|
||||
with FRIDA_SCRIPT.open("r") as file:
|
||||
script = file.read()
|
||||
|
||||
pid = device.spawn([app])
|
||||
session = device.attach(pid)
|
||||
script = session.create_script(script)
|
||||
|
||||
script.on(
|
||||
"message",
|
||||
on_message,
|
||||
)
|
||||
|
||||
# Load script
|
||||
script.load()
|
||||
# Resume the execution of the APK
|
||||
device.resume(pid)
|
||||
|
||||
print("Press ENTER to finish the analysis")
|
||||
input()
|
||||
62
frida/theseus_frida/hook.js
Normal file
62
frida/theseus_frida/hook.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
Java.perform(() => {
|
||||
|
||||
const Method = Java.use("java.lang.reflect.Method");
|
||||
const Class = Java.use("java.lang.Class");
|
||||
const Constructor = Java.use("java.lang.reflect.Constructor");
|
||||
Method.invoke.overload(
|
||||
"java.lang.Object", "[Ljava.lang.Object;" // the Frida type parser is so cursted...
|
||||
).implementation = function (obj, args) {
|
||||
send({
|
||||
"type": "invoke",
|
||||
"data": {
|
||||
"method": {
|
||||
"name": this.getName(),
|
||||
"class": this.getDeclaringClass().getName(),
|
||||
"args": this.getParameterTypes().map((argty) => argty.getName() ),
|
||||
"ret": this.getReturnType().getName(),
|
||||
},
|
||||
"caller_method": "?",
|
||||
"addr": 0,
|
||||
}
|
||||
});
|
||||
return this.invoke(obj, args);
|
||||
};
|
||||
Class.newInstance.overload(
|
||||
).implementation = function () {
|
||||
send({
|
||||
"type": "class-new-inst",
|
||||
"data": {
|
||||
"constructor": {
|
||||
"name": "<init>",
|
||||
"class": this.getName(),
|
||||
"args": [],
|
||||
"ret": "V",
|
||||
},
|
||||
"caller_method": "?",
|
||||
"addr": 0,
|
||||
}
|
||||
});
|
||||
return this.newInstance();
|
||||
};
|
||||
Constructor.newInstance.overload(
|
||||
"[Ljava.lang.Object;"
|
||||
).implementation = function (args) {
|
||||
send({
|
||||
"type": "cnstr-new-isnt",
|
||||
"data": {
|
||||
"constructor": {
|
||||
"name": "<init>",
|
||||
"class": this.getDeclaringClass().getName(),
|
||||
"args": this.getParameterTypes().map((argty) => argty.getName()),
|
||||
"ret": "V",
|
||||
},
|
||||
"caller_method": "?",
|
||||
"addr": 0,
|
||||
}
|
||||
});
|
||||
return this.newInstance(args);
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue