wip compare class laoder

This commit is contained in:
Jean-Marie Mineau 2025-05-05 17:31:23 +02:00
parent 59d6caabd8
commit 1884ff4ac8
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
7 changed files with 326 additions and 108 deletions

View file

@ -59,6 +59,8 @@ def on_message(message, data, data_storage: dict, file_storage: Path):
elif message["type"] == "send" and message["payload"]["type"] == "classloader-done": elif message["type"] == "send" and message["payload"]["type"] == "classloader-done":
global CLASSLOADER_DONE global CLASSLOADER_DONE
CLASSLOADER_DONE = True CLASSLOADER_DONE = True
elif message["type"] == "send" and message["payload"]["type"] == "app_info":
handle_app_info(message["payload"]["data"], data_storage)
else: else:
print("[-] message:", message) print("[-] message:", message)
@ -234,6 +236,14 @@ def handle_load_dex(data, data_storage: dict, file_storage: Path):
) )
def handle_app_info(data, data_storage: dict):
data["actualSourceDir"] = data["sourceDir"].removesuffix("/base.apk")
data_storage["app_info"] = data
print("[+] Received app info:")
for k in data.keys():
print(f" {k}: {data[k]}")
def setup_frida(device_name: str, env: dict[str, str], adb: str) -> frida.core.Device: def setup_frida(device_name: str, env: dict[str, str], adb: str) -> frida.core.Device:
if device_name != "": if device_name != "":
device = frida.get_device(device_name) device = frida.get_device(device_name)
@ -424,7 +434,7 @@ def collect_runtime(
cls = {} cls = {}
for cl in data_storage["classloaders"]: for cl in data_storage["classloaders"]:
# This is verry doubious # This is verry doubious
if cl["cname"] == "dalvik.system.PathClassLoader": if cl["cname"] == "Ldalvik/system/PathClassLoader;":
zip_files = list( zip_files = list(
map( map(
lambda s: s.removeprefix('zip file "').removesuffix('"'), lambda s: s.removeprefix('zip file "').removesuffix('"'),

View file

@ -1,6 +1,7 @@
const sended_class_loaders = new Set(); const sended_class_loaders = new Set();
function send_class_loader(cl) { function send_class_loader(cl) {
get_app_info();
const System = Java.use('java.lang.System'); const System = Java.use('java.lang.System');
let cl_id = System.identityHashCode(cl); let cl_id = System.identityHashCode(cl);
while (cl != null && !sended_class_loaders.has(cl_id)) { while (cl != null && !sended_class_loaders.has(cl_id)) {
@ -9,7 +10,7 @@ function send_class_loader(cl) {
"id": cl_id, "id": cl_id,
"parent_id": System.identityHashCode(parent_), "parent_id": System.identityHashCode(parent_),
"str": cl.toString(), "str": cl.toString(),
"cname": cl.$className "cname": cl.getClass().descriptorString()
}}); }});
sended_class_loaders.add(cl_id); sended_class_loaders.add(cl_id);
cl = parent_; cl = parent_;
@ -26,6 +27,37 @@ function dump_classloaders() {
}); });
} }
let info_sent = false
function get_app_info() {
if (info_sent) {
return;
}
var app = Java.use('android.app.ActivityThread').currentApplication();
if (app == null) {
return;
}
var context = app.getApplicationContext();
if (context == null) {
return;
}
var appinfo = context.getApplicationInfo();
if (appinfo == null) {
return;
}
send({"type": "app_info", "data": {
"dataDir": appinfo.dataDir.value,
"deviceProtectedDataDir": appinfo.deviceProtectedDataDir.value,
"nativeLibraryDir": appinfo.nativeLibraryDir.value,
"publicSourceDir": appinfo.publicSourceDir.value,
"sharedLibraryFiles": appinfo.sharedLibraryFiles.value,
"sourceDir": appinfo.sourceDir.value,
"splitNames": appinfo.splitNames.value,
"splitPublicSourceDirs": appinfo.splitPublicSourceDirs.value,
"splitSourceDirs": appinfo.splitSourceDirs.value,
}});
info_sent = true;
}
/* ----- Frida Native Class Loading ----- /* ----- Frida Native Class Loading -----
* Broken, for some ineffable frida-android reason. * Broken, for some ineffable frida-android reason.
function registerStackConsumer() { function registerStackConsumer() {
@ -117,6 +149,8 @@ Java.perform(() => {
const System = Java.use('java.lang.System'); const System = Java.use('java.lang.System');
*/ */
const StackWalker = Java.use('java.lang.StackWalker'); const StackWalker = Java.use('java.lang.StackWalker');
const StackWalkerOptions = Java.use('java.lang.StackWalker$Option'); const StackWalkerOptions = Java.use('java.lang.StackWalker$Option');
const StackWalkerOptionsShowHidden = StackWalkerOptions.valueOf("SHOW_HIDDEN_FRAMES"); const StackWalkerOptionsShowHidden = StackWalkerOptions.valueOf("SHOW_HIDDEN_FRAMES");
@ -304,7 +338,8 @@ Java.perform(() => {
let classloader_class = null; let classloader_class = null;
let classloader_id = System.identityHashCode(loader); let classloader_id = System.identityHashCode(loader);
if (loader !== null) { if (loader !== null) {
send_class_loader(loader); // send_class_loader(loader); // Sending names before the end of the initialization
// collect the wrong string representation !
classloader_class = loader.getClass().descriptorString(); classloader_class = loader.getClass().descriptorString();
} }
send({ send({

View file

@ -102,15 +102,27 @@ pub(crate) static SCAL_TO_OBJ_DOUBLE: LazyLock<IdMethod> = LazyLock::new(|| {
pub(crate) static GET_CLASS_LOADER: LazyLock<IdMethod> = LazyLock::new(|| { pub(crate) static GET_CLASS_LOADER: LazyLock<IdMethod> = LazyLock::new(|| {
IdMethod::from_smali("Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;").unwrap() IdMethod::from_smali("Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;").unwrap()
}); });
pub(crate) static GET_PARENT: LazyLock<IdMethod> = LazyLock::new(|| {
IdMethod::from_smali("Ljava/lang/ClassLoader;->getParent()Ljava/lang/ClassLoader;").unwrap()
});
pub(crate) static GET_CLASS: LazyLock<IdMethod> = LazyLock::new(|| {
IdMethod::from_smali("Ljava/lang/Object;->getClass()Ljava/lang/Class;").unwrap()
});
pub(crate) static TO_STRING: LazyLock<IdMethod> = LazyLock::new(|| { pub(crate) static TO_STRING: LazyLock<IdMethod> = LazyLock::new(|| {
IdMethod::from_smali("Ljava/lang/Object;->toString()Ljava/lang/String;").unwrap() IdMethod::from_smali("Ljava/lang/Object;->toString()Ljava/lang/String;").unwrap()
}); });
pub(crate) static BOOT_CLASS_LOADER_TY: LazyLock<IdType> =
LazyLock::new(|| IdType::from_smali("Ljava/lang/BootClassLoader;").unwrap());
pub(crate) static OBJECT_TY: LazyLock<IdType> = pub(crate) static OBJECT_TY: LazyLock<IdType> =
LazyLock::new(|| IdType::from_smali("Ljava/lang/Object;").unwrap()); LazyLock::new(|| IdType::from_smali("Ljava/lang/Object;").unwrap());
pub(crate) static DELEGATE_LAST_CLASS_LOADER: LazyLock<IdType> = pub(crate) static DELEGATE_LAST_CLASS_LOADER: LazyLock<IdType> =
LazyLock::new(|| IdType::from_smali("Ldalvik/system/DelegateLastClassLoader;").unwrap()); LazyLock::new(|| IdType::from_smali("Ldalvik/system/DelegateLastClassLoader;").unwrap());
pub(crate) static LOG_INFO: LazyLock<IdMethod> = LazyLock::new(|| {
IdMethod::from_smali("Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I").unwrap()
});
/// Get the method that convert a object to its scalar conterpart (eg `java.lang.Integer` to `int` with /// Get the method that convert a object to its scalar conterpart (eg `java.lang.Integer` to `int` with
/// `Ljava/lang/Integer;->intValue()I`) /// `Ljava/lang/Integer;->intValue()I`)
/// ///

View file

@ -8,6 +8,8 @@ use std::hash::{DefaultHasher, Hash, Hasher};
use crate::{dex_types::*, register_manipulation::*, runtime_data::*}; use crate::{dex_types::*, register_manipulation::*, runtime_data::*};
const DEBUG: bool = false;
// Interesting stuff: https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/reg_type.h;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=94 // Interesting stuff: https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/reg_type.h;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=94
// https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=5328 // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=83db0626fad8c6e0508754fffcbbd58e539d14a5;l=5328
/// `meth`: the method that make reflectif calls. This is the method to patch. /// `meth`: the method that make reflectif calls. This is the method to patch.
@ -340,11 +342,12 @@ fn gen_tester_method(
tester_methods_class: IdType, tester_methods_class: IdType,
method_to_test: IdMethod, method_to_test: IdMethod,
is_constructor: bool, is_constructor: bool,
classloader: Option<&ClassLoaderData>, classloader: Option<String>,
classloaders: &HashMap<String, ClassLoaderData>,
) -> Result<Method> { ) -> Result<Method> {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
if let Some(classloader) = classloader { if let Some(ref id) = classloader {
classloader.id.hash(&mut hasher); id.hash(&mut hasher);
} else { } else {
"00000000".hash(&mut hasher); "00000000".hash(&mut hasher);
} }
@ -363,8 +366,9 @@ fn gen_tester_method(
} }
}; };
let method_test_name = format!("check_is_{c_name}_{m_name}_{hash:016x}");
let descriptor = IdMethod::new( let descriptor = IdMethod::new(
format!("check_is_{c_name}_{m_name}_{hash:016x}").into(), method_test_name.as_str().into(),
IdMethodType::new( IdMethodType::new(
IdType::boolean(), IdType::boolean(),
vec![if is_constructor { vec![if is_constructor {
@ -379,8 +383,11 @@ fn gen_tester_method(
let no_label: String = "lable_no".into(); let no_label: String = "lable_no".into();
let reg_arr = 0; let reg_arr = 0;
let reg_arr_idx = 1; let reg_arr_idx = 1;
let reg_arr_val = 2; let reg_tst_val = 2;
let reg_ref_method = 3; let reg_def_type = 3;
let reg_cmp_val = 4;
let reg_class_loader = 5;
let reg_ref_method = 6;
// Check for arg type // Check for arg type
let mut insns = if !is_constructor { let mut insns = if !is_constructor {
vec![ vec![
@ -407,12 +414,12 @@ fn gen_tester_method(
arr: reg_arr, arr: reg_arr,
}, },
Instruction::Const { Instruction::Const {
reg: reg_arr_val, reg: reg_tst_val,
lit: method_to_test.proto.get_parameters().len() as i32, lit: method_to_test.proto.get_parameters().len() as i32,
}, },
Instruction::IfNe { Instruction::IfNe {
a: reg_arr_idx, a: reg_arr_idx,
b: reg_arr_val, b: reg_tst_val,
label: no_label.clone(), label: no_label.clone(),
}, },
]); ]);
@ -428,40 +435,38 @@ fn gen_tester_method(
lit: i as i32, lit: i as i32,
}); });
insns.push(Instruction::AGetObject { insns.push(Instruction::AGetObject {
dest: reg_arr_val, dest: reg_tst_val,
arr: reg_arr, arr: reg_arr,
idx: reg_arr_idx, idx: reg_arr_idx,
}); });
insns.push(Instruction::ConstClass { insns.push(Instruction::ConstClass {
reg: reg_arr_idx, // wrong name, but available for tmp val reg: reg_cmp_val,
lit: param, lit: param,
}); });
insns.push(Instruction::InvokeVirtual { insns.push(Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(), method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_arr_idx as u16], args: vec![reg_cmp_val as u16],
}); });
insns.push(Instruction::MoveResultObject { to: reg_arr_idx }); insns.push(Instruction::MoveResultObject { to: reg_cmp_val });
insns.push(Instruction::InvokeVirtual { insns.push(Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(), method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_arr_val as u16], args: vec![reg_tst_val as u16],
}); });
insns.push(Instruction::MoveResultObject { to: reg_arr_val }); insns.push(Instruction::MoveResultObject { to: reg_tst_val });
insns.push(Instruction::InvokeVirtual { insns.push(Instruction::InvokeVirtual {
method: STR_EQ.clone(), method: STR_EQ.clone(),
args: vec![reg_arr_idx as u16, reg_arr_val as u16], args: vec![reg_cmp_val as u16, reg_tst_val as u16],
});
insns.push(Instruction::MoveResult {
to: reg_arr_idx, // wrong name, but available for tmp val
}); });
insns.push(Instruction::MoveResult { to: reg_cmp_val });
insns.push(Instruction::IfEqZ { insns.push(Instruction::IfEqZ {
a: reg_arr_idx, a: reg_cmp_val,
label: no_label.clone(), label: no_label.clone(),
}); });
// Comparing Type does not work when different types share the same name (eg type from // Comparing Type does not work when different types share the same name (eg type from
// another class loader) // another class loader)
//insns.push(Instruction::IfNe { //insns.push(Instruction::IfNe {
// a: reg_arr_idx, // a: reg_arr_idx,
// b: reg_arr_val, // b: reg_tst_val,
// label: no_label.clone(), // label: no_label.clone(),
//}) //})
} }
@ -473,22 +478,18 @@ fn gen_tester_method(
method: MTH_GET_NAME.clone(), method: MTH_GET_NAME.clone(),
args: vec![reg_ref_method], args: vec![reg_ref_method],
}, },
Instruction::MoveResultObject { Instruction::MoveResultObject { to: reg_tst_val },
to: reg_arr_idx, // wrong name, but available for tmp val
},
Instruction::ConstString { Instruction::ConstString {
reg: reg_arr_val, // wrong name, but available for tmp val reg: reg_cmp_val,
lit: method_to_test.name.clone(), lit: method_to_test.name.clone(),
}, },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: STR_EQ.clone(), method: STR_EQ.clone(),
args: vec![reg_arr_idx as u16, reg_arr_val as u16], args: vec![reg_tst_val as u16, reg_cmp_val as u16],
},
Instruction::MoveResult {
to: reg_arr_idx, // wrong name, but available for tmp val
}, },
Instruction::MoveResult { to: reg_cmp_val },
Instruction::IfEqZ { Instruction::IfEqZ {
a: reg_arr_idx, a: reg_cmp_val,
label: no_label.clone(), label: no_label.clone(),
}, },
// Check Return Type // Check Return Type
@ -496,39 +497,35 @@ fn gen_tester_method(
method: MTH_GET_RET_TY.clone(), method: MTH_GET_RET_TY.clone(),
args: vec![reg_ref_method], args: vec![reg_ref_method],
}, },
Instruction::MoveResultObject { Instruction::MoveResultObject { to: reg_tst_val },
to: reg_arr_idx, // wrong name, but available for tmp val Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_tst_val as u16],
}, },
Instruction::MoveResultObject { to: reg_tst_val },
Instruction::ConstClass { Instruction::ConstClass {
reg: reg_arr_val, // wrong name, but available for tmp val reg: reg_cmp_val,
lit: method_to_test.proto.get_return_type(), lit: method_to_test.proto.get_return_type(),
}, },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(), method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_arr_idx as u16], args: vec![reg_cmp_val as u16],
}, },
Instruction::MoveResultObject { to: reg_arr_idx }, Instruction::MoveResultObject { to: reg_cmp_val },
Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_arr_val as u16],
},
Instruction::MoveResultObject { to: reg_arr_val },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: STR_EQ.clone(), method: STR_EQ.clone(),
args: vec![reg_arr_idx as u16, reg_arr_val as u16], args: vec![reg_cmp_val as u16, reg_tst_val as u16],
},
Instruction::MoveResult {
to: reg_arr_idx, // wrong name, but available for tmp val
}, },
Instruction::MoveResult { to: reg_cmp_val },
Instruction::IfEqZ { Instruction::IfEqZ {
a: reg_arr_idx, a: reg_cmp_val,
label: no_label.clone(), label: no_label.clone(),
}, },
// Comparing Type does not work when different types share the same name (eg type from // Comparing Type does not work when different types share the same name (eg type from
// another class loader) // another class loader)
//Instruction::IfNe { //Instruction::IfNe {
// a: reg_arr_idx, // a: reg_arr_idx,
// b: reg_arr_val, // b: reg_tst_val,
// label: no_label.clone(), // label: no_label.clone(),
//}, //},
]); ]);
@ -545,109 +542,211 @@ fn gen_tester_method(
args: vec![reg_ref_method], args: vec![reg_ref_method],
}); });
} }
insns.push(Instruction::MoveResultObject { to: reg_def_type });
//Check the classloader //Check the classloader
if let Some(classloader) = classloader { let mut current_classloader = classloader.as_ref().and_then(|id| classloaders.get(id));
let check_class_loader = current_classloader.is_some();
if check_class_loader {
insns.append(&mut vec![ insns.append(&mut vec![
// Get the string representation of the classloader. // Get the string representation of the classloader.
// Not the ideal, but best cross execution classloader identifier we have. // Not the ideal, but best cross execution classloader identifier we have.
Instruction::MoveResultObject {
to: reg_arr_idx, // wrong name, but available for tmp val
},
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: GET_CLASS_LOADER.clone(), method: GET_CLASS_LOADER.clone(),
args: vec![reg_arr_idx as u16], args: vec![reg_def_type as u16],
},
Instruction::MoveResultObject { to: reg_arr_idx },
Instruction::InvokeVirtual {
method: TO_STRING.clone(),
args: vec![reg_arr_idx as u16],
}, },
Instruction::MoveResultObject { Instruction::MoveResultObject {
to: reg_arr_idx, // wrong name, but available for tmp val to: reg_class_loader,
}, },
]);
}
while let Some(classloader) = current_classloader {
if classloader.cname == *BOOT_CLASS_LOADER_TY {
// Ljava/lang/BootClassLoader; is complicated.
// It's string rep is "java.lang.BootClassLoader@7e2aeab" where "7e2aeab" is it's
// runtime hash id: the name change at each run. We need to compare with its type (it's
// ok, it's supposed to be a singleton).
// Also, it can be represented at runtime by the null pointer, so we need to accept the
// null pointer as a valid value.
insns.append(&mut vec![
Instruction::IfEqZ {
a: reg_class_loader,
label: "label_end_classloader_test".into(),
},
Instruction::InvokeVirtual {
method: GET_CLASS.clone(),
args: vec![reg_class_loader as u16],
},
Instruction::MoveResultObject { to: reg_tst_val },
Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_tst_val as u16],
},
Instruction::MoveResultObject { to: reg_tst_val },
Instruction::ConstClass {
reg: reg_cmp_val,
lit: BOOT_CLASS_LOADER_TY.clone(),
},
Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_cmp_val as u16],
},
Instruction::MoveResultObject { to: reg_cmp_val },
Instruction::InvokeVirtual {
method: STR_EQ.clone(),
args: vec![reg_cmp_val as u16, reg_tst_val as u16],
},
Instruction::MoveResult { to: reg_cmp_val },
Instruction::IfEqZ {
a: reg_cmp_val,
label: no_label.clone(),
},
Instruction::Label {
name: "label_end_classloader_test".into(),
},
]);
break;
}
insns.append(&mut vec![
Instruction::IfEqZ {
a: reg_class_loader,
label: no_label.clone(),
},
Instruction::InvokeVirtual {
method: TO_STRING.clone(),
args: vec![reg_class_loader as u16],
},
Instruction::MoveResultObject { to: reg_tst_val },
Instruction::ConstString { Instruction::ConstString {
reg: reg_arr_val, reg: reg_cmp_val,
lit: classloader.string_representation.as_str().into(), lit: classloader.string_representation.as_str().into(),
}, },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: STR_EQ.clone(), method: STR_EQ.clone(),
args: vec![reg_arr_idx as u16, reg_arr_val as u16], args: vec![reg_cmp_val as u16, reg_tst_val as u16],
},
Instruction::MoveResult {
to: reg_arr_idx, // wrong name, but available for tmp val
}, },
Instruction::MoveResult { to: reg_cmp_val },
Instruction::IfEqZ { Instruction::IfEqZ {
a: reg_arr_idx, a: reg_cmp_val,
label: no_label.clone(), label: no_label.clone(),
}, },
Instruction::InvokeVirtual {
method: GET_PARENT.clone(),
args: vec![reg_class_loader as u16],
},
Instruction::MoveResultObject {
to: reg_class_loader,
},
]); ]);
// Get Declaring Type let parent_id = classloader.parent_id.clone();
if is_constructor { // If parent_id is None, the parent is in fact the boot class loader (except for the
insns.push(Instruction::InvokeVirtual { // boot class loader itself, already handled at the start of the loop).
method: CNSTR_GET_DEC_CLS.clone(), current_classloader = if let Some(ref id) = parent_id {
args: vec![reg_ref_method], classloaders.get(id)
});
} else { } else {
insns.push(Instruction::InvokeVirtual { classloaders
method: MTH_GET_DEC_CLS.clone(), .values()
args: vec![reg_ref_method], .find(|cl| cl.cname == *BOOT_CLASS_LOADER_TY)
}); };
}
} }
// Check Declaring Type // Check Declaring Type
insns.append(&mut vec![ insns.append(&mut vec![
Instruction::MoveResultObject {
to: reg_arr_idx, // wrong name, but available for tmp val
},
Instruction::ConstClass { Instruction::ConstClass {
reg: reg_arr_val, // wrong name, but available for tmp val reg: reg_cmp_val,
lit: method_to_test.class_.clone(), lit: method_to_test.class_.clone(),
}, },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(), method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_arr_idx as u16], args: vec![reg_cmp_val as u16],
}, },
Instruction::MoveResultObject { to: reg_arr_idx }, Instruction::MoveResultObject { to: reg_cmp_val },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: CLT_GET_DESCR_STRING.clone(), method: CLT_GET_DESCR_STRING.clone(),
args: vec![reg_arr_val as u16], args: vec![reg_def_type as u16],
}, },
Instruction::MoveResultObject { to: reg_arr_val }, Instruction::MoveResultObject { to: reg_tst_val },
Instruction::InvokeVirtual { Instruction::InvokeVirtual {
method: STR_EQ.clone(), method: STR_EQ.clone(),
args: vec![reg_arr_idx as u16, reg_arr_val as u16], args: vec![reg_cmp_val as u16, reg_tst_val as u16],
},
Instruction::MoveResult {
to: reg_arr_idx, // wrong name, but available for tmp val
}, },
Instruction::MoveResult { to: reg_cmp_val },
Instruction::IfEqZ { Instruction::IfEqZ {
a: reg_arr_idx, a: reg_cmp_val,
label: no_label.clone(), label: no_label.clone(),
}, },
// Comparing Type does not work when different types share the same name (eg type from // Comparing Type does not work when different types share the same name (eg type from
// another class loader) // another class loader)
//Instruction::IfNe { //Instruction::IfNe {
// a: reg_arr_idx, // a: reg_arr_idx,
// b: reg_arr_val, // b: reg_tst_val,
// label: no_label.clone(), // label: no_label.clone(),
//}, //},
]);
if DEBUG {
insns.append(&mut vec![
Instruction::ConstString {
reg: reg_tst_val,
lit: "THESEUS".into(),
},
Instruction::ConstString {
reg: reg_cmp_val,
lit: format!(
"T.{method_test_name}() (test of {}) returned true",
method_to_test
.try_to_smali()
.unwrap_or("failed to convert".into())
)
.into(),
},
Instruction::InvokeStatic {
method: LOG_INFO.clone(),
args: vec![reg_tst_val as u16, reg_cmp_val as u16],
},
]);
}
insns.append(&mut vec![
Instruction::Const { Instruction::Const {
reg: reg_arr_val, reg: reg_cmp_val,
lit: 1, lit: 1,
}, },
Instruction::Return { reg: reg_arr_val }, Instruction::Return { reg: reg_cmp_val },
Instruction::Label { name: no_label }, Instruction::Label { name: no_label },
]);
if DEBUG {
insns.append(&mut vec![
Instruction::ConstString {
reg: reg_tst_val,
lit: "THESEUS".into(),
},
Instruction::ConstString {
reg: reg_cmp_val,
lit: format!(
"T.{method_test_name}() (test of {}) returned false",
method_to_test
.try_to_smali()
.unwrap_or("failed to convert".into())
)
.into(),
},
Instruction::InvokeStatic {
method: LOG_INFO.clone(),
args: vec![reg_tst_val as u16, reg_cmp_val as u16],
},
]);
}
insns.append(&mut vec![
Instruction::Const { Instruction::Const {
reg: reg_arr_val, reg: reg_cmp_val,
lit: 0, lit: 0,
}, },
Instruction::Return { reg: reg_arr_val }, Instruction::Return { reg: reg_cmp_val },
]); ]);
method.is_static = true; method.is_static = true;
method.is_final = true; method.is_final = true;
method.code = Some(Code::new( method.code = Some(Code::new(
4, //registers_size, 3 reg + 1 parameter reg 7, //registers_size, 6 reg + 1 parameter reg
insns, insns,
Some(vec![Some("meth".into())]), // parameter_names Some(vec![Some("meth".into())]), // parameter_names
)); ));
@ -667,6 +766,7 @@ fn gen_tester_method(
/// reflected method. If None, the classloader is not tested. Platform classes should probably /// reflected method. If None, the classloader is not tested. Platform classes should probably
/// not be tested (the bootclassloader can be represented with a null reference, which may /// not be tested (the bootclassloader can be represented with a null reference, which may
/// lead to a null pointer exception). /// lead to a null pointer exception).
#[allow(clippy::too_many_arguments)]
fn test_method( fn test_method(
method_obj_reg: u16, method_obj_reg: u16,
id_method: IdMethod, id_method: IdMethod,
@ -674,7 +774,8 @@ fn test_method(
reg_inf: &mut RegistersInfo, reg_inf: &mut RegistersInfo,
tester_methods_class: IdType, tester_methods_class: IdType,
tester_methods: &mut HashMap<IdMethod, Method>, tester_methods: &mut HashMap<IdMethod, Method>,
classloader: Option<&ClassLoaderData>, classloader: Option<String>,
classloaders: &HashMap<String, ClassLoaderData>,
) -> Result<Vec<Instruction>> { ) -> Result<Vec<Instruction>> {
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
let tst_descriptor = match tester_methods.entry(id_method.clone()) { let tst_descriptor = match tester_methods.entry(id_method.clone()) {
@ -684,6 +785,7 @@ fn test_method(
id_method, id_method,
false, false,
classloader, classloader,
classloaders,
)?), )?),
} }
.descriptor .descriptor
@ -765,7 +867,7 @@ fn get_invoke_block(
let classloader = if ref_data.method.class_.is_platform_class() { let classloader = if ref_data.method.class_.is_platform_class() {
None None
} else { } else {
classloaders.get(&ref_data.method_cl_id) Some(ref_data.method_cl_id.clone())
}; };
let mut insns = test_method( let mut insns = test_method(
method_obj, method_obj,
@ -775,6 +877,7 @@ fn get_invoke_block(
tester_methods_class, tester_methods_class,
tester_methods, tester_methods,
classloader, classloader,
classloaders,
)?; )?;
if !ref_data.is_static { if !ref_data.is_static {
@ -979,7 +1082,7 @@ fn get_cnstr_new_inst_block(
let classloader = if ref_data.constructor.class_.is_platform_class() { let classloader = if ref_data.constructor.class_.is_platform_class() {
None None
} else { } else {
classloaders.get(&ref_data.constructor_cl_id) Some(ref_data.constructor_cl_id.clone())
}; };
let mut insns = test_cnstr( let mut insns = test_cnstr(
cnst_reg, cnst_reg,
@ -989,6 +1092,7 @@ fn get_cnstr_new_inst_block(
tester_methods_class, tester_methods_class,
tester_methods, tester_methods,
classloader, classloader,
classloaders,
)?; )?;
insns.append(&mut get_args_from_obj_arr( insns.append(&mut get_args_from_obj_arr(
&ref_data.constructor.proto.get_parameters(), // TODO: what if args are renammed? &ref_data.constructor.proto.get_parameters(), // TODO: what if args are renammed?
@ -1039,6 +1143,7 @@ fn get_cnstr_new_inst_block(
/// - `classloader`: is the runtime data of the classloader that loaded the. If None, the classloader /// - `classloader`: is the runtime data of the classloader that loaded the. If None, the classloader
/// is not tested. Platform classes should probably not be tested (the bootclassloader can be /// is not tested. Platform classes should probably not be tested (the bootclassloader can be
/// represented with a null reference, which may lead to a null pointer exception). /// represented with a null reference, which may lead to a null pointer exception).
#[allow(clippy::too_many_arguments)]
fn test_cnstr( fn test_cnstr(
cnst_reg: u16, cnst_reg: u16,
id_method: IdMethod, id_method: IdMethod,
@ -1046,7 +1151,8 @@ fn test_cnstr(
reg_inf: &mut RegistersInfo, reg_inf: &mut RegistersInfo,
tester_methods_class: IdType, tester_methods_class: IdType,
tester_methods: &mut HashMap<IdMethod, Method>, tester_methods: &mut HashMap<IdMethod, Method>,
classloader: Option<&ClassLoaderData>, classloader: Option<String>,
classloaders: &HashMap<String, ClassLoaderData>,
) -> Result<Vec<Instruction>> { ) -> Result<Vec<Instruction>> {
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
let tst_descriptor = match tester_methods.entry(id_method.clone()) { let tst_descriptor = match tester_methods.entry(id_method.clone()) {
@ -1056,6 +1162,7 @@ fn test_cnstr(
id_method, id_method,
true, true,
classloader, classloader,
classloaders,
)?), )?),
} }
.descriptor .descriptor

View file

@ -210,5 +210,5 @@ pub struct ClassLoaderData {
#[serde(rename = "str")] #[serde(rename = "str")]
pub string_representation: String, pub string_representation: String,
/// The class of the class loader. /// The class of the class loader.
pub cname: String, // TODO: IdType, pub cname: IdType,
} }

View file

@ -14,6 +14,14 @@ import java.lang.reflect.Method;
import com.example.theseus.Utils; import com.example.theseus.Utils;
public class Main { public class Main {
static ClassLoader delegateLastClassLoaderParent = null;
static ClassLoader delegateLastClassLoader = null;
static ClassLoader dexClassLoaderParent = null;
static ClassLoader dexClassLoader = null;
static ClassLoader inMemoryDexClassLoaderParent = null;
static ClassLoader inMemoryDexClassLoader = null;
static ClassLoader pathClassLoaderParent = null;
static ClassLoader pathClassLoader = null;
public static String getdexfile(Activity ac, String name) throws Exception { public static String getdexfile(Activity ac, String name) throws Exception {
File dexfile = new File(ac.getCacheDir(), name); File dexfile = new File(ac.getCacheDir(), name);
@ -30,25 +38,65 @@ public class Main {
} }
public static void run(Activity ac, String clname, boolean hasCollision, boolean hasParent, String methodType) { public static void run(Activity ac, String clname, boolean hasCollision, boolean hasParent, String methodType) {
ClassLoader cl = Main.class.getClassLoader();
ClassLoader parent;
try { try {
Log.i("THESEUS", "clname: " + clname + ", hasCollision: " + hasCollision + ", hasParent: " + hasParent + ", methodType: " + methodType); Log.i("THESEUS", "clname: " + clname + ", hasCollision: " + hasCollision + ", hasParent: " + hasParent + ", methodType: " + methodType);
ClassLoader cl; String name = "a.dex";
ClassLoader parent;
if (hasParent) { if (hasParent) {
parent = Main.class.getClassLoader(); parent = Main.class.getClassLoader();
name = "b.dex";
} else { } else {
parent = null; parent = null;
} }
if (clname.equals("DelegateLastClassLoader")) { if (clname.equals("DelegateLastClassLoader")) {
cl = new DelegateLastClassLoader(getdexfile(ac, "a.dex"), parent); if (parent == null) {
if (delegateLastClassLoader == null) {
delegateLastClassLoader = new DelegateLastClassLoader(getdexfile(ac, name), parent);
}
cl = delegateLastClassLoader;
} else {
if (delegateLastClassLoaderParent == null) {
delegateLastClassLoaderParent = new DelegateLastClassLoader(getdexfile(ac, name), parent);
}
cl = delegateLastClassLoaderParent;
}
} else if (clname.equals("DexClassLoader")) { } else if (clname.equals("DexClassLoader")) {
cl = new DexClassLoader(getdexfile(ac, "a.dex"), null, null, parent); if (parent == null) {
if (dexClassLoader == null) {
dexClassLoader = new DexClassLoader(getdexfile(ac, name), null, null, parent);
}
cl = dexClassLoader;
} else {
if (dexClassLoaderParent == null) {
dexClassLoaderParent = new DexClassLoader(getdexfile(ac, name), null, null, parent);
}
cl = dexClassLoaderParent;
}
} else if (clname.equals("InMemoryDexClassLoader")) { } else if (clname.equals("InMemoryDexClassLoader")) {
cl = new InMemoryDexClassLoader(getdexbuffer(ac, "a.dex"), parent); if (parent == null) {
if (inMemoryDexClassLoader == null) {
inMemoryDexClassLoader = new InMemoryDexClassLoader(getdexbuffer(ac, name), parent);
}
cl = inMemoryDexClassLoader;
} else {
if (inMemoryDexClassLoaderParent == null) {
inMemoryDexClassLoaderParent = new InMemoryDexClassLoader(getdexbuffer(ac, name), parent);
}
cl = inMemoryDexClassLoaderParent;
}
} else if (clname.equals("PathClassLoader")) { } else if (clname.equals("PathClassLoader")) {
cl = new PathClassLoader(getdexfile(ac, "a.dex"), parent); if (parent == null) {
} else { if (pathClassLoader == null) {
cl = Main.class.getClassLoader(); pathClassLoader = new PathClassLoader(getdexfile(ac, name), parent);
}
cl = pathClassLoader;
} else {
if (pathClassLoaderParent == null) {
pathClassLoaderParent = new PathClassLoader(getdexfile(ac, name), parent);
}
cl = pathClassLoaderParent;
}
} }
Class clz = null; Class clz = null;
@ -194,6 +242,7 @@ public class Main {
return; return;
}; };
} catch (Exception e) { } catch (Exception e) {
Log.e("THESEUS", "class loader name: " + cl.toString());
Log.e("THESEUS", "error:", e); Log.e("THESEUS", "error:", e);
} }
} }

View file

@ -49,6 +49,11 @@ public class MainActivity extends Activity {
out = new FileOutputStream(outFile); out = new FileOutputStream(outFile);
Utils.copy(in, out); Utils.copy(in, out);
outFile.renameTo(new File(getCacheDir(), "a.dex")); // security? outFile.renameTo(new File(getCacheDir(), "a.dex")); // security?
in = assetManager.open("a.dex");
outFile = new File(getCacheDir(), "b.dex_");
out = new FileOutputStream(outFile);
Utils.copy(in, out);
outFile.renameTo(new File(getCacheDir(), "b.dex"));
} catch (IOException e) {} } catch (IOException e) {}
try { try {
in.close(); in.close();