implemented, no test
This commit is contained in:
parent
59b801dfe3
commit
f2f9f6a2d1
5 changed files with 292 additions and 9 deletions
25
README.md
Normal file
25
README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# ADBD Guard
|
||||||
|
|
||||||
|
A daemon for aarch64 android to monitor on tcp stream of adbd and notify if suspicious actions are detected.
|
||||||
|
|
||||||
|
NOTE: some part of libs may not available, so try dev on linux.
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
### Install Cargo NDK
|
||||||
|
```bash
|
||||||
|
cargo install cargo-ndk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install NDK
|
||||||
|
```
|
||||||
|
https://developer.android.com/ndk/downloads
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
ANDROID_NDK_HOME=/path/to/ndk_top_level cargo ndk --platform 30 -t aarch64-linux-android build --release
|
||||||
|
```
|
||||||
51
adbdguard.json
Normal file
51
adbdguard.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"debounce_ms": 3000,
|
||||||
|
"max_payload_peek": 256,
|
||||||
|
"broadcast_action": "com.adbguard.ALERT",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"name": "exec_any",
|
||||||
|
"when": "service",
|
||||||
|
"contains": [
|
||||||
|
"exec:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "danger_shell",
|
||||||
|
"when": "service",
|
||||||
|
"contains": [
|
||||||
|
"shell:"
|
||||||
|
],
|
||||||
|
"patterns": "(su|setenforce|mount|dd|pm\\s+grant|appops|iptables|chcon)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"abb_pm",
|
||||||
|
"when": "service",
|
||||||
|
"contains": [
|
||||||
|
"abb_exec:pm "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sync_sensitive",
|
||||||
|
"when": "service",
|
||||||
|
"contains": [
|
||||||
|
"sync:"
|
||||||
|
],
|
||||||
|
"patterns": "/(system|vendor|data/system|data/adb)/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "remount",
|
||||||
|
"when": "service",
|
||||||
|
"contains": [
|
||||||
|
"remount"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tcpip",
|
||||||
|
"when":"service",
|
||||||
|
"contains": [
|
||||||
|
"tcpip:"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
#[cfg(all(feature = "ptrace", target_arch = "aarch64"))]
|
|
||||||
pub mod ptrace;
|
pub mod ptrace;
|
||||||
|
|
|
||||||
|
|
@ -11,19 +11,163 @@ use nix::{
|
||||||
unistd::Pid,
|
unistd::Pid,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap, hash::Hash, time::{Duration, Instant}
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PTRACE_GETREGSET_FALLBACK: i32 = 0x4204;
|
const PTRACE_GETREGSET_FALLBACK: libc::c_int = 0x4204;
|
||||||
const NT_PRSTATUS_FALLBACK: i32 = 1;
|
const PTRACE_SEIZE_FALLBACK: libc::c_int = 0x4206;
|
||||||
|
const PTRACE_INTERRUPT: libc::c_int = 0x4207;
|
||||||
|
const NT_PRSTATUS_FALLBACK: libc::c_int = 1;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_arch = "aarch64")]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy, Default)]
|
#[derive(Clone, Copy, Default)]
|
||||||
struct UserAtRegs {
|
struct UserPtRegs {
|
||||||
regs: [u64; 31],
|
regs: [u64; 31],
|
||||||
sp: u64,
|
sp: u64,
|
||||||
pc: u64,
|
pc: u64,
|
||||||
pstate: u64,
|
pstate: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_regs(pid: Pid) -> Result<UserPtRegs> {
|
||||||
|
let mut regs = UserPtRegs::default();
|
||||||
|
unsafe {
|
||||||
|
let r = libc::ptrace(
|
||||||
|
PTRACE_GETREGSET_FALLBACK.try_into().unwrap(),
|
||||||
|
pid.as_raw(),
|
||||||
|
NT_PRSTATUS_FALLBACK,
|
||||||
|
&mut regs as *mut _,
|
||||||
|
);
|
||||||
|
if r < 0 {
|
||||||
|
return Err(anyhow::anyhow!("GETREGSET failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(regs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// x8 holds syscall
|
||||||
|
fn syscall_no(regs: &UserPtRegs) -> u64 {
|
||||||
|
regs.regs[8]
|
||||||
|
}
|
||||||
|
|
||||||
|
// x0 1st arg
|
||||||
|
fn arg0(regs: &UserPtRegs) -> u64 {
|
||||||
|
regs.regs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// x1 buf/count
|
||||||
|
fn arg1(regs: &UserPtRegs) -> u64 {
|
||||||
|
regs.regs[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn arg2(regs: &UserPtRegs) -> u64 {
|
||||||
|
regs.regs[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYS_write: u64 = 64;
|
||||||
|
const SYS_sendto: u64 = 206;
|
||||||
|
const SYS_read: u64 = 63;
|
||||||
|
const SYS_recvfrom: u64 = 207;
|
||||||
|
|
||||||
|
struct ConnState {
|
||||||
|
buf: BytesMut,
|
||||||
|
last_alert: Instant
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnState {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
buf: BytesMut::with_capacity(4096),
|
||||||
|
last_alert: Instant::now() - Duration::from_secs(3600)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn ptrace_cmd(cmd: libc::c_int, pid: Pid, addr: usize) -> nix::Result<()> {
|
||||||
|
let mut regs = UserPtRegs::default();
|
||||||
|
let r = unsafe { libc::ptrace(cmd.try_into().unwrap(), pid.as_raw(), addr as *mut libc::c_int, &mut regs as *mut _) };
|
||||||
|
if r == -1 {
|
||||||
|
return Err(nix::errno::Errno::last());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(pid: i32, rules: RuleSet, action: String, debounce: Duration, peek: usize) -> Result<()> {
|
||||||
|
let pid = Pid::from_raw(pid);
|
||||||
|
// cannot compiled
|
||||||
|
// ptrace::seize(pid, ptrace::Options::PTRACE_O_TRACESYSGOOD)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptrace_cmd(PTRACE_SEIZE_FALLBACK.try_into().unwrap(), pid, 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot compiled
|
||||||
|
// ptrace::interrupt(pid)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ptrace_cmd(PTRACE_INTERRUPT.try_into().unwrap(), pid, 0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
waitpid(pid, None);
|
||||||
|
|
||||||
|
let mut conns: HashMap<i32, ConnState> = HashMap::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
ptrace::syscall(pid, None)?;
|
||||||
|
let st = waitpid(pid, Some(WaitPidFlag::__WALL))?;
|
||||||
|
let WSIG = |ws: &WaitStatus| matches!(ws, WaitStatus::PtraceSyscall(_));
|
||||||
|
if !WSIG(&st){continue;}
|
||||||
|
|
||||||
|
// syscall entry
|
||||||
|
let regs = get_regs(pid)?;
|
||||||
|
let scn = syscall_no(®s);
|
||||||
|
let is_out = scn == SYS_write || scn == SYS_sendto;
|
||||||
|
let is_in = scn == SYS_read || scn == SYS_recvfrom;
|
||||||
|
if !(is_in || is_out){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fd = arg0(®s) as i32;
|
||||||
|
let buf_ptr = if is_out {
|
||||||
|
arg1(®s)
|
||||||
|
} else {
|
||||||
|
arg0(®s)
|
||||||
|
};
|
||||||
|
let count = if is_out {
|
||||||
|
arg2(®s)
|
||||||
|
} else {
|
||||||
|
arg1(®s)
|
||||||
|
} as usize;
|
||||||
|
let take = count.min(peek);
|
||||||
|
|
||||||
|
// syscall run to exit, return value
|
||||||
|
ptrace::syscall(pid, None)?;
|
||||||
|
let _ = waitpid(pid, Some(WaitPidFlag::__WALL))?;
|
||||||
|
|
||||||
|
// copy user buffer
|
||||||
|
if take > 0 {
|
||||||
|
let mut data = vec![0u8; take];
|
||||||
|
let local_iov = libc::iovec { iov_base: data.as_mut_ptr() as *mut _, iov_len: take};
|
||||||
|
let remote_iov = libc::iovec { iov_base: buf_ptr as *mut _, iov_len: take};
|
||||||
|
unsafe {
|
||||||
|
let n = libc::process_vm_readv(pid.as_raw(), &local_iov as *const _, 1, &remote_iov as *const _, 1, 0);
|
||||||
|
if n as isize <= 0 {continue;}
|
||||||
|
}
|
||||||
|
let s = conns.entry(fd).or_insert_with(ConnState::new);
|
||||||
|
s.buf.extend_from_slice(&data);
|
||||||
|
|
||||||
|
while let Some(frame) = adb::try_parse(&mut s.buf){
|
||||||
|
if frame.cmd == adb::CMD_OPEN {
|
||||||
|
let service = String::from_utf8_lossy(&frame.payload).to_string();
|
||||||
|
if let Some(rule) = rules.match_service(&service) {
|
||||||
|
if s.last_alert.elapsed() >= debounce {
|
||||||
|
broadcaster::broadcast(&action, &service, rule);
|
||||||
|
s.last_alert = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main.rs
67
src/main.rs
|
|
@ -1,9 +1,72 @@
|
||||||
|
use std::{path::PathBuf, sync::{Arc, Mutex}, time::Duration};
|
||||||
|
|
||||||
|
|
||||||
|
use config::Config;
|
||||||
|
use notify::{RecommendedWatcher, Watcher};
|
||||||
|
use rule::RuleSet;
|
||||||
|
use tracing::{info, error};
|
||||||
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
mod adb;
|
mod adb;
|
||||||
mod backend;
|
mod backend;
|
||||||
mod broadcaster;
|
mod broadcaster;
|
||||||
mod config;
|
mod config;
|
||||||
mod rule;
|
mod rule;
|
||||||
|
|
||||||
fn main() {
|
fn pidof(name: &str) -> Option<i32> {
|
||||||
println!("Hello, world!");
|
let out = std::process::Command::new("pidof").arg(name).output().ok()?;
|
||||||
|
let s = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||||
|
s.split_whitespace().next()?.parse::<i32>().ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn watch_config(path: PathBuf, cfg: Arc<Mutex<Config>>) -> notify::Result<RecommendedWatcher> {
|
||||||
|
let saved_path = path.clone();
|
||||||
|
let mut w = notify::recommended_watcher(move |res: Result<notify::Event, _>| {
|
||||||
|
if let Ok(event) = res {
|
||||||
|
if event.kind.is_modify() {
|
||||||
|
if let Ok(newc) = Config::load(path.clone().to_str().unwrap()){
|
||||||
|
*cfg.lock().unwrap() = newc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
w.watch(&saved_path, notify::RecursiveMode::NonRecursive)?;
|
||||||
|
Ok(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let sub = FmtSubscriber::builder().without_time().with_target(false).finish();
|
||||||
|
tracing::subscriber::set_global_default(sub).ok();
|
||||||
|
|
||||||
|
let mut args = std::env::args().skip(1);
|
||||||
|
let cfg_path = args.next().filter(|s| s=="--config").and_then(|_| args.next()).unwrap_or_else(|| "/data/adb/modules/adbguard/adbguard.json".into());
|
||||||
|
let cfg0 = Config::load(&cfg_path)?;
|
||||||
|
let cfg = Arc::new(Mutex::new(cfg0));
|
||||||
|
let _w = watch_config((&cfg_path).into(), cfg.clone()).ok();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let pid = loop {
|
||||||
|
if let Some(p) = pidof("adbd"){
|
||||||
|
break p;
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("attaching to adbd {}", pid);
|
||||||
|
|
||||||
|
let snapshot = {
|
||||||
|
cfg.lock().unwrap().clone()
|
||||||
|
};
|
||||||
|
let rules = RuleSet::from_cfg(&snapshot);
|
||||||
|
let action = snapshot.broadcast_action.clone();
|
||||||
|
|
||||||
|
#[cfg(feature = "ptrace")]
|
||||||
|
{
|
||||||
|
if let Err(e) = backend::ptrace::run(pid, rules, action, snapshot.debounce(), snapshot.max_payload_peek){
|
||||||
|
error!("backend exited, {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue