Compare commits

..

1 commit

Author SHA1 Message Date
Pakin
0d544cf6c3 add feature proxy 2025-09-02 12:47:37 +07:00
16 changed files with 646 additions and 153588 deletions

551
Cargo.lock generated
View file

@ -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"

View file

@ -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 = []

View file

@ -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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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
View 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(())
}

View file

@ -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(&regs_e);
let (fd, buf_ptr, count) = if is_send_syscall(no) {
(
arg0(&regs_e) as i32,
arg1(&regs_e) as usize,
arg2(&regs_e) as usize,
)
} else if is_recv_syscall(no) {
(
arg0(&regs_e) as i32,
arg0(&regs_e) as usize,
arg1(&regs_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(&regs_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(&regs);
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(&regs) as i32;
let buf_ptr = if is_out {
arg1(&regs)
} else {
arg0(&regs)
};
let count = if is_out {
arg2(&regs)
} else {
arg1(&regs)
} 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();
}
}
}
}
}
}
}

View file

@ -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(&regs_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(&regs_e) as usize, arg1(&regs_e) as usize)
} else {
(arg1(&regs_e) as usize, arg2(&regs_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(&regs_x);
if ret > 0 && (is_recv(no) || is_send(no)) {
let (fd, buf_ptr, count) = (
arg0(&regs_e) as i32,
arg1(&regs_e) as usize,
arg2(&regs_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(())
}

View file

@ -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(())
}

View file

@ -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(());
}
}

View file

@ -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,

View file

@ -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
View file

@ -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;
}
}

View file

@ -1,4 +1,4 @@
use crate::config::Config;
use crate::config::{Config, Rule};
use regex::Regex;

19
src/traits.rs Normal file
View 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;
}