Compare commits

..

54 Commits

Author SHA1 Message Date
17f04f7506 Merge pull request #318
Added DNS check to plugins command
2025-01-27 10:24:45 +01:00
76c4888e88 Merge pull request #319
Update gdrivesync.py
2025-01-27 10:23:58 +01:00
d44b8d02b6 Merge remote-tracking branch 'origin/noai' into noai 2025-01-27 10:23:05 +01:00
02454597b2 Version 2.9.5.3
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-27 10:22:55 +01:00
cb207d4d70 Update gdrivesync.py
Updated backup items

Signed-off-by: findingmoist <128169791+findingmoist@users.noreply.github.com>
2025-01-25 20:06:15 -05:00
7b9150af6b Merge branch 'jayofelony:noai' into noai 2025-01-25 23:02:21 +00:00
8787c1bdd3 Update cmd.py
Signed-off-by: wpa-2 <9049886+wpa-2@users.noreply.github.com>
2025-01-25 23:02:06 +00:00
ec93838b7a Update defaults.toml 2025-01-25 17:26:31 +01:00
f6d5a481bb Removed unused import
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-24 23:48:38 +01:00
eee3eb962b Small changes on pisugarx.py
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-24 23:48:25 +01:00
847e9f5908 Enable auto-update by default now.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-24 23:47:58 +01:00
af1544535c Merge pull request #316 from wlmh110/noai
Update pisugarx.py
2025-01-24 09:41:50 +01:00
0fbf209881 Update pisugarx.py
Complete the basic information retrieval for the PiSugar power module without the need for additional installation of PiSugar-server:
1. Automatically determine the model of PiSugar in use.
2. Automatically initialize the power module.
3. Retrieve the battery level and charging status.
2025-01-24 15:16:51 +08:00
cf14f3f663 Another fix for bt-tether
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-21 17:30:06 +01:00
948fe89ce6 Another fix for bt-tether
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-21 14:37:02 +01:00
8d1a5babe8 Version 2.9.5.2 will be next
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-20 21:30:58 +01:00
042d5ba765 Change in defaults for PwnDroid.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-20 21:13:17 +01:00
58857058a4 Revert "PwnDroid, new plugin for the companion app on Android to share GPS data from your Android phone."
This reverts commit 0e06d3bd76.
2025-01-20 21:11:40 +01:00
5e6443ae58 Merge remote-tracking branch 'origin/noai' into noai 2025-01-20 21:11:14 +01:00
0e06d3bd76 PwnDroid, new plugin for the companion app on Android to share GPS data from your Android phone.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-20 21:10:46 +01:00
64f7c6e1e5 Merge pull request #311 from fmatray/noai
Web redirections for GRID, OHCAPI and Wigle
2025-01-20 19:18:10 +01:00
8442ce93be Update ohcapi.py
version update

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-20 15:43:55 +01:00
730fa7dc8e Update wigle.py
version update

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-20 15:43:35 +01:00
0959140098 Update grid.py
Rediction to https://opwngrid.xyz in the plugins' page

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-20 15:41:49 +01:00
7c4764bff8 Update ohcapi.py
add a web hook to redirect to onlinehashcrack

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-20 15:30:00 +01:00
e179165850 Update wigle.py
Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-20 15:28:46 +01:00
0140a1fc97 Update wigle.py
add a redirection to wiggle.net

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-20 15:27:54 +01:00
cb0c6c6e44 Quick release, because of error in bt-tether plugin.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-18 10:30:52 +01:00
0ceeb94111 https://github.com/jayofelony/pwnagotchi/issues/300
It needed a comma at the end of the line. Silly me.

Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-18 10:29:41 +01:00
6bf50a36dd Merge remote-tracking branch 'origin/noai' into noai 2025-01-18 10:28:09 +01:00
fb0fda4d0d https://github.com/jayofelony/pwnagotchi/issues/300
Revert, as this is the correct method.

Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-18 10:27:59 +01:00
51f86d0286 Merge pull request #302 from fmatray/noai
Update ohcapi.py
2025-01-16 07:37:10 +01:00
614e844a51 https://github.com/jayofelony/pwnagotchi/issues/300
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-15 21:58:15 +01:00
fd16b2c94d Update ohcapi.py
catch an exception when internet is not available

Signed-off-by: Frédéric <fmatray@users.noreply.github.com>
2025-01-13 15:32:48 +01:00
a4d5930477 Set ohcapi to false
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-12 22:36:42 +01:00
f9b2a76665 Change bettercap to my own repo
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-12 18:23:38 +01:00
80279ca02b Increased extra_channels for auto-tune so hopefully multiple pwns can see each other better.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-10 20:39:02 +01:00
c7a06a4e43 Add checks for non empty config
Add manual DNS 8.8.8.8/1.1.1.1

Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-06 21:50:18 +01:00
9b969375b7 Next release will be 2.9.5
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-06 12:09:15 +01:00
61eff05227 Removal of onlinehashcrack.py, we now have ohcapi.py instead. This uses the new API of OHC.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-06 12:08:38 +01:00
21151dd9ef Merge pull request #292 from rohanday3/rohanday3-patch-1
Add OHC API plugin
2025-01-06 11:23:07 +01:00
cd3eb0963b Update defaults.toml
Signed-off-by: Rohan Dayaram <rohanday4@gmail.com>
2025-01-06 10:53:34 +02:00
a41a8a277f Create ohcapi.py
Signed-off-by: Rohan Dayaram <rohanday4@gmail.com>
2025-01-06 10:48:46 +02:00
4b7a3bc138 Create pwndroid_privacy_policy
Signed-off-by: Jayofelony <oudshoorn.jeroen@gmail.com>
2025-01-05 20:16:51 +01:00
6fa6ca8a67 If MAC is left empty, we don't write it to config.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-05 19:19:43 +01:00
6b5631373a Update for better wifi channel calculation
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-04 17:54:26 +01:00
de5cd8705f Update for better wifi channel calculation
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-04 15:55:19 +01:00
a317528aeb Update bug report template
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-04 09:29:23 +01:00
1ec6931740 Update bug report template
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-04 09:28:08 +01:00
45138c7fae Update bug report template
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-04 09:25:49 +01:00
dcd91268a1 Update bug report template
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-04 09:25:06 +01:00
27c8a113de Make webui login optional.
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-03 12:04:17 +01:00
9ea6728a26 2.9.4-2
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-02 22:30:25 +01:00
af4e4d9a35 Another wpa-sec fix
Signed-off-by: Jeroen Oudshoorn <oudshoorn.jeroen@gmail.com>
2025-01-02 22:30:04 +01:00
19 changed files with 906 additions and 207 deletions

View File

@ -33,12 +33,19 @@ body:
label: Version
description: What version of our software are you running?
options:
- 2.9.3
- 2.9.3-2
- 2.9.4
- 2.9.4-2
default: 0
validations:
required: true
- type: dropdown
id: 3rd-party-hardware
attributes:
label: 3rd Party Hardware
description: Are you using any 3rd party hardware? By selecting "Yes", you agree that you have tested the issue without the 3rd party hardware. And acknowledge that the issue may be related to the 3rd party hardware, for which we cannot provide full support.
options:
- "Yes"
- "No"
default: 1
- type: textarea
id: logs
attributes:

View File

@ -33,12 +33,19 @@ body:
label: Version
description: What version of our software are you running?
options:
- 2.9.3
- 2.9.3-2
- 2.9.4
- 2.9.4-2
default: 0
validations:
required: true
- type: dropdown
id: 3rd-party-hardware
attributes:
label: 3rd Party Hardware
description: Are you using any 3rd party hardware? By selecting "Yes", you agree that you have tested the issue without the 3rd party hardware. And acknowledge that the issue may be related to the 3rd party hardware, for which we cannot provide full support.
options:
- "Yes"
- "No"
default: 1
- type: textarea
id: logs
attributes:

View File

@ -1 +1 @@
__version__ = '2.9.4-1'
__version__ = '2.9.5.3'

View File

@ -208,6 +208,7 @@ def pwnagotchi_cli():
ssid = input("SSID (Name): ")
bssid = input("BSSID (MAC): ")
f.write(f"\t\"{ssid}\",\n")
if bssid != "":
f.write(f"\t\"{bssid}\",\n")
f.write("]\n")
# set bluetooth tether

View File

@ -19,8 +19,8 @@ main.custom_plugins = "/usr/local/share/pwnagotchi/custom-plugins/"
main.plugins.auto-tune.enabled = true
main.plugins.auto-update.enabled = false
main.plugins.auto-update.install = false
main.plugins.auto-update.enabled = true
main.plugins.auto-update.install = true
main.plugins.auto-update.interval = 1
main.plugins.bt-tether.enabled = false
@ -54,10 +54,13 @@ main.plugins.memtemp.enabled = false
main.plugins.memtemp.scale = "celsius"
main.plugins.memtemp.orientation = "horizontal"
main.plugins.onlinehashcrack.enabled = false
main.plugins.onlinehashcrack.email = ""
main.plugins.onlinehashcrack.dashboard = ""
main.plugins.onlinehashcrack.single_files = false
main.plugins.ohcapi.enabled = false
main.plugins.ohcapi.api_key = "sk_your_api_key_here"
main.plugins.ohcapi.receive_email = "yes"
main.plugins.pwndroid.enabled = false
main.plugins.pwndroid.display = false # show coords on display
main.plugins.pwndroid.display_altitude = false # show altitude on display
main.plugins.pisugarx.enabled = false
main.plugins.pisugarx.rotation = false
@ -158,8 +161,9 @@ ui.faces.position_y = 34
ui.web.enabled = true
ui.web.address = "::" # listening on both ipv4 and ipv6 - switch to 0.0.0.0 to listen on just ipv4
ui.web.username = "changeme"
ui.web.password = "changeme"
ui.web.auth = false
ui.web.username = "changeme" # if auth is true
ui.web.password = "changeme" # if auth is true
ui.web.origin = ""
ui.web.port = 8080
ui.web.on_frame = ""

View File

@ -16,8 +16,10 @@ def freq_to_channel(freq: float) -> int:
return 14
# 5 GHz Wi-Fi channels
elif 5150 <= freq <= 5850: # 5 GHz Wi-Fi
if freq < 5270: # Channels 36-48
if 5150 <= freq <= 5350: # Channels 36-64
return int(((freq - 5180) / 20) + 36)
elif 5470 <= freq <= 5725: # Channels 100-144
return int(((freq - 5500) / 20) + 100)
else: # Channels 149-165
return int(((freq - 5745) / 20) + 149)
# 6 GHz Wi-Fi channels

View File

@ -1,10 +1,9 @@
# Handles the commandline stuff
import os
import logging
import glob
import re
import shutil
import socket # <-- Added for DNS check
from fnmatch import fnmatch
from pwnagotchi.utils import download_file, unzip, save_config, parse_version, md5
from pwnagotchi.plugins import default_path
@ -348,12 +347,34 @@ def _analyse_dir(path):
return results
def _check_internet():
"""
Simple DNS check to verify that we can resolve a common hostname.
Returns True if DNS resolution succeeds, False otherwise.
"""
try:
socket.gethostbyname('google.com')
return True
except:
return False
def update(config):
"""
Updates the database
"""
global SAVE_DIR
if not _check_internet():
logging.error("No internet connection or DNS not working. Please follow these instructions:")
logging.error("https://github.com/jayofelony/pwnagotchi/wiki/Step-2-Connecting")
print("No internet/DNS. Please follow these instructions:")
print("https://github.com/jayofelony/pwnagotchi/wiki/Step-2-Connecting")
return 1
else:
logging.info("Internet detected - Please run sudo pwnagotchi plugins list")
print("Internet detected - Please run sudo pwnagotchi plugins list")
urls = config['main']['custom_plugin_repos']
if not urls:
logging.info('No plugin repositories configured.')
@ -393,3 +414,4 @@ def update(config):
logging.error('Error while updating plugins: %s', ex)
rc = 1
return rc

View File

@ -374,7 +374,7 @@ class auto_tune(plugins.Plugin):
try:
defaults = {'show_hidden': False,
'reset_history': True,
'extra_channels': 3,
'extra_channels': 15,
}
for d in defaults:

View File

@ -208,7 +208,7 @@ class AutoUpdate(plugins.Plugin):
to_install = []
to_check = [
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
('jayofelony/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
('jayofelony/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
('jayofelony/pwnagotchi', pwnagotchi.__version__, False, 'pwnagotchi')
]

View File

@ -20,6 +20,8 @@ class BTTether(plugins.Plugin):
logging.info("[BT-Tether] plugin loaded.")
def on_config_changed(self, config):
if any(self.options[key] == '' for key in ['phone', 'phone-name', 'ip', 'mac']):
self.ready = False
ip = self.options['ip']
mac = self.options['mac']
phone_name = self.options['phone-name'] + ' Network'
@ -39,7 +41,8 @@ class BTTether(plugins.Plugin):
'bluetooth.type', 'panu',
'bluetooth.bdaddr', f'{mac}',
'ipv4.method', 'manual',
'ipv4.addresses', f'{address}',
'ipv4.dns', '8.8.8.8 1.1.1.1',
'ipv4.addresses', f'{address}/24',
'ipv4.gateway', f'{gateway}',
'ipv4.route-metric', '100'
], check=True)
@ -47,9 +50,12 @@ class BTTether(plugins.Plugin):
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
except Exception as e:
logging.error(f"[BT-Tether] Failed to connect to device: {e}")
logging.error(f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?")
self.ready = True
def on_ready(self, agent):
if any(self.options[key] == '' for key in ['phone', 'phone-name', 'ip', 'mac']):
self.ready = False
self.ready = True
def on_ui_setup(self, ui):
@ -59,6 +65,7 @@ class BTTether(plugins.Plugin):
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
if self.ready:
phone_name = self.options['phone-name'] + ' Network'
if (subprocess.run(['bluetoothctl', 'info'], capture_output=True, text=True)).stdout.find('Connected: yes') != -1:
self.status = 'C'
@ -67,8 +74,10 @@ class BTTether(plugins.Plugin):
try:
subprocess.run(['nmcli', 'connection', 'up', f'{phone_name}'], check=True)
except Exception as e:
logging.error(f"[BT-Tether] Failed to connect to device: {e}")
logging.debug(f"[BT-Tether] Failed to connect to device: {e}")
logging.error(f"[BT-Tether] Failed to connect to device: have you enabled bluetooth tethering on your phone?")
ui.set('bluetooth', self.status)
return
def on_unload(self, ui):
phone_name = self.options['phone-name'] + ' Network'

View File

@ -13,8 +13,8 @@ import zipfile
class GdriveSync(plugins.Plugin):
__author__ = '@jayofelony'
__version__ = '1.2'
__author__ = '@jayofelony & Moist'
__version__ = '1.4'
__license__ = 'GPL3'
__description__ = 'A plugin to backup various pwnagotchi files and folders to Google Drive. Once every hour from loading plugin.'
@ -26,12 +26,15 @@ class GdriveSync(plugins.Plugin):
self.status = StatusFile('/root/.gdrive-backup')
self.backup = True
self.backupfiles = [
'/root/brain.nn',
'/root/brain.json',
'/root/.api-report.json',
'/root/handshakes',
'/home/pi/handshakes',
'/root/peers',
'/etc/pwnagotchi'
'/etc/pwnagotchi',
'.etc/profile/',
'/usr/local/share/pwnagotchi/custom-plugins',
'/boot/firmware/config.txt',
'/boot/firmware/cmdline.txt'
]
def on_loaded(self):

View File

@ -44,7 +44,7 @@ def parse_pcap(filename):
class Grid(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.1'
__version__ = '1.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
'networks to opwngrid.xyz '
@ -69,6 +69,11 @@ class Grid(plugins.Plugin):
def on_loaded(self):
logging.info("grid plugin loaded.")
def on_webhook(self, path, request):
from flask import make_response, redirect
response = make_response(redirect("https://opwngrid.xyz", code=302))
return response
def set_reported(self, reported, net_id):
if net_id not in reported:
reported.append(net_id)

View File

@ -0,0 +1,233 @@
import os
import logging
import requests
import time
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
from json.decoder import JSONDecodeError
class ohcapi(plugins.Plugin):
__author__ = 'Rohan Dayaram'
__version__ = '1.1.0'
__license__ = 'GPL3'
__description__ = 'Uploads WPA/WPA2 handshakes to OnlineHashCrack.com using the new API (V2), no dashboard.'
def __init__(self):
self.ready = False
self.lock = Lock()
try:
self.report = StatusFile('/root/handshakes/.ohc_uploads', data_format='json')
except JSONDecodeError:
os.remove('/root/.ohc_newapi_uploads')
self.report = StatusFile('/root/handshakes/.ohc_uploads', data_format='json')
self.skip = list()
self.last_run = 0 # Track last time periodic tasks were run
self.internet_active = False # Track whether internet is currently available
def on_loaded(self):
"""
Called when the plugin is loaded.
"""
required_fields = ['api_key']
missing = [field for field in required_fields if field not in self.options or not self.options[field]]
if missing:
logging.error(f"OHC NewAPI: Missing required config fields: {missing}")
return
if 'receive_email' not in self.options:
self.options['receive_email'] = 'yes' # default
if 'sleep' not in self.options:
self.options['sleep'] = 60*60 # default to 1 hour
self.ready = True
logging.info("OHC NewAPI: Plugin loaded and ready.")
def on_webhook(self, path, request):
from flask import make_response, redirect
response = make_response(redirect("https://www.onlinehashcrack.com", code=302))
return response
def on_internet_available(self, agent):
"""
Called once when the internet becomes available.
Run upload/download tasks immediately.
"""
if not self.ready or self.lock.locked():
return
self.internet_active = True
self._run_tasks(agent) # Run immediately when internet is detected
self.last_run = time.time() # Record the time of this run
def on_ui_update(self, ui):
"""
Called periodically by the UI. We will use this event to run tasks every 60 seconds if internet is still available.
"""
if not self.ready:
return
# Attempt to get agent from ui
agent = getattr(ui, '_agent', None)
if agent is None:
return
# Check if the internet is still available by pinging Google
try:
response = requests.get('https://www.google.com', timeout=5)
except requests.ConnectionError:
self.internet_active = False
return
if response.status_code == 200:
self.internet_active = True
else:
self.internet_active = False
return
current_time = time.time()
if current_time - self.last_run >= self.options['sleep']:
self._run_tasks(agent)
self.last_run = current_time
def _extract_essid_bssid_from_hash(self, hash_line):
parts = hash_line.strip().split('*')
essid = 'unknown_ESSID'
bssid = '00:00:00:00:00:00'
if len(parts) > 5:
essid_hex = parts[5]
try:
essid = bytes.fromhex(essid_hex).decode('utf-8', errors='replace')
except:
essid = 'unknown_ESSID'
if len(parts) > 3:
apmac = parts[3]
if len(apmac) == 12:
bssid = ':'.join(apmac[i:i+2] for i in range(0, 12, 2))
if essid == 'unknown_ESSID' or bssid == '00:00:00:00:00:00':
logging.debug(f"OHC NewAPI: Failed to extract ESSID/BSSID from hash -> {hash_line}")
return essid, bssid
def _run_tasks(self, agent):
"""
Encapsulates the logic of extracting, uploading, and updating tasks.
"""
with self.lock:
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=[])
processed_stations = self.report.data_field_or('processed_stations', default=[])
handshake_dir = config['bettercap']['handshakes']
# Find .pcap files
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename)
for filename in handshake_filenames if filename.endswith('.pcap')]
# If the corresponding .22000 file exists, skip re-upload
handshake_paths = [p for p in handshake_paths if not os.path.exists(p.replace('.pcap', '.22000'))]
# Filter out already reported and skipped .pcap files
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info(f"OHC NewAPI: Processing {len(handshake_new)} new PCAP handshakes.")
all_hashes = []
successfully_extracted = []
essid_bssid_map = {}
for idx, pcap_path in enumerate(handshake_new):
hashes = self._extract_hashes_from_handshake(pcap_path)
if hashes:
# Extract ESSID and BSSID from the first hash line
essid, bssid = self._extract_essid_bssid_from_hash(hashes[0])
if (essid, bssid) in processed_stations:
logging.debug(f"OHC NewAPI: Station {essid}/{bssid} already processed, skipping {pcap_path}.")
self.skip.append(pcap_path)
continue
all_hashes.extend(hashes)
successfully_extracted.append(pcap_path)
essid_bssid_map[pcap_path] = (essid, bssid)
else:
logging.debug(f"OHC NewAPI: No hashes extracted from {pcap_path}, skipping.")
self.skip.append(pcap_path)
# Now upload all extracted hashes
if all_hashes:
batches = [all_hashes[i:i+50] for i in range(0, len(all_hashes), 50)]
upload_success = True
for batch_idx, batch in enumerate(batches):
display.on_uploading(f"onlinehashcrack.com ({(batch_idx+1)*50}/{len(all_hashes)})")
if not self._add_tasks(batch):
upload_success = False
break
if upload_success:
# Mark all successfully extracted pcaps as reported
for pcap_path in successfully_extracted:
reported.append(pcap_path)
essid, bssid = essid_bssid_map[pcap_path]
processed_stations.append((essid, bssid))
self.report.update(data={'reported': reported, 'processed_stations': processed_stations})
logging.debug("OHC NewAPI: Successfully reported all new handshakes.")
else:
# Upload failed, skip these pcaps for future attempts
for pcap_path in successfully_extracted:
self.skip.append(pcap_path)
logging.debug("OHC NewAPI: Failed to upload tasks, added to skip list.")
else:
logging.debug("OHC NewAPI: No hashes were extracted from the new pcaps. Nothing to upload.")
display.on_normal()
else:
logging.debug("OHC NewAPI: No new PCAP files to process.")
def _add_tasks(self, hashes, timeout=30):
clean_hashes = [h.strip() for h in hashes if h.strip()]
if not clean_hashes:
return True # No hashes to add is success
payload = {
'api_key': self.options['api_key'],
'agree_terms': "yes",
'action': 'add_tasks',
'algo_mode': 22000,
'hashes': clean_hashes,
'receive_email': self.options['receive_email']
}
try:
result = requests.post('https://api.onlinehashcrack.com/v2',
json=payload,
timeout=timeout)
result.raise_for_status()
data = result.json()
logging.info(f"OHC NewAPI: Add tasks response: {data}")
return True
except requests.exceptions.RequestException as e:
logging.debug(f"OHC NewAPI: Exception while adding tasks -> {e}")
return False
def _extract_hashes_from_handshake(self, pcap_path):
hashes = []
hcxpcapngtool = '/usr/bin/hcxpcapngtool'
hccapx_path = pcap_path.replace('.pcap', '.22000')
hcxpcapngtool_cmd = f"{hcxpcapngtool} -o {hccapx_path} {pcap_path}"
os.popen(hcxpcapngtool_cmd).read()
if os.path.exists(hccapx_path) and os.path.getsize(hccapx_path) > 0:
logging.debug(f"OHC NewAPI: Extracted hashes from {pcap_path}")
with open(hccapx_path, 'r') as hccapx_file:
hashes = hccapx_file.readlines()
else:
logging.debug(f"OHC NewAPI: Failed to extract hashes from {pcap_path}")
if os.path.exists(hccapx_path):
os.remove(hccapx_path)
return hashes

View File

@ -1,147 +0,0 @@
import os
import csv
import logging
import re
import requests
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile, remove_whitelisted
import pwnagotchi.plugins as plugins
from json.decoder import JSONDecodeError
class OnlineHashCrack(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.1.0'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
def __init__(self):
self.ready = False
try:
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
except JSONDecodeError:
os.remove('/root/.ohc_uploads')
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
self.lock = Lock()
self.options = dict()
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
if 'email' not in self.options or ('email' in self.options and not self.options['email']):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
return
self.ready = True
logging.info("OHC: OnlineHashCrack plugin loaded.")
def _upload_to_ohc(self, path, timeout=30):
"""
Uploads the file to onlinehashcrack.com
"""
with open(path, 'rb') as file_to_upload:
data = {'email': self.options['email']}
payload = {'file': file_to_upload}
try:
result = requests.post('https://api.onlinehashcrack.com',
data=data,
files=payload,
timeout=timeout)
if 'already been sent' in result.text:
logging.debug(f"{path} was already uploaded.")
except requests.exceptions.RequestException as e:
logging.debug(f"OHC: Got an exception while uploading {path} -> {e}")
raise e
def _download_cracked(self, save_file, timeout=120):
"""
Downloads the cracked passwords and saves them
returns the number of downloaded passwords
"""
try:
s = requests.Session()
dashboard = s.get(self.options['dashboard'], timeout=timeout)
result = s.get('https://www.onlinehashcrack.com/wpa-exportcsv', timeout=timeout)
result.raise_for_status()
with open(save_file, 'wb') as output_file:
output_file.write(result.content)
except requests.exceptions.RequestException as req_e:
raise req_e
except OSError as os_e:
raise os_e
def on_webhook(self, path, request):
import requests
from flask import redirect
s = requests.Session()
s.get('https://www.onlinehashcrack.com/dashboard')
r = s.post('https://www.onlinehashcrack.com/dashboard', data={'emailTasks': self.options['email'], 'submit': ''})
return redirect(r.url, code=302)
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
"""
if not self.ready or self.lock.locked():
return
with self.lock:
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
filename.endswith('.pcap')]
# pull out whitelisted APs
handshake_paths = remove_whitelisted(handshake_paths, config['main']['whitelist'])
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onlinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.on_uploading(f"onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
try:
self._upload_to_ohc(handshake)
if handshake not in reported:
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.debug(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.debug("OHC: %s", req_e)
continue
except OSError as os_e:
self.skip.append(handshake)
logging.debug("OHC: %s", os_e)
continue
display.on_normal()
if 'dashboard' in self.options and self.options['dashboard']:
cracked_file = os.path.join(handshake_dir, 'onlinehashcrack.cracked')
if os.path.exists(cracked_file):
last_check = datetime.fromtimestamp(os.path.getmtime(cracked_file))
if last_check is not None and ((datetime.now() - last_check).seconds / (60 * 60)) < 1:
return
try:
self._download_cracked(cracked_file)
logging.info("OHC: Downloaded cracked passwords.")
except requests.exceptions.RequestException as req_e:
logging.debug("OHC: %s", req_e)
except OSError as os_e:
logging.debug("OHC: %s", os_e)
if 'single_files' in self.options and self.options['single_files']:
with open(cracked_file, 'r') as cracked_list:
for row in csv.DictReader(cracked_list):
if row['password']:
filename = re.sub(r'[^a-zA-Z0-9]', '', row['ESSID']) + '_' + row['BSSID'].replace(':','')
if os.path.exists( os.path.join(handshake_dir, filename+'.pcap')):
with open(os.path.join(handshake_dir, filename+'.pcap.cracked'), 'w') as f:
f.write(row['password'])

View File

@ -6,9 +6,405 @@ import pwnagotchi.ui.fonts as fonts
import pwnagotchi.plugins as plugins
import pwnagotchi
import time
from pisugar import *
import smbus
from flask import abort
from flask import render_template_string
from collections import deque
import threading
PiSugar_addresses = {
"PiSugar2": 0x75, # PiSugar2\2Plus
"PiSugar3": 0x57, # PiSugar3\3Plus
"PiSugar2 RTC": 0x32 # PiSugar2\2Plus RTC
}
curve1200 = [
(4.16, 100.0),
(4.05, 95.0),
(4.00, 80.0),
(3.92, 65.0),
(3.86, 40.0),
(3.79, 25.5),
(3.66, 10.0),
(3.52, 6.5),
(3.49, 3.2),
(3.1, 0.0),
]
curve1200_3= [
(4.2, 100.0), # 高电量阶段 (100%)
(4.0, 80.0), # 中电量阶段 (80%)
(3.7, 60.0), # 中电量阶段 (60%)
(3.5, 20.0), # 低电量阶段 (20%)
(3.1, 0.0) # 电量耗尽 (0%)
]
curve5000 = [
(4.10, 100.0),
(4.05, 95.0),
(3.90, 88.0),
(3.80, 77.0),
(3.70, 65.0),
(3.62, 55.0),
(3.58, 49.0),
(3.49, 25.6),
(3.32, 4.5),
(3.1, 0.0),
]
class PiSugarServer:
def __init__(self):
"""
PiSugar initialization, if unable to connect to any version of PiSugar, return false
"""
self._bus = smbus.SMBus(1)
self.modle = None
self.i2creg = []
self.address = 0
self.battery_voltage = 0
self.voltage_history = deque(maxlen=10)
self.battery_level = 0
self.battery_charging = 0
self.temperature = 0
self.power_plugged = False
self.allow_charging = True
while self.modle is None:
if self.check_device(PiSugar_addresses["PiSugar2"]) is not None:
self.address = PiSugar_addresses["PiSugar2"]
if self.check_device(PiSugar_addresses["PiSugar2"], 0Xc2) != 0:
self.modle = "PiSugar2Plus"
else:
self.modle = "PiSugar2"
self.device_init()
elif self.check_device(PiSugar_addresses["PiSugar3"]) is not None:
self.modle = 'PiSugar3'
self.address = PiSugar_addresses["PiSugar3"]
else:
self.modle = None
logging.error(
"No PiSugar device was found. Please check if the PiSugar device is powered on.")
time.sleep(5)
# self.update_value()
self.start_timer()
while len(self.i2creg) < 256:
time.sleep(1)
def start_timer(self):
# 创建一个线程来执行定时函数
timer_thread = threading.Thread(target=self.update_value)
timer_thread.daemon = True # 设置为守护线程,主程序退出时自动结束
timer_thread.start()
def update_value(self):
"""每三秒更新pisugar状态包括触发自动关机"""
while True:
try:
self.i2creg = []
for i in range(0, 256, 32):
# 计算当前读取的起始寄存器地址
current_register = 0 + i
# 计算当前读取的数据长度
current_length = min(32, 256 - i)
# 读取数据块
chunk = self._bus.read_i2c_block_data(
self.address, current_register, current_length)
# 将读取的数据块添加到结果列表中
self.i2creg.extend(chunk)
time.sleep(0.1)
logging.debug(f"Data length: {len(self.i2creg)}")
logging.debug(f"Data: {self.i2creg}")
if self.modle == 'PiSugar3':
low = self.i2creg[0x23]
high = self.i2creg[0x22]
self.battery_voltage = (((high << 8) + low) / 1000)
self.temperature = self.i2creg[0x04]-40
ctr1 = self.i2creg[0x02] # 读取控制寄存器 1
self.power_plugged = (ctr1 & (1 << 7)) != 0 # 检查电源是否插入
self.allow_charging = (ctr1 & (1 << 6)) != 0 # 检查是否允许充电
elif self.modle == 'PiSugar2':
high = self.i2creg[0xa3]
low = self.i2creg[0xa2]
self.battery_voltage = (2600.0 - (((high | 0b11000000) << 8) + low) * 0.26855) / \
1000.0 if high & 0x20 else (
2600.0 + (((high & 0x1f) << 8) + low) * 0.26855) / 1000.0
self.power_plugged = (self.i2creg[0x55] & 0b00010000) != 0
elif self.modle == 'PiSugar2Plus':
low = self.i2creg[0xd0]
high = self.i2creg[0xd1]
self.battery_voltage = (
(((high & 0b00111111) << 8) + low) * 0.26855 + 2600.0)/1000
self.power_plugged = self.i2creg[0xdd] == 0x1f
self.voltage_history.append(self.battery_voltage)
self.battery_level=self.convert_battery_voltage_to_level()
time.sleep(3)
except:
logging.error(f"read error")
time.sleep(3)
def check_device(self, address, reg=0):
"""Check if a device is present at the specified address"""
try:
return self._bus.read_byte_data(address, reg)
except OSError as e:
logging.debug(f"Device not found at address {address}: {e}")
return None
def device_init(self):
if self.modle == "PiSugar2Plus":
'''初始化GPIO'''
self._bus.write_byte_data(self.address, 0x52, self._bus.read_byte_data(
self.address, 0x52) | 0b00000010)
self._bus.write_byte_data(self.address, 0x54, self._bus.read_byte_data(
self.address, 0x54) | 0b00000010)
self._bus.write_byte_data(self.address, 0x52, self._bus.read_byte_data(
self.address, 0x52) | 0b00000100)
self._bus.write_byte_data(self.address, 0x29, self._bus.read_byte_data(
self.address, 0x29) & 0b10111111)
self._bus.write_byte_data(self.address, 0x52, self._bus.read_byte_data(
self.address, 0x52) & 0b10011111 | 0b01000000)
self._bus.write_byte_data(self.address, 0xc2, self._bus.read_byte_data(
self.address, 0xc2) | 0b00010000)
logging.debug(f"PiSugar2Plus GPIO 初始化完毕")
'''Init boost intensity, 0x3f*50ma, 3A'''
self._bus.write_byte_data(self.address, 0x30, self._bus.read_byte_data(
self.address, 0x30) & 0b11000000 | 0x3f)
logging.debug(f"PiSugar2Plus 电流设置完毕")
elif self.modle == "PiSugar2":
'''初始化GPIO'''
self._bus.write_byte_data(self.address, 0x51, (self._bus.read_byte_data(
self.address, 0x51) & 0b11110011) | 0b00000100)
self._bus.write_byte_data(self.address, 0x53, self._bus.read_byte_data(
self.address, 0x53) | 0b00000010)
self._bus.write_byte_data(self.address, 0x51, (self._bus.read_byte_data(
self.address, 0x51) & 0b11001111) | 0b00010000)
self._bus.write_byte_data(self.address, 0x26, self._bus.read_byte_data(
self.address, 0x26) & 0b10110000)
self._bus.write_byte_data(self.address, 0x52, (self._bus.read_byte_data(
self.address, 0x52) & 0b11110011) | 0b00000100)
self._bus.write_byte_data(self.address, 0x53, (self._bus.read_byte_data(
self.address, 0x53) & 0b11101111) | 0b00010000)
logging.debug(f"PiSugar2 GPIO 初始化完毕")
pass
def convert_battery_voltage_to_level(self):
"""
将电池电压转换为电量百分比。
:param voltage: 当前电池电压
:param curve: 电池阈值曲线,格式为 [(电压1, 电量1), (电压2, 电量2), ...]
:return: 电量百分比
"""
if (self.modle == "PiSugar2Plus") | (self.modle == "PiSugar3Plus"):
curve = curve5000
elif self.modle == "PiSugar2":
curve = curve1200
elif self.modle == "PiSugar3":
curve = curve1200_3
# 将当前电压加入历史记录
# 如果历史记录不足 5 次,直接返回平均值(避免截尾后无有效数据)
if len(self.voltage_history) < 5:
avg_voltage = sum(self.voltage_history) / len(self.voltage_history)
else:
# 排序后去掉最高 2 个和最低 2 个
sorted_history = sorted(self.voltage_history)
trimmed_history = sorted_history[2:-2] # 去掉前两个和后两个
avg_voltage = sum(trimmed_history) / len(trimmed_history) # 计算截尾平均
# 遍历电池曲线的每一段
for (v1, p1), (v2, p2) in zip(curve, curve[1:]):
# 如果电压在当前区间内
if v2 <= avg_voltage <= v1:
# 使用线性插值计算电量
return p2 + (p1 - p2) * (avg_voltage - v2) / (v1 - v2)
# 如果电压超出曲线范围,返回最低或最高电量
return curve[-1][1] if avg_voltage < curve[-1][0] else curve[0][1]
def get_version(self):
"""
Get the firmware version of the PiSugar3.
If not PiSugar3, return None
:return: Version string or None
"""
if self.modle == 'PiSugar3':
try:
return bytes(self.i2creg[0xe2:0xee]).decode('ascii')
except OSError as e:
logging.error(f"Failed to read version from PiSugar3: {e}")
return None
return None
def get_model(self):
"""
Get the model of the PiSugar hardware.
:return: Model string.
"""
return self.modle
def get_battery_level(self):
"""
Get the current battery level in percentage.
:return: Battery level as a percentage (0-100).
"""
return self.battery_level
def get_battery_voltage(self):
"""
Get the current battery voltage.
:return: Battery voltage in volts.
"""
return self.battery_voltage
def get_battery_current(self):
"""
Get the current battery current.
:return: Battery current in amperes.
"""
pass
def get_battery_allow_charging(self):
"""
Check if battery charging is allowed.
:return: True if charging is allowed, False otherwise.
"""
return self.allow_charging
def get_battery_charging_range(self):
"""
Get the battery charging range.
:return: Charging range string.
"""
pass
def get_battery_full_charge_duration(self):
"""
Get the duration of keeping the battery charging when full.
:return: Duration in seconds.
"""
pass
def get_battery_safe_shutdown_level(self):
"""
Get the safe shutdown level for the battery.
:return: Safe shutdown level as a percentage.
"""
pass
def get_battery_safe_shutdown_delay(self):
"""
Get the safe shutdown delay.
:return: Delay in seconds.
"""
pass
def get_battery_auto_power_on(self):
"""
Check if auto power on is enabled.
:return: True if enabled, False otherwise.
"""
pass
def get_battery_soft_poweroff(self):
"""
Check if soft power off is enabled.
:return: True if enabled, False otherwise.
"""
pass
def get_system_time(self):
"""
Get the system time.
:return: System time string.
"""
pass
def get_rtc_adjust_ppm(self):
"""
Get the RTC adjust PPM.
:return: RTC adjust PPM value.
"""
pass
def get_rtc_alarm_repeat(self):
"""
Get the RTC alarm repeat setting.
:return: RTC alarm repeat string.
"""
pass
def get_tap_enable(self, tap):
"""
Check if a specific tap (single, double, long) is enabled.
:param tap: Type of tap ('single', 'double', 'long').
:return: True if enabled, False otherwise.
"""
pass
def get_tap_shell(self, tap):
"""
Get the shell command associated with a specific tap.
:param tap: Type of tap ('single', 'double', 'long').
:return: Shell command string.
"""
pass
def get_anti_mistouch(self):
"""
Check if anti-mistouch protection is enabled.
:return: True if enabled, False otherwise.
"""
pass
def get_temperature(self):
"""
Get the current temperature.
:return: Temperature in degrees Celsius.
"""
return self.temperature
def get_battery_power_plugged(self):
"""
Check if the battery is plugged in.
:return: True if plugged in, False otherwise.
"""
return self.power_plugged
def get_battery_charging(self):
"""
Check if the battery is currently charging.
:return: True if charging, False otherwise.
"""
pass
def rtc_web(self):
"""
Synchronize RTC with web time.
"""
pass
class PiSugar(plugins.Plugin):
__author__ = "jayofelony"
@ -26,8 +422,7 @@ class PiSugar(plugins.Plugin):
self.options = dict()
self.ps = None
try:
conn, event_conn = connect_tcp()
self.ps = PiSugarServer(conn, event_conn)
self.ps = PiSugarServer()
except Exception as e:
# Log at debug to avoid clutter since it might be a false positive
logging.debug("[PiSugarX] Unable to establish connection: %s", repr(e))
@ -69,11 +464,6 @@ class PiSugar(plugins.Plugin):
def on_ready(self, agent):
self.ready = True
self._agent = agent
led_amount = self.safe_get(self.ps.get_battery_led_amount, default=0)
if led_amount == 2:
self.is_new_model = True
else:
self.is_new_model = False
def on_internet_available(self, agent):
self._agent = agent
@ -92,7 +482,6 @@ class PiSugar(plugins.Plugin):
battery_level = self.safe_get(self.ps.get_battery_level, default='N/A')
battery_voltage = self.safe_get(self.ps.get_battery_voltage, default='N/A')
battery_current = self.safe_get(self.ps.get_battery_current, default='N/A')
battery_led_amount = self.safe_get(self.ps.get_battery_led_amount, default='N/A') if model == 'Pisugar 2' else 'Not supported'
battery_allow_charging = self.safe_get(self.ps.get_battery_allow_charging, default=False)
battery_charging_range = self.safe_get(self.ps.get_battery_charging_range, default='N/A') if self.is_new_model or model == 'Pisugar 3' else 'Not supported'
battery_full_charge_duration = getattr(self.ps, 'get_battery_full_charge_duration', lambda: 'N/A')()
@ -172,7 +561,6 @@ class PiSugar(plugins.Plugin):
<tr><td>Battery Level</td><td>{battery_level}%</td></tr>
<tr><td>Battery Voltage</td><td>{battery_voltage}V</td></tr>
<tr><td>Battery Current</td><td>{battery_current}A</td></tr>
<tr><td>Battery LED Amount</td><td>{battery_led_amount}</td></tr>
<tr><td>Battery Allow Charging</td><td>{"Yes" if battery_allow_charging and self.is_new_model else "No"}</td></tr>
<tr><td>Battery Charging Range</td><td>{battery_charging_range}</td></tr>
<tr><td>Duration of Keep Charging When Full</td><td>{battery_full_charge_duration} seconds</td></tr>
@ -284,3 +672,4 @@ class PiSugar(plugins.Plugin):
f"[PiSugarX] Empty battery (<= {safe_shutdown_level}%): shutting down"
)
ui.update(force=True, new_data={"status": "Battery exhausted, bye ..."})

View File

@ -106,7 +106,7 @@ def _send_to_wigle(lines, api_key, donate=True, timeout=30):
class Wigle(plugins.Plugin):
__author__ = "Dadav and updated by Jayofelony"
__version__ = "3.0.1"
__version__ = "3.1.0"
__license__ = "GPL3"
__description__ = "This plugin automatically uploads collected WiFi to wigle.net"
@ -128,6 +128,11 @@ class Wigle(plugins.Plugin):
self.ready = True
logging.info("WIGLE: ready")
def on_webhook(self, path, request):
from flask import make_response, redirect
response = make_response(redirect("https://www.wigle.net/", code=302))
return response
def on_internet_available(self, agent):
"""
Called when there's internet connectivity

View File

@ -1,7 +1,6 @@
import os
import logging
import requests
import subprocess
from datetime import datetime
from threading import Lock
from pwnagotchi.utils import StatusFile, remove_whitelisted
@ -161,8 +160,7 @@ class WpaSec(plugins.Plugin):
def on_unload(self, ui):
with ui._lock:
ui.remove_element('ssid')
ui.remove_element('password')
ui.remove_element('pass')
def on_ui_update(self, ui):
if 'show_pwd' in self.options and self.options['show_pwd'] and 'download_results' in self.options and self.options['download_results']:

View File

@ -64,6 +64,9 @@ class Handler:
def with_auth(self, f):
@wraps(f)
def wrapper(*args, **kwargs):
if not self._config['auth']:
return f(*args, **kwargs)
else:
auth = request.authorization
if not auth or not auth.username or not auth.password or not self._check_creds(auth.username,
auth.password):

158
pwndroid_privacy_policy Normal file
View File

@ -0,0 +1,158 @@
Privacy Policy for PwnDroid
Privacy Policy
Last updated: January 05, 2025
This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.
We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the help of the Privacy Policy Generator.
Interpretation and Definitions
Interpretation
The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.
Definitions
For the purposes of this Privacy Policy:
Account means a unique account created for You to access our Service or parts of our Service.
Affiliate means an entity that controls, is controlled by or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.
Application refers to PwnDroid, the software program provided by the Company.
Company (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to PwnDroid.
Country refers to: Netherlands
Device means any device that can access the Service such as a computer, a cellphone or a digital tablet.
Personal Data is any information that relates to an identified or identifiable individual.
Service refers to the Application.
Service Provider means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.
Usage Data refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).
You means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.
Collecting and Using Your Personal Data
Types of Data Collected
Personal Data
While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:
Usage Data
Usage Data
Usage Data is collected automatically when using the Service.
Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.
When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.
We may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device.
Information Collected while Using the Application
While using Our Application, in order to provide features of Our Application, We may collect, with Your prior permission:
Information regarding your location
We use this information to provide features of Our Service, to improve and customize Our Service. The information will not be uploaded to the Company's servers and/or a Service Provider's server or it will only be simply stored on Your device.
You can enable or disable access to this information at any time, through Your Device settings.
Use of Your Personal Data
The Company may use Personal Data for the following purposes:
To provide and maintain our Service, including to monitor the usage of our Service.
To manage Your Account: to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.
For the performance of a contract: the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.
To contact You: To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.
To provide You with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless You have opted not to receive such information.
To manage Your requests: To attend and manage Your requests to Us.
For business transfers: We may use Your information to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.
For other purposes: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.
We may share Your personal information in the following situations:
With Service Providers: We may share Your personal information with Service Providers to monitor and analyze the use of our Service, to contact You.
For business transfers: We may share or transfer Your personal information in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.
With Affiliates: We may share Your information with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.
With business partners: We may share Your information with Our business partners to offer You certain products, services or promotions.
With other users: when You share personal information or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.
With Your consent: We may disclose Your personal information for any other purpose with Your consent.
Retention of Your Personal Data
The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.
The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods.
Transfer of Your Personal Data
Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.
Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer.
The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.
Delete Your Personal Data
You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.
Our Service may give You the ability to delete certain information about You from within the Service.
You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us.
Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.
Disclosure of Your Personal Data
Business Transactions
If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.
Law enforcement
Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).
Other legal requirements
The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:
Comply with a legal obligation
Protect and defend the rights or property of the Company
Prevent or investigate possible wrongdoing in connection with the Service
Protect the personal safety of Users of the Service or the public
Protect against legal liability
Security of Your Personal Data
The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security.
Children's Privacy
Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers.
If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.
Links to Other Websites
Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.
We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.
Changes to this Privacy Policy
We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.
We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.
You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
Contact Us
If you have any questions about this Privacy Policy, You can contact us:
By email: oudshoorn.jeroen@gmail.com
Generated using Privacy Policies Generator