You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
init-sigstop-based ptrace zygisk
This commit is contained in:
@@ -23,10 +23,6 @@ proc-maps = "0.3"
|
||||
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread"] }
|
||||
tokio = { version = "1.28", features = ["full"] }
|
||||
|
||||
binder = { git = "https://github.com/Kernel-SU/binder_rs", rev = "c9f2b62d6a744fd2264056c638c1b061a6a2932d" }
|
||||
fuser = { git = "https://github.com/Dr-TSNG/fuser", default-features = false }
|
||||
ptrace-do = { git = "https://github.com/5ec1cff/ptrace-do" }
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
opt-level = "z"
|
||||
|
||||
@@ -22,8 +22,6 @@ pub const PATH_ZYGISK_LIB: &str = concatcp!(lp_select!("/system/lib", "/system/l
|
||||
pub const PATH_WORK_DIR: &str = "/dev/zygisk"; // TODO: Replace with /debug_ramdisk/zygisk
|
||||
pub const PATH_PROP_OVERLAY: &str = concatcp!(PATH_WORK_DIR, "/module.prop");
|
||||
pub const PATH_CP_SOCKET: &str = concatcp!(PATH_WORK_DIR, lp_select!("/cp32.sock", "/cp64.sock"));
|
||||
pub const PATH_FUSE_DIR: &str = concatcp!(PATH_WORK_DIR, "/fuse");
|
||||
pub const PATH_FUSE_PCL: &str = concatcp!(PATH_FUSE_DIR, "/preloaded-classes");
|
||||
|
||||
pub const PATH_MODULES_DIR: &str = "..";
|
||||
pub const PATH_MODULE_PROP: &str = "module.prop";
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
use std::cmp::min;
|
||||
use anyhow::{bail, Result};
|
||||
use std::ffi::OsStr;
|
||||
use std::{fs, thread};
|
||||
use std::io::Read;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::{mpsc, Mutex};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use fuser::{FileAttr, Filesystem, FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyOpen, Request};
|
||||
use libc::ENOENT;
|
||||
use log::{error, info};
|
||||
use rustix::fs::UnmountFlags;
|
||||
use rustix::mount::{mount_bind, unmount};
|
||||
use rustix::path::Arg;
|
||||
use crate::constants;
|
||||
use crate::utils::LateInit;
|
||||
|
||||
pub struct DelegateFilesystem;
|
||||
|
||||
const fn attr(inode: u64, size: u64, kind: FileType) -> FileAttr {
|
||||
FileAttr {
|
||||
ino: inode,
|
||||
size,
|
||||
blocks: 0,
|
||||
atime: SystemTime::UNIX_EPOCH,
|
||||
mtime: SystemTime::UNIX_EPOCH,
|
||||
ctime: SystemTime::UNIX_EPOCH,
|
||||
crtime: SystemTime::UNIX_EPOCH,
|
||||
kind,
|
||||
perm: 0o644,
|
||||
nlink: 0,
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
rdev: 0,
|
||||
blksize: 0,
|
||||
flags: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const INO_DIR: u64 = 1;
|
||||
const INO_PCL: u64 = 2;
|
||||
|
||||
static ATTR_DIR: FileAttr = attr(INO_DIR, 0, FileType::Directory);
|
||||
static ATTR_PCL: LateInit<FileAttr> = LateInit::new();
|
||||
|
||||
static PCL_CONTENT: LateInit<Vec<u8>> = LateInit::new();
|
||||
|
||||
const ENTRIES: &[(u64, FileType, &str)] = &[
|
||||
(INO_DIR, FileType::Directory, "."),
|
||||
(INO_DIR, FileType::Directory, ".."),
|
||||
(INO_PCL, FileType::RegularFile, "preloaded-classes"),
|
||||
];
|
||||
|
||||
const TTL: Duration = Duration::from_secs(1);
|
||||
|
||||
fn ptrace_zygote64(pid: u32) -> Result<()> {
|
||||
static LAST: Mutex<u32> = Mutex::new(0);
|
||||
|
||||
let mut last = LAST.lock().unwrap();
|
||||
if *last == pid {
|
||||
return Ok(());
|
||||
}
|
||||
*last = pid;
|
||||
let (sender, receiver) = mpsc::channel::<()>();
|
||||
|
||||
let worker = move || -> Result<()> {
|
||||
let mut child = Command::new(constants::PATH_PTRACE_BIN64).stdout(Stdio::piped()).arg(format!("{}", pid)).spawn()?;
|
||||
child.stdout.as_mut().unwrap().read_exact(&mut [0u8; 1])?;
|
||||
info!("child attached");
|
||||
sender.send(())?;
|
||||
let result = child.wait()?;
|
||||
info!("ptrace64 process status {}", result);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = worker() {
|
||||
error!("Crashed: {:?}", e);
|
||||
}
|
||||
});
|
||||
|
||||
receiver.recv()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ptrace_zygote32(pid: u32) -> Result<()> {
|
||||
static LAST: Mutex<u32> = Mutex::new(0);
|
||||
|
||||
let mut last = LAST.lock().unwrap();
|
||||
if *last == pid {
|
||||
return Ok(());
|
||||
}
|
||||
*last = pid;
|
||||
let (sender, receiver) = mpsc::channel::<()>();
|
||||
|
||||
let worker = move || -> Result<()> {
|
||||
let mut child = Command::new(constants::PATH_PTRACE_BIN32).stdout(Stdio::piped()).arg(format!("{}", pid)).spawn()?;
|
||||
child.stdout.as_mut().unwrap().read_exact(&mut [0u8; 1])?;
|
||||
info!("child attached");
|
||||
sender.send(())?;
|
||||
let result = child.wait()?;
|
||||
info!("ptrace32 process status {}", result);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = worker() {
|
||||
error!("Crashed: {:?}", e);
|
||||
}
|
||||
});
|
||||
|
||||
receiver.recv()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Filesystem for DelegateFilesystem {
|
||||
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
|
||||
if parent != INO_DIR {
|
||||
reply.error(ENOENT);
|
||||
return;
|
||||
}
|
||||
match name.as_str().unwrap() {
|
||||
"preloaded-classes" => reply.entry(&TTL, &ATTR_PCL, 0),
|
||||
_ => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
|
||||
fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
|
||||
match ino {
|
||||
INO_DIR => reply.attr(&TTL, &ATTR_DIR),
|
||||
INO_PCL => reply.attr(&TTL, &ATTR_PCL),
|
||||
_ => reply.error(ENOENT),
|
||||
}
|
||||
}
|
||||
|
||||
fn open(&mut self, req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) {
|
||||
if ino == INO_PCL {
|
||||
let pid = req.pid();
|
||||
let process = format!("/proc/{}/cmdline", pid);
|
||||
let process = fs::read_to_string(process).unwrap();
|
||||
let process = &process[..process.find('\0').unwrap()];
|
||||
info!("Process {} is reading preloaded-classes", process);
|
||||
match process {
|
||||
"zygote64" => ptrace_zygote64(pid).unwrap(),
|
||||
"zygote" => ptrace_zygote32(pid).unwrap(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
reply.opened(0, 0);
|
||||
}
|
||||
|
||||
fn read(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, size: u32, _flags: i32, _lock_owner: Option<u64>, reply: ReplyData) {
|
||||
let offset = offset as usize;
|
||||
let size = size as usize;
|
||||
if ino == INO_PCL {
|
||||
let len = PCL_CONTENT.len();
|
||||
if offset >= len {
|
||||
reply.data(&[]);
|
||||
} else {
|
||||
let end = min(offset + size, len);
|
||||
reply.data(&PCL_CONTENT[offset..end]);
|
||||
}
|
||||
} else {
|
||||
reply.error(ENOENT);
|
||||
}
|
||||
}
|
||||
|
||||
fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) {
|
||||
if ino != INO_DIR {
|
||||
reply.error(ENOENT);
|
||||
return;
|
||||
}
|
||||
for (i, entry) in ENTRIES.iter().enumerate().skip(offset as usize) {
|
||||
if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
reply.ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
info!("Start zygisk fuse");
|
||||
fs::create_dir(constants::PATH_WORK_DIR)?;
|
||||
fs::create_dir(constants::PATH_FUSE_DIR)?;
|
||||
PCL_CONTENT.init(fs::read(constants::PATH_PCL)?);
|
||||
ATTR_PCL.init(attr(INO_PCL, PCL_CONTENT.len() as u64, FileType::RegularFile));
|
||||
let options = [
|
||||
fuser::MountOption::FSName(String::from("zygisk")),
|
||||
fuser::MountOption::AllowOther,
|
||||
fuser::MountOption::RO,
|
||||
];
|
||||
let session = fuser::spawn_mount2(
|
||||
DelegateFilesystem,
|
||||
constants::PATH_FUSE_DIR,
|
||||
&options,
|
||||
)?;
|
||||
mount_bind(constants::PATH_FUSE_PCL, constants::PATH_PCL)?;
|
||||
let crash = session.guard.join();
|
||||
unmount(constants::PATH_PCL, UnmountFlags::DETACH)?;
|
||||
match crash {
|
||||
Err(e) => bail!("Fuse mount crashed: {:?}", e),
|
||||
_ => bail!("Fuse mount exited unexpectedly"),
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
mod constants;
|
||||
mod dl;
|
||||
mod fuse;
|
||||
mod ptrace;
|
||||
mod root_impl;
|
||||
mod utils;
|
||||
mod watchdog;
|
||||
@@ -28,9 +26,7 @@ fn start(name: &str) -> Result<()> {
|
||||
root_impl::setup();
|
||||
match name.trim_start_matches("zygisk-") {
|
||||
"wd" => async_start(watchdog::main())?,
|
||||
"fuse" => fuse::main()?,
|
||||
lp_select!("cp32", "cp64") => zygiskd::main()?,
|
||||
lp_select!("ptrace32", "ptrace64") => ptrace::main()?,
|
||||
_ => println!("Available commands: wd, fuse, cp, ptrace"),
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
use log::{debug, info};
|
||||
use std::ffi::CString;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use rustix::path::Arg;
|
||||
use proc_maps::{get_process_maps, MapRange, Pid};
|
||||
use ptrace_do::{RawProcess, TracedProcess};
|
||||
use rustix::process::getpid;
|
||||
use crate::{constants, lp_select};
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
const ANDROID_LIBC: &str = "bionic/libc.so";
|
||||
const ANDROID_LIBDL: &str = "bionic/libdl.so";
|
||||
|
||||
fn find_module_for_pid(pid: Pid, library: &str) -> Result<MapRange> {
|
||||
let maps = get_process_maps(pid)?;
|
||||
for map in maps.into_iter() {
|
||||
if let Some(p) = map.filename() {
|
||||
if p.as_str()?.contains(library) {
|
||||
return Ok(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
bail!("Cannot find module {library} for pid {pid}");
|
||||
}
|
||||
|
||||
fn find_remote_procedure(
|
||||
pid: Pid,
|
||||
library: &str,
|
||||
local_addr: usize,
|
||||
) -> Result<usize> {
|
||||
let local_module = find_module_for_pid(getpid().as_raw_nonzero().get(), library)?;
|
||||
debug!(
|
||||
"Identifed local range {library} ({:?}) at {:x}",
|
||||
local_module.filename(),
|
||||
local_module.start()
|
||||
);
|
||||
|
||||
let remote_module = find_module_for_pid(pid, library)?;
|
||||
debug!(
|
||||
"Identifed remote range {library} ({:?}) at {:x}",
|
||||
remote_module.filename(),
|
||||
remote_module.start()
|
||||
);
|
||||
|
||||
Ok(local_addr - local_module.start() + remote_module.start())
|
||||
}
|
||||
|
||||
fn ptrace_zygote(pid: u32) -> Result<()> {
|
||||
info!("Injecting into pid {}", pid);
|
||||
let zygisk_lib = CString::new(constants::PATH_ZYGISK_LIB)?;
|
||||
let libc_base = find_module_for_pid(pid as i32, ANDROID_LIBC)?.start();
|
||||
let mmap_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBC,
|
||||
libc::mmap as usize,
|
||||
)?;
|
||||
let munmap_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBC,
|
||||
libc::munmap as usize,
|
||||
)?;
|
||||
let dlopen_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBDL,
|
||||
libc::dlopen as usize,
|
||||
)?;
|
||||
let dlsym_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBDL,
|
||||
libc::dlsym as usize,
|
||||
)?;
|
||||
let errno_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBC,
|
||||
libc::__errno as usize,
|
||||
)?;
|
||||
let dlerror_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBDL,
|
||||
libc::dlerror as usize,
|
||||
)?;
|
||||
let strlen_remote = find_remote_procedure(
|
||||
pid as i32,
|
||||
ANDROID_LIBC,
|
||||
libc::strlen as usize,
|
||||
)?;
|
||||
|
||||
let tracer = TracedProcess::attach(RawProcess::new(pid as i32))?;
|
||||
std::io::stdout().write(b"1")?;
|
||||
info!("attached process {}", pid);
|
||||
std::io::stdout().flush()?;
|
||||
let frame = tracer.next_frame()?;
|
||||
debug!("Waited for a frame");
|
||||
|
||||
// Map a buffer in the remote process
|
||||
debug!("remote mmap addr {:x}", mmap_remote);
|
||||
let mmap_params: [usize; 6] = [
|
||||
0,
|
||||
0x1000,
|
||||
(libc::PROT_READ | libc::PROT_WRITE) as usize,
|
||||
(libc::MAP_ANONYMOUS | libc::MAP_PRIVATE) as usize,
|
||||
0,
|
||||
0,
|
||||
];
|
||||
let mut arr: Vec<u8> = Vec::new();
|
||||
for p in mmap_params {
|
||||
arr.extend_from_slice(&p.to_le_bytes());
|
||||
}
|
||||
arr.as_slice();
|
||||
let (regs, mut frame) = frame.invoke_remote(
|
||||
mmap_remote,
|
||||
libc_base,
|
||||
&mmap_params,
|
||||
)?;
|
||||
let buf_addr = regs.return_value();
|
||||
debug!("remote stopped at addr {:x}", regs.program_counter());
|
||||
if regs.program_counter() != libc_base {
|
||||
let data = std::mem::MaybeUninit::<libc::siginfo_t>::uninit();
|
||||
let siginfo = unsafe {
|
||||
libc::ptrace(libc::PTRACE_GETSIGINFO, pid, 0, &data);
|
||||
data.assume_init()
|
||||
};
|
||||
bail!(
|
||||
"stopped at unexpected addr {:x} signo {} si_code {} si_addr {:?}",
|
||||
regs.program_counter(),
|
||||
siginfo.si_signo,
|
||||
siginfo.si_code,
|
||||
unsafe { siginfo.si_addr() },
|
||||
);
|
||||
}
|
||||
if buf_addr == usize::MAX {
|
||||
debug!("errno remote {:x}", errno_remote);
|
||||
let (regs, frame) = frame.invoke_remote(
|
||||
errno_remote,
|
||||
libc_base,
|
||||
&[],
|
||||
)?;
|
||||
debug!("errno called");
|
||||
if regs.program_counter() != libc_base {
|
||||
bail!("stopped at unexpected addr {:x} when getting errno", regs.program_counter());
|
||||
}
|
||||
let err_addr = regs.return_value();
|
||||
let mut buf = [0u8; 4];
|
||||
frame.read_memory_mut(err_addr, &mut buf)?;
|
||||
let err = i32::from_le_bytes(buf);
|
||||
bail!("remote failed with {}", err);
|
||||
}
|
||||
debug!("Buffer addr: {:x}", buf_addr);
|
||||
|
||||
// Load zygisk into remote process
|
||||
frame.write_memory(buf_addr, zygisk_lib.as_bytes_with_nul())?;
|
||||
let (regs, mut frame) = frame.invoke_remote(
|
||||
dlopen_remote,
|
||||
libc_base,
|
||||
&[buf_addr, libc::RTLD_NOW as usize],
|
||||
)?;
|
||||
let handle = regs.return_value();
|
||||
debug!("Load zygisk into remote process: {:x}", handle);
|
||||
if regs.program_counter() != libc_base {
|
||||
let data = std::mem::MaybeUninit::<libc::siginfo_t>::uninit();
|
||||
let siginfo = unsafe {
|
||||
libc::ptrace(libc::PTRACE_GETSIGINFO, pid, 0, &data);
|
||||
data.assume_init()
|
||||
};
|
||||
bail!(
|
||||
"stopped at unexpected addr {:x} signo {} si_code {} si_addr {:?}",
|
||||
regs.program_counter(),
|
||||
siginfo.si_signo,
|
||||
siginfo.si_code,
|
||||
unsafe { siginfo.si_addr() },
|
||||
);
|
||||
}
|
||||
if handle == 0 {
|
||||
debug!("got handle 0");
|
||||
let (regs, frame) = frame.invoke_remote(
|
||||
dlerror_remote,
|
||||
libc_base,
|
||||
&[],
|
||||
)?;
|
||||
let err_addr = regs.return_value();
|
||||
if err_addr == 0 {
|
||||
bail!("dlerror err addr 0");
|
||||
}
|
||||
debug!("err addr {:x}", err_addr);
|
||||
let (regs, frame) = frame.invoke_remote(
|
||||
strlen_remote,
|
||||
libc_base,
|
||||
&[err_addr],
|
||||
)?;
|
||||
let len = regs.return_value();
|
||||
if len == 0 {
|
||||
bail!("dlerror len 0");
|
||||
}
|
||||
debug!("err len {}", len);
|
||||
let mut buf = vec![0u8; len];
|
||||
frame.read_memory_mut(err_addr, buf.as_mut_slice())?;
|
||||
bail!("err {:?}", buf);
|
||||
}
|
||||
|
||||
let entry = CString::new("entry")?;
|
||||
frame.write_memory(buf_addr, entry.as_bytes_with_nul())?;
|
||||
let (regs, frame) = frame.invoke_remote(
|
||||
dlsym_remote,
|
||||
libc_base,
|
||||
&[handle, buf_addr],
|
||||
)?;
|
||||
let entry = regs.return_value();
|
||||
debug!("Call zygisk entry: {:x}", entry);
|
||||
let (_, frame) = frame.invoke_remote(
|
||||
entry,
|
||||
libc_base,
|
||||
&[handle],
|
||||
)?;
|
||||
|
||||
// Cleanup
|
||||
let _ = frame.invoke_remote(
|
||||
munmap_remote,
|
||||
libc_base,
|
||||
&[buf_addr],
|
||||
)?;
|
||||
debug!("Cleaned up");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
info!("Start zygisk ptrace");
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let pid = args[1].parse::<u32>().unwrap();
|
||||
info!("ptracing {} pid {}", lp_select!("zygote32", "zygote64"), pid);
|
||||
ptrace_zygote(pid)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,8 +4,6 @@ use std::fs;
|
||||
use std::future::Future;
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use binder::IBinder;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error, info};
|
||||
@@ -128,27 +126,6 @@ async fn spawn_daemon() -> Result<()> {
|
||||
futures.push(Box::pin(spawn_daemon(it)));
|
||||
}
|
||||
|
||||
async fn binder_listener() -> Result<()> {
|
||||
let mut binder = loop {
|
||||
match binder::get_service("activity") {
|
||||
Some(binder) => break binder,
|
||||
None => {
|
||||
log::trace!("System server not ready, wait for 1s...");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
info!("System server ready");
|
||||
|
||||
loop {
|
||||
if binder.ping_binder().is_err() { break; }
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
bail!("System server died");
|
||||
}
|
||||
futures.push(Box::pin(binder_listener()));
|
||||
|
||||
if let Err(e) = futures.next().await.unwrap() {
|
||||
error!("{}", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user