This commit is contained in:
particle-box
2026-01-23 20:22:47 +05:30
parent de4d8dac39
commit 327efafbe5
22 changed files with 449 additions and 819 deletions

View File

@@ -24,16 +24,11 @@ pub static CLIENT_MODULE: Lazy<MappedLib> = Lazy::new(|| {
pub fn set_native_lib_instance(instance: GlobalRef) {
NATIVE_LIB_INSTANCE
.set(instance)
.expect("NativeLib instance already set");
NATIVE_LIB_INSTANCE.set(instance).expect("NativeLib instance already set");
}
pub fn native_lib_instance() -> GlobalRef {
NATIVE_LIB_INSTANCE
.get()
.expect("NativeLib instance not set")
.clone()
NATIVE_LIB_INSTANCE.get().expect("NativeLib instance not set").clone()
}
pub fn set_java_vm(vm: *mut jni::sys::JavaVM) {
@@ -42,16 +37,13 @@ pub fn set_java_vm(vm: *mut jni::sys::JavaVM) {
pub fn java_vm() -> JavaVM {
unsafe {
JavaVM::from_raw(*JAVA_VM.get().expect("JavaVM not set") as *mut jni::sys::JavaVM)
.expect("Failed to get JavaVM")
JavaVM::from_raw(*JAVA_VM.get().expect("JavaVM not set") as *mut jni::sys::JavaVM).expect("Failed to get JavaVM")
}
}
pub fn attach_jni_env(block: impl FnOnce(&mut jni::JNIEnv)) {
let jvm = java_vm();
let mut env: jni::AttachGuard = jvm
.attach_current_thread()
.expect("Failed to attach to current thread");
let mut env: jni::AttachGuard = jvm.attach_current_thread().expect("Failed to attach to current thread");
block(&mut env);
}
}

View File

@@ -1,16 +1,11 @@
use crate::{secstrings, util::get_jni_string};
use jni::{objects::JObject, JNIEnv};
use std::{error::Error, sync::Mutex};
use jni::{objects::JObject, JNIEnv};
use crate::{secstrings, util::get_jni_string};
static NATIVE_CONFIG: Mutex<Option<NativeConfig>> = Mutex::new(None);
pub fn native_config() -> NativeConfig {
NATIVE_CONFIG
.lock()
.unwrap()
.as_ref()
.expect("NativeConfig not loaded")
.clone()
NATIVE_CONFIG.lock().unwrap().as_ref().expect("NativeConfig not loaded").clone()
}
#[derive(Debug, Clone)]
@@ -32,13 +27,11 @@ impl NativeConfig {
macro_rules! get_string {
($field:expr) => {
match env.get_field(&obj, $field, "Ljava/lang/String;")?.l()? {
jstring => {
if !jstring.is_null() {
Some(get_jni_string(env, jstring.into())?)
} else {
None
}
}
jstring => if !jstring.is_null() {
Some(get_jni_string(env, jstring.into())?)
} else {
None
},
}
};
}
@@ -80,11 +73,10 @@ pub fn get_blocker_config() -> BlockerConfig {
}
}
pub fn load_config(mut env: JNIEnv, _class: JObject, obj: JObject) {
NATIVE_CONFIG
.lock()
.unwrap()
.replace(NativeConfig::new(&mut env, obj).expect("Failed to load NativeConfig"));
pub fn load_config(mut env: JNIEnv, _class: JObject, obj: JObject) {
NATIVE_CONFIG.lock().unwrap().replace(
NativeConfig::new(&mut env, obj).expect("Failed to load NativeConfig")
);
info!("Config loaded {:?}", native_config());
}

View File

@@ -36,3 +36,15 @@ macro_rules! dobby_hook {
};
}
#[macro_export]
macro_rules! dobby_hook_sym {
($lib:expr, $sym:expr, $hook:expr) => {
if let Some(hook_symbol) = dobby_rs::resolve_symbol($lib, $sym) {
crate::dobby_hook!(hook_symbol, $hook);
debug!("hooked symbol: {}", $sym);
} else {
panic!("Failed to resolve symbol: {}", $sym);
}
};
}

View File

@@ -3,11 +3,11 @@ extern crate log;
mod common;
mod config;
mod hook;
mod mapped_lib;
mod sig;
mod util;
mod mapped_lib;
mod config;
mod sig;
mod modules;
mod security;
@@ -15,10 +15,7 @@ mod secstrings;
use android_logger::Config;
use log::LevelFilter;
use modules::{
custom_font_hook, duplex_hook, fstat_hook, linker_hook, sqlite_hook, unary_call_hook,
valdi_hook,
};
use modules::{valdi_hook, custom_font_hook, duplex_hook, fstat_hook, linker_hook, sqlite_hook, unary_call_hook};
use jni::{JNIEnv, JavaVM, NativeMethod};
use jni::objects::{JObject, JString, JClass, JValue};
@@ -169,11 +166,10 @@ fn init(mut env: JNIEnv, _class: JObject, signature_cache: JString) -> jstring {
let start_time = std::time::Instant::now();
// load signature cache
if !signature_cache.is_null() {
let sig_cache_str = util::get_jni_string(&mut env, signature_cache)
.expect("Failed to convert mappings to string");
let sig_cache_str = util::get_jni_string(&mut env, signature_cache).expect("Failed to convert mappings to string");
if let Ok(signature_cache) = serde_json::from_str(sig_cache_str.as_str()) {
sig::add_signatures(signature_cache);
} else {
@@ -181,11 +177,7 @@ fn init(mut env: JNIEnv, _class: JObject, signature_cache: JString) -> jstring {
}
}
common::set_native_lib_instance(
env.new_global_ref(_class)
.ok()
.expect("Failed to create global ref"),
);
common::set_native_lib_instance(env.new_global_ref(_class).ok().expect("Failed to create global ref"));
let _ = common::CLIENT_MODULE;

View File

@@ -24,24 +24,21 @@ impl MappedLib {
}
pub fn search(&mut self) -> Result<&Self, Box<dyn Error>> {
procfs::process::Process::myself()?
.maps()?
.iter()
.for_each(|map| {
let pathname = &map.pathname;
procfs::process::Process::myself()?.maps()?.iter().for_each(|map| {
let pathname = &map.pathname;
if let MMapPath::Path(path_buffer) = pathname {
let path = path_buffer.to_string_lossy();
if let MMapPath::Path(path_buffer) = pathname {
let path = path_buffer.to_string_lossy();
if path.contains(&self.name) {
self.regions.push(MappedRegion {
start: map.address.0,
end: map.address.1,
perms: map.perms,
});
}
if path.contains(&self.name) {
self.regions.push(MappedRegion {
start: map.address.0,
end: map.address.1,
perms: map.perms,
});
}
});
}
});
if self.regions.is_empty() {
return Err(format!("No regions found for {}", self.name).into());

View File

@@ -2,41 +2,33 @@ use std::{ffi::CStr, fs};
use nix::libc::{self, c_uint};
use crate::{config, def_hook, dobby_hook, modules::util::elf};
use crate::{config, def_hook, dobby_hook_sym};
def_hook!(open_hook, i32, |path: *const u8,
flags: i32,
mode: c_uint| {
if let Ok(pathname) = CStr::from_ptr(path).to_str() {
if pathname == "/system/fonts/NotoColorEmoji.ttf" {
if let Some(font_path) = config::native_config().custom_emoji_font_path {
if fs::metadata(&font_path).is_ok() {
return libc::openat(
libc::AT_FDCWD,
font_path.as_ptr() as *const u8,
flags,
mode,
);
} else {
warn!("custom emoji font path does not exist: {}", font_path);
def_hook!(
open_hook,
i32,
|path: *const u8, flags: i32, mode: c_uint| {
if let Ok(pathname) = CStr::from_ptr(path).to_str() {
if pathname == "/system/fonts/NotoColorEmoji.ttf" {
if let Some(font_path) = config::native_config().custom_emoji_font_path {
if fs::metadata(&font_path).is_ok() {
return libc::openat(libc::AT_FDCWD, font_path.as_ptr() as *const u8, flags, mode);
} else {
warn!("custom emoji font path does not exist: {}", font_path);
}
}
}
}
}
open_hook_original.unwrap()(path, flags, mode)
});
open_hook_original.unwrap()(path, flags, mode)
}
);
pub fn init() {
if config::native_config().custom_emoji_font_path.is_none() {
return;
}
let libc = elf::Elf::from_maps("/libc.so").expect("Failed to find libc.so in maps");
if let Some(ptr) = libc.get_symbol_address("open") {
dobby_hook!(ptr as _, open_hook);
} else {
panic!("Failed to find open symbol");
}
}
dobby_hook_sym!("libc.so", "open", open_hook);
}

View File

@@ -4,6 +4,7 @@ use jni::{objects::JObject, sys::jboolean, JNIEnv};
use crate::{common, def_hook, dobby_hook, util::get_jni_string};
def_hook!(
is_same_object,
jboolean,
@@ -19,15 +20,9 @@ def_hook!(
if !env.is_instance_of(&obj1, class).unwrap() {
return is_same_object_original.unwrap()(env, obj1, obj2);
}
let obj1_class_name = env
.call_method(&obj1, "getName", "()Ljava/lang/String;", &[])
.unwrap()
.l()
.unwrap()
.into();
let class_name =
get_jni_string(&mut env, obj1_class_name).expect("Failed to get class name");
let obj1_class_name = env.call_method(&obj1, "getName", "()Ljava/lang/String;", &[]).unwrap().l().unwrap().into();
let class_name = get_jni_string(&mut env, obj1_class_name).expect("Failed to get class name");
if class_name.contains("com.snapchat.client.duplex.MessageHandler") {
debug!("is_same_object hook: MessageHandler");
@@ -38,11 +33,9 @@ def_hook!(
}
);
pub fn init() {
common::attach_jni_env(|env| {
dobby_hook!(
(**env.get_native_interface()).IsSameObject.unwrap() as *mut c_void,
is_same_object
);
dobby_hook!((**env.get_native_interface()).IsSameObject.unwrap() as *mut c_void, is_same_object);
});
}
}

View File

@@ -1,48 +1,44 @@
use crate::{
config::{self, native_config},
def_hook, dobby_hook,
modules::util::elf,
};
use nix::libc;
use std::fs;
def_hook!(fstat_hook, i32, |fd: i32, statbuf: *mut libc::stat| {
if let Ok(link) = fs::read_link("/proc/self/fd/".to_owned() + &fd.to_string()) {
let link_str = link.to_string_lossy();
let config = native_config();
if config.disable_metrics && link_str.contains("files/blizzardv2/queues") {
if libc::unlink((link_str.to_string() + "\0").as_ptr()) == -1 {
warn!("Failed to unlink {}", link_str);
}
return -1;
}
use nix::libc;
if config.disable_bitmoji {
if link_str.contains("com.snap.file_manager_4_SCContent") {
use crate::{config::{self, native_config}, def_hook, dobby_hook_sym};
def_hook!(
fstat_hook,
i32,
|fd: i32, statbuf: *mut libc::stat| {
if let Ok(link) = fs::read_link("/proc/self/fd/".to_owned() + &fd.to_string()) {
let link_str = link.to_string_lossy();
let config = native_config();
if config.disable_metrics && link_str.contains("files/blizzardv2/queues") {
if libc::unlink((link_str.to_string() + "\0").as_ptr()) == -1 {
warn!("Failed to unlink {}", link_str);
}
return -1;
}
if link_str.contains("/files/file_manager/") {
let lower = link_str.to_lowercase();
if lower.contains("bitmoji") {
if config.disable_bitmoji {
if link_str.contains("com.snap.file_manager_4_SCContent") {
return -1;
}
if link_str.contains("/files/file_manager/") {
let lower = link_str.to_lowercase();
if lower.contains("bitmoji") {
return -1;
}
}
}
}
}
fstat_hook_original.unwrap()(fd, statbuf)
});
fstat_hook_original.unwrap()(fd, statbuf)
}
);
pub fn init() {
let config = config::native_config();
if config.disable_metrics || config.disable_bitmoji {
let libc = elf::Elf::from_maps("/libc.so").expect("Failed to find libc.so in maps");
if let Some(ptr) = libc.get_symbol_address("fstat") {
dobby_hook!(ptr as _, fstat_hook);
} else {
panic!("Failed to find fstat symbol");
}
dobby_hook_sym!("libc.so", "fstat", fstat_hook);
}
}
}

View File

@@ -1,20 +1,12 @@
use std::{
collections::HashMap,
ffi::{c_void, CStr},
sync::Mutex,
};
use std::{collections::HashMap, ffi::{c_void, CStr}, sync::Mutex};
use jni::{
objects::{JByteArray, JString},
JNIEnv,
};
use jni::{objects::{JByteArray, JString}, JNIEnv};
use nix::libc;
use once_cell::sync::Lazy;
use crate::{def_hook, dobby_hook, modules::util::elf};
use crate::{def_hook, dobby_hook_sym};
static SHARED_LIBRARIES: Lazy<Mutex<HashMap<String, Box<Vec<i8>>>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static SHARED_LIBRARIES: Lazy<Mutex<HashMap<String, Box<Vec<i8>>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
def_hook!(
linker_openat,
@@ -25,13 +17,8 @@ def_hook!(
if let Some(content) = SHARED_LIBRARIES.lock().unwrap().remove(&pathname_str) {
let memfd = libc::syscall(libc::SYS_memfd_create, "jit-cache\0".as_ptr(), 0) as i32;
let content = content.into_boxed_slice();
if libc::write(
memfd,
content.as_ptr() as *const c_void,
content.len() as libc::size_t,
) == -1
{
if libc::write(memfd, content.as_ptr() as *const c_void, content.len() as libc::size_t) == -1 {
panic!("failed to write to memfd");
}
@@ -49,44 +36,25 @@ def_hook!(
}
);
pub fn add_linker_shared_library(
mut env: JNIEnv,
_: *mut c_void,
path: JString,
content: JByteArray,
) {
pub fn add_linker_shared_library(mut env: JNIEnv, _: *mut c_void, path: JString, content: JByteArray) {
let path = env.get_string(&path).unwrap().to_str().unwrap().to_string();
let content_length = env
.get_array_length(&content)
.expect("Failed to get array length");
let content_length = env.get_array_length(&content).expect("Failed to get array length");
let mut content_buffer = Box::new(vec![0i8; content_length as usize]);
env.get_byte_array_region(content, 0, content_buffer.as_mut_slice())
.expect("Failed to get byte array region");
env.get_byte_array_region(content, 0, content_buffer.as_mut_slice()).expect("Failed to get byte array region");
debug!("added shared library: {}", path);
SHARED_LIBRARIES
.lock()
.unwrap()
.insert(path, content_buffer);
SHARED_LIBRARIES.lock().unwrap().insert(path, content_buffer);
}
pub fn init() {
let linker = {
#[cfg(target_arch = "aarch64")]
{
elf::Elf::from_maps("/linker64").expect("Failed to find linker64 in maps")
}
#[cfg(target_arch = "arm")]
{
elf::Elf::from_maps("/linker").expect("Failed to find linker in maps")
}
};
if let Some(ptr) = linker.get_symbol_address("__dl___openat") {
dobby_hook!(ptr as _, linker_openat);
} else {
panic!("Failed to find open symbol");
#[cfg(target_arch = "aarch64")]
{
dobby_hook_sym!("linker64", "__dl___openat", linker_openat);
}
#[cfg(target_arch = "arm")]
{
dobby_hook_sym!("linker", "__dl___openat", linker_openat);
}
}

View File

@@ -1,8 +1,8 @@
pub mod custom_font_hook;
pub mod duplex_hook;
pub mod fstat_hook;
pub mod linker_hook;
pub mod sqlite_hook;
pub mod unary_call_hook;
pub mod util;
pub mod linker_hook;
pub mod duplex_hook;
pub mod sqlite_hook;
pub mod fstat_hook;
pub mod unary_call_hook;
pub mod valdi_hook;
pub mod custom_font_hook;

View File

@@ -1,34 +1,25 @@
use std::{
collections::HashMap,
ffi::{c_void, CStr},
mem::size_of,
ptr::addr_of_mut,
sync::Mutex,
};
use std::{collections::HashMap, ffi::{c_void, CStr}, mem::size_of, ptr::addr_of_mut, sync::Mutex};
use jni::{
objects::{JObject, JString},
JNIEnv,
};
use jni::{objects::{JObject, JString}, JNIEnv};
use nix::libc::{self, pthread_mutex_t};
use once_cell::sync::Lazy;
use crate::{common, def_hook, dobby_hook, sig, util::get_jni_string};
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct Sqlite3Mutex {
mutex: pthread_mutex_t,
mutex: pthread_mutex_t
}
#[repr(C)]
struct Sqlite3 {
pad: [u8; 3 * size_of::<usize>()],
mutex: *mut Sqlite3Mutex,
mutex: *mut Sqlite3Mutex
}
static SQLITE3_MUTEX_MAP: Lazy<Mutex<HashMap<String, pthread_mutex_t>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static SQLITE3_MUTEX_MAP: Lazy<Mutex<HashMap<String, pthread_mutex_t>>> = Lazy::new(|| Mutex::new(HashMap::new()));
def_hook!(
sqlite3_open,
@@ -40,38 +31,27 @@ def_hook!(
let sqlite3_mutex = (**pp_db).mutex;
if sqlite3_mutex != std::ptr::null_mut() {
let filename = CStr::from_ptr(filename)
.to_string_lossy()
.to_string()
.split("/")
.last()
.expect("Failed to get filename")
.to_string();
let filename = CStr::from_ptr(filename).to_string_lossy().to_string().split("/").last().expect("Failed to get filename").to_string();
debug!("sqlite3_open hook {:?}", filename);
SQLITE3_MUTEX_MAP
.lock()
.unwrap()
.insert(filename, (*sqlite3_mutex).mutex);
SQLITE3_MUTEX_MAP.lock().unwrap().insert(
filename,
(*sqlite3_mutex).mutex
);
}
}
result
}
}
);
pub fn lock_database(mut env: JNIEnv, _: *mut c_void, filename: JString, runnable: JObject) {
let database_filename =
get_jni_string(&mut env, filename).expect("Failed to get database filename");
let mutex = SQLITE3_MUTEX_MAP
.lock()
.unwrap()
.get(&database_filename)
.map(|mutex| *mutex);
let database_filename = get_jni_string(&mut env, filename).expect("Failed to get database filename");
let mutex = SQLITE3_MUTEX_MAP.lock().unwrap().get(&database_filename).map(|mutex| *mutex);
let call_runnable = || {
env.call_method(runnable, "run", "()V", &[])
.expect("Failed to call run method");
env.call_method(runnable, "run", "()V", &[]).expect("Failed to call run method");
};
if let Some(mut mutex) = mutex {
@@ -91,17 +71,16 @@ pub fn lock_database(mut env: JNIEnv, _: *mut c_void, filename: JString, runnabl
}
}
pub fn init() {
if let Some(signature) = sig::find_signature(
&common::CLIENT_MODULE,
"FF FF 00 A9 3F 00 00 F9",
-0x3C,
"9A 46 90 46 78 44 89 46 05 68",
-0xd,
&common::CLIENT_MODULE,
"FF FF 00 A9 3F 00 00 F9", -0x3C,
"9A 46 90 46 78 44 89 46 05 68",-0xd
) {
debug!("Found sqlite3_open signature: {:#x}", signature);
dobby_hook!(signature as *mut c_void, sqlite3_open);
} else {
panic!("Failed to find sqlite3_open signature");
warn!("Failed to find sqlite3_open signature");
}
}
}

View File

@@ -1,23 +1,17 @@
use std::ffi::{c_void, CStr};
use jni::{
objects::{JByteArray, JMethodID, JValue},
signature::ReturnType,
};
use jni::{objects::{JByteArray, JMethodID, JValue}, signature::ReturnType};
use nix::libc;
use once_cell::sync::OnceCell;
use crate::{
common::{self},
def_hook, dobby_hook, sig,
};
use crate::{common::{self}, def_hook, dobby_hook, sig};
#[repr(C)]
#[derive(Copy, Clone)]
struct SliceByteBuffer {
struct RefCountedSliceByteBuffer {
ref_counter: *mut c_void,
length: usize,
data: *mut u8,
data: *mut u8
}
#[repr(C)]
@@ -25,7 +19,7 @@ struct GrpcByteBuffer {
reserved: *mut c_void,
type_: *mut c_void,
compression: *mut c_void,
slice_buffer: *mut SliceByteBuffer,
slice_buffer: *mut RefCountedSliceByteBuffer
}
static NATIVE_LIB_ON_UNARY_CALL_METHOD: OnceCell<JMethodID> = OnceCell::new();
@@ -33,12 +27,7 @@ static NATIVE_LIB_ON_UNARY_CALL_METHOD: OnceCell<JMethodID> = OnceCell::new();
def_hook!(
unary_call,
*mut c_void,
|unk1: *mut c_void,
uri: *const u8,
grpc_byte_buffer: *mut *mut GrpcByteBuffer,
unk4: *mut c_void,
unk5: *mut c_void,
unk6: *mut c_void| {
|unk1: *mut c_void, uri: *const u8, grpc_byte_buffer: *mut *mut GrpcByteBuffer, unk4: *mut c_void, unk5: *mut c_void, unk6: *mut c_void| {
macro_rules! call_original {
() => {
unary_call_original.unwrap()(unk1, uri, grpc_byte_buffer, unk4, unk5, unk6)
@@ -56,73 +45,45 @@ def_hook!(
let mut env = java_vm.get_env().expect("Failed to get JNIEnv");
let slice_buffer_length = slice_buffer.length as usize;
let jni_buffer = env
.new_byte_array(slice_buffer_length as i32)
.expect("Failed to create new byte array");
env.set_byte_array_region(
&jni_buffer,
0,
std::slice::from_raw_parts(slice_buffer.data as *const i8, slice_buffer_length),
)
.expect("Failed to set byte array region");
let jni_buffer = env.new_byte_array(slice_buffer_length as i32).expect("Failed to create new byte array");
env.set_byte_array_region(&jni_buffer, 0, std::slice::from_raw_parts(slice_buffer.data as *const i8, slice_buffer_length)).expect("Failed to set byte array region");
let uri_str = CStr::from_ptr(uri).to_str().unwrap();
let native_request_data_object = env
.call_method_unchecked(
common::native_lib_instance(),
NATIVE_LIB_ON_UNARY_CALL_METHOD.get().unwrap(),
ReturnType::Object,
&[
JValue::from(&env.new_string(uri_str).unwrap()).as_jni(),
JValue::from(&jni_buffer).as_jni(),
],
)
.expect("Failed to call onNativeUnaryCall method")
.l()
.unwrap();
let native_request_data_object = env.call_method_unchecked(
common::native_lib_instance(),
NATIVE_LIB_ON_UNARY_CALL_METHOD.get().unwrap(),
ReturnType::Object,
&[
JValue::from(&env.new_string(uri_str).unwrap()).as_jni(),
JValue::from(&jni_buffer).as_jni()
]
).expect("Failed to call onNativeUnaryCall method").l().unwrap();
if native_request_data_object.is_null() {
return call_original!();
}
let is_canceled = env
.get_field(&native_request_data_object, "canceled", "Z")
.expect("Failed to get canceled field")
.z()
.unwrap();
let is_canceled = env.get_field(&native_request_data_object, "canceled", "Z").expect("Failed to get canceled field").z().unwrap();
if is_canceled {
info!("canceled request for {}", uri_str);
return std::ptr::null_mut();
}
let new_buffer: JByteArray = env
.get_field(&native_request_data_object, "buffer", "[B")
.expect("Failed to get buffer field")
.l()
.unwrap()
.into();
let new_buffer_length = env
.get_array_length(&new_buffer)
.expect("Failed to get array length") as usize;
let new_buffer: JByteArray = env.get_field(&native_request_data_object, "buffer", "[B").expect("Failed to get buffer field").l().unwrap().into();
let new_buffer_length = env.get_array_length(&new_buffer).expect("Failed to get array length") as usize;
let mut new_buffer_data = Box::new(vec![0i8; new_buffer_length]);
env.get_byte_array_region(&new_buffer, 0, new_buffer_data.as_mut_slice())
.expect("Failed to get byte array region");
env.get_byte_array_region(&new_buffer, 0, new_buffer_data.as_mut_slice()).expect("Failed to get byte array region");
let ref_counter_struct_size =
(slice_buffer.data as usize) - (slice_buffer.ref_counter as usize);
let ref_counter_struct_size = (slice_buffer.data as usize) - (slice_buffer.ref_counter as usize);
//we need to allocate a new ref_counter struct and copy the old ref_counter and the new_buffer to it
let new_ref = {
let new_ref = libc::malloc(ref_counter_struct_size + new_buffer_length) as *mut c_void;
libc::memcpy(new_ref, slice_buffer.ref_counter, ref_counter_struct_size);
libc::memcpy(
new_ref.offset(ref_counter_struct_size as isize),
new_buffer_data.as_ptr() as *const c_void,
new_buffer_length,
);
libc::memcpy(new_ref.offset(ref_counter_struct_size as isize), new_buffer_data.as_ptr() as *const c_void, new_buffer_length);
libc::free(slice_buffer.ref_counter);
new_ref
};
@@ -143,25 +104,20 @@ def_hook!(
pub fn init() {
if let Some(signature) = sig::find_signature(
&common::CLIENT_MODULE,
"AA A8 03 1F F8 ?? ?? 00 94 ?? ?? 05 91",
-0x47,
"0A 90 00 F0 3F F9",
-0x37,
"A8 03 1F F8 ?? ?? 00 94 ?? ?? ?? 91 ?? ?? ?? A9", -0x48,
"0A 90 00 F0 3F F9", -0x37
) {
dobby_hook!(signature as *mut c_void, unary_call);
common::attach_jni_env(|env| {
NATIVE_LIB_ON_UNARY_CALL_METHOD
.set(
env.get_method_id(
env.get_object_class(common::native_lib_instance()).unwrap(),
"onNativeUnaryCall",
"(Ljava/lang/String;[B)Lme/eternal/purrfectsnap/nativelib/NativeRequestData;",
)
.expect("Failed to get onNativeUnaryCall method id"),
)
.expect("unary call method already set");
NATIVE_LIB_ON_UNARY_CALL_METHOD.set(
env.get_method_id(
env.get_object_class(common::native_lib_instance()).unwrap(),
"onNativeUnaryCall",
"(Ljava/lang/String;[B)Lme/eternal/purrfectsnap/nativelib/NativeRequestData;"
).expect("Failed to get onNativeUnaryCall method id")
).expect("unary call method already set");
});
} else {
panic!("Can't find unaryCall signature");
error!("Can't find unaryCall signature");
}
}

View File

@@ -1,61 +0,0 @@
use procfs::process::MMapPath;
pub struct Elf<'elf> {
base_address: usize,
elf: goblin::elf::Elf<'elf>,
_buffer: &'elf [u8],
}
impl<'elf> Elf<'elf> {
pub fn from_maps(lib: &str) -> Option<Self> {
let maps = procfs::process::Process::myself().ok()?.maps().ok()?;
for memory_map in maps.iter() {
if let MMapPath::Path(path) = &memory_map.pathname {
let path = path.to_string_lossy();
if !path.contains(lib) {
continue;
}
let file_data = std::fs::read(path.to_string()).ok()?;
let file_buffer = Box::leak(file_data.into_boxed_slice());
if let Ok(elf) = goblin::elf::Elf::parse(file_buffer) {
return Some(Elf {
base_address: memory_map.address.0 as usize,
elf,
_buffer: file_buffer,
});
} else {
warn!(
"Failed to parse ELF for library {} at address {:x}",
lib, memory_map.address.0
);
}
}
}
None
}
pub fn get_symbol_address(&self, symbol: &str) -> Option<usize> {
for sym in &self.elf.dynsyms {
if let Some(name) = self.elf.dynstrtab.get_at(sym.st_name) {
if name == symbol {
return Some(self.base_address + sym.st_value as usize);
}
}
}
for sym in &self.elf.syms {
if let Some(name) = self.elf.strtab.get_at(sym.st_name) {
if name == symbol {
return Some(self.base_address + sym.st_value as usize);
}
}
}
None
}
}

View File

@@ -1,2 +1 @@
pub mod elf;
pub mod valdi_utils;

View File

@@ -43,12 +43,7 @@ pub struct ValdiModule {
impl ValdiModule {
pub fn parse(buffer: Vec<u8>) -> Result<ValdiModule, Error> {
let mut offset = 0;
let magic = u32::from_be_bytes([
buffer[offset],
buffer[offset + 1],
buffer[offset + 2],
buffer[offset + 3],
]);
let magic = u32::from_be_bytes([buffer[offset], buffer[offset + 1], buffer[offset + 2], buffer[offset + 3]]);
offset += 4;
@@ -90,12 +85,13 @@ impl ValdiModule {
tags.push(ModuleTag::new(has_padding, tag_buffer));
}
let tags = tags
.chunks(2)
.map(|chunk| (chunk[0].clone(), chunk[1].clone()))
.collect();
let tags = tags.chunks(2).map(|chunk| {
(chunk[0].clone(), chunk[1].clone())
}).collect();
Ok(ValdiModule { tags })
Ok(ValdiModule {
tags,
})
}
pub fn to_bytes(&self) -> Vec<u8> {

View File

@@ -1,29 +1,35 @@
use super::util::valdi_utils::{ModuleTag, ValdiModule};
use crate::{config, def_hook, dobby_hook, modules::util::elf, util::get_jni_string};
#![allow(dead_code, unused_imports)]
use super::util::valdi_utils::{ValdiModule, ModuleTag};
use std::{collections::HashMap, ffi::c_void, sync::Mutex};
use jni::{objects::JString, JNIEnv};
use once_cell::sync::Lazy;
use std::{
collections::HashMap,
ffi::c_void,
sync::Mutex,
};
use crate::{common, config, def_hook, dobby_hook, dobby_hook_sym, util::get_jni_string};
static AASSET_MAP: Lazy<Mutex<HashMap<usize, Vec<u8>>>> = Lazy::new(|| Mutex::new(HashMap::new()));
static LOADER_DATA: Mutex<Option<String>> = Mutex::new(None);
def_hook!(aasset_get_length, i32, |arg0: *mut c_void| {
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
return buffer.len() as i32;
def_hook!(
aasset_get_length,
i32,
|arg0: *mut c_void| {
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
return buffer.len() as i32;
}
aasset_get_length_original.unwrap()(arg0)
}
aasset_get_length_original.unwrap()(arg0)
});
);
def_hook!(aasset_get_buffer, *const c_void, |arg0: *mut c_void| {
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
return buffer.as_ptr() as *const c_void;
def_hook!(
aasset_get_buffer,
*const c_void,
|arg0: *mut c_void| {
if let Some(buffer) = AASSET_MAP.lock().unwrap().get(&(arg0 as usize)) {
return buffer.as_ptr() as *const c_void;
}
aasset_get_buffer_original.unwrap()(arg0)
}
aasset_get_buffer_original.unwrap()(arg0)
});
);
def_hook!(
aasset_manager_open,
@@ -39,13 +45,9 @@ def_hook!(
let loader_data = LOADER_DATA.lock().unwrap().clone().expect("No loader data");
let archive_buffer: Vec<u8> =
std::slice::from_raw_parts(asset_buffer as *const u8, asset_length as usize)
.to_vec();
let decompressed = zstd::stream::decode_all(&archive_buffer[..])
.expect("Failed to decompress valdi archive");
let mut valdi_module =
ValdiModule::parse(decompressed).expect("Failed to parse valdi module");
let archive_buffer: Vec<u8> = std::slice::from_raw_parts(asset_buffer as *const u8, asset_length as usize).to_vec();
let decompressed = zstd::stream::decode_all(&archive_buffer[..]).expect("Failed to decompress valdi archive");
let mut valdi_module = ValdiModule::parse(decompressed).expect("Failed to parse valdi module");
let mut tags = valdi_module.get_tags();
let mut new_tags = Vec::new();
@@ -56,22 +58,19 @@ def_hook!(
continue;
}
let old_file_name = name.split_once(".").unwrap().0.to_owned()
+ rand::random::<u32>().to_string().as_str();
let old_file_name = name.split_once(".").unwrap().0.to_owned() + rand::random::<u32>().to_string().as_str();
tag1.set_buffer((old_file_name.to_owned() + ".js").as_bytes().to_vec());
let original_module_path =
path.split_once(".").unwrap().0.to_owned() + "/" + &old_file_name;
let original_module_path = path.split_once(".").unwrap().0.to_owned() + "/" + &old_file_name;
let hooked_module = format!(
"{};module.exports = require(\"{}\");",
loader_data, original_module_path
let hooked_module = format!("{};module.exports = require(\"{}\");", loader_data, original_module_path);
new_tags.push(
(
ModuleTag::new(true, name.as_bytes().to_vec()),
ModuleTag::new(true, hooked_module.as_bytes().to_vec())
)
);
new_tags.push((
ModuleTag::new(true, name.as_bytes().to_vec()),
ModuleTag::new(true, hooked_module.as_bytes().to_vec()),
));
debug!("Valdi loader injected in {}", name);
break;
}
@@ -80,22 +79,22 @@ def_hook!(
valdi_module.set_tags(tags);
let compressed = valdi_module.to_bytes();
let compressed =
zstd::stream::encode_all(&compressed[..], 3).expect("Failed to compress");
let compressed = zstd::stream::encode_all(&compressed[..], 3).expect("Failed to compress");
AASSET_MAP
.lock()
.unwrap()
.insert(handle as usize, compressed);
AASSET_MAP.lock().unwrap().insert(handle as usize, compressed);
}
handle
}
);
def_hook!(aasset_close, c_void, |handle: *mut c_void| {
AASSET_MAP.lock().unwrap().remove(&(handle as usize));
aasset_close_original.unwrap()(handle)
});
def_hook!(
aasset_close,
c_void,
|handle: *mut c_void| {
AASSET_MAP.lock().unwrap().remove(&(handle as usize));
aasset_close_original.unwrap()(handle)
}
);
pub fn set_valdi_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
let new_code = get_jni_string(&mut env, code).expect("Failed to get loader code");
@@ -104,33 +103,11 @@ pub fn set_valdi_loader(mut env: JNIEnv, _: *mut c_void, code: JString) {
pub fn init() {
if !config::native_config().valdi_hooks {
return;
return
}
let lib_android =
elf::Elf::from_maps("/libandroid.so").expect("Failed to find libandroid.so in maps");
if let Some(ptr) = lib_android.get_symbol_address("AAsset_getBuffer") {
dobby_hook!(ptr as _, aasset_get_buffer);
} else {
panic!("Failed to find AAsset_getBuffer symbol");
}
if let Some(ptr) = lib_android.get_symbol_address("AAsset_getLength") {
dobby_hook!(ptr as _, aasset_get_length);
} else {
panic!("Failed to find AAsset_getLength symbol");
}
if let Some(ptr) = lib_android.get_symbol_address("AAsset_close") {
dobby_hook!(ptr as _, aasset_close);
} else {
panic!("Failed to find AAsset_close symbol");
}
if let Some(ptr) = lib_android.get_symbol_address("AAssetManager_open") {
dobby_hook!(ptr as _, aasset_manager_open);
} else {
panic!("Failed to find AAssetManager_open symbol");
}
dobby_hook_sym!("libandroid.so", "AAsset_getBuffer", aasset_get_buffer);
dobby_hook_sym!("libandroid.so", "AAsset_getLength", aasset_get_length);
dobby_hook_sym!("libandroid.so", "AAsset_close", aasset_close);
dobby_hook_sym!("libandroid.so", "AAssetManager_open", aasset_manager_open);
}

View File

@@ -61,18 +61,8 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
let mut mask = Vec::new();
let mut i = 0;
if let Some(cache) = SIGNATURE_CACHE
.lock()
.unwrap()
.iter()
.find(|(sig, _)| sig == pattern)
{
return cache
.1
.clone()
.into_iter()
.map(|offset| module_base + offset)
.collect();
if let Some(cache) = SIGNATURE_CACHE.lock().unwrap().iter().find(|(sig, _)| sig == pattern) {
return cache.1.clone().into_iter().map(|offset| module_base + offset).collect();
}
while i < pattern.len() {
@@ -80,7 +70,7 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
bytes.push(0);
mask.push('?');
} else {
bytes.push(u8::from_str_radix(&pattern[i..i + 2], 16).unwrap());
bytes.push(u8::from_str_radix(&pattern[i..i+2], 16).unwrap());
mask.push('x');
}
i += 3;
@@ -102,10 +92,7 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
}
if found {
if once {
SIGNATURE_CACHE
.lock()
.unwrap()
.push((pattern.to_string(), vec![i]));
SIGNATURE_CACHE.lock().unwrap().push((pattern.to_string(), vec![i]));
return vec![module_base + i];
}
results.push(module_base + i);
@@ -113,22 +100,14 @@ pub fn find_signatures(module_base: usize, bytes_buffer: &[u8], pattern: &str, o
i += 1;
}
SIGNATURE_CACHE
.lock()
.unwrap()
.push((pattern.to_string(), results.clone()));
SIGNATURE_CACHE.lock().unwrap().push((pattern.to_string(), results.clone()));
results
}
pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Option<usize> {
let executable_regions = mapped_lib
.regions
.iter()
.filter(|region| {
region.perms.contains(MMPermissions::EXECUTE)
&& region.perms.contains(MMPermissions::READ)
})
.collect::<Vec<_>>();
let executable_regions = mapped_lib.regions.iter().filter(|region| {
region.perms.contains(MMPermissions::EXECUTE)
}).collect::<Vec<_>>();
for region in executable_regions {
let size = (region.end - region.start) as usize;
@@ -138,27 +117,16 @@ pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Optio
let bytes_buffer = match read_region_bytes(module_base, size) {
Some(buffer) => buffer,
None => {
warn!(
"Unable to read executable region: {:#x} - {:#x}",
region.start, region.end
);
warn!("Unable to read executable region: {:#x} - {:#x}", region.start, region.end);
continue;
}
};
let results = find_signatures(module_base, &bytes_buffer, pattern, true);
if results.is_empty() {
warn!(
"Signature not found in region: {:#x} - {:#x}",
region.start, region.end
);
warn!("Signature not found in region: {:#x} - {:#x}", region.start, region.end);
} else {
debug!(
"Found {} results in region: {:#x} - {:#x}",
results.len(),
region.start,
region.end
);
debug!("Found {} results in region: {:#x} - {:#x}", results.len(), region.start, region.end);
return Some(results[0]);
}
}
@@ -167,21 +135,13 @@ pub fn find_signature_executable(mapped_lib: &MappedLib, pattern: &str) -> Optio
None
}
pub fn find_signature(
mapped_lib: &MappedLib,
_arm64_pattern: &str,
_arm64_offset: i64,
_arm32_pattern: &str,
_arm32_offset: i64,
) -> Option<usize> {
pub fn find_signature(mapped_lib: &MappedLib, _arm64_pattern: &str, _arm64_offset: i64, _arm32_pattern: &str, _arm32_offset: i64) -> Option<usize> {
#[cfg(target_arch = "aarch64")]
{
return find_signature_executable(mapped_lib, _arm64_pattern)
.map(|address| (address as i64 + _arm64_offset) as usize);
return find_signature_executable(mapped_lib, _arm64_pattern).map(|address| (address as i64 + _arm64_offset) as usize);
}
#[cfg(target_arch = "arm")]
{
return find_signature_executable(mapped_lib, _arm32_pattern)
.map(|address| (address as i64 + _arm32_offset) as usize);
return find_signature_executable(mapped_lib, _arm32_pattern).map(|address| (address as i64 + _arm32_offset) as usize);
}
}