Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d544cf6c3 |
16 changed files with 646 additions and 153588 deletions
551
Cargo.lock
generated
551
Cargo.lock
generated
|
|
@ -4,24 +4,18 @@ version = 4
|
|||
|
||||
[[package]]
|
||||
name = "adbdguard"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"aya",
|
||||
"aya-log",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
"futures",
|
||||
"libc",
|
||||
"crc32fast",
|
||||
"nix",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.16",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
|
@ -51,100 +45,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
|
||||
[[package]]
|
||||
name = "assert_matches"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "aya"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d18bc4e506fbb85ab7392ed993a7db4d1a452c71b75a246af4a80ab8c9d2dd50"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"aya-obj",
|
||||
"bitflags 2.9.3",
|
||||
"bytes",
|
||||
"libc",
|
||||
"log",
|
||||
"object",
|
||||
"once_cell",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aya-log"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b600d806c1d07d3b81ab5f4a2a95fd80f479a0d3f1d68f29064d660865f85f02"
|
||||
dependencies = [
|
||||
"aya",
|
||||
"aya-log-common",
|
||||
"bytes",
|
||||
"log",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aya-log-common"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "befef9fe882e63164a2ba0161874e954648a72b0e1c4b361f532d590638c4eec"
|
||||
dependencies = [
|
||||
"num_enum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aya-obj"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51b96c5a8ed8705b40d655273bc4212cbbf38d4e3be2788f36306f154523ec7"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"core-error",
|
||||
"hashbrown",
|
||||
"log",
|
||||
"object",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
|
|
@ -172,27 +78,12 @@ version = "2.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
|
|
@ -205,35 +96,6 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-error"
|
||||
version = "0.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efcdb2972eb64230b4c50646d8498ff73f5128d196a90c7236eec4cbe8619b8f"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
|
|
@ -243,58 +105,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
|
|
@ -304,146 +114,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.11.0"
|
||||
|
|
@ -481,16 +157,6 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
|
|
@ -602,45 +268,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
|
|
@ -662,12 +295,6 @@ version = "0.2.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
|
|
@ -686,26 +313,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
|
|
@ -741,12 +348,6 @@ version = "0.1.26"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
|
|
@ -803,12 +404,6 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
|
|
@ -842,33 +437,13 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -898,6 +473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
|
|
@ -988,12 +564,6 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
|
@ -1010,64 +580,6 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
@ -1099,65 +611,12 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
|
|
|||
35
Cargo.toml
35
Cargo.toml
|
|
@ -1,49 +1,24 @@
|
|||
[package]
|
||||
name = "adbdguard"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
aya = "0.13.1"
|
||||
aya-log = "0.2.1"
|
||||
bytes = "1.10.1"
|
||||
chrono = "0.4.41"
|
||||
crossbeam-channel = "0.5.15"
|
||||
futures = "0.3.31"
|
||||
libc = "0.2.175"
|
||||
nix = { version = "0.30.1", features = [
|
||||
"ptrace",
|
||||
"process",
|
||||
"uio",
|
||||
"signal",
|
||||
"sched",
|
||||
"fs",
|
||||
] }
|
||||
crc32fast = "1.5.0"
|
||||
nix = { version = "0.30.1", features = ["ptrace", "process", "uio", "signal"] }
|
||||
notify = "8.2.0"
|
||||
once_cell = "1.21.3"
|
||||
rayon = "1.11.0"
|
||||
regex = "1.11.2"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.143"
|
||||
thiserror = "2.0.16"
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "time"] }
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "time", "net", "io-util", "fs"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["fmt", "ansi"] }
|
||||
|
||||
[features]
|
||||
default = ["ffs_proxy"]
|
||||
ffs_proxy = []
|
||||
ptrace = []
|
||||
ptrace_parallel = []
|
||||
ptracev2 = []
|
||||
uprobes = []
|
||||
|
||||
# For cross-compilation to Android
|
||||
[profile.release]
|
||||
strip = true
|
||||
lto = true
|
||||
opt-level = "z" # Optimize for size
|
||||
|
||||
[profile.dev]
|
||||
debug = true
|
||||
ffs_proxy = []
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
INCDIR=/usr/include
|
||||
clang -O2 -g -target bpf \
|
||||
-D__TARGET_ARCH_arm64 \
|
||||
-I"$INCDIR" \
|
||||
-c sendrecv.bpf.c -o sendrecv.bpf.o
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
#include "vmlinux.h"
|
||||
#include <asm/types.h>
|
||||
|
||||
#include <linux/bpf_common.h>
|
||||
// #include <bpf/bpf.h>
|
||||
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
// Target: arm64 calling convention
|
||||
#ifndef __TARGET_ARCH_arm64
|
||||
# define __TARGET_ARCH_arm64
|
||||
#endif
|
||||
|
||||
#ifndef __PT_PARM1_REG
|
||||
struct user_pt_regs {
|
||||
__u64 regs[31];
|
||||
__u64 sp;
|
||||
__u64 pc;
|
||||
__u64 pstate;
|
||||
};
|
||||
#define __PT_PARM1_REG regs[0]
|
||||
#define __PT_PARM2_REG regs[1]
|
||||
#define __PT_PARM3_REG regs[2]
|
||||
#define __PT_PARM4_REG regs[3]
|
||||
#define __PT_PARM5_REG regs[4]
|
||||
#define __PT_PARM6_REG regs[5]
|
||||
#define __PT_RC_REG regs[0]
|
||||
#endif
|
||||
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||||
|
||||
#define MAX_PEEK 256
|
||||
|
||||
struct call_ctx {
|
||||
uint64_t buf;
|
||||
uint64_t len;
|
||||
uint32_t is_send; // 1=send/write, 0=recv/read
|
||||
};
|
||||
|
||||
struct event {
|
||||
uint32_t pid, tid;
|
||||
uint32_t is_send;
|
||||
uint32_t len;
|
||||
uint8_t data[MAX_PEEK];
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, uint32_t); // tid
|
||||
__type(value, struct call_ctx);
|
||||
__uint(max_entries, 8192);
|
||||
} in_flight SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 1 << 22);
|
||||
} rb SEC(".maps");
|
||||
|
||||
// helpers
|
||||
static __always_inline void save_ctx(uint32_t tid, uint64_t buf, uint64_t len, uint32_t is_send) {
|
||||
struct call_ctx ctx = {.buf = buf, .len = len, .is_send = is_send};
|
||||
bpf_map_update_elem(&in_flight, &tid, &ctx, BPF_ANY);
|
||||
}
|
||||
|
||||
static __always_inline int emit_ret(uint32_t tid, int64_t ret, uint32_t is_send) {
|
||||
struct call_ctx *ctx = bpf_map_lookup_elem(&in_flight, &tid);
|
||||
if (!ctx) return 0;
|
||||
if (ret <= 0) { bpf_map_delete_elem(&in_flight, &tid); return 0; }
|
||||
|
||||
uint64_t want = ctx->len < (unsigned long)ret ? ctx->len : (unsigned long)ret;
|
||||
if (want > MAX_PEEK) want = MAX_PEEK;
|
||||
|
||||
struct event *e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
|
||||
if (!e) { bpf_map_delete_elem(&in_flight, &tid); return 0; }
|
||||
e->pid = bpf_get_current_pid_tgid() >> 32;
|
||||
e->tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
e->is_send = is_send;
|
||||
e->len = (uint32_t)want;
|
||||
|
||||
if (want > 0 && ctx->buf) {
|
||||
bpf_probe_read_user(e->data, want, (void*)ctx->buf);
|
||||
}
|
||||
|
||||
bpf_ringbuf_submit(e, 0);
|
||||
bpf_map_delete_elem(&in_flight, &tid);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- send/write entry (x1 = buf, x2 = len)
|
||||
SEC("uprobe/libc_send")
|
||||
int BPF_KPROBE(up_send) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
uint64_t buf = PT_REGS_PARM2(ctx);
|
||||
uint64_t len = PT_REGS_PARM3(ctx);
|
||||
save_ctx(tid, buf, len, 1);
|
||||
return 0;
|
||||
}
|
||||
SEC("uretprobe/libc_send")
|
||||
int BPF_KRETPROBE(ur_send) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
return emit_ret(tid, PT_REGS_RC(ctx), 1);
|
||||
}
|
||||
|
||||
// write(int fd, const void *buf, size_t count)
|
||||
SEC("uprobe/libc_write")
|
||||
int BPF_KPROBE(up_write) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
uint64_t buf = PT_REGS_PARM2(ctx);
|
||||
uint64_t len = PT_REGS_PARM3(ctx);
|
||||
save_ctx(tid, buf, len, 1);
|
||||
return 0;
|
||||
}
|
||||
SEC("uretprobe/libc_write")
|
||||
int BPF_KRETPROBE(ur_write) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
return emit_ret(tid, PT_REGS_RC(ctx), 1);
|
||||
}
|
||||
|
||||
// ---- recv/read entry (x1 = buf, x2 = len)
|
||||
SEC("uprobe/libc_recv")
|
||||
int BPF_KPROBE(up_recv) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
uint64_t buf = PT_REGS_PARM2(ctx);
|
||||
uint64_t len = PT_REGS_PARM3(ctx);
|
||||
save_ctx(tid, buf, len, 0);
|
||||
return 0;
|
||||
}
|
||||
SEC("uretprobe/libc_recv")
|
||||
int BPF_KRETPROBE(ur_recv) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
return emit_ret(tid, PT_REGS_RC(ctx), 0);
|
||||
}
|
||||
|
||||
// read(int fd, void *buf, size_t count)
|
||||
SEC("uprobe/libc_read")
|
||||
int BPF_KPROBE(up_read) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
uint64_t buf = PT_REGS_PARM2(ctx);
|
||||
uint64_t len = PT_REGS_PARM3(ctx);
|
||||
save_ctx(tid, buf, len, 0);
|
||||
return 0;
|
||||
}
|
||||
SEC("uretprobe/libc_read")
|
||||
int BPF_KRETPROBE(ur_read) {
|
||||
uint32_t tid = (uint32_t)bpf_get_current_pid_tgid();
|
||||
return emit_ret(tid, PT_REGS_RC(ctx), 0);
|
||||
}
|
||||
150324
bpf/vmlinux.h
150324
bpf/vmlinux.h
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,5 @@
|
|||
#[cfg(feature = "ptrace")]
|
||||
pub mod ptrace;
|
||||
|
||||
#[cfg(feature = "ptracev2")]
|
||||
pub mod ptrace_v2;
|
||||
|
||||
#[cfg(feature = "ptrace_parallel")]
|
||||
pub mod ptrace_prl;
|
||||
|
||||
#[cfg(feature = "uprobes")]
|
||||
pub mod uprobes;
|
||||
#[cfg(feature = "ffs_proxy")]
|
||||
pub mod proxy;
|
||||
|
|
|
|||
510
src/backend/proxy.rs
Normal file
510
src/backend/proxy.rs
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
use std::{
|
||||
ffi::CString,
|
||||
fs::OpenOptions,
|
||||
io::Write,
|
||||
iter::Enumerate,
|
||||
os::fd::{AsRawFd, FromRawFd},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use bytes::{Buf, BufMut, BytesMut};
|
||||
use crc32fast::Hasher;
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use crate::traits::EnumerateValue;
|
||||
|
||||
pub const FFS_EP0: &str = "/dev/usb-ffs/adb/ep0";
|
||||
pub const FFS_EP1: &str = "/dev/usb-ffs/adb/ep1";
|
||||
pub const FFS_EP2: &str = "/dev/usb-ffs/adb/ep2";
|
||||
|
||||
pub const ENDPOINT_XFER_BULK: u8 = 2;
|
||||
|
||||
// FFS Descriptors
|
||||
#[repr(C, packed)]
|
||||
struct FfsDescHeadV2 {
|
||||
magic: u32, // FUNCTIONFS_DESCRIPTORS_MAGIC_V2 = 0xB8 0x4B 0x3F 0x94
|
||||
length: u32,
|
||||
flags: u32, // FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed)]
|
||||
struct UsbInterfaceDescriptor {
|
||||
bLength: u8,
|
||||
bDescriptiorType: u8,
|
||||
bInterfaceNumber: u8,
|
||||
bAlternateSetting: u8,
|
||||
bNumEndpoints: u8,
|
||||
bInterfaceClass: u8,
|
||||
bInterfaceSubClass: u8,
|
||||
bInterfaceProtocol: u8,
|
||||
iInterface: u8,
|
||||
}
|
||||
|
||||
/// REF: https://vovkos.github.io/doxyrest/samples/libusb/struct_libusb_endpoint_descriptor.html
|
||||
#[allow(non_snake_case)]
|
||||
#[repr(C, packed)]
|
||||
struct UsbEndpointDescriptor {
|
||||
/// Size of descriptor (bytes)
|
||||
bLength: u8,
|
||||
/// Descriptor type
|
||||
bDescriptorType: u8,
|
||||
/// Address of endpoint
|
||||
/// Bit 0-3 = endpoint number
|
||||
/// Bit 4-6 = reserved
|
||||
/// Bit 7 = direction
|
||||
bEndpointAddress: u8,
|
||||
/// Attributes apply to endpoint
|
||||
/// Bit 0-1 = transfer type
|
||||
/// Bit 2-3 = isochronus endpoint
|
||||
/// Bit 4-5 = isochronus endpoint
|
||||
/// Bit 6-7 = reserved
|
||||
bmAttributes: u8,
|
||||
/// Packet size capable of send/receive
|
||||
wMaxPacketSize: u16,
|
||||
/// Interval for polling endpoint for data transfer
|
||||
bInterval: u8,
|
||||
/// Audio only: rate at which synchronization feedback is provided
|
||||
bRefresh: u8, // not used for bulk
|
||||
/// Audio only: address if synch endpoint
|
||||
bSynchAddress: u8, // not used for bulk
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Clone)]
|
||||
enum DescriptorType {
|
||||
LIBUSB_DT_DEVICE = 0x01,
|
||||
LIBUSB_DT_CONFIG,
|
||||
LIBUSB_DT_STRING,
|
||||
LIBUSB_DT_INTERFACE,
|
||||
LIBUSB_DT_ENDPOINT,
|
||||
LIBUSB_DT_BOS = 0x0f,
|
||||
LIBUSB_DT_DEVICE_CAPABILITY,
|
||||
LIBUSB_DT_HID = 0x21,
|
||||
LIBUSB_DT_REPORT,
|
||||
LIBUSB_DT_PHYSICAL,
|
||||
LIBUSB_DT_HUB = 0x29,
|
||||
LIBUSB_DT_SUPERSPEED_HUB,
|
||||
LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30,
|
||||
}
|
||||
|
||||
impl EnumerateValue<u8> for DescriptorType {
|
||||
fn name(&self) -> String
|
||||
where
|
||||
Self: std::fmt::Debug,
|
||||
{
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
fn value(&self) -> u8
|
||||
where
|
||||
Self: Clone,
|
||||
{
|
||||
self.clone() as u8
|
||||
}
|
||||
|
||||
fn get(val: u8) -> Self {
|
||||
return unsafe { std::mem::transmute::<_, Self>(val) };
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, Clone)]
|
||||
enum EndpointDirection {
|
||||
ENDPOINT_IN = 0x80, // device -> host
|
||||
ENDPOINT_OUT = 0x00, // host -> device
|
||||
}
|
||||
|
||||
impl EnumerateValue<u8> for EndpointDirection {
|
||||
fn name(&self) -> String
|
||||
where
|
||||
Self: std::fmt::Debug,
|
||||
{
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
fn value(&self) -> u8
|
||||
where
|
||||
Self: Clone,
|
||||
{
|
||||
self.clone() as u8
|
||||
}
|
||||
|
||||
fn get(val: u8) -> Self {
|
||||
return unsafe { std::mem::transmute::<_, Self>(val) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn any_as_bytes<T: Sized>(p: &T) -> &[u8] {
|
||||
std::slice::from_raw_parts((p as *const T) as *const u8, size_of::<T>())
|
||||
}
|
||||
|
||||
/// Build tiny FS+HS descriptor set for ADB
|
||||
/// class 0xFF
|
||||
/// subclass 0x42
|
||||
/// proto 0x01
|
||||
fn write_ffs_descriptors(ep0: &mut std::fs::File) -> anyhow::Result<()> {
|
||||
let interface = UsbInterfaceDescriptor {
|
||||
bLength: size_of::<UsbInterfaceDescriptor>() as u8,
|
||||
bDescriptiorType: DescriptorType::LIBUSB_DT_INTERFACE.value(),
|
||||
bInterfaceNumber: 0,
|
||||
bAlternateSetting: 0,
|
||||
bNumEndpoints: 2,
|
||||
bInterfaceClass: 0xFF,
|
||||
bInterfaceSubClass: 0x42,
|
||||
bInterfaceProtocol: 0x01,
|
||||
iInterface: 1,
|
||||
};
|
||||
let fs_out = UsbEndpointDescriptor {
|
||||
bLength: 7,
|
||||
bDescriptorType: DescriptorType::LIBUSB_DT_ENDPOINT.value(),
|
||||
bEndpointAddress: 0x01 | EndpointDirection::ENDPOINT_OUT.value(),
|
||||
bmAttributes: ENDPOINT_XFER_BULK,
|
||||
wMaxPacketSize: 64u16.to_le(),
|
||||
bInterval: 0,
|
||||
bRefresh: 0,
|
||||
bSynchAddress: 0,
|
||||
};
|
||||
let fs_in = UsbEndpointDescriptor {
|
||||
bLength: 7,
|
||||
bDescriptorType: DescriptorType::LIBUSB_DT_ENDPOINT.value(),
|
||||
bEndpointAddress: 0x82 | EndpointDirection::ENDPOINT_IN.value(),
|
||||
bmAttributes: ENDPOINT_XFER_BULK,
|
||||
wMaxPacketSize: 64u16.to_le(),
|
||||
bInterval: 0,
|
||||
bRefresh: 0,
|
||||
bSynchAddress: 0,
|
||||
};
|
||||
let hs_out = UsbEndpointDescriptor {
|
||||
wMaxPacketSize: 512u16.to_le(),
|
||||
..fs_out
|
||||
};
|
||||
let hs_in = UsbEndpointDescriptor {
|
||||
wMaxPacketSize: 512u16.to_le(),
|
||||
..fs_in
|
||||
};
|
||||
|
||||
// header
|
||||
const MAGIC_V2: u32 = 0xB84B3F94;
|
||||
// body
|
||||
let body_len = size_of::<UsbInterfaceDescriptor>()
|
||||
+ 2 * size_of::<UsbEndpointDescriptor>()
|
||||
+ size_of::<UsbInterfaceDescriptor>()
|
||||
+ 2 * size_of::<UsbEndpointDescriptor>();
|
||||
|
||||
let header = FfsDescHeadV2 {
|
||||
magic: MAGIC_V2,
|
||||
length: (size_of::<FfsDescHeadV2>() + body_len) as u32,
|
||||
flags: 0x3,
|
||||
};
|
||||
|
||||
// write descriptors
|
||||
let mut buf = Vec::with_capacity(header.length as usize);
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&header) });
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&interface) });
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&fs_out) });
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&fs_in) });
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&interface) });
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&hs_out) });
|
||||
buf.extend_from_slice(unsafe { any_as_bytes(&hs_in) });
|
||||
|
||||
ep0.write_all(&buf)?;
|
||||
|
||||
// header v2
|
||||
#[repr(C, packed)]
|
||||
struct FfsStrHeadV2 {
|
||||
magic: u32,
|
||||
length: u32,
|
||||
str_count: u32,
|
||||
lang_count: u32,
|
||||
}
|
||||
|
||||
const STR_MAGIC_V2: u32 = 0xC6 | (0x85 << 8) | (0x93 << 16) | (0x9F << 24);
|
||||
let sh = FfsStrHeadV2 {
|
||||
magic: STR_MAGIC_V2,
|
||||
length: 0,
|
||||
str_count: 1,
|
||||
lang_count: 1,
|
||||
};
|
||||
|
||||
let s0 = CString::new("ADB Interface").unwrap();
|
||||
let mut sbuf = Vec::new();
|
||||
sbuf.extend_from_slice(unsafe { any_as_bytes(&sh) });
|
||||
sbuf.extend_from_slice(&0x0409u16.to_le_bytes());
|
||||
sbuf.extend_from_slice(s0.as_bytes_with_nul());
|
||||
|
||||
let len_le = (sbuf.len() as u32).to_le_bytes();
|
||||
sbuf[4..8].copy_from_slice(&len_le);
|
||||
ep0.write_all(&sbuf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ADB Helper
|
||||
#[repr(C, packed)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct AdbHdr {
|
||||
cmd: u32,
|
||||
arg0: u32,
|
||||
arg1: u32,
|
||||
data_len: u32,
|
||||
data_crc32: u32,
|
||||
magic: u32,
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
enum ADB_COMMAND {
|
||||
CNXN = 0x4E584E43,
|
||||
AUTH = 0x48545541,
|
||||
OPEN = 0x4E45504F,
|
||||
OKAY = 0x59414B4F,
|
||||
CLSE = 0x45534C43,
|
||||
WRTE = 0x45545257,
|
||||
}
|
||||
|
||||
impl EnumerateValue<u32> for ADB_COMMAND {
|
||||
fn name(&self) -> String
|
||||
where
|
||||
Self: std::fmt::Debug,
|
||||
{
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
fn value(&self) -> u32
|
||||
where
|
||||
Self: Clone,
|
||||
{
|
||||
self.clone() as u32
|
||||
}
|
||||
|
||||
fn get(val: u32) -> Self {
|
||||
return unsafe { std::mem::transmute::<_, Self>(val) };
|
||||
}
|
||||
}
|
||||
|
||||
fn hdr_ok(h: &AdbHdr) -> bool {
|
||||
h.magic == (h.cmd ^ 0xffffffff)
|
||||
}
|
||||
|
||||
async fn read_frame_r<R: AsyncReadExt + Unpin>(
|
||||
r: &mut R,
|
||||
buf: &mut BytesMut,
|
||||
) -> anyhow::Result<(AdbHdr, Vec<u8>)> {
|
||||
while buf.len() < 24 {
|
||||
let n = r.read_buf(buf).await?;
|
||||
if n == 0 {
|
||||
return Err(anyhow!("eof"));
|
||||
}
|
||||
}
|
||||
|
||||
let mut h = AdbHdr {
|
||||
cmd: u32::from_le_bytes(buf[0..4].try_into().unwrap()),
|
||||
arg0: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
|
||||
arg1: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
|
||||
data_len: u32::from_le_bytes(buf[12..16].try_into().unwrap()),
|
||||
data_crc32: u32::from_le_bytes(buf[16..20].try_into().unwrap()),
|
||||
magic: u32::from_le_bytes(buf[20..24].try_into().unwrap()),
|
||||
};
|
||||
|
||||
if !hdr_ok(&h) {
|
||||
return Err(anyhow!("bad magic"));
|
||||
}
|
||||
|
||||
buf.advance(24);
|
||||
while buf.len() < h.data_len as usize {
|
||||
let n = r.read_buf(buf).await?;
|
||||
if n == 0 {
|
||||
return Err(anyhow!("eof"));
|
||||
}
|
||||
}
|
||||
let payload = buf.split_to(h.data_len as usize).to_vec();
|
||||
|
||||
Ok((h, payload))
|
||||
}
|
||||
|
||||
async fn write_frame_w<W: AsyncWriteExt + Unpin>(
|
||||
w: &mut W,
|
||||
mut h: AdbHdr,
|
||||
payload: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let mut hasher = Hasher::new();
|
||||
hasher.update(payload);
|
||||
h.data_len = payload.len() as u32;
|
||||
h.data_crc32 = hasher.finalize();
|
||||
h.magic = h.cmd ^ 0xffffffff;
|
||||
|
||||
let mut b = BytesMut::with_capacity(24 + payload.len());
|
||||
for v in [h.cmd, h.arg0, h.arg1, h.data_len, h.data_crc32, h.magic] {
|
||||
b.put_u32_le(v);
|
||||
}
|
||||
|
||||
b.extend_from_slice(payload);
|
||||
w.write_all(&b).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn service_from_open_payload(bytes: &[u8]) -> String {
|
||||
let s = std::str::from_utf8(bytes).unwrap_or_default();
|
||||
s.trim_end_matches('\0').to_string()
|
||||
}
|
||||
|
||||
fn suspicious(service: &str) -> bool {
|
||||
let s = service.to_ascii_lowercase();
|
||||
s.starts_with("shell:su")
|
||||
|| s.contains("setenforce")
|
||||
|| s.starts_with("tcpip:")
|
||||
|| s.contains("pm grant")
|
||||
|| s.contains("appops")
|
||||
}
|
||||
|
||||
async fn reject_stream_to_host<W: AsyncWriteExt + Unpin>(
|
||||
w: &mut W,
|
||||
local_id: u32,
|
||||
reason: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
// OKAY L,0 (ack stream so host accepts WRTE)
|
||||
write_frame_w(
|
||||
w,
|
||||
AdbHdr {
|
||||
cmd: ADB_COMMAND::OKAY.value(),
|
||||
arg0: local_id,
|
||||
arg1: 0,
|
||||
data_len: 0,
|
||||
data_crc32: 0,
|
||||
magic: 0,
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
// WRTE L,0 with reason
|
||||
write_frame_w(
|
||||
w,
|
||||
AdbHdr {
|
||||
cmd: ADB_COMMAND::WRTE.value(),
|
||||
arg0: local_id,
|
||||
arg1: 0,
|
||||
data_len: 0,
|
||||
data_crc32: 0,
|
||||
magic: 0,
|
||||
},
|
||||
reason.as_bytes(),
|
||||
)
|
||||
.await?;
|
||||
// CLSE L,0
|
||||
write_frame_w(
|
||||
w,
|
||||
AdbHdr {
|
||||
cmd: ADB_COMMAND::CLSE.value(),
|
||||
arg0: local_id,
|
||||
arg1: 0,
|
||||
data_len: 0,
|
||||
data_crc32: 0,
|
||||
magic: 0,
|
||||
},
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// probing check IN vs OUT
|
||||
fn open_ffs_endpoints() -> anyhow::Result<(std::fs::File, std::fs::File)> {
|
||||
let f1 = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(FFS_EP1)
|
||||
.with_context(|| format!("open {FFS_EP1}"))?;
|
||||
let f2 = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(FFS_EP2)
|
||||
.with_context(|| format!("open {FFS_EP2}"))?;
|
||||
|
||||
let mut a = f1.try_clone()?;
|
||||
let mut b = f2.try_clone()?;
|
||||
let w1 = a.write(&[0u8; 0]).map(|_| true).unwrap_or(false);
|
||||
let w2 = b.write(&[0u8; 0]).map(|_| true).unwrap_or(false);
|
||||
|
||||
let w1b = if !w1 {
|
||||
a.write(&[0x41]).map(|n| n == 1).unwrap_or(false)
|
||||
} else {
|
||||
w1
|
||||
};
|
||||
let w2b = if !w2 {
|
||||
b.write(&[0x41]).map(|n| n == 1).unwrap_or(false)
|
||||
} else {
|
||||
w2
|
||||
};
|
||||
|
||||
if w1b && !w2b {
|
||||
Ok((f1, f2))
|
||||
} else if w2b && !w1b {
|
||||
Ok((f2, f1))
|
||||
} else {
|
||||
Ok((f2, f1))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run() -> anyhow::Result<()> {
|
||||
let mut ep0 = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(FFS_EP0)
|
||||
.context("open ep0")?;
|
||||
write_ffs_descriptors(&mut ep0).context("write FFS descriptors")?;
|
||||
|
||||
let (ep_in_file, ep_out_file) = open_ffs_endpoints().context("open endpoints")?;
|
||||
|
||||
let mut ffs_in =
|
||||
tokio::fs::File::from_std(unsafe { std::fs::File::from_raw_fd(ep_in_file.as_raw_fd()) });
|
||||
let mut ffs_out =
|
||||
tokio::fs::File::from_std(unsafe { std::fs::File::from_raw_fd(ep_out_file.as_raw_fd()) });
|
||||
|
||||
let mut adbd = loop {
|
||||
match TcpStream::connect("127.0.0.1:5566").await {
|
||||
Ok(s) => break s,
|
||||
Err(e) => {
|
||||
eprintln!("wait adbd 5566: {e}");
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut hb = BytesMut::with_capacity(64 * 1024);
|
||||
let mut db = BytesMut::with_capacity(64 * 1024);
|
||||
|
||||
println!("proxy ready");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
h2d = read_frame_r(&mut ffs_out, &mut hb) => {
|
||||
let (h, payload) = match h2d { Ok(v)=>v, Err(e)=> { eprintln!("ffs_out read: {e}"); break; } };
|
||||
if h.cmd == ADB_COMMAND::OPEN.value() {
|
||||
let service = service_from_open_payload(&payload);
|
||||
if suspicious(&service) {
|
||||
let reason = format!("rejected by policy: {service}");
|
||||
if let Err(e) = reject_stream_to_host(&mut ffs_in, h.arg0, &reason).await {
|
||||
eprintln!("reject write: {e}");
|
||||
break;
|
||||
}
|
||||
continue; // do not forward to adbd
|
||||
}
|
||||
}
|
||||
if let Err(e) = write_frame_w(&mut adbd, h, &payload).await {
|
||||
eprintln!("to adbd: {e}"); break;
|
||||
}
|
||||
}
|
||||
d2h = read_frame_r(&mut adbd, &mut db) => {
|
||||
let (h, payload) = match d2h { Ok(v)=>v, Err(e)=> { eprintln!("adbd read: {e}"); break; } };
|
||||
if let Err(e) = write_frame_w(&mut ffs_in, h, &payload).await {
|
||||
eprintln!("to host: {e}"); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -2,27 +2,24 @@ use crate::{adb, broadcaster, rule::RuleSet};
|
|||
use anyhow::Result;
|
||||
use bytes::BytesMut;
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
libc::{self, iovec},
|
||||
libc,
|
||||
sys::{
|
||||
ptrace::{self, Options},
|
||||
ptrace,
|
||||
signal::Signal,
|
||||
wait::{WaitPidFlag, WaitStatus, waitpid},
|
||||
},
|
||||
unistd::Pid,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
time::{Duration, Instant},
|
||||
collections::HashMap, hash::Hash, time::{Duration, Instant}
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
// FALLBACK constants, in case of unsupported os while compiling
|
||||
const PTRACE_GETREGSET_FALLBACK: libc::c_int = 0x4204;
|
||||
// const PTRACE_SEIZE_FALLBACK: libc::c_int = 0x4206;
|
||||
// const PTRACE_INTERRUPT: libc::c_int = 0x4207;
|
||||
const PTRACE_SEIZE_FALLBACK: libc::c_int = 0x4206;
|
||||
const PTRACE_INTERRUPT: libc::c_int = 0x4207;
|
||||
const NT_PRSTATUS_FALLBACK: libc::c_int = 1;
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct UserPtRegs {
|
||||
|
|
@ -34,101 +31,26 @@ struct UserPtRegs {
|
|||
|
||||
fn get_regs(pid: Pid) -> Result<UserPtRegs> {
|
||||
let mut regs = UserPtRegs::default();
|
||||
let mut iovec = iovec {
|
||||
iov_base: (&mut regs as *mut UserPtRegs).cast(),
|
||||
iov_len: std::mem::size_of::<UserPtRegs>(),
|
||||
};
|
||||
unsafe {
|
||||
let r = libc::ptrace(
|
||||
PTRACE_GETREGSET_FALLBACK.try_into().unwrap(),
|
||||
pid.as_raw(),
|
||||
NT_PRSTATUS_FALLBACK,
|
||||
&mut iovec,
|
||||
&mut regs as *mut _,
|
||||
);
|
||||
if r < 0 {
|
||||
let e = std::io::Error::last_os_error();
|
||||
return Err(anyhow::anyhow!("GETREGSET failed: {e}"));
|
||||
return Err(anyhow::anyhow!("GETREGSET failed"));
|
||||
}
|
||||
}
|
||||
Ok(regs)
|
||||
}
|
||||
|
||||
/// List all thread ids under `pid`
|
||||
fn list_tids(pid: i32) -> Vec<Pid> {
|
||||
let mut out = Vec::new();
|
||||
if let Ok(rd) = fs::read_dir(format!("/proc/{pid}/task")) {
|
||||
for e in rd.flatten() {
|
||||
if let Ok(s) = e.file_name().into_string() {
|
||||
if let Ok(t) = s.parse::<i32>() {
|
||||
out.push(Pid::from_raw(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn attach_and_prepare(tid: Pid) -> anyhow::Result<()> {
|
||||
match ptrace::attach(tid) {
|
||||
Ok(_) => {
|
||||
info!("attached to {tid}");
|
||||
}
|
||||
Err(e) if e == Errno::EPERM || e == Errno::EBUSY => {
|
||||
// already traced
|
||||
}
|
||||
Err(e) => return Err(anyhow::anyhow!("attach({tid}) failed: {e}")),
|
||||
}
|
||||
|
||||
loop {
|
||||
match waitpid(tid, Some(WaitPidFlag::__WALL))? {
|
||||
WaitStatus::Stopped(_, _)
|
||||
| WaitStatus::PtraceSyscall(_)
|
||||
| WaitStatus::PtraceEvent(_, _, _) => break,
|
||||
WaitStatus::Exited(_, _) | WaitStatus::Signaled(_, _, _) => {
|
||||
return Err(anyhow::anyhow!("tid {tid} exited while attaching"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
ptrace::setoptions(
|
||||
tid,
|
||||
Options::PTRACE_O_TRACESYSGOOD
|
||||
| Options::PTRACE_O_TRACECLONE
|
||||
| Options::PTRACE_O_TRACEEXEC
|
||||
| Options::PTRACE_O_TRACEEXIT,
|
||||
)?;
|
||||
|
||||
info!("{tid} setup completed!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resume_syscall(tid: Pid) -> anyhow::Result<()> {
|
||||
ptrace::syscall(tid, None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn geteventmsg_u32(tid: Pid) -> u32 {
|
||||
let mut val: libc::c_ulong = 0;
|
||||
unsafe {
|
||||
libc::ptrace(
|
||||
libc::PTRACE_GETEVENTMSG,
|
||||
tid.as_raw(),
|
||||
0,
|
||||
&mut val as *mut _,
|
||||
)
|
||||
};
|
||||
val as u32
|
||||
}
|
||||
|
||||
// x8 holds syscall
|
||||
fn syscall_no(regs: &UserPtRegs) -> u64 {
|
||||
regs.regs[8]
|
||||
}
|
||||
|
||||
// x0 1st arg fd/buf
|
||||
// x0 1st arg
|
||||
fn arg0(regs: &UserPtRegs) -> u64 {
|
||||
regs.regs[0]
|
||||
}
|
||||
|
|
@ -138,225 +60,114 @@ fn arg1(regs: &UserPtRegs) -> u64 {
|
|||
regs.regs[1]
|
||||
}
|
||||
|
||||
// x2 count (write/sento)
|
||||
fn arg2(regs: &UserPtRegs) -> u64 {
|
||||
regs.regs[2]
|
||||
}
|
||||
|
||||
fn retval(regs: &UserPtRegs) -> i64 {
|
||||
regs.regs[0] as i64
|
||||
}
|
||||
|
||||
// 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(())
|
||||
// }
|
||||
|
||||
fn read_remote(pid: Pid, addr: usize, want: usize, out: &mut Vec<u8>) -> anyhow::Result<usize> {
|
||||
if want == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
const PAGE: usize = 4096;
|
||||
// let start = addr;
|
||||
let end = addr + want;
|
||||
let first_clip = ((addr + PAGE) & !(PAGE - 1)).min(end);
|
||||
let take = first_clip - addr;
|
||||
|
||||
out.resize(take, 0);
|
||||
let mut local = iovec {
|
||||
iov_base: out.as_mut_ptr().cast(),
|
||||
iov_len: take,
|
||||
};
|
||||
let mut remote = iovec {
|
||||
iov_base: addr as *mut _,
|
||||
iov_len: take,
|
||||
};
|
||||
let n = unsafe { libc::process_vm_readv(pid.as_raw(), &mut local, 1, &mut remote, 1, 0) };
|
||||
|
||||
if n < 0 {
|
||||
let e = std::io::Error::last_os_error();
|
||||
anyhow::bail!("process vm ready failed at 0x{addr:x} len={take}: {e}");
|
||||
}
|
||||
|
||||
Ok(n as usize)
|
||||
}
|
||||
|
||||
// const SYS_write: u64 = 64;
|
||||
// const SYS_sendto: u64 = 206;
|
||||
// const SYS_read: u64 = 63;
|
||||
// const SYS_recvfrom: u64 = 207;
|
||||
|
||||
fn is_send_syscall(no: u64) -> bool {
|
||||
no as i64 == libc::SYS_write || no as i64 == libc::SYS_sendto
|
||||
}
|
||||
|
||||
fn is_recv_syscall(no: u64) -> bool {
|
||||
no as i64 == libc::SYS_read || no as i64 == libc::SYS_recvfrom
|
||||
}
|
||||
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,
|
||||
last_alert: Instant
|
||||
}
|
||||
|
||||
impl ConnState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
buf: BytesMut::with_capacity(4096),
|
||||
last_alert: Instant::now() - Duration::from_secs(3600),
|
||||
last_alert: Instant::now() - Duration::from_secs(3600)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// v2: rework to also read all threads under pid
|
||||
pub fn run(
|
||||
pid: i32,
|
||||
rules: RuleSet,
|
||||
action: String,
|
||||
debounce: Duration,
|
||||
peek: usize,
|
||||
) -> Result<()> {
|
||||
let mut tids: HashSet<Pid> = list_tids(pid).into_iter().collect();
|
||||
anyhow::ensure!(!tids.is_empty(), "no threads for adbd");
|
||||
|
||||
info!("threads: {:?}", tids);
|
||||
|
||||
// attach to all tid
|
||||
for t in &tids {
|
||||
attach_and_prepare(*t)?;
|
||||
}
|
||||
|
||||
// set all to resume state
|
||||
for t in &tids {
|
||||
resume_syscall(*t)?;
|
||||
}
|
||||
|
||||
// Connection state of (tid, fd)
|
||||
let mut conns: HashMap<(i32, i32), ConnState> = HashMap::new();
|
||||
|
||||
loop {
|
||||
let st = waitpid(Pid::from_raw(-1), Some(WaitPidFlag::__WALL))?;
|
||||
|
||||
match st {
|
||||
// syscall stop -> entry
|
||||
WaitStatus::PtraceSyscall(tid) => {
|
||||
let regs_e = get_regs(tid)?;
|
||||
let no = syscall_no(®s_e);
|
||||
|
||||
let (fd, buf_ptr, count) = if is_send_syscall(no) {
|
||||
(
|
||||
arg0(®s_e) as i32,
|
||||
arg1(®s_e) as usize,
|
||||
arg2(®s_e) as usize,
|
||||
)
|
||||
} else if is_recv_syscall(no) {
|
||||
(
|
||||
arg0(®s_e) as i32,
|
||||
arg0(®s_e) as usize,
|
||||
arg1(®s_e) as usize,
|
||||
)
|
||||
} else {
|
||||
//
|
||||
resume_syscall(tid)?;
|
||||
let _ = waitpid(tid, Some(WaitPidFlag::__WALL))?;
|
||||
resume_syscall(tid)?;
|
||||
continue;
|
||||
};
|
||||
|
||||
info!("syscall({},{},{})", fd, buf_ptr, count);
|
||||
|
||||
// run to exit
|
||||
resume_syscall(tid)?;
|
||||
let st2 = waitpid(tid, Some(WaitPidFlag::__WALL))?;
|
||||
if let WaitStatus::PtraceSyscall(_tid2) = st2 {
|
||||
let regs_x = get_regs(tid)?;
|
||||
let ret = retval(®s_x);
|
||||
|
||||
info!("regX({ret})");
|
||||
|
||||
if ret > 0 {
|
||||
// peek byte from user buffer
|
||||
let want = (ret as usize).min(peek).min(count);
|
||||
if want > 0 {
|
||||
let mut tmp = Vec::new();
|
||||
if let Ok(n) = read_remote(tid, buf_ptr, want, &mut tmp) {
|
||||
info!("read_remote({tid}, {buf_ptr}, {want}, {n}) = {tmp:?}");
|
||||
if n > 0 {
|
||||
let key = (tid.as_raw(), fd);
|
||||
let s = conns.entry(key).or_insert_with(ConnState::new);
|
||||
s.buf.extend_from_slice(&tmp[..n]);
|
||||
|
||||
// try parsing
|
||||
while let Some(frame) = adb::try_parse(&mut s.buf) {
|
||||
info!("[cmd] {}, {:?}", frame.cmd, frame.payload);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resume_syscall(tid)?;
|
||||
} else {
|
||||
// error but resume to keep thread running
|
||||
resume_syscall(tid)?;
|
||||
}
|
||||
}
|
||||
WaitStatus::Stopped(tid, _sig) => {
|
||||
info!("{tid} stopped");
|
||||
// signal stop -> put back to syscall
|
||||
let _ = resume_syscall(tid);
|
||||
}
|
||||
WaitStatus::PtraceEvent(tid, _sig, ev) => {
|
||||
let ev_i = ev as i32;
|
||||
info!("{tid} ev: {ev_i}");
|
||||
if ev_i == libc::PTRACE_EVENT_CLONE {
|
||||
// has new tid
|
||||
let new_tid = Pid::from_raw(geteventmsg_u32(tid) as i32);
|
||||
if !tids.contains(&new_tid) {
|
||||
attach_and_prepare(new_tid)?;
|
||||
resume_syscall(new_tid)?;
|
||||
tids.insert(new_tid);
|
||||
}
|
||||
}
|
||||
// resume parent
|
||||
let _ = resume_syscall(tid);
|
||||
}
|
||||
WaitStatus::Exited(tid, _) | WaitStatus::Signaled(tid, _, _) => {
|
||||
info!("{tid} exitted");
|
||||
tids.remove(&tid);
|
||||
// cleanup
|
||||
conns.retain(|(t, _fd), _| *t != tid.as_raw());
|
||||
if tids.is_empty() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// ignored other statuses
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,631 +0,0 @@
|
|||
use anyhow::{Context, Result};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use crossbeam_channel as xch;
|
||||
use nix::{
|
||||
errno::Errno,
|
||||
sys::{
|
||||
ptrace::{self, Options},
|
||||
uio::process_vm_readv,
|
||||
wait::{WaitPidFlag, WaitStatus, waitpid},
|
||||
},
|
||||
unistd::Pid,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs,
|
||||
io::Read,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{adb, broadcaster, rule::RuleSet};
|
||||
|
||||
// ====== arch & regs (aarch64) ======
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct UserPtRegs {
|
||||
regs: [u64; 31],
|
||||
sp: u64,
|
||||
pc: u64,
|
||||
pstate: u64,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn sys_no(r: &UserPtRegs) -> i64 {
|
||||
r.regs[8] as i64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arg0(r: &UserPtRegs) -> u64 {
|
||||
r.regs[0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arg1(r: &UserPtRegs) -> u64 {
|
||||
r.regs[1]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn arg2(r: &UserPtRegs) -> u64 {
|
||||
r.regs[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn retval(r: &UserPtRegs) -> i64 {
|
||||
r.regs[0] as i64
|
||||
}
|
||||
|
||||
// FALLBACK constants, in case of unsupported os while compiling
|
||||
const PTRACE_GETREGSET_FALLBACK: nix::libc::c_int = 0x4204;
|
||||
// const PTRACE_SEIZE_FALLBACK: libc::c_int = 0x4206;
|
||||
// const PTRACE_INTERRUPT: libc::c_int = 0x4207;
|
||||
const NT_PRSTATUS_FALLBACK: nix::libc::c_int = 1;
|
||||
const SYS_fork: nix::libc::c_long = 57;
|
||||
const SYS_vfork: nix::libc::c_long = 58;
|
||||
|
||||
fn get_regs(tid: Pid) -> Result<UserPtRegs> {
|
||||
use nix::libc::iovec;
|
||||
let mut regs = UserPtRegs::default();
|
||||
let mut iov = iovec {
|
||||
iov_base: (&mut regs as *mut UserPtRegs).cast(),
|
||||
iov_len: core::mem::size_of::<UserPtRegs>(),
|
||||
};
|
||||
let rc = unsafe {
|
||||
nix::libc::ptrace(
|
||||
PTRACE_GETREGSET_FALLBACK.try_into().unwrap(),
|
||||
tid.as_raw(),
|
||||
NT_PRSTATUS_FALLBACK,
|
||||
&mut iov,
|
||||
)
|
||||
};
|
||||
if rc == -1 {
|
||||
Err(anyhow::anyhow!(
|
||||
"GETREGSET({tid}) {}",
|
||||
std::io::Error::last_os_error()
|
||||
))
|
||||
} else {
|
||||
Ok(regs)
|
||||
}
|
||||
}
|
||||
|
||||
// ====== tiny helpers ======
|
||||
fn read_to_string(p: impl AsRef<std::path::Path>) -> Option<String> {
|
||||
let mut f = fs::File::open(p).ok()?;
|
||||
let mut s = String::new();
|
||||
f.read_to_string(&mut s).ok()?;
|
||||
Some(s)
|
||||
}
|
||||
fn list_tids(pid: i32) -> Vec<i32> {
|
||||
fs::read_dir(format!("/proc/{pid}/task"))
|
||||
.ok()
|
||||
.into_iter()
|
||||
.flat_map(|it| it.filter_map(|e| e.ok()))
|
||||
.filter_map(|e| e.file_name().into_string().ok())
|
||||
.filter_map(|s| s.parse::<i32>().ok())
|
||||
.collect()
|
||||
}
|
||||
fn hex_port_to_u16(hex: &str) -> Option<u16> {
|
||||
u16::from_str_radix(hex, 16).ok()
|
||||
}
|
||||
fn parse_tcp_inodes(target_port: u16) -> HashSet<u64> {
|
||||
fn grab(file: &str, target_port: u16) -> HashSet<u64> {
|
||||
let mut set = HashSet::new();
|
||||
if let Some(s) = read_to_string(file) {
|
||||
for line in s.lines().skip(1) {
|
||||
let cols: Vec<_> = line.split_whitespace().collect();
|
||||
if cols.len() < 10 {
|
||||
continue;
|
||||
}
|
||||
let lp = cols[1].split(':').nth(1).unwrap_or("");
|
||||
if let Some(p) = hex_port_to_u16(lp) {
|
||||
if p == target_port {
|
||||
if let Ok(i) = cols[9].parse::<u64>() {
|
||||
set.insert(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set
|
||||
}
|
||||
let mut s = grab("/proc/net/tcp", target_port);
|
||||
s.extend(grab("/proc/net/tcp6", target_port));
|
||||
s
|
||||
}
|
||||
|
||||
/// Discover TIDs that touch ADB transport FDs.
|
||||
fn discover_transport_tids(pid: i32, port: u16) -> HashSet<i32> {
|
||||
let tcp_inodes = parse_tcp_inodes(port);
|
||||
let mut tids = HashSet::new();
|
||||
for tid in list_tids(pid) {
|
||||
let fd_dir = format!("/proc/{pid}/task/{tid}/fd");
|
||||
let Ok(rd) = fs::read_dir(&fd_dir) else {
|
||||
continue;
|
||||
};
|
||||
'fdloop: for fd in rd.flatten() {
|
||||
let target = fs::read_link(fd.path()).unwrap_or(PathBuf::new());
|
||||
let s = target.to_string_lossy();
|
||||
if s.contains("usb-ffs") && s.contains("/adb/") {
|
||||
tids.insert(tid);
|
||||
break 'fdloop;
|
||||
}
|
||||
if let Some(idx) = s.find("socket:[") {
|
||||
if let Ok(inode) = s[idx + 8..].trim_end_matches(']').parse::<u64>() {
|
||||
if tcp_inodes.contains(&inode) {
|
||||
tids.insert(tid);
|
||||
break 'fdloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tids
|
||||
}
|
||||
|
||||
struct ResumeGuard(Pid);
|
||||
impl Drop for ResumeGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = nix::sys::ptrace::syscall(self.0, None);
|
||||
}
|
||||
}
|
||||
|
||||
// ====== ptrace bits ======
|
||||
fn attach_one(tid: Pid) -> Result<()> {
|
||||
match ptrace::attach(tid) {
|
||||
Ok(_) => {}
|
||||
Err(e) if e == Errno::EPERM || e == Errno::EBUSY => return Ok(()),
|
||||
Err(e) => return Err(anyhow::anyhow!("attach({tid}) {e}")),
|
||||
}
|
||||
loop {
|
||||
match waitpid(tid, Some(WaitPidFlag::__WALL))? {
|
||||
WaitStatus::Stopped(_, _)
|
||||
| WaitStatus::PtraceSyscall(_)
|
||||
| WaitStatus::PtraceEvent(_, _, _) => break,
|
||||
WaitStatus::Exited(_, _) | WaitStatus::Signaled(_, _, _) => {
|
||||
return Err(anyhow::anyhow!("tid {tid} exited"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ptrace::setoptions(
|
||||
tid,
|
||||
Options::PTRACE_O_TRACESYSGOOD | Options::PTRACE_O_TRACECLONE | Options::PTRACE_O_TRACEEXIT,
|
||||
)?;
|
||||
ptrace::syscall(tid, None)?;
|
||||
Ok(())
|
||||
}
|
||||
fn detach_one(tid: Pid) {
|
||||
let _ = ptrace::detach(tid, None);
|
||||
}
|
||||
|
||||
/// Saftety read each page
|
||||
fn read_user_page_safe(tid: Pid, addr: usize, want: usize, out: &mut Vec<u8>) -> Result<usize> {
|
||||
use nix::libc::{iovec, process_vm_readv};
|
||||
if want == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
const PAGE: usize = 4096;
|
||||
let page_end = ((addr + PAGE) & !(PAGE - 1)).max(addr);
|
||||
let take = want.min(page_end.saturating_sub(addr));
|
||||
out.resize(take, 0);
|
||||
let mut liov = iovec {
|
||||
iov_base: out.as_mut_ptr().cast(),
|
||||
iov_len: take,
|
||||
};
|
||||
let mut riov = iovec {
|
||||
iov_base: addr as *mut _,
|
||||
iov_len: take,
|
||||
};
|
||||
let n = unsafe { process_vm_readv(tid.as_raw(), &mut liov, 1, &mut riov, 1, 0) };
|
||||
if n < 0 {
|
||||
Err(anyhow::anyhow!(
|
||||
"process_vm_readv {tid} 0x{addr:x} {take}: {}",
|
||||
std::io::Error::last_os_error()
|
||||
))
|
||||
} else {
|
||||
Ok(n as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_cstring(pid: Pid, mut addr: usize, max: usize) -> anyhow::Result<String> {
|
||||
const PAGE: usize = 4096;
|
||||
let mut out = Vec::new();
|
||||
|
||||
while out.len() < max {
|
||||
let end = ((addr + PAGE) & !(PAGE - 1)).max(addr);
|
||||
let take = (end - addr).min(max - out.len());
|
||||
let mut buf = vec![0u8; take];
|
||||
let n = read_user_page_safe(pid, addr, take, &mut buf)?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(z) = buf[..n].iter().position(|&b| b == 0) {
|
||||
out.extend_from_slice(&buf[..z]);
|
||||
break;
|
||||
} else {
|
||||
out.extend_from_slice(&buf[..n]);
|
||||
addr += n;
|
||||
if n < take {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&out).to_string())
|
||||
}
|
||||
|
||||
fn read_argv(
|
||||
pid: Pid,
|
||||
argv_ptr: usize,
|
||||
max_args: usize,
|
||||
max_len: usize,
|
||||
) -> anyhow::Result<Vec<String>> {
|
||||
use nix::libc::{iovec, process_vm_readv};
|
||||
let ps = std::mem::size_of::<usize>();
|
||||
let mut res = Vec::new();
|
||||
|
||||
for i in 0..max_args {
|
||||
let mut pbuf = [0u8; 8];
|
||||
let mut liov = iovec {
|
||||
iov_base: pbuf.as_mut_ptr().cast(),
|
||||
iov_len: ps,
|
||||
};
|
||||
let mut riov = iovec {
|
||||
iov_base: (argv_ptr + i * ps) as *mut _,
|
||||
iov_len: ps,
|
||||
};
|
||||
let n = unsafe { process_vm_readv(pid.as_raw(), &mut liov, 1, &mut riov, 1, 0) };
|
||||
if n as usize != ps {
|
||||
break;
|
||||
}
|
||||
let ptr = usize::from_ne_bytes(pbuf);
|
||||
if ptr == 0 {
|
||||
break;
|
||||
}
|
||||
res.push(read_cstring(pid, ptr, max_len).unwrap_or_default());
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn fd_target(tid: i32, fd: i32) -> Option<String> {
|
||||
fs::read_link(format!("/proc/{tid}/fd/{fd}"))
|
||||
.ok()
|
||||
.map(|p| p.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
fn ascii_preview(s: &[u8]) -> String {
|
||||
let mut o = String::new();
|
||||
for &b in s {
|
||||
match b {
|
||||
b'\n' => o.push_str("\\n"),
|
||||
b'\r' => o.push_str("\\r"),
|
||||
b'\t' => o.push_str("\\t"),
|
||||
0x20..=0x7e => o.push(b as char),
|
||||
_ => o.push_str(&format!("\\x{:02x}", b)),
|
||||
}
|
||||
}
|
||||
o
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_send(no: i64) -> bool {
|
||||
// no == nix::libc::SYS_write as i64 || no == nix::libc::SYS_sendto as i64
|
||||
false
|
||||
}
|
||||
#[inline]
|
||||
fn is_recv(no: i64) -> bool {
|
||||
no == nix::libc::SYS_read as i64 || no == nix::libc::SYS_recvfrom as i64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_exec(no: i64) -> bool {
|
||||
no == nix::libc::SYS_execve as i64 || no == nix::libc::SYS_execveat as i64
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_clone(no: i64) -> bool {
|
||||
no == nix::libc::SYS_clone as i64 || no == SYS_fork as i64 || no == SYS_vfork as i64
|
||||
}
|
||||
|
||||
// ====== public entry ======
|
||||
|
||||
/// Start the combined reactor. Call this from your tokio::main.
|
||||
pub async fn run_with_tokio_rayon(
|
||||
adbd_pid: i32,
|
||||
rules: RuleSet,
|
||||
broadcast_action: String,
|
||||
debounce: Duration,
|
||||
peek: usize,
|
||||
adb_tcp_port: u16,
|
||||
) -> Result<()> {
|
||||
// -------- channels --------
|
||||
type Slice = (i32 /*tid*/, i32 /*fd*/, Bytes);
|
||||
// ptrace → consumers (bounded to never block ptrace thread)
|
||||
let (tx_slice, rx_slice) = xch::bounded::<Slice>(1024);
|
||||
// control: desired TID set (rescan task → ptrace thread)
|
||||
let (tx_ctl, rx_ctl) = xch::bounded::<HashSet<i32>>(4);
|
||||
|
||||
// -------- ptrace thread (blocking) --------
|
||||
thread::spawn({
|
||||
let tx = tx_slice.clone();
|
||||
move || {
|
||||
let mut attached: HashSet<i32> = HashSet::new();
|
||||
loop {
|
||||
// Non-blockingly apply control updates
|
||||
while let Ok(want) = rx_ctl.try_recv() {
|
||||
// attach new
|
||||
for tid in want.difference(&attached.clone()) {
|
||||
if attach_one(Pid::from_raw(*tid)).is_ok() {
|
||||
tracing::info!("attached transport tid={}", tid);
|
||||
attached.insert(*tid);
|
||||
}
|
||||
}
|
||||
// detach stale
|
||||
for tid in attached.clone() {
|
||||
if !want.contains(&tid) {
|
||||
detach_one(Pid::from_raw(tid));
|
||||
attached.remove(&tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if attached.is_empty() {
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
continue;
|
||||
}
|
||||
|
||||
// wait for any stop (from our tracees)
|
||||
let st = match waitpid(Pid::from_raw(-1), Some(WaitPidFlag::__WALL)) {
|
||||
Ok(s) => s,
|
||||
Err(Errno::ECHILD) => {
|
||||
attached.clear();
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("waitpid: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match st {
|
||||
WaitStatus::PtraceSyscall(tid) => {
|
||||
let _rg = ResumeGuard(tid);
|
||||
// entry
|
||||
let regs_e = match get_regs(tid) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let no = sys_no(®s_e);
|
||||
|
||||
let is_in_target =
|
||||
is_send(no) || is_recv(no) || is_clone(no) || is_exec(no);
|
||||
|
||||
if !is_in_target {
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
let _ = waitpid(tid, Some(WaitPidFlag::__WALL));
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ------ ENTRY actions ---------
|
||||
if is_exec(no) {
|
||||
// execve(path=a0, argv=a1)
|
||||
// execveat(path=a1, argv=a2)
|
||||
let (path_ptr, argv_ptr) = if no == nix::libc::SYS_execve as i64 {
|
||||
(arg0(®s_e) as usize, arg1(®s_e) as usize)
|
||||
} else {
|
||||
(arg1(®s_e) as usize, arg2(®s_e) as usize)
|
||||
};
|
||||
|
||||
if let Ok(path) = read_cstring(tid, path_ptr, 512) {
|
||||
let argv = read_argv(tid, argv_ptr, 8, 256).unwrap_or_default();
|
||||
info!("execve({}, {:?})", path, argv);
|
||||
}
|
||||
}
|
||||
|
||||
// run to exit
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
if let Ok(WaitStatus::PtraceSyscall(_)) =
|
||||
waitpid(tid, Some(WaitPidFlag::__WALL))
|
||||
{
|
||||
let regs_x = match get_regs(tid) {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let ret = retval(®s_x);
|
||||
if ret > 0 && (is_recv(no) || is_send(no)) {
|
||||
let (fd, buf_ptr, count) = (
|
||||
arg0(®s_e) as i32,
|
||||
arg1(®s_e) as usize,
|
||||
arg2(®s_e) as usize,
|
||||
);
|
||||
let want = (ret as usize).min(peek).min(count);
|
||||
if want > 0 {
|
||||
let mut tmp = Vec::new();
|
||||
if let Ok(n) = read_user_page_safe(tid, buf_ptr, want, &mut tmp)
|
||||
{
|
||||
// resume ASAP
|
||||
// let _ = ptrace::syscall(tid, None);
|
||||
// // push slice (drop if full)
|
||||
// let _ = tx.try_send((
|
||||
// tid.as_raw(),
|
||||
// fd,
|
||||
// Bytes::copy_from_slice(&tmp[..n]),
|
||||
// ));
|
||||
// continue;
|
||||
|
||||
// log pty i/o
|
||||
if let Some(tgt) = fd_target(tid.as_raw(), fd) {
|
||||
if tgt.contains("/dev/pts") {
|
||||
let what =
|
||||
if is_recv(no) { "read" } else { "write" };
|
||||
info!(
|
||||
"{} fd={} {}: {}",
|
||||
what,
|
||||
fd,
|
||||
tgt,
|
||||
ascii_preview(&tmp[..n])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// resume even if no data
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
} else {
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
}
|
||||
}
|
||||
// plain signal stops → keep moving
|
||||
WaitStatus::Stopped(tid, _sig) => {
|
||||
let _rg = ResumeGuard(tid);
|
||||
}
|
||||
WaitStatus::PtraceEvent(tid, _sig, ev) => {
|
||||
let _rg = ResumeGuard(tid);
|
||||
|
||||
match (ev as i32) {
|
||||
1..=3 => {
|
||||
// CLONE/FORK/VFORK
|
||||
let new_tid = {
|
||||
let mut v: nix::libc::c_ulong = 0;
|
||||
unsafe {
|
||||
nix::libc::ptrace(
|
||||
nix::libc::PTRACE_GETEVENTMSG,
|
||||
tid.as_raw(),
|
||||
0,
|
||||
&mut v as *mut _,
|
||||
)
|
||||
};
|
||||
v as i32
|
||||
};
|
||||
|
||||
let ntid = Pid::from_raw(new_tid);
|
||||
if let Ok(_) = ptrace::attach(ntid) {
|
||||
let _ = waitpid(ntid, Some(WaitPidFlag::__WALL));
|
||||
let _ = ptrace::setoptions(
|
||||
ntid,
|
||||
Options::PTRACE_O_TRACESYSGOOD
|
||||
| Options::PTRACE_O_TRACECLONE
|
||||
| Options::PTRACE_O_TRACEFORK
|
||||
| Options::PTRACE_O_TRACEVFORK
|
||||
| Options::PTRACE_O_TRACEEXEC,
|
||||
);
|
||||
let _ = ptrace::syscall(ntid, None);
|
||||
}
|
||||
}
|
||||
4 => {
|
||||
// EXEC
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// always resume
|
||||
let _ = ptrace::syscall(tid, None);
|
||||
}
|
||||
// exits
|
||||
WaitStatus::Exited(tid, _) | WaitStatus::Signaled(tid, _, _) => {
|
||||
let _rg = ResumeGuard(tid);
|
||||
attached.remove(&tid.as_raw());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// -------- Tokio task: periodic rescan & control updates --------
|
||||
tokio::spawn({
|
||||
let tx_ctl = tx_ctl.clone();
|
||||
async move {
|
||||
loop {
|
||||
let want = discover_transport_tids(adbd_pid, adb_tcp_port);
|
||||
// best effort; if the channel is full, the next tick will update
|
||||
let _ = tx_ctl.try_send(want);
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// -------- Dispatcher → Rayon workers --------
|
||||
// Shared per-conn state (tid,fd) → buffer + last_alert
|
||||
let state = Arc::new(Mutex::new(HashMap::<(i32, i32), (BytesMut, Instant)>::new()));
|
||||
let rules = Arc::new(rules);
|
||||
let action = broadcast_action.clone();
|
||||
|
||||
// Blocking dispatcher thread to drain crossbeam receiver efficiently
|
||||
thread::spawn({
|
||||
let state = Arc::clone(&state);
|
||||
let rules = Arc::clone(&rules);
|
||||
move || {
|
||||
// rayon pool defaults are fine; configure if you want:
|
||||
// rayon::ThreadPoolBuilder::new().num_threads(...).build_global().ok();
|
||||
|
||||
loop {
|
||||
// Build a batch (blocking for first item)
|
||||
let mut batch: Vec<(i32, i32, Bytes)> = Vec::with_capacity(256);
|
||||
match rx_slice.recv() {
|
||||
Ok(first) => batch.push(first),
|
||||
Err(_) => break, // channel closed
|
||||
}
|
||||
while let Ok(item) = rx_slice.try_recv() {
|
||||
batch.push(item);
|
||||
if batch.len() >= 1024 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Process in parallel
|
||||
batch.par_iter().for_each(|(tid, fd, chunk)| {
|
||||
let mut to_broadcast: Option<String> = None;
|
||||
let mut matched_rule: Option<&str> = None;
|
||||
{
|
||||
let mut st = state.lock().unwrap();
|
||||
let entry = st.entry((*tid, *fd)).or_insert_with(|| {
|
||||
(BytesMut::with_capacity(8192), Instant::now() - debounce * 2)
|
||||
});
|
||||
entry.0.extend_from_slice(chunk);
|
||||
|
||||
while let Some(f) = adb::try_parse(&mut entry.0) {
|
||||
if f.cmd == adb::CMD_OPEN {
|
||||
let service = String::from_utf8_lossy(&f.payload).to_string();
|
||||
if let Some(rule) = rules.match_service(&service) {
|
||||
matched_rule = Some(rule);
|
||||
if entry.1.elapsed() >= debounce {
|
||||
entry.1 = Instant::now();
|
||||
to_broadcast = Some(service);
|
||||
break; // one alert per batch per conn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if entry.0.len() > 64 * 1024 {
|
||||
entry.0.truncate(4096);
|
||||
}
|
||||
}
|
||||
if let Some(service) = to_broadcast {
|
||||
if matched_rule.is_some() {
|
||||
crate::broadcaster::broadcast(&action, &service, matched_rule.unwrap());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// keep the async runtime alive (your main probably does more)
|
||||
futures::future::pending::<()>().await;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,982 +0,0 @@
|
|||
// V2
|
||||
// - Fully write in daemon
|
||||
// - Will use seize for asynchronus (not blocking the process)
|
||||
// Note: this may not able to capture suspicious and block immediately
|
||||
|
||||
use nix::{
|
||||
sys::{
|
||||
signal::{self, SigHandler, Signal},
|
||||
wait::{WaitPidFlag, WaitStatus, waitpid},
|
||||
},
|
||||
unistd::{ForkResult, Pid, fork, setsid},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::{self, File, OpenOptions},
|
||||
io::Write,
|
||||
os::fd::{AsFd, AsRawFd},
|
||||
path::Path,
|
||||
sync::{Arc, Mutex, atomic::AtomicBool},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::nr::NR_CODES;
|
||||
|
||||
// Configuration
|
||||
const DAEMON_NAME: &str = "adbdguard";
|
||||
const PID_FILE: &str = "/data/local/adbdguard/adbdguard.pid";
|
||||
const LOG_FILE: &str = "/data/local/adbdguard/adbdg.log";
|
||||
const CONFIG_FILE: &str = "/data/local/adbdguard/adbdguard.conf";
|
||||
const CONTROL_SOCKET: &str = "/data/local/adbdguard/adbdguard.sock";
|
||||
|
||||
// Global State
|
||||
static DAEMON_RUNNING: AtomicBool = AtomicBool::new(false);
|
||||
static RELOAD_CONFIG: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Helpers
|
||||
pub type DefaultResult = Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
// Daemon config
|
||||
// - this config is not from adbdguard.json
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DaemonConfig {
|
||||
pub log_level: LogLevel,
|
||||
pub monitor_syscalls: bool,
|
||||
pub monitor_network: bool,
|
||||
pub monitor_files: bool,
|
||||
pub rotation_size: u64,
|
||||
/// seconds
|
||||
pub status_interval: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
Trace,
|
||||
}
|
||||
|
||||
impl Default for DaemonConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
log_level: LogLevel::Debug,
|
||||
monitor_syscalls: true,
|
||||
monitor_network: true,
|
||||
monitor_files: false,
|
||||
rotation_size: 10 * 1024 * 1024,
|
||||
status_interval: 30,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------- Process --------------------
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProcessInfo {
|
||||
pid: Pid,
|
||||
name: String,
|
||||
cmdline: String,
|
||||
}
|
||||
|
||||
// Options for SEIZE (does not use from lib)
|
||||
const PTRACE_O_TRACESYSGOOD: i32 = 0x00000001;
|
||||
const PTRACE_O_TRACEFORK: i32 = 0x00000002;
|
||||
const PTRACE_O_TRACEVFORK: i32 = 0x00000004;
|
||||
const PTRACE_O_TRACECLONE: i32 = 0x00000008;
|
||||
const PTRACE_O_TRACEEXEC: i32 = 0x00000010;
|
||||
const PTRACE_O_TRACEVFORKDONE: i32 = 0x00000020;
|
||||
const PTRACE_O_TRACEEXIT: i32 = 0x00000040;
|
||||
const PTRACE_O_TRACESECCOMP: i32 = 0x00000080;
|
||||
|
||||
const PTRACE_GETREGSET_FALLBACK: nix::libc::c_int = 0x4204;
|
||||
const PTRACE_SEIZE_FALLBACK: libc::c_int = 0x4206;
|
||||
const PTRACE_INTERRUPT: libc::c_int = 0x4207;
|
||||
const NT_PRSTATUS_FALLBACK: nix::libc::c_int = 1;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DaemonLogger {
|
||||
file: Arc<Mutex<File>>,
|
||||
config: Arc<Mutex<DaemonConfig>>,
|
||||
}
|
||||
|
||||
impl DaemonLogger {
|
||||
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(LOG_FILE)?;
|
||||
|
||||
Ok(Self {
|
||||
file: Arc::new(Mutex::new(file)),
|
||||
config: Arc::new(Mutex::new(DaemonConfig::default())),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn log(&self, level: LogLevel, message: &str) {
|
||||
let config = self.config.lock().unwrap();
|
||||
if self.should_log(&level, &config.log_level) {
|
||||
drop(config);
|
||||
|
||||
let timestamp = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC");
|
||||
let log_line = format!("[{}] {:?}: {}\n", timestamp, level, message);
|
||||
|
||||
if let Ok(mut file) = self.file.lock() {
|
||||
let _ = file.write_all(log_line.as_bytes());
|
||||
let _ = file.flush();
|
||||
}
|
||||
|
||||
// Also print to stdout if not fully daemonized (for debugging)
|
||||
if std::env::var("ADBDGUARD_DEBUG").is_ok() {
|
||||
match level {
|
||||
LogLevel::Error => error!("{}", message),
|
||||
LogLevel::Warn => warn!("{}", message),
|
||||
LogLevel::Info => info!("{}", message),
|
||||
LogLevel::Debug => debug!("{}", message),
|
||||
LogLevel::Trace => info!("{}", message),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn should_log(&self, msg_level: &LogLevel, config_level: &LogLevel) -> bool {
|
||||
let def_level = |il: &LogLevel| -> i32 {
|
||||
match il {
|
||||
LogLevel::Error => 0,
|
||||
LogLevel::Warn => 1,
|
||||
LogLevel::Info => 2,
|
||||
LogLevel::Debug => 3,
|
||||
LogLevel::Trace => 4,
|
||||
}
|
||||
};
|
||||
|
||||
def_level(msg_level) <= def_level(config_level)
|
||||
}
|
||||
|
||||
pub fn update_config(&self, new_config: DaemonConfig) {
|
||||
*self.config.lock().unwrap() = new_config;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
struct UserPtRegs {
|
||||
regs: [u64; 31],
|
||||
sp: u64,
|
||||
pc: u64,
|
||||
pstate: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PtraceDaemon {
|
||||
target_processes: Vec<ProcessInfo>,
|
||||
seized_pids: HashMap<Pid, bool>,
|
||||
monitoring_active: bool,
|
||||
logger: Arc<DaemonLogger>,
|
||||
config: Arc<Mutex<DaemonConfig>>,
|
||||
}
|
||||
|
||||
impl PtraceDaemon {
|
||||
pub fn new(logger: Arc<DaemonLogger>) -> Self {
|
||||
Self {
|
||||
target_processes: Vec::new(),
|
||||
seized_pids: HashMap::new(),
|
||||
monitoring_active: false,
|
||||
logger: logger.clone(),
|
||||
config: Arc::new(Mutex::new(DaemonConfig::default())),
|
||||
}
|
||||
}
|
||||
|
||||
/// find adbd processes in the system
|
||||
pub fn find_adbd_processes(&mut self) -> DefaultResult {
|
||||
self.target_processes.clear();
|
||||
let proc_dir = fs::read_dir("proc")?;
|
||||
|
||||
let mut adbd_pid: Option<Pid> = None;
|
||||
|
||||
for entry in proc_dir {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if let Some(pid_str) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if let Ok(pid_num) = pid_str.parse::<i32>() {
|
||||
let pid = Pid::from_raw(pid_num);
|
||||
let comm_path = path.join("comm");
|
||||
if let Ok(comm) = fs::read_to_string(&comm_path) {
|
||||
let process_name = comm.trim();
|
||||
|
||||
if (process_name.contains("adbd") || process_name == "adbd")
|
||||
&& !process_name.contains("adbdguard")
|
||||
{
|
||||
let cmdline_path = path.join("cmdline");
|
||||
let cmdline = fs::read_to_string(&cmdline_path)
|
||||
.unwrap_or_default()
|
||||
.replace('\0', " ");
|
||||
|
||||
self.target_processes.push(ProcessInfo {
|
||||
pid,
|
||||
name: process_name.to_string(),
|
||||
cmdline: cmdline.clone(),
|
||||
});
|
||||
|
||||
self.logger.log(
|
||||
LogLevel::Info,
|
||||
&format!(
|
||||
"Found adbd process: PID={}, Name={}, Cmdline={}",
|
||||
pid, process_name, cmdline
|
||||
),
|
||||
);
|
||||
|
||||
// seize main adbd
|
||||
if let Err(e) = self.seize_process(pid) {
|
||||
self.logger.log(
|
||||
LogLevel::Error,
|
||||
&format!("Failed to seize PID {}: {}", pid, e),
|
||||
);
|
||||
} else {
|
||||
// Enable syscall tracing
|
||||
if let Err(e) = self.enable_syscall_tracing(pid) {
|
||||
self.logger.log(
|
||||
LogLevel::Warn,
|
||||
&format!("Syscall tracing failed for PID {}: {}", pid, e),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
adbd_pid = Some(pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.target_processes.is_empty() {
|
||||
self.logger.log(LogLevel::Warn, "No adbd processes found");
|
||||
}
|
||||
|
||||
if adbd_pid.is_some() {
|
||||
if let Err(e) = self.seize_adb_tasks(adbd_pid.unwrap()) {
|
||||
self.logger.log(
|
||||
LogLevel::Error,
|
||||
&format!("Unable to seize task under adb: {e}"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// find tasks under adbd
|
||||
fn seize_adb_tasks(&mut self, adbd_pid: Pid) -> DefaultResult {
|
||||
let mut subtask = Vec::new();
|
||||
// `/proc/{adbd_pid}/task`
|
||||
if let Ok(rd) = fs::read_dir(format!("/proc/{}/task", adbd_pid.as_raw())) {
|
||||
for e in rd.flatten() {
|
||||
if let Ok(s) = e.file_name().into_string() {
|
||||
if let Ok(t) = s.parse::<i32>() {
|
||||
subtask.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// seize process
|
||||
for pid_raw in subtask {
|
||||
if pid_raw == adbd_pid.as_raw() {
|
||||
self.logger.log(LogLevel::Debug, "skip root");
|
||||
continue;
|
||||
}
|
||||
let expected_process_comm = format!("/proc/{pid_raw}/comm");
|
||||
let expected_process_cmdline = format!("/proc/{pid_raw}/cmdline");
|
||||
|
||||
let comm_res = fs::read_to_string(&expected_process_comm);
|
||||
let cmdline_res = fs::read_to_string(&expected_process_cmdline);
|
||||
|
||||
if comm_res.is_ok() && cmdline_res.is_ok() {
|
||||
let pid = Pid::from_raw(pid_raw);
|
||||
let name = comm_res.unwrap().to_string();
|
||||
let cmdline = cmdline_res.unwrap().clone();
|
||||
self.target_processes.push(ProcessInfo {
|
||||
pid,
|
||||
name: name.clone(),
|
||||
cmdline: cmdline.clone(),
|
||||
});
|
||||
self.logger.log(
|
||||
LogLevel::Info,
|
||||
&format!(
|
||||
"Found adbd process: PID={}, Name={}, Cmdline={}",
|
||||
pid,
|
||||
name.clone(),
|
||||
cmdline.clone()
|
||||
),
|
||||
);
|
||||
|
||||
if let Err(e) = self.seize_process(pid) {
|
||||
self.logger.log(
|
||||
LogLevel::Error,
|
||||
&format!("Failed to seize PID {}: {}", pid, e),
|
||||
);
|
||||
return Err(format!("Failed to seize PID {}: {}", pid, e).into());
|
||||
} else {
|
||||
if let Err(e) = self.enable_syscall_tracing(pid) {
|
||||
self.logger.log(
|
||||
LogLevel::Warn,
|
||||
&format!("Syscall tracing failed for PID {}: {}", pid, e),
|
||||
);
|
||||
return Err(format!("Syscall tracing failed for PID {}: {}", pid, e).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// seize the process
|
||||
pub fn seize_process(&mut self, pid: Pid) -> DefaultResult {
|
||||
self.logger
|
||||
.log(LogLevel::Debug, &format!("attempting seize pid {}", pid));
|
||||
|
||||
let options = PTRACE_O_TRACESYSGOOD
|
||||
| PTRACE_O_TRACEFORK
|
||||
| PTRACE_O_TRACECLONE
|
||||
| PTRACE_O_TRACEEXEC
|
||||
| PTRACE_O_TRACEEXIT
|
||||
| PTRACE_O_TRACESECCOMP;
|
||||
|
||||
let result = unsafe {
|
||||
libc::ptrace(
|
||||
PTRACE_SEIZE_FALLBACK.try_into().unwrap(),
|
||||
pid.as_raw(),
|
||||
std::ptr::null_mut::<libc::c_void>(),
|
||||
options as libc::c_ulong,
|
||||
)
|
||||
};
|
||||
|
||||
if result == -1 {
|
||||
let error = nix::Error::last();
|
||||
return Err(format!("failed to seize pid {}: {}", pid, error).into());
|
||||
}
|
||||
|
||||
self.logger
|
||||
.log(LogLevel::Info, &format!("successfully seized pid {}", pid));
|
||||
self.seized_pids.insert(pid, true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// start monitoring loop
|
||||
pub fn start_monitoring(&mut self) -> DefaultResult {
|
||||
if self.seized_pids.is_empty() {
|
||||
return Err(format!("No seized processes to monitor").into());
|
||||
}
|
||||
|
||||
self.monitoring_active = true;
|
||||
self.logger.log(
|
||||
LogLevel::Info,
|
||||
&format!("total processes to monitor: {}", self.seized_pids.len()),
|
||||
);
|
||||
|
||||
let mut event_count = 0u64;
|
||||
let start_time = Instant::now();
|
||||
let mut last_status = start_time;
|
||||
|
||||
while self.monitoring_active && DAEMON_RUNNING.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
if RELOAD_CONFIG.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
self.reload_configuration()?;
|
||||
RELOAD_CONFIG.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
let pids: Vec<Pid> = self.seized_pids.keys().cloned().collect();
|
||||
let mut any_activity = false;
|
||||
|
||||
for pid in pids {
|
||||
let _status = waitpid(pid, Some(WaitPidFlag::WNOHANG))?;
|
||||
if _status != WaitStatus::StillAlive {
|
||||
self.logger
|
||||
.log(LogLevel::Info, &format!("{pid}: {_status:?}"));
|
||||
}
|
||||
match _status {
|
||||
WaitStatus::StillAlive => continue,
|
||||
WaitStatus::PtraceSyscall(stopped_pid) => {
|
||||
event_count += 1;
|
||||
any_activity = true;
|
||||
self.handle_syscall_event(stopped_pid)?;
|
||||
|
||||
nix::sys::ptrace::syscall(stopped_pid, None)?;
|
||||
}
|
||||
WaitStatus::Stopped(stopped_pid, signal) => {
|
||||
event_count += 1;
|
||||
any_activity = true;
|
||||
|
||||
self.logger.log(
|
||||
LogLevel::Debug,
|
||||
&format!("pid {stopped_pid} stopped with code: {:?}", signal),
|
||||
);
|
||||
|
||||
nix::sys::ptrace::cont(stopped_pid, None)?;
|
||||
}
|
||||
WaitStatus::PtraceEvent(stopped_pid, signal, event) => {
|
||||
event_count += 1;
|
||||
any_activity = true;
|
||||
self.handle_ptrace_event(stopped_pid, signal, event)?;
|
||||
|
||||
nix::sys::ptrace::cont(stopped_pid, None)?;
|
||||
}
|
||||
WaitStatus::Exited(exited_pid, exit_code) => {
|
||||
self.logger.log(
|
||||
LogLevel::Info,
|
||||
&format!("pid {exited_pid} exitted with code {exit_code}"),
|
||||
);
|
||||
self.seized_pids.remove(&exited_pid);
|
||||
any_activity = true;
|
||||
|
||||
if self.seized_pids.is_empty() {
|
||||
self.logger
|
||||
.log(LogLevel::Info, "All monitored processes exited");
|
||||
self.monitoring_active = false;
|
||||
}
|
||||
}
|
||||
WaitStatus::Signaled(signaled_pid, signal, core_dump) => {
|
||||
self.logger.log(LogLevel::Warn, &format!("pid {signaled_pid} terminated by signal {signal:?} (core {core_dump})"));
|
||||
self.seized_pids.remove(&signaled_pid);
|
||||
any_activity = true;
|
||||
if self.seized_pids.is_empty() {
|
||||
self.logger
|
||||
.log(LogLevel::Info, "All monitored processes exited");
|
||||
self.monitoring_active = false;
|
||||
}
|
||||
}
|
||||
status => {
|
||||
self.logger.log(
|
||||
LogLevel::Debug,
|
||||
&format!("Unsupported status for pid {}: {:?}", pid, status),
|
||||
);
|
||||
any_activity = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// status report
|
||||
let now = Instant::now();
|
||||
if now.duration_since(last_status).as_secs()
|
||||
>= self.config.lock().unwrap().status_interval
|
||||
{
|
||||
self.logger.log(
|
||||
LogLevel::Info,
|
||||
&format!(
|
||||
"Status: {} events in {:?}, {} processes active",
|
||||
event_count,
|
||||
now.duration_since(start_time),
|
||||
self.seized_pids.len()
|
||||
),
|
||||
);
|
||||
last_status = now;
|
||||
}
|
||||
|
||||
if !any_activity {
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
self.logger.log(
|
||||
LogLevel::Info,
|
||||
&format!("Monitoring stopped. Total events: {}", event_count),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// clone processes info
|
||||
pub fn clone_processes_info(self) -> Vec<ProcessInfo> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
for pif in self.target_processes {
|
||||
result.push(pif.clone());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// clone seized pid state
|
||||
pub fn clone_seized_pids(self) -> HashMap<Pid, bool> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for (pid, state) in self.seized_pids.clone().iter() {
|
||||
result.insert(pid.clone(), state.clone());
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
// handler ----------------------------
|
||||
|
||||
fn handle_syscall_event(&self, pid: Pid) -> DefaultResult {
|
||||
use nix::libc::iovec;
|
||||
let mut regs = UserPtRegs::default();
|
||||
let mut iov = iovec {
|
||||
iov_base: (&mut regs as *mut UserPtRegs).cast(),
|
||||
iov_len: core::mem::size_of::<UserPtRegs>(),
|
||||
};
|
||||
let rc = unsafe {
|
||||
nix::libc::ptrace(
|
||||
PTRACE_GETREGSET_FALLBACK.try_into().unwrap(),
|
||||
pid.as_raw(),
|
||||
NT_PRSTATUS_FALLBACK,
|
||||
&mut iov,
|
||||
)
|
||||
};
|
||||
|
||||
let syscall_num = regs.regs[8];
|
||||
|
||||
self.logger.log(
|
||||
LogLevel::Debug,
|
||||
&format!("{} [{syscall_num}]>>[{rc}]", pid.as_raw()),
|
||||
);
|
||||
|
||||
if rc == -1 {
|
||||
return Err(format!("GETREGSET({pid}) {}", std::io::Error::last_os_error()).into());
|
||||
}
|
||||
|
||||
if self.is_scoped_syscall(syscall_num) {
|
||||
self.logger.log(
|
||||
LogLevel::Debug,
|
||||
&format!(
|
||||
"pid {} syscall: {} ({})",
|
||||
pid,
|
||||
syscall_num,
|
||||
self.syscall_name(syscall_num)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_ptrace_event(&self, pid: Pid, _signal: Signal, event: i32) -> DefaultResult {
|
||||
let event_desc = match event {
|
||||
1 => "forked",
|
||||
2 => "vforked",
|
||||
3 => "cloned",
|
||||
4 => "exec'd",
|
||||
5 => "vfork done",
|
||||
6 => "exiting",
|
||||
7 => "seccomp event",
|
||||
_ => "unknown event",
|
||||
};
|
||||
|
||||
self.logger
|
||||
.log(LogLevel::Info, &format!("PID {} {}", pid, event_desc));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
|
||||
fn reload_configuration(&mut self) -> DefaultResult {
|
||||
self.logger.log(LogLevel::Info, "Reload configuration");
|
||||
|
||||
if Path::new(CONFIG_FILE).exists() {
|
||||
let config_str = fs::read_to_string(CONFIG_FILE)?;
|
||||
let mut config = DaemonConfig::default();
|
||||
|
||||
for line in config_str.lines() {
|
||||
let parts: Vec<&str> = line.splitn(2, '=').collect();
|
||||
if parts.len() == 2 {
|
||||
match parts[0].trim() {
|
||||
"log_level" => {
|
||||
config.log_level = match parts[1].trim() {
|
||||
"error" => LogLevel::Error,
|
||||
"info" => LogLevel::Info,
|
||||
"debug" => LogLevel::Debug,
|
||||
"warn" => LogLevel::Warn,
|
||||
"trace" => LogLevel::Trace,
|
||||
_ => LogLevel::Debug,
|
||||
};
|
||||
}
|
||||
"monitor_syscalls" => {
|
||||
config.monitor_syscalls = parts[1].trim().parse().unwrap_or(true);
|
||||
}
|
||||
"monitor_network" => {
|
||||
config.monitor_network = parts[1].trim().parse().unwrap_or(false);
|
||||
}
|
||||
"status_interval" => {
|
||||
config.status_interval = parts[1].trim().parse().unwrap_or(30);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*self.config.lock().unwrap() = config.clone();
|
||||
self.logger.update_config(config);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_scoped_syscall(&self, syscall_num: u64) -> bool {
|
||||
// match syscall_num {
|
||||
// 41..=50 => true, // socket family
|
||||
// 2..=6 => true, // file operations
|
||||
// 57..=59 => true, // process operations
|
||||
// 16 | 17 => true, // ioctl, pread64
|
||||
// _ => false,
|
||||
// }
|
||||
|
||||
let code = NR_CODES::get(syscall_num);
|
||||
|
||||
code.value() != u64::max_value()
|
||||
}
|
||||
|
||||
fn syscall_name(&self, syscall_num: u64) -> String {
|
||||
// match syscall_num {
|
||||
// 2 => "open",
|
||||
// 3 => "close",
|
||||
// 4 => "stat",
|
||||
// 5 => "fstat",
|
||||
// 6 => "lstat",
|
||||
// 16 => "ioctl",
|
||||
// 17 => "pread64",
|
||||
// 41 => "socket",
|
||||
// 42 => "connect",
|
||||
// 43 => "accept",
|
||||
// 44 => "sendto",
|
||||
// 45 => "recvfrom",
|
||||
// 46 => "sendmsg",
|
||||
// 47 => "recvmsg",
|
||||
// 57 => "fork",
|
||||
// 58 => "vfork",
|
||||
// 59 => "execve",
|
||||
// _ => "unknown",
|
||||
// }
|
||||
|
||||
let nr_code = NR_CODES::get(syscall_num);
|
||||
let nr_code_name = nr_code.name();
|
||||
|
||||
nr_code_name
|
||||
}
|
||||
|
||||
pub fn enable_syscall_tracing(&self, pid: Pid) -> DefaultResult {
|
||||
if !self.seized_pids.contains_key(&pid) {
|
||||
return Err(format!("PID {} not seized", pid).into());
|
||||
}
|
||||
|
||||
self.logger.log(
|
||||
LogLevel::Debug,
|
||||
&format!("Enabling syscall tracing for PID: {}", pid),
|
||||
);
|
||||
|
||||
// Interrupt and start syscall tracing
|
||||
let interrupt_result = unsafe {
|
||||
libc::ptrace(
|
||||
PTRACE_INTERRUPT.try_into().unwrap(),
|
||||
pid.as_raw(),
|
||||
std::ptr::null_mut::<libc::c_void>(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if interrupt_result != -1 {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
nix::sys::ptrace::syscall(pid, None)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self) {
|
||||
let pids: Vec<Pid> = self.seized_pids.keys().cloned().collect();
|
||||
for pid in pids {
|
||||
let _ = nix::sys::ptrace::detach(pid, None);
|
||||
}
|
||||
self.seized_pids.clear();
|
||||
self.monitoring_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Signal handlers for daemon control
|
||||
fn setup_signal_handlers() -> Result<(), nix::Error> {
|
||||
extern "C" fn handle_sigterm(_: libc::c_int) {
|
||||
DAEMON_RUNNING.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
extern "C" fn handle_sighup(_: libc::c_int) {
|
||||
RELOAD_CONFIG.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
extern "C" fn handle_sigint(_: libc::c_int) {
|
||||
DAEMON_RUNNING.store(false, std::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
signal::signal(Signal::SIGTERM, SigHandler::Handler(handle_sigterm))?;
|
||||
signal::signal(Signal::SIGHUP, SigHandler::Handler(handle_sighup))?;
|
||||
signal::signal(Signal::SIGINT, SigHandler::Handler(handle_sigint))?;
|
||||
signal::signal(Signal::SIGPIPE, SigHandler::SigIgn)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write daemon PID to file
|
||||
fn write_pid_file() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let pid = std::process::id();
|
||||
fs::write(PID_FILE, pid.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove PID file
|
||||
fn remove_pid_file() {
|
||||
let _ = fs::remove_file(PID_FILE);
|
||||
}
|
||||
|
||||
/// Check if daemon is already running
|
||||
fn is_daemon_running() -> bool {
|
||||
if let Ok(pid_str) = fs::read_to_string(PID_FILE) {
|
||||
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
||||
// Check if process exists
|
||||
Path::new(&format!("/proc/{}", pid)).exists()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DaemonCommand {
|
||||
Start,
|
||||
Stop,
|
||||
Restart,
|
||||
Status,
|
||||
Reload,
|
||||
}
|
||||
|
||||
fn daemon_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Setup logging
|
||||
let logger = Arc::new(DaemonLogger::new()?);
|
||||
logger.log(LogLevel::Info, "ADB Ptrace Daemon starting");
|
||||
|
||||
// Setup signal handlers
|
||||
setup_signal_handlers()?;
|
||||
|
||||
// Write PID file
|
||||
write_pid_file()?;
|
||||
|
||||
// Create daemon instance
|
||||
let mut daemon = PtraceDaemon::new(logger.clone());
|
||||
|
||||
DAEMON_RUNNING.store(true, std::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
// Main daemon loop
|
||||
loop {
|
||||
if !DAEMON_RUNNING.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
logger.log(LogLevel::Info, "Shutdown signal received");
|
||||
break;
|
||||
}
|
||||
|
||||
// Find and seize adbd processes
|
||||
if let Err(e) = daemon.find_adbd_processes() {
|
||||
logger.log(
|
||||
LogLevel::Error,
|
||||
&format!("Failed to find adbd processes: {}", e),
|
||||
);
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Err(e) = daemon.start_monitoring() {
|
||||
logger.log(LogLevel::Error, &format!("Monitoring error: {}", e));
|
||||
}
|
||||
|
||||
daemon.cleanup();
|
||||
|
||||
// Brief pause before retrying if processes disappeared
|
||||
if DAEMON_RUNNING.load(std::sync::atomic::Ordering::SeqCst) {
|
||||
std::thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
daemon.cleanup();
|
||||
logger.log(LogLevel::Info, "ADB Ptrace Daemon stopped");
|
||||
remove_pid_file();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn parse_args() -> DaemonCommand {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
return DaemonCommand::Start;
|
||||
}
|
||||
|
||||
match args[1].as_str() {
|
||||
"start" => DaemonCommand::Start,
|
||||
"stop" => DaemonCommand::Stop,
|
||||
"restart" => DaemonCommand::Restart,
|
||||
"status" => DaemonCommand::Status,
|
||||
"reload" => DaemonCommand::Reload,
|
||||
_ => {
|
||||
eprintln!("Usage: {} [start|stop|restart|status|reload]", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(command: DaemonCommand) -> DefaultResult {
|
||||
match command {
|
||||
DaemonCommand::Start => {
|
||||
if is_daemon_running() {
|
||||
println!("Daemon is already running");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("Starting ADB Ptrace Daemon...");
|
||||
|
||||
// Check if we should run in foreground (for debugging)
|
||||
if std::env::var("ADBD_TRACKER_FOREGROUND").is_ok() {
|
||||
return daemon_main();
|
||||
}
|
||||
|
||||
// Fork to background
|
||||
match unsafe { fork() } {
|
||||
Ok(ForkResult::Parent { .. }) => {
|
||||
println!("Daemon started successfully");
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(ForkResult::Child) => {
|
||||
// Become session leader
|
||||
setsid()?;
|
||||
|
||||
// Change to root directory
|
||||
std::env::set_current_dir("/")?;
|
||||
|
||||
// Close stdin, stdout, stderr
|
||||
let devnull = fs::File::open("/dev/null")?;
|
||||
nix::unistd::dup2_stdin(devnull.as_fd())?;
|
||||
nix::unistd::dup2_stdout(devnull.as_fd())?;
|
||||
nix::unistd::dup2_stderr(devnull.as_fd())?;
|
||||
|
||||
return daemon_main();
|
||||
}
|
||||
Err(e) => return Err(format!("Fork failed: {}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
DaemonCommand::Stop => {
|
||||
if let Ok(pid_str) = fs::read_to_string(PID_FILE) {
|
||||
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
||||
println!("Stopping daemon (PID: {})...", pid);
|
||||
unsafe {
|
||||
libc::kill(pid, libc::SIGTERM);
|
||||
}
|
||||
|
||||
// Wait for daemon to stop
|
||||
for _ in 0..30 {
|
||||
if !Path::new(&format!("/proc/{}", pid)).exists() {
|
||||
println!("Daemon stopped successfully");
|
||||
remove_pid_file();
|
||||
return Ok(());
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
|
||||
println!("Daemon did not stop gracefully, sending SIGKILL");
|
||||
unsafe {
|
||||
libc::kill(pid, libc::SIGKILL);
|
||||
}
|
||||
remove_pid_file();
|
||||
} else {
|
||||
println!("Invalid PID file");
|
||||
}
|
||||
} else {
|
||||
println!("Daemon is not running");
|
||||
}
|
||||
}
|
||||
|
||||
DaemonCommand::Restart => {
|
||||
// Stop if running
|
||||
if is_daemon_running() {
|
||||
if let Ok(pid_str) = fs::read_to_string(PID_FILE) {
|
||||
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
||||
println!("Stopping daemon...");
|
||||
unsafe {
|
||||
libc::kill(pid, libc::SIGTERM);
|
||||
}
|
||||
|
||||
// Wait for stop
|
||||
for _ in 0..30 {
|
||||
if !Path::new(&format!("/proc/{}", pid)).exists() {
|
||||
break;
|
||||
}
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start again
|
||||
println!("Starting daemon...");
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
|
||||
match unsafe { fork() } {
|
||||
Ok(ForkResult::Parent { .. }) => {
|
||||
println!("Daemon restarted successfully");
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(ForkResult::Child) => {
|
||||
setsid()?;
|
||||
std::env::set_current_dir("/")?;
|
||||
|
||||
let devnull = fs::File::open("/dev/null")?;
|
||||
nix::unistd::dup2_stdin(devnull.as_fd())?;
|
||||
nix::unistd::dup2_stdout(devnull.as_fd())?;
|
||||
nix::unistd::dup2_stderr(devnull.as_fd())?;
|
||||
|
||||
return daemon_main();
|
||||
}
|
||||
Err(e) => return Err(format!("Fork failed: {}", e).into()),
|
||||
}
|
||||
}
|
||||
|
||||
DaemonCommand::Status => {
|
||||
if is_daemon_running() {
|
||||
if let Ok(pid_str) = fs::read_to_string(PID_FILE) {
|
||||
println!("Daemon is running (PID: {})", pid_str.trim());
|
||||
|
||||
// Show log tail if available
|
||||
if Path::new(LOG_FILE).exists() {
|
||||
println!("\nRecent log entries:");
|
||||
let _ = std::process::Command::new("tail")
|
||||
.args(&["-n", "10", LOG_FILE])
|
||||
.status();
|
||||
}
|
||||
} else {
|
||||
println!("Daemon appears to be running but PID file is invalid");
|
||||
}
|
||||
} else {
|
||||
println!("Daemon is not running");
|
||||
}
|
||||
}
|
||||
|
||||
DaemonCommand::Reload => {
|
||||
if let Ok(pid_str) = fs::read_to_string(PID_FILE) {
|
||||
if let Ok(pid) = pid_str.trim().parse::<i32>() {
|
||||
println!("Reloading daemon configuration...");
|
||||
unsafe {
|
||||
libc::kill(pid, libc::SIGHUP);
|
||||
}
|
||||
println!("Reload signal sent");
|
||||
} else {
|
||||
println!("Invalid PID file");
|
||||
}
|
||||
} else {
|
||||
println!("Daemon is not running");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
use anyhow::{Context, Result};
|
||||
// use libbpf_rs::{MapCore, MapMut, ObjectBuilder, ProgramMut, RingBufferBuilder, UprobeOpts};
|
||||
use aya::{Ebpf, maps::RingBuf, programs::UProbe};
|
||||
use bytes::BytesMut;
|
||||
use std::{fs, io::BufRead, path::PathBuf, time::Instant};
|
||||
use tracing::error;
|
||||
|
||||
#[repr(C)]
|
||||
struct Event {
|
||||
pid: u32,
|
||||
tid: u32,
|
||||
is_send: u32,
|
||||
len: u32,
|
||||
data: [u8; 256],
|
||||
}
|
||||
|
||||
fn find_libc_path_for(pid: i32) -> Result<PathBuf> {
|
||||
let f = fs::File::open(format!("/proc/{pid}/maps"))?;
|
||||
let rdr = std::io::BufReader::new(f);
|
||||
let mut candidates = Vec::new();
|
||||
for line in rdr.lines().flatten() {
|
||||
if line.contains("libc.so") && line.contains("/lib64/") {
|
||||
if let Some(path) = line.split_whitespace().last() {
|
||||
if path.starts_with('/') {
|
||||
candidates.push(PathBuf::from(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
candidates
|
||||
.into_iter()
|
||||
.next()
|
||||
.context("libc.so path not found in maps")
|
||||
}
|
||||
|
||||
// fn attach_sym(prog: &Arc<ProgramMut>, path: &str, sym: &str, pid: i32) -> Result<()> {
|
||||
// let _ = prog.attach_uprobe_with_opts(
|
||||
// pid,
|
||||
// path,
|
||||
// 0,
|
||||
// UprobeOpts {
|
||||
// func_name: Some(sym.to_string()),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// )?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// /// Find the BPF map with the given name, panic if it does not exist.
|
||||
// #[track_caller]
|
||||
// pub fn get_map_mut<'obj>(object: &'obj mut libbpf_rs::Object, name: &str) -> MapMut<'obj> {
|
||||
// object
|
||||
// .maps_mut()
|
||||
// .find(|map| map.name() == name)
|
||||
// .unwrap_or_else(|| panic!("failed to find map `{name}`"))
|
||||
// }
|
||||
|
||||
// /// Find the BPF program with the given name, panic if it does not exist.
|
||||
// #[track_caller]
|
||||
// pub fn get_prog_mut<'obj>(object: &'obj mut libbpf_rs::Object, name: &str) -> ProgramMut<'obj> {
|
||||
// object
|
||||
// .progs_mut()
|
||||
// .find(|map| map.name() == name)
|
||||
// .unwrap_or_else(|| panic!("failed to find program `{name}`"))
|
||||
// }
|
||||
|
||||
// /// Find the BPF program with the given vector of symbol names.
|
||||
// #[track_caller]
|
||||
// pub fn get_multiple_prog_mut<'obj>(
|
||||
// object: &'obj mut libbpf_rs::Object,
|
||||
// names: Vec<String>,
|
||||
// ) -> Arc<HashMap<String, Arc<ProgramMut<'obj>>>> {
|
||||
// let mut result = HashMap::new();
|
||||
|
||||
// for name in names {
|
||||
// let prog = object.progs_mut().find(|map| *map.name() == *name).take();
|
||||
|
||||
// if prog.is_some() {
|
||||
// result.insert(name.clone(), Arc::new(prog.unwrap()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// Arc::new(result)
|
||||
// }
|
||||
|
||||
struct PollFd<T>(T);
|
||||
fn poll_fd<T>(t: T) -> PollFd<T> {
|
||||
PollFd(t)
|
||||
}
|
||||
|
||||
impl<T> PollFd<T> {
|
||||
fn readable(&mut self) -> Guard<'_, T> {
|
||||
Guard(self)
|
||||
}
|
||||
}
|
||||
|
||||
struct Guard<'a, T>(&'a mut PollFd<T>);
|
||||
|
||||
impl<T> Guard<'_, T> {
|
||||
fn inner_mut(&mut self) -> &mut T {
|
||||
let Guard(PollFd(t)) = self;
|
||||
t
|
||||
}
|
||||
|
||||
fn clear_read(&mut self) {}
|
||||
}
|
||||
|
||||
fn load_bpf_bytes() -> anyhow::Result<Vec<u8>> {
|
||||
let bytes = include_bytes!("../../bpf/sendrecv.bpf.o");
|
||||
if bytes.len() >= 4 && &bytes[..4] == b"\x7fELF" {
|
||||
return Ok(bytes.to_vec());
|
||||
}
|
||||
// fallback
|
||||
let p = "/data/local/adbdguard/bpf/sendrecv.bpf.o";
|
||||
let v = std::fs::read(p)?;
|
||||
if v.len() >= 4 && &v[..4] == b"\x7fELF" {
|
||||
return Ok(v);
|
||||
}
|
||||
anyhow::bail!(
|
||||
"BPF object is not ELF: embeded={} disk={}",
|
||||
bytes.len(),
|
||||
v.len()
|
||||
);
|
||||
}
|
||||
|
||||
pub fn run(
|
||||
adbd_pid: i32,
|
||||
rules: crate::rule::RuleSet,
|
||||
action: String,
|
||||
debounce: std::time::Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
// 1) Load object
|
||||
let data = load_bpf_bytes().context("load bpf obj bytes")?; // or read from file
|
||||
let mut skel = Ebpf::load(&data).context("load bpf obj")?;
|
||||
let mut frames: std::collections::HashMap<i32, BytesMut> = Default::default();
|
||||
let mut last_alert: std::collections::HashMap<i32, Instant> = Default::default();
|
||||
|
||||
// attach
|
||||
let libc_path = find_libc_path_for(adbd_pid)?;
|
||||
|
||||
for (sec, sym, alt) in vec![
|
||||
("up_send", "send", "sendto"),
|
||||
("ur_send", "send", "sendto"),
|
||||
("up_recv", "recv", "recvfrom"),
|
||||
("ur_recv", "recv", "recvfrom"),
|
||||
("up_write", "write", "write"),
|
||||
("up_read", "read", "read"),
|
||||
("ur_write", "write", "write"),
|
||||
("ur_read", "read", "read"),
|
||||
] {
|
||||
let _prog: &mut UProbe = skel.program_mut(sec).unwrap().try_into()?;
|
||||
_prog.load()?;
|
||||
if let Err(e) = _prog.attach(Some(sym), 0, libc_path.clone(), Some(adbd_pid)) {
|
||||
error!("retry {sec} with {alt}: {e}");
|
||||
let _prog_retry: &mut UProbe = skel.program_mut(alt).unwrap().try_into()?;
|
||||
_prog_retry.load()?;
|
||||
let _ = _prog_retry.attach(Some(alt), 0, libc_path.clone(), Some(adbd_pid))?;
|
||||
}
|
||||
}
|
||||
|
||||
let _rb = skel.map_mut("rb");
|
||||
if _rb.is_some() {
|
||||
let rb = RingBuf::try_from(_rb.unwrap())?;
|
||||
let mut poll = poll_fd(rb);
|
||||
|
||||
loop {
|
||||
let mut guard = poll.readable();
|
||||
let ringbuf = guard.inner_mut();
|
||||
|
||||
while let Some(item) = ringbuf.next() {
|
||||
if item.len() < std::mem::size_of::<Event>() {
|
||||
break;
|
||||
}
|
||||
|
||||
let evt: &Event = unsafe { &*(item.as_ptr() as *const Event) };
|
||||
|
||||
let key = evt.tid as i32;
|
||||
let entry = frames
|
||||
.entry(key)
|
||||
.or_insert_with(|| BytesMut::with_capacity(8192));
|
||||
entry.extend_from_slice(&evt.data[..evt.len as usize]);
|
||||
|
||||
while let Some(f) = crate::adb::try_parse(entry) {
|
||||
if f.cmd == crate::adb::CMD_OPEN {
|
||||
let service = String::from_utf8_lossy(&f.payload).to_string();
|
||||
if let Some(rule) = rules.match_service(&service) {
|
||||
let last = last_alert
|
||||
.entry(key)
|
||||
.or_insert(Instant::now() - debounce * 2);
|
||||
if last.elapsed() >= debounce {
|
||||
crate::broadcaster::broadcast(&action, &service, rule);
|
||||
*last = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard.clear_read();
|
||||
}
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ use std::{fs, time::Duration};
|
|||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Rule {
|
||||
pub name: String,
|
||||
#[allow(dead_code)]
|
||||
pub when: String,
|
||||
#[serde(default)]
|
||||
pub contains: Vec<String>,
|
||||
|
|
@ -17,7 +16,6 @@ pub struct Rule {
|
|||
pub struct Config {
|
||||
#[serde(default = "default_debounce")]
|
||||
pub debounce_ms: u64,
|
||||
#[allow(dead_code)]
|
||||
#[serde(default = "default_peek")]
|
||||
pub max_payload_peek: usize,
|
||||
pub broadcast_action: String,
|
||||
|
|
|
|||
99
src/main.rs
99
src/main.rs
|
|
@ -4,8 +4,6 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(feature = "ptracev2")]
|
||||
use backend::ptrace_v2;
|
||||
use config::Config;
|
||||
use notify::{RecommendedWatcher, Watcher};
|
||||
use rule::RuleSet;
|
||||
|
|
@ -16,8 +14,8 @@ mod adb;
|
|||
mod backend;
|
||||
mod broadcaster;
|
||||
mod config;
|
||||
mod nr;
|
||||
mod rule;
|
||||
mod traits;
|
||||
|
||||
fn pidof(name: &str) -> Option<i32> {
|
||||
let out = std::process::Command::new("pidof")
|
||||
|
|
@ -42,101 +40,12 @@ fn watch_config(path: PathBuf, cfg: Arc<Mutex<Config>>) -> notify::Result<Recomm
|
|||
w.watch(&saved_path, notify::RecursiveMode::NonRecursive)?;
|
||||
Ok(w)
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "multi_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "ptracev2"))]
|
||||
#[cfg(feature = "ffs_proxy")]
|
||||
{
|
||||
let sub = FmtSubscriber::builder()
|
||||
.without_time()
|
||||
.with_target(false)
|
||||
.log_internal_errors(true)
|
||||
.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();
|
||||
|
||||
#[cfg(feature = "ptrace_parallel")]
|
||||
{
|
||||
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();
|
||||
let peek = 256usize;
|
||||
let adb_tcp_port = 5555u16;
|
||||
|
||||
return backend::ptrace_prl::run_with_tokio_rayon(
|
||||
pid,
|
||||
rules,
|
||||
action,
|
||||
snapshot.debounce(),
|
||||
peek,
|
||||
adb_tcp_port,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ptrace_parallel"))]
|
||||
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:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "uprobes")]
|
||||
{
|
||||
backend::uprobes::run(pid, rules, action, snapshot.debounce())?;
|
||||
}
|
||||
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
backend::proxy::run().await?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "ptracev2")]
|
||||
{
|
||||
let command = ptrace_v2::parse_args();
|
||||
match ptrace_v2::run(command) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
anyhow::bail!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
321
src/nr.rs
321
src/nr.rs
|
|
@ -1,321 +0,0 @@
|
|||
///
|
||||
/// Android Syscall Table (Android 10)
|
||||
///
|
||||
///
|
||||
/// https://android.googlesource.com/platform/bionic/+/refs/heads/android10-release/libc/kernel/uapi/asm-generic/unistd.h
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[repr(u64)]
|
||||
pub enum NR_CODES {
|
||||
__NR_io_setup = 0u64,
|
||||
__NR_io_destroy,
|
||||
__NR_io_submit,
|
||||
__NR_io_cancel,
|
||||
__NR_io_getevents,
|
||||
__NR_setxattr,
|
||||
__NR_lsetxattr,
|
||||
__NR_fsetxattr,
|
||||
__NR_getxattr,
|
||||
__NR_lgetxattr,
|
||||
__NR_fgetxattr,
|
||||
__NR_listxattr,
|
||||
__NR_llistxattr,
|
||||
__NR_flistxattr,
|
||||
__NR_removexattr,
|
||||
__NR_lremovexattr,
|
||||
__NR_fremovexattr,
|
||||
__NR_getcwd,
|
||||
__NR_lookup_dcookie,
|
||||
__NR_eventfd2,
|
||||
__NR_epoll_create1,
|
||||
__NR_epoll_ctl,
|
||||
__NR_epoll_pwait,
|
||||
__NR_dup,
|
||||
__NR_dup3,
|
||||
__NR3264_fcntl,
|
||||
__NR_inotify_init1,
|
||||
__NR_inotify_add_watch,
|
||||
__NR_inotify_rm_watch,
|
||||
__NR_ioctl,
|
||||
__NR_ioprio_set,
|
||||
__NR_ioprio_get,
|
||||
__NR_flock,
|
||||
__NR_mknodat,
|
||||
__NR_mkdirat,
|
||||
__NR_unlinkat,
|
||||
__NR_symlinkat,
|
||||
__NR_linkat,
|
||||
__NR_renameat,
|
||||
__NR_umount2,
|
||||
__NR_mount,
|
||||
__NR_pivot_root,
|
||||
__NR_nfsservctl,
|
||||
__NR3264_statfs,
|
||||
__NR3264_fstatfs,
|
||||
__NR3264_truncate,
|
||||
__NR3264_ftruncate,
|
||||
__NR_fallocate,
|
||||
__NR_faccessat,
|
||||
__NR_chdir,
|
||||
__NR_fchdir,
|
||||
__NR_chroot,
|
||||
__NR_fchmod,
|
||||
__NR_fchmodat,
|
||||
__NR_fchownat,
|
||||
__NR_fchown,
|
||||
__NR_openat,
|
||||
__NR_close,
|
||||
__NR_vhangup,
|
||||
__NR_pipe2,
|
||||
__NR_quotactl,
|
||||
__NR_getdents64 ,
|
||||
__NR3264_lseek,
|
||||
__NR_read,
|
||||
__NR_write,
|
||||
__NR_readv,
|
||||
__NR_writev,
|
||||
__NR_pread64,
|
||||
__NR_pwrite64,
|
||||
__NR_preadv,
|
||||
__NR_pwritev,
|
||||
__NR3264_sendfile,
|
||||
__NR_pselect6,
|
||||
__NR_ppoll,
|
||||
__NR_signalfd4,
|
||||
__NR_vmsplice,
|
||||
__NR_splice,
|
||||
__NR_tee,
|
||||
__NR_readlinkat,
|
||||
__NR3264_fstatat,
|
||||
__NR3264_fstat,
|
||||
__NR_sync,
|
||||
__NR_fsync,
|
||||
__NR_fdatasync,
|
||||
__NR_sync_file_range,
|
||||
__NR_timerfd_create,
|
||||
__NR_timerfd_settime,
|
||||
__NR_timerfd_gettime,
|
||||
__NR_utimensat,
|
||||
__NR_acct,
|
||||
__NR_capget,
|
||||
__NR_capset,
|
||||
__NR_personality,
|
||||
__NR_exit,
|
||||
__NR_exit_group,
|
||||
__NR_waitid,
|
||||
__NR_set_tid_address,
|
||||
__NR_unshare,
|
||||
__NR_futex,
|
||||
__NR_set_robust_list,
|
||||
__NR_get_robust_list,
|
||||
__NR_nanosleep,
|
||||
__NR_getitimer,
|
||||
__NR_setitimer,
|
||||
__NR_kexec_load,
|
||||
__NR_init_module,
|
||||
__NR_delete_module,
|
||||
__NR_timer_create,
|
||||
__NR_timer_gettime,
|
||||
__NR_timer_getoverrun,
|
||||
__NR_timer_settime,
|
||||
__NR_timer_delete,
|
||||
__NR_clock_settime,
|
||||
__NR_clock_gettime,
|
||||
__NR_clock_getres,
|
||||
__NR_clock_nanosleep,
|
||||
__NR_syslog,
|
||||
__NR_ptrace,
|
||||
__NR_sched_setparam,
|
||||
__NR_sched_setscheduler,
|
||||
__NR_sched_getscheduler,
|
||||
__NR_sched_getparam,
|
||||
__NR_sched_setaffinity,
|
||||
__NR_sched_getaffinity,
|
||||
__NR_sched_yield,
|
||||
__NR_sched_get_priority_max,
|
||||
__NR_sched_get_priority_min,
|
||||
__NR_sched_rr_get_interval,
|
||||
__NR_restart_syscall,
|
||||
__NR_kill,
|
||||
__NR_tkill,
|
||||
__NR_tgkill,
|
||||
__NR_sigaltstack,
|
||||
__NR_rt_sigsuspend,
|
||||
__NR_rt_sigaction,
|
||||
__NR_rt_sigprocmask,
|
||||
__NR_rt_sigpending,
|
||||
__NR_rt_sigtimedwait,
|
||||
__NR_rt_sigqueueinfo,
|
||||
__NR_rt_sigreturn,
|
||||
__NR_setpriority,
|
||||
__NR_getpriority,
|
||||
__NR_reboot,
|
||||
__NR_setregid,
|
||||
__NR_setgid,
|
||||
__NR_setreuid,
|
||||
__NR_setuid,
|
||||
__NR_setresuid,
|
||||
__NR_getresuid,
|
||||
__NR_setresgid,
|
||||
__NR_getresgid,
|
||||
__NR_setfsuid,
|
||||
__NR_setfsgid,
|
||||
__NR_times,
|
||||
__NR_setpgid,
|
||||
__NR_getpgid,
|
||||
__NR_getsid,
|
||||
__NR_setsid,
|
||||
__NR_getgroups,
|
||||
__NR_setgroups,
|
||||
__NR_uname,
|
||||
__NR_sethostname,
|
||||
__NR_setdomainname,
|
||||
__NR_getrlimit,
|
||||
__NR_setrlimit,
|
||||
__NR_getrusage,
|
||||
__NR_umask,
|
||||
__NR_prctl,
|
||||
__NR_getcpu,
|
||||
__NR_gettimeofday,
|
||||
__NR_settimeofday,
|
||||
__NR_adjtimex,
|
||||
__NR_getpid,
|
||||
__NR_getppid,
|
||||
__NR_getuid,
|
||||
__NR_geteuid,
|
||||
__NR_getgid,
|
||||
__NR_getegid,
|
||||
__NR_gettid,
|
||||
__NR_sysinfo,
|
||||
__NR_mq_open,
|
||||
__NR_mq_unlink,
|
||||
__NR_mq_timedsend,
|
||||
__NR_mq_timedreceive,
|
||||
__NR_mq_notify,
|
||||
__NR_mq_getsetattr,
|
||||
__NR_msgget,
|
||||
__NR_msgctl,
|
||||
__NR_msgrcv,
|
||||
__NR_msgsnd,
|
||||
__NR_semget,
|
||||
__NR_semctl,
|
||||
__NR_semtimedop,
|
||||
__NR_semop,
|
||||
__NR_shmget,
|
||||
__NR_shmctl,
|
||||
__NR_shmat,
|
||||
__NR_shmdt,
|
||||
__NR_socket,
|
||||
__NR_socketpair,
|
||||
__NR_bind,
|
||||
__NR_listen,
|
||||
__NR_accept,
|
||||
__NR_connect,
|
||||
__NR_getsockname,
|
||||
__NR_getpeername,
|
||||
__NR_sendto,
|
||||
__NR_recvfrom,
|
||||
__NR_setsockopt,
|
||||
__NR_getsockopt,
|
||||
__NR_shutdown,
|
||||
__NR_sendmsg,
|
||||
__NR_recvmsg,
|
||||
__NR_readahead,
|
||||
__NR_brk,
|
||||
__NR_munmap,
|
||||
__NR_mremap,
|
||||
__NR_add_key,
|
||||
__NR_request_key,
|
||||
__NR_keyctl,
|
||||
__NR_clone,
|
||||
__NR_execve,
|
||||
__NR3264_mmap,
|
||||
__NR3264_fadvise64,
|
||||
__NR_swapon,
|
||||
__NR_swapoff,
|
||||
__NR_mprotect,
|
||||
__NR_msync,
|
||||
__NR_mlock,
|
||||
__NR_munlock,
|
||||
__NR_mlockall,
|
||||
__NR_munlockall,
|
||||
__NR_mincore,
|
||||
__NR_madvise,
|
||||
__NR_remap_file_pages,
|
||||
__NR_mbind,
|
||||
__NR_get_mempolicy,
|
||||
__NR_set_mempolicy,
|
||||
__NR_migrate_pages,
|
||||
__NR_move_pages ,
|
||||
__NR_rt_tgsigqueueinfo,
|
||||
__NR_perf_event_open,
|
||||
__NR_accept4,
|
||||
__NR_recvmmsg,
|
||||
__NR_arch_specific_syscall,
|
||||
__NR_wait4 = 260u64,
|
||||
__NR_prlimit64,
|
||||
__NR_fanotify_init,
|
||||
__NR_fanotify_mark,
|
||||
__NR_name_to_handle_at,
|
||||
__NR_open_by_handle_at,
|
||||
__NR_clock_adjtime,
|
||||
__NR_syncfs,
|
||||
__NR_setns,
|
||||
__NR_sendmmsg,
|
||||
__NR_process_vm_readv,
|
||||
__NR_process_vm_writev,
|
||||
__NR_kcmp,
|
||||
__NR_finit_module,
|
||||
__NR_sched_setattr,
|
||||
__NR_sched_getattr,
|
||||
__NR_renameat2,
|
||||
__NR_seccomp,
|
||||
__NR_getrandom,
|
||||
__NR_memfd_create,
|
||||
__NR_bpf,
|
||||
__NR_execveat,
|
||||
__NR_userfaultfd,
|
||||
__NR_membarrier,
|
||||
__NR_mlock2,
|
||||
__NR_copy_file_range,
|
||||
__NR_preadv2,
|
||||
__NR_pwritev2,
|
||||
__NR_pkey_mprotect,
|
||||
__NR_pkey_alloc,
|
||||
__NR_pkey_free,
|
||||
__NR_statx,
|
||||
__NR_io_pgetevents,
|
||||
__NR_rseq,
|
||||
__NR_kexec_file_load,
|
||||
__NR_syscalls,
|
||||
unknown = u64::max_value()
|
||||
}
|
||||
|
||||
impl NR_CODES {
|
||||
|
||||
/// get enum name
|
||||
pub fn name(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
/// get value of enum
|
||||
pub fn value(&self) -> u64 {
|
||||
self.clone() as u64
|
||||
}
|
||||
|
||||
/// get enum from u64
|
||||
///
|
||||
/// value may return `unknown` if more than 0x127 or in range between 0xF4 and 0x104
|
||||
pub fn get(val: u64) -> NR_CODES {
|
||||
|
||||
if (val > 0x127) || (val > 0xF4 && val < 0x104) {
|
||||
return NR_CODES::unknown;
|
||||
}
|
||||
|
||||
let nr_code = unsafe {
|
||||
std::mem::transmute::<_, NR_CODES>(val)
|
||||
};
|
||||
|
||||
return nr_code;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::config::Config;
|
||||
use crate::config::{Config, Rule};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
|
|
|
|||
19
src/traits.rs
Normal file
19
src/traits.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
pub trait EnumerateValue<T> {
|
||||
/// get enum name
|
||||
fn name(&self) -> String
|
||||
where
|
||||
Self: Debug,
|
||||
{
|
||||
format!("{:?}", self)
|
||||
}
|
||||
|
||||
/// get value of enum
|
||||
fn value(&self) -> T
|
||||
where
|
||||
Self: Clone;
|
||||
|
||||
/// get enum from expected type
|
||||
fn get(val: T) -> Self;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue