-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecrypter.py
More file actions
335 lines (266 loc) · 14.6 KB
/
decrypter.py
File metadata and controls
335 lines (266 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#!/usr/bin/env python3
import argparse
import importlib.util
import os
import sys
import datetime
import math
from collections import Counter
import itertools
# option menus
import menus.menuHelp as menuHelp
import menus.menuMods as menuMods
# ANSI colour mapping
RED = '\033[31m'
GREEN = '\033[32m'
BLUE = '\033[34m'
RESET = '\033[0m'
modules = []
if str(os.name) == 'posix':
SCRIPT_DR = os.path.dirname(os.path.realpath(__file__))
MOD_PATH = os.path.join(SCRIPT_DR, 'modules')
else:
MOD_PATH = os.path.join(os.path.dirname(__file__), 'modules')
# dynamic loading for modules
def loadMods():
# modules = []
count = -1 # total count of files in the modules subfolder (-1 for __pychache__)
iCount = 0 # count of all successfully imported modules
print(f"\n{BLUE}[*] Loading modules...{RESET}")
for fName in os.listdir(MOD_PATH): # checks /modules/
count += 1
if fName.startswith("mod") and fName.endswith(".py"): # starts with mod and ends with .py
name = fName[:-3] # removes mod
fPath = os.path.join(MOD_PATH, fName) # full path
spec = importlib.util.spec_from_file_location(name, fPath)
if not spec or not spec.loader: # spec/error handling
print(f"{GREEN}[-] Skipping {name}: No loader or spec.{RESET}")
continue
try: # more error handling
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
if hasattr(mod, "conv") and callable(mod.conv): # checks if it has 'conv' function
iCount += 1
modules.append(mod) # adds to the list of modules
else:
print(f"{GREEN}[-] Skipping {name}: No 'conv()' function.{RESET}")
except Exception as e:
print(f"{RED}[!] Failed to load {name}: {e}{RESET}")
print(f"{BLUE}[*] Loaded {iCount}/{count} modules.{RESET}")
return modules
def dOptList(modules, optList): # creates option list of tuples for printing
for i, m in enumerate(modules):
optList.append((i + 1, m.__name__[3:]))
return optList
# call decryption module
def callMod(opt, encryptS, file, modules, optList):
d = False
try:
mod = modules[opt - 1] # map chosen module
ret = mod.conv(encryptS, d) # store retured result
if ret is False:
print(f"\nInvalid {optList[opt - 1][1]} string: {encryptS}") # print if invalid
ret = encryptS
else:
print(f"\nFrom {optList[opt - 1][1]}: {ret}") # print the module and returned string
except Exception:
return False # return false if not a valid module
while True: # RED/RESET: ANSI escape and colour codes predefined
run = input(f" {RED}[c]{RESET}Continue with another technique\n " \
f"{RED}[r]{RESET}Revert back a step\n {RED}[e]{RESET}Exit \nChoice: ").strip().lower()
if run == 'e': # if exit
print(f"Final string: {ret}\n") # print final
if file != None: # check if output file
file.write(f"\nFinal string: \n{optList[opt - 1][1]}: {ret}\n") # write to file
exit(0)
elif run == 'c': # if continue
if file != None: # check if output file
file.write(f"\n{optList[opt - 1][1]}: {ret}\n") # write to file
return ret # return result string
elif run == 'r': # if revert
print(f"Revert back to string: {encryptS}") # print string before decryption attempt
return encryptS # return current string
else:
print("\nInvalid option, try again.")
def readFile(fpath): # if input file
file = open(fpath, "r") # store and read file
return file.readline() # read line, store string
def asciiTitle(): # terminal ascii title output
print(" ___ ____ __ ___ ___ ______ ____ ___ ")
print("|| \\\\ || // \\\\ || \\\\ \\\\ // || \\\\ || || || \\\\ ")
print("|| || ||___ || ||__// \\\\// ||__// || ||___ ||__// ")
print("|| || || || || \\\\ // || || || || \\\\ ")
print("||__// ||___ \\\\__// || \\\\ // || || ||___ || \\\\ ")
# ___ ____ __ ___ ___ ______ ____ ___
# || \\ || // \\ || \\ \\ // || \\ || || || \\
# || || ||___ || ||__// \\// ||__// || ||___ ||__// output of
# || || || || || \\ // || || || || \\ the above
# ||__// ||___ \\__// || \\ // || || ||___ || \\
return
def entropy(encryptS, optList): # entropy detection
frequency = Counter(encryptS)
total_characters = len(encryptS)
entropy = -sum((count / total_characters) * math.log2(count / total_characters)
for count in frequency.values())
print(f"\nEntropy: {entropy}")
d = True
for idx, opt in optList: # map chosen module
mod = modules[idx - 1]
ret = mod.conv(encryptS, d) # store retured result
if ret is not False and ret is not True:
if ret != "":
print(f" From {optList[idx - 1][1]}: {ret}") # print the module and returned string
# elif ret is False:
# print(f" INVALID {optList[idx - 1][1]} string") # print if invalid
def chain_bruteforce(input_text, modules, optList):
print("\nBrute Force Chaining")
clen = input("Enter chain length OR range (ex. 2-4): ").strip()
if "-" in clen: # parse a single length or range
start, end = clen.split("-")
chain_lengths = range(int(start), int(end) + 1)
else:
chain_lengths = [int(clen)]
mod_indexes = list(range(len(modules))) # list of module indexes
original = input_text
def valid_output(text, original):
if not isinstance(text, str): # avoids errors, ensure it's a string
return False
if (
not text # empty string, None, False
or text == original # unchanged
or text == "?" # placeholder
or isinstance(text, bool) # True/False
or text.strip() == "" # whitespace-only
or text.startswith("") # weird junk patterns
or text == "????????????????????????????????????" # weird junk
or text == None # empty strings
or "\x00" in text # null character
):
return False
return True
print("\n[+] Starting chained brute force...\n")
for L in chain_lengths: # loop over chain lengths
printed_header = False
for chain in itertools.product(mod_indexes, repeat=L): # every possible sequence of operations
active = [(original, [])] # [(text, path)]
for mod_index in chain: # process each module in the chain
new_active = []
mod = modules[mod_index]
mod_name = optList[mod_index][1].lower()
for text, path in active:
if optList[mod_index][1] in path: # skip duplicate module
continue
if mod_name in ("rot", "rot13") and any(p.startswith("ROT") for p in path):
continue # skip ROT if already used, no matter the shift
if mod_name == "rot13" or mod_name == "rot": # ROT special case
try:
rot_results = mod.silent_rot(text) # dict {shift: result}
except:
continue
for shift, out in rot_results.items():
if valid_output(out, input_text):
new_active.append((out, path + [f"ROT{shift}"]))
continue
try: # normal module
out = mod.conv(text, True)
except:
out = None
if valid_output(out, input_text):
new_active.append((out, path + [optList[mod_index][1]]))
active = new_active # move to next step in chain
if not active: # stop, no valid branches
break
for final_text, path in active: # print results
if not valid_output(final_text, input_text):
continue
if not printed_header:
print(f"--- Length {L} ---")
printed_header = True
chain_str = " > ".join(path)
print(f"{chain_str} → {GREEN}{final_text}{RESET}")
print("\n[+] Chain brute force complete.\n")
def main(argv):
asciiTitle()
parser = argparse.ArgumentParser(description="Modular CLI decryption tool", add_help=False) # set default help to false
parser.add_argument("input", nargs="?", help="Encrypted string (or use -f for file input)") # input
parser.add_argument("-f", "--file", help="Path to input file") # input file
parser.add_argument("-o", "--output", action="store_true", help="Write output to dated file") # output file
parser.add_argument("-m", "--mods", action="store_true", help="Show modules list") # mod list
parser.add_argument("-h", "--help", action="store_true", help="Show help menu") # help menu
args = parser.parse_args()
if args.help: # if help option
menuHelp.main() # call help menu
exit(0)
if args.mods: # if module option
menuMods.main() # call module menu
exit(0)
file = None
optList = []
modules = loadMods()
optList = dOptList(modules, optList)
if args.file: # if input file
encryptS = readFile(args.file) # store returned string from file
elif args.input:
encryptS = args.input
else:
print("Usage: decrypter <encrypted string> [-f <file>] [-o] [-m] [-h]") # print help message
exit(0)
if args.output:
cDate = datetime.datetime.now().date() # store date
fpath = f"decrypter-{cDate}.txt" # create filename
file = open(fpath, "w") # open as append
file.write(f"Encrypted String: {encryptS}\n") # write given encryption string
# option page functionality
optPerPage = 5
optPage = 1
opt = None
# run if option not exit
while opt != 'e':
# option page functionality
optStart = (optPage - 1) * optPerPage
optEnd = optStart + optPerPage
pageOpt = optList[optStart:optEnd]
print(f"\nDecode Options: \n{RED}[e]{RESET} Exit") # print list header and exit option
if optPage > 1: # check if not the first page
print(f"{RED}[p]{RESET} Previous Page") # print previous page option
# for n, opt in pageOpt: # loop through options
# print(f" {BLUE}[{n}]{RESET} {opt}") # print
# leftC = pageOpt[:4]
# rightC = pageOpt[4:]
# while len(rightC) < len(leftC):
# rightC.append(('', ''))
# for i in range(len(leftC)):
# left = leftC[i]
# right = rightC[i]
# leftS = f"{BLUE}[{left[0]}]{RESET} {left[1]}" if left[0] else ""
# rightS = f"{BLUE}[{right[0]}]{RESET} {right[1]}" if right[0] else ""
# print(f" {leftS}\t\t{rightS}")
for n, name in pageOpt:
print(f" {BLUE}[{n}]{RESET} {name}")
print(f"{RED}[d]{RESET} Detect") # print the detect option
print(f"{RED}[b]{RESET} Brute-Force Chaining")
if optEnd < len(optList): # check if not the last page
print(f"{RED}[n]{RESET} Next Page") # print next page option
opt = input("Choice: ").strip().lower() # store user choice
if opt == 'n' and optEnd < len(optList): # if next page chosen
optPage += 1 # move to next page
elif opt == 'p' and optPage > 1: # if previous page chosen
optPage -= 1 # move back a page
elif opt == 'e': # if exit chosen
print(f"Final string: {encryptS}\n") # print current/final string
exit(0)
elif opt == 'd': # if detect is chosen
entropy(encryptS, optList) # call entropy function
elif opt == 'b': # if chaining method is chosen
chain_bruteforce(encryptS, modules, optList) # call chaining funtion
elif opt.isdigit(): # if number is chosen
run = callMod(int(opt), encryptS, file, modules, optList) # call run with option, encrypted string, and file path
if run == False: # if false
print("\nInvalid option, try again.") # invalid option
else:
encryptS = run # store returned string
optPage = 1 # reset to first page
else:
print("\nInvalid input, try again.")
if __name__ == '__main__':\
main(sys.argv)