├── .cargo └── config ├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── bin └── cli.rs ├── build.rs └── src ├── lib.rs ├── remote_mem.rs ├── remote_module.rs ├── remote_proc.rs ├── shellcode.rs └── utils.rs /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "aarch64-linux-android" -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | tags: 7 | - "v*" 8 | pull_request: 9 | branches: ["main"] 10 | 11 | workflow_dispatch: 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: set up cargo-ndk 23 | run: cargo install cargo-ndk 24 | 25 | - name: set up aarch64 target 26 | run: rustup target add aarch64-linux-android 27 | 28 | - name: build 29 | run: cargo ndk --target aarch64-linux-android build --release 30 | 31 | - name: move files 32 | run: | 33 | mv target/aarch64-linux-android/release/linjector-cli ./linjector-cli 34 | 35 | - name: save hashes in env 36 | run: | 37 | echo '```' > hashes.txt 38 | echo "SHA256 hashes:" >> hashes.txt 39 | sha256sum linjector-cli >> hashes.txt 40 | echo '```' >> hashes.txt 41 | 42 | - name: release 43 | uses: softprops/action-gh-release@v1 44 | if: startsWith(github.ref, 'refs/tags/v') 45 | with: 46 | body_path: hashes.txt 47 | files: | 48 | ./linjector-cli 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linjector-rs" 3 | version = "0.1.2" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "linjector_rs" 8 | path = "src/lib.rs" 9 | 10 | [[bin]] 11 | name = "linjector-cli" 12 | path = "bin/cli.rs" 13 | 14 | [profile.release] 15 | opt-level = "z" 16 | debug = false 17 | lto = true 18 | debug-assertions = false 19 | codegen-units = 1 20 | panic = "abort" 21 | strip = true 22 | 23 | [dependencies] 24 | android_logger = "0.13.3" 25 | backtrace = "0.3.69" 26 | clap = { version = "4.4.7", features = ["derive"] } 27 | dynasm = "2.0.0" 28 | dynasmrt = "2.0.0" 29 | goblin = "0.7.1" 30 | hxdmp = "0.2.1" 31 | jni = { version = "0.21.1", default-features = false } 32 | log = "0.4.20" 33 | nix = { version = "0.27.1", features = ["uio"] } 34 | pretty-hex = "0.4.0" 35 | proc-maps = "0.3.2" 36 | simple_logger = "4.3.3" 37 | glob = "0.3.1" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2024 erfur 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linjector-rs 2 | 3 | Android port of [linux_injector](https://github.com/namazso/linux_injector). Library injection using /proc/mem, without ptrace. Only aarch64 is supported. 4 | 5 | To get an idea of how it works, you can read the [blog post](https://erfur.github.io/blog/dev/code-injection-without-ptrace). 6 | 7 | ## Usage 8 | 9 | ``` 10 | Inject code into a running process using /proc/mem 11 | 12 | Usage: linjector-cli [OPTIONS] --file 13 | 14 | Options: 15 | -p, --pid 16 | pid of the target process 17 | 18 | -a, --app-package-name 19 | target application's package name, (re)start the application and do injection 20 | 21 | -f, --file 22 | path of the library/shellcode to inject 23 | 24 | -i, --injection-type 25 | type of injection 26 | 27 | [default: raw-dlopen] 28 | 29 | Possible values: 30 | - raw-dlopen: Use dlopen to inject a library 31 | - memfd-dlopen: Use memfd_create and dlopen to inject a library 32 | - raw-shellcode: Inject raw shellcode 33 | 34 | --func-sym 35 | function to hijack for injection, in the form "lib.so!symbol_name" 36 | 37 | --var-sym 38 | variable to hijack for injection, in the form "lib.so!symbol_name" 39 | 40 | -d, --debug 41 | enable debug logs 42 | 43 | --logcat 44 | print logs to logcat 45 | 46 | -h, --help 47 | Print help (see a summary with '-h') 48 | 49 | -V, --version 50 | Print version 51 | ``` 52 | 53 | ## Modes 54 | 55 | **Currently only raw dlopen mode works**. Since SELinux doesn't allow calling dlopen on a memfd, memfd dlopen will not work. Shellcode mode is not yet implemented. 56 | 57 | -------------------------------------------------------------------------------- /bin/cli.rs: -------------------------------------------------------------------------------- 1 | use android_logger::Config; 2 | use clap::{Parser, ValueEnum}; 3 | use log::{error, info, warn, LevelFilter}; 4 | use simple_logger::SimpleLogger; 5 | 6 | /// Inject code into a running process using /proc/mem 7 | #[derive(Parser, Debug)] 8 | #[command(version, about)] 9 | struct Args { 10 | /// pid of the target process 11 | #[arg(short, long)] 12 | pid: Option, 13 | 14 | /// target application's package name, (re)start the application and do injection 15 | #[arg(short, long)] 16 | app_package_name: Option, 17 | 18 | /// path of the library/shellcode to inject 19 | #[arg(short, long)] 20 | file: String, 21 | 22 | /// type of injection 23 | #[arg(short, long, value_enum, default_value_t = InjectionType::RawDlopen)] 24 | injection_type: InjectionType, 25 | 26 | /// function to hijack for injection, 27 | /// in the form "lib.so!symbol_name" 28 | #[arg(long)] 29 | func_sym: Option, 30 | 31 | /// variable to hijack for injection, 32 | /// in the form "lib.so!symbol_name" 33 | #[arg(long)] 34 | var_sym: Option, 35 | 36 | /// enable debug logs 37 | #[arg(short, long)] 38 | debug: bool, 39 | 40 | /// print logs to logcat 41 | #[arg(long)] 42 | logcat: bool, 43 | } 44 | 45 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] 46 | enum InjectionType { 47 | /// Use dlopen to inject a library 48 | RawDlopen, 49 | /// Use memfd_create and dlopen to inject a library 50 | MemfdDlopen, 51 | /// Inject raw shellcode 52 | RawShellcode, 53 | } 54 | 55 | fn main() { 56 | let args = Args::parse(); 57 | 58 | if args.logcat { 59 | if args.debug { 60 | android_logger::init_once(Config::default().with_max_level(LevelFilter::Debug)); 61 | } else { 62 | android_logger::init_once(Config::default().with_max_level(LevelFilter::Info)); 63 | } 64 | } else if args.debug { 65 | SimpleLogger::new() 66 | .with_level(LevelFilter::Debug) 67 | .init() 68 | .unwrap(); 69 | } else { 70 | SimpleLogger::new() 71 | .with_level(LevelFilter::Info) 72 | .init() 73 | .unwrap(); 74 | } 75 | 76 | let mut target_pid = args.pid.unwrap_or(0); 77 | if target_pid <= 0 { 78 | let Some(name) = args.app_package_name else { 79 | error!("No pid or app_package_name is specified"); 80 | return; 81 | }; 82 | let Ok(app_pid) = linjector_rs::Injector::restart_app_and_get_pid(&name) else { 83 | error!("Can't restart package: {}, or cannot found pid", name); 84 | return; 85 | }; 86 | target_pid = app_pid as i32; 87 | } 88 | 89 | info!("target process pid: {}", target_pid); 90 | 91 | let mut injector = match linjector_rs::Injector::new(target_pid) { 92 | Ok(injector) => injector, 93 | Err(e) => { 94 | error!("Error creating injector: {:?}", e); 95 | std::process::exit(1); 96 | } 97 | }; 98 | 99 | match injector.set_file_path(args.file) { 100 | Ok(_) => {} 101 | Err(e) => { 102 | error!("Error setting file path: {:?}", e); 103 | std::process::exit(1); 104 | } 105 | } 106 | 107 | match args.injection_type { 108 | InjectionType::RawDlopen => { 109 | injector.use_raw_dlopen().unwrap(); 110 | } 111 | InjectionType::MemfdDlopen => { 112 | injector.use_memfd_dlopen().unwrap(); 113 | } 114 | InjectionType::RawShellcode => { 115 | injector.use_raw_shellcode().unwrap(); 116 | } 117 | } 118 | 119 | if let Some(func_sym) = &args.func_sym { 120 | let sym_pair: Vec<&str> = func_sym.split('!').collect(); 121 | if sym_pair.len() != 2 { 122 | error!("Invalid function symbol format, use lib.so!symbol_name"); 123 | std::process::exit(1); 124 | } 125 | match injector.set_func_sym(sym_pair[0], sym_pair[1]) { 126 | Ok(_) => {} 127 | Err(e) => { 128 | error!("Error setting function symbol: {:?}", e); 129 | std::process::exit(1); 130 | } 131 | }; 132 | } 133 | 134 | if let Some(var_sym) = &args.var_sym { 135 | let sym_pair: Vec<&str> = var_sym.split('!').collect(); 136 | if sym_pair.len() != 2 { 137 | error!("Invalid variable symbol format, use lib.so!symbol_name"); 138 | std::process::exit(1); 139 | } 140 | match injector.set_var_sym(sym_pair[0], sym_pair[1]) { 141 | Ok(_) => {} 142 | Err(e) => { 143 | error!("Error setting variable symbol: {:?}", e); 144 | std::process::exit(1); 145 | } 146 | }; 147 | } 148 | 149 | // if either func_sym or var_sym is not provided, use default symbols 150 | if args.func_sym.is_none() || args.var_sym.is_none() { 151 | warn!("function or variable symbol not specified, using defaults"); 152 | match injector.set_default_syms() { 153 | Ok(_) => {} 154 | Err(e) => { 155 | error!("Error setting default symbols: {:?}", e); 156 | std::process::exit(1); 157 | } 158 | }; 159 | } 160 | 161 | match injector.inject() { 162 | Ok(_) => { 163 | info!("injection successful"); 164 | } 165 | Err(e) => { 166 | error!("Error injecting: {:?}", e); 167 | std::process::exit(1); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::{env, path::{Path, PathBuf}}; 2 | 3 | fn main() { 4 | if env::var("CARGO_CFG_TARGET_OS").unwrap() == "android" { 5 | android(); 6 | } 7 | } 8 | 9 | fn android() { 10 | println!("cargo:rustc-link-lib=c++_shared"); 11 | 12 | if let Ok(output_path) = env::var("CARGO_NDK_OUTPUT_PATH") { 13 | let sysroot_libs_path = 14 | PathBuf::from(env::var_os("CARGO_NDK_SYSROOT_LIBS_PATH").unwrap()); 15 | let lib_path = sysroot_libs_path.join("libc++_shared.so"); 16 | std::fs::copy( 17 | lib_path, 18 | Path::new(&output_path) 19 | .join(&env::var("CARGO_NDK_ANDROID_TARGET").unwrap()) 20 | .join("libc++_shared.so"), 21 | ) 22 | .unwrap(); 23 | 24 | let lib_path2 = sysroot_libs_path.join("liblogger.so"); 25 | std::fs::copy( 26 | lib_path2, 27 | Path::new(&output_path) 28 | .join(&env::var("CARGO_NDK_ANDROID_TARGET").unwrap()) 29 | .join("liblogger.so"), 30 | ) 31 | .unwrap(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod remote_mem; 2 | mod remote_module; 3 | mod remote_proc; 4 | mod shellcode; 5 | mod utils; 6 | 7 | use std::collections::HashMap; 8 | 9 | #[macro_use] 10 | extern crate log; 11 | 12 | #[derive(Debug)] 13 | pub enum InjectionError { 14 | RemoteProcessError, 15 | RemoteMemoryError, 16 | RemoteModuleError, 17 | ModuleNotFound, 18 | SymbolNotFound, 19 | FileError, 20 | CommandError, 21 | ShellcodeError, 22 | PidNotFound, 23 | } 24 | 25 | pub struct Injector { 26 | pid: i32, 27 | remote_proc: remote_proc::RemoteProc, 28 | file_path: String, 29 | injection_type: InjectionType, 30 | target_func_sym_name: String, 31 | target_func_sym_addr: usize, 32 | target_var_sym_name: String, 33 | target_var_sym_addr: usize, 34 | module_cache: HashMap, 35 | sym_cache: HashMap, 36 | } 37 | 38 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 39 | enum InjectionType { 40 | /// Use dlopen to inject a library 41 | RawDlopen, 42 | /// Use memfd_create and dlopen to inject a library 43 | MemFdDlopen, 44 | /// Inject raw shellcode 45 | RawShellcode, 46 | } 47 | 48 | impl Injector { 49 | pub fn new(pid: i32) -> Result { 50 | info!("new injector for pid: {}", pid); 51 | Ok(Injector { 52 | pid, 53 | remote_proc: remote_proc::RemoteProc::new(pid)?, 54 | file_path: String::new(), 55 | injection_type: InjectionType::RawDlopen, 56 | target_func_sym_name: String::new(), 57 | target_func_sym_addr: 0, 58 | target_var_sym_name: String::new(), 59 | target_var_sym_addr: 0, 60 | module_cache: HashMap::new(), 61 | sym_cache: HashMap::new(), 62 | }) 63 | } 64 | 65 | pub fn set_file_path(&mut self, file_path: String) -> Result<&mut Self, InjectionError> { 66 | let file = std::fs::File::open(&file_path); 67 | if file.is_err() { 68 | error!("File not found: {}", file_path); 69 | return Err(InjectionError::FileError); 70 | } 71 | 72 | self.file_path = file_path; 73 | Ok(self) 74 | } 75 | 76 | fn prepare_file(&self) -> Result { 77 | if self.injection_type == InjectionType::RawDlopen 78 | || self.injection_type == InjectionType::MemFdDlopen 79 | { 80 | utils::verify_elf_file(self.file_path.as_str())?; 81 | } 82 | 83 | let tmp_file_path = utils::copy_file_to_tmp(self.file_path.as_str())?; 84 | utils::fix_file_context(tmp_file_path.as_str())?; 85 | utils::fix_file_permissions(tmp_file_path.as_str())?; 86 | utils::print_file_hexdump(tmp_file_path.as_str())?; 87 | Ok(tmp_file_path) 88 | } 89 | 90 | fn add_sym(&mut self, module_name: &str, sym_name: &str) -> Result { 91 | debug!("add_sym: {}!{}", module_name, sym_name); 92 | 93 | if !self.module_cache.contains_key(module_name) { 94 | let module = self.remote_proc.module(module_name)?; 95 | self.module_cache.insert(module_name.to_string(), module); 96 | } 97 | 98 | let module = self.module_cache.get(module_name).unwrap(); 99 | debug!("add_sym: {} 0x{:x}", module_name, module.vm_addr); 100 | 101 | if !self.sym_cache.contains_key(sym_name) { 102 | let sym = module.dlsym_from_fs(sym_name)?; 103 | self.sym_cache.insert(sym_name.to_string(), sym); 104 | } 105 | 106 | debug!( 107 | "add_sym: {} 0x{:x}", 108 | sym_name, 109 | self.sym_cache.get(sym_name).unwrap() 110 | ); 111 | Ok(*self.sym_cache.get(sym_name).unwrap()) 112 | } 113 | 114 | pub fn set_func_sym( 115 | &mut self, 116 | module_name: &str, 117 | sym_name: &str, 118 | ) -> Result<&mut Self, InjectionError> { 119 | let sym_addr = self.add_sym(module_name, sym_name)?; 120 | self.target_func_sym_name = sym_name.to_string(); 121 | self.target_func_sym_addr = sym_addr; 122 | debug!("set_func_sym: {} 0x{:x}", sym_name, sym_addr); 123 | Ok(self) 124 | } 125 | 126 | pub fn set_var_sym( 127 | &mut self, 128 | module_name: &str, 129 | sym_name: &str, 130 | ) -> Result<&mut Self, InjectionError> { 131 | let sym_addr = self.add_sym(module_name, sym_name)?; 132 | self.target_var_sym_name = sym_name.to_string(); 133 | self.target_var_sym_addr = sym_addr; 134 | debug!("set_var_sym: {} 0x{:x}", sym_name, sym_addr); 135 | Ok(self) 136 | } 137 | 138 | pub fn set_default_syms(&mut self) -> Result<&mut Self, InjectionError> { 139 | self.set_func_sym("libc.so", "malloc")?; 140 | self.set_var_sym("libc.so", "timezone")?; 141 | Ok(self) 142 | } 143 | 144 | pub fn set_test_syms(&mut self) -> Result<&mut Self, InjectionError> { 145 | self.set_func_sym( 146 | "liblasso.so", 147 | "Java_com_github_erfur_lasso_MainActivity_testFunction", 148 | )?; 149 | self.set_var_sym("liblasso.so", "test_var")?; 150 | Ok(self) 151 | } 152 | 153 | pub fn use_raw_dlopen(&mut self) -> Result<&mut Self, InjectionError> { 154 | self.set_func_sym("libdl.so", "dlopen")?; 155 | self.injection_type = InjectionType::RawDlopen; 156 | Ok(self) 157 | } 158 | 159 | pub fn use_memfd_dlopen(&mut self) -> Result<&mut Self, InjectionError> { 160 | self.set_func_sym("libdl.so", "dlopen")?; 161 | self.set_func_sym("libc.so", "sprintf")?; 162 | self.injection_type = InjectionType::MemFdDlopen; 163 | Ok(self) 164 | } 165 | 166 | pub fn use_raw_shellcode(&mut self) -> Result<&mut Self, InjectionError> { 167 | self.injection_type = InjectionType::RawShellcode; 168 | Ok(self) 169 | } 170 | 171 | pub fn restart_app_and_get_pid(package_name: &str) -> Result { 172 | let pid = utils::restart_app_and_get_pid(package_name); 173 | if pid > 0 { 174 | Ok(pid) 175 | } else { 176 | Err(InjectionError::PidNotFound) 177 | } 178 | } 179 | 180 | pub fn inject(&mut self) -> Result<(), InjectionError> { 181 | let file_path = self.prepare_file()?; 182 | let proc = remote_proc::RemoteProc::new(self.pid)?; 183 | 184 | if self.target_func_sym_name.is_empty() || self.target_var_sym_name.is_empty() { 185 | warn!("target_func_sym or target_var_sym is empty, using defaults"); 186 | self.set_default_syms()?; 187 | } 188 | 189 | info!("build second stage shellcode"); 190 | let second_stage = match self.injection_type { 191 | InjectionType::RawDlopen => shellcode::raw_dlopen_shellcode( 192 | *self.sym_cache.get("dlopen").unwrap(), 193 | file_path, 194 | *self.sym_cache.get("malloc").unwrap(), 195 | ) 196 | .unwrap(), 197 | InjectionType::MemFdDlopen => shellcode::memfd_dlopen_shellcode( 198 | *self.sym_cache.get("dlopen").unwrap(), 199 | *self.sym_cache.get("malloc").unwrap(), 200 | &std::fs::read(file_path.as_str()).unwrap(), 201 | *self.sym_cache.get("sprintf").unwrap(), 202 | ) 203 | .unwrap(), 204 | InjectionType::RawShellcode => shellcode::raw_shellcode().unwrap(), 205 | }; 206 | 207 | info!("build first stage shellcode"); 208 | let first_stage = 209 | shellcode::main_shellcode(self.target_var_sym_addr, second_stage.len()).unwrap(); 210 | 211 | info!("read original bytes"); 212 | let func_original_bytes = proc 213 | .mem 214 | .read(self.target_func_sym_addr, first_stage.len()) 215 | .unwrap(); 216 | let var_original_bytes = proc.mem.read(self.target_var_sym_addr, 0x8).unwrap(); 217 | 218 | info!("write first stage shellcode"); 219 | proc.mem 220 | .write(self.target_var_sym_addr, &vec![0x0; 0x8]) 221 | .unwrap(); 222 | proc.mem 223 | .write(self.target_func_sym_addr, &first_stage) 224 | .unwrap(); 225 | 226 | info!("wait for shellcode to trigger"); 227 | let mut new_map: u64; 228 | loop { 229 | std::thread::sleep(std::time::Duration::from_millis(10)); 230 | let data = proc.mem.read(self.target_var_sym_addr, 0x8).unwrap(); 231 | // u64 from val 232 | new_map = u64::from_le_bytes(data[0..8].try_into().unwrap()); 233 | if (new_map & 0x1 != 0) && (new_map & 0xffff_ffff_ffff_fff0 != 0) { 234 | break; 235 | } 236 | } 237 | 238 | new_map &= 0xffff_ffff_ffff_fff0; 239 | info!("new map: 0x{:x}", new_map); 240 | 241 | info!("overwrite malloc with loop"); 242 | proc.mem 243 | .write(self.target_func_sym_addr, &shellcode::self_jmp().unwrap()) 244 | .unwrap(); 245 | 246 | // wait for 100ms 247 | std::thread::sleep(std::time::Duration::from_millis(100)); 248 | 249 | info!("restore original bytes"); 250 | proc.mem 251 | .write_code(self.target_func_sym_addr, &func_original_bytes, 1) 252 | .unwrap(); 253 | proc.mem 254 | .write(self.target_var_sym_addr, &var_original_bytes) 255 | .unwrap(); 256 | 257 | info!("overwrite new map"); 258 | proc.mem 259 | .write_code(new_map as usize, &second_stage, 1) 260 | .unwrap(); 261 | 262 | info!("injection done."); 263 | Ok(()) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/remote_mem.rs: -------------------------------------------------------------------------------- 1 | use nix::sys::uio::{pread, pwrite}; 2 | use std::fs::{File, OpenOptions}; 3 | 4 | use crate::InjectionError; 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct RemoteMem { 8 | fd: File, 9 | } 10 | 11 | impl RemoteMem { 12 | pub fn new(pid: i32) -> Result { 13 | let mem_path: String = format!("/proc/{}/mem", pid); 14 | // open file in read-write mode 15 | let fd = OpenOptions::new() 16 | .read(true) 17 | .write(true) 18 | .open(&mem_path) 19 | .map_err(|_| InjectionError::RemoteMemoryError)?; 20 | 21 | Ok(Self { fd }) 22 | } 23 | 24 | pub fn read(&self, addr: usize, len: usize) -> Result, InjectionError> { 25 | let mut buf = vec![0; len]; 26 | self.read_vec(addr, &mut buf)?; 27 | Ok(buf) 28 | } 29 | 30 | pub fn read_vec(&self, addr: usize, buf: &mut Vec) -> Result<(), InjectionError> { 31 | debug!( 32 | "reading from remote memory: addr: 0x{:x}, len: {}", 33 | addr, 34 | buf.len() 35 | ); 36 | match pread(&self.fd, buf, addr as i64) { 37 | Ok(_) => {} 38 | Err(e) => { 39 | error!("error while reading from remote memory: {:?}", e); 40 | return Err(InjectionError::RemoteMemoryError); 41 | } 42 | } 43 | Ok(()) 44 | } 45 | 46 | pub fn write(&self, addr: usize, buf: &Vec) -> Result<(), InjectionError> { 47 | debug!( 48 | "writing into remote memory: addr: 0x{:x}, len: {}", 49 | addr, 50 | buf.len() 51 | ); 52 | match pwrite(&self.fd, &buf, addr as i64) { 53 | Ok(_) => Ok(()), 54 | Err(e) => { 55 | error!("error while writing into remote memory: {:?}", e); 56 | Err(InjectionError::RemoteMemoryError) 57 | } 58 | } 59 | } 60 | 61 | /// Write code into remote memory, leaving the first `skip` instructions to last. 62 | /// This is (hopefully) useful when overwriting code that is currently being executed. 63 | pub fn write_code( 64 | &self, 65 | addr: usize, 66 | buf: &Vec, 67 | skip: usize, 68 | ) -> Result<(), InjectionError> { 69 | let skip_offset = skip * 4; 70 | match pwrite(&self.fd, &buf[skip_offset..], (addr + skip_offset) as i64) { 71 | Ok(_) => {} 72 | Err(e) => { 73 | error!("error while writing into remote memory: {:?}", e); 74 | return Err(InjectionError::RemoteMemoryError); 75 | } 76 | } 77 | 78 | match pwrite(&self.fd, &buf[..skip_offset], (addr) as i64) { 79 | Ok(_) => {} 80 | Err(e) => { 81 | error!("error while writing into remote memory: {:?}", e); 82 | return Err(InjectionError::RemoteMemoryError); 83 | } 84 | } 85 | 86 | Ok(()) 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | 92 | mod tests { 93 | use proc_maps::{get_process_maps, Pid}; 94 | 95 | use super::*; 96 | 97 | #[test] 98 | fn test_read_mem() { 99 | let remote_mem = RemoteMem::new(std::process::id() as i32).unwrap(); 100 | let buf = remote_mem.read(0x7f7f7f7f7f7f, 0x10); 101 | println!("{:?}", buf); 102 | } 103 | 104 | #[test] 105 | fn test_list_self_maps() { 106 | let pid: u32 = std::process::id(); 107 | let maps = get_process_maps(Pid::from(pid as u16)).unwrap(); 108 | for map in maps { 109 | println!("{:?}", map); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/remote_module.rs: -------------------------------------------------------------------------------- 1 | use crate::InjectionError; 2 | 3 | pub(crate) struct RemoteModule { 4 | pub name: String, 5 | pub vm_addr: usize, 6 | #[allow(dead_code)] 7 | pub bytes: Vec, 8 | } 9 | 10 | impl RemoteModule { 11 | pub fn new(name: &str, vm_addr: usize, bytes: Vec) -> Self { 12 | Self { 13 | name: name.to_string(), 14 | vm_addr, 15 | bytes, 16 | } 17 | } 18 | 19 | pub fn dlsym_from_fs(&self, symbol_name: &str) -> Result { 20 | let bytes = std::fs::read(&self.name).map_err(|_| InjectionError::FileError)?; 21 | let elf = goblin::elf::Elf::parse(&bytes).map_err(|_| InjectionError::RemoteModuleError)?; 22 | 23 | let result = elf 24 | .syms 25 | .iter() 26 | .find(|sym| symbol_name == elf.strtab.get_at(sym.st_name).unwrap()); 27 | 28 | if result.is_some() { 29 | let offset = result.unwrap().st_value as usize; 30 | return Ok(offset + self.vm_addr); 31 | } 32 | 33 | warn!( 34 | "symbol not found in .symtab, trying .dynsym: {}", 35 | symbol_name 36 | ); 37 | 38 | let result = elf 39 | .dynsyms 40 | .iter() 41 | .find(|sym| symbol_name == elf.dynstrtab.get_at(sym.st_name).unwrap()); 42 | 43 | if result.is_none() { 44 | error!("symbol not found: {}", symbol_name); 45 | return Err(InjectionError::SymbolNotFound); 46 | } 47 | 48 | let offset = result.unwrap().st_value as usize; 49 | Ok(offset + self.vm_addr) 50 | } 51 | 52 | fn _dlsym_from_mem(&self, _symbol_name: &str) -> Result { 53 | unimplemented!("dlsym_from_mem"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/remote_proc.rs: -------------------------------------------------------------------------------- 1 | use proc_maps::{get_process_maps, MapRange, Pid}; 2 | 3 | use crate::{remote_mem::RemoteMem, remote_module::RemoteModule, InjectionError}; 4 | 5 | pub(crate) struct RemoteProc { 6 | pid: i32, 7 | pub mem: RemoteMem, 8 | } 9 | 10 | impl RemoteProc { 11 | pub fn new(pid: i32) -> Result { 12 | let mem = RemoteMem::new(pid)?; 13 | Ok(Self { pid, mem }) 14 | } 15 | 16 | fn maps(&self) -> Result, InjectionError> { 17 | get_process_maps(self.pid as Pid).map_err(|_| InjectionError::RemoteProcessError) 18 | } 19 | 20 | fn maps_by_name(&self, name: &str) -> Result, InjectionError> { 21 | let maps = self.maps()?; 22 | let mut maps_by_name: Vec = Vec::new(); 23 | for map in maps { 24 | match map.filename() { 25 | None => continue, 26 | Some(filename) => { 27 | if filename.ends_with(name) { 28 | maps_by_name.push(map); 29 | } 30 | } 31 | } 32 | } 33 | 34 | if maps_by_name.is_empty() { 35 | return Err(InjectionError::ModuleNotFound); 36 | } 37 | 38 | Ok(maps_by_name) 39 | } 40 | 41 | fn module_bytes(&self, module_name: &str) -> Result, InjectionError> { 42 | let maps = self.maps_by_name(module_name)?; 43 | let mut module_bytes: Vec = Vec::new(); 44 | for map in maps { 45 | // debug!("map: {:?}", map); 46 | module_bytes.resize(map.offset, 0); 47 | let mut buf = self.mem.read(map.start(), map.size())?; 48 | module_bytes.append(&mut buf); 49 | } 50 | 51 | Ok(module_bytes) 52 | } 53 | 54 | pub fn module(&self, module_name: &str) -> Result { 55 | let maps = self.maps_by_name(module_name)?; 56 | Ok(RemoteModule::new( 57 | maps[0].filename().unwrap().to_str().unwrap(), 58 | maps[0].start(), 59 | self.module_bytes(module_name)?, 60 | )) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/shellcode.rs: -------------------------------------------------------------------------------- 1 | use dynasmrt::{dynasm, DynasmApi, DynasmLabelApi}; 2 | 3 | use crate::InjectionError; 4 | 5 | pub fn main_shellcode(var_addr: usize, alloc_len: usize) -> Result, InjectionError> { 6 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 7 | 8 | dynasm!(ops 9 | ; .arch aarch64 10 | 11 | ; ->start: 12 | // check if the bit is set 13 | ; ldr x6, ->var_addr 14 | ; ldxrb w1, [x6] 15 | ; cbnz w1, ->start 16 | 17 | // set the bit 18 | ; mov w2, 0x1 19 | ; stxrb w1, w2, [x6] 20 | ; cbnz w1, ->start 21 | 22 | // save the registers 23 | ; sub sp, sp, #0x100 24 | ; stp x0, x1, [sp, #0x0] 25 | ; stp x2, x3, [sp, #0x10] 26 | ; stp x4, x5, [sp, #0x20] 27 | ; stp x6, x7, [sp, #0x30] 28 | ; stp x8, x9, [sp, #0x40] 29 | ; stp x10, x11, [sp, #0x50] 30 | ; stp x12, x13, [sp, #0x60] 31 | ; stp x14, x15, [sp, #0x70] 32 | ; stp x16, x17, [sp, #0x80] 33 | ; stp x18, x19, [sp, #0x90] 34 | ; stp x20, x21, [sp, #0xa0] 35 | ; stp x22, x23, [sp, #0xb0] 36 | ; stp x24, x25, [sp, #0xc0] 37 | ; stp x26, x27, [sp, #0xd0] 38 | ; stp x28, x29, [sp, #0xe0] 39 | ; stp x30, xzr, [sp, #0xf0] 40 | 41 | // mmap call 42 | ; mov x0, #0x0 // addr (NULL) 43 | ; mov x1, alloc_len as _ // len (0x1000) 44 | ; mov x2, #0x7 // prot (RWX) 45 | ; mov x3, #0x22 // flags (MAP_PRIVATE | MAP_ANONYMOUS) 46 | ; mvn x4, xzr // fd (-1) 47 | ; mov x5, #0x0 // offset (ignored) 48 | ; mov x8, #0xde // syscall no (mmap) 49 | ; svc #0x0 // syscall 50 | 51 | // write self loop instruction to the new map 52 | ; ldr w1, ->self_jmp 53 | ; str w1, [x0] 54 | 55 | // flush cache 56 | // https://chromium.googlesource.com/v8/v8/+/9405fcfdd1984341ea06a192b3b08bdb6069db15/src/arm64/cpu-arm64.cc 57 | // ; dc civac, x0 58 | // ; dsb ish 59 | // ; ic ivau, x0 60 | ; dsb ish 61 | ; isb 62 | 63 | // save mmap addr (with bit set to keep the other threads spinning) 64 | ; orr x0, x0, #0x1 65 | ; str x0, [x6, #0x0] 66 | 67 | // turn off the bit 68 | ; eor x0, x0, #0x1 69 | 70 | // jump to the new map 71 | ; br x0 72 | 73 | ; .align 4 74 | ; ->minus_one: 75 | ; .qword -1 as _ 76 | 77 | ; .align 4 78 | ; ->var_addr: 79 | ; .qword var_addr as _ 80 | 81 | ; .align 4 82 | ; ->alloc_len: 83 | ; .qword alloc_len as _ 84 | 85 | ; .align 4 86 | ; ->self_jmp: 87 | ; b ->self_jmp 88 | ); 89 | 90 | match ops.finalize() { 91 | Ok(shellcode) => Ok(shellcode.to_vec()), 92 | Err(_) => Err(InjectionError::ShellcodeError), 93 | } 94 | } 95 | 96 | pub fn raw_dlopen_shellcode( 97 | dlopen_addr: usize, 98 | dlopen_path: String, 99 | jmp_addr: usize, 100 | ) -> Result, InjectionError> { 101 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 102 | 103 | // dlopen flags RTLD_NOW 104 | let dlopen_flags: usize = 0x2; 105 | let dlopen_path_bytes: &[u8] = dlopen_path.as_bytes(); 106 | 107 | dynasm!(ops 108 | ; .arch aarch64 109 | 110 | // for testing 111 | // ; brk #0x1 112 | 113 | // load args 114 | ; adr x0, ->dlopen_path 115 | ; ldr x1, ->dlopen_flags 116 | 117 | // call dlopen 118 | ; ldr x8, ->dlopen 119 | ; blr x8 120 | 121 | // if dlopen fails, crash 122 | ; cbz x0, ->crash 123 | 124 | // load the original args 125 | ; ldp x0, x1, [sp, #0x0] 126 | ; ldp x2, x3, [sp, #0x10] 127 | ; ldp x4, x5, [sp, #0x20] 128 | ; ldp x6, x7, [sp, #0x30] 129 | ; ldp x8, x9, [sp, #0x40] 130 | ; ldp x10, x11, [sp, #0x50] 131 | ; ldp x12, x13, [sp, #0x60] 132 | ; ldp x14, x15, [sp, #0x70] 133 | ; ldp x16, x17, [sp, #0x80] 134 | ; ldp x18, x19, [sp, #0x90] 135 | ; ldp x20, x21, [sp, #0xa0] 136 | ; ldp x22, x23, [sp, #0xb0] 137 | ; ldp x24, x25, [sp, #0xc0] 138 | ; ldp x26, x27, [sp, #0xd0] 139 | ; ldp x28, x29, [sp, #0xe0] 140 | ; ldp x30, xzr, [sp, #0xf0] 141 | ; add sp, sp, #0x100 142 | 143 | // jump to the original function 144 | ; ldr x8, ->oldfun 145 | ; br x8 146 | 147 | ; ->crash: 148 | ; brk #0x1 149 | 150 | ; .align 4 151 | ; ->dlopen_path: 152 | ; .bytes dlopen_path_bytes 153 | ; .bytes [0x0] 154 | 155 | ; .align 4 156 | ; ->dlopen_flags: 157 | ; .qword dlopen_flags as _ 158 | 159 | ; .align 4 160 | ; ->dlopen: 161 | ; .qword dlopen_addr as _ 162 | 163 | ; .align 4 164 | ; ->oldfun: 165 | ; .qword jmp_addr as _ 166 | ); 167 | 168 | match ops.finalize() { 169 | Ok(shellcode) => Ok(shellcode.to_vec()), 170 | Err(_) => Err(InjectionError::ShellcodeError), 171 | } 172 | } 173 | 174 | pub fn memfd_dlopen_shellcode( 175 | dlopen_addr: usize, 176 | jmp_addr: usize, 177 | library_data_bytes: &Vec, 178 | sprintf_addr: usize, 179 | ) -> Result, InjectionError> { 180 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 181 | 182 | // dlopen flags RTLD_NOW 183 | let dlopen_flags: usize = 0x2; 184 | 185 | dynasm!(ops 186 | ; .arch aarch64 187 | 188 | // call memfd_create 189 | ; adr x0, ->memfd_name // name 190 | ; mov x1, #0x0 // flags 191 | ; mov x8, #0x117 // syscall no (memfd_create) 192 | ; svc #0x0 // syscall 193 | 194 | // create the path string with sprintf 195 | ; mov x2, x0 196 | ; adr x0, ->fd_format_buffer 197 | ; adr x1, ->fd_template 198 | ; ldr x8, ->sprintf 199 | ; blr x8 200 | 201 | // write data to memfd 202 | ; mov x0, x2 // fd 203 | ; adr x1, ->library_data // offset 204 | ; mov x2, library_data_bytes.len() as u64 // len 205 | ; mov x8, #0x40 // syscall no (write) 206 | ; svc #0x0 // syscall 207 | 208 | // call dlopen 209 | ; adr x0, ->fd_format_buffer 210 | ; ldr x1, ->dlopen_flags 211 | ; ldr x8, ->dlopen 212 | ; blr x8 213 | 214 | // load the original args 215 | ; ldp x0, x1, [sp, #0x0] 216 | ; ldp x2, x3, [sp, #0x10] 217 | ; ldp x4, x5, [sp, #0x20] 218 | ; ldp x6, x7, [sp, #0x30] 219 | ; ldp x8, x9, [sp, #0x40] 220 | ; ldp x10, x11, [sp, #0x50] 221 | ; ldp x12, x13, [sp, #0x60] 222 | ; ldp x14, x15, [sp, #0x70] 223 | ; ldp x16, x17, [sp, #0x80] 224 | ; ldp x18, x19, [sp, #0x90] 225 | ; ldp x20, x21, [sp, #0xa0] 226 | ; ldp x22, x23, [sp, #0xb0] 227 | ; ldp x24, x25, [sp, #0xc0] 228 | ; ldp x26, x27, [sp, #0xd0] 229 | ; ldp x28, x29, [sp, #0xe0] 230 | ; ldp x30, xzr, [sp, #0xf0] 231 | ; add sp, sp, #0x100 232 | 233 | // jump to the original function 234 | ; ldr x8, ->oldfun 235 | ; br x8 236 | 237 | ; .align 4 238 | ; ->memfd_name: 239 | ; .bytes "".as_bytes() 240 | 241 | ; .align 4 242 | ; ->dlopen_flags: 243 | ; .qword dlopen_flags as _ 244 | 245 | ; .align 4 246 | ; ->dlopen: 247 | ; .qword dlopen_addr as _ 248 | 249 | ; .align 4 250 | ; ->oldfun: 251 | ; .qword jmp_addr as _ 252 | 253 | ; .align 4 254 | ; ->sprintf: 255 | ; .qword sprintf_addr as _ 256 | 257 | ; .align 4 258 | ; ->fd_template: 259 | ; .bytes "/proc/self/fd/%d\0".as_bytes() 260 | 261 | ; .align 4 262 | ; ->fd_format_buffer: 263 | ; .bytes [0x0; 0x20] 264 | 265 | ; .align 4 266 | ; ->library_data: 267 | ; .bytes library_data_bytes 268 | ); 269 | 270 | match ops.finalize() { 271 | Ok(shellcode) => Ok(shellcode.to_vec()), 272 | Err(_) => Err(InjectionError::ShellcodeError), 273 | } 274 | } 275 | 276 | pub fn raw_shellcode() -> Result, InjectionError> { 277 | unimplemented!() 278 | } 279 | 280 | pub(crate) fn self_jmp() -> Result, InjectionError> { 281 | let mut ops = dynasmrt::aarch64::Assembler::new().unwrap(); 282 | 283 | dynasm!(ops 284 | ; .arch aarch64 285 | ; ->self_jmp: 286 | ; b ->self_jmp 287 | ); 288 | 289 | match ops.finalize() { 290 | Ok(shellcode) => Ok(shellcode.to_vec()), 291 | Err(_) => Err(InjectionError::ShellcodeError), 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use hxdmp::hexdump; 2 | use std::io::{ErrorKind, Read}; 3 | use std::process::Output; 4 | use std::str::from_utf8; 5 | 6 | use crate::InjectionError; 7 | 8 | use glob::glob; 9 | use std::thread; 10 | use std::time::Duration; 11 | 12 | const HEXDUMP_BUFFER_SIZE: usize = 0x200; 13 | const TMP_DIR_PATH: &str = "/data/local/tmp"; 14 | 15 | pub fn print_file_hexdump(file_path: &str) -> Result<(), InjectionError> { 16 | let mut file = match std::fs::File::open(file_path) { 17 | Ok(file) => file, 18 | Err(e) => { 19 | error!("Error opening file: {}", e); 20 | return Err(InjectionError::FileError); 21 | } 22 | }; 23 | 24 | let mut in_buffer = [0; HEXDUMP_BUFFER_SIZE]; 25 | let mut out_buffer = Vec::new(); 26 | 27 | match file.read_exact(&mut in_buffer) { 28 | Ok(_) => {} 29 | Err(e) => { 30 | if e.kind() == ErrorKind::UnexpectedEof { 31 | // ignore 32 | } else { 33 | error!("Error reading file: {}", e); 34 | return Err(InjectionError::FileError); 35 | } 36 | } 37 | } 38 | 39 | hexdump(&in_buffer, &mut out_buffer).unwrap(); 40 | 41 | debug!("Hexdump of file: {}", String::from_utf8_lossy(&out_buffer)); 42 | Ok(()) 43 | } 44 | 45 | pub fn verify_elf_file(file_path: &str) -> Result<(), InjectionError> { 46 | let file = match std::fs::File::open(file_path) { 47 | Ok(file) => file, 48 | Err(e) => { 49 | error!("Error opening file: {}", e); 50 | return Err(InjectionError::FileError); 51 | } 52 | }; 53 | 54 | let mut magic = [0; 4]; 55 | match file.take(4).read_exact(&mut magic) { 56 | Ok(_) => {} 57 | Err(e) => { 58 | error!("Error reading file: {}", e); 59 | return Err(InjectionError::FileError); 60 | } 61 | } 62 | 63 | if magic != [0x7f, 0x45, 0x4c, 0x46] { 64 | error!("File is not an ELF file"); 65 | return Err(InjectionError::FileError); 66 | } 67 | 68 | Ok(()) 69 | } 70 | 71 | pub fn copy_file_to_tmp(file_path: &str) -> Result { 72 | // get absolute path 73 | let file_path_absolute = match std::path::Path::new(file_path).canonicalize() { 74 | Ok(path) => path, 75 | Err(e) => { 76 | error!("Error getting file path: {}", e); 77 | return Err(InjectionError::FileError); 78 | } 79 | }; 80 | 81 | info!("File path: {}", file_path_absolute.to_str().unwrap()); 82 | 83 | // skip if the file is already in /dev/local/tmp 84 | if file_path_absolute.starts_with(TMP_DIR_PATH) { 85 | info!("File is already in {}", TMP_DIR_PATH); 86 | return Ok(String::from(file_path_absolute.to_str().unwrap())); 87 | } 88 | 89 | let file_name = match file_path_absolute.file_name() { 90 | Some(name) => name.to_str().unwrap(), 91 | None => { 92 | error!("Error getting file name"); 93 | return Err(InjectionError::FileError); 94 | } 95 | }; 96 | 97 | // copy file to /data/local/tmp so that the target app can access it 98 | let tmp_file_path = std::path::Path::new(TMP_DIR_PATH) 99 | .join(file_name) 100 | .as_os_str() 101 | .to_str() 102 | .unwrap() 103 | .to_string(); 104 | 105 | info!("Copying file {} to {}", file_path, tmp_file_path); 106 | match std::fs::copy(file_path, &tmp_file_path) { 107 | Ok(_) => { 108 | info!("File copied successfully"); 109 | Ok(tmp_file_path) 110 | } 111 | Err(e) => { 112 | error!("Error copying file: {}", e); 113 | Err(InjectionError::FileError) 114 | } 115 | } 116 | } 117 | 118 | pub fn fix_file_context(file_path: &str) -> Result<(), InjectionError> { 119 | // set file context to apk_data_file for dlopen to succeed 120 | info!("Fixing file context for {}", file_path); 121 | match std::process::Command::new("chcon") 122 | .arg("u:object_r:apk_data_file:s0") 123 | .arg(file_path) 124 | .output() 125 | { 126 | Ok(output) => { 127 | if !output.status.success() { 128 | error!( 129 | "Error running chcon: {}", 130 | String::from_utf8_lossy(&output.stderr) 131 | ); 132 | Err(InjectionError::CommandError) 133 | } else { 134 | info!("File context fixed"); 135 | Ok(()) 136 | } 137 | } 138 | Err(e) => { 139 | error!("Error running chcon: {}", e); 140 | Err(InjectionError::CommandError) 141 | } 142 | } 143 | } 144 | 145 | pub fn fix_file_permissions(file_path: &str) -> Result<(), InjectionError> { 146 | // add executable permission to file 147 | info!("Fixing file permissions for {}", file_path); 148 | match std::process::Command::new("chmod") 149 | .arg("+r") 150 | .arg(file_path) 151 | .output() 152 | { 153 | Ok(output) => { 154 | if !output.status.success() { 155 | error!( 156 | "Error running chmod: {}", 157 | String::from_utf8_lossy(&output.stderr) 158 | ); 159 | Err(InjectionError::CommandError) 160 | } else { 161 | info!("File permissions fixed"); 162 | Ok(()) 163 | } 164 | } 165 | Err(e) => { 166 | error!("Error running chmod: {}", e); 167 | Err(InjectionError::CommandError) 168 | } 169 | } 170 | } 171 | 172 | pub fn execute_command(program: &str, args: &Vec<&str>) -> Result { 173 | match std::process::Command::new(program).args(args).output() { 174 | Ok(output) => { 175 | if !output.status.success() { 176 | error!( 177 | "Error running cmd {} {:?} err: {}", 178 | program, 179 | args, 180 | String::from_utf8_lossy(&output.stderr) 181 | ); 182 | Err(InjectionError::CommandError) 183 | } else { 184 | info!("Running cmd successfully: {} {:?}", program, args); 185 | Ok(output) 186 | } 187 | } 188 | Err(e) => { 189 | error!("Error running cmd {} {:?} err: {}", program, args, e); 190 | Err(InjectionError::CommandError) 191 | } 192 | } 193 | } 194 | 195 | pub fn get_pid_by_package(pkg_name: &str) -> std::io::Result { 196 | for entry in glob("/proc/*/cmdline").unwrap() { 197 | match entry { 198 | Ok(path) => { 199 | let mut file = std::fs::File::open(&path)?; 200 | let mut contents = String::new(); 201 | file.read_to_string(&mut contents)?; 202 | let tmp_name = contents.trim_end_matches('\0'); 203 | if tmp_name == pkg_name { 204 | let path_str = path.to_str().unwrap(); 205 | let pid_str = path_str.split("/").nth(2).unwrap(); 206 | let pid = pid_str.parse::().unwrap(); 207 | return Ok(pid); 208 | } 209 | } 210 | Err(err) => println!("{:?}", err), 211 | } 212 | } 213 | Ok(0) 214 | } 215 | 216 | pub fn get_pid_by_package_with_polling(pkg_name: &str) -> u32 { 217 | let mut _pid: u32 = 0; 218 | 219 | let count = 100; 220 | for _i in 0..count { 221 | _pid = get_pid_by_package(pkg_name).unwrap(); 222 | if _pid > 0 { 223 | break; 224 | } 225 | thread::sleep(Duration::from_micros(500)); 226 | } 227 | 228 | _pid 229 | } 230 | 231 | pub fn restart_app_and_get_pid(pkg_name: &str) -> u32 { 232 | let _ = execute_command("am", &vec!["force-stop", pkg_name]); 233 | // check if this command can start the application 234 | let _ = execute_command( 235 | "monkey", 236 | &vec![ 237 | "-p", 238 | pkg_name, 239 | "-c", 240 | "android.intent.category.LAUNCHER", 241 | "1", 242 | ], 243 | ); 244 | 245 | let mut _pid = get_pid_by_package_with_polling(pkg_name); 246 | 247 | if _pid == 0 { 248 | // try another way to start the application 249 | let get_main_activity_result = execute_command( 250 | "cmd", 251 | &vec![ 252 | "package", 253 | "resolve-activity", 254 | "--brief", 255 | pkg_name, 256 | "|", 257 | "tail", 258 | "-n", 259 | "1", 260 | ], 261 | ) 262 | .unwrap(); 263 | let result_str = from_utf8(&get_main_activity_result.stdout).unwrap(); 264 | let last_line = result_str.lines().last().unwrap(); 265 | let _ = execute_command("am", &vec!["start", last_line]); 266 | _pid = get_pid_by_package_with_polling(pkg_name); 267 | } 268 | 269 | _pid 270 | } 271 | --------------------------------------------------------------------------------