From 53763e608e658c14fa2591cd42b98a02ad1838bd Mon Sep 17 00:00:00 2001 From: Martin Olivier Date: Mon, 18 Sep 2023 09:17:52 +0200 Subject: [PATCH] feat: version 0.4.2 (#31) * feat: added categories, provides and conflicts to packaging * feat: packaging for both x86_64 and aarch64 * feat: channel filter box now goes red on invalid input * fix: better channel filter input checks * fix: backend error management * feat: logger Signed-off-by: Martin Olivier Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .fpm | 5 +- .github/dependabot.yml | 4 +- .github/workflows/CI.yml | 46 +++++++++------ Cargo.lock | 85 ++++++++++++++++++++++----- Cargo.toml | 10 ++-- README.md | 15 ++++- package/.desktop | 1 + src/backend/app.rs | 29 ++++----- src/backend/capture.rs | 7 +++ src/backend/deauth.rs | 61 +++++++++++++------ src/backend/decrypt.rs | 15 +---- src/backend/interface.rs | 44 +++++++++++--- src/backend/scan.rs | 83 ++++++++++++++++++++++---- src/backend/settings.rs | 5 ++ src/error.rs | 16 ++--- src/frontend/connections/app.rs | 6 +- src/frontend/connections/deauth.rs | 13 ++-- src/frontend/connections/decrypt.rs | 26 ++++---- src/frontend/connections/interface.rs | 7 +-- src/frontend/connections/scan.rs | 69 +++++++++------------- src/frontend/interfaces/app.rs | 16 +++++ src/frontend/interfaces/decrypt.rs | 1 - src/frontend/mod.rs | 2 +- src/frontend/widgets/dialog.rs | 32 ++++------ src/globals.rs | 2 +- src/main.rs | 2 + 26 files changed, 391 insertions(+), 211 deletions(-) diff --git a/.fpm b/.fpm index 9e0733f..ac7a23b 100644 --- a/.fpm +++ b/.fpm @@ -1,11 +1,14 @@ -s dir ./target/release/airgorah ./icons/app_icon.png package README.md LICENSE --name airgorah --license MIT ---version 0.4.1 +--version 0.4.2 --description "A WiFi auditing software that can perform deauth attacks and passwords cracking" --url "https://github.com/martin-olivier/airgorah" --maintainer "Martin Olivier " +--provides airgorah +--conflicts airgorah + --depends bash --depends systemd --depends xfce4-terminal diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7be018e..e509831 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,6 +3,4 @@ updates: - package-ecosystem: "cargo" directory: "/" schedule: - interval: "daily" - reviewers: - - "martin-olivier" \ No newline at end of file + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index def656e..62ab830 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -47,8 +47,18 @@ jobs: run: cargo fmt --all -- --check build: - name: build - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, ubuntu-latest-arm64] + include: + - os: ubuntu-latest + arch: x86_64 + - os: ubuntu-latest-arm64 + arch: aarch64 + + name: build ${{ matrix.arch }} + runs-on: ${{ matrix.os }} needs: linter permissions: write-all @@ -64,6 +74,7 @@ jobs: ruby ruby-dev rubygems + rpm libarchive-tools - name: Install fpm @@ -74,7 +85,7 @@ jobs: - name: Build debian package run: > - fpm -t deb -p airgorah_amd64.deb --architecture amd64 + fpm -t deb -p airgorah_${{ matrix.arch }}.deb --architecture native --depends policykit-1 --depends "libgtk-4-1 (>= 4.6.0)" --depends dbus-x11 @@ -86,13 +97,13 @@ jobs: - name: Save debian artifact uses: actions/upload-artifact@v3 with: - name: airgorah_amd64.deb - path: ./airgorah_amd64.deb + name: airgorah_${{ matrix.arch }}.deb + path: ./airgorah_${{ matrix.arch }}.deb if-no-files-found: error - name: Build redhat package run: > - fpm -t rpm -p airgorah_amd64.rpm --architecture amd64 + fpm -t rpm -p airgorah_${{ matrix.arch }}.rpm --architecture native --depends polkit --depends "gtk4-devel >= 4.6.0" --depends dbus-x11 @@ -102,13 +113,13 @@ jobs: - name: Save redhat artifact uses: actions/upload-artifact@v3 with: - name: airgorah_amd64.rpm - path: ./airgorah_amd64.rpm + name: airgorah_${{ matrix.arch }}.rpm + path: ./airgorah_${{ matrix.arch }}.rpm if-no-files-found: error - name: Build archlinux package run: > - fpm -t pacman -p airgorah_amd64.pkg.tar.zst --architecture amd64 + fpm -t pacman -p airgorah_${{ matrix.arch }}.pkg.tar.zst --architecture native --depends polkit --depends "gtk4 (>= 4.6.0)" --depends dbus @@ -119,24 +130,25 @@ jobs: - name: Save archlinux artifact uses: actions/upload-artifact@v3 with: - name: airgorah_amd64.pkg.tar.zst - path: ./airgorah_amd64.pkg.tar.zst + name: airgorah_${{ matrix.arch }}.pkg.tar.zst + path: ./airgorah_${{ matrix.arch }}.pkg.tar.zst if-no-files-found: error - name: Name packages for release if: github.event_name == 'release' run: | - cp ./airgorah_amd64.deb ./airgorah_${{ github.ref_name }}_amd64.deb - cp ./airgorah_amd64.rpm ./airgorah_${{ github.ref_name }}_amd64.rpm - cp ./airgorah_amd64.pkg.tar.zst ./airgorah_${{ github.ref_name }}_amd64.pkg.tar.zst + echo "RELEASE_VERSION=${{ github.ref_name }}" | sed 's/v//' >> $GITHUB_ENV + cp ./airgorah_${{ matrix.arch }}.deb ./airgorah_$RELEASE_VERSION_${{ matrix.arch }}.deb + cp ./airgorah_${{ matrix.arch }}.rpm ./airgorah_$RELEASE_VERSION_${{ matrix.arch }}.rpm + cp ./airgorah_${{ matrix.arch }}.pkg.tar.zst ./airgorah_$RELEASE_VERSION_${{ matrix.arch }}.pkg.tar.zst - name: Upload packages if: github.event_name == 'release' uses: softprops/action-gh-release@v1 with: files: | - ./airgorah_${{ github.ref_name }}_amd64.deb - ./airgorah_${{ github.ref_name }}_amd64.rpm - ./airgorah_${{ github.ref_name }}_amd64.pkg.tar.zst + ./airgorah_$RELEASE_VERSION_${{ matrix.arch }}.deb + ./airgorah_$RELEASE_VERSION_${{ matrix.arch }}.rpm + ./airgorah_$RELEASE_VERSION_${{ matrix.arch }}.pkg.tar.zst env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 579e542..17bcf8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,18 +19,20 @@ dependencies = [ [[package]] name = "airgorah" -version = "0.4.1" +version = "0.4.2" dependencies = [ "csv", "ctrlc", + "env_logger", "glib", "gtk4", "lazy_static", + "log", "regex", "serde", "serde_json", "sudo", - "toml 0.7.8", + "toml 0.8.0", "ureq", "which", ] @@ -157,6 +159,19 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -368,9 +383,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.18.1" +version = "0.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331156127e8166dd815cf8d2db3a5beb492610c716c03ee6db4f2d07092af0a7" +checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19" dependencies = [ "bitflags", "futures-channel", @@ -550,6 +565,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + [[package]] name = "home" version = "0.5.5" @@ -559,6 +580,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "idna" version = "0.3.0" @@ -589,6 +616,17 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.5" @@ -624,12 +662,9 @@ checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" @@ -946,9 +981,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", @@ -1030,6 +1065,15 @@ dependencies = [ "version-compare", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.38" @@ -1076,14 +1120,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" dependencies = [ "serde", "serde_spanned", "toml_datetime 0.6.3", - "toml_edit 0.19.15", + "toml_edit 0.20.0", ] [[package]] @@ -1114,9 +1158,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.19.15" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" dependencies = [ "indexmap 2.0.0", "serde", @@ -1300,6 +1344,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 5737f69..eb180bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "airgorah" -version = "0.4.1" +version = "0.4.2" edition = "2021" license = "MIT" description = "A WiFi auditing software that can perform deauth attacks and passwords cracking" @@ -14,10 +14,10 @@ exclude = ["/.github", "/package"] [dependencies] gtk4 = { version = "0.7.2", features = ["v4_6"] } -glib = "0.18.1" +glib = "0.18.2" serde = { version = "1.0.188", features = ["derive"] } -serde_json = "1.0.106" -toml = "0.7.8" +serde_json = "1.0.107" +toml = "0.8.0" csv = "1.2.2" sudo = "0.6.0" ctrlc = "3.4.1" @@ -25,3 +25,5 @@ regex = "1.9.5" lazy_static = "1.4.0" ureq = { version = "2.7.1", features = ["json"] } which = "4.4.2" +log = "0.4.20" +env_logger = "0.10.0" diff --git a/README.md b/README.md index 1095fdd..37adebc 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,29 @@

A WiFi auditing software that can perform deauth attacks and passwords cracking

- - version + + version license - cppversion + lang ci

+

+ + crates + + + aur + +

+ ![illustration](.github/assets/illustration.png) `Airgorah` is a WiFi auditing software that can discover the clients connected to an access point, perform deauthentication attacks against specific clients or all the clients connected to it, capture WPA handshakes, and crack the password of the access point. diff --git a/package/.desktop b/package/.desktop index 5c5af83..3664c42 100644 --- a/package/.desktop +++ b/package/.desktop @@ -4,5 +4,6 @@ Comment=A WiFi auditing software that can perform deauth attacks and passwords c Exec=pkexec airgorah Icon=/usr/share/pixmaps/airgorah.png ApplicationId=com.molivier.airgorah +Categories=System;Utility; Type=Application Terminal=false diff --git a/src/backend/app.rs b/src/backend/app.rs index 6673e9b..5fbc6fa 100755 --- a/src/backend/app.rs +++ b/src/backend/app.rs @@ -1,7 +1,6 @@ use super::*; use crate::error::Error; use crate::globals::*; -use crate::types::*; /// Check if the user is root, and if all the dependencies are installed pub fn app_setup() -> Result<(), Error> { @@ -38,23 +37,9 @@ pub fn app_setup() -> Result<(), Error> { /// Stop the scan process, kill all the attack process, and remove all the files created by the app pub fn app_cleanup() { stop_scan_process().ok(); + stop_all_deauth_attacks(); - for attacked_ap in get_attack_pool().iter_mut() { - match &mut attacked_ap.1 .1 { - AttackedClients::All(child) => { - child.kill().ok(); - child.wait().ok(); - } - AttackedClients::Selection(child_list) => { - for (_, child) in child_list { - child.kill().ok(); - child.wait().ok(); - } - } - } - } - - if let Some(iface) = IFACE.lock().unwrap().as_ref() { + if let Some(ref iface) = get_iface() { disable_monitor_mode(iface).ok(); restore_network_manager().ok(); } @@ -74,8 +59,8 @@ pub fn check_required_dependencies(deps: &[&str]) -> Result<(), Error> { for dep in deps { if !has_dependency(dep) { return Err(Error::new(&format!( - "Missing required dependency: \"{}\"\n{}", - dep, "Please install it using your package manager" + "Missing required dependency: \"{}\"", + dep, ))); } } @@ -90,9 +75,15 @@ pub fn check_update(current_version: &str) -> Option { if let Ok(json) = response.into_json::() { if json["tag_name"] != current_version { let new_version = json["tag_name"].as_str().unwrap_or("unknown").to_owned(); + + log::info!("a new version is available: \"{}\"", new_version); + return Some(new_version); } } } + + log::info!("airgorah is up to date"); + None } diff --git a/src/backend/capture.rs b/src/backend/capture.rs index 7f17770..dd85a9d 100755 --- a/src/backend/capture.rs +++ b/src/backend/capture.rs @@ -26,9 +26,13 @@ pub fn update_handshakes() -> Result<(), Error> { } } } + + log::trace!("handshakes updated"); + Ok(()) } +/// Get the access points infos of the handshakes contained in the capture file pub fn get_handshakes(path: &str) -> Result, Error> { let capture_output = Command::new("aircrack-ng").args([path]).output()?; @@ -60,5 +64,8 @@ pub fn get_handshakes(path: &str) -> Result, Error> { /// Save the current capture to a file pub fn save_capture(path: &str) -> Result<(), Error> { std::fs::copy(OLD_SCAN_PATH.to_string() + "-01.cap", path)?; + + log::info!("capture saved to \"{}\"", path); + Ok(()) } diff --git a/src/backend/deauth.rs b/src/backend/deauth.rs index c9f73d9..579c648 100755 --- a/src/backend/deauth.rs +++ b/src/backend/deauth.rs @@ -1,4 +1,3 @@ -use super::*; use crate::error::Error; use crate::globals::*; use crate::types::*; @@ -7,15 +6,11 @@ use std::sync::MutexGuard; /// Launch a deauth attack on a specific AP pub fn launch_deauth_attack( + iface: &str, ap: AP, specific_clients: Option>, software: AttackSoftware, ) -> Result<(), Error> { - let iface = match get_iface() { - Some(res) => res, - None => return Err(Error::new("No interface set")), - }; - let mut attack_pool = get_attack_pool(); let attack_targets = match specific_clients { @@ -25,14 +20,19 @@ pub fn launch_deauth_attack( let (soft, args) = match software { AttackSoftware::Aireplay => ( "aireplay-ng", - vec!["-0", "0", "-D", "-a", &ap.bssid, "-c", &cli, &iface], - ), - AttackSoftware::Mdk4 => ( - "mdk4", - vec![iface.as_str(), "d", "-B", &ap.bssid, "-S", &cli], + vec!["-0", "0", "-D", "-a", &ap.bssid, "-c", &cli, iface], ), + AttackSoftware::Mdk4 => ("mdk4", vec![iface, "d", "-B", &ap.bssid, "-S", &cli]), }; + log::info!( + "[{}] start deauth attack on ({}): {} {}", + ap.bssid, + cli, + soft, + args.join(" ") + ); + cli_attack_targets.push(( cli.to_owned(), Command::new(soft) @@ -45,13 +45,20 @@ pub fn launch_deauth_attack( } None => { let (soft, args) = match software { - AttackSoftware::Aireplay => ( - "aireplay-ng", - vec!["-0", "0", "-D", "-a", &ap.bssid, &iface], - ), - AttackSoftware::Mdk4 => ("mdk4", vec![iface.as_str(), "d", "-B", &ap.bssid]), + AttackSoftware::Aireplay => { + ("aireplay-ng", vec!["-0", "0", "-D", "-a", &ap.bssid, iface]) + } + AttackSoftware::Mdk4 => ("mdk4", vec![iface, "d", "-B", &ap.bssid]), }; + log::info!( + "[{}] start deauth attack on ({}): {} {}", + ap.bssid, + "FF:FF:FF:FF:FF:FF", + soft, + args.join(" ") + ); + AttackedClients::All( Command::new(soft) .args(args) @@ -70,14 +77,22 @@ pub fn launch_deauth_attack( pub fn stop_deauth_attack(ap_bssid: &str) { let mut attack_pool = get_attack_pool(); - if let Some(attack_target) = attack_pool.get_mut(ap_bssid) { - match &mut attack_target.1 { + if let Some((ap, target)) = attack_pool.get_mut(ap_bssid) { + match target { AttackedClients::All(child) => { + log::info!( + "[{}] stop deauth attack on ({})", + ap.bssid, + "FF:FF:FF:FF:FF:FF" + ); + child.kill().ok(); child.wait().ok(); } AttackedClients::Selection(child_list) => { - for (_cli, child) in child_list { + for (cli, child) in child_list { + log::info!("[{}] stop deauth attack on ({})", ap.bssid, cli); + child.kill().ok(); child.wait().ok(); } @@ -87,6 +102,14 @@ pub fn stop_deauth_attack(ap_bssid: &str) { attack_pool.remove(ap_bssid); } +pub fn stop_all_deauth_attacks() { + let attacked_aps: Vec<_> = get_attack_pool().keys().cloned().collect(); + + for bssid in attacked_aps { + stop_deauth_attack(&bssid); + } +} + /// Get the attack pool pub fn get_attack_pool() -> MutexGuard<'static, AttackPool> { ATTACK_POOL.lock().unwrap() diff --git a/src/backend/decrypt.rs b/src/backend/decrypt.rs index dc7e6c5..88a55f2 100755 --- a/src/backend/decrypt.rs +++ b/src/backend/decrypt.rs @@ -1,5 +1,3 @@ -use crate::error::Error; - use std::process::{Command, Stdio}; const CRUNCH_LOWERCASE: &str = "abcdefghijklmnopqrstuvwxyz"; @@ -8,12 +6,7 @@ const CRUNCH_NUMBERS: &str = "0123456789"; const CRUNCH_SYMBOLS: &str = "!#$%/=?{}[]-*:;"; /// Launch a new terminal window to run aircrack-ng to decrypt a handshake with the specified wordlist -pub fn run_decrypt_wordlist_process( - handshake: &str, - bssid: &str, - essid: &str, - wordlist: &str, -) -> Result<(), Error> { +pub fn run_decrypt_wordlist_process(handshake: &str, bssid: &str, essid: &str, wordlist: &str) { let cmd = format!( "aircrack-ng '{}' -b '{}' -w '{}'", handshake, bssid, wordlist @@ -35,8 +28,6 @@ pub fn run_decrypt_wordlist_process( std::thread::spawn(move || { process.output().unwrap(); }); - - Ok(()) } /// Launch a new terminal window to run aircrack-ng to decrypt a handshake using bruteforce @@ -48,7 +39,7 @@ pub fn run_decrypt_bruteforce_process( up: bool, num: bool, sym: bool, -) -> Result<(), Error> { +) { let charset = format!( "{}{}{}{}", match low { @@ -89,6 +80,4 @@ pub fn run_decrypt_bruteforce_process( std::thread::spawn(move || { process.output().unwrap(); }); - - Ok(()) } diff --git a/src/backend/interface.rs b/src/backend/interface.rs index 8129121..d8a6322 100644 --- a/src/backend/interface.rs +++ b/src/backend/interface.rs @@ -23,17 +23,23 @@ pub fn is_5ghz_supported(iface: &str) -> Result { let phy_path = format!("/sys/class/net/{}/phy80211", iface); let phy_link = std::fs::read_link(phy_path)?; - let phy_name = phy_link - .file_name() - .ok_or(Error::new("phy parsing error"))?; - let phy_name_str = phy_name.to_str().ok_or(Error::new("phy parsing error"))?; + + let phy_name = match phy_link.file_name() { + Some(name) => name, + None => return Err(Error::new("phy parsing error")), + }; + + let phy_name_str = match phy_name.to_str() { + Some(name) => name, + None => return Err(Error::new("phy parsing error")), + }; let check_band_cmd = Command::new("iw") .args(["phy", phy_name_str, "info"]) .output()?; if !check_band_cmd.status.success() { - return Err(Error::new("No such interface")); + return Err(Error::new(&format!("{}: No such phy", phy_name_str))); } let check_band_output = String::from_utf8(check_band_cmd.stdout)?; @@ -50,7 +56,7 @@ pub fn is_monitor_mode(iface: &str) -> Result { let check_monitor_cmd = Command::new("iw").args(["dev", iface, "info"]).output()?; if !check_monitor_cmd.status.success() { - return Err(Error::new("No such interface")); + return Err(Error::new(&format!("{}: No such interface", iface))); } let check_monitor_output = String::from_utf8(check_monitor_cmd.stdout)?; @@ -100,6 +106,12 @@ pub fn set_mac_address(iface: &str) -> Result<(), Error> { )); } + log::info!( + "{}: MAC address changed to {}", + iface, + get_settings().mac_address + ); + Ok(()) } @@ -115,13 +127,21 @@ pub fn enable_monitor_mode(iface: &str) -> Result { let enable_monitor_cmd = Command::new("airmon-ng").args(["start", iface]).output()?; if !enable_monitor_cmd.status.success() { - return Err(Error::new("Failed to enable monitor mode")); + return Err(Error::new(&format!( + "Could not enable monitor mode on \"{}\"", + iface + ))); } + log::info!("{}: monitor mode enabled", iface); + match is_monitor_mode(&(iface.to_string() + "mon")) { Ok(res) => match res { true => Ok(iface.to_string() + "mon"), - false => Err(Error::new("Failed to enable monitor mode")), + false => Err(Error::new(&format!( + "Interface \"{}mon\" is in managed mode", + iface + ))), }, Err(_) => { let new_interface_list = get_interfaces()?; @@ -133,7 +153,7 @@ pub fn enable_monitor_mode(iface: &str) -> Result { } Err(Error::new( - "Monitor mode has been enabled but the new interface has not been found", + &format!("Monitor mode has been enabled on \"{}\", but the new interface name could not been found", iface) )) } } @@ -155,6 +175,8 @@ pub fn disable_monitor_mode(iface: &str) -> Result<(), Error> { let disable_monitor_cmd = Command::new("airmon-ng").args(["stop", iface]).output()?; + log::info!("{}: monitor mode disabled", iface); + match disable_monitor_cmd.status.success() { true => Ok(()), false => Err(Error::new( @@ -177,6 +199,8 @@ pub fn set_iface(iface: String) { pub fn kill_network_manager() -> Result<(), Error> { if get_settings().kill_network_manager { Command::new("airmon-ng").args(["check", "kill"]).output()?; + + log::warn!("network manager killed"); } Ok(()) @@ -198,5 +222,7 @@ pub fn restore_network_manager() -> Result<(), Error> { .args(["restart", "wpa-supplicant"]) .output()?; + log::warn!("network manager restored"); + Ok(()) } diff --git a/src/backend/scan.rs b/src/backend/scan.rs index b7c5dcc..357afde 100644 --- a/src/backend/scan.rs +++ b/src/backend/scan.rs @@ -1,4 +1,3 @@ -use regex::Regex; use std::collections::HashMap; use std::path::Path; use std::process::{Command, Stdio}; @@ -71,33 +70,61 @@ pub fn is_scan_process() -> bool { } /// Check if the content of the channel filter is valid -pub fn is_valid_channel_filter(channel_filter: &str) -> bool { - let channel_regex = Regex::new(r"^[1-9]+[0-9]*$").unwrap(); +pub fn is_valid_channel_filter(channel_filter: &str, ghz_2_4_but: bool, ghz_5_but: bool) -> bool { let channel_list: Vec = channel_filter .split_terminator(',') .map(String::from) .collect(); - for chan in channel_list { - if !channel_regex.is_match(&chan) { + let mut channel_buf = vec![]; + + if channel_filter.ends_with(',') { + return false; + } + + for channel_str in channel_list { + let channel = match channel_str.parse::() { + Ok(chan) => chan, + Err(_) => return false, + }; + + if channel < 1 || (15..=35).contains(&channel) || channel > 165 { + return false; + } + + if (1..=14).contains(&channel) && !ghz_2_4_but { return false; } + + if (36..=165).contains(&channel) && !ghz_5_but { + return false; + } + + if channel_buf.contains(&channel) { + return false; + } + + channel_buf.push(channel); } true } /// Set the scan process -pub fn set_scan_process(args: &[&str]) -> Result<(), Error> { - let iface = match get_iface().as_ref() { - Some(res) => res.to_string(), - None => return Err(Error::new("No interface set")), - }; +pub fn set_scan_process( + iface: &str, + ghz_2_4: bool, + ghz_5: bool, + channel_filter: Option, +) -> Result<(), Error> { + if !ghz_2_4 && !ghz_5 { + return Err(Error::new("No band selected")); + } - stop_scan_process().ok(); + stop_scan_process()?; let mut proc_args = vec![ - iface.as_str(), + iface, "-a", "--output-format", "csv,cap", @@ -106,7 +133,28 @@ pub fn set_scan_process(args: &[&str]) -> Result<(), Error> { "--write-interval", "1", ]; - proc_args.append(&mut args.to_vec()); + + let mut band = String::new(); + + if ghz_5 { + band += "a"; + } + + if ghz_2_4 { + band += "bg"; + } + + proc_args.push("--band"); + proc_args.push(&band); + + let channels; + + if let Some(ref filter) = channel_filter { + channels = filter; + + proc_args.push("--channel"); + proc_args.push(channels); + } let child = Command::new("airodump-ng") .args(proc_args) @@ -115,6 +163,13 @@ pub fn set_scan_process(args: &[&str]) -> Result<(), Error> { SCAN_PROC.lock().unwrap().replace(child); + log::info!( + "scan started: 2.4ghz: {}, 5ghz: {}, channel filter: {:?}", + ghz_2_4, + ghz_5, + channel_filter + ); + Ok(()) } @@ -128,6 +183,8 @@ pub fn stop_scan_process() -> Result<(), Error> { SCAN_PROC.lock().unwrap().take(); + log::info!("scan stopped"); + let old_path_exists = Path::new(&(OLD_SCAN_PATH.to_string() + "-01.cap")).exists(); let live_path_exists = Path::new(&(LIVE_SCAN_PATH.to_string() + "-01.cap")).exists(); diff --git a/src/backend/settings.rs b/src/backend/settings.rs index 5a21af8..e71f072 100644 --- a/src/backend/settings.rs +++ b/src/backend/settings.rs @@ -8,6 +8,9 @@ pub fn load_settings() { if Path::new(CONFIG_PATH).exists() { let config = std::fs::read_to_string(CONFIG_PATH).unwrap_or_default(); let settings: Settings = toml::from_str(&config).unwrap_or_default(); + + log::debug!("settings loaded from \"{}\"", CONFIG_PATH); + *SETTINGS.lock().unwrap() = settings; } } @@ -17,6 +20,8 @@ pub fn save_settings(settings: Settings) { if Path::new(CONFIG_PATH).exists() { if let Ok(toml_settings) = toml::to_string(&settings) { std::fs::write(CONFIG_PATH, toml_settings).ok(); + + log::debug!("settings saved into \"{}\"", CONFIG_PATH); } } *SETTINGS.lock().unwrap() = settings; diff --git a/src/error.rs b/src/error.rs index 52f044d..706e81e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,32 +1,34 @@ #[derive(Debug)] pub struct Error { - pub error_message: String, + pub message: String, } impl Error { - pub fn new(error_message: &str) -> Self { + pub fn new(message: &str) -> Self { + log::error!("{}", message.to_lowercase()); + Self { - error_message: error_message.to_string(), + message: message.to_string(), } } } impl std::error::Error for Error { fn description(&self) -> &str { - &self.error_message + &self.message } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.error_message) + write!(f, "{}", self.message) } } impl From for Error { fn from(error: std::io::Error) -> Error { Error { - error_message: error.to_string(), + message: error.to_string(), } } } @@ -34,7 +36,7 @@ impl From for Error { impl From for Error { fn from(error: std::string::FromUtf8Error) -> Error { Error { - error_message: error.to_string(), + message: error.to_string(), } } } diff --git a/src/frontend/connections/app.rs b/src/frontend/connections/app.rs index ea8e85f..d4bed67 100755 --- a/src/frontend/connections/app.rs +++ b/src/frontend/connections/app.rs @@ -342,9 +342,9 @@ fn connect_capture_button(app_data: Rc) { }; let path = gio_file.path().unwrap().to_str().unwrap().to_string(); - backend::save_capture(&path).unwrap_or_else(|e| { - ErrorDialog::spawn(&app_data.app_gui.window, "Save failed", &e.to_string(), false) - }); + if let Err(e) = backend::save_capture(&path) { + return ErrorDialog::spawn(&app_data.app_gui.window, "Save failed", &e.to_string()); + } for (_, ap) in backend::get_aps().iter_mut() { if ap.handshake { diff --git a/src/frontend/connections/deauth.rs b/src/frontend/connections/deauth.rs index 36fae1f..c041752 100755 --- a/src/frontend/connections/deauth.rs +++ b/src/frontend/connections/deauth.rs @@ -79,6 +79,11 @@ fn connect_attack_but(app_data: Rc) { false => None, }; + let iface = match backend::get_iface() { + Some(iface) => iface, + None => return, + }; + let iter = match app_data.app_gui.aps_view.selection().selected() { Some((_, iter)) => iter, None => return, @@ -94,18 +99,16 @@ fn connect_attack_but(app_data: Rc) { if attack_software == AttackSoftware::Mdk4 && !backend::has_dependency("mdk4") { return ErrorDialog::spawn( &app_data.deauth_gui.window, - "Error", + "Missing dependency", "\"mdk4\" is not installed on your system", - false, ); } - if let Err(e) = backend::launch_deauth_attack(backend::get_aps()[&bssid].clone(), params, attack_software) { + if let Err(e) = backend::launch_deauth_attack(&iface, backend::get_aps()[&bssid].clone(), params, attack_software) { return ErrorDialog::spawn( &app_data.deauth_gui.window, - "Error", + "Deauth attack failed", &format!("Could not start deauth process:\n\n{}", e), - false, ); }; diff --git a/src/frontend/connections/decrypt.rs b/src/frontend/connections/decrypt.rs index a106bc4..0e18056 100755 --- a/src/frontend/connections/decrypt.rs +++ b/src/frontend/connections/decrypt.rs @@ -58,7 +58,11 @@ fn connect_handshake_button(app_data: Rc) { let handshakes = backend::get_handshakes(file_path).unwrap_or_default(); if handshakes.is_empty() { - return ErrorDialog::spawn(&app_data.decrypt_gui.window, "Invalid capture", &format!("\"{}\" doesn't contain any valid handshake", file_path), false); + return ErrorDialog::spawn( + &app_data.decrypt_gui.window, + "Invalid capture", + &format!("\"{}\" doesn't contain any valid handshake", file_path) + ); } app_data.decrypt_gui.target_model.clear(); @@ -166,19 +170,17 @@ fn connect_decrypt_button(app_data: Rc) { let stack = app_data.decrypt_gui.stack.visible_child_name().unwrap(); + if stack == "bruteforce" && !backend::has_dependency("crunch") { + let err_msg = "\"crunch\" is not installed on your system, could not generate a wordlist from a charset"; + return ErrorDialog::spawn(&app_data.decrypt_gui.window, "Failed to run decryption", err_msg); + } + if stack == "dictionary" { - if let Err(e) = backend::run_decrypt_wordlist_process(&handshake_entry, &bssid, &essid, &wordlist_entry) { - return ErrorDialog::spawn(&app_data.decrypt_gui.window, "Failed to run decryption", &e.to_string(), false); - } - } else { - if !backend::has_dependency("crunch") { - let err_msg = "\"crunch\" is not installed on your system, could not generate a wordlist from a charset"; - return ErrorDialog::spawn(&app_data.decrypt_gui.window, "Failed to run decryption", err_msg, false); - } - if let Err(e) = backend::run_decrypt_bruteforce_process(&handshake_entry, &bssid, &essid, low, up, num, sym) { - return ErrorDialog::spawn(&app_data.decrypt_gui.window, "Failed to run decryption", &e.to_string(), false); - } + backend::run_decrypt_wordlist_process(&handshake_entry, &bssid, &essid, &wordlist_entry); + } else if stack == "bruteforce" { + backend::run_decrypt_bruteforce_process(&handshake_entry, &bssid, &essid, low, up, num, sym); } + app_data.decrypt_gui.window.close(); })); } diff --git a/src/frontend/connections/interface.rs b/src/frontend/connections/interface.rs index 9eb9965..f1efbc7 100644 --- a/src/frontend/connections/interface.rs +++ b/src/frontend/connections/interface.rs @@ -21,7 +21,6 @@ fn connect_interface_refresh(app_data: Rc) { &app_data.interface_gui.window, "Failed to get interfaces", &e.to_string(), - false, ); } }; @@ -59,7 +58,6 @@ fn connect_interface_select(app_data: Rc) { &app_data.interface_gui.window, "Failed to set MAC address", &e.to_string(), - false, ); } backend::set_iface(res.clone()); @@ -69,13 +67,14 @@ fn connect_interface_select(app_data: Rc) { app_data.app_gui.scan_but.emit_clicked(); } Err(e) => { + backend::restore_network_manager().ok(); + app_data.interface_gui.refresh_but.emit_clicked(); ErrorDialog::spawn( &app_data.interface_gui.window, "Monitor mode failed", - &format!("Could not enable monitor mode on \"{}\":\n{}", iface, e), - false, + &e.message, ); } }; diff --git a/src/frontend/connections/scan.rs b/src/frontend/connections/scan.rs index ed82bf1..19c992f 100644 --- a/src/frontend/connections/scan.rs +++ b/src/frontend/connections/scan.rs @@ -8,8 +8,6 @@ use gtk4::*; use std::rc::Rc; fn run_scan(app_data: &AppData) { - let mut args = vec![]; - let iface = match backend::get_iface() { Some(iface) => iface, None => return app_data.interface_gui.window.show(), @@ -20,11 +18,11 @@ fn run_scan(app_data: &AppData) { &app_data.app_gui.window, "Error", "You need to select at least one frequency band", - false, ); } - let mut bands = "".to_string(); + let mut ghz_2_4 = false; + let mut ghz_5 = false; if app_data.app_gui.ghz_5_but.is_active() { if !backend::is_5ghz_supported(&iface).unwrap() { @@ -32,48 +30,30 @@ fn run_scan(app_data: &AppData) { &app_data.app_gui.window, "Error", "Your network card doesn't support 5GHz", - false, ); return app_data.app_gui.ghz_5_but.set_active(false); } - bands.push('a'); + ghz_5 = true; } if app_data.app_gui.ghz_2_4_but.is_active() { - bands.push_str("bg"); + ghz_2_4 = true; } - args.push("--band"); - args.push(&bands); - let channel_filter = app_data - .app_gui - .channel_filter_entry - .text() - .as_str() - .replace(' ', ""); + let channel_filter = app_data.app_gui.channel_filter_entry.text(); - if !channel_filter.is_empty() { - match backend::is_valid_channel_filter(&channel_filter) { - true => { - args.push("--channel"); - args.push(&channel_filter); - } - false => { - return ErrorDialog::spawn( - &app_data.app_gui.window, - "Error", - "You need to put a valid channel filter", - false, - ); - } - } - } + let channel_filter = match !channel_filter.is_empty() { + true => match backend::is_valid_channel_filter(&channel_filter, ghz_2_4, ghz_5) { + true => Some(channel_filter.to_string()), + false => None, + }, + false => None, + }; - if let Err(e) = backend::set_scan_process(&args) { + if let Err(e) = backend::set_scan_process(&iface, ghz_2_4, ghz_5, channel_filter) { return ErrorDialog::spawn( &app_data.app_gui.window, "Error", &format!("Could not start scan process:\n\n{}", e), - false, ); } @@ -153,7 +133,7 @@ fn connect_save_button(app_data: Rc) { if was_scanning { app_data.app_gui.scan_but.emit_clicked(); } - return ErrorDialog::spawn(&app_data.app_gui.window, "Save failed", &e.to_string(), false); + return ErrorDialog::spawn(&app_data.app_gui.window, "Save failed", &e.to_string()); } for (_, ap) in backend::get_aps().iter_mut() { @@ -175,13 +155,15 @@ fn connect_ghz_2_4_button(app_data: Rc) { .app_gui .ghz_2_4_but .connect_toggled(clone!(@strong app_data => move |this| { + let filter = app_data.app_gui.channel_filter_entry.text(); + app_data.app_gui.channel_filter_entry.set_text(&filter); + if backend::is_scan_process() { if !this.is_active() && !app_data.app_gui.ghz_5_but.is_active() { ErrorDialog::spawn( &app_data.app_gui.window, "Error", "You need to select at least one frequency band", - false, ); return this.set_active(true); } @@ -195,17 +177,19 @@ pub fn connect_ghz_5_button(app_data: Rc) { .app_gui .ghz_5_but .connect_toggled(clone!(@strong app_data => move |this| { + let filter = app_data.app_gui.channel_filter_entry.text(); + app_data.app_gui.channel_filter_entry.set_text(&filter); + let iface = match backend::get_iface() { Some(iface) => iface, None => return, }; - if !backend::is_5ghz_supported(&iface).unwrap() && this.is_active() { + if !backend::is_5ghz_supported(&iface).unwrap_or(false) && this.is_active() { ErrorDialog::spawn( &app_data.app_gui.window, "Error", "Your network card doesn't support 5GHz", - false, ); return this.set_active(false); } @@ -216,7 +200,6 @@ pub fn connect_ghz_5_button(app_data: Rc) { &app_data.app_gui.window, "Error", "You need to select at least one frequency band", - false, ); return this.set_active(true); } @@ -230,7 +213,6 @@ fn connect_channel_entry(app_data: Rc) { clone!(@strong app_data => move |this| { let channel_filter = this .text() - .replace(' ', "") .chars() .filter(|c| c.is_numeric() || *c == ',') .collect::(); @@ -239,8 +221,13 @@ fn connect_channel_entry(app_data: Rc) { return this.set_text(&channel_filter); } - if !channel_filter.is_empty() && !backend::is_valid_channel_filter(&channel_filter) { - return; + let ghz_2_4_but = app_data.app_gui.ghz_2_4_but.is_active(); + let ghz_5_but = app_data.app_gui.ghz_5_but.is_active(); + + if !channel_filter.is_empty() && !backend::is_valid_channel_filter(&channel_filter, ghz_2_4_but, ghz_5_but) { + this.style_context().add_class("error"); + } else { + this.style_context().remove_class("error"); } if backend::is_scan_process() { diff --git a/src/frontend/interfaces/app.rs b/src/frontend/interfaces/app.rs index f2e5904..2991cc1 100755 --- a/src/frontend/interfaces/app.rs +++ b/src/frontend/interfaces/app.rs @@ -303,6 +303,22 @@ impl AppGui { .margin_bottom(4) .build(); + let css_provider = CssProvider::new(); + css_provider.load_from_data( + std::str::from_utf8( + b" + .error { + color: red; + border-color: red; + } + ", + ) + .unwrap(), + ); + + let style_context = channel_filter_entry.style_context(); + style_context.add_provider(&css_provider, gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION); + let channel_frame = Frame::new(Some("Channel")); channel_frame.set_child(Some(&channel_filter_entry)); diff --git a/src/frontend/interfaces/decrypt.rs b/src/frontend/interfaces/decrypt.rs index c119349..b1cac74 100644 --- a/src/frontend/interfaces/decrypt.rs +++ b/src/frontend/interfaces/decrypt.rs @@ -200,7 +200,6 @@ impl DecryptGui { &self.window, "Invalid capture", &format!("\"{}\" doesn't contain any valid handshake", path), - false, ); } diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index 5ade316..51845b6 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -17,7 +17,7 @@ pub fn build_ui(app: &Application) { gui_data.app_gui.window.show(); if let Err(e) = backend::app_setup() { - return ErrorDialog::spawn(&gui_data.app_gui.window, "Error", &e.to_string(), true); + return PanicDialog::spawn(&gui_data.app_gui.window, &e.to_string()); } gui_data.interface_gui.window.show(); diff --git a/src/frontend/widgets/dialog.rs b/src/frontend/widgets/dialog.rs index bf374af..a929628 100644 --- a/src/frontend/widgets/dialog.rs +++ b/src/frontend/widgets/dialog.rs @@ -1,12 +1,10 @@ -#![allow(unused)] - use gtk4::prelude::*; use gtk4::*; pub struct ErrorDialog; impl ErrorDialog { - pub fn spawn(parent: &impl IsA, title: &str, content: &str, terminate: bool) { + pub fn spawn(parent: &impl IsA, title: &str, content: &str) { let dialog = MessageDialog::builder() .text(title) .secondary_text(content) @@ -19,35 +17,31 @@ impl ErrorDialog { dialog.connect_response(move |this, _| { this.close(); - if terminate { - std::process::exit(1); - } - }); - dialog.connect_close(move |_| { - if terminate { - std::process::exit(1); - } }); dialog.show(); } } -pub struct InfoDialog; +pub struct PanicDialog; -impl InfoDialog { - pub fn spawn(parent: &impl IsA, title: &str, content: &str) { +impl PanicDialog { + pub fn spawn(parent: &impl IsA, message: &str) { let dialog = MessageDialog::builder() - .text(title) - .secondary_text(content) + .text("Error") + .secondary_text(message) .decorated(true) - .message_type(MessageType::Info) - .buttons(ButtonsType::Ok) + .message_type(MessageType::Error) + .buttons(ButtonsType::Close) .modal(true) .transient_for(parent) .build(); - dialog.connect_response(|this, _| { + dialog.connect_response(move |this, _| { this.close(); + std::process::exit(1); + }); + dialog.connect_close(move |_| { + std::process::exit(1); }); dialog.show(); } diff --git a/src/globals.rs b/src/globals.rs index 14bfafa..a3af350 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -7,7 +7,7 @@ use std::sync::Mutex; use std::thread::JoinHandle; pub static APP_ID: &str = "com.molivier.airgorah"; -pub static VERSION: &str = "v0.4.1"; +pub static VERSION: &str = "v0.4.2"; pub static LIVE_SCAN_PATH: &str = "/tmp/airgorah_live_scan"; pub static OLD_SCAN_PATH: &str = "/tmp/airgorah_old_scan"; diff --git a/src/main.rs b/src/main.rs index 4a20716..2f0b59a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,8 @@ mod globals; mod types; fn main() { + env_logger::init(); + let application = Application::builder() .application_id(globals::APP_ID) .build();