intercept code loading
This commit is contained in:
parent
8e03afd4ae
commit
b906988141
2 changed files with 198 additions and 3 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
|
import base64
|
||||||
import os
|
import os
|
||||||
|
import hashlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
|
@ -16,7 +18,7 @@ STACK_CONSUMER_B64 = Path(__file__).parent / "StackConsumer.dex.b64"
|
||||||
|
|
||||||
|
|
||||||
# Define handler to event generated by the scripts
|
# Define handler to event generated by the scripts
|
||||||
def on_message(message, data, data_storage: dict):
|
def on_message(message, data, data_storage: dict, file_storage: Path):
|
||||||
if message["type"] == "error":
|
if message["type"] == "error":
|
||||||
print(f"[error] {message['description']}")
|
print(f"[error] {message['description']}")
|
||||||
print(message["stack"])
|
print(message["stack"])
|
||||||
|
|
@ -26,6 +28,8 @@ def on_message(message, data, data_storage: dict):
|
||||||
handle_class_new_inst_data(message["payload"]["data"], data_storage)
|
handle_class_new_inst_data(message["payload"]["data"], data_storage)
|
||||||
elif message["type"] == "send" and message["payload"]["type"] == "cnstr-new-isnt":
|
elif message["type"] == "send" and message["payload"]["type"] == "cnstr-new-isnt":
|
||||||
handle_cnstr_new_inst_data(message["payload"]["data"], data_storage)
|
handle_cnstr_new_inst_data(message["payload"]["data"], data_storage)
|
||||||
|
elif message["type"] == "send" and message["payload"]["type"] == "load-dex":
|
||||||
|
handle_load_dex(message["payload"]["data"], data_storage, file_storage)
|
||||||
else:
|
else:
|
||||||
print("[on_message] message:", message)
|
print("[on_message] message:", message)
|
||||||
|
|
||||||
|
|
@ -130,6 +134,43 @@ def handle_cnstr_new_inst_data(data, data_storage: dict):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_load_dex(data, data_storage: dict, file_storage: Path):
|
||||||
|
dex = data["dex"]
|
||||||
|
classloader_class = data["classloader_class"]
|
||||||
|
classloader = data["classloader"]
|
||||||
|
short_class = classloader_class.split("/")[-1].removesuffix(";")
|
||||||
|
files = []
|
||||||
|
print("DEX file loaded:")
|
||||||
|
print(f" by: {classloader_class} ({classloader})")
|
||||||
|
for file in dex:
|
||||||
|
print(f"{file=}")
|
||||||
|
file_bin = base64.b64decode(file)
|
||||||
|
hasher = hashlib.sha1()
|
||||||
|
hasher.update(file_bin)
|
||||||
|
h = hasher.digest().hex()
|
||||||
|
print(f" hash: {h}")
|
||||||
|
fname = (
|
||||||
|
file_storage / f"{short_class}_{classloader}_{h[:16]}.bytecode"
|
||||||
|
) # not .dex, can also be .jar or .apk or .oat or ...
|
||||||
|
i = 1
|
||||||
|
while fname.exists():
|
||||||
|
fname = file_storage / f"{short_class}_{classloader}_{h[:16]}_{i}.bytecode"
|
||||||
|
i += 1
|
||||||
|
fname = fname.absolute().resolve()
|
||||||
|
|
||||||
|
with fname.open("wb") as fp:
|
||||||
|
fp.write(file_bin)
|
||||||
|
print(f" stored: {str(fname)}")
|
||||||
|
files.append(str(fname))
|
||||||
|
data_storage["dyn_code_load"].append(
|
||||||
|
{
|
||||||
|
"classloader_class": classloader_class,
|
||||||
|
"classloader": classloader,
|
||||||
|
"files": files,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="Android Theseus project",
|
prog="Android Theseus project",
|
||||||
|
|
@ -151,9 +192,23 @@ def main():
|
||||||
help="where to dump the collected data, default is stdout",
|
help="where to dump the collected data, default is stdout",
|
||||||
type=Path,
|
type=Path,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-d",
|
||||||
|
"--dex-dir",
|
||||||
|
default=Path("."),
|
||||||
|
help="where to store dynamically loaded bytecode",
|
||||||
|
type=Path,
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
env = dict(os.environ)
|
env = dict(os.environ)
|
||||||
|
|
||||||
|
file_storage = args.dex_dir
|
||||||
|
if not file_storage.exists():
|
||||||
|
file_storage.mkdir(parents=True)
|
||||||
|
if not file_storage.is_dir():
|
||||||
|
print("--dex-dir must be a directory")
|
||||||
|
exit()
|
||||||
|
|
||||||
if args.device != "":
|
if args.device != "":
|
||||||
device = frida.get_device(args.device)
|
device = frida.get_device(args.device)
|
||||||
env["ANDROID_SERIAL"] = args.device
|
env["ANDROID_SERIAL"] = args.device
|
||||||
|
|
@ -177,17 +232,26 @@ def main():
|
||||||
|
|
||||||
pid = device.spawn([app])
|
pid = device.spawn([app])
|
||||||
session = device.attach(pid)
|
session = device.attach(pid)
|
||||||
script = session.create_script(script)
|
try:
|
||||||
|
script = session.create_script(script)
|
||||||
|
except frida.InvalidArgumentError as e:
|
||||||
|
print(
|
||||||
|
"\n".join(
|
||||||
|
map(lambda v: f"{v[0]+1: 3} {v[1]}", enumerate(script.split("\n")))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
data_storage = {
|
data_storage = {
|
||||||
"invoke_data": [],
|
"invoke_data": [],
|
||||||
"class_new_inst_data": [],
|
"class_new_inst_data": [],
|
||||||
"cnstr_new_inst_data": [],
|
"cnstr_new_inst_data": [],
|
||||||
|
"dyn_code_load": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
script.on(
|
script.on(
|
||||||
"message",
|
"message",
|
||||||
lambda msg, data: on_message(msg, data, data_storage),
|
lambda msg, data: on_message(msg, data, data_storage, file_storage),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load script
|
# Load script
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,17 @@ Java.perform(() => {
|
||||||
const Class = Java.use("java.lang.Class");
|
const Class = Java.use("java.lang.Class");
|
||||||
const Constructor = Java.use("java.lang.reflect.Constructor");
|
const Constructor = Java.use("java.lang.reflect.Constructor");
|
||||||
const Modifier = Java.use("java.lang.reflect.Modifier");
|
const Modifier = Java.use("java.lang.reflect.Modifier");
|
||||||
|
const DexFile = Java.use("dalvik.system.DexFile");
|
||||||
|
|
||||||
|
const File = Java.use('java.io.File');
|
||||||
|
const Files = Java.use('java.nio.file.Files');
|
||||||
|
const Path = Java.use('java.nio.file.Path');
|
||||||
|
const System = Java.use('java.lang.System');
|
||||||
|
const Arrays = Java.use('java.util.Arrays');
|
||||||
|
|
||||||
|
// ****** Reflexive Method Calls ******
|
||||||
|
|
||||||
|
// Method.invoke(obj, ..args)
|
||||||
Method.invoke.overload(
|
Method.invoke.overload(
|
||||||
"java.lang.Object", "[Ljava.lang.Object;" // the Frida type parser is so cursted...
|
"java.lang.Object", "[Ljava.lang.Object;" // the Frida type parser is so cursted...
|
||||||
).implementation = function (obj, args) {
|
).implementation = function (obj, args) {
|
||||||
|
|
@ -87,6 +98,10 @@ Java.perform(() => {
|
||||||
});
|
});
|
||||||
return this.invoke(obj, args);
|
return this.invoke(obj, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ****** Reflexive Class Instantiation ******
|
||||||
|
|
||||||
|
// Class.newInstance()
|
||||||
Class.newInstance.overload(
|
Class.newInstance.overload(
|
||||||
).implementation = function () {
|
).implementation = function () {
|
||||||
send({
|
send({
|
||||||
|
|
@ -106,6 +121,7 @@ Java.perform(() => {
|
||||||
});
|
});
|
||||||
return this.newInstance();
|
return this.newInstance();
|
||||||
};
|
};
|
||||||
|
// Constructor.newInstance(..args)
|
||||||
Constructor.newInstance.overload(
|
Constructor.newInstance.overload(
|
||||||
"[Ljava.lang.Object;"
|
"[Ljava.lang.Object;"
|
||||||
).implementation = function (args) {
|
).implementation = function (args) {
|
||||||
|
|
@ -129,5 +145,120 @@ Java.perform(() => {
|
||||||
return this.newInstance(args);
|
return this.newInstance(args);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ****** Dynamic Class Loading ******
|
||||||
|
|
||||||
|
// DexFile.openDexFileNative(sourceName, outputName, flags, loader, elements): load .dex from file
|
||||||
|
// See https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;drc=2f8a31e93fc238a88a48bfeed82557e07e1d5003;l=477
|
||||||
|
// https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/native/dalvik_system_DexFile.cc;drc=3d19fbcc09b1b44928639b06cd0b88f735cd988d;l=368
|
||||||
|
DexFile.openDexFileNative.overload(
|
||||||
|
'java.lang.String',
|
||||||
|
'java.lang.String',
|
||||||
|
'int',
|
||||||
|
'java.lang.ClassLoader',
|
||||||
|
'[Ldalvik.system.DexPathList$Element;',
|
||||||
|
).implementation = function (
|
||||||
|
sourceName,
|
||||||
|
outputName,
|
||||||
|
flags,
|
||||||
|
loader,
|
||||||
|
elements,
|
||||||
|
) {
|
||||||
|
let file = File.$new(sourceName);
|
||||||
|
|
||||||
|
let path = Path.of(sourceName, []);
|
||||||
|
let dex = Files.readAllBytes(path);
|
||||||
|
let b64 = Base64.encodeToString(dex, Base64.DEFAULT.value);
|
||||||
|
let classloader_class = "";
|
||||||
|
let classloader_id = System.identityHashCode(loader);
|
||||||
|
if (loader !== null) {
|
||||||
|
classloader_class = loader.getClass().descriptorString();
|
||||||
|
}
|
||||||
|
send({
|
||||||
|
"type": "load-dex",
|
||||||
|
"data": {
|
||||||
|
"dex": [b64],
|
||||||
|
"classloader_class": classloader_class,
|
||||||
|
"classloader": classloader_id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let is_wr = file.canWrite();
|
||||||
|
if (is_wr) {
|
||||||
|
file.setReadOnly();
|
||||||
|
}
|
||||||
|
let result = this.openDexFileNative(
|
||||||
|
sourceName,
|
||||||
|
outputName,
|
||||||
|
flags,
|
||||||
|
loader,
|
||||||
|
elements,
|
||||||
|
);
|
||||||
|
/* TODO: FIX
|
||||||
|
if (is_wr) {
|
||||||
|
file.setWritable(true, false);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
// DexFile.openInMemoryDexFilesNative(bufs, arrays, starts, ends, loader,elements): load .dex from memory
|
||||||
|
// See https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;drc=2f8a31e93fc238a88a48bfeed82557e07e1d5003;l=431
|
||||||
|
// https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/native/dalvik_system_DexFile.cc;l=253;drc=3d19fbcc09b1b44928639b06cd0b88f735cd988d
|
||||||
|
DexFile.openInMemoryDexFilesNative.overload(
|
||||||
|
'[Ljava.nio.ByteBuffer;',
|
||||||
|
'[[B',
|
||||||
|
'[I',
|
||||||
|
'[I',
|
||||||
|
'java.lang.ClassLoader',
|
||||||
|
'[Ldalvik.system.DexPathList$Element;',
|
||||||
|
).implementation = function (
|
||||||
|
bufs,
|
||||||
|
arrays,
|
||||||
|
starts,
|
||||||
|
ends,
|
||||||
|
loader,
|
||||||
|
elements,
|
||||||
|
) {
|
||||||
|
let dex = [];
|
||||||
|
// openInMemoryDexFilesNative() checks bufs.length == arrays.length == starts.length === ends.length
|
||||||
|
for (let i = 0; i < bufs.length; i++) {
|
||||||
|
let s = starts[i];
|
||||||
|
let e = starts[i];
|
||||||
|
// openInMemoryDexFilesNative() checks s < e
|
||||||
|
let array = arrays[i];
|
||||||
|
let buf = bufs[i];
|
||||||
|
let raw = [];
|
||||||
|
// match code from art/runtime/native/dalvik_system_DexFile.cc commit 3d19fbcc09b1b44928639b06cd0b88f735cd988d
|
||||||
|
if (array === null) {
|
||||||
|
raw = Arrays.copyOf([], e-s);
|
||||||
|
raw = buf.get(s, raw, 0, e-s);
|
||||||
|
} else {
|
||||||
|
raw = Arrays.copyOfRange(array, s, e);
|
||||||
|
}
|
||||||
|
let b64 = Base64.encodeToString(raw, Base64.DEFAULT.value);
|
||||||
|
dex.push(b64);
|
||||||
|
}
|
||||||
|
|
||||||
|
let classloader_class = "";
|
||||||
|
let classloader_id = System.identityHashCode(loader);
|
||||||
|
if (loader !== null) {
|
||||||
|
classloader_class = loader.getClass().descriptorString();
|
||||||
|
}
|
||||||
|
send({
|
||||||
|
"type": "load-dex",
|
||||||
|
"data": {
|
||||||
|
"dex": dex,
|
||||||
|
"classloader_class": classloader_class,
|
||||||
|
"classloader": classloader_id,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return this.openInMemoryDexFilesNative(
|
||||||
|
bufs,
|
||||||
|
arrays,
|
||||||
|
starts,
|
||||||
|
ends,
|
||||||
|
loader,
|
||||||
|
elements,
|
||||||
|
);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue