use rane::ffi::*;
use std::ffi::{c_void, CStr, CString};
extern "C" {
fn free(p: *mut c_void);
}
fn libc_free(p: *mut c_void) {
unsafe {
free(p);
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== CoreML MLModel probe ===\n");
for name in &["AppleNeuralEngine", "ANECompiler", "ANEServices"] {
let path = format!("/System/Library/PrivateFrameworks/{name}.framework/{name}");
let c = CString::new(path).unwrap();
unsafe {
dlopen(c.as_ptr(), RTLD_NOW);
}
}
let cml = CString::new("/System/Library/Frameworks/CoreML.framework/CoreML").unwrap();
unsafe {
dlopen(cml.as_ptr(), RTLD_NOW);
}
let ml_model_cls = cls("MLModel");
if ml_model_cls.is_null() {
println!("MLModel class not found");
return Ok(());
}
println!("MLModel class: found");
let bundle_path = "/System/Library/PrivateFrameworks/VoiceActions.framework/Versions/A/Resources/aa_encoder_125141826.mlmodelc";
unsafe {
type StrFn2 = unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId) -> ObjcId;
type ModelFn3e =
unsafe extern "C" fn(ObjcId, ObjcSel, ObjcId, ObjcId, *mut ObjcId) -> ObjcId;
type DictFn = unsafe extern "C" fn(ObjcId, ObjcSel) -> ObjcId;
let sf: StrFn2 = std::mem::transmute(objc_msgSend as *const c_void);
let mf: ModelFn3e = std::mem::transmute(objc_msgSend as *const c_void);
let df: DictFn = std::mem::transmute(objc_msgSend as *const c_void);
let cls_nsstr = cls("NSString");
let cls_nsurl = cls("NSURL");
let cls_dict = cls("NSDictionary");
let cls_config = cls("MLModelConfiguration");
let cs = CString::new(bundle_path).unwrap();
let ns_str = sf(
cls_nsstr as ObjcId,
sel("stringWithUTF8String:"),
cs.as_ptr() as ObjcId,
);
let ns_url = sf(cls_nsurl as ObjcId, sel("fileURLWithPath:"), ns_str);
let config = if !cls_config.is_null() {
let cfg = df(cls_config as ObjcId, sel("new"));
type SetIntFn = unsafe extern "C" fn(ObjcId, ObjcSel, i64);
let sif: SetIntFn = std::mem::transmute(objc_msgSend as *const c_void);
sif(cfg, sel("setComputeUnits:"), 3); println!("MLModelConfiguration: cpuAndNeuralEngine");
cfg
} else {
std::ptr::null_mut()
};
let mut err: ObjcId = std::ptr::null_mut();
println!("Loading VoiceActions bundle via [MLModel modelWithContentsOfURL:configuration:error:]...");
let ml_model = mf(
ml_model_cls as ObjcId,
sel("modelWithContentsOfURL:configuration:error:"),
ns_url,
config,
&mut err,
);
if ml_model.is_null() {
println!("MLModel = null: {:?}", nserror_string(err));
return Ok(());
}
println!("MLModel loaded OK: {ml_model:p}");
println!("\n=== MLModel class hierarchy + own methods ===");
let ml_cls = object_getClass(ml_model);
walk_and_print(ml_cls, ml_model);
println!("\n=== Searching for _ANEInMemoryModel inside MLModel ===");
find_ane_model(ml_model);
}
Ok(())
}
unsafe fn walk_and_print(cls: ObjcClass, obj: ObjcId) {
let mut c = cls;
while !c.is_null() {
let cname = {
let p = class_getName(c);
if p.is_null() {
"?".to_string()
} else {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
};
let mut icount: u32 = 0;
let ilist = class_copyIvarList(c, &mut icount);
if !ilist.is_null() && icount > 0 {
println!(" [{cname}] ivars:");
for i in 0..icount {
let ivar = *ilist.add(i as usize);
let np = ivar_getName(ivar);
let tp = ivar_getTypeEncoding(ivar);
let off = ivar_getOffset(ivar);
let name = if np.is_null() {
"?".into()
} else {
CStr::from_ptr(np).to_string_lossy().into_owned()
};
let ty = if tp.is_null() {
"?".into()
} else {
CStr::from_ptr(tp).to_string_lossy().into_owned()
};
let val = object_getIvar(obj, ivar);
print!(" +{off:04x} {name}: {ty} = {val:p}");
if ty.starts_with('@') && !val.is_null() {
let vcls = object_getClass(val);
if !vcls.is_null() {
let vp = class_getName(vcls);
if !vp.is_null() {
let vn = CStr::from_ptr(vp).to_string_lossy();
print!(" [{vn}]");
}
}
}
println!();
}
libc_free(ilist as *mut c_void);
}
let mut mcount: u32 = 0;
let mlist = class_copyMethodList(c, &mut mcount);
if !mlist.is_null() && mcount > 0 {
let mut names: Vec<String> = Vec::new();
for i in 0..mcount {
let method = *mlist.add(i as usize);
let s = method_getName(method);
if !s.is_null() {
let np = sel_getName(s);
if !np.is_null() {
names.push(CStr::from_ptr(np).to_string_lossy().into_owned());
}
}
}
libc_free(mlist as *mut c_void);
names.sort();
let interesting: Vec<_> = names
.iter()
.filter(|n| {
n.contains("ANE")
|| n.contains("ane")
|| n.contains("neural")
|| n.contains("Neural")
|| n.contains("compile")
|| n.contains("Compile")
|| n.contains("program")
|| n.contains("Program")
|| n.contains("backend")
|| n.contains("Backend")
|| n.contains("engine")
|| n.contains("Engine")
})
.cloned()
.collect();
if !interesting.is_empty() {
println!(" [{cname}] interesting methods:");
for n in &interesting {
println!(" {n}");
}
}
}
c = class_getSuperclass(c);
}
}
unsafe fn find_ane_model(obj: ObjcId) {
let mut queue: Vec<(ObjcId, String)> = vec![(obj, "root".to_string())];
let mut visited: std::collections::HashSet<usize> = std::collections::HashSet::new();
let mut depth = 0;
while !queue.is_empty() && depth < 4 {
let next_queue: Vec<(ObjcId, String)> = queue
.drain(..)
.flat_map(|(o, path)| {
if o.is_null() || !visited.insert(o as usize) {
return vec![];
}
let cls = object_getClass(o);
if cls.is_null() {
return vec![];
}
let cname = {
let p = class_getName(cls);
if p.is_null() {
return vec![];
}
CStr::from_ptr(p).to_string_lossy().into_owned()
};
if cname.contains("ANEInMemory") {
println!(" FOUND: {path} โ {cname} @ {o:p}");
let mut ic: u32 = 0;
let il = class_copyIvarList(cls, &mut ic);
if !il.is_null() {
for i in 0..ic {
let iv = *il.add(i as usize);
let np = ivar_getName(iv);
let tp = ivar_getTypeEncoding(iv);
let off = ivar_getOffset(iv);
let name = if np.is_null() {
"?".into()
} else {
CStr::from_ptr(np).to_string_lossy().into_owned()
};
let ty = if tp.is_null() {
"?".into()
} else {
CStr::from_ptr(tp).to_string_lossy().into_owned()
};
let val = object_getIvar(o, iv);
println!(" +{off:04x} {name}: {ty} = {val:p}");
}
libc_free(il as *mut c_void);
}
return vec![];
}
let mut children = vec![];
let mut c = cls;
while !c.is_null() {
let mut ic: u32 = 0;
let il = class_copyIvarList(c, &mut ic);
if !il.is_null() {
for i in 0..ic {
let iv = *il.add(i as usize);
let tp = ivar_getTypeEncoding(iv);
let np = ivar_getName(iv);
if !tp.is_null() {
let ty = CStr::from_ptr(tp).to_string_lossy();
if ty.starts_with('@') {
let child = object_getIvar(o, iv);
if !child.is_null() {
let iname = if np.is_null() {
"?".into()
} else {
CStr::from_ptr(np).to_string_lossy().into_owned()
};
children.push((child, format!("{path}.{iname}")));
}
}
}
}
libc_free(il as *mut c_void);
}
c = class_getSuperclass(c);
}
children
})
.collect();
queue = next_queue;
depth += 1;
}
}