├── module ├── __init__.py ├── fofa │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── __init__.cpython-312.pyc │ │ ├── __init__.cpython-39.pyc │ │ ├── resource.cpython-310.pyc │ │ ├── resource.cpython-311.pyc │ │ ├── resource.cpython-312.pyc │ │ ├── resource.cpython-39.pyc │ │ ├── search_all.cpython-310.pyc │ │ ├── search_all.cpython-311.pyc │ │ ├── search_all.cpython-312.pyc │ │ └── search_all.cpython-39.pyc │ ├── resource.py │ └── search_all.py ├── hunter │ ├── __init__.py │ ├── __pycache__ │ │ ├── search.cpython-310.pyc │ │ ├── search.cpython-311.pyc │ │ ├── search.cpython-312.pyc │ │ ├── search.cpython-39.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── __init__.cpython-312.pyc │ │ └── __init__.cpython-39.pyc │ └── search.py ├── quake │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-39.pyc │ │ ├── resource.cpython-39.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── __init__.cpython-312.pyc │ │ ├── resource.cpython-310.pyc │ │ ├── resource.cpython-311.pyc │ │ ├── resource.cpython-312.pyc │ │ ├── host_search.cpython-310.pyc │ │ ├── host_search.cpython-311.pyc │ │ ├── host_search.cpython-312.pyc │ │ ├── host_search.cpython-39.pyc │ │ ├── service_search.cpython-310.pyc │ │ ├── service_search.cpython-311.pyc │ │ ├── service_search.cpython-312.pyc │ │ └── service_search.cpython-39.pyc │ ├── resource.py │ ├── host_search.py │ └── service_search.py ├── shodan │ ├── __init__.py │ ├── resource.py │ ├── __pycache__ │ │ ├── search.cpython-310.pyc │ │ ├── search.cpython-311.pyc │ │ ├── search.cpython-312.pyc │ │ ├── search.cpython-39.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── __init__.cpython-312.pyc │ │ └── __init__.cpython-39.pyc │ └── search.py ├── zoomeye │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-39.pyc │ │ ├── resource.cpython-39.pyc │ │ ├── __init__.cpython-310.pyc │ │ ├── __init__.cpython-311.pyc │ │ ├── __init__.cpython-312.pyc │ │ ├── domain_ip.cpython-310.pyc │ │ ├── domain_ip.cpython-311.pyc │ │ ├── domain_ip.cpython-312.pyc │ │ ├── domain_ip.cpython-39.pyc │ │ ├── host_search.cpython-39.pyc │ │ ├── resource.cpython-310.pyc │ │ ├── resource.cpython-311.pyc │ │ ├── resource.cpython-312.pyc │ │ ├── web_search.cpython-310.pyc │ │ ├── web_search.cpython-311.pyc │ │ ├── web_search.cpython-312.pyc │ │ ├── web_search.cpython-39.pyc │ │ ├── host_search.cpython-310.pyc │ │ ├── host_search.cpython-311.pyc │ │ └── host_search.cpython-312.pyc │ ├── domain_ip.py │ ├── resource.py │ ├── web_search.py │ └── host_search.py └── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── __init__.cpython-311.pyc │ ├── __init__.cpython-312.pyc │ ├── __init__.cpython-39.pyc │ ├── domain_ip.cpython-39.pyc │ ├── resource.cpython-39.pyc │ ├── host_search.cpython-39.pyc │ └── web_search.cpython-39.pyc ├── LOGO.ico ├── pic ├── csv.png ├── fofa.png ├── mysql.png ├── quake.png ├── config.jpg ├── hunter.jpg └── shodan.jpg ├── requirements.txt ├── .gitignore ├── config.json ├── setup.py ├── Intro ├── Update.md ├── Update_EN.md └── Statistics.md ├── README.md ├── README_EN.md ├── LICENSE └── ThunderSearch.py /module/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module/fofa/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module/hunter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module/quake/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module/shodan/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module/zoomeye/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module/shodan/resource.py: -------------------------------------------------------------------------------- 1 | import requests 2 | -------------------------------------------------------------------------------- /LOGO.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/LOGO.ico -------------------------------------------------------------------------------- /pic/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/csv.png -------------------------------------------------------------------------------- /pic/fofa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/fofa.png -------------------------------------------------------------------------------- /pic/mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/mysql.png -------------------------------------------------------------------------------- /pic/quake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/quake.png -------------------------------------------------------------------------------- /pic/config.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/config.jpg -------------------------------------------------------------------------------- /pic/hunter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/hunter.jpg -------------------------------------------------------------------------------- /pic/shodan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/pic/shodan.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | tk==0.1.0 3 | PyMySQL==1.1.1 4 | shodan 5 | chardet 6 | setuptools 7 | -------------------------------------------------------------------------------- /module/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /module/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /module/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /module/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /module/__pycache__/domain_ip.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/domain_ip.cpython-39.pyc -------------------------------------------------------------------------------- /module/__pycache__/resource.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/resource.cpython-39.pyc -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | LOGO.* 3 | .idea/ 4 | dist/ 5 | build/ 6 | todo.md 7 | ThunderSearch.dmgcanvas 8 | .eggs/ 9 | __pycache__ 10 | -------------------------------------------------------------------------------- /module/__pycache__/host_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/host_search.cpython-39.pyc -------------------------------------------------------------------------------- /module/__pycache__/web_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/__pycache__/web_search.cpython-39.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/resource.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/resource.cpython-310.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/resource.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/resource.cpython-311.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/resource.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/resource.cpython-312.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/resource.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/resource.cpython-39.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/search.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/search.cpython-310.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/search.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/search.cpython-311.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/search.cpython-312.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/search.cpython-39.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/resource.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/resource.cpython-39.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/search.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/search.cpython-310.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/search.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/search.cpython-311.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/search.cpython-312.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/search.cpython-39.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/search_all.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/search_all.cpython-310.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/search_all.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/search_all.cpython-311.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/search_all.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/search_all.cpython-312.pyc -------------------------------------------------------------------------------- /module/fofa/__pycache__/search_all.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/fofa/__pycache__/search_all.cpython-39.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /module/hunter/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/hunter/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/resource.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/resource.cpython-310.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/resource.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/resource.cpython-311.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/resource.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/resource.cpython-312.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /module/shodan/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/shodan/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/resource.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/resource.cpython-39.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/host_search.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/host_search.cpython-310.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/host_search.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/host_search.cpython-311.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/host_search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/host_search.cpython-312.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/host_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/host_search.cpython-39.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/domain_ip.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/domain_ip.cpython-310.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/domain_ip.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/domain_ip.cpython-311.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/domain_ip.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/domain_ip.cpython-312.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/domain_ip.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/domain_ip.cpython-39.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/host_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/host_search.cpython-39.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/resource.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/resource.cpython-310.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/resource.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/resource.cpython-311.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/resource.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/resource.cpython-312.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/web_search.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/web_search.cpython-310.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/web_search.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/web_search.cpython-311.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/web_search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/web_search.cpython-312.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/web_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/web_search.cpython-39.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/service_search.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/service_search.cpython-310.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/service_search.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/service_search.cpython-311.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/service_search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/service_search.cpython-312.pyc -------------------------------------------------------------------------------- /module/quake/__pycache__/service_search.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/quake/__pycache__/service_search.cpython-39.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/host_search.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/host_search.cpython-310.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/host_search.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/host_search.cpython-311.pyc -------------------------------------------------------------------------------- /module/zoomeye/__pycache__/host_search.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xzajyjs/ThunderSearch/HEAD/module/zoomeye/__pycache__/host_search.cpython-312.pyc -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | {"language": "ch", "zoomeye_username": "", "zoomeye_password": "", "zoomeye_api": "", "fofa_username": "", "fofa_api": "", "quake_api": "", "shodan_api": "", "hunter_api": "", "file": "", "host": "", "port": "", "database": "", "username": "", "password": "", "sqlitepath": ""} -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a setup.py script generated by py2applet 3 | 4 | Usage: 5 | python setup.py py2app 6 | """ 7 | 8 | from setuptools import setup 9 | from ThunderSearch import VERSION 10 | 11 | APP = ['ThunderSearch.py'] 12 | DATA_FILES = [] 13 | OPTIONS = { 14 | 'iconfile': 'LOGO.icns', 15 | 'plist': { 16 | 'CFBundleShortVersionString': VERSION, 17 | 'NSHumanReadableCopyright': 'Copyright © 2023 xzajyjs All rights reserved.', 18 | }, 19 | 'arch': 'universal2' 20 | } 21 | 22 | setup( 23 | app=APP, 24 | data_files=DATA_FILES, 25 | options={'py2app': OPTIONS}, 26 | setup_requires=['py2app'], 27 | ) 28 | -------------------------------------------------------------------------------- /module/fofa/resource.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def fofa_search_resource(email, key): 5 | params = { 6 | "email": email, 7 | "key": key 8 | } 9 | try: 10 | resp = requests.get('https://fofa.info/api/v1/info/my', data=params) 11 | data = resp.json() 12 | if "'error': True" in str(data): 13 | return f"[!] Error: {data['errmsg']}\n" 14 | string = f"======== Fofa ========\n[+] Email: {data['email']}\n[+] Username: {data['username']}\n[+] Fcoin: {data['fcoin']}\n[+] isVip: {data['isvip']}\n[+] Vip_level: {data['vip_level']}\n[+] is_Verified: {data['is_verified']}\n[+] Avatar: {data['avatar']}\n========================\n" 15 | return string 16 | except Exception as e: 17 | return e 18 | -------------------------------------------------------------------------------- /module/quake/resource.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | def quake_resource_search(key, mode): 5 | headers = { 6 | "X-QuakeToken": key 7 | } 8 | try: 9 | resp = requests.get(url="https://quake.360.cn/api/v3/user/info", headers=headers, timeout=5) 10 | if resp.status_code == 401: 11 | return '[!] Error: API-KEY error.\n' 12 | data = resp.json()['data'] 13 | except Exception as e: 14 | print(e) 15 | return f"{str(e)}\n" 16 | else: 17 | if mode == "complete": 18 | string = f"======== 360Quake ========\n[+] Username: {data['user']['username']}\n[+] Id: {data['id']}\n[+] E-mail: {data['user']['email']}\n[+] Credit: {data['credit']}\n[+] Persistent_credit: {data['persistent_credit']}\n[+] Mobile_phone: {data['mobile_phone']}\n[+] Role: {data['role'][0]['fullname']}\n[+] Ban_status: {data['ban_status']}\n==========================\n" 19 | elif mode == "easy": 20 | string = f"[!] Remaining free query quota is {data['credit']}.\n" 21 | return f"{str(string)}\n" 22 | -------------------------------------------------------------------------------- /Intro/Update.md: -------------------------------------------------------------------------------- 1 | # 🏝 更新日志 2 | 3 | ## v2.5.1 4 | - 新增页面自适应, 针对Windows的HiDPI进行优化 5 | - 完善英文支持 6 | 7 | ## v2.5 8 | - 增加Hunter搜索 9 | - 增加版本更新检测 10 | - 适当调整界面布局 11 | 12 | ## v2.4.1 13 | - 增加Shodan搜索 14 | - 优化搜索结果获取方式 15 | - 优化报错提示 16 | 17 | ## v2.3.2 18 | - 增加了部分界面的英文支持 19 | - 调整了界面的大小 20 | 21 | ## v2.3.1 22 | - 支持原生ARM 23 | - 适配Mac深色模式 24 | 25 | ## v2.3 26 | - 新增支持360Quake搜索 27 | - 修复Zoomeye数据库存储错误 28 | - 优化模式选择切换 29 | - 优化资源请求 30 | - 增加清空配置功能 31 | 32 | ## v2.2 33 | - 新增支持Fofa搜索 34 | - 优化了Zoomeye的部分搜索结果 35 | - 增加了帮助标签页 36 | - 调整了UI布局 37 | 38 | ## v2.0 39 | - 优化配置设置 40 | - 全新界面 41 | - 支持Fofa(即将开放) 42 | 43 | ## v1.7.6 44 | - 增加数据不保存模式 45 | 46 | ## v1.7.5 47 | - 增加web应用搜索 48 | - 增加导出至mysql数据库 49 | - 加入更多查询内容 50 | 51 | ## v1.7 52 | - 代码重构 53 | - 优化登陆逻辑 54 | - 修复bug 55 | 56 | ## v1.6 57 | - 增加读取json文件登陆 58 | - 修改敏感信息为隐式显示 59 | - 优化登陆逻辑 60 | 61 | ## v1.5.1 62 | - 修复致命bug(**建议更新至此版本及更新**) 63 | - 增加API-KEY登陆 64 | - 调整界面布局 65 | 66 | ## v1.3 67 | - 增加个人信息查询模式 68 | - 设置界面不可缩放 69 | - 汉化了模式选择菜单 70 | > 注:从此版本后将不再发布windows打包版本 71 | 72 | ## v1.2 73 | - 新增域名/ip互查 74 | - 修复二次查询后结果不清空的bug 75 | - 调整界面布局 76 | 77 | ## v1.0 78 | - 多线程支持 -------------------------------------------------------------------------------- /module/zoomeye/domain_ip.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from concurrent.futures import ThreadPoolExecutor 3 | 4 | info_list = [] 5 | headers = {} 6 | session = requests.Session() 7 | 8 | def domain_ip(query, page, thread): 9 | global info_list 10 | info_list = [] 11 | with ThreadPoolExecutor(thread) as t: 12 | for i in range(1,int(page)+1): 13 | t.submit(domain_ip_threadpool, query=query, page=i) 14 | return ("End of search.\n") 15 | 16 | def domain_ip_threadpool(query, page): 17 | url = f'https://api.zoomeye.org/domain/search?q={query}&type=0&page={page}' 18 | print(url) 19 | try: 20 | resp = session.get(url, headers=headers) 21 | session.close() 22 | for each in resp.json()['list']: 23 | each_dic = {} 24 | try: 25 | each_dic['ip'] = each['ip'] 26 | except: 27 | each_dic['ip'] = None 28 | each_dic['ip'] = each_dic['ip'].replace(",",";") 29 | each_dic['name'] = each['name'] 30 | print(each_dic) 31 | info_list.append(each_dic) 32 | except Exception as e: 33 | if str(e.message) == 'resp': 34 | return ('[-] info : account was break, excceeding the max limitations\n') 35 | else: 36 | return (f'[-] info : {str(e.message)}\n') -------------------------------------------------------------------------------- /module/fofa/search_all.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import base64 3 | 4 | total_num = 0 5 | info_list = [] 6 | session = requests.session() 7 | 8 | 9 | def fofa_search(email, key, query, size): 10 | global info_list, total_num 11 | info_list = [] 12 | qbase64 = base64.b64encode(query.encode('utf8')) 13 | params = { 14 | 'email': email, 15 | 'key': key, 16 | 'qbase64': qbase64, 17 | 'fields': 'ip,port,protocol,country_name,region,city,as_organization,host,domain,os,server,icp,title,jarm', 18 | 'page': 1, 19 | 'size': size 20 | } 21 | try: 22 | resp = session.get('https://fofa.info/api/v1/search/all', data=params) 23 | if resp.json()['error']: 24 | return resp.json()['errmsg'] 25 | data = resp.json()['results'] 26 | total_num = len(data) 27 | for each in data: 28 | each_dic = {'ip': each[0], 'port': each[1], 'protocol': each[2], 'country_name': each[3], 'region': each[4], 29 | 'city': each[5], 'as_organization': each[6], 'host': each[7], 'domain': each[8], 'os': each[9], 30 | 'server': each[10], 'icp': each[11], 'title': each[12], 'jarm': each[13]} 31 | info_list.append(each_dic) 32 | return None 33 | except Exception as e: 34 | return f"{str(e)}\n" 35 | -------------------------------------------------------------------------------- /module/shodan/search.py: -------------------------------------------------------------------------------- 1 | import shodan 2 | 3 | info_list = [] 4 | 5 | 6 | def search(key, query, page): 7 | global info_list 8 | info_list = [] 9 | api = shodan.Shodan(key) 10 | try: 11 | for i in range(int(page)): 12 | res_lst = api.search(query, page=i + 1) 13 | for each in res_lst['matches']: 14 | each_dic = { 15 | 'ip': each.get('ip_str', None), 16 | 'port': each.get('port', None), 17 | 'country': str(each.get('location', {}).get('country_name', None)).replace(',', '-'), 18 | 'city': str(each.get('location', {}).get('city', None)).replace(',', '-'), 19 | 'domains': ';'.join(each.get('domains', [])), 20 | 'os': str(each.get('os', None)).replace(',', '-'), 21 | 'title': str(each.get('http', {}).get('title', None)).replace(',', '-'), 22 | 'product': str(each.get('product', None)).replace(',', '-'), 23 | 'timestamp': each.get('timestamp', None), 24 | 'info': str(each.get('info', None)).replace(',', '-'), 25 | 'isp': each.get('isp', None) 26 | } 27 | info_list.append(each_dic) 28 | print(len(info_list)) 29 | return None 30 | except Exception as e: 31 | return f"{str(e)}\n" 32 | -------------------------------------------------------------------------------- /module/zoomeye/resource.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | headers = {} 4 | 5 | 6 | def resource(mode): # user_info 7 | try: 8 | resp = requests.get('https://api.zoomeye.org/resources-info', headers=headers) 9 | if 'error' in str(resp.json()): 10 | return f"[!] Error: {resp.json()['message']}\n" 11 | last = resp.json()['resources']['search'] 12 | except Exception as e: 13 | return str(e) 14 | else: 15 | if mode == "easy": 16 | return f"[!] Your account's Remaining query quota: {last} (this month).\n" 17 | elif mode == "complete": 18 | inteval = resp.json()['resources']['interval'] 19 | uname = resp.json()['user_info']['name'] 20 | role = resp.json()['user_info']['role'] 21 | expired_at = resp.json()['user_info']['expired_at'] 22 | remain_free_quota = resp.json()['quota_info']['remain_free_quota'] 23 | remain_pay_quota = resp.json()['quota_info']['remain_pay_quota'] 24 | remain_total_quota = resp.json()['quota_info']['remain_total_quota'] 25 | return ( 26 | f'========= Zoomeye ========\n[+] Name: {uname}\n[+] Role: {role}\n[+] Intevel: {inteval}\n[+] Expired_at: {expired_at}\n[+] Remain_free_quota: {remain_free_quota}\n[+] Remain_pay_quota: {remain_pay_quota}\n[+] Remain_total_quota: {remain_total_quota}\n========================\n') 27 | -------------------------------------------------------------------------------- /module/quake/host_search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | info_list = [] 4 | 5 | 6 | def quake_host_search(query, page, key): 7 | global info_list 8 | info_list = [] 9 | headers = { 10 | "X-QuakeToken": key, 11 | "Content-Type": "application/json" 12 | } 13 | data = { 14 | "query": query, 15 | "size": int(page) * 10, 16 | } 17 | try: 18 | resp = requests.post("https://quake.360.cn/api/v3/search/quake_host", headers=headers, json=data) 19 | matches = resp.json()['data'] 20 | for each in matches: 21 | each_dic = { 22 | 'ip': each.get('ip', None), 23 | 'service_port': each.get('services', {})[0].get('port', None), 24 | 'service_name': each.get('services', {})[0].get('name', None), 25 | 'service_version': each.get('services', {})[0].get('version', None), 26 | 'service_id': each.get('services', {})[0].get('service_id', None), 27 | 'domains': str(each.get('domains', [])).replace(",", ";"), 28 | 'hostname': each.get('hostname', None), 29 | 'os_name': each.get('os_name', None), 30 | 'os_version': each.get('os_version', None), 31 | 'country_en': each.get('location', {}).get('country_en', None), 32 | 'city_en': each.get('location', {}).get('city_en', None) 33 | } 34 | info_list.append(each_dic) 35 | 36 | except Exception as e: 37 | return f"{str(e)}\n" 38 | return None 39 | -------------------------------------------------------------------------------- /module/zoomeye/web_search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from concurrent.futures import ThreadPoolExecutor 3 | import threading 4 | 5 | info_list = [] 6 | headers = {} 7 | session = requests.Session() 8 | lock = threading.Lock() 9 | 10 | 11 | def web_search(query, page, thread): 12 | global info_list 13 | info_list = [] 14 | with ThreadPoolExecutor(thread) as t: 15 | return_info = [] 16 | for i in range(1, int(page) + 1): 17 | return_info.append(t.submit(web_search_threadpool, query=query, page=i)) 18 | 19 | for res in return_info: 20 | if res.result() is not None: 21 | return res.result() 22 | return None 23 | 24 | 25 | def web_search_threadpool(query, page): 26 | url = f'https://api.zoomeye.org/web/search?query={query}&page={page}' 27 | try: 28 | matches = requests.get(url, headers=headers).json() 29 | if matches['total'] == 0: 30 | return 'Total: 0' 31 | if "'error': '" in matches: 32 | return matches['message'] 33 | for each in matches['matches']: 34 | each_dic = { 35 | 'ip': ";".join(each.get('ip', [])), 36 | 'site': each.get('site', None), 37 | 'title': each.get('title', None), 38 | 'city': each.get('geoinfo', {}).get('city', {}).get('names', {}).get('en', None), 39 | 'country': each.get('geoinfo', {}).get('country', {}).get('names', {}).get('en', None), 40 | 'continent': each.get('geoinfo', {}).get('continent', {}).get('names', {}).get('en', None), 41 | 'isp': each.get('geoinfo', {}).get('isp', None) 42 | } 43 | with lock: 44 | info_list.append(each_dic) 45 | return None 46 | except Exception as e: 47 | return f"{str(e)}\n" 48 | -------------------------------------------------------------------------------- /module/quake/service_search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | info_list = [] 4 | 5 | 6 | def quake_service_search(query, page, key): 7 | global info_list 8 | info_list = [] 9 | headers = { 10 | "X-QuakeToken": key, 11 | "Content-Type": "application/json" 12 | } 13 | data = { 14 | "query": query, 15 | "size": int(page) * 10, 16 | } 17 | try: 18 | resp = requests.post("https://quake.360.cn/api/v3/search/quake_service", headers=headers, json=data) 19 | if resp.status_code == 401: 20 | return "API-KEY error." 21 | matches = resp.json()['data'] 22 | for each in matches: 23 | each_dic = { 24 | 'ip': each.get('ip', None), 25 | 'port': each.get('port', None), 26 | 'org': each.get('org', None), 27 | 'hostname': each.get('hostname', None), 28 | 'service_name': each.get('service', {}).get('name', None), 29 | 'service_title': each.get('service', {}).get(each.get('service', {}).get('name', None), {}).get('title', 30 | None), 31 | 'service_server': each.get('service', {}).get(each.get('service', {}).get('name', None), {}).get( 32 | 'server', None), 33 | 'transport': each.get('transport', None), 34 | 'os_name': each.get('os_name', None), 35 | 'country_en': each.get('location', {}).get('country_en', None), 36 | 'city_en': each.get('location', {}).get('city_en', None), 37 | 'os_version': each.get('os_version', None) 38 | } 39 | 40 | info_list.append(each_dic) 41 | 42 | except Exception as e: 43 | return f"{str(e)}\n" 44 | -------------------------------------------------------------------------------- /Intro/Update_EN.md: -------------------------------------------------------------------------------- 1 | # 🏝 Update Log 2 | 3 | ## v2.5.1 4 | - Added page adaptation, optimized for Windows HiDPI 5 | - Improve English support 6 | 7 | ## v2.5 8 | - Support `Hunter` search 9 | - Add version update detection 10 | - Appropriately adjust the interface layout 11 | 12 | ## v2.4.1 13 | - Support `Shodan` search 14 | - Optimize the way to obtain search results 15 | - Optimize Fofa error prompt 16 | 17 | ## v2.3.2 18 | - Partly english support 19 | - Adjusted the size of the GUI 20 | 21 | ## v2.3.1 22 | - Support native ARM on Mac. 23 | - Adapt to Mac dark mode 24 | 25 | ## v2.3 26 | - Add support for 360Quake search 27 | - Fix Zoomeye database storage bug 28 | - Optimize mode selection toggle 29 | - Optimize resource requests 30 | - Add "Clear Config" function 31 | 32 | ## v2.2 33 | - Add support for Fofa search 34 | - Optimized some search results of Zoomeye 35 | - Add help tab 36 | - Adjust UI layout 37 | 38 | ## v2.0 39 | - Optimize configuration settings 40 | - Brand-new interface 41 | - Support Fofa (coming soon) 42 | 43 | ## v1.7.6 44 | - Add data not saving mode 45 | 46 | ## v1.7.5 47 | - Add Web Application search 48 | - Support export to mysql database 49 | - Add more query content 50 | 51 | ## v1.7 52 | - Refactoring 53 | - Optimized login logic 54 | - fix bugs 55 | 56 | ## v1.6 57 | - Added reading json file login 58 | - Modify sensitive information to be displayed implicitly 59 | - Optimized login logic 60 | 61 | ## v1.5.1 62 | - Fixed fatal bugs (**Recommended to update to this version and newer**) 63 | - Added API-KEY login 64 | - Adjust interface layout 65 | 66 | ## v1.3 67 | - Added personal information query mode 68 | - Settings interface is not zoomable 69 | - Chineseized the mode selection menu 70 | > Note: Windows packaged version will no longer be released after this release 71 | 72 | ## v1.2 73 | - Added domain name/ip mutual check 74 | - Fix the bug that the result is not empty after the second query 75 | - Adjust interface layout 76 | 77 | ## v1.0 78 | - Multithreading support -------------------------------------------------------------------------------- /module/zoomeye/host_search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from concurrent.futures import ThreadPoolExecutor 3 | import threading 4 | 5 | info_list = [] 6 | headers = {} 7 | session = requests.Session() 8 | lock = threading.Lock() 9 | 10 | 11 | def host_search(query, page, thread): 12 | global info_list 13 | info_list = [] 14 | with ThreadPoolExecutor(thread) as t: 15 | return_info = [] 16 | for i in range(1, int(page) + 1): 17 | t.submit(host_search_threadpool, query=query, page=i) 18 | 19 | for res in return_info: 20 | if res.result() is not None: 21 | return res.result() 22 | return None 23 | 24 | 25 | def host_search_threadpool(query, page): 26 | url = f'https://api.zoomeye.org/host/search?query={query}&page={page}&facets=app,os' 27 | try: 28 | resp = requests.get(url, headers=headers, timeout=5) 29 | matches = resp.json() 30 | if "'error': '" in str(matches): 31 | return matches['message'] 32 | for each in matches['matches']: 33 | title_value = each.get('portinfo', {}).get('title', None) 34 | # 检查 title_value 是否可迭代(列表或元组) 35 | if isinstance(title_value, (list, tuple)): 36 | # 如果可迭代,使用 join 方法连接成字符串 37 | joined_title = ';'.join(map(str, title_value)) 38 | else: 39 | # 如果不可迭代,使用原始值(或某个默认值) 40 | joined_title = str(title_value) 41 | each_dic = { 42 | 'ip': each.get('ip', None), 43 | 'port': each.get('portinfo', {}).get('port', None), 44 | 'os': each.get('portinfo', {}).get('os', None), 45 | 'app': each.get('portinfo', {}).get('app', None), 46 | 'version': each.get('portinfo', {}).get('version', None), 47 | # 'title': ';'.join(each.get('portinfo', {}).get('title', None)), 48 | 'title': joined_title, 49 | 'city': each.get('geoinfo', {}).get('city', {}).get('names', {}).get('en', None), 50 | 'country': each.get('geoinfo', {}).get('country', {}).get('names', {}).get('en', None), 51 | 'continent': each.get('geoinfo', {}).get('continent', {}).get('names', {}).get('en', None) 52 | } 53 | with lock: 54 | info_list.append(each_dic) 55 | 56 | except Exception as e: 57 | return f"{str(e)}\n" 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

ThunderSearch 闪电搜索器

2 |

3 | ThunderSearch 4 | ThunderSearch 5 | ThunderSearch 6 | ThunderSearchz1 7 | ThunderSearch 8 | ThunderSearch 9 |

10 | 11 | ## 🎸 Intro介绍 ([EN_README](README_EN.md)) 12 | ThunderSearch(闪电搜索器)是一款使用多个(目前支持`Fofa`、`Shodan`、`Hunter`、`Zoomeye`、`360Quake`)网络空间搜索引擎官方api开发的GUI界面的信息搜集工具。具体支持查询项[点此](Intro/Statistics.md) 13 | 14 | - 支持通过通过图形化修改配置信息 15 | - 支持账号密码和API-KEY登陆 16 | - 支持多个网络资产搜索引擎 17 | - 查询显示结果仅为部分,完整内容保存至`指定文件`或`数据库` 18 | - 支持查询用户个人信息 19 | 20 | --- 21 | ## 💡 使用方式 22 | ### -> Run 23 | - 直接运行即可。每次通过GUI修改配置后务必`保存配置` 24 | - Zoomeye支持两种登录方式(账户密码/API-KEY),选其一即可,优先使用API-KEY登录。Fofa需同时填写邮箱和API-KEY。Quake仅需填写API-KEY 25 | 26 | #### 配置文件`config.json`说明 27 | ``` 28 | "language": "en" 29 | "zoomeye_username": "" 30 | "zoomeye_password": "" 31 | "zoomeye_api": "" 32 | "fofa_username": "" 33 | "fofa_api": "" 34 | "quake_api": "" 35 | "shodan_api": "" 36 | "hunter_api": "" 37 | "file": "" 38 | "host": "" 39 | "port": "" 40 | "database": "" 41 | "username": "" 42 | "password": "" 43 | ``` 44 | 45 | > 通过界面中的`Language`选项来修改语言。同时也可通过修改`language`的值来修改语言。目前支持:`ch`(中文), `en`(英文) 46 | 47 | ### -> 构建和运行 48 | ``` 49 | pip3 install -r requirements.txt 50 | python3 ThunderSearch.py 51 | ``` 52 | > 推荐Python版本: 3.9,3.10,3.12(注意,3.11在Mac Sonoma上可能会出现bug) 53 | --- 54 | ## 📡 支持统计内容 55 | 56 | [查看详情](Intro/Statistics.md) 57 | 58 | --- 59 | ## 🏝 更新日志 60 | 61 | [查看详情](Intro/Update.md) 62 | 63 | --- 64 | ## 🌏 效果演示 65 | ![](pic/fofa.png) 66 | ![](pic/quake.png) 67 | ![](pic/shodan.jpg) 68 | ![](pic/hunter.jpg) 69 | ![](pic/config.jpg) 70 | ![](pic/mysql.png) 71 | ![](pic/csv.png) -------------------------------------------------------------------------------- /module/hunter/search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import base64 3 | import traceback 4 | 5 | info_list = [] 6 | other_info = {} 7 | 8 | 9 | def search(key, query, page): 10 | global info_list 11 | info_list = [] 12 | query = str(base64.urlsafe_b64encode(query.encode("utf-8")), 'utf-8') 13 | try: 14 | for i in range(int(page)): 15 | url = f"https://hunter.qianxin.com/openApi/search?api-key={key}&search={query}&page={i + 1}&page_size=20&is_web=3" 16 | resp = requests.get(url) 17 | # print(resp.json()) 18 | if resp.json()['code'] != 200 or resp.json()['message'] != 'success': 19 | return f"{str(resp.json()['message'])}\n" 20 | data = resp.json()['data'] 21 | if i == 0: 22 | global other_info 23 | other_info['total'], other_info['consume_quota'], other_info['rest_quota'] = data['total'], data[ 24 | 'consume_quota'].replace('消耗积分:', ''), data['rest_quota'].replace('今日剩余积分:', '') 25 | for each in data['arr']: 26 | dic = { 27 | 'is_risk': each.get('is_risk', None), 28 | 'url': each.get('url', None), 29 | 'ip': each.get('ip', None), 30 | 'port': each.get('port', None), 31 | 'web_title': each.get('web_title', None), 32 | 'domain': each.get('domain', None), 33 | 'is_risk_protocol': each.get('is_risk_protocol', None), 34 | 'protocol': each.get('protocol', None), 35 | 'base_protocol': each.get('base_protocol', None), 36 | 'status_code': each.get('status_code', None), 37 | 'component': each.get('component', None), 38 | 'os': each.get('os', None), 39 | 'company': each.get('company', None), 40 | 'number': each.get('number', None), 41 | 'country': each.get('country', None), 42 | 'province': each.get('province', None), 43 | 'city': each.get('city', None), 44 | 'updated_at': each.get('updated_at', None), 45 | 'is_web': each.get('is_web', None), 46 | 'as_org': each.get('as_org', None), 47 | 'isp': each.get('isp', None), 48 | # 'banner': each.get('banner', None), 49 | 'vul_list': each.get('vul_list', None) 50 | } 51 | for key, value in dic.items(): 52 | dic[key] = str(value).replace(',', '-').replace('\n', ' ') 53 | 54 | # print("======================") 55 | # print(dic) 56 | # print("======================") 57 | 58 | info_list.append(dic) 59 | return None 60 | except Exception: 61 | return f"{str(traceback.format_exc())}\n" 62 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | #

ThunderSearch

2 |

3 | ThunderSearch 4 | ThunderSearch 5 | ThunderSearch 6 | ThunderSearch 7 | ThunderSearch 8 | ThunderSearch 9 |

10 | 11 | ## 🎸 Intro ([CN_README](README.md)) 12 | ThunderSearch is an information collection tool with GUI interface developed using the official api of multiple cyberspace search engines(Including `Fofa`, `Shodan`, `Hunter`, `Zoomeye`, `Quake`). Specific support query items [click here](Intro/Statistics.md). 13 | 14 | - Support for modifying configuration information through graphics 15 | - Support account password and API-KEY login 16 | - Supports multiple cyberspace search engines 17 | - The query display result is only partial, and the complete content is saved to the `specified file` or `database` 18 | - Support query user personal information 19 | 20 | --- 21 | ## 💡 Usage 22 | ### -> Run 23 | - Just run the packaged application。Remember to click the button `SaveConfig` 24 | - Zoomeye supports two ways to login(Acc and Pass/API-KEY), just choose one of them is OK. Use API-KEY to log in first. Fofa needs to fill in the E-mail and API-KEY at the same time. Quake only needs to fill in the API-KEY. 25 | 26 | #### `config.json` 27 | ``` 28 | "language": "en" 29 | "zoomeye_username": "" 30 | "zoomeye_password": "" 31 | "zoomeye_api": "" 32 | "fofa_username": "" 33 | "fofa_api": "" 34 | "quake_api": "" 35 | "shodan_api": "" 36 | "hunter_api": "" 37 | "file": "" 38 | "host": "" 39 | "port": "" 40 | "database": "" 41 | "username": "" 42 | "password": "" 43 | ``` 44 | 45 | > Modify the language through the `Language` option in the interface. At the same time, the value of `language` can also be modified by modifying the language parameter. You can change language from `ch`(Chinese) to `en`(English). 46 | 47 | ### -> Build and Run 48 | ``` 49 | pip3 install -r requirements.txt 50 | python3 ThunderSearch.py 51 | ``` 52 | > Recommend Python version: 3.9, 3.10, 3.12(notice: 3.11 is not recommended on Mac Sonoma) 53 | --- 54 | ## 📡 Statistical content 55 | 56 | [For details](Intro/Statistics.md) 57 | 58 | --- 59 | 60 | ## 🏝 Update Log 61 | 62 | [For details](Intro/Update_EN.md) 63 | 64 | --- 65 | ## 🌏 Screenshots 66 | ![](pic/fofa.png) 67 | ![](pic/quake.png) 68 | ![](pic/shodan.jpg) 69 | ![](pic/hunter.jpg) 70 | ![](pic/config.jpg) 71 | ![](pic/mysql.png) 72 | ![](pic/csv.png) -------------------------------------------------------------------------------- /Intro/Statistics.md: -------------------------------------------------------------------------------- 1 | # 📡 支持统计内容(Statistical content) 2 | 3 | ## Zoomeye 4 | 5 | ### 主机搜索 (Host Search) 6 | 7 | | 统计项 | 类型 | 说明 | 8 | |:----------:|:------:|:------:| 9 | | ip | String | ip地址 | 10 | | port | String | 端口 | 11 | | os | String | 操作系统 | 12 | | app | String | 应用、设备等 | 13 | | version | String | 应用版本号 | 14 | | title | String | 标题 | 15 | | city | String | 城市 | 16 | | country | String | 国家 | 17 | | continents | String | 大洲 | 18 | 19 | ### 域名/IP (Domain/IP) 20 | 21 | | 统计项 | 类型 | 说明 | 22 | |:----:|:------:|:----:| 23 | | ip | String | ip地址 | 24 | | name | String | 域名 | 25 | 26 | ### web应用搜索 (web application search) 27 | 28 | | 统计项 | 类型 | 说明 | 29 | |:---------:|:------:|:-------:| 30 | | ip | String | ip地址 | 31 | | site | String | 站点 | 32 | | title | String | 网站标题 | 33 | | city | String | 城市 | 34 | | country | String | 国家 | 35 | | continent | String | 大洲 | 36 | | isp | String | 网络服务提供商 | 37 | 38 | --- 39 | 40 | ## Fofa 41 | 42 | | 统计项 | 类型 | 说明 | 43 | |:---------------:|:------:|:--------:| 44 | | ip | String | ip地址 | 45 | | port | String | 端口 | 46 | | protocol | String | 协议名 | 47 | | country_name | String | 国家名 | 48 | | region | String | 地区 | 49 | | city | String | 城市 | 50 | | as_organization | String | asn组织 | 51 | | host | String | 主机名 | 52 | | domain | String | 域名 | 53 | | os | String | 操作系统 | 54 | | server | String | 网站server | 55 | | icp | String | icp备案号 | 56 | | title | String | 网站标题 | 57 | | jarm | String | jarm指纹 | 58 | 59 | --- 60 | 61 | ## Quake 62 | 63 | ### 主机数据查询 (Host) 64 | 65 | | 统计项 | 类型 | 说明 | 66 | |:----------------:|:------:|:-------:| 67 | | ip | String | ip地址 | 68 | | service_port | String | 服务端口 | 69 | | service_name | String | 服务名 | 70 | | service_versioin | String | 服务版本 | 71 | | service_id | String | 服务id | 72 | | domains | String | 域名 | 73 | | hostname | String | 主机名 | 74 | | os_name | String | 系统名 | 75 | | os_version | String | 系统版本 | 76 | | country_en | String | 国家名(En) | 77 | | city_en | String | 城市名(En) | 78 | 79 | ### 服务数据查询 (Service) 80 | 81 | | 统计项 | 类型 | 说明 | 82 | |:--------------:|:------:|:-------:| 83 | | ip | String | ip地址 | 84 | | port | String | 端口 | 85 | | org | String | 组织名 | 86 | | hostname | String | 主机名 | 87 | | service_name | String | 服务名称 | 88 | | service_title | String | 服务标题 | 89 | | service_server | String | 服务服务器 | 90 | | transport | String | 协议 | 91 | | os_name | String | 操作系统名 | 92 | | country_en | String | 国家名(En) | 93 | | city_en | String | 城市名(En) | 94 | | os_version | String | 系统版本 | 95 | 96 | --- 97 | 98 | ## Shodan 99 | 100 | | 统计项 | 类型 | 说明 | 101 | |:---------:|:------:|:-------:| 102 | | ip | String | ip地址 | 103 | | port | String | 端口 | 104 | | domains | String | 域名 | 105 | | title | String | 标题 | 106 | | product | String | 软件或产品 | 107 | | os | String | 系统 | 108 | | info | String | 产品相关信息 | 109 | | isp | String | 网络服务提供商 | 110 | | country | String | 国家 | 111 | | city | String | 城市 | 112 | | timestamp | String | 时间戳 | 113 | 114 | ## Hunter 115 | 116 | | 字段名 | 类型 | 描述 | 117 | |:----------------:|:-------:|:-------:| 118 | | url | String | URL地址 | 119 | | ip | String | IP地址 | 120 | | port | String | 端口号 | 121 | | web_title | String | 网页标题 | 122 | | domain | String | 域名或域名列表 | 123 | | is_risk_protocol | Boolean | 是否为风险协议 | 124 | | protocol | String | 协议 | 125 | | base_protocol | String | 基础协议 | 126 | | status_code | String | 状态码 | 127 | | component | String | 组件或组件列表 | 128 | | os | String | 操作系统 | 129 | | company | String | 公司 | 130 | | number | String | 号码 | 131 | | country | String | 国家 | 132 | | province | String | 省份 | 133 | | city | String | 城市 | 134 | | updated_at | String | 更新时间 | 135 | | is_web | Boolean | 是否为网页 | 136 | | as_org | String | AS组织 | 137 | | isp | String | ISP提供商 | 138 | | vul_list | String | 漏洞列表 | 139 | | is_risk | Boolean | 是否为风险 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /ThunderSearch.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import ctypes 5 | import sqlite3 6 | import traceback 7 | 8 | import pymysql 9 | import requests 10 | import tkinter as tk 11 | from os import system 12 | from threading import Thread 13 | from tkinter import messagebox 14 | from tkinter.ttk import Notebook, Treeview, Style 15 | 16 | # zoomeye 17 | import module.zoomeye.host_search as zoomeye_hostsearch 18 | import module.zoomeye.domain_ip as zoomeye_domain_ip 19 | import module.zoomeye.resource as zoomeye_resource 20 | import module.zoomeye.web_search as zoomeye_websearch 21 | 22 | # fofa 23 | import module.fofa.search_all as fofa_search_all 24 | import module.fofa.resource as fofa_resource 25 | 26 | # quake 27 | import module.quake.resource as quake_resource 28 | import module.quake.host_search as quake_hostsearch 29 | import module.quake.service_search as quake_servicesearch 30 | 31 | # shodan 32 | import module.shodan.search as shodan_search 33 | 34 | # hunter 35 | import module.hunter.search as hunter_search 36 | 37 | VERSION = "v2.5.1" 38 | 39 | 40 | class Application(tk.Frame): 41 | def __init__(self, master): 42 | super().__init__(master) 43 | self.master = master 44 | self.delete_access_token() 45 | self.createWidget() 46 | self.language = self.dic['language'] 47 | self.setLanguage() 48 | self.pack(padx=0, pady=0, fill='both', expand=True) 49 | self.checkUpdate() 50 | 51 | def checkUpdate(self): 52 | resp = {'message': ''} 53 | url = "https://api.github.com/repos/xzajyjs/ThunderSearch/releases/latest" 54 | try: 55 | resp = requests.get(url, timeout=5).json() 56 | latest_version, latest_version_url = resp['name'], resp['html_url'] 57 | if VERSION != latest_version: 58 | self.log_insert(f'[!] Update to new version {latest_version}: {latest_version_url}\n') 59 | return 60 | except Exception as e: 61 | if 'API rate limit' in resp['message']: 62 | self.log_insert("[!] CheckUpdate error: This IP has API limit.\n") 63 | return 64 | self.log_insert(f"[!] CheckUpdate error: {traceback.format_exc()}.\n") 65 | 66 | def createWidget(self): 67 | self.notebook = Notebook(self) 68 | self.search_frame = tk.Frame() 69 | self.config_frame = tk.Frame() 70 | self.help_frame = tk.Frame() 71 | self.notebook.add(self.search_frame, text='查询') 72 | self.notebook.add(self.config_frame, text='配置') 73 | self.notebook.add(self.help_frame, text='帮助') 74 | self.notebook.pack(padx=0, pady=0, fill='both', expand=True) 75 | 76 | self.initHelpFrame() 77 | self.initConfigFrame() 78 | self.initSearchFrame() 79 | 80 | def initSearchFrame(self): 81 | self.label_thread = tk.Label(self.search_frame, text='线程数:', width=6) 82 | self.label_thread.grid(row=0, column=0) 83 | self.thread_choice = tk.StringVar(self) 84 | self.thread_choice.set('5') 85 | self.THREAD = tk.OptionMenu(self.search_frame, self.thread_choice, '1', '5', '10', '20', '30') 86 | self.THREAD.grid(row=0, column=1) 87 | self.label_page = tk.Label(self.search_frame, text='查询页数:') 88 | self.label_page.grid(row=0, column=2) 89 | self.page_choice = tk.StringVar(self) 90 | self.page_choice.set('1') 91 | self.PAGE = tk.OptionMenu(self.search_frame, self.page_choice, '1', '2', '3', '4', '5', '10', '15', '20', '30', 92 | '50', '80', '100', '200', '300', '500', '1000') 93 | self.PAGE.grid(row=0, column=3) 94 | self.label_mode = tk.Label(self.search_frame, text='模式:') 95 | self.label_mode.grid(row=0, column=4) 96 | 97 | self.mode_dict = { 98 | 'Fofa': ['数据查询', '个人信息'], 99 | 'Shodan': ['数据搜索'], 100 | 'Hunter': ['数据搜索'], 101 | 'Zoomeye': ['主机搜索', '域名/IP', 'web应用', '个人信息'], 102 | 'Quake': ['主机搜索', '服务搜索', '个人信息'] 103 | } 104 | 105 | self.query_mode_choice = tk.StringVar(self) 106 | self.MODE = tk.OptionMenu(self.search_frame, self.query_mode_choice, '') 107 | self.MODE.grid(row=0, column=5) 108 | 109 | self.search_engine_choice = tk.StringVar(self) 110 | self.search_engine_choice.trace('w', self.update_mode_menu) 111 | self.search_engine_choice.set('Fofa') 112 | 113 | self.label_saveMode = tk.Label(self.search_frame, text='存储模式:') 114 | self.label_saveMode.grid(row=0, column=6) 115 | self.save_mode_choice = tk.StringVar(self) 116 | self.save_mode_choice.set('不保存') 117 | self.SAVE_MODE = tk.OptionMenu(self.search_frame, self.save_mode_choice, '不保存', '存文件', 'mysql', 'sqlite') 118 | self.SAVE_MODE.grid(row=0, column=7) 119 | 120 | self.label_searchEngine = tk.Label(self.search_frame, text='搜索引擎:') 121 | self.label_searchEngine.grid(row=0, column=8) 122 | self.SEARCH_ENGINE = tk.OptionMenu(self.search_frame, self.search_engine_choice, *self.mode_dict.keys()) 123 | self.SEARCH_ENGINE.grid(row=0, column=9) 124 | 125 | self.label_searchQuery = tk.Label(self.search_frame, text='查询语句:') 126 | self.label_searchQuery.grid(row=1, column=0) 127 | self.QUERY = tk.Entry(self.search_frame, width=60, borderwidth=1) 128 | self.QUERY.grid(row=1, column=1, columnspan=8, sticky=tk.EW) 129 | self.START = tk.Button(self.search_frame, text='查询', command=self.thread) 130 | self.START.grid(row=1, column=9) 131 | 132 | # windows hidpi treeview row_height 133 | multiple = 1 if "darwin" in sys.platform else 2 134 | if multiple == 2: 135 | Style().configure('Treeview', rowheight=40) 136 | self.TREEVIEW = Treeview(self.search_frame, show='headings') 137 | self.TREEVIEW.grid(row=3, column=0, columnspan=10, sticky=tk.NSEW) 138 | self.TREEVIEW['columns'] = ("ID", "IP", "PORT/DOMAIN", "OS", "TITLE") 139 | self.TREEVIEW.column("ID", width=50) 140 | self.TREEVIEW.column("IP", width=160) 141 | self.TREEVIEW.column("PORT/DOMAIN", width=235) 142 | self.TREEVIEW.column("OS", width=90) 143 | self.TREEVIEW.column("TITLE", width=200) 144 | self.TREEVIEW.heading("ID", text="ID") 145 | self.TREEVIEW.heading("IP", text="IP") 146 | self.TREEVIEW.heading("PORT/DOMAIN", text="PORT/DOMAIN") 147 | self.TREEVIEW.heading("OS", text="OS") 148 | self.TREEVIEW.heading("TITLE", text="TITLE") 149 | self.LOG = tk.Text(self.search_frame, relief=tk.SOLID, borderwidth=1, height=15, width=104, fg='gray') 150 | self.LOG.grid(row=4, column=0, columnspan=10, sticky=tk.EW) 151 | 152 | # 信息处理 153 | self.log_insert( 154 | f"@ Version: {VERSION}\n@ Author: xzajyjs\n@ E-mail: xuziang16@gmail.com\n@ Repo: https://github.com/xzajyjs/ThunderSearch\n") 155 | if not os.path.exists('config.json'): 156 | self.language = "ch" 157 | self.save_config() 158 | try: 159 | self.load_config() 160 | except Exception as e: 161 | messagebox.showerror(title='Error', message=f'Fail to load config.json, {e}') 162 | 163 | # 页面自适应 164 | for i in range(4): 165 | self.search_frame.rowconfigure(i + 1, weight=1) 166 | for j in range(9): 167 | self.search_frame.columnconfigure(j + 1, weight=1) 168 | 169 | def initConfigFrame(self): 170 | # zoomeye 171 | tk.Label(self.config_frame, text='Zoomeye', width=10).grid(row=0, column=1, sticky=tk.EW) 172 | self.label_zoomeyeUser = tk.Label(self.config_frame, text='账号:') 173 | self.label_zoomeyeUser.grid(row=1, column=0) 174 | self.label_zoomeyePass = tk.Label(self.config_frame, text='密码:') 175 | self.label_zoomeyePass.grid(row=2, column=0) 176 | tk.Label(self.config_frame, text='API-KEY:').grid(row=3, column=0) 177 | self.ZOOMEYE_USERNAME = tk.Entry(self.config_frame, width=24, borderwidth=1) 178 | self.ZOOMEYE_USERNAME.grid(row=1, column=1, sticky=tk.EW) 179 | self.ZOOMEYE_PASSWORD = tk.Entry(self.config_frame, width=24, show="*", borderwidth=1) 180 | self.ZOOMEYE_PASSWORD.grid(row=2, column=1, sticky=tk.EW) 181 | self.ZOOMEYE_API = tk.Entry(self.config_frame, width=24, borderwidth=1, show='*') 182 | self.ZOOMEYE_API.grid(row=3, column=1, sticky=tk.EW) 183 | 184 | # fofa 185 | tk.Label(self.config_frame, text='Fofa', width=10).grid(row=4, column=1, sticky=tk.EW) 186 | self.label_fofaEmail = tk.Label(self.config_frame, text='邮箱:') 187 | self.label_fofaEmail.grid(row=5, column=0) 188 | tk.Label(self.config_frame, text='API-KEY:').grid(row=6, column=0) 189 | self.FOFA_USERNAME = tk.Entry(self.config_frame, width=24, borderwidth=1) 190 | self.FOFA_USERNAME.grid(row=5, column=1, sticky=tk.EW) 191 | self.FOFA_API = tk.Entry(self.config_frame, width=24, borderwidth=1, show='*') 192 | self.FOFA_API.grid(row=6, column=1, sticky=tk.EW) 193 | 194 | # quake 195 | tk.Label(self.config_frame, text="360Quake").grid(row=7, column=1, sticky=tk.EW) 196 | tk.Label(self.config_frame, text="API-KEY").grid(row=8, column=0) 197 | self.QUAKE_API = tk.Entry(self.config_frame, width=24, show="*", borderwidth=1) 198 | self.QUAKE_API.grid(row=8, column=1, sticky=tk.EW) 199 | 200 | # shodan 201 | tk.Label(self.config_frame, text="Shodan").grid(row=9, column=1, sticky=tk.EW) 202 | tk.Label(self.config_frame, text="API-KEY").grid(row=10, column=0) 203 | self.SHODAN_API = tk.Entry(self.config_frame, width=24, show="*", borderwidth=1) 204 | self.SHODAN_API.grid(row=10, column=1, sticky=tk.EW) 205 | 206 | # hunter 207 | tk.Label(self.config_frame, text="Hunter").grid(row=11, column=1, sticky=tk.EW) 208 | tk.Label(self.config_frame, text="API-KEY").grid(row=12, column=0) 209 | self.HUNTER_API = tk.Entry(self.config_frame, width=24, show="*", borderwidth=1) 210 | self.HUNTER_API.grid(row=12, column=1, sticky=tk.EW) 211 | 212 | # 右侧配置 213 | self.label_fileConfig = tk.Label(self.config_frame, text='文件存储配置') 214 | self.label_fileConfig.grid(row=0, column=3, sticky=tk.EW) 215 | self.label_filePath = tk.Label(self.config_frame, text='文件路径') 216 | self.label_filePath.grid(row=1, column=2) 217 | self.label_dbConfig = tk.Label(self.config_frame, text='数据库配置(MySQL)') 218 | self.label_dbConfig.grid(row=2, column=3, sticky=tk.EW) 219 | self.label_dbHost = tk.Label(self.config_frame, text='主机') 220 | self.label_dbHost.grid(row=3, column=2) 221 | self.label_dbPort = tk.Label(self.config_frame, text='端口') 222 | self.label_dbPort.grid(row=4, column=2) 223 | self.label_dbName = tk.Label(self.config_frame, text='数据库名') 224 | self.label_dbName.grid(row=5, column=2) 225 | self.label_dbUser = tk.Label(self.config_frame, text='用户名') 226 | self.label_dbUser.grid(row=6, column=2) 227 | self.label_dbPass = tk.Label(self.config_frame, text='密码') 228 | self.label_dbPass.grid(row=7, column=2) 229 | self.label_sqliteConfig = tk.Label(self.config_frame, text='Sqlite配置') 230 | self.label_sqliteConfig.grid(row=8, column=3, sticky=tk.EW) 231 | self.label_sqlite = tk.Label(self.config_frame, text='Sqlite路径') 232 | self.label_sqlite.grid(row=9, column=2) 233 | self.label_otherConfig = tk.Label(self.config_frame, text='其他配置') 234 | self.label_otherConfig.grid(row=10, column=3, sticky=tk.EW) 235 | self.label_languageSet = tk.Label(self.config_frame, text='Language') 236 | self.label_languageSet.grid(row=11, column=2) 237 | 238 | self.FILE = tk.Entry(self.config_frame, width=30, borderwidth=1) 239 | self.FILE.grid(row=1, column=3, sticky=tk.EW) 240 | self.DATABASE_HOST = tk.Entry(self.config_frame, width=30, borderwidth=1) 241 | self.DATABASE_HOST.grid(row=3, column=3, sticky=tk.EW) 242 | self.DATABASE_PORT = tk.Entry(self.config_frame, width=30, borderwidth=1) 243 | self.DATABASE_PORT.grid(row=4, column=3, sticky=tk.EW) 244 | self.DATABASE_DATABASE = tk.Entry(self.config_frame, width=30, borderwidth=1) 245 | self.DATABASE_DATABASE.grid(row=5, column=3, sticky=tk.EW) 246 | self.DATABASE_USERNAME = tk.Entry(self.config_frame, width=30, borderwidth=1) 247 | self.DATABASE_USERNAME.grid(row=6, column=3, sticky=tk.EW) 248 | self.DATABASE_PASSWORD = tk.Entry(self.config_frame, width=30, borderwidth=1, show="*") 249 | self.DATABASE_PASSWORD.grid(row=7, column=3, sticky=tk.EW) 250 | self.SQLITE_FILENAME = tk.Entry(self.config_frame, width=30, borderwidth=1) 251 | self.SQLITE_FILENAME.grid(row=9, column=3, sticky=tk.EW) 252 | self.language_choice = tk.StringVar() 253 | # self.language_choice.set(self.dic['language']) 254 | self.LANGUAGE_BTN = tk.OptionMenu(self.config_frame, self.language_choice, 'ch', 'en') 255 | self.LANGUAGE_BTN.grid(row=11, column=3, sticky=tk.W) 256 | 257 | self.SAVE = tk.Button(self.config_frame, text='保存配置', command=self.save_config) 258 | self.SAVE.grid(row=3, column=4) 259 | self.LOAD = tk.Button(self.config_frame, text='读取配置', command=self.load_config) 260 | self.LOAD.grid(row=4, column=4) 261 | self.CLEAR = tk.Button(self.config_frame, text='清空配置', command=self.clear_config) 262 | self.CLEAR.grid(row=5, column=4) 263 | self.TESTDB = tk.Button(self.config_frame, text='数据库测试', command=self.db_test) 264 | self.TESTDB.grid(row=6, column=4) 265 | self.CLEARTOKEN = tk.Button(self.config_frame, text='清除token', command=self.delete_access_token) 266 | self.CLEARTOKEN.grid(row=7, column=4) 267 | 268 | self.config_frame.columnconfigure(1, weight=1) 269 | self.config_frame.columnconfigure(3, weight=1) 270 | 271 | def initHelpFrame(self): 272 | self.help_text = tk.Text(self.help_frame, relief=tk.SOLID, borderwidth=1, height=35, width=100, fg='gray') 273 | self.help_text.grid(row=0, column=0, sticky=tk.NSEW) 274 | text = """使用指南: 275 | 1.先填写配置选项卡中的选项(有选择性填写) 276 | 2.填写完配置请先点击保存 277 | 3.文件路径请填写绝对路径,如/Users/xxx/fofa.csv 278 | 4.查询页数在Fofa和Quake查询下乘以10即为查询数据条数 279 | 5.Shodan单页搜索为100条 280 | 6.Hunter单页搜索为20条 281 | ============= 282 | 283 | Fofa语法 284 | title="beijing" 从标题中搜索“北京" 285 | header="elastic" 从http头中搜索“elastic" 286 | body="网络空间测绘" 从html正文中搜索“网络空间测绘" 287 | domain="qq.com" 搜索根域名带有qq.com的网站。 288 | icon_hash="-247388890" 搜索使用此icon的资产。 仅限FOFA高级会员使用 289 | host=".gov.cn" 从url中搜索".gov.cn" 搜索要用host作为名称 290 | port="6379" 查找对应“6379"端口的资产 291 | icp="京ICP证030173号" 查找备案号为“京ICP证030173号"的网站 搜索网站类型资产 292 | ip="1.1.1.1" 从ip中搜索包含“1.1.1.1"的网站 搜索要用ip作为名称 293 | ip="220.181.111.1/24" 查询IP为“220.181.111.1"的C网段资产 294 | status_code="402" 查询服务器状态为“402"的资产 295 | protocol="quic" 查询quic协议资产 搜索指定协议类型(在开启端口扫描的情况下有效) 296 | country="CN" 搜索指定国家(编码)的资产。 297 | region="HeNan" 搜索指定行政区的资产。 298 | city="HanDan" 搜索指定城市的资产。 299 | cert="baidu" 搜索证书(https或者imaps等)中带有baidu的资产。 300 | cert.subject="Oracle Corporation" 搜索证书持有者是Oracle Corporation的资产 301 | cert.issuer="DigiCert" 搜索证书颁发者为DigiCert Inc的资产 302 | cert.is_valid=true 验证证书是否有效,true有效,false无效,仅限FOFA高级会员使用 303 | banner=users && protocol=ftp 搜索FTP协议中带有users文本的资产。 304 | type=service 搜索所有协议资产,支持subdomain和service两种。 305 | os="centos" 搜索操作系统为CentOS资产。 306 | server=="Microsoft-IIS/10" 搜索IIS 10服务器。 307 | app="Microsoft-Exchange" 搜索Microsoft-Exchange设备 308 | after="2017" && before="2017-10-01" 时间范围段搜索 309 | asn="19551" 搜索指定asn的资产。 310 | org="Amazon.com, Inc." 搜索指定org(组织)的资产。 311 | base_protocol="udp" 搜索指定udp协议的资产。 312 | is_fraud=false 排除仿冒/欺诈数据 313 | is_honeypot=false 排除蜜罐数据,仅限FOFA高级会员使用 314 | is_ipv6=true 搜索ipv6的资产,只接受true和false。 315 | is_domain=true 搜索域名的资产,只接受true和false。 316 | port_size="6" 查询开放端口数量等于"6"的资产,仅限FOFA会员使用 317 | port_size_gt="6" 查询开放端口数量大于"6"的资产,仅限FOFA会员使用 318 | port_size_lt="12" 查询开放端口数量小于"12"的资产,仅限FOFA会员使用 319 | ip_ports="80,161" 搜索同时开放80和161端口的ip资产(以ip为单位的资产数据) 320 | ip_country="CN" 搜索中国的ip资产(以ip为单位的资产数据)。 321 | ip_region="Zhejiang" 搜索指定行政区的ip资产(以ip为单位的资产数据)。 322 | ip_city="Hangzhou" 搜索指定城市的ip资产(以ip为单位的资产数据)。 323 | ip_after="2021-03-18" 搜索2021-03-18以后的ip资产(以ip为单位的资产数据)。 324 | ip_before="2019-09-09" 搜索2019-09-09以前的ip资产(以ip为单位的资产数据)。 325 | ============ 326 | 327 | Zoomeye语法 328 | app:nginx   组件名 329 | ver:1.0   版本 330 | os:windows   操作系统 331 | country:"China"   国家 332 | city:"hangzhou"   城市 333 | port:80   端口 334 | hostname:google   主机名 335 | site:thief.one   网站域名 336 | desc:nmask   描述 337 | keywords:nmask'blog   关键词 338 | service:ftp   服务类型 339 | ip:8.8.8.8   ip地址 340 | cidr:8.8.8.8/24   ip地址段 341 | city:"beijing" port:80 同时满足两个条件 342 | ============ 343 | 344 | Quake语法 345 | https://quake.360.cn/quake/#/help?id=5eb238f110d2e850d5c6aec8&title=%E6%A3%80%E7%B4%A2%E5%85%B3%E9%94%AE%E8%AF%8D 346 | ============ 347 | 348 | Shodan语法 349 | 例如:product:"nginx" city:"Beijing" port:"8088" 350 | https://www.shodan.io/search/filters 351 | https://www.shodan.io/search/examples 352 | ============ 353 | 354 | 355 | Hunter语法 356 | 例如:domain.suffix="'qianxin.com" && title="北京" 357 | https://hunter.qianxin.com/""" 358 | self.help_text.insert(tk.END, text) 359 | self.help_frame.rowconfigure(0, weight=1) 360 | self.help_frame.columnconfigure(0, weight=1) 361 | 362 | def setLanguage(self): 363 | if self.language == "en": 364 | # tab 365 | self.notebook.tab(0, text='Search') 366 | self.notebook.tab(1, text='Config') 367 | self.notebook.tab(2, text='Help') 368 | 369 | # search_tab 370 | self.label_thread['text'] = "Thread" 371 | self.label_mode['text'] = "Mode" 372 | self.label_page['text'] = "Page" 373 | self.label_saveMode['text'] = "SaveMode" 374 | self.label_searchEngine['text'] = "SearchEngine" 375 | self.label_searchQuery['text'] = "Query" 376 | self.START.configure(text='Search') 377 | self.mode_dict = { 378 | 'Fofa': ['Data', 'Account'], 379 | 'Shodan': ['Data'], 380 | 'Hunter': ['Data'], 381 | 'Zoomeye': ['HostSearch', 'Domain/IP', 'WebApp', 'Account'], 382 | 'Quake': ['HostSearch', 'Service', 'Account'] 383 | } 384 | self.update_mode_menu() 385 | self.save_mode_choice.set('NotSave') 386 | self.SAVE_MODE = tk.OptionMenu(self.search_frame, self.save_mode_choice, 'NotSave', 'File', 'mysql', 387 | 'sqlite') 388 | self.SAVE_MODE.grid(row=0, column=7) 389 | 390 | # config_tab 391 | self.label_zoomeyeUser['text'] = "Account" 392 | self.label_zoomeyePass['text'] = "Password" 393 | self.label_fofaEmail['text'] = "E-mail" 394 | self.label_fileConfig['text'] = "Save File Config" 395 | self.label_filePath['text'] = "FilePath" 396 | self.label_dbConfig['text'] = "DB Config(MySQL)" 397 | self.label_dbHost['text'] = "Host" 398 | self.label_dbPort['text'] = "Port" 399 | self.label_dbName['text'] = "dbName" 400 | self.label_dbUser['text'] = "User" 401 | self.label_dbPass['text'] = "Pass" 402 | self.label_sqliteConfig['text'] = "SqliteConfig" 403 | self.label_sqlite['text'] = "SqlitePath" 404 | self.label_otherConfig['text'] = "OtherConfig" 405 | self.SAVE.configure(text='SaveConfig') 406 | self.LOAD.configure(text='LoadConfig') 407 | self.CLEAR.configure(text='ClearConfig') 408 | self.TESTDB.configure(text='TestDB') 409 | self.CLEARTOKEN.configure(text='ClearToken') 410 | self.language_choice.set('en') 411 | 412 | self.help_text.delete("1.0", "end") 413 | text_en = """Guidance: 414 | 1. First fill in the options in the configuration tab (fill in optional) 415 | 2. After filling in the configuration, please click Save first. 416 | 3. Please fill in the absolute path for the file path, such as /Users/xxx/fofa.csv 417 | 4. The number of query pages multiplied by 10 under Fofa and Quake queries is the number of query data items. 418 | 5.Shodan single page search is 100 items 419 | 6.Hunter single page search is 20 items 420 | ============= 421 | 422 | Fofa syntax 423 | title="beijing" Search "Beijing" from the title 424 | header="elastic" Search for "elastic" from the http header 425 | body="Cyberspace Surveying and Mapping" Search for "Cyberspace Surveying and Mapping" from the HTML text 426 | domain="qq.com" searches for websites with the root domain name qq.com. 427 | icon_hash="-247388890" Search for assets using this icon. Only available to FOFA premium members 428 | host=".gov.cn" Search for ".gov.cn" from the url. Use host as the name for the search. 429 | port="6379" Find assets corresponding to the "6379" port 430 | icp="Beijing ICP Certificate No. 030173" Find the website with the registration number "Beijing ICP Certificate No. 030173" Search website type assets 431 | ip="1.1.1.1" Search for websites containing "1.1.1.1" from ip. Use ip as the name when searching. 432 | ip="220.181.111.1/24" Query the C network segment assets whose IP is "220.181.111.1" 433 | status_code="402" Query the assets with server status "402" 434 | protocol="quic" Query quic protocol assets and search for the specified protocol type (valid when port scanning is enabled) 435 | country="CN" searches for assets in the specified country (code). 436 | region="HeNan" searches for assets in the specified administrative region. 437 | city="HanDan" searches for assets in the specified city. 438 | cert="baidu" searches for assets with baidu in the certificate (https or imaps, etc.). 439 | cert.subject="Oracle Corporation" Searches for certificate holders that are assets of Oracle Corporation 440 | cert.issuer="DigiCert" searches for assets whose certificate issuer is DigiCert Inc. 441 | cert.is_valid=true Verifies whether the certificate is valid, true is valid, false is invalid, only available to FOFA senior members 442 | banner=users && protocol=ftp searches for assets with users text in the FTP protocol. 443 | type=service searches all protocol assets, supporting both subdomain and service. 444 | os="centos" searches operating systems for CentOS assets. 445 | server=="Microsoft-IIS/10" searches for IIS 10 servers. 446 | app="Microsoft-Exchange" Search for Microsoft-Exchange devices 447 | after="2017" && before="2017-10-01" time range search 448 | asn="19551" searches for assets with the specified asn. 449 | org="Amazon.com, Inc." searches for assets of the specified org (organization). 450 | base_protocol="udp" searches for assets with the specified udp protocol. 451 | is_fraud=false excludes counterfeit/fraudulent data 452 | is_honeypot=false excludes honeypot data, only available to FOFA premium members 453 | is_ipv6=true searches for ipv6 assets and only accepts true and false. 454 | is_domain=true searches domain name assets, only accepts true and false. 455 | port_size="6" Query the assets with the number of open ports equal to "6", only available to FOFA members 456 | port_size_gt="6" Query assets with open ports greater than "6", only available to FOFA members 457 | port_size_lt="12" Query assets with open ports less than "12", only available to FOFA members 458 | ip_ports="80,161" searches for ip assets that open ports 80 and 161 at the same time (asset data in ip units) 459 | ip_country="CN" searches for China's IP assets (asset data in IP units). 460 | ip_region="Zhejiang" searches for ip assets in the specified administrative region (asset data in ip units). 461 | ip_city="Hangzhou" searches for ip assets in the specified city (asset data in ip units). 462 | ip_after="2021-03-18" searches for ip assets after 2021-03-18 (asset data in ip units). 463 | ip_before="2019-09-09" searches for ip assets before 2019-09-09 (asset data in ip units). 464 | ============ 465 | 466 | Zoomeye syntax 467 | app:nginx Component name 468 | ver:1.0 Version 469 | os:windows Operating system 470 | country:"China" Country 471 | city:"hangzhou" City 472 | port:80 Port 473 | hostname:google  Hostname 474 | site:thief.one  Website domain name 475 | desc:nmask  Description 476 | keywords:nmask'blog  Keywords 477 | service:ftp  Service type 478 | ip:8.8.8.8  ip address 479 | cidr:8.8.8.8/24  IP address segment 480 | city:"beijing" port:80 satisfies two conditions at the same time 481 | ============ 482 | 483 | Quake syntax 484 | https://quake.360.cn/quake/#/help?id=5eb238f110d2e850d5c6aec8&title=%E6%A3%80%E7%B4%A2%E5%85%B3%E9%94%AE%E8%AF%8D 485 | ============ 486 | 487 | Shodan syntax 488 | For example: product: "nginx" city: "Beijing" port: "8088" 489 | https://www.shodan.io/search/filters 490 | https://www.shodan.io/search/examples 491 | ============ 492 | 493 | 494 | Hunter Grammar 495 | For example: domain.suffix="'qianxin.com" && title="Beijing" 496 | https://hunter.qianxin.com/""" 497 | self.help_text.insert(tk.END, text_en) 498 | 499 | elif self.language == "ch": 500 | self.language_choice.set('ch') 501 | 502 | def update_mode_menu(self, *args): 503 | modes = self.mode_dict[self.search_engine_choice.get()] 504 | self.query_mode_choice.set(modes[0]) 505 | menu = self.MODE['menu'] 506 | menu.delete(0, 'end') 507 | for mode in modes: 508 | menu.add_command(label=mode, command=lambda mode=mode: self.query_mode_choice.set(mode)) 509 | 510 | def _delete_config(self): # 清空显示 511 | self.ZOOMEYE_USERNAME.delete(0, tk.END) 512 | self.ZOOMEYE_PASSWORD.delete(0, tk.END) 513 | self.ZOOMEYE_API.delete(0, tk.END) 514 | self.FOFA_USERNAME.delete(0, tk.END) 515 | self.FOFA_API.delete(0, tk.END) 516 | self.QUAKE_API.delete(0, tk.END) 517 | self.SHODAN_API.delete(0, tk.END) 518 | self.HUNTER_API.delete(0, tk.END) 519 | self.FILE.delete(0, tk.END) 520 | self.DATABASE_HOST.delete(0, tk.END) 521 | self.DATABASE_PORT.delete(0, tk.END) 522 | self.DATABASE_DATABASE.delete(0, tk.END) 523 | self.DATABASE_USERNAME.delete(0, tk.END) 524 | self.DATABASE_PASSWORD.delete(0, tk.END) 525 | self.SQLITE_FILENAME.delete(0, tk.END) 526 | 527 | def _insert_config(self): # 插入数据 528 | self.ZOOMEYE_USERNAME.insert(tk.END, self.dic['zoomeye_username']) 529 | self.ZOOMEYE_PASSWORD.insert(tk.END, self.dic['zoomeye_password']) 530 | self.ZOOMEYE_API.insert(tk.END, self.dic['zoomeye_api']) 531 | self.FOFA_USERNAME.insert(tk.END, self.dic['fofa_username']) 532 | self.FOFA_API.insert(tk.END, self.dic['fofa_api']) 533 | self.QUAKE_API.insert(tk.END, self.dic['quake_api']) 534 | self.SHODAN_API.insert(tk.END, self.dic['shodan_api']) 535 | self.HUNTER_API.insert(tk.END, self.dic['hunter_api']) 536 | self.FILE.insert(tk.END, self.dic['file']) 537 | self.DATABASE_HOST.insert(tk.END, self.dic['host']) 538 | self.DATABASE_PORT.insert(tk.END, self.dic['port']) 539 | self.DATABASE_DATABASE.insert(tk.END, self.dic['database']) 540 | self.DATABASE_USERNAME.insert(tk.END, self.dic['username']) 541 | self.DATABASE_PASSWORD.insert(tk.END, self.dic['password']) 542 | self.SQLITE_FILENAME.insert(tk.END, self.dic['sqlitepath']) 543 | 544 | def _save_config(self): # 保存数据 545 | self.dic = { 546 | 'language': "en" if not os.path.exists('config.json') else self.language_choice.get(), 547 | 'zoomeye_username': self.ZOOMEYE_USERNAME.get(), 548 | 'zoomeye_password': self.ZOOMEYE_PASSWORD.get(), 549 | 'zoomeye_api': self.ZOOMEYE_API.get(), 550 | 'fofa_username': self.FOFA_USERNAME.get(), 551 | 'fofa_api': self.FOFA_API.get(), 552 | 'quake_api': self.QUAKE_API.get(), 553 | 'shodan_api': self.SHODAN_API.get(), 554 | 'hunter_api': self.HUNTER_API.get(), 555 | 'file': self.FILE.get(), 556 | 'host': self.DATABASE_HOST.get(), 557 | 'port': self.DATABASE_PORT.get(), 558 | 'database': self.DATABASE_DATABASE.get(), 559 | 'username': self.DATABASE_USERNAME.get(), 560 | 'password': self.DATABASE_PASSWORD.get(), 561 | 'sqlitepath': self.SQLITE_FILENAME.get() 562 | } 563 | with open("config.json", "w", encoding='utf8') as f: 564 | json.dump(self.dic, f) 565 | 566 | def load_config(self): # 读取配置 567 | with open("config.json", "r", encoding='utf8') as f: 568 | self.dic = json.load(f) 569 | self._delete_config() 570 | self._insert_config() 571 | 572 | def save_config(self): # 保存配置 573 | try: 574 | self._save_config() 575 | except Exception as e: 576 | messagebox.showerror('Error', f'Fail to save。Info:{traceback.format_exc()}') 577 | else: 578 | messagebox.showinfo('Success', 'Success to save.\nIf you change Language, please restart the program.') 579 | 580 | def clear_config(self): 581 | self._delete_config() 582 | try: 583 | self._save_config() 584 | except Exception as e: 585 | messagebox.showerror("Error", traceback.format_exc()) 586 | else: 587 | messagebox.showinfo("Success", "Success to clear.") 588 | 589 | def db_test(self): 590 | # mysql 591 | try: 592 | pymysql.connect(database=self.dic['database'], host=self.dic['host'], port=int(self.dic['port']), 593 | user=self.dic['username'], password=self.dic['password']) 594 | except Exception as e: 595 | messagebox.showerror(title='Error', message=f'mysql数据库连接失败。 {e}') 596 | else: 597 | messagebox.showinfo(title='Success', message='mysql数据库连接成功') 598 | 599 | # sqlite 600 | try: 601 | if self.dic['sqlitepath'] == "": 602 | raise ValueError("Empty path for sqlite!") 603 | sqlite3.connect(self.dic['sqlitepath']) 604 | except Exception as e: 605 | messagebox.showerror(title='Error', message=f'sqlite数据库连接失败。 {e}') 606 | else: 607 | 608 | messagebox.showinfo(title='Success', message='sqlite数据库连接成功') 609 | 610 | def delete_tree(self, tree): 611 | for item in tree.get_children(): 612 | tree.delete(item) 613 | 614 | def delete_access_token(self): 615 | try: 616 | system(f"rm zoomeye_access_token.txt") 617 | except Exception as e: 618 | messagebox.showerror('Error', e) 619 | return None 620 | 621 | def log_insert(self, str): # update log 622 | self.LOG.insert(tk.END, chars=str) 623 | self.LOG.see(tk.END) 624 | 625 | def login(self): 626 | if self.search_engine_choice.get() == "Zoomeye": 627 | # 1.查看是否存在zoomeye_access_token.txt 628 | if os.path.exists('zoomeye_access_token.txt'): 629 | with open("zoomeye_access_token.txt", "r", encoding="utf-8") as f: 630 | access_token = f.read().strip('\n') 631 | self.zoomeye_headers = { 632 | 'Authorization': 'JWT ' + access_token 633 | } 634 | self.log_insert("[+] Load file 'access_token.txt' successfully.\n") 635 | self.log_insert( 636 | f"[+] Access_Token: {self.zoomeye_headers['Authorization'][:7] + '*' * 10 + self.zoomeye_headers['Authorization'][-3:]}\n") 637 | # 2.通过api-key/username-password登录 638 | else: 639 | if self.dic['zoomeye_api'] != "": 640 | self.zoomeye_api_key_login(self.dic['zoomeye_api']) 641 | elif self.dic['zoomeye_username'] != "" and self.dic['zoomeye_password'] != "": 642 | self.zoomeye_user_pass_login(self.dic['zoomeye_username'], self.dic['zoomeye_password']) 643 | else: 644 | messagebox.showerror(title='Error', message='请填写Zoomeye配置信息' if self.language == 'ch' else 'Please fill in Zoomeye config.') 645 | 646 | def zoomeye_api_key_login(self, api_key): 647 | self.zoomeye_headers = { 648 | "API-KEY": api_key 649 | } 650 | 651 | def zoomeye_user_pass_login(self, username, password): 652 | url = 'https://api.zoomeye.org/user/login' 653 | data = { 654 | 'username': username, 655 | 'password': password 656 | } 657 | encoded_data = json.dumps(data) 658 | resp_json = requests.post(url=url, data=encoded_data).json() 659 | # print(resp_json) 660 | try: 661 | access_token = resp_json['access_token'] 662 | except: 663 | self.log_insert(f'[-] Login fail. {resp_json["message"]}. {resp_json["error"]}.\n') 664 | else: 665 | self.log_insert('[+] Login success!\n') 666 | with open("zoomeye_access_token.txt", "w", encoding="utf-8") as f: 667 | f.write(access_token) 668 | self.zoomeye_headers = { 669 | 'Authorization': 'JWT ' + access_token 670 | } 671 | self.log_insert( 672 | f"[+] Access_Token: {self.zoomeye_headers['Authorization'][:7] + '*' * 10 + self.zoomeye_headers['Authorization'][-3:]}\n") 673 | 674 | def thread(self): 675 | if self.query_mode_choice.get() in ["个人信息", "Account"] or self.QUERY.get() != "": 676 | if self.save_mode_choice.get() == "mysql": 677 | try: 678 | self.conn = pymysql.connect(database=self.dic['database'], host=self.dic['host'], 679 | port=int(self.dic['port']), user=self.dic['username'], 680 | password=self.dic['password']) 681 | except Exception as e: 682 | messagebox.showerror(title='Error', message=f'[!] mysql数据库连接失败! {e}' if self.language == 'ch' else f'[!] Fail to connect to MySQL. {e}') 683 | return 684 | else: 685 | self.log_insert('[+] 数据库连接成功\n' if self.language == 'ch' else '[+] Connect to MySQL successfully.\n') 686 | self.cursor = self.conn.cursor() 687 | elif self.save_mode_choice.get() in ["存文件", "File"] and self.FILE.get() == "": 688 | messagebox.showerror(title='Error', message='[!] 文件名为空!' if self.language == 'ch' else '[!] File name empty.') 689 | return 690 | t = Thread(target=self.run, daemon=True) 691 | t.start() 692 | else: 693 | messagebox.showerror(title='Error', message='[!] 查询语句为空!' if self.language == 'ch' else '[!] Query empty.') 694 | 695 | def run(self): 696 | if self.save_mode_choice.get() == "sqlite": 697 | try: 698 | self.sqlite_conn = sqlite3.connect(self.dic['sqlitepath']) 699 | except Exception as e: 700 | messagebox.showerror(title='Error', message=f'[!] Fail to connect to sqlite! {e}') 701 | return 702 | else: 703 | self.log_insert('[+] Success to connect to sqlite\n') 704 | self.sqlite_cursor = self.sqlite_conn.cursor() 705 | # zoomeye 706 | if self.search_engine_choice.get() == "Zoomeye": 707 | self.log_insert('[Zoomeye] Start searching...\n') 708 | if self.query_mode_choice.get() not in ['个人信息', 'Account']: 709 | self.delete_tree(self.TREEVIEW) 710 | self.login() 711 | query = self.QUERY.get().replace(" ", '%20') 712 | 713 | if self.query_mode_choice.get() in ['主机搜索', 'HostSearch']: 714 | zoomeye_hostsearch.headers = self.zoomeye_headers 715 | error_info = zoomeye_hostsearch.host_search(query=query, page=self.page_choice.get(), 716 | thread=int(self.thread_choice.get())) 717 | if error_info is not None: 718 | self.log_insert(f'[!] Error: {error_info}\n') 719 | 720 | j = 1 721 | if self.save_mode_choice.get() == "mysql": 722 | self.cursor.execute('''CREATE TABLE if not exists zoomeye_host_search( 723 | ip text,port text,os text,app text,version text,title text,city text,country text,continents text);''') 724 | self.conn.commit() 725 | for each_dic in zoomeye_hostsearch.info_list: 726 | self.TREEVIEW.insert("", tk.END, values=( 727 | j, each_dic['ip'], each_dic['port'], each_dic['os'], each_dic['title'])) 728 | self.cursor.execute( 729 | f'''INSERT INTO zoomeye_host_search VALUES("{each_dic["ip"]}","{each_dic["port"]}","{each_dic["os"]}","{each_dic["app"]}","{each_dic["version"]}","{each_dic["title"]}","{each_dic["city"]}","{each_dic["country"]}","{each_dic["continents"]}")''') 730 | self.conn.commit() 731 | j += 1 732 | elif self.save_mode_choice.get() == "sqlite": 733 | self.sqlite_cursor.execute('''CREATE TABLE if not exists zoomeye_host_search( 734 | ip text,port text,os text,app text,version text,title text,city text,country text,continents text);''') 735 | self.sqlite_conn.commit() 736 | for each_dic in zoomeye_hostsearch.info_list: 737 | self.TREEVIEW.insert("", tk.END, values=( 738 | j, each_dic['ip'], each_dic['port'], each_dic['os'], each_dic['title'])) 739 | self.sqlite_cursor.execute( 740 | f'''INSERT INTO zoomeye_host_search VALUES("{each_dic["ip"]}","{each_dic["port"]}","{each_dic["os"]}","{each_dic["app"]}","{each_dic["version"]}","{each_dic["title"]}","{each_dic["city"]}","{each_dic["country"]}","{each_dic["continents"]}")''') 741 | self.sqlite_conn.commit() 742 | j += 1 743 | elif self.save_mode_choice.get() in ["存文件", "File"]: 744 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 745 | f.write("ip,port,os,app,version,title,city,country,continents\n") 746 | for each_dic in zoomeye_hostsearch.info_list: 747 | self.TREEVIEW.insert("", tk.END, values=( 748 | j, each_dic['ip'], each_dic['port'], each_dic['os'], each_dic['title'])) 749 | f.write( 750 | f'''{each_dic["ip"]},{each_dic["port"]},{each_dic["os"]},{each_dic["app"]},{each_dic["version"]},{each_dic["title"]},{each_dic["city"]},{each_dic["country"]},{each_dic["continents"]}\n''') 751 | j += 1 752 | elif self.save_mode_choice.get() in ["不保存", "NotSave"]: 753 | for each_dic in zoomeye_hostsearch.info_list: 754 | self.TREEVIEW.insert("", tk.END, values=( 755 | j, each_dic['ip'], each_dic['port'], each_dic['os'], each_dic['title'])) 756 | j += 1 757 | 758 | if self.query_mode_choice.get() in ['域名/IP', 'Domain/IP']: 759 | zoomeye_domain_ip.headers = self.zoomeye_headers 760 | error_info = zoomeye_domain_ip.domain_ip(query=query, page=self.page_choice.get(), 761 | thread=int(self.thread_choice.get())) 762 | if error_info is not None: 763 | self.log_insert(f'[!] Error: {error_info}\n') 764 | 765 | j = 1 766 | if self.save_mode_choice.get() == "mysql": 767 | self.cursor.execute('''CREATE TABLE if not exists zoomeye_domain_ip( 768 | ip text,name text);''') 769 | self.conn.commit() 770 | for each_dic in zoomeye_domain_ip.info_list: 771 | self.TREEVIEW.insert("", tk.END, values=(j, each_dic['ip'], each_dic['name'], None)) 772 | self.cursor.execute( 773 | f'''INSERT INTO zoomeye_domain_ip VALUES("{each_dic['ip']}","{each_dic['name']}")''') 774 | self.conn.commit() 775 | j += 1 776 | elif self.save_mode_choice.get() == "sqlite": 777 | self.sqlite_cursor.execute('''CREATE TABLE if not exists zoomeye_domain_ip( 778 | ip text,name text);''') 779 | self.sqlite_conn.commit() 780 | for each_dic in zoomeye_domain_ip.info_list: 781 | self.TREEVIEW.insert("", tk.END, values=(j, each_dic['ip'], each_dic['name'], None)) 782 | self.sqlite_cursor.execute( 783 | f'''INSERT INTO zoomeye_domain_ip VALUES("{each_dic['ip']}","{each_dic['name']}")''') 784 | self.sqlite_conn.commit() 785 | j += 1 786 | elif self.save_mode_choice.get() in ["存文件", "File"]: 787 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 788 | f.write("ip ,name\n") 789 | for each_dic in zoomeye_domain_ip.info_list: 790 | self.TREEVIEW.insert("", tk.END, values=(j, each_dic['ip'], each_dic['name'], None)) 791 | f.write(f'''{str(each_dic["ip"])},{each_dic["name"]}\n''') 792 | j += 1 793 | elif self.save_mode_choice.get() in ["不保存", "NotSave"]: 794 | for each_dic in zoomeye_domain_ip.info_list: 795 | self.TREEVIEW.insert("", tk.END, values=(j, each_dic['ip'], each_dic['name'], None)) 796 | j += 1 797 | 798 | if self.query_mode_choice.get() in ['web应用', 'WebApp']: 799 | zoomeye_websearch.headers = self.zoomeye_headers 800 | error_info = zoomeye_websearch.web_search(query=query, page=self.page_choice.get(), 801 | thread=int(self.thread_choice.get())) 802 | if error_info is not None: 803 | self.log_insert(f'[!] Error: {error_info}\n') 804 | 805 | j = 1 806 | if self.save_mode_choice.get() == "mysql": 807 | self.cursor.execute('''CREATE TABLE if not exists zoomeye_web_search( 808 | ip text,site text,title text,city text,country text,continent text,isp text);''') 809 | self.conn.commit() 810 | for each_dic in zoomeye_websearch.info_list: 811 | self.TREEVIEW.insert("", tk.END, 812 | values=(j, each_dic['ip'], each_dic['site'], '', each_dic['title'])) 813 | self.cursor.execute( 814 | f'''INSERT INTO zoomeye_web_search VALUES("{each_dic["ip"]}","{each_dic["site"]}","{each_dic["title"]}","{each_dic["city"]}","{each_dic["country"]}","{each_dic["continent"]}","{each_dic['isp']}");''') 815 | self.conn.commit() 816 | j += 1 817 | elif self.save_mode_choice.get() == "sqlite": 818 | self.sqlite_cursor.execute('''CREATE TABLE if not exists zoomeye_web_search( 819 | ip text,site text,title text,city text,country text,continent text,isp text);''') 820 | self.sqlite_conn.commit() 821 | for each_dic in zoomeye_websearch.info_list: 822 | self.TREEVIEW.insert("", tk.END, 823 | values=(j, each_dic['ip'], each_dic['site'], '', each_dic['title'])) 824 | self.sqlite_cursor.execute( 825 | f'''INSERT INTO zoomeye_web_search VALUES("{each_dic["ip"]}","{each_dic["site"]}","{each_dic["title"]}","{each_dic["city"]}","{each_dic["country"]}","{each_dic["continent"]}","{each_dic['isp']}");''') 826 | self.sqlite_conn.commit() 827 | j += 1 828 | elif self.save_mode_choice.get() in ["存文件", "File"]: 829 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 830 | f.write("ip ,site ,title ,city ,country ,continents, isp\n") 831 | for each_dic in zoomeye_websearch.info_list: 832 | self.TREEVIEW.insert("", tk.END, 833 | values=(j, each_dic['ip'], each_dic['site'], '', each_dic['title'])) 834 | f.write( 835 | f'''{each_dic["ip"]},{each_dic["site"]},{each_dic["title"]},{each_dic["city"]},{each_dic["country"]},{each_dic["continent"]},{each_dic['isp']}\n''') 836 | j += 1 837 | elif self.save_mode_choice.get() in ["不保存", "NotSave"]: 838 | for each_dic in zoomeye_websearch.info_list: 839 | self.TREEVIEW.insert("", tk.END, 840 | values=(j, each_dic['ip'], each_dic['site'], '', each_dic['title'])) 841 | j += 1 842 | 843 | zoomeye_resource.headers = self.zoomeye_headers 844 | if self.query_mode_choice.get() in ['个人信息', 'Account']: 845 | self.log_insert(zoomeye_resource.resource(mode="complete")) 846 | else: 847 | self.log_insert( 848 | f'[Zoomeye] End of search. Complete information has been stored by mode "{self.save_mode_choice.get()}".\n') 849 | self.log_insert(zoomeye_resource.resource(mode="easy")) 850 | 851 | # fofa 852 | elif self.search_engine_choice.get() == "Fofa": 853 | self.log_insert('[Fofa] Start searching...\n') 854 | if self.query_mode_choice.get() in ["个人信息", "Account"]: 855 | text = fofa_resource.fofa_search_resource(email=self.dic['fofa_username'], key=self.dic['fofa_api']) 856 | self.log_insert(text) 857 | else: 858 | self.delete_tree(self.TREEVIEW) 859 | error_info = fofa_search_all.fofa_search(email=self.dic['fofa_username'], key=self.dic['fofa_api'], 860 | query=str(self.QUERY.get()), 861 | size=int(self.page_choice.get()) * 10) 862 | if error_info is not None: 863 | self.log_insert(f'[!] Error: {error_info}\n') 864 | 865 | j = 1 866 | for each_dic in fofa_search_all.info_list: 867 | self.TREEVIEW.insert("", tk.END, values=( 868 | j, each_dic['ip'], each_dic['port'] + "/" + each_dic['domain'], each_dic['os'], 869 | each_dic['title'])) 870 | j += 1 871 | if self.save_mode_choice.get() == "mysql": 872 | self.cursor.execute('''CREATE TABLE if not exists fofa_search_all( 873 | ip text,port text,host text,domain text,os text,server text,title text,protocol text,country_name text,region text,city text,as_organization text,icp text,jarm text);''') 874 | self.conn.commit() 875 | for each in fofa_search_all.info_list: 876 | self.cursor.execute(f'''insert into fofa_search_all values( 877 | "{each['ip']}","{each['port']}","{each['host']}","{each['domain']}","{each['os']}","{each['server']}","{each['title']}","{each['protocol']}","{each['country_name']}","{each['region']}","{each['city']}","{each['as_organization']}","{each['icp']}","{each['jarm']}" 878 | )''') 879 | self.conn.commit() 880 | elif self.save_mode_choice.get() == "sqlite": 881 | self.sqlite_cursor.execute('''CREATE TABLE if not exists fofa_search_all( 882 | ip text,port text,host text,domain text,os text,server text,title text,protocol text,country_name text,region text,city text,as_organization text,icp text,jarm text);''') 883 | self.sqlite_conn.commit() 884 | for each in fofa_search_all.info_list: 885 | self.sqlite_cursor.execute(f'''insert into fofa_search_all values( 886 | "{each['ip']}","{each['port']}","{each['host']}","{each['domain']}","{each['os']}","{each['server']}","{each['title']}","{each['protocol']}","{each['country_name']}","{each['region']}","{each['city']}","{each['as_organization']}","{each['icp']}","{each['jarm']}" 887 | )''') 888 | self.sqlite_conn.commit() 889 | elif self.save_mode_choice.get() in ["存文件", "File"]: 890 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 891 | f.write( 892 | "ip,port,host,domain,os,server,title,protocol,country_name,region,city,as_organization,icp,jarm\n") 893 | for each_dic in fofa_search_all.info_list: 894 | f.write( 895 | f"{each_dic['ip']},{each_dic['port']},{each_dic['host']},{each_dic['domain']},{each_dic['os']},{each_dic['server']},{each_dic['title']},{each_dic['protocol']},{each_dic['country_name']},{each_dic['region']},{each_dic['city']},{each_dic['as_organization']},{each_dic['icp']},{each_dic['jarm']}\n") 896 | elif self.save_mode_choice.get() in ["不保存", "NotSave"]: 897 | pass 898 | self.log_insert( 899 | f'[Fofa] End of Search. Obtain totally {fofa_search_all.total_num}\nComplete information has been stored by mode "{self.save_mode_choice.get()}".\n') 900 | 901 | # quake 902 | elif self.search_engine_choice.get() == "Quake": 903 | self.log_insert('[Quake] Start searching...\n') 904 | query = self.QUERY.get() 905 | if self.query_mode_choice.get() not in ['个人信息', 'Account']: 906 | self.delete_tree(self.TREEVIEW) 907 | if self.query_mode_choice.get() in ["主机搜索", "HostSearch"]: 908 | error_info = quake_hostsearch.quake_host_search(query=query, page=self.page_choice.get(), 909 | key=self.dic['quake_api']) 910 | if error_info is not None: 911 | self.log_insert(f'[!] Error: {error_info}\n') 912 | 913 | j = 1 914 | if self.save_mode_choice.get() in ["不保存", "NotSave"]: 915 | for each_dic in quake_hostsearch.info_list: 916 | self.TREEVIEW.insert("", tk.END, values=( 917 | j, each_dic['ip'], each_dic['service_port'], each_dic['os_name'], '')) 918 | j += 1 919 | elif self.save_mode_choice.get() in ["存文件", "File"]: 920 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 921 | f.write( 922 | "ip,service_port,service_name,service_version,service_id,domains,hostname,os_name,os_version,country_en,city_en\n") 923 | for each_dic in quake_hostsearch.info_list: 924 | f.write( 925 | f'''{each_dic['ip']},{each_dic['service_port']},{each_dic['service_name']},{each_dic['service_version']},{each_dic['service_id']},{each_dic['domains']},{each_dic['hostname']},{each_dic['os_name']},{each_dic['os_version']},{each_dic['country_en']},{each_dic['city_en']}\n''') 926 | self.TREEVIEW.insert("", tk.END, values=( 927 | j, each_dic['ip'], each_dic['service_port'], each_dic['os_name'], '')) 928 | j += 1 929 | elif self.save_mode_choice.get() == "mysql": 930 | self.cursor.execute('''CREATE TABLE if not exists quake_host_search( 931 | ip text,service_port text,service_name text,service_version text,service_id text,domains text,hostname text,os_name text,os_version text,country_en text,city_en text);''') 932 | self.conn.commit() 933 | for each_dic in quake_hostsearch.info_list: 934 | self.TREEVIEW.insert("", tk.END, values=( 935 | j, each_dic['ip'], each_dic['service_port'], each_dic['os_name'], '')) 936 | self.cursor.execute( 937 | f'''INSERT INTO quake_host_search VALUES("{each_dic["ip"]}","{each_dic["service_port"]}","{each_dic["service_name"]}","{each_dic["service_version"]}","{each_dic["service_id"]}","{each_dic["domains"]}","{each_dic["hostname"]}","{each_dic["os_name"]}","{each_dic["os_version"]}","{each_dic["country_en"]}","{each_dic["city_en"]}")''') 938 | self.conn.commit() 939 | j += 1 940 | elif self.save_mode_choice.get() == "sqlite": 941 | self.sqlite_cursor.execute('''CREATE TABLE if not exists quake_host_search( 942 | ip text,service_port text,service_name text,service_version text,service_id text,domains text,hostname text,os_name text,os_version text,country_en text,city_en text);''') 943 | self.sqlite_conn.commit() 944 | for each_dic in quake_hostsearch.info_list: 945 | self.TREEVIEW.insert("", tk.END, values=( 946 | j, each_dic['ip'], each_dic['service_port'], each_dic['os_name'], '')) 947 | self.sqlite_cursor.execute( 948 | f'''INSERT INTO quake_host_search VALUES("{each_dic["ip"]}","{each_dic["service_port"]}","{each_dic["service_name"]}","{each_dic["service_version"]}","{each_dic["service_id"]}","{each_dic["domains"]}","{each_dic["hostname"]}","{each_dic["os_name"]}","{each_dic["os_version"]}","{each_dic["country_en"]}","{each_dic["city_en"]}")''') 949 | self.sqlite_conn.commit() 950 | j += 1 951 | if self.query_mode_choice.get() in ["服务搜索", "Service"]: 952 | error_info = quake_servicesearch.quake_service_search(query=query, page=self.page_choice.get(), 953 | key=self.dic['quake_api']) 954 | if error_info is not None: 955 | self.log_insert(f'[!] Error: {error_info}\n') 956 | 957 | j = 1 958 | if self.save_mode_choice.get() in ["不保存", "NotSave"]: 959 | for each_dic in quake_servicesearch.info_list: 960 | self.TREEVIEW.insert("", tk.END, values=( 961 | j, each_dic['ip'], each_dic['port'], each_dic['os_name'], each_dic['service_title'])) 962 | j += 1 963 | elif self.save_mode_choice.get() in ["存文件", "File"]: 964 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 965 | f.write( 966 | "ip,port,org,hostname,service_title,service_server,transport,os_name,os_version,service_name,country_en,city_en\n") 967 | for each_dic in quake_servicesearch.info_list: 968 | f.write( 969 | f'''{each_dic['ip']},{each_dic['port']},{each_dic['org']},{each_dic['hostname']},{each_dic['service_title']},{each_dic['service_server']},{each_dic['transport']},{each_dic['os_name']},{each_dic['os_version']},{each_dic['service_name']},{each_dic['country_en']},{each_dic['city_en']}\n''') 970 | self.TREEVIEW.insert("", tk.END, values=( 971 | j, each_dic['ip'], each_dic['port'], each_dic['os_name'], each_dic['service_title'])) 972 | j += 1 973 | elif self.save_mode_choice.get() == "mysql": 974 | self.cursor.execute('''CREATE TABLE if not exists quake_service_search( 975 | ip text,port text,org text,hostname text,service_title text,service_server text,transport text,os_name text,service_name text,country_en text,city_en text,os_version text);''') 976 | self.conn.commit() 977 | for each_dic in quake_servicesearch.info_list: 978 | self.TREEVIEW.insert("", tk.END, values=( 979 | j, each_dic['ip'], each_dic['port'], each_dic['os_name'], each_dic['service_title'])) 980 | self.cursor.execute( 981 | f'''INSERT INTO quake_service_search VALUES("{each_dic["ip"]}","{each_dic["port"]}","{each_dic["org"]}","{each_dic["hostname"]}","{each_dic["service_title"]}","{each_dic["service_server"]}","{each_dic["transport"]}","{each_dic["os_name"]}","{each_dic["service_name"]}","{each_dic["country_en"]}","{each_dic["city_en"]}","{each_dic["os_version"]}")''') 982 | self.conn.commit() 983 | j += 1 984 | elif self.save_mode_choice.get() == "sqlite": 985 | self.sqlite_cursor.execute('''CREATE TABLE if not exists quake_service_search( 986 | ip text,port text,org text,hostname text,service_title text,service_server text,transport text,os_name text,service_name text,country_en text,city_en text,os_version text);''') 987 | self.sqlite_conn.commit() 988 | for each_dic in quake_servicesearch.info_list: 989 | self.TREEVIEW.insert("", tk.END, values=( 990 | j, each_dic['ip'], each_dic['port'], each_dic['os_name'], each_dic['service_title'])) 991 | self.sqlite_cursor.execute( 992 | f'''INSERT INTO quake_service_search VALUES("{each_dic["ip"]}","{each_dic["port"]}","{each_dic["org"]}","{each_dic["hostname"]}","{each_dic["service_title"]}","{each_dic["service_server"]}","{each_dic["transport"]}","{each_dic["os_name"]}","{each_dic["service_name"]}","{each_dic["country_en"]}","{each_dic["city_en"]}","{each_dic["os_version"]}")''') 993 | self.sqlite_conn.commit() 994 | j += 1 995 | if self.query_mode_choice.get() in ['个人信息', 'Account']: 996 | text = quake_resource.quake_resource_search(key=self.dic['quake_api'], mode="complete") 997 | self.log_insert(text) 998 | else: 999 | self.log_insert( 1000 | f'[Quake] End of search. Complete information has been stored by mode "{self.save_mode_choice.get()}".\n') 1001 | self.log_insert(quake_resource.quake_resource_search(key=self.dic['quake_api'], mode="easy")) 1002 | 1003 | # Shodan 1004 | elif self.search_engine_choice.get() == "Shodan": 1005 | self.delete_tree(self.TREEVIEW) 1006 | self.log_insert('[Shodan] Start searching...\n') 1007 | query = self.QUERY.get() 1008 | if self.query_mode_choice.get() in ["数据搜索", "Data"]: 1009 | error_info = shodan_search.search(key=self.dic['shodan_api'], query=str(query), 1010 | page=self.page_choice.get()) 1011 | if error_info is not None: 1012 | self.log_insert(f'[!] Error: {error_info}\n') 1013 | 1014 | j = 1 1015 | if self.save_mode_choice.get() in ["不保存", "NotSave"]: 1016 | for each_dic in shodan_search.info_list: 1017 | self.TREEVIEW.insert("", tk.END, values=( 1018 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domains']), each_dic['os'], 1019 | each_dic['title'])) 1020 | j += 1 1021 | elif self.save_mode_choice.get() in ["存文件", "File"]: 1022 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 1023 | f.write( 1024 | "ip,port,domains,title,product,os,info,isp,country,city,timestamp\n") 1025 | for each_dic in shodan_search.info_list: 1026 | f.write( 1027 | f'''{each_dic['ip']},{each_dic['port']},{each_dic['domains']},{each_dic['title']},{each_dic['product']},{each_dic['os']},{each_dic['info']},{each_dic['isp']},{each_dic['country']},{each_dic['city']},{each_dic['timestamp']}\n''') 1028 | self.TREEVIEW.insert("", tk.END, values=( 1029 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domains']), 1030 | each_dic['os'], 1031 | each_dic['title'])) 1032 | j += 1 1033 | elif self.save_mode_choice.get() == "mysql": 1034 | self.cursor.execute('''CREATE TABLE if not exists shodan_search( 1035 | ip text,port text,domains text,title text,product text,os text,info text,isp text,country text,city text,timestamp text);''') 1036 | self.conn.commit() 1037 | for each_dic in shodan_search.info_list: 1038 | self.TREEVIEW.insert("", tk.END, values=( 1039 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domains']), each_dic['os'], 1040 | each_dic['title'])) 1041 | self.cursor.execute( 1042 | f'''INSERT INTO shodan_search VALUES("{each_dic["ip"]}","{each_dic["port"]}","{each_dic["domains"]}","{each_dic["title"]}","{each_dic["product"]}","{each_dic["os"]}","{each_dic["info"]}","{each_dic["isp"]}","{each_dic["country"]}","{each_dic["city"]}","{each_dic["timestamp"]}")''') 1043 | self.conn.commit() 1044 | j += 1 1045 | elif self.save_mode_choice.get() == "sqlite": 1046 | self.sqlite_cursor.execute('''CREATE TABLE if not exists shodan_search( 1047 | ip text,port text,domains text,title text,product text,os text,info text,isp text,country text,city text,timestamp text);''') 1048 | self.sqlite_conn.commit() 1049 | for each_dic in shodan_search.info_list: 1050 | self.TREEVIEW.insert("", tk.END, values=( 1051 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domains']), each_dic['os'], 1052 | each_dic['title'])) 1053 | self.sqlite_cursor.execute( 1054 | f'''INSERT INTO shodan_search VALUES("{each_dic["ip"]}","{each_dic["port"]}","{each_dic["domains"]}","{each_dic["title"]}","{each_dic["product"]}","{each_dic["os"]}","{each_dic["info"]}","{each_dic["isp"]}","{each_dic["country"]}","{each_dic["city"]}","{each_dic["timestamp"]}")''') 1055 | self.sqlite_conn.commit() 1056 | j += 1 1057 | 1058 | self.log_insert( 1059 | f'[Shodan] End of search. Complete information has been stored by mode "{self.save_mode_choice.get()}".\n') 1060 | 1061 | # Hunter 1062 | elif self.search_engine_choice.get() == "Hunter": 1063 | self.delete_tree(self.TREEVIEW) 1064 | self.log_insert('[Hunter] Start searching...\n') 1065 | query = self.QUERY.get() 1066 | if self.query_mode_choice.get() in ["数据搜索", "Data"]: 1067 | error_info = hunter_search.search(key=self.dic['hunter_api'], query=str(query), 1068 | page=self.page_choice.get()) 1069 | if error_info is not None: 1070 | self.log_insert(f'[!] Error: {error_info}\n') 1071 | 1072 | j = 1 1073 | if self.save_mode_choice.get() in ["不保存", "NotSave"]: 1074 | for each_dic in hunter_search.info_list: 1075 | self.TREEVIEW.insert("", tk.END, values=( 1076 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domain']), each_dic['os'], 1077 | each_dic['web_title'])) 1078 | j += 1 1079 | elif self.save_mode_choice.get() in ["存文件", "File"]: 1080 | with open(self.FILE.get(), "w", encoding="utf-8") as f: 1081 | f.write( 1082 | "url,ip,port,web_title,domain,is_risk_protocol,protocol,base_protocol,status_code,component,os,company,number,country,province,city,updated_at,is_web,as_org,isp,vul_list,is_risk\n") 1083 | for each_dic in hunter_search.info_list: 1084 | f.write( 1085 | f'''{each_dic['url']},{each_dic['ip']},{each_dic['port']},{each_dic['web_title']},{each_dic['domain']},{each_dic['is_risk_protocol']},{each_dic['protocol']},{each_dic['base_protocol']},{each_dic['status_code']},{each_dic['component']},{each_dic['os']},{each_dic['company']},{each_dic['number']},{each_dic['country']},{each_dic['province']},{each_dic['city']},{each_dic['updated_at']},{each_dic['is_web']},{each_dic['as_org']},{each_dic['isp']},{each_dic['vul_list']},{each_dic['is_risk']}\n''') 1086 | self.TREEVIEW.insert("", tk.END, values=( 1087 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domain']), 1088 | each_dic['os'], 1089 | each_dic['web_title'])) 1090 | j += 1 1091 | elif self.save_mode_choice.get() == "mysql": 1092 | self.cursor.execute('''CREATE TABLE if not exists hunter_search( 1093 | url text, ip text, port text, web_title text, domain text, is_risk_protocol text, protocol text, base_protocol text, status_code text, component text, os text, company text, number text, country text, province text, city text, updated_at text, is_web text, as_org text, isp text, vul_list text, is_risk text);''') 1094 | self.conn.commit() 1095 | for each_dic in hunter_search.info_list: 1096 | self.TREEVIEW.insert("", tk.END, values=( 1097 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domain']), each_dic['os'], 1098 | each_dic['web_title'])) 1099 | self.cursor.execute( 1100 | f'''INSERT INTO hunter_search VALUES("{each_dic['url']}","{each_dic['ip']}","{each_dic['port']}","{each_dic['web_title']}","{each_dic['domain']}","{each_dic['is_risk_protocol']}","{each_dic['protocol']}","{each_dic['base_protocol']}","{each_dic['status_code']}","{each_dic['component']}","{each_dic['os']}","{each_dic['company']}","{each_dic['number']}","{each_dic['country']}","{each_dic['province']}","{each_dic['city']}","{each_dic['updated_at']}","{each_dic['is_web']}","{each_dic['as_org']}","{each_dic['isp']}","{each_dic['vul_list']}","{each_dic['is_risk']}")''') 1101 | self.conn.commit() 1102 | j += 1 1103 | elif self.save_mode_choice.get() == "sqlite": 1104 | self.sqlite_cursor.execute('''CREATE TABLE if not exists hunter_search( 1105 | url text, ip text, port text, web_title text, domain text, is_risk_protocol text, protocol text, base_protocol text, status_code text, component text, os text, company text, number text, country text, province text, city text, updated_at text, is_web text, as_org text, isp text, vul_list text, is_risk);''') 1106 | self.sqlite_conn.commit() 1107 | for each_dic in hunter_search.info_list: 1108 | self.TREEVIEW.insert("", tk.END, values=( 1109 | j, each_dic['ip'], str(each_dic['port']) + "/" + str(each_dic['domain']), each_dic['os'], 1110 | each_dic['web_title'])) 1111 | self.sqlite_cursor.execute( 1112 | f'''INSERT INTO hunter_search VALUES("{each_dic['url']}","{each_dic['ip']}","{each_dic['port']}","{each_dic['web_title']}","{each_dic['domain']}","{each_dic['is_risk_protocol']}","{each_dic['protocol']}","{each_dic['base_protocol']}","{each_dic['status_code']}","{each_dic['component']}","{each_dic['os']}","{each_dic['company']}","{each_dic['number']}","{each_dic['country']}","{each_dic['province']}","{each_dic['city']}","{each_dic['updated_at']}","{each_dic['is_web']}","{each_dic['as_org']}","{each_dic['isp']}","{each_dic['vul_list']}","{each_dic['is_risk']}")''') 1113 | self.sqlite_conn.commit() 1114 | j += 1 1115 | self.log_insert( 1116 | f'[Hunter] End of search. Totally {hunter_search.other_info["total"]} results on Hunter. Complete information has been stored by mode "{self.save_mode_choice.get()}". Consume {hunter_search.other_info["consume_quota"]} points, {hunter_search.other_info["rest_quota"]} points remaining today.\n') 1117 | 1118 | 1119 | if __name__ == '__main__': 1120 | root = tk.Tk() 1121 | if "darwin" in sys.platform: 1122 | root.geometry('793x526+320+100') 1123 | else: 1124 | # HiDPI 1125 | ctypes.windll.shcore.SetProcessDpiAwareness(1) 1126 | ScaleFactor = ctypes.windll.shcore.GetScaleFactorForDevice(0) 1127 | root.tk.call('tk', 'scaling', ScaleFactor / 75) 1128 | root.resizable(True, False) 1129 | root.title(f'ThunderSearch {VERSION} --xzajyjs') 1130 | Application(master=root) 1131 | root.mainloop() 1132 | --------------------------------------------------------------------------------