├── wof.sh ├── .gitignore ├── requirements.txt ├── ascii ├── small.txt └── normal.txt ├── LICENSE.txt ├── utils ├── wof_cache.py ├── wof_blechat.py ├── wof_install.py ├── wof_display.py ├── bluetooth_utils.py └── wof_library.py ├── WallofFlippers.py └── README.md /wof.sh: -------------------------------------------------------------------------------- 1 | source .venv/bin/activate 2 | python3 WallofFlippers.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | __pycache__ 3 | db/Flipper.json 4 | db/Backup.json -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bluepy; sys_platform == "linux" 2 | bleak; sys_platform != "linux" -------------------------------------------------------------------------------- /ascii/small.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | YAao, Wall of Flippers 4 | Y8888b, v3.2.2 5 | ,oA8888888b, 6 | ,aaad8888888888888888bo, 7 | ,d888888888888888888888888888b, 8 | ,888888888888888888888888888888888b, 9 | d8888888888888888888888888888888888888, 10 | d888888888888888888888888888888888888888b 11 | d888888P' `Y888888888 \, 12 | 88888P' Ybaaaa888888 0 l 13 | a8888' `Y8888P' `V888888 14 | d8888888a `Y8888 15 | AY/'' `\Y8b . ``Y8b 16 | Y' `YP . 17 | . 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Kiyomi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ascii/normal.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | YAao, 4 | Y8888b, Created By: Kiyomi & Jbohack 5 | ,oA8888888b, Kiyomi: https://ko-fi.com/k3yomi 6 | ,aaad8888888888888888bo, Jbohack: https://ko-fi.com/jbohack 7 | ,d888888888888888888888888888b, Github: github.com/K3YOMI/Wall-of-Flippers 8 | ,888888888888888888888888888888888b, 9 | d8888888888888888888888888888888888888, "[RANDOM_QUOTE]" 10 | d888888888888888888888888888888888888888b . 11 | d888888P' `Y888888888 \, . 12 | 88888P' Ybaaaa888888 0 l . 13 | a8888' `Y8888P' `V888888 14 | d8888888a `Y8888 15 | AY/'' `\Y8b ``Y8b 16 | Y' `YP 17 | 18 | _ __ ____ ____ _________ 19 | | | / /___ _/ / / ____ / __/ / ____/ (_)___ ____ ___ __________ 20 | | | /| / / __ `/ / / / __ \/ /_ / /_ / / / __ \/ __ \/ _ \/ ___/ ___/ 21 | | |/ |/ / /_/ / / / / /_/ / __/ / __/ / / / /_/ / /_/ / __/ / (__ ) 22 | |__/|__/\__,_/_/_/ \____/_/ /_/ /_/_/ .___/ .___/\___/_/ /____/ 23 | /_/ /_/ v3.2.2 -------------------------------------------------------------------------------- /utils/wof_cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # YAao, 4 | # Y8888b, Created By: Kiyomi & Jbohack 5 | # ,oA8888888b, Kiyomi: https://ko-fi.com/k3yomi 6 | # ,aaad8888888888888888bo, Jbohack: https://ko-fi.com/jbohack 7 | # ,d888888888888888888888888888b, 8 | # ,888888888888888888888888888888888b, 9 | # d8888888888888888888888888888888888888, 10 | # d888888888888888888888888888888888888888b 11 | # d888888P' `Y88888888Ꙩ \, 12 | # 88888P' Ybaaaa888888 Ꙩ l 13 | # a8888' `Y8888P' `V888888 14 | # d8888888a `Y8888 15 | # AY/'' `\Y8b ``Y8b 16 | # Y' `YP ~~ 17 | # _ __ ____ ____ _________ 18 | # | | / /___ _/ / / ____ / __/ / ____/ (_)___ ____ ___ __________ 19 | # | | /| / / __ `/ / / / __ \/ /_ / /_ / / / __ \/ __ \/ _ \/ ___/ ___/ 20 | # | |/ |/ / /_/ / / / / /_/ / __/ / __/ / / / /_/ / /_/ / __/ / (__ ) 21 | # |__/|__/\__,_/_/_/ \____/_/ /_/ /_/_/ .___/ .___/\___/_/ /____/ 22 | # /_/ /_/ 23 | import random 24 | import time 25 | 26 | wof_data = { 27 | # Generic Cache (ignore unless you want persistent cache data, refer to -h for more info) 28 | "bool_isScanning": False, 29 | "system_type": None, 30 | "found_flippers": [], 31 | "base_flippers": [], 32 | "live_flippers": [], 33 | "display_live": [], 34 | "display_offline": [], 35 | "cachedMessages": [], 36 | "narrow_mode": False, 37 | "badge_mode": False, 38 | "toggle_adveriser": False, 39 | "forbidden_packets_found": [], 40 | "all_packets_found": [], 41 | "nearbyWof": [], 42 | 43 | # Display Settings and Price Calculation 44 | "narrow_mode_limit": 100, # Minimum number of columns for narrow mode to kick in 45 | "max_online": 15, # Max amount of online flippers to display on the screen 46 | "max_offline": 15, # Max amount of offline flippers to display on the screen 47 | "flipper_volume_price": 169, # Flipper Zero Price 48 | 49 | # Ratelimiting for flippers (Prevent Unauthorized Spammers) 50 | "max_flippers_ratelimited": 3, # Max amount of flippers to be displayed in x seconds 51 | "ratelimit_seconds": 5, # Amount of seconds to ratelimit flippers 52 | "last_ratelimit": time.time(), # Last time ratelimited 53 | "is_ratelimited": False, # Ratelimiting flag 54 | 55 | # Advertising Data (Broadcast to others you're using Wall of Flippers) 56 | "wof_advertiser": (0x1e, 0xff, 0x2c, 0x22, 0x22, 0x22, 0x22, 0x22), 57 | "wof_advertiserName": f"WoF-{random.randint(1000, 9999)}", 58 | "wof_advertiserRaw": "2c2222222222", 59 | 60 | # BLE Chat Settings (Modify if you want to change the BLE Chat Advertiser) 61 | "wof_blechatAdvertiser": (0x1e, 0xff, 0x2c, 0x22, 0x22, 0x24, 0x24, 0x24), 62 | "wof_bleAdvertiserRaw": "2c2222242424", 63 | "wof_displayName": "WoF-Guest", 64 | 65 | # BLE Service UIDs to detect flippers 66 | "flipper_types": { 67 | "00003081-0000-1000-8000-00805f9b34fb": "B", # Black 68 | "00003082-0000-1000-8000-00805f9b34fb": "W", # White 69 | "00003083-0000-1000-8000-00805f9b34fb": "T", # Transparent 70 | }, 71 | 72 | # BLE Spamming Detections and Settings 73 | "forbidden_packets": [ # Not complete and feel free to add more ("_" = Random Value) 74 | {"PCK": "00001812-0000-1000-8000-00805f9b34fb", "TYPE": "BLE_HUMAN_INTERFACE_DEVICE"}, 75 | {"PCK": "4c000719010_2055_______________", "TYPE": "BLE_APPLE_DEVICE_POPUP_CLOSE"}, 76 | {"PCK": "4c000f05c00____________________", "TYPE": "BLE_APPLE_ACTION_MODAL_LONG"}, 77 | {"PCK": "4c00071907_____________________", "TYPE": "BLE_APPLE_DEVICE_CONNECT"}, 78 | {"PCK": "4c0004042a0000000f05c1__604c950", "TYPE": "BLE_APPLE_DEVICE_SETUP"}, 79 | {"PCK": "2cfe___________________________", "TYPE": "BLE_ANDROID_DEVICE_CONNECT"}, 80 | {"PCK": "750042098102141503210109____01_", "TYPE": "BLE_SAMSUNG_BUDS_POPUP_LONG"}, 81 | {"PCK": "7500010002000101ff000043_______", "TYPE": "BLE_SAMSUNG_WATCH_PAIR_LONG"}, 82 | {"PCK": "0600030080_____________________", "TYPE": "BLE_WINDOWS_SWIFT_PAIR_SHORT"}, 83 | {"PCK": "ff006db643ce97fe427c___________", "TYPE": "BLE_LOVE_TOYS_SHORT_DISTANCE"}, 84 | ], 85 | "max_ble_packets": 10, # Max amount of BLE packets to display on the screen 86 | "min_byte_length": 3, # Minimum amount of bytes for a packet to be considered valid 87 | "max_byte_length": 450, # Maximum amount of bytes to be considred suspicious 88 | "ble_threshold": 25, # Amount of forbidden packets to be csonsidered a BLE attack 89 | 90 | # Selections and Quotes + Ascii Art 91 | "dolphin_thinking": [ # Random quotes for the dolphin to say 92 | "Let's hunt some flippers", 93 | "Ya'll like war driving flippers?", 94 | "Skid detector 9000", 95 | "Nya- Who?", # Starflight was totally not here 96 | "I'm a flipper, you're a flipper, we're all flippers!", 97 | "Flipper Zero : Advanced Warefare", 98 | "Don't be a skid!!!!!!", 99 | "Only 3 commits lol", 100 | "https://discord.gg/TuQ9mSm5Y6 (Hack Nexus)", 101 | "Hack the planet!", 102 | "Now headless with commands, WallofFlippers.py -h", 103 | "Please do not put your flipper in wet damp areas." 104 | ], 105 | "init_directory_options": [ # Main Menu Options 106 | {"option": "1", "action": "Wall of Flippers", "description": "Wall of Flippers (Default)", "return": "wall_of_flippers"}, 107 | {"option": "2", "action": "BLE Chat", "description": "Chat with others using BLE", "return": "wall_of_talking"}, 108 | {"option": "3", "action": "Auto-Install", "description": "Install dependencies for Wall of Flippers (Windows / (APT) Debian Linux)", "return": "install_dependencies"}, 109 | {"option": "4", "action": "Exit", "description": "....", "return": "exit"}, 110 | ], 111 | "ascii_normal": open('./ascii/normal.txt', 'r', encoding="utf-8").read().encode("ascii", "ignore").decode("ascii"), 112 | "ascii_small": open('./ascii/small.txt', 'r', encoding="utf-8").read().encode("ascii", "ignore").decode("ascii"), 113 | } 114 | -------------------------------------------------------------------------------- /utils/wof_blechat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # YAao, 4 | # Y8888b, Created By: Kiyomi & Jbohack 5 | # ,oA8888888b, Kiyomi: https://ko-fi.com/k3yomi 6 | # ,aaad8888888888888888bo, Jbohack: https://ko-fi.com/jbohack 7 | # ,d888888888888888888888888888b, 8 | # ,888888888888888888888888888888888b, 9 | # d8888888888888888888888888888888888888, 10 | # d888888888888888888888888888888888888888b 11 | # d888888P' `Y88888888Ꙩ \, 12 | # 88888P' Ybaaaa888888 Ꙩ l 13 | # a8888' `Y8888P' `V888888 14 | # d8888888a `Y8888 15 | # AY/'' `\Y8b ``Y8b 16 | # Y' `YP ~~ 17 | # _ __ ____ ____ _________ 18 | # | | / /___ _/ / / ____ / __/ / ____/ (_)___ ____ ___ __________ 19 | # | | /| / / __ `/ / / / __ \/ /_ / /_ / / / __ \/ __ \/ _ \/ ___/ ___/ 20 | # | |/ |/ / /_/ / / / / /_/ / __/ / __/ / / / /_/ / /_/ / __/ / (__ ) 21 | # |__/|__/\__,_/_/_/ \____/_/ /_/ /_/_/ .___/ .___/\___/_/ /____/ 22 | # /_/ /_/ 23 | 24 | # Standard library Imports 25 | import platform 26 | import sys 27 | import time 28 | import asyncio 29 | import threading 30 | 31 | 32 | # Wall of Flippers "library" for important functions and classes :3 33 | import utils.wof_cache as cache # Wall of Flippers "cache" for important configurations and data :3 34 | import utils.wof_library as library # Wall of Flippers "library" for important functions and classes :3 35 | 36 | def chect2Limit(string:str, limit:int): 37 | if len(string) > limit: 38 | print(f"[!] Wall of Flippers >> {string} is too long! Please enter a name with {limit} characters or less.") 39 | return False 40 | return True 41 | 42 | def send_traffic(sock:int, start:object, stop:object): 43 | try: 44 | displayName = cache.wof_data['wof_displayName'] + "::" # :: = Splitter 45 | totalCharsLeft = 31 - len(displayName) - len(cache.wof_data['wof_bleAdvertiserRaw']) 46 | global customMessage 47 | library.print_ascii_art("You are now broadcasting your messages!") 48 | for i in cache.wof_data['cachedMessages'][:20]: 49 | readable_date = time.strftime('%H:%M:%S', time.localtime(i['time'])) 50 | print(f"[+] {readable_date} {i['displayName']} >> {i['message']}") 51 | customMessage = input(f"[?] Wall of Flippers >> (MAX: {totalCharsLeft} chars) (Empty = Refresh) >> ") 52 | if (customMessage == ""): send_traffic(sock, start, stop) 53 | isStringAllowed = chect2Limit(customMessage, totalCharsLeft) 54 | if (not isStringAllowed): library.print_ascii_art(f"Message is too long! Please enter a message with less than {totalCharsLeft} characters."); send_traffic(sock, start, stop) 55 | cache.wof_data['cachedMessages'].append({"displayName": cache.wof_data['wof_displayName'], "message": customMessage, "time": int(time.time())}) 56 | for i in range(0, 10): 57 | advertisementData = cache.wof_data['wof_blechatAdvertiser'] 58 | advertisementData = list(advertisementData) 59 | advertisementData += list(bytes.fromhex(displayName.encode().hex())) 60 | advertisementData += list(bytes.fromhex(customMessage.encode().hex())) 61 | advertisementData = tuple(advertisementData) 62 | advertisementData += (0x00,) * (31 - len(advertisementData)) 63 | to_hex = lambda data: ''.join(f"{i:02x}" for i in advertisementData) 64 | start(sock, adv_type=0x03, data=advertisementData) 65 | time.sleep(0.1) 66 | stop(sock) 67 | send_traffic(sock, start, stop) 68 | except KeyboardInterrupt: 69 | library.print_ascii_art("Thank you for using Wall of Flippers... Goodbye!") 70 | print("\n[!] Wall of Flippers >> Exiting...") 71 | stop(sock) 72 | sys.exit() 73 | 74 | 75 | def sort_traffic(ble_packets:list): 76 | for advertisement in ble_packets: 77 | advertisement_packets = advertisement['PCK'] 78 | for advertisement_packet in advertisement_packets: 79 | if str(advertisement_packet).startswith(cache.wof_data['wof_bleAdvertiserRaw']): 80 | decodedMessage = bytes.fromhex(advertisement_packet.replace(cache.wof_data['wof_bleAdvertiserRaw'], "")).decode('utf-8').replace("\x00", "") 81 | decodedDisplayName = decodedMessage.split("::")[0] 82 | decodedDisplayMessage = decodedMessage.split("::")[1] 83 | # check for duplicates 84 | if any(i['message'] == decodedDisplayMessage for i in cache.wof_data['cachedMessages']) and any(i['displayName'] == decodedDisplayName for i in cache.wof_data['cachedMessages']): 85 | continue 86 | cache.wof_data['cachedMessages'].append({"displayName": decodedDisplayName, "message": decodedDisplayMessage, "time": int(time.time())}) 87 | async def read_traffic(sock:int, Scanner:object): 88 | cache.wof_data['bool_isScanning'] = True 89 | ble_packets = [] 90 | scanner = Scanner(sock) # Thank you Talking Sasquach for testing this! 91 | devices = scanner.scan(5) # Scan the area for 5 seconds.... 92 | if devices: 93 | for device in devices: 94 | scan_list = device.getScanData() 95 | device_packets = [] 96 | device_formatted = [] 97 | for scan_list_item in scan_list: 98 | device_formatted.append({"ADTYPE": scan_list_item[0], "Description": scan_list_item[1], "Value": scan_list_item[2]}) 99 | for i_data in device_formatted: 100 | device_packets.append(i_data['Value']) 101 | ble_packets.append({"PCK": device_packets}) 102 | sort_traffic(ble_packets) 103 | cache.wof_data['bool_isScanning'] = False 104 | 105 | 106 | def init(): 107 | try: 108 | from utils.bluetooth_utils import toggle_device, start_le_advertising, stop_le_advertising 109 | import bluetooth._bluetooth as bluez 110 | from bluepy.btle import Scanner 111 | except ImportError as e: 112 | library.print_ascii_art("Error: Failed to import dependencies") 113 | print(f"[!] Wall of Flippers >> Failed to import dependencies >> {e}") 114 | sys.exit() 115 | except KeyboardInterrupt: 116 | library.print_ascii_art("Thank you for using Wall of Flippers... Goodbye!") 117 | print("\n[!] Wall of Flippers >> Exiting...") 118 | sys.exit() 119 | DEVIC_HCI = library.adapter2Selection() 120 | sock = bluez.hci_open_dev(int(DEVIC_HCI)) 121 | toggle_device(int(DEVIC_HCI), True) 122 | displayNameSelection = input("[?] Wall of Flippers >> Please enter your display name (MAX: 6 chars) >> ") 123 | isStringAllowed = chect2Limit(displayNameSelection, 6) 124 | if (not isStringAllowed): sys.exit() 125 | cache.wof_data['wof_displayName'] = displayNameSelection 126 | try: 127 | threading.Thread(target=send_traffic, args=(sock, start_le_advertising, stop_le_advertising)).start() 128 | while True: 129 | time.sleep(0.1) 130 | if not cache.wof_data['bool_isScanning']: 131 | asyncio.run(read_traffic(DEVIC_HCI, Scanner)) 132 | except KeyboardInterrupt: 133 | library.print_ascii_art("Thank you for using Wall of Flippers... Goodbye!") 134 | print("\n[!] Wall of Flippers >> Exiting...") 135 | stop_le_advertising(sock) 136 | sys.exit() 137 | -------------------------------------------------------------------------------- /utils/wof_install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # YAao, 4 | # Y8888b, Created By: Kiyomi & Jbohack 5 | # ,oA8888888b, Kiyomi: https://ko-fi.com/k3yomi 6 | # ,aaad8888888888888888bo, Jbohack: https://ko-fi.com/jbohack 7 | # ,d888888888888888888888888888b, 8 | # ,888888888888888888888888888888888b, 9 | # d8888888888888888888888888888888888888, 10 | # d888888888888888888888888888888888888888b 11 | # d888888P' `Y88888888Ꙩ \, 12 | # 88888P' Ybaaaa888888 Ꙩ l 13 | # a8888' `Y8888P' `V888888 14 | # d8888888a `Y8888 15 | # AY/'' `\Y8b ``Y8b 16 | # Y' `YP ~~ 17 | # _ __ ____ ____ _________ 18 | # | | / /___ _/ / / ____ / __/ / ____/ (_)___ ____ ___ __________ 19 | # | | /| / / __ `/ / / / __ \/ /_ / /_ / / / __ \/ __ \/ _ \/ ___/ ___/ 20 | # | |/ |/ / /_/ / / / / /_/ / __/ / __/ / / / /_/ / /_/ / __/ / (__ ) 21 | # |__/|__/\__,_/_/_/ \____/_/ /_/ /_/_/ .___/ .___/\___/_/ /____/ 22 | # /_/ /_/ 23 | 24 | # Standard library Imports 25 | import os 26 | import sys 27 | import json 28 | 29 | # Wall of Flippers "library" for important functions and classes :3 30 | import utils.wof_library as library # Wall of Flippers "library" for important functions and classes :3 31 | 32 | 33 | def init(): 34 | try: # while there is no KeyboardInterrupt 35 | library.print_ascii_art("Welcome to the easy install process! Please read carefully.") 36 | 37 | linux_cmd = ["python3 -m pip install distro"] 38 | arch_cmd = ["python3 -m pip install git+https://github.com/pybluez/pybluez.git#egg=pybluez", "python3 -m pip install bluepy"] 39 | debian_dependencies_cmd = ['sudo apt-get install libglib2.0-dev', 'python3 -m pip install bluepy', 'python3 -m pip install git+https://github.com/pybluez/pybluez.git#egg=pybluez'] 40 | fedora_dependencies_cmd = ['sudo dnf install glib2-devel', 'python3 -m pip install bluepy', 'python3 -m pip install git+https://github.com/pybluez/pybluez.git#egg=pybluez'] 41 | windows_dependencies_cmd = ['pip install bleak'] 42 | system_type = os.name 43 | 44 | # Windows Auto Install 45 | if system_type == "nt": 46 | library.print_ascii_art("Hmm, I've detected that you are running under Windows!") 47 | print(f"[!] Wall of Flippers >> Would it be okay if we ran these commands on your system?\n{json.dumps(windows_dependencies_cmd, indent=4)}") 48 | 49 | user_input_ok = input("[?] Wall of Flippers (Y/N) >> ") 50 | if user_input_ok.lower() == "y": 51 | print("[!] Wall of Flippers >> What pip version do you use >> (pip/pip3)") 52 | user_input_pip = input("[?] Wall of Flippers (pip/pip3) >> ") 53 | windows_dependencies_cmd = [cmd.replace("pip", user_input_pip) for cmd in windows_dependencies_cmd] 54 | print("[!] Wall of Flippers >> Installing dependencies...") 55 | for cmd in windows_dependencies_cmd: 56 | os.system(cmd) 57 | library.print_ascii_art("We have successfully installed the dependencies!") # todo: add a check to see if the dependencies were really installed successfully 58 | print("[!] Wall of Flippers >> Dependencies installed successfully!") 59 | 60 | # Linux Auto Install 61 | elif system_type == "posix": 62 | library.print_ascii_art("Hmm, I've detected that you are running under linux!") 63 | linux_distro = [ 64 | {"name": "debian", "rolling": ["debian", "ubuntu", "kali", "raspbian"]}, 65 | {"name": "fedora", "rolling": ["fedora"]}, 66 | {"name": "arch", "rolling": ["arch"]} 67 | ] 68 | def get_like_distro(): 69 | os_file = open("/etc/os-release", "r") 70 | os_data = os_file.read() 71 | os_file.close() 72 | os_data = os_data.split("\n") 73 | os_data = [data.split("=") for data in os_data] 74 | os_data = {data[0]: data[1].replace('"', "") for data in os_data if len(data) == 2} 75 | for distro in linux_distro: 76 | name_only = os_data["NAME"].lower().split(" ")[0] 77 | if name_only in distro["rolling"]: 78 | return [distro["name"], distro['rolling']] 79 | return [os_data["NAME"], os_data["NAME"]] 80 | distribution_info = get_like_distro() 81 | # Fedora Auto Install 82 | if "fedora" in distribution_info[0]: 83 | library.print_ascii_art("Hmm, I've detected that you are running under Fedora!") 84 | print(f"[!] Wall of Flippers >> Would it be okay if we ran these commands on your system?\n{json.dumps(fedora_dependencies_cmd, indent=4)}") 85 | user_input_ok = input("[?] Wall of Flippers (Y/N) >> ") 86 | if user_input_ok.lower() == "y": 87 | print("[!] Wall of Flippers >> Installing dependencies...") 88 | for cmd in fedora_dependencies_cmd: 89 | os.system(cmd) 90 | library.print_ascii_art("We have successfully installed the dependencies!") # todo: add a check to see if the dependencies were really installed successfully 91 | print("[!] Wall of Flippers >> Dependencies installed successfully!") 92 | if "arch" in distribution_info[0]: 93 | library.print_ascii_art("Hmm, I've detected that you are running under Arch!") 94 | print(f"[!] Wall of Flippers >> Would it be okay if we ran these commands on your system?\n{json.dumps(arch_cmd, indent=4)}") 95 | user_input_ok = input("[?] Wall of Flippers (Y/N) >> ") 96 | if user_input_ok.lower() == "y": 97 | print("[!] Wall of Flippers >> Installing dependencies...") 98 | for cmd in arch_cmd: 99 | os.system(cmd) 100 | library.print_ascii_art("We have successfully installed the dependencies!") 101 | print("[!] Wall of Flippers >> Dependencies installed successfully!") 102 | # Debian Auto Install 103 | elif "debian" in distribution_info[0]: 104 | library.print_ascii_art("Hmm, I've detected that you are running under Debian!") 105 | print(f"[!] Wall of Flippers >> Would it be okay if we ran these commands on your system?\n{json.dumps(debian_dependencies_cmd, indent=4)}") 106 | user_input_ok = input("[?] Wall of Flippers (Y/N) >> ") 107 | if user_input_ok.lower() == "y": 108 | print("[!] Wall of Flippers >> Installing dependencies...") 109 | for cmd in debian_dependencies_cmd: 110 | os.system(cmd) 111 | library.print_ascii_art("We have successfully installed the dependencies!") # todo: add a check to see if the dependencies were really installed successfully 112 | print("[!] Wall of Flippers >> Dependencies installed successfully!") 113 | else: 114 | library.print_ascii_art(f"Hmm, I am unable to provide an automated install for your system. ({distribution_info[0]})") 115 | print(f"[!] Wall of Flippers >> Please install the dependencies manually. ({distribution_info[0]})") 116 | 117 | except KeyboardInterrupt: 118 | library.print_ascii_art("Thank you for using Wall of Flippers... Goodbye!") 119 | print("\n[!] Wall of Flippers >> Exiting...") 120 | sys.exit() 121 | -------------------------------------------------------------------------------- /WallofFlippers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # YAao, 4 | # Y8888b, Created By: Kiyomi & Jbohack 5 | # ,oA8888888b, Kiyomi: https://ko-fi.com/k3yomi 6 | # ,aaad8888888888888888bo, Jbohack: https://ko-fi.com/jbohack 7 | # ,d888888888888888888888888888b, 8 | # ,888888888888888888888888888888888b, 9 | # d8888888888888888888888888888888888888, 10 | # d888888888888888888888888888888888888888b 11 | # d888888P' `Y88888888Ꙩ \, 12 | # 88888P' Ybaaaa888888 Ꙩ l 13 | # a8888' `Y8888P' `V888888 14 | # d8888888a `Y8888 15 | # AY/'' `\Y8b ``Y8b 16 | # Y' `YP ~~ 17 | # _ __ ____ ____ _________ 18 | # | | / /___ _/ / / ____ / __/ / ____/ (_)___ ____ ___ __________ 19 | # | | /| / / __ `/ / / / __ \/ /_ / /_ / / / __ \/ __ \/ _ \/ ___/ ___/ 20 | # | |/ |/ / /_/ / / / / /_/ / __/ / __/ / / / /_/ / /_/ / __/ / (__ ) 21 | # |__/|__/\__,_/_/_/ \____/_/ /_/ /_/_/ .___/ .___/\___/_/ /____/ 22 | # /_/ /_/ 23 | 24 | 25 | # Standard library Imports 26 | import os 27 | import sys 28 | import shutil 29 | import time 30 | import asyncio 31 | import json 32 | import random 33 | import argparse 34 | 35 | 36 | 37 | 38 | 39 | 40 | # Wall of Flippers Imports 41 | import utils.wof_cache as cache # for important configurations and data :3 42 | import utils.wof_library as library # for important functions :3 43 | import utils.wof_display as wall_display # for important functions :3 44 | import utils.wof_install as installer # for important functions and classes :3 45 | import utils.wof_blechat as blechat # for important functions and classes :3 46 | 47 | Scanner = None # This is the scanner object for the bluepy package (Default = None) 48 | 49 | 50 | parser = argparse.ArgumentParser(description='Wall of Flippers', prog='WallofFlippers.py') 51 | parser.add_argument('-w', '--wall', action='store_true', help='Wall of Flippers') 52 | parser.add_argument('-i', '--install', action='store_true', help='Install Wall of Flippers') 53 | parser.add_argument('-d', '--device', action='store', help='Select a bluetooth device') 54 | parser.add_argument('-b', '--badgemode', action='store_true', help='Toggle Badge Mode') 55 | parser.add_argument('-a', '--advertise', action='store_true', help='Advertise WoF Exsistance (OFF=Default)') 56 | args = parser.parse_args() 57 | 58 | async def detection_async(os_param:str, detection_type=0): 59 | try: 60 | cache.wof_data['bool_isScanning'] = True 61 | ble_packets = [] 62 | if (os_param != "nt") and (os_param != "posix"): 63 | library.print_ascii_art("Error: Unsupported OS") 64 | print("[!] Wall of Flippers >> Error: Unsupported OS") 65 | sys.exit() 66 | if os_param == "nt": # Windows Detection 67 | devices = await BleakScanner.discover() 68 | if devices: # If devices are found 69 | for device in devices: 70 | device_info = library.flipper2Validation(device, os_param) 71 | ble_packets.append(device_info) 72 | if os_param == "posix": # Linux Detection 73 | scanner = Scanner(detection_type) # Thank you Talking Sasquach for testing this! 74 | devices = scanner.scan(5) # Scan the area for 5 seconds.... 75 | ble_packets = [] 76 | if devices: # If devices are found 77 | for device in devices: 78 | device_info = library.flipper2Validation(device, os_param) 79 | ble_packets.append(device_info) 80 | any_flippers_discovered, flippers_discovered_list, latest_discovered_list, total_new, ratelimited = library.ble2Sort(ble_packets) 81 | if not any_flippers_discovered: 82 | if ratelimited == True: 83 | ratelimit_time = cache.wof_data['last_ratelimit'] - time.time() 84 | if (ratelimit_time < 0): 85 | ratelimit_time = 0 86 | wall_display.display(f"Haulting logs for {int(ratelimit_time)} seconds due to {str(total_new)}+ flippers | Possible Spoofing") 87 | else: 88 | wall_display.display(None) 89 | cache.wof_data['bool_isScanning'] = False 90 | return 91 | else: 92 | if ratelimited == True: 93 | ratelimit_time = cache.wof_data['last_ratelimit'] - time.time() 94 | if (ratelimit_time < 0): 95 | ratelimit_time = 0 96 | wall_display.display(f"Haulting logs for {int(ratelimit_time)} seconds due to {str(total_new)}+ flippers | Possible Spoofing") 97 | cache.wof_data['bool_isScanning'] = False 98 | return 99 | latest_name = latest_discovered_list['Name'] 100 | latest_mac = latest_discovered_list['MAC'] 101 | wall_display.display(f"I've found a wild {latest_name} ({latest_mac})") 102 | cache.wof_data['bool_isScanning'] = False 103 | except Exception as e: 104 | library.print_ascii_art("Error: Failed to scan for BLE devices") 105 | print("[!] Wall of Flippers >> Error: Failed to scan for BLE devices >> " + str(e)) 106 | cache.wof_data['bool_isScanning'] = False 107 | 108 | # Start of the program 109 | library.required2files() 110 | os.system('cls' if os.name == 'nt' else 'clear') 111 | 112 | cache.wof_data['system_type'] = os.name 113 | if cache.wof_data['system_type'] == "posix": # Linux Auto Install 114 | if not os.path.exists(".venv/bin/activate"): # Check if the user has setup their virtual environment 115 | library.print_ascii_art("Uh oh, it seems like you have not setup your virtual environment yet!") 116 | print("[!] Wall of Flippers >> It seems like you have not setup your virtual environment yet.\n\t Reason: .venv/bin/activate does not exist.\n[!] Wall of Flippers >> Would you like to setup your virtual environment now?") 117 | if input("[?] Wall of Flippers (Y/N) >> ").lower() == "y": 118 | os.system("python3 -m venv .venv") 119 | print("[!] Wall of Flippers >> Virtual environment setup successfully!") 120 | sys.exit() 121 | if library.is_in_venv() == False: # Check if the user is in their virtual environment 122 | library.print_ascii_art("Uh oh, it seems like you are not in your virtual environment!") 123 | print("[!] Wall of Flippers >> It seems like you are not in your virtual environment. Please use the following command to enter your virtual environment.\n\tsource .venv/bin/activate\n\tor\n\tbash wof.sh") 124 | sys.exit() 125 | 126 | 127 | if args == None: 128 | selection_box = library.init() 129 | else: 130 | if args.badgemode: 131 | cache.wof_data['badge_mode'] = not cache.wof_data['badge_mode'] 132 | if args.advertise: 133 | cache.wof_data['toggle_adveriser'] = not cache.wof_data['toggle_adveriser'] 134 | if args.wall: 135 | selection_box = 'wall_of_flippers' 136 | elif args.install: 137 | selection_box = 'install_dependencies' 138 | else: 139 | selection_box = library.init() 140 | if selection_box == 'wall_of_flippers': 141 | try: 142 | if cache.wof_data['system_type'] == "nt": 143 | from bleak import BleakScanner # Windows BLE Package 144 | if cache.wof_data['system_type'] == "posix": 145 | from bluepy.btle import Scanner 146 | if (cache.wof_data['toggle_adveriser']): 147 | from utils.bluetooth_utils import toggle_device, start_le_advertising, stop_le_advertising 148 | import bluetooth._bluetooth as bluez 149 | except ImportError as e: 150 | library.print_ascii_art("Error: Failed to import dependencies") 151 | print(f"[!] Wall of Flippers >> Failed to import dependencies >> {e}") 152 | sys.exit() 153 | if cache.wof_data['system_type'] == "posix" and not os.geteuid() == 0: 154 | library.print_ascii_art("I require root privileges to run!") 155 | print("[!] Wall of Flippers >> I require root privileges to run.\n\t Reason: Dependency on bluepy library.") 156 | sys.exit() 157 | try: 158 | DEVIC_HCI = library.adapter2Selection(args.device) 159 | if (cache.wof_data['toggle_adveriser']) and (cache.wof_data['system_type'] == "posix"): # Start the BLE Advertiser if the user has it enabled 160 | sock = bluez.hci_open_dev(int(DEVIC_HCI)) 161 | toggle_device(int(DEVIC_HCI), True) 162 | wall_display.display(f"Thank you for using Wall of Flippers!") 163 | while True: 164 | time.sleep(1) 165 | if (cache.wof_data['toggle_adveriser']) and (cache.wof_data['system_type'] == "posix"): # Start the BLE Advertiser if the user has it enabled 166 | for i in range (0, 10): 167 | advertisementData = cache.wof_data['wof_advertiser'] 168 | advertismentName = tuple(cache.wof_data['wof_advertiserName'] .encode()) # Convert the advertiser name to bytes 169 | advertisementData += advertismentName 170 | advertisementData += (0x00,) * (31 - len(advertisementData)) # Padding 171 | if len(advertisementData) > 31: 172 | print("[!] Wall of Flippers >> Error: Advertisement data is too long; change the wof_advertiserName in utils/wof_cache.py") 173 | sys.exit() 174 | to_hex = lambda data: ''.join(f"{i:02x}" for i in advertisementData) 175 | data_hex = to_hex(advertisementData) 176 | start_le_advertising(sock, adv_type=0x03, data=advertisementData) 177 | time.sleep(0.1) 178 | stop_le_advertising(sock) 179 | if not cache.wof_data['bool_isScanning']: 180 | asyncio.run(detection_async(cache.wof_data['system_type'], DEVIC_HCI)) 181 | except KeyboardInterrupt: 182 | library.print_ascii_art("Thank you for using Wall of Flippers... Goodbye!") 183 | print("\n[!] Wall of Flippers >> Exiting...") 184 | sys.exit() 185 | if selection_box == 'install_dependencies': 186 | installer.init() 187 | if selection_box == 'wall_of_talking': 188 | if cache.wof_data['system_type'] == "posix" and not os.geteuid() == 0: 189 | library.print_ascii_art("I require root privileges to run!") 190 | print("[!] Wall of Flippers >> I require root privileges to run.\n\t Reason: Dependency on bluepy library.") 191 | sys.exit() 192 | if cache.wof_data['system_type'] != "posix": 193 | library.print_ascii_art("Error: BLE Chat is not supported on this OS") 194 | print("[!] Wall of Flippers >> Error: BLE Chat is not supported on this OS") 195 | sys.exit() 196 | blechat.init() 197 | -------------------------------------------------------------------------------- /utils/wof_display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # YAao, 4 | # Y8888b, Created By: Kiyomi & Jbohack 5 | # ,oA8888888b, Kiyomi: https://ko-fi.com/k3yomi 6 | # ,aaad8888888888888888bo, Jbohack: https://ko-fi.com/jbohack 7 | # ,d888888888888888888888888888b, 8 | # ,888888888888888888888888888888888b, 9 | # d8888888888888888888888888888888888888, 10 | # d888888888888888888888888888888888888888b 11 | # d888888P' `Y88888888Ꙩ \, 12 | # 88888P' Ybaaaa888888 Ꙩ l 13 | # a8888' `Y8888P' `V888888 14 | # d8888888a `Y8888 15 | # AY/'' `\Y8b ``Y8b 16 | # Y' `YP ~~ 17 | # _ __ ____ ____ _________ 18 | # | | / /___ _/ / / ____ / __/ / ____/ (_)___ ____ ___ __________ 19 | # | | /| / / __ `/ / / / __ \/ /_ / /_ / / / __ \/ __ \/ _ \/ ___/ ___/ 20 | # | |/ |/ / /_/ / / / / /_/ / __/ / __/ / / / /_/ / /_/ / __/ / (__ ) 21 | # |__/|__/\__,_/_/_/ \____/_/ /_/ /_/_/ .___/ .___/\___/_/ /____/ 22 | # /_/ /_/ 23 | 24 | # Standard library Imports 25 | import json 26 | import shutil 27 | 28 | # Wall of Flippers "library" for important functions and classes :3 29 | import utils.wof_cache as cache # Wall of Flippers "cache" for important configurations and data :3 30 | import utils.wof_library as library # Wall of Flippers "library" for important functions and classes :3 31 | 32 | def isLive(mac:str, string:str): # Fixed same mac spoofing implementation (Duplicate MACs) 33 | for key in cache.wof_data['live_flippers']: 34 | if key['MAC'] == mac and key['Name'] == string: 35 | return True 36 | return False 37 | 38 | def display(custom_text:str=None): 39 | """displays the data in a nice format""" 40 | try: 41 | # Load flipper data from Flipper.json 42 | with open('db/Flipper.json', 'r', encoding='utf-8') as flipper_file: 43 | flipper_data = json.load(flipper_file) 44 | with open('db/Backup.json', 'w', encoding='utf-8') as flipper_file: 45 | json.dump(flipper_data, flipper_file, indent=4) 46 | # on any error, return 47 | except: 48 | print("Error: Could not load Flipper.json") 49 | # open the backup and save to the main file 50 | with open('db/Backup.json', 'r', encoding='utf-8') as flipper_file: 51 | flipper_data = json.load(flipper_file) 52 | with open('db/Flipper.json', 'w', encoding='utf-8') as flipper_file: 53 | json.dump(flipper_data, flipper_file, indent=4) # save the backup to the main file 54 | # Update base_flippers list with flipper data 55 | cache.wof_data['base_flippers'] = [key for key in flipper_data] 56 | 57 | # Categorize flipper data into live and offline flippers 58 | for key in cache.wof_data['base_flippers']: 59 | if 'Type' not in key: # Add a check for the 'Type' key 60 | key['Type'] = "Unknown" 61 | isOnline = isLive(key['MAC'], key['Name']) 62 | key['Name'] = key['Name'].replace("Flipper ", "")[:15] 63 | if isOnline: # Fixed same mac spoofing implementation (Duplicate MACs) 64 | cache.wof_data['display_live'].append(key) 65 | else: 66 | cache.wof_data['display_offline'].append(key) 67 | 68 | # Calculate various stats 69 | t_allignment = 8 70 | ble_spamming_macs = [] 71 | number_of_total_blacklisted_packets = len(cache.wof_data['forbidden_packets_found']) 72 | number_of_total_ble_packets = len(cache.wof_data['all_packets_found']) 73 | number_of_flippers_online = len(cache.wof_data['display_live']) 74 | number_of_flippers_offline = len(cache.wof_data['display_offline']) 75 | 76 | # check if the terminal is too small 77 | if shutil.get_terminal_size().columns < cache.wof_data['narrow_mode_limit']: # if the terminal size is less than *narrow_mode_limit* columns (default: 100) 78 | cache.wof_data['narrow_mode'] = True 79 | else: 80 | cache.wof_data['narrow_mode'] = False 81 | 82 | # Display ASCII art 83 | if custom_text is None or custom_text == "": 84 | library.print_ascii_art(None) 85 | else: 86 | library.print_ascii_art(custom_text) 87 | 88 | # Display stats for POSIX systems (Unix-like operating systems) 89 | if cache.wof_data['system_type'] == "posix": 90 | if not cache.wof_data['badge_mode']: 91 | print(f"Latest Forbidden Advertisements..: {number_of_total_blacklisted_packets}\nLatest Advertisements............: {number_of_total_ble_packets}") 92 | if len(cache.wof_data['all_packets_found']) > 0: # if there are packets found 93 | packet_counts = {} 94 | addrs = [] 95 | # Count occurrences of each packet 96 | for packet in cache.wof_data['all_packets_found']: 97 | packet_value = packet['PCK'] 98 | packet_counts[packet_value] = packet_counts.get(packet_value, 0) + 1 99 | # Find the most common packet 100 | most_common_packet = max(packet_counts, key=packet_counts.get) 101 | # Find unique addresses for the most common packet 102 | for packet in cache.wof_data['all_packets_found']: 103 | if packet['PCK'] == most_common_packet: 104 | if packet['MAC'] not in addrs: 105 | addrs.append(packet['MAC']) 106 | if len(packet['PCK']) > cache.wof_data['max_byte_length']: 107 | cache.wof_data['forbidden_packets_found'].append({"MAC": packet['MAC'], "PCK": packet['PCK'], "Type": f"SUSPICIOUS_PACKET (+{cache.wof_data['max_byte_length']} bytes)"}) 108 | if cache.wof_data['narrow_mode']: 109 | print(f"Most Common Advertisement........: {most_common_packet} ({packet_counts[most_common_packet]} packets) ({len(addrs)} unique addresses)") 110 | else: 111 | print(f"Most Common Advertisement........: {most_common_packet} ({packet_counts[most_common_packet]} packets) ({len(addrs)} unique addresses)") 112 | # Add a summary if there are too many unique addresses 113 | if len(addrs) > 5: 114 | cache.wof_data['forbidden_packets_found'].append({"MAC": str(len(addrs)) + " Unique Addresses", "PCK": most_common_packet, "Type": "SUSPICIOUS_ADVERTISEMENT"}) 115 | else: # if there are no packets found 116 | print("Most Common Advertisement........: None") 117 | # Display forbidden packets 118 | if len(cache.wof_data['forbidden_packets_found']) > 0: 119 | t_packets = 0 120 | print("\n\n[!] Wall of Flippers >> These packets may not be related to the Flipper Zero.\n[NAME]\t\t\t\t\t[RSSI]\t[ADDR]\t\t [PACKET]") 121 | print(shutil.get_terminal_size().columns * "-") 122 | for key in cache.wof_data['forbidden_packets_found']: 123 | if ble_spamming_macs.count(key['MAC']) == 0: 124 | ble_spamming_macs.append(key['MAC']) 125 | t_packets += 1 126 | if t_packets <= cache.wof_data['max_ble_packets']: # Max amount of packets to display on the screen 127 | rssi_value = str(key.get('RSSI', 'N/A')) 128 | print(f"{key['Type'].ljust(t_allignment)}\t\t{rssi_value.ljust(t_allignment)}{key['MAC'].ljust(t_allignment)} {str(key['PCK']).ljust(t_allignment)}") 129 | if number_of_total_blacklisted_packets > cache.wof_data['ble_threshold']: 130 | print(f"------------------ Bluetooth Low Energy (BLE) Attacks Detected ({number_of_total_blacklisted_packets} Advertisements) --------------------") 131 | else: # if the system is not POSIX (Windows) 132 | print("\n------------------ BLE Attack Detection is not available for Windows yet. ------------------") 133 | # Display flipper stats 134 | print(f"\nTotal Online.....................: {number_of_flippers_online}\nTotal Offline....................: {number_of_flippers_offline}") 135 | print(f"Volume Collected.................: ${(number_of_flippers_online + number_of_flippers_offline) * cache.wof_data['flipper_volume_price']}") 136 | print(f"WoF Instances Nearby.............: {(cache.wof_data['nearbyWof'])}") 137 | if cache.wof_data['narrow_mode']: 138 | print("\n\nFlipper, Address, First Seen, Last Seen, RSSI, Detection") 139 | else: 140 | print(f"\n\n[FLIPPER]{''.ljust(t_allignment)}[ADDR]{''.ljust(t_allignment)}\t\t[FIRST]{''.ljust(t_allignment)}[LAST]{''.ljust(t_allignment)}[RSSI]\t[DETECTION]") 141 | print("-"*shutil.get_terminal_size().columns) 142 | # Display online flippers if there are any 143 | if number_of_flippers_online > 0: 144 | t_live = 0 145 | cache.wof_data['display_live'] = sorted(cache.wof_data['display_live'], key=lambda k: k['unixLastSeen'], reverse=True) 146 | for key in cache.wof_data['display_live']: 147 | t_live += 1 148 | if t_live <= cache.wof_data['max_online']: 149 | key['RSSI'] = str(f"{key['RSSI']} dBm") 150 | if cache.wof_data['narrow_mode']: # if narrow mode, display in a more compact format 151 | print(f"{key['Name']}, {key['MAC']}, {library.unix2text(key['unixFirstSeen'])}, {library.unix2text(key['unixLastSeen'])}, {str(key['RSSI'])}, {key['Detection Type']} ({key['Type']})") 152 | else: 153 | print(f"{key['Name'].ljust(t_allignment)}\t{key['MAC'].ljust(t_allignment)}\t{library.unix2text(key['unixFirstSeen']).ljust(t_allignment)}\t{library.unix2text(key['unixLastSeen']).ljust(t_allignment)} {str(key['RSSI']).ljust(t_allignment)}\t{key['Detection Type']} ({key['Type']})".ljust(t_allignment)) 154 | if t_live > cache.wof_data['max_online']: 155 | t_left_over = number_of_flippers_online - cache.wof_data['max_online'] 156 | print(f"Too many devices to display. ({t_left_over} devices)") 157 | break 158 | # Display offline flippers if there are any 159 | if number_of_flippers_offline > 0: 160 | if not cache.wof_data['badge_mode']: 161 | t_offline = 0 162 | print("\033[2m".center(shutil.get_terminal_size().columns)) 163 | cache.wof_data['display_offline'] = sorted(cache.wof_data['display_offline'], key=lambda k: k['unixLastSeen'], reverse=True) 164 | for key in cache.wof_data['display_offline']: 165 | t_offline += 1 166 | if t_offline <= cache.wof_data['max_offline']: 167 | key['RSSI'] = "Offline" 168 | if cache.wof_data['narrow_mode']: # if narrow mode, display in a more compact format 169 | print(f"{key['Name']}, {key['MAC']}, {library.unix2text(key['unixFirstSeen'])}, {library.unix2text(key['unixLastSeen'])}, {str(key['RSSI'])}, {key['Detection Type']} ({key['Type']})") 170 | else: 171 | print(f"{key['Name'].ljust(t_allignment)}\t{key['MAC'].ljust(t_allignment)}\t{library.unix2text(key['unixFirstSeen']).ljust(t_allignment)}\t{library.unix2text(key['unixLastSeen']).ljust(t_allignment)} {str(key['RSSI']).ljust(t_allignment)}\t{key['Detection Type']} ({key['Type']})".ljust(t_allignment)) 172 | if t_offline > cache.wof_data['max_offline']: 173 | t_left_over = number_of_flippers_offline - cache.wof_data['max_offline'] 174 | print(f"\033[0mToo many devices to display. ({t_left_over} devices)\033[0m") 175 | break 176 | print("\033[0m") # reset the text style 177 | 178 | # Display message if no devices detected 179 | if number_of_flippers_offline == 0 and number_of_flippers_online == 0: 180 | print("No devices detected, scanning...".center(shutil.get_terminal_size().columns)) 181 | 182 | # Reset data for next refresh of the display 183 | cache.wof_data['display_live'] = [] 184 | cache.wof_data['display_offline'] = [] 185 | cache.wof_data['live_flippers'] = [] 186 | cache.wof_data['forbidden_packets_found'] = [] 187 | cache.wof_data['all_packets_found'] = [] 188 | cache.wof_data['duplicated_packets'] = [] 189 | cache.wof_data['nearbyWof'] = [] 190 | -------------------------------------------------------------------------------- /utils/bluetooth_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Module containing some bluetooth utility functions (linux only). 4 | 5 | It either uses HCI commands using PyBluez, or does ioctl calls like it's 6 | done in Bluez tools such as hciconfig. 7 | 8 | Main functions: 9 | - toggle_device : enable or disable a bluetooth device 10 | - set_scan : set scan type on a device ("noscan", "iscan", "pscan", "piscan") 11 | - enable/disable_le_scan : enable BLE scanning 12 | - parse_le_advertising_events : parse and read BLE advertisements packets 13 | - start/stop_le_advertising : advertise custom data using BLE 14 | 15 | Bluez : http://www.bluez.org/ 16 | PyBluez : http://karulis.github.io/pybluez/ 17 | 18 | The module was in particular inspired from 'iBeacon-Scanner-' 19 | https://github.com/switchdoclabs/iBeacon-Scanner-/blob/master/blescan.py 20 | and sometimes directly from the Bluez sources. 21 | """ 22 | 23 | from __future__ import absolute_import 24 | import sys 25 | import struct 26 | import fcntl 27 | import array 28 | import socket 29 | from errno import EALREADY 30 | 31 | # import PyBluez 32 | import bluetooth._bluetooth as bluez 33 | 34 | __all__ = ('toggle_device', 'set_scan', 35 | 'enable_le_scan', 'disable_le_scan', 'parse_le_advertising_events', 36 | 'start_le_advertising', 'stop_le_advertising', 37 | 'raw_packet_to_str') 38 | 39 | LE_META_EVENT = 0x3E 40 | LE_PUBLIC_ADDRESS = 0x00 41 | LE_RANDOM_ADDRESS = 0x01 42 | 43 | OGF_LE_CTL = 0x08 44 | OCF_LE_SET_SCAN_PARAMETERS = 0x000B 45 | OCF_LE_SET_SCAN_ENABLE = 0x000C 46 | OCF_LE_CREATE_CONN = 0x000D 47 | OCF_LE_SET_ADVERTISING_PARAMETERS = 0x0006 48 | OCF_LE_SET_ADVERTISE_ENABLE = 0x000A 49 | OCF_LE_SET_ADVERTISING_DATA = 0x0008 50 | 51 | SCAN_TYPE_PASSIVE = 0x00 52 | SCAN_FILTER_DUPLICATES = 0x01 53 | SCAN_DISABLE = 0x00 54 | SCAN_ENABLE = 0x01 55 | 56 | # sub-events of LE_META_EVENT 57 | EVT_LE_CONN_COMPLETE = 0x01 58 | EVT_LE_ADVERTISING_REPORT = 0x02 59 | EVT_LE_CONN_UPDATE_COMPLETE = 0x03 60 | EVT_LE_READ_REMOTE_USED_FEATURES_COMPLETE = 0x04 61 | 62 | # Advertisement event types 63 | ADV_IND = 0x00 64 | ADV_DIRECT_IND = 0x01 65 | ADV_SCAN_IND = 0x02 66 | ADV_NONCONN_IND = 0x03 67 | ADV_SCAN_RSP = 0x04 68 | 69 | # Allow Scan Request from Any, Connect Request from Any 70 | FILTER_POLICY_NO_WHITELIST = 0x00 71 | # Allow Scan Request from White List Only, Connect Request from Any 72 | FILTER_POLICY_SCAN_WHITELIST = 0x01 73 | # Allow Scan Request from Any, Connect Request from White List Only 74 | FILTER_POLICY_CONN_WHITELIST = 0x02 75 | # Allow Scan Request from White List Only, Connect Request from White List Only 76 | FILTER_POLICY_SCAN_AND_CONN_WHITELIST = 0x03 77 | 78 | 79 | def toggle_device(dev_id, enable): 80 | """ 81 | Power ON or OFF a bluetooth device. 82 | 83 | :param dev_id: Device id. 84 | :type dev_id: ``int`` 85 | :param enable: Whether to enable of disable the device. 86 | :type enable: ``bool`` 87 | """ 88 | hci_sock = socket.socket(socket.AF_BLUETOOTH, 89 | socket.SOCK_RAW, 90 | socket.BTPROTO_HCI) 91 | # print("Power %s bluetooth device %d" % ('ON' if enable else 'OFF', dev_id)) 92 | # di = struct.pack("HbBIBBIIIHHHH10I", dev_id, *((0,) * 22)) 93 | # fcntl.ioctl(hci_sock.fileno(), bluez.HCIGETDEVINFO, di) 94 | req_str = struct.pack("H", dev_id) 95 | request = array.array("b", req_str) 96 | try: 97 | fcntl.ioctl(hci_sock.fileno(), 98 | bluez.HCIDEVUP if enable else bluez.HCIDEVDOWN, 99 | request[0]) 100 | except IOError as e: 101 | if e.errno == EALREADY: 102 | pass 103 | # print("Bluetooth device %d is already %s" % ( 104 | # dev_id, 'enabled' if enable else 'disabled')) 105 | else: 106 | raise 107 | finally: 108 | hci_sock.close() 109 | 110 | 111 | # Types of bluetooth scan 112 | SCAN_DISABLED = 0x00 113 | SCAN_INQUIRY = 0x01 114 | SCAN_PAGE = 0x02 115 | 116 | 117 | def set_scan(dev_id, scan_type): 118 | """ 119 | Set scan type on a given bluetooth device. 120 | 121 | :param dev_id: Device id. 122 | :type dev_id: ``int`` 123 | :param scan_type: One of 124 | ``'noscan'`` 125 | ``'iscan'`` 126 | ``'pscan'`` 127 | ``'piscan'`` 128 | :type scan_type: ``str`` 129 | """ 130 | hci_sock = socket.socket(socket.AF_BLUETOOTH, 131 | socket.SOCK_RAW, 132 | socket.BTPROTO_HCI) 133 | if scan_type == "noscan": 134 | dev_opt = SCAN_DISABLED 135 | elif scan_type == "iscan": 136 | dev_opt = SCAN_INQUIRY 137 | elif scan_type == "pscan": 138 | dev_opt = SCAN_PAGE 139 | elif scan_type == "piscan": 140 | dev_opt = SCAN_PAGE | SCAN_INQUIRY 141 | else: 142 | raise ValueError("Unknown scan type %r" % scan_type) 143 | 144 | req_str = struct.pack("HI", dev_id, dev_opt) 145 | # print("Set scan type %r to bluetooth device %d" % (scan_type, dev_id)) 146 | try: 147 | fcntl.ioctl(hci_sock.fileno(), bluez.HCISETSCAN, req_str) 148 | finally: 149 | hci_sock.close() 150 | 151 | 152 | def raw_packet_to_str(pkt): 153 | """ 154 | Returns the string representation of a raw HCI packet. 155 | """ 156 | if sys.version_info > (3, 0): 157 | return ''.join('%02x' % struct.unpack("B", bytes([x]))[0] for x in pkt) 158 | else: 159 | return ''.join('%02x' % struct.unpack("B", x)[0] for x in pkt) 160 | 161 | 162 | def enable_le_scan(sock, interval=0x0800, window=0x0800, 163 | filter_policy=FILTER_POLICY_NO_WHITELIST, 164 | filter_duplicates=True): 165 | """ 166 | Enable LE passive scan (with filtering of duplicate packets enabled). 167 | 168 | :param sock: A bluetooth HCI socket (retrieved using the 169 | ``hci_open_dev`` PyBluez function). 170 | :param interval: Scan interval. 171 | :param window: Scan window (must be less or equal than given interval). 172 | :param filter_policy: One of 173 | ``FILTER_POLICY_NO_WHITELIST`` (default value) 174 | ``FILTER_POLICY_SCAN_WHITELIST`` 175 | ``FILTER_POLICY_CONN_WHITELIST`` 176 | ``FILTER_POLICY_SCAN_AND_CONN_WHITELIST`` 177 | 178 | .. note:: Scan interval and window are to multiply by 0.625 ms to 179 | get the real time duration. 180 | """ 181 | # print("Enable LE scan") 182 | own_bdaddr_type = LE_PUBLIC_ADDRESS # does not work with LE_RANDOM_ADDRESS 183 | cmd_pkt = struct.pack(" 31: 242 | raise ValueError("data is too long (%d but max is 31 bytes)", 243 | data_length) 244 | cmd_pkt = struct.pack(" 1000: 82 | t_hours, t_minutes = divmod(t_minutes, 60) 83 | if t_hours > 24: 84 | t_days, t_hours = divmod(t_hours, 24) 85 | if t_days > 1000: 86 | t_years, t_days = divmod(t_days, 365) 87 | return f"1Year+" 88 | return f"{t_days}d {t_hours}h" 89 | return f"{t_hours}h {t_minutes}m" 90 | return f"{t_minutes}m {t_seconds}s" 91 | 92 | def ble2Sort(packets:list): # Sorts BLE packets and updates the list/cache 93 | """Sorts the BLE packets based on the type of packet""" 94 | any_flippers_discovered = False 95 | flippers_discovered_list = [] 96 | latest_discovered_list = [] 97 | suspiciousFlippers = [] 98 | forbidden_packets_list = cache.wof_data['forbidden_packets'] 99 | wof_advertiserRaw = cache.wof_data['wof_advertiserRaw'] 100 | totalFlippersFound = (advertisement[0]["flipper"] for advertisement in packets) 101 | totalNewFound = 0 102 | totalNewFound = sum(1 for advertisement in packets if advertisement[0]["flipper"] and not any(flipper['MAC'] == advertisement[0]["address"] for flipper in cache.wof_data['base_flippers'])) 103 | totalNewFoundArray = [advertisement[0] for advertisement in packets if advertisement[0]["flipper"] and not any(flipper['MAC'] == advertisement[0]["address"] for flipper in cache.wof_data['base_flippers'])] 104 | for advertisement in totalNewFoundArray: suspiciousFlippers.append(advertisement["address"]) 105 | if (totalNewFound >= cache.wof_data['max_flippers_ratelimited'] and not cache.wof_data['is_ratelimited']): 106 | cache.wof_data['is_ratelimited'] = True 107 | cache.wof_data['last_ratelimit'] = int(time.time()) + cache.wof_data['ratelimit_seconds'] 108 | for advertisement in packets: 109 | advertisement = advertisement[0] 110 | adv_name = advertisement["name"] 111 | adv_type = advertisement["color"] 112 | adv_rssi = advertisement["rssi"] 113 | adv_address = advertisement["address"] 114 | adv_packets = advertisement["packets"] 115 | adv_uid = advertisement["uid"] 116 | adv_isFlipper = advertisement["flipper"] 117 | adv_detection = advertisement["detection"] 118 | adv_blacklisted = None 119 | if adv_address in suspiciousFlippers and cache.wof_data['is_ratelimited']: adv_isFlipper = False # If the flipper is in the suspicious list, mark it down as not a flipper 120 | for packet in adv_packets: 121 | for forbidden_packet in forbidden_packets_list: 122 | if all(p1 == p2 or p2 == "_" for p1, p2 in zip(packet, forbidden_packet['PCK'])): 123 | int_get_non_underscore = len(forbidden_packet['PCK'].replace("_", "")) 124 | int_total_found = sum(p != "_" for p in packet) 125 | if int_total_found >= int_get_non_underscore: 126 | cache.wof_data['forbidden_packets_found'].append({"Type": forbidden_packet['TYPE'],"PCK": packet,"MAC": adv_address, "RSSI": adv_rssi}) 127 | if len(packet) > cache.wof_data['min_byte_length']: # If the packet is longer than the minimum byte length, then it is a valid packet we want to log 128 | cache.wof_data['all_packets_found'].append({"PCK": packet,"MAC": adv_address}) 129 | if str(packet).startswith(wof_advertiserRaw): 130 | decodedAdvertiser = bytes.fromhex(packet.replace(wof_advertiserRaw, "")).decode('utf-8').replace("\x00", "") 131 | cache.wof_data['nearbyWof'].append(decodedAdvertiser) 132 | if adv_isFlipper: # if flipper is set to true :3 133 | int_recorded = int(time.time()) 134 | cache.wof_data['found_flippers'] = [flipper for flipper in cache.wof_data['found_flippers'] if adv_address != flipper['MAC']] 135 | t_data = {"Name": adv_name,"RSSI": adv_rssi,"MAC": adv_address,"Detection Type": adv_detection,"unixLastSeen": int_recorded,"unixFirstSeen": int_recorded,"Type": adv_type,"UID": adv_uid} 136 | if not any(flipper['MAC'] == adv_address and flipper['Name'] == adv_name for flipper in cache.wof_data['found_flippers']): # if the flipper is not in the list, add it 137 | cache.wof_data['found_flippers'].append(t_data) 138 | cache.wof_data['live_flippers'].append(t_data) 139 | log(t_data) 140 | any_flippers_discovered = True 141 | flippers_discovered_list.append(t_data) 142 | latest_discovered_list = t_data 143 | if (cache.wof_data['last_ratelimit'] < int(time.time())) and cache.wof_data['is_ratelimited']: 144 | cache.wof_data['is_ratelimited'] = False 145 | return any_flippers_discovered, flippers_discovered_list, latest_discovered_list, totalNewFound, cache.wof_data['is_ratelimited'] 146 | 147 | def flipper2Validation(data:list, os:str): # Validates incoming flippers/ble packets 148 | device_packets = [] 149 | device_information = [] 150 | device_name = "UNK" 151 | device_manufacturer = "UNK" 152 | device_uid = "UNK" 153 | device_color = "UNK" 154 | device_formatted = [] 155 | device_mac = "UNK" 156 | device_rssi = data.rssi 157 | isFlipper = False 158 | keyFound = False 159 | detectionType = "Unknown" 160 | if os == "nt": 161 | device_mac = str(data.address.lower()) 162 | device_name = str(data.name) 163 | advertisement_uid = str(data.metadata.get('uids')).replace("['", "").replace("']", "") 164 | for key, value in cache.wof_data['flipper_types'].items(): 165 | if key in advertisement_uid: 166 | device_uid = advertisement_uid 167 | device_color = value 168 | device_packets = ["06", device_name, device_uid, "00"] 169 | keyFound = True 170 | if not keyFound: 171 | if advertisement_uid.startswith("0000308") and advertisement_uid.endswith("0000-1000-8000-00805f9b34fb"): 172 | device_uid = advertisement_uid 173 | device_color = "SPF" 174 | device_packets = ["06", device_name, device_uid, "00"] 175 | if os == "posix": 176 | device_mac = data.addr.lower() 177 | scan_list = data.getScanData() 178 | for scan_list_item in scan_list: 179 | device_formatted.append({"ADTYPE": scan_list_item[0], "Description": scan_list_item[1], "Value": scan_list_item[2]}) 180 | for i_data in device_formatted: 181 | if i_data['Description'] == "Complete Local Name": 182 | device_name = i_data['Value'] 183 | if i_data['Description'] == "Manufacturer": 184 | device_manufacturer = i_data['Value'] 185 | for key, value in cache.wof_data['flipper_types'].items(): 186 | if i_data['Value'] == key: 187 | device_uid = i_data['Value'] 188 | device_color = value 189 | keyFound = True 190 | if not keyFound: 191 | if i_data['Value'].startswith("0000308") and i_data['Value'].endswith("0000-1000-8000-00805f9b34fb"): 192 | device_uid = i_data['Value'] 193 | device_color = "SPF" 194 | device_packets.append(i_data['Value']) 195 | if device_uid != "UNK" and len(device_packets) == 4: 196 | if device_packets[0] == "06" and device_packets[1] == device_name and device_packets[2] == device_uid and device_packets[3] == "00": 197 | if device_name.lower().startswith("flipper"): 198 | isFlipper = True 199 | detectionType = "Name" 200 | elif device_mac.startswith(("80:e1:26", "80:e1:27", "0c:fa:22")): # FLIPPER DEVICES INC (https://maclookup.app/macaddress/0cfa22) 201 | detectionType = "Address" 202 | isFlipper = True 203 | else: 204 | detectionType = "Identifier" 205 | isFlipper = True 206 | device_information.append({ 207 | "name": device_name, 208 | "address": device_mac, 209 | "rssi": device_rssi, 210 | "packets": device_packets, 211 | "uid": device_uid, 212 | "manufacturer": device_manufacturer, 213 | "color": device_color, 214 | "genericdata": device_formatted, 215 | "detection": detectionType, 216 | "flipper": isFlipper 217 | }) 218 | return device_information 219 | 220 | def adapter2Selection(deviceArgs:str=None): 221 | ble_adapters = [] 222 | if cache.wof_data['system_type'] == "posix": 223 | ble_adapters = [adapter for adapter in os.listdir('/sys/class/bluetooth/') if 'hci' in adapter] 224 | # make a selection of the bluetooth adapter 225 | if deviceArgs == None: 226 | print("\n\n[#]\t[HCI DEVICE]\n" + "-" * shutil.get_terminal_size().columns) 227 | for adapter in ble_adapters: 228 | print(f"{ble_adapters.index(adapter)}".ljust(8) + f"{adapter}".ljust(34)) 229 | DEVIC_HCI = input("[?] Wall of Flippers >> ") 230 | else: 231 | DEVIC_HCI = deviceArgs 232 | else: 233 | DEVIC_HCI = 0 234 | if DEVIC_HCI == "": # If the user does not select a device, default to 0 235 | DEVIC_HCI = 0 236 | return DEVIC_HCI 237 | 238 | def is_in_venv(): 239 | """Returns True if the user is in a virtual environment, otherwise returns False""" 240 | return sys.prefix != sys.base_prefix 241 | 242 | def print_ascii_art(custom_text:str=None): 243 | """Displays ASCII art in the terminal with the custom text if provided, otherwise displays a random quote""" 244 | os.system('cls' if os.name == 'nt' else 'clear') 245 | r_quote = random.choice(cache.wof_data['dolphin_thinking']) if not custom_text else custom_text 246 | # selecting adequate ASCII art based on the terminal size and if the user is in narrow mode 247 | print("\033[0;94m") 248 | if cache.wof_data['narrow_mode']: 249 | print(f"{cache.wof_data['ascii_small']}\n\"{r_quote}\"\n".center(50) + "\033[0m") 250 | else: 251 | ascii_art = cache.wof_data['ascii_normal'] 252 | 253 | if cache.wof_data['badge_mode']: 254 | # shorten ascii art by 10 lines 255 | ascii_art = "\n".join(ascii_art.split("\n")[:-7]) 256 | print(f"{ascii_art.replace('[RANDOM_QUOTE]', r_quote)}\n\033[0m") 257 | else: 258 | print(f"{ascii_art.replace('[RANDOM_QUOTE]', r_quote)}\n\033[0m") 259 | 260 | def init(): 261 | """Initial Selection Box (Upon starup) 262 | This init() function allows the user to select what action they would like to preform (cached options stored in utils/wof_cache.py) 263 | returns: the action the user selected (str) 264 | """ 265 | # check terminal size to set narrow mode (false by default) 266 | if shutil.get_terminal_size().columns < cache.wof_data['narrow_mode_limit']: # if the terminal size is less than *narrow_mode_limit* columns (default: 100) 267 | cache.wof_data['narrow_mode'] = True 268 | 269 | dialogue_options = cache.wof_data['init_directory_options'] 270 | dialogue_options_dict = {option['option']: option['return'] for option in dialogue_options} 271 | print_ascii_art("Please Select an option to continue") 272 | 273 | #Library dependencies check 274 | #This checks the bleak, bluepy, and bluetooth python packages and libraries 275 | #for Wall of Flippers to work properly. 276 | 277 | try: 278 | import bleak # Universal (Mostly for Windows) BLE Library 279 | print("[X] Bleak is installed") 280 | except ImportError: 281 | print("[ ] Bleak is installed") 282 | try: 283 | import bluepy # Linux BLE Library 284 | print("[X] Bluepy is installed") 285 | except ImportError: 286 | print("[ ] Bluepy is installed") 287 | try: 288 | import bluetooth # Bluetooth Library 289 | print("[X] Bluetooth is installed") 290 | except ImportError: 291 | print("[ ] Bluetooth is installed") 292 | 293 | 294 | #Initial selection box for the user to select what they want to do. 295 | 296 | if cache.wof_data['narrow_mode']: 297 | # dont display the description if the terminal is too narrow 298 | print("\n\n[#]\t[ACTION]\n" + "-"*shutil.get_terminal_size().columns + "\n" + "\n".join([f"{option['option'].ljust(8)}{option['action']}" for option in dialogue_options])) 299 | else: 300 | print("\n\n[#]\t[ACTION]\t\t\t [DESCRIPTION]\n" + "-"*shutil.get_terminal_size().columns + "\n" + "\n".join([f"{option['option'].ljust(8)}{option['action'].ljust(34)}{option['description']}" for option in dialogue_options])) 301 | 302 | 303 | try: 304 | str_input = input("\n[?] Wall of Flippers >> ") 305 | return dialogue_options_dict.get(str_input) 306 | except KeyboardInterrupt: 307 | print_ascii_art("Thank you for using Wall of Flippers... Goodbye!") 308 | print("\n[!] Wall of Flippers >> Exiting...") 309 | sys.exit() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Wall of Flippers 3 |

4 | 5 | 6 | 7 | 8 |

Wall of Flippers

9 | 10 |
11 |

🐬 A simple and easy way to find Flipper Zero Devices and Bluetooth Low Energy Based Attacks 🐬

12 |

🐬 Documentation written by @k3yomi 🐬

13 |
14 | 15 | 16 | 23 | 30 | 31 |
17 | 18 | 19 | 20 | 21 |

k3yomi (Project Maintainer)

22 |
24 | 25 | 26 | 27 | 28 |

Jbohack (Contributor)

29 |
32 | GitHub Repo stars 33 | GitHub forks 34 | GitHub issues 35 | GitHub pull requests 36 | 37 | Discord Shield 38 | 39 |
40 |
41 | 42 | --- 43 | 44 |

🐬 Table of Contents

45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | - [Introduction](#doc_introduction) 56 | - [Features](#doc_features) 57 | - [Videos](#doc_videos) 58 | - [Install Guide](#doc_install) 59 | - [How to install](#install_guides) 60 | - [Debian Linux Install](#debian_install) 61 | - [Fedora Install](#fedora_install) 62 | - [Arch Linux Install (SOON)](#arch_install) 63 | - [Windows Install](#windows_install) 64 | - [Headless Usage](#headless_usage) 65 | - [Issues and Fixes](#doc_issues_and_fixes) 66 | - [Common Errors and Fixes](#doc_c_and_e) 67 | - [Related Projects](#doc_related) 68 | - [Notice](#doc_statement) 69 | 70 |

71 | 72 | 73 | 74 | # 🐬 Wall of Flippers? 75 | > Wall of Flippers (WoF) is a python based project involving the discovery of the Flipper Zero device and the identification of potential Bluetooth advertisement attacks. Please keep in mind that these two types of detections may **not** be related. Feel free to submit pull requests if you would like to contribute! 76 | 77 | 78 | 79 | # 🐬 Current features and future updates 80 | - [x] Discover Flipper Zero Devices (Bluetooth must be enabled) 81 | - [X] Flipper Name Discovery 82 | - [X] Flipper Address Discovery 83 | - [X] Flipper "Identifier" Discovery ( Transparent, White, & Black Flipper Detection) 84 | - [ ] Spoof Detection (Coming Soon) 85 | - [x] Ability to archive past flipper zero devices discovered 86 | - [x] Auto-install functionality for Debian Linux and Windows 87 | - [x] Ability to identify potential Bluetooth Advertisement attacks 88 | - [x] Suspected Advertisement Attacks 89 | - [x] ~iOS Crash Advertisement Attack~ 90 | - [x] iOS Popup Advertisement Attacks 91 | - [x] Samsung and Android BLE Advertisement Attacks 92 | - [x] Windows Swift Pair Advertisement Attacks 93 | - [x] LoveSpouse Advertisement Attacks (Denial of Pleasure) 94 | - [x] BT Settings Flood 95 | - [x] Bluetooth Remote Detection 96 | - [x] BLE Advertiser (Detect other WoF Instances) 97 | - [x] Custom Name Implementation 98 | - [x] BLE Chat 99 | - [x] Badge Mode (Less fancy stats) 100 | - [x] BLE External / Internal Adapter Support 101 | - [x] Linux Supported 102 | - [ ] Windows Supported 103 | - [x] Ratelimiting (New Connections) 104 | - [x] Linux Supported 105 | - [x] Windows Supported (Limited) 106 | - [ ] Chromium Web Bluetooth Support 107 | - [ ] iOS/Android Detection (Pairing) 108 | - [ ] Animations (Looking for ascii artists) 109 | 110 | 111 | # 🐬 Articles 112 | 113 | 114 | 115 | 121 | 127 | 133 | 134 |
116 | 117 | 118 | 119 |

Talking Sasquach - Wall of Flippers Busts Flipper Zero BLE Spammers Red Handed!

120 |
128 | 129 | 130 | 131 |

BleepingComputer - ‘Wall of Flippers’ detects Flipper Zero Bluetooth spam attacks

132 |
135 | 136 | 137 | 138 | # 🐬 Installing and Requirements 139 | 140 | > A few things are required to properly run Wall of Flippers. We Recommend a Raspberry Pi as it's compact and portable! It's also required to have a `chipset` or a USB `adapter` that supports Bluetooth Low Energy. At this current time, there is `limited` support for Wall of Flippers on Windows. Hence we recommend using a linux based operating system as that has been used for testing and development. For BLE advertising, I recommened an external USB adapter as the internal ble on the Raspberry Pi is not powerful enough to send BLE advertisements long range. 141 | 142 | 143 | 144 | ## How to install 145 | 146 | 147 | 148 |
149 | Debian Linux Install Guide 150 | 151 | 152 | ### Debian Linux Install Guide 153 | > Wall of Flippers on debian linux is currently one of the best ways to run Wall of Flippers. Mostly due to it being stable and having a lot of support for BTLE. To start off, is is highly recommened to follow all instructions we provide unless you know what you are doing. To get started, we need to set up the directory and install the required packages. 154 | 155 | ### Step 1 (One): Full system upgrade / update 156 | > Before we continue with the installation, we need to make sure our system is up to date. To do this update through the command line. 157 | 158 | sudo apt-get update && sudo apt-get upgrade -y 159 | 160 | ### Step 2 (Two): Git Clone and Git Installiation 161 | > To start off, we need to clone the repository and install the required packages. To do this, we need to run the following commands in the terminal. However, if you do not have git installed, you can simply install it by running this command (apt package manager only): 162 | 163 | sudo apt-get install git 164 | git clone https://www.github.com/K3YOMI/Wall-of-Flippers 165 | cd ./Wall-of-Flippers 166 | 167 | ### Step 3 (Three): Installing python (python3) 168 | > Installing python3 is required to run wall of flippers and installs it's dependencies. The command below will install python3 for you. 169 | 170 | sudo apt-get install python3 171 | sudo apt-get install python3-dev 172 | 173 | ### Step 4 (Four): Setting up and Installing the required packages (Multiple Ways) 174 | > Installing the required packets and dependencies can be done in three ways with this install. You can choose to use the terminal with the commands below, use requirements.txt, or you use the easy install script within Wall of Flippers. The choice is up to you depending on your preference. To get started with the terminal way. We will use these commands below. 175 | 176 | sudo apt-get install libglib2.0-dev 177 | sudo apt-get install python3-bluez 178 | python3 -m venv .venv 179 | source .venv/bin/activate 180 | ################## PACKAGES ######################## 181 | # requirement.txt method 182 | python3 -m pip install -r requirements.txt 183 | # command method 184 | python3 -m pip install bluepy 185 | python3 -m pip install git+https://github.com/pybluez/pybluez.git#egg=pybluez 186 | ################## PACKAGES ######################## 187 | deactivate 188 | 189 | > If you would like to use the easy install script, you can use the following commands below. 190 | 191 | bash wof.sh 192 | # You should get a prompt upon startup, about setting up a managed environment, feel free to let it do for you. Then once an environemnet is complete run `wof.sh` again 193 | and use the auto install option to install the dependencies. 194 | 195 | ### Step 5 (Five): Running Wall of Flippers 196 | > Once you have finished with all the dependencies and requirements, you can now run Wall of Flippers. To do this, you can run the following command below. 197 | 198 | bash wof.sh 199 | 200 | > Please keep note that running Wall of Flippers requires elevated privileges. Hence the `sudo` command. If you do not want to run Wall of Flippers with elevated privileges, you can run the following command below. 201 | 202 | sudo chmod +x WallofFlippers.py 203 | ./WallofFlippers.py 204 |
205 |
206 | Fedora Linux Install Guide 207 | 208 | ### Fedora Install Guide 209 | > To start off, is is highly recommened to follow all instructions we provide unless you know what you are doing. To get started, we need to set up the directory and install the required packages. 210 | 211 | ### Step 1 (One): Full system upgrade / update 212 | > Before we continue with the installation, we need to make sure our system is up to date. To do this update through the command line. 213 | 214 | sudo dnf update && sudo dnf upgrade -y 215 | ### Step 2 (Two): Git Clone and Git Installiation 216 | > To start off, we need to clone the repository and install the required packages. To do this, we need to run the following commands in the terminal. However, if you do not have git installed, you can simply install it by running this command (apt package manager only): 217 | 218 | sudo dnf install git 219 | git clone https://www.github.com/K3YOMI/Wall-of-Flippers 220 | cd ./Wall-of-Flippers 221 | 222 | ### Step 3 (Three): Installing python (python3) 223 | > Installing python3 is required to run wall of flippers and installs it's dependencies. The command below will install python3 for you. 224 | 225 | sudo dnf install python3 226 | sudo dnf install python3-dev 227 | 228 | ### Step 4 (Four): Setting up and Installing the required packages (Multiple Ways) 229 | > Installing the required packets and dependencies can be done in three ways with this install. You can choose to use the terminal with the commands below, use requirements.txt, or you use the easy install script within Wall of Flippers. The choice is up to you depending on your preference. To get started with the terminal way. We will use these commands below. 230 | 231 | sudo dnf install glib2-devel 232 | sudo dnf install python3-bluez 233 | python3 -m venv .venv 234 | source .venv/bin/activate 235 | ################## PACKAGES ######################## 236 | # requirement.txt method 237 | python3 -m pip install -r requirements.txt 238 | # command method 239 | python3 -m pip install bluepy 240 | python3 -m pip install git+https://github.com/pybluez/pybluez.git#egg=pybluez 241 | ################## PACKAGES ######################## 242 | deactivate 243 | 244 | > If you would like to use the easy install script, you can use the following commands below. 245 | 246 | bash wof.sh 247 | # You should get a prompt upon startup, about setting up a managed environment, feel free to let it do for you. Then once an environemnet is complete run `wof.sh` again 248 | and use the auto install option to install the dependencies. 249 | 250 | ### Step 5 (Five): Running Wall of Flippers 251 | > Once you have finished with all the dependencies and requirements, you can now run Wall of Flippers. To do this, you can run the following command below. 252 | 253 | bash wof.sh 254 | 255 | > Please keep note that running Wall of Flippers requires elevated privileges. Hence the `sudo` command. If you do not want to run Wall of Flippers with elevated privileges, you can run the following command below. 256 | 257 | sudo chmod +x WallofFlippers.py 258 | ./WallofFlippers.py 259 | 260 | 261 |
262 | 263 |
264 | Windows Install Guide 265 | 266 | ## Windows Install Guide 267 | > Windows is currently not fully supported. However, you can still run Wall of Flippers on Windows. A few missing features like the ability to detect advertisement attacks and ability to send advertisements. You also miss out on validation features to ensure that you won't have spoofed flipper data. However the detection of the Flipper Zero device is still supported. To get started, we will need to clone the repository and install the required packages. To do this, we need to run the following commands in the command prompt. However, if you do not have git installed, you can simply install it by downloading it from the official website. 268 | 269 | ### Step 1 (One): Git Clone and Git Installiation 270 | 271 | 272 | Download Link: https://git-scm.com/downloads 273 | 274 | > Once you have downloaded git, you can now run the following commands below. 275 | 276 | git clone https://www.github.com/K3YOMI/Wall-of-Flippers 277 | cd ./Wall-of-Flippers 278 | 279 | 280 | ### Step 2 (Two): Installing python and pip (python / pip) 281 | > This step is quite straightforward as we will be installing python and pip. To do this, we will need to download the latest version of python from the official website. Once you have downloaded the installer, you can run it and install python. Please make sure to check the box that says `Add Python to PATH`. This will allow you to run python from the command prompt. Once you have installed python, you can now install the required packages. 282 | 283 | Download Link: https://www.python.org/downloads/ 284 | 285 | > Once you have installed python, you can now install the required packages. To do this, we will need to run the following commands below. 286 | 287 | pip install bleak 288 | 289 | 290 | > Alternatively, you can use the requirements.txt file to install the required packages. To do this, we will need to run the following commands below. 291 | 292 | pip install -r requirements.txt 293 | 294 | > If you would like to use the easy install script, you can use the following commands below. 295 | 296 | python WallofFlippers.py 297 | # You should get a prompt upon startup, use the auto install option to install the dependencies. for the easy install and follow the directions and prompts for the install. 298 | 299 | > If you are having issues with pip being not recognized as a command, please refer to this question below:\ 300 | https://stackoverflow.com/questions/23708898/pip-is-not-recognized-as-an-internal-or-external-command 301 | 302 | ### Step 3 (Three): Running Wall of Flippers 303 | > Once you have finished with all the dependencies and requirements, you can now run Wall of Flippers. To do this, you can run the following command below. 304 | 305 | python WallofFlippers.py 306 | 307 | > Please keep note that this is a watered down version of Wall of Flippers. Hence the lack of features. If you would like to run the full version of Wall of Flippers, please refer to the Linux Install Guide above. 308 | 309 | 310 |
311 | 312 | # Headless Usage 313 | > Wall of Flippers now supports the use of a command only interface. Thanks to @cyberartemio for the recommendation. The commands below can be used to automate the use the Wall of Flippers. Whether that be for systemd or just basic general automation. (If you are running/using a virtual environment, make sure to source to be able to use WallofFlippers.py) 314 | 315 | usage: WalloFlippers.py [-h] [-w] [i] [-d DEVICE] 316 | options: 317 | -h, --help Help Message 318 | -w, --wall Wall of Flippers 319 | -i, --install Install Dependencies 320 | -b, --badgemode Toggle Badge Mode 321 | -a, --advertise Advertise WoF Exsistance (OFF=Default) 322 | -d DEVICE, --device DEVICE A bluetooth device (External/Internal) 323 | 324 | # Issues and Fixes 325 | > If you encounter any issues or bugs, please report them to us on our github page. We will try our best to fix them as soon as possible. If you would like to contribute to the project, please feel free to make a pull request. We will review it and merge it if it is a good addition to the project. We will be starting a discord server soon for support and development. Please keep an eye out for that. Thank you for your support and we hope you enjoy this project! <3 326 | 327 | 328 | # Common Errors and Fixes 329 | ### No such file or directory /sys/class/bluetooth 330 | > If the `/sys/class/bluetooth` directory is not present on your system, it may indicate that the Bluetooth subsystem is not properly detected or enabled. To check if you have the right hardware, please run 331 | 332 | sudo service bluetooth status 333 | 334 | > If the status is `dead` you may not have a valid bluetooth chipset or adapter present. If `inactive`, you can enable the service using this command 335 | 336 | sudo service bluetooth restart 337 | 338 | ### pybluez failing to properly install 339 | > If you're experiencing issues while installing `pybluez`, make sure to install the python-dev package. For further documentation (https://pybluez.readthedocs.io/en/latest/install.html) 340 | 341 | 342 | # Related Projects 343 | > A list of repositories that have integrated Wall of Flippers (WoF) into their projects. 344 | 345 | **Pwnagotchi plugin**\ 346 | *Written by: cyberartemio*\ 347 | https://github.com/cyberartemio/wof-pwnagotchi-plugin 348 | 349 | **Evil-M5Core2**\ 350 | *Written by: 7h30th3r0n3*\ 351 | https://github.com/7h30th3r0n3/Evil-M5Core2 352 | 353 | 354 | # Notice 355 | > This project isn't the solution to combat the Flipper Zero device or any form of btle attacks. **THIS DOES NOT MITIGATE OR STOP ANYTHING**!!! However, the flipper zero device is a great tool for learning and understanding the inctracies of the cyberworld. Now for the detections for this project, we heavily rely on the advertisements that the Flipper Zero sends out for detection. While a user can do many things to avoid being detected by Wall of Flippers. (Depending if the Identifier method gets worked around) We highly advise using this project for an end all solution. While not all bluetooth attacks are sent from only the flipper, it's a good start to understand the world of bluetooth and the attacks that can be accomplished with simple devices. We hope you enjoy this project and we hope you take the time to learn and build off of this. We are always looking for contributions and new ideas. Thank you for looking at this project and we hope you enjoy it! -k3yomi 356 | --------------------------------------------------------------------------------