knightcrypt 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- knightcrypt/__init__.py +53 -0
- knightcrypt/cli.py +39 -0
- knightcrypt/obfuscation.py +189 -0
- knightcrypt/runtime.py +230 -0
- knightcrypt/utils.py +31 -0
- knightcrypt-4.0.0.dist-info/METADATA +19 -0
- knightcrypt-4.0.0.dist-info/RECORD +10 -0
- knightcrypt-4.0.0.dist-info/WHEEL +5 -0
- knightcrypt-4.0.0.dist-info/entry_points.txt +2 -0
- knightcrypt-4.0.0.dist-info/top_level.txt +1 -0
knightcrypt/__init__.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Sancho (@sanchoforza / @gloriasancho on Discord)
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
License: MIT
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "1.0.0"
|
|
8
|
+
__author__ = "Sancho"
|
|
9
|
+
__discord__ = "@sanchoforza / @gloriasancho"
|
|
10
|
+
__license__ = "MIT"
|
|
11
|
+
|
|
12
|
+
# Import core classes for easy access
|
|
13
|
+
from knightcrypt.obfuscation import KnightObfuscator
|
|
14
|
+
from knightcrypt.cli import main as cli_main
|
|
15
|
+
|
|
16
|
+
# Optional: Expose key functions for programmatic use
|
|
17
|
+
def encrypt_file(input_path, output_path=None, key="default", level="low"):
|
|
18
|
+
"""
|
|
19
|
+
Encrypts a Python file using KnightCrypt.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
input_path (str): Path to the .py file to encrypt.
|
|
23
|
+
output_path (str): Optional path for the encrypted output.
|
|
24
|
+
key (str): Encryption key.
|
|
25
|
+
level (str): Obfuscation level ('low', 'medium', 'high').
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: Path to the generated encrypted file.
|
|
29
|
+
"""
|
|
30
|
+
obf = KnightObfuscator(key=key, level=level)
|
|
31
|
+
return obf.encrypt_file(input_path, output_path)
|
|
32
|
+
|
|
33
|
+
def decrypt_file(enc_path, key="default"):
|
|
34
|
+
"""
|
|
35
|
+
Decrypts a KnightCrypt encrypted file.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
enc_path (str): Path to the .kn encrypted file.
|
|
39
|
+
key (str): Decryption key.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
str: The decrypted source code.
|
|
43
|
+
"""
|
|
44
|
+
from knightcrypt.runtime import KnightRuntime
|
|
45
|
+
runtime = KnightRuntime.from_file(enc_path, key)
|
|
46
|
+
return runtime.decrypt_source()
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"KnightObfuscator",
|
|
50
|
+
"encrypt_file",
|
|
51
|
+
"decrypt_file",
|
|
52
|
+
"cli_main",
|
|
53
|
+
]
|
knightcrypt/cli.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# knightcrypt/cli.py
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import argparse
|
|
5
|
+
from knightcrypt.obfuscation import KnightObfuscator
|
|
6
|
+
from knightcrypt.utils import print_banner, log_info, log_success, log_warning, log_error
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description="KnightCrypt - Advanced Python Encryption")
|
|
10
|
+
parser.add_argument("file", help="Python file to encrypt")
|
|
11
|
+
parser.add_argument("-k", "--key", default="secret", help="Encryption key")
|
|
12
|
+
parser.add_argument("-o", "--output", help="Output filename (optional)")
|
|
13
|
+
parser.add_argument("-l", "--level", choices=["low", "medium", "high"], default="high", help="Obfuscation level")
|
|
14
|
+
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
if not os.path.exists(args.file):
|
|
18
|
+
log_error(f"File not found: {args.file}")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
print_banner()
|
|
22
|
+
|
|
23
|
+
obfuscator = KnightObfuscator(key=args.key, level=args.level)
|
|
24
|
+
enc_path = obfuscator.encrypt_file(args.file, args.output)
|
|
25
|
+
|
|
26
|
+
# Generate wrapper
|
|
27
|
+
wrapper_name = os.path.splitext(args.file)[0] + "_run.py"
|
|
28
|
+
obfuscator.generate_wrapper(enc_path, wrapper_name)
|
|
29
|
+
|
|
30
|
+
log_success(f"Encrypted {args.file} -> {enc_path}")
|
|
31
|
+
log_success(f"Generated wrapper: {wrapper_name}")
|
|
32
|
+
log_info(f"Run your app with: python {wrapper_name}")
|
|
33
|
+
|
|
34
|
+
# Display stats
|
|
35
|
+
if hasattr(obfuscator, 'obfuscation_stats'):
|
|
36
|
+
log_info(f"Obfuscation Stats: {obfuscator.obfuscation_stats}")
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# knightcrypt/obfuscator.py
|
|
2
|
+
import ast
|
|
3
|
+
import marshal
|
|
4
|
+
import os
|
|
5
|
+
import zlib
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
import struct
|
|
9
|
+
from knightcrypt.utils import log_info, log_success, log_warning
|
|
10
|
+
|
|
11
|
+
class VariableRenamer(ast.NodeTransformer):
|
|
12
|
+
"""Renames all variables to random hex strings."""
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.counter = 0
|
|
15
|
+
|
|
16
|
+
def _new_name(self):
|
|
17
|
+
name = f"_0x{self.counter:x}"
|
|
18
|
+
self.counter += 1
|
|
19
|
+
return name
|
|
20
|
+
|
|
21
|
+
def visit_Name(self, node):
|
|
22
|
+
if isinstance(node.ctx, (ast.Store, ast.Load)):
|
|
23
|
+
new_node = ast.Name(id=self._new_name(), ctx=node.ctx)
|
|
24
|
+
ast.copy_location(new_node, node)
|
|
25
|
+
return new_node
|
|
26
|
+
return node
|
|
27
|
+
|
|
28
|
+
def visit_FunctionDef(self, node):
|
|
29
|
+
# Rename function arguments too
|
|
30
|
+
for arg in node.args.args:
|
|
31
|
+
arg.arg = self._new_name()
|
|
32
|
+
return self.generic_visit(node)
|
|
33
|
+
|
|
34
|
+
class StringEncryptor(ast.NodeTransformer):
|
|
35
|
+
"""Encrypts all string literals using XOR."""
|
|
36
|
+
def __init__(self, key="default"):
|
|
37
|
+
self.key = key
|
|
38
|
+
|
|
39
|
+
def visit_Str(self, node):
|
|
40
|
+
original_str = node.s
|
|
41
|
+
encrypted_bytes = bytes([ord(c) ^ ord(self.key[i % len(self.key)]) for i, c in enumerate(original_str)])
|
|
42
|
+
# Return a call to a helper function that decrypts at runtime
|
|
43
|
+
# For simplicity, we'll just replace with a decrypted constant
|
|
44
|
+
# In a real scenario, you'd generate a decryption function
|
|
45
|
+
return ast.Constant(value=original_str) # Simplified for demo
|
|
46
|
+
|
|
47
|
+
class ControlFlowFlattener(ast.NodeVisitor):
|
|
48
|
+
"""
|
|
49
|
+
Converts basic blocks into a switch-case style loop.
|
|
50
|
+
This makes static analysis very difficult.
|
|
51
|
+
"""
|
|
52
|
+
def __init__(self):
|
|
53
|
+
self.blocks = []
|
|
54
|
+
self.current_block_id = 0
|
|
55
|
+
|
|
56
|
+
def visit_If(self, node):
|
|
57
|
+
# Create two blocks: one for True, one for False
|
|
58
|
+
true_block_id = self.current_block_id + 1
|
|
59
|
+
false_block_id = self.current_block_id + 2
|
|
60
|
+
|
|
61
|
+
self.blocks.append({
|
|
62
|
+
'id': self.current_block_id,
|
|
63
|
+
'type': 'if',
|
|
64
|
+
'condition': ast.unparse(node.test),
|
|
65
|
+
'true_target': true_block_id,
|
|
66
|
+
'false_target': false_block_id,
|
|
67
|
+
'body': [ast.dump(stmt) for stmt in node.body],
|
|
68
|
+
'orelse': [ast.dump(stmt) for stmt in node.orelse]
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
self.current_block_id += 3
|
|
72
|
+
self.visit_children(node)
|
|
73
|
+
|
|
74
|
+
def visit_Assign(self, node):
|
|
75
|
+
self.blocks.append({
|
|
76
|
+
'id': self.current_block_id,
|
|
77
|
+
'type': 'assign',
|
|
78
|
+
'target': ast.unparse(node.targets[0]),
|
|
79
|
+
'value': ast.unparse(node.value),
|
|
80
|
+
'next': self.current_block_id + 1
|
|
81
|
+
})
|
|
82
|
+
self.current_block_id += 1
|
|
83
|
+
|
|
84
|
+
def visit_children(self, node):
|
|
85
|
+
for child in ast.iter_child_nodes(node):
|
|
86
|
+
self.visit(child)
|
|
87
|
+
|
|
88
|
+
def generate_switch_code(self):
|
|
89
|
+
"""Generates Python code for a switch-case loop."""
|
|
90
|
+
code = "state = 0\nwhile True:\n"
|
|
91
|
+
for block in self.blocks:
|
|
92
|
+
indent = " "
|
|
93
|
+
if block['type'] == 'if':
|
|
94
|
+
code += f"{indent}if state == {block['id']}:\n"
|
|
95
|
+
code += f"{indent} if {block['condition']}:\n"
|
|
96
|
+
code += f"{indent} state = {block['true_target']}\n"
|
|
97
|
+
code += f"{indent} else:\n"
|
|
98
|
+
code += f"{indent} state = {block['false_target']}\n"
|
|
99
|
+
elif block['type'] == 'assign':
|
|
100
|
+
code += f"{indent}if state == {block['id']}:\n"
|
|
101
|
+
code += f"{indent} {block['target']} = {block['value']}\n"
|
|
102
|
+
code += f"{indent} state = {block['next']}\n"
|
|
103
|
+
code += " break\n"
|
|
104
|
+
return code
|
|
105
|
+
|
|
106
|
+
class KnightObfuscator:
|
|
107
|
+
def __init__(self, key="default_key", level="high"):
|
|
108
|
+
self.key = key
|
|
109
|
+
self.level = level # low, medium, high
|
|
110
|
+
self.obfuscation_stats = {}
|
|
111
|
+
|
|
112
|
+
def obfuscate_source(self, source_code):
|
|
113
|
+
tree = ast.parse(source_code)
|
|
114
|
+
|
|
115
|
+
# Step 1: Rename Variables
|
|
116
|
+
renamer = VariableRenamer()
|
|
117
|
+
tree = renamer.visit(tree)
|
|
118
|
+
self.obfuscation_stats['variables_renamed'] = renamer.counter
|
|
119
|
+
|
|
120
|
+
# Step 2: Encrypt Strings (Simplified)
|
|
121
|
+
encryptor = StringEncryptor(key=self.key)
|
|
122
|
+
tree = encryptor.visit(tree)
|
|
123
|
+
|
|
124
|
+
# Step 3: Flatten Control Flow (if level is high)
|
|
125
|
+
if self.level == "high":
|
|
126
|
+
flattener = ControlFlowFlattener()
|
|
127
|
+
flattener.visit(tree)
|
|
128
|
+
switch_code = flattener.generate_switch_code()
|
|
129
|
+
# Append the switch code to the end of the source
|
|
130
|
+
source_code = source_code + "\n" + switch_code
|
|
131
|
+
|
|
132
|
+
# Step 4: Add Opaque Predicates
|
|
133
|
+
predicate = "if (hash(str(__name__)) % 2 == 0): pass\n"
|
|
134
|
+
source_code = predicate + source_code
|
|
135
|
+
|
|
136
|
+
return source_code
|
|
137
|
+
|
|
138
|
+
def encrypt_file(self, input_path, output_path=None):
|
|
139
|
+
if output_path is None:
|
|
140
|
+
output_path = input_path.replace('.py', '_enc.kn')
|
|
141
|
+
|
|
142
|
+
with open(input_path, 'r') as f:
|
|
143
|
+
source = f.read()
|
|
144
|
+
|
|
145
|
+
# Obfuscate
|
|
146
|
+
obfuscated_source = self.obfuscate_source(source)
|
|
147
|
+
|
|
148
|
+
# Compile to bytecode
|
|
149
|
+
code_obj = compile(obfuscated_source, input_path, 'exec')
|
|
150
|
+
|
|
151
|
+
# Marshal and Compress
|
|
152
|
+
marshaled = marshal.dumps(code_obj)
|
|
153
|
+
compressed = zlib.compress(marshaled)
|
|
154
|
+
|
|
155
|
+
# Encrypt with XOR
|
|
156
|
+
encrypted = bytes([b ^ ord(self.key[i % len(self.key)]) for i, b in enumerate(compressed)])
|
|
157
|
+
|
|
158
|
+
# Write output with header
|
|
159
|
+
header = len(self.key).to_bytes(2, 'big')
|
|
160
|
+
key_bytes = self.key.encode('utf-8')
|
|
161
|
+
|
|
162
|
+
with open(output_path, 'wb') as f:
|
|
163
|
+
f.write(header)
|
|
164
|
+
f.write(key_bytes)
|
|
165
|
+
f.write(encrypted)
|
|
166
|
+
|
|
167
|
+
return output_path
|
|
168
|
+
|
|
169
|
+
def generate_wrapper(self, enc_filepath, wrapper_name="run_knight.py"):
|
|
170
|
+
wrapper_content = '''
|
|
171
|
+
import sys
|
|
172
|
+
import os
|
|
173
|
+
from knightcrypt.runtime import load_and_run
|
|
174
|
+
|
|
175
|
+
def main():
|
|
176
|
+
enc_path = os.path.join(os.path.dirname(__file__), "{enc_filename}")
|
|
177
|
+
key = "{key}"
|
|
178
|
+
try:
|
|
179
|
+
load_and_run(enc_path, key)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
print(f"[-] KnightCrypt Runtime Error: {{e}}")
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|
|
186
|
+
'''.format(enc_filename=os.path.basename(enc_filepath), key=self.key)
|
|
187
|
+
|
|
188
|
+
with open(wrapper_name, 'w') as f:
|
|
189
|
+
f.write(wrapper_content)
|
knightcrypt/runtime.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
import marshal
|
|
5
|
+
import zlib
|
|
6
|
+
import struct
|
|
7
|
+
import hashlib
|
|
8
|
+
import types
|
|
9
|
+
import traceback
|
|
10
|
+
import gc
|
|
11
|
+
import dis
|
|
12
|
+
import time
|
|
13
|
+
import platform
|
|
14
|
+
import subprocess
|
|
15
|
+
import ctypes
|
|
16
|
+
from Crypto.Cipher import AES
|
|
17
|
+
from Crypto.Util.Padding import unpad
|
|
18
|
+
from Crypto.Random import get_random_bytes
|
|
19
|
+
import threading
|
|
20
|
+
import atexit
|
|
21
|
+
|
|
22
|
+
class KnightRuntimeError(Exception):
|
|
23
|
+
"""Custom exception for KnightCrypt runtime errors."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
class KnightRuntime:
|
|
27
|
+
# Magic bytes to identify valid KnightCrypt files
|
|
28
|
+
MAGIC_HEADER = b'KNIGHT'
|
|
29
|
+
VERSION = 1
|
|
30
|
+
|
|
31
|
+
# Known debugger processes
|
|
32
|
+
DEBUGGER_PROCESSES = ['devenv.exe', 'ida.exe', 'x64dbg.exe', 'ollydbg.exe', 'gdb', 'lldb']
|
|
33
|
+
|
|
34
|
+
def __init__(self, encrypted_data: bytes, key: str):
|
|
35
|
+
self.encrypted_data = encrypted_data
|
|
36
|
+
self.key = key
|
|
37
|
+
self.globals = {}
|
|
38
|
+
self.locals = {}
|
|
39
|
+
self._verify_integrity()
|
|
40
|
+
self._check_debuggers()
|
|
41
|
+
|
|
42
|
+
def _get_key_material(self, password: str) -> tuple:
|
|
43
|
+
"""Derives a 256-bit key and IV from the password using PBKDF2."""
|
|
44
|
+
salt = b'somegoofyahhshi' # Fixed salt for consistency in this demo; in production, store it separately or randomize
|
|
45
|
+
pbkdf2 = hashlib.pbkdf2_hmac('sha256',
|
|
46
|
+
password.encode('utf-8'),
|
|
47
|
+
salt,
|
|
48
|
+
iterations=100000)
|
|
49
|
+
return pbkdf2[:32], pbkdf2[32:44] # 32 bytes key + 12 bytes IV
|
|
50
|
+
|
|
51
|
+
def _verify_integrity(self):
|
|
52
|
+
"""Verifies the integrity of the runtime module itself."""
|
|
53
|
+
# In a production scenario, you would hash the runtime.py file
|
|
54
|
+
# and compare it against a known good hash embedded here.
|
|
55
|
+
current_file = os.path.abspath(__file__)
|
|
56
|
+
with open(current_file, 'rb') as f:
|
|
57
|
+
content = f.read()
|
|
58
|
+
current_hash = hashlib.sha256(content).hexdigest()
|
|
59
|
+
expected_hash = "your_expected_hash_here" # Replace with actual hash of this file
|
|
60
|
+
if current_hash != expected_hash:
|
|
61
|
+
raise KnightRuntimeError("Runtime integrity check failed: File may have been tampered with.")
|
|
62
|
+
|
|
63
|
+
def _check_debuggers(self):
|
|
64
|
+
"""Checks for common debugger processes."""
|
|
65
|
+
try:
|
|
66
|
+
if platform.system() == "Windows":
|
|
67
|
+
import psutil
|
|
68
|
+
for proc in psutil.process_iter(['name']):
|
|
69
|
+
if proc.info['name'].lower() in [db.lower() for db in self.DEBUGGER_PROCESSES]:
|
|
70
|
+
raise KnightRuntimeError(f"Debugger detected: {proc.info['name']}")
|
|
71
|
+
else:
|
|
72
|
+
# For Linux/Mac, you might use /proc or other methods
|
|
73
|
+
pass
|
|
74
|
+
except ImportError:
|
|
75
|
+
# If psutil is not available, skip this check
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
def decrypt_payload(self) -> bytes:
|
|
79
|
+
"""
|
|
80
|
+
Decrypts the payload using AES-256-GCM.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
bytes: Decrypted compressed marshaled code object.
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
KnightRuntimeError: If decryption fails or integrity check fails.
|
|
87
|
+
"""
|
|
88
|
+
if not self.encrypted_data:
|
|
89
|
+
raise KnightRuntimeError("Empty encrypted data")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
# Extract header and ciphertext
|
|
93
|
+
# Format: [MAGIC(6)] [VERSION(1)] [SALT(8)] [IV(12)] [TAG(16)] [CIPHERTEXT]
|
|
94
|
+
if len(self.encrypted_data) < 6 + 1 + 8 + 12 + 16:
|
|
95
|
+
raise KnightRuntimeError("Invalid file format: too short")
|
|
96
|
+
|
|
97
|
+
magic = self.encrypted_data[:6]
|
|
98
|
+
if magic != self.MAGIC_HEADER:
|
|
99
|
+
raise KnightRuntimeError(f"Invalid magic header: {magic}")
|
|
100
|
+
|
|
101
|
+
version = self.encrypted_data[6]
|
|
102
|
+
if version != self.VERSION:
|
|
103
|
+
raise KnightRuntimeError(f"Unsupported version: {version}")
|
|
104
|
+
|
|
105
|
+
salt = self.encrypted_data[7:15]
|
|
106
|
+
iv = self.encrypted_data[15:27]
|
|
107
|
+
tag = self.encrypted_data[27:43]
|
|
108
|
+
ciphertext = self.encrypted_data[43:]
|
|
109
|
+
|
|
110
|
+
# Derive key from password and salt
|
|
111
|
+
key, derived_iv = self._get_key_material(self.key)
|
|
112
|
+
|
|
113
|
+
# Use the stored IV for GCM mode
|
|
114
|
+
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
|
|
115
|
+
|
|
116
|
+
# Decrypt and verify tag
|
|
117
|
+
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
|
|
118
|
+
|
|
119
|
+
return plaintext
|
|
120
|
+
|
|
121
|
+
except ValueError as e:
|
|
122
|
+
raise KnightRuntimeError(f"Decryption failed: Invalid key or corrupted data. {e}")
|
|
123
|
+
except Exception as e:
|
|
124
|
+
raise KnightRuntimeError(f"Unexpected error during decryption: {e}")
|
|
125
|
+
|
|
126
|
+
def load_code_object(self, decrypted_data: bytes) -> types.CodeType:
|
|
127
|
+
"""
|
|
128
|
+
Decompresses and loads the marshaled code object.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
decrypted_data (bytes): Decrypted compressed data.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
types.CodeType: The compiled code object.
|
|
135
|
+
"""
|
|
136
|
+
try:
|
|
137
|
+
# Decompress zlib data
|
|
138
|
+
decompressed = zlib.decompress(decrypted_data)
|
|
139
|
+
|
|
140
|
+
# Load marshaled code object
|
|
141
|
+
code_obj = marshal.loads(decompressed)
|
|
142
|
+
|
|
143
|
+
if not isinstance(code_obj, types.CodeType):
|
|
144
|
+
raise KnightRuntimeError("Invalid code object format")
|
|
145
|
+
|
|
146
|
+
return code_obj
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
raise KnightRuntimeError(f"Failed to load code object: {e}")
|
|
150
|
+
|
|
151
|
+
def execute(self, code_obj: types.CodeType) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Executes the code object in a controlled environment.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
code_obj (types.CodeType): The code object to execute.
|
|
157
|
+
"""
|
|
158
|
+
try:
|
|
159
|
+
# Create a new frame for isolated execution
|
|
160
|
+
frame = types.FrameType(
|
|
161
|
+
code=code_obj,
|
|
162
|
+
globals=self.globals,
|
|
163
|
+
locals=self.locals
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Execute the code
|
|
167
|
+
exec(code_obj, self.globals, self.locals)
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print(f"[!] Execution Error: {e}", file=sys.stderr)
|
|
171
|
+
traceback.print_exc(file=sys.stderr)
|
|
172
|
+
raise KnightRuntimeError(f"Execution failed: {e}")
|
|
173
|
+
|
|
174
|
+
def run(self) -> None:
|
|
175
|
+
"""Main entry point to decrypt and execute the payload."""
|
|
176
|
+
try:
|
|
177
|
+
decrypted_data = self.decrypt_payload()
|
|
178
|
+
code_obj = self.load_code_object(decrypted_data)
|
|
179
|
+
self.execute(code_obj)
|
|
180
|
+
except KnightRuntimeError as e:
|
|
181
|
+
print(f"[!] KnightCrypt Runtime Error: {e}", file=sys.stderr)
|
|
182
|
+
sys.exit(1)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
print(f"[!] Unexpected Runtime Error: {e}", file=sys.stderr)
|
|
185
|
+
traceback.print_exc(file=sys.stderr)
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
finally:
|
|
188
|
+
self._cleanup_memory()
|
|
189
|
+
|
|
190
|
+
def _cleanup_memory(self):
|
|
191
|
+
"""Clears sensitive data from memory."""
|
|
192
|
+
# Clear globals and locals
|
|
193
|
+
self.globals.clear()
|
|
194
|
+
self.locals.clear()
|
|
195
|
+
|
|
196
|
+
# Garbage collect
|
|
197
|
+
gc.collect()
|
|
198
|
+
|
|
199
|
+
# Optionally, overwrite encrypted data with zeros
|
|
200
|
+
if hasattr(self, 'encrypted_data'):
|
|
201
|
+
self.encrypted_data = b'\x00' * len(self.encrypted_data)
|
|
202
|
+
|
|
203
|
+
def load_and_run(filepath: str, key: str) -> None:
|
|
204
|
+
"""
|
|
205
|
+
Convenience function to load and run an encrypted file.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
filepath (str): Path to the encrypted .kn file.
|
|
209
|
+
key (str): Decryption key.
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
with open(filepath, 'rb') as f:
|
|
213
|
+
encrypted_data = f.read()
|
|
214
|
+
|
|
215
|
+
runtime = KnightRuntime(encrypted_data, key)
|
|
216
|
+
runtime.run()
|
|
217
|
+
|
|
218
|
+
except FileNotFoundError:
|
|
219
|
+
print(f"[!] File not found: {filepath}", file=sys.stderr)
|
|
220
|
+
sys.exit(1)
|
|
221
|
+
except IOError as e:
|
|
222
|
+
print(f"[!] I/O Error: {e}", file=sys.stderr)
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
except KnightRuntimeError as e:
|
|
225
|
+
print(f"[!] KnightCrypt Runtime Error: {e}", file=sys.stderr)
|
|
226
|
+
sys.exit(1)
|
|
227
|
+
except Exception as e:
|
|
228
|
+
print(f"[!] Unexpected Error: {e}", file=sys.stderr)
|
|
229
|
+
traceback.print_exc(file=sys.stderr)
|
|
230
|
+
sys.exit(1)
|
knightcrypt/utils.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# knightcrypt/utils.py
|
|
2
|
+
class Colors:
|
|
3
|
+
HEADER = '\033[95m'
|
|
4
|
+
BLUE = '\033[94m'
|
|
5
|
+
CYAN = '\033[96m'
|
|
6
|
+
GREEN = '\033[92m'
|
|
7
|
+
YELLOW = '\033[93m'
|
|
8
|
+
RED = '\033[91m'
|
|
9
|
+
ENDC = '\033[0m'
|
|
10
|
+
BOLD = '\033[1m'
|
|
11
|
+
UNDERLINE = '\033[4m'
|
|
12
|
+
|
|
13
|
+
def print_banner():
|
|
14
|
+
print(f"{Colors.CYAN}{'='*50} {Colors.ENDC}")
|
|
15
|
+
print(f"{Colors.HEADER} KnightCrypt - Python Library for File Encryption {Colors.ENDC}")
|
|
16
|
+
print(f"{Colors.CYAN}{'='*50} {Colors.ENDC}")
|
|
17
|
+
print(f"{Colors.GREEN}[+] Welcome to KnightCrypt! {Colors.ENDC}")
|
|
18
|
+
print(f"{Colors.YELLOW}[!] Use this library responsibly and ensure you have permission to encrypt/decrypt files. {Colors.ENDC}")
|
|
19
|
+
print(f"{Colors.CYAN}{'='*50}{Colors.ENDC}")
|
|
20
|
+
|
|
21
|
+
def log_info(msg):
|
|
22
|
+
print(f"{Colors.BLUE}[INFO]{Colors.ENDC} {msg}")
|
|
23
|
+
|
|
24
|
+
def log_success(msg):
|
|
25
|
+
print(f"{Colors.GREEN}[SUCCESS]{Colors.ENDC} {msg}")
|
|
26
|
+
|
|
27
|
+
def log_warning(msg):
|
|
28
|
+
print(f"{Colors.YELLOW}[WARNING]{Colors.ENDC} {msg}")
|
|
29
|
+
|
|
30
|
+
def log_error(msg):
|
|
31
|
+
print(f"{Colors.RED}[ERROR]{Colors.ENDC} {msg}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: knightcrypt
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: cool python encryption lib yesyes
|
|
5
|
+
Author: Sancho
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Security :: Cryptography
|
|
12
|
+
Requires-Python: >=3.6
|
|
13
|
+
Requires-Dist: pycryptodome
|
|
14
|
+
Requires-Dist: psutil
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
Dynamic: summary
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
knightcrypt/__init__.py,sha256=dXO26Q5kb1LVxChsX-7bJOk6Ee59a1ZMEqJYb9T-AQ4,1507
|
|
2
|
+
knightcrypt/cli.py,sha256=fV7qRidCL4xFElZRLOz7GEj3z1GPuTsfGkqUcY0S518,1496
|
|
3
|
+
knightcrypt/obfuscation.py,sha256=0DkE8QlJOtmoaCVjlymacXRbUZVZq2YI471uJVPxXP8,6700
|
|
4
|
+
knightcrypt/runtime.py,sha256=oIOb16hU4_2ELnYI3ruhSNIukyC-ALWfYyp1wGMZ0ic,8560
|
|
5
|
+
knightcrypt/utils.py,sha256=vSs4nTC7pL9XWatIaie3be87UAi9pxjkYb5yyEXJjX0,1045
|
|
6
|
+
knightcrypt-4.0.0.dist-info/METADATA,sha256=X9p9_fTDqRGQ_AlZFpuLIAfujf_1vcrWDBJpe0QexZQ,585
|
|
7
|
+
knightcrypt-4.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
knightcrypt-4.0.0.dist-info/entry_points.txt,sha256=TMGcLlvWfwnHxRHCfz_T-tK03GkW2fZ4jLqtBOu53ng,61
|
|
9
|
+
knightcrypt-4.0.0.dist-info/top_level.txt,sha256=WKNDYgL7E6Cb39gowd_4IernmMNbwbLyeXUCUCHRjm0,12
|
|
10
|
+
knightcrypt-4.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
knightcrypt
|