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;
|
||||
|
|
|
|||
|
|
@ -11,19 +11,163 @@ use nix::{
|
|||
unistd::Pid,
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
time::{Duration, Instant},
|
||||
collections::HashMap, hash::Hash, time::{Duration, Instant}
|
||||
};
|
||||
|
||||
const PTRACE_GETREGSET_FALLBACK: i32 = 0x4204;
|
||||
const NT_PRSTATUS_FALLBACK: i32 = 1;
|
||||
const PTRACE_GETREGSET_FALLBACK: libc::c_int = 0x4204;
|
||||
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)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct UserAtRegs {
|
||||
struct UserPtRegs {
|
||||
regs: [u64; 31],
|
||||
sp: u64,
|
||||
pc: 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 backend;
|
||||
mod broadcaster;
|
||||
mod config;
|
||||
mod rule;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
fn pidof(name: &str) -> Option<i32> {
|
||||
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