Skip to content

Commit

Permalink
Merge pull request #2 from erfur/develop
Browse files Browse the repository at this point in the history
Move to trunk-based branching
  • Loading branch information
erfur authored Mar 20, 2024
2 parents 7bf0e66 + 9d446e1 commit c9a5cd9
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 54 deletions.
17 changes: 2 additions & 15 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the "main" branch
push:
branches: ["main"]
tags:
- "*"
- "v*"
pull_request:
branches: ["main"]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

permissions:
contents: write

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

Expand All @@ -35,27 +28,21 @@ jobs:
- name: build
run: cargo ndk --target aarch64-linux-android build --release

# - name: Setup tmate session
# uses: mxschmitt/action-tmate@v3

- name: move files
run: |
mv target/aarch64-linux-android/release/linjector-cli ./linjector-cli
xz -9 -k ./linjector-cli
- name: save hashes in env
run: |
echo '```' > hashes.txt
echo "SHA256 hashes:" >> hashes.txt
sha256sum linjector-cli >> hashes.txt
sha256sum linjector-cli.xz >> hashes.txt
echo '```' >> hashes.txt
- name: release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(github.ref, 'refs/tags/v')
with:
body_path: hashes.txt
files: |
./linjector-cli
./linjector-cli.xz
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ path = "src/lib.rs"
name = "linjector-cli"
path = "bin/cli.rs"

[profile.release]
opt-level = "z"
debug = false
lto = true
debug-assertions = false
codegen-units = 1
panic = "abort"
strip = true

[dependencies]
android_logger = "0.13.3"
backtrace = "0.3.69"
Expand All @@ -25,3 +34,4 @@ nix = { version = "0.27.1", features = ["uio"] }
pretty-hex = "0.4.0"
proc-maps = "0.3.2"
simple_logger = "4.3.3"
glob = "0.3.1"
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@ To get an idea of how it works, you can read the [blog post](https://erfur.githu
```
Inject code into a running process using /proc/mem
Usage: linjector-cli [OPTIONS] --pid <PID> --file <FILE>
Usage: linjector-cli [OPTIONS] --file <FILE>
Options:
-p, --pid <PID>
pid of the target process
-a, --app-package-name <APP_PACKAGE_NAME>
target application's package name, (re)start the application and do injection
-f, --file <FILE>
path of the library/shellcode to inject
-i, --injection-type <INJECTION_TYPE>
type of injection
[default: raw-dlopen]
Possible values:
Expand Down
43 changes: 30 additions & 13 deletions bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ use simple_logger::SimpleLogger;
struct Args {
/// pid of the target process
#[arg(short, long)]
pid: i32,
pid: Option<i32>,

/// target application's package name, (re)start the application and do injection
#[arg(short, long)]
app_package_name: Option<String>,

/// path of the library/shellcode to inject
#[arg(short, long)]
Expand Down Expand Up @@ -57,21 +61,34 @@ fn main() {
} else {
android_logger::init_once(Config::default().with_max_level(LevelFilter::Info));
}
} else if args.debug {
SimpleLogger::new()
.with_level(LevelFilter::Debug)
.init()
.unwrap();
} else {
if args.debug {
SimpleLogger::new()
.with_level(LevelFilter::Debug)
.init()
.unwrap();
} else {
SimpleLogger::new()
.with_level(LevelFilter::Info)
.init()
.unwrap();
}
SimpleLogger::new()
.with_level(LevelFilter::Info)
.init()
.unwrap();
}

let mut injector = match linjector_rs::Injector::new(args.pid) {
let mut target_pid = args.pid.unwrap_or(0);
if target_pid <= 0 {
let Some(name) = args.app_package_name else {
error!("No pid or app_package_name is specified");
return;
};
let Ok(app_pid) = linjector_rs::Injector::restart_app_and_get_pid(&name) else {
error!("Can't restart package: {}, or cannot found pid", name);
return;
};
target_pid = app_pid as i32;
}

info!("target process pid: {}", target_pid);

let mut injector = match linjector_rs::Injector::new(target_pid) {
Ok(injector) => injector,
Err(e) => {
error!("Error creating injector: {:?}", e);
Expand Down
50 changes: 27 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub enum InjectionError {
FileError,
CommandError,
ShellcodeError,
PidNotFound,
}

pub struct Injector {
Expand Down Expand Up @@ -167,6 +168,15 @@ impl Injector {
Ok(self)
}

pub fn restart_app_and_get_pid(package_name: &str) -> Result<u32, InjectionError> {
let pid = utils::restart_app_and_get_pid(package_name);
if pid > 0 {
Ok(pid)
} else {
Err(InjectionError::PidNotFound)
}
}

pub fn inject(&mut self) -> Result<(), InjectionError> {
let file_path = self.prepare_file()?;
let proc = remote_proc::RemoteProc::new(self.pid)?;
Expand All @@ -177,29 +187,22 @@ impl Injector {
}

info!("build second stage shellcode");
let second_stage: Vec<u8>;
match self.injection_type {
InjectionType::RawDlopen => {
second_stage = shellcode::raw_dlopen_shellcode(
*self.sym_cache.get("dlopen").unwrap(),
file_path,
*self.sym_cache.get("malloc").unwrap(),
)
.unwrap();
}
InjectionType::MemFdDlopen => {
second_stage = shellcode::memfd_dlopen_shellcode(
*self.sym_cache.get("dlopen").unwrap(),
*self.sym_cache.get("malloc").unwrap(),
&std::fs::read(file_path.as_str()).unwrap(),
*self.sym_cache.get("sprintf").unwrap(),
)
.unwrap();
}
InjectionType::RawShellcode => {
second_stage = shellcode::raw_shellcode().unwrap();
}
}
let second_stage = match self.injection_type {
InjectionType::RawDlopen => shellcode::raw_dlopen_shellcode(
*self.sym_cache.get("dlopen").unwrap(),
file_path,
*self.sym_cache.get("malloc").unwrap(),
)
.unwrap(),
InjectionType::MemFdDlopen => shellcode::memfd_dlopen_shellcode(
*self.sym_cache.get("dlopen").unwrap(),
*self.sym_cache.get("malloc").unwrap(),
&std::fs::read(file_path.as_str()).unwrap(),
*self.sym_cache.get("sprintf").unwrap(),
)
.unwrap(),
InjectionType::RawShellcode => shellcode::raw_shellcode().unwrap(),
};

info!("build first stage shellcode");
let first_stage =
Expand All @@ -223,6 +226,7 @@ impl Injector {
info!("wait for shellcode to trigger");
let mut new_map: u64;
loop {
std::thread::sleep(std::time::Duration::from_millis(10));
let data = proc.mem.read(self.target_var_sym_addr, 0x8).unwrap();
// u64 from val
new_map = u64::from_le_bytes(data[0..8].try_into().unwrap());
Expand Down
2 changes: 1 addition & 1 deletion src/remote_module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl RemoteModule {
.iter()
.find(|sym| symbol_name == elf.strtab.get_at(sym.st_name).unwrap());

if !result.is_none() {
if result.is_some() {
let offset = result.unwrap().st_value as usize;
return Ok(offset + self.vm_addr);
}
Expand Down
106 changes: 106 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use hxdmp::hexdump;
use std::io::{ErrorKind, Read};
use std::process::Output;
use std::str::from_utf8;

use crate::InjectionError;

use glob::glob;
use std::thread;
use std::time::Duration;

const HEXDUMP_BUFFER_SIZE: usize = 0x200;
const TMP_DIR_PATH: &str = "/data/local/tmp";

Expand Down Expand Up @@ -162,3 +168,103 @@ pub fn fix_file_permissions(file_path: &str) -> Result<(), InjectionError> {
}
}
}

pub fn execute_command(program: &str, args: &Vec<&str>) -> Result<Output, InjectionError> {
match std::process::Command::new(program).args(args).output() {
Ok(output) => {
if !output.status.success() {
error!(
"Error running cmd {} {:?} err: {}",
program,
args,
String::from_utf8_lossy(&output.stderr)
);
Err(InjectionError::CommandError)
} else {
info!("Running cmd successfully: {} {:?}", program, args);
Ok(output)
}
}
Err(e) => {
error!("Error running cmd {} {:?} err: {}", program, args, e);
Err(InjectionError::CommandError)
}
}
}

pub fn get_pid_by_package(pkg_name: &str) -> std::io::Result<u32> {
for entry in glob("/proc/*/cmdline").unwrap() {
match entry {
Ok(path) => {
let mut file = std::fs::File::open(&path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let tmp_name = contents.trim_end_matches('\0');
if tmp_name == pkg_name {
let path_str = path.to_str().unwrap();
let pid_str = path_str.split("/").nth(2).unwrap();
let pid = pid_str.parse::<u32>().unwrap();
return Ok(pid);
}
}
Err(err) => println!("{:?}", err),
}
}
Ok(0)
}

pub fn get_pid_by_package_with_polling(pkg_name: &str) -> u32 {
let mut _pid: u32 = 0;

let count = 100;
for _i in 0..count {
_pid = get_pid_by_package(pkg_name).unwrap();
if _pid > 0 {
break;
}
thread::sleep(Duration::from_micros(500));
}

_pid
}

pub fn restart_app_and_get_pid(pkg_name: &str) -> u32 {
let _ = execute_command("am", &vec!["force-stop", pkg_name]);
// check if this command can start the application
let _ = execute_command(
"monkey",
&vec![
"-p",
pkg_name,
"-c",
"android.intent.category.LAUNCHER",
"1",
],
);

let mut _pid = get_pid_by_package_with_polling(pkg_name);

if _pid == 0 {
// try another way to start the application
let get_main_activity_result = execute_command(
"cmd",
&vec![
"package",
"resolve-activity",
"--brief",
pkg_name,
"|",
"tail",
"-n",
"1",
],
)
.unwrap();
let result_str = from_utf8(&get_main_activity_result.stdout).unwrap();
let last_line = result_str.lines().last().unwrap();
let _ = execute_command("am", &vec!["start", last_line]);
_pid = get_pid_by_package_with_polling(pkg_name);
}

_pid
}

0 comments on commit c9a5cd9

Please sign in to comment.