securedypkt 0.1.0__tar.gz

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.
@@ -0,0 +1,19 @@
1
+ Securedy Labs Proprietary License
2
+ Copyright (c) 2026 Securedy Labs. All rights reserved.
3
+
4
+ This software and its source code are the property of Securedy Labs.
5
+ Unauthorized copying, distribution, modification, or use of this software,
6
+ in whole or in part, is strictly prohibited without prior written permission
7
+ from Securedy Labs.
8
+
9
+ --- Third-Party Attributions ---
10
+
11
+ Portions of this software (Decipher/twofish.py) are derived from an
12
+ implementation by Dr. Brian Gladman (gladman@seven77.demon.co.uk), used
13
+ with permission for free direct or derivative use, subject to acknowledgment
14
+ of its origin. The original Twofish algorithm was authored by Bruce Schneier
15
+ and colleagues.
16
+
17
+ The original open-source codebase this project was derived from was released
18
+ under the MIT License by Punkcake21 (2026). Securedy Labs' modifications,
19
+ extensions, and proprietary additions are subject to the terms above.
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: securedypkt
3
+ Version: 0.1.0
4
+ Summary: Pure-Python toolkit for decrypting and re-encrypting Cisco Packet Tracer (.pkt) files
5
+ Author-email: Securedy Labs <nathanrampersaud@gmail.com>
6
+ Project-URL: Homepage, https://github.com/Nathan8044/SecuredyPKT
7
+ Keywords: cisco,packet-tracer,pkt,twofish,eax,crypto,network
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Security :: Cryptography
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # SecuredyPKT
24
+
25
+ **SecuredyPKT** is a pure-Python research tool by **Securedy Labs** for decrypting and re-encrypting Cisco Packet Tracer (`.pkt`) files.
26
+
27
+ It fully reimplements the proprietary cryptographic and obfuscation pipeline used internally by Packet Tracer, enabling offline inspection, diffing, and version-controlling of lab topologies — without requiring the official application.
28
+
29
+ This is an active research and development project. A custom crypto model and extended feature set are planned.
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install securedypkt
37
+ ```
38
+
39
+ No additional dependencies required. Pure Python 3.8+.
40
+
41
+ ---
42
+
43
+ ## Python API (for agents and integrations)
44
+
45
+ ```python
46
+ import securedypkt
47
+
48
+ # Decrypt a .pkt file → XML bytes
49
+ xml_bytes = securedypkt.decrypt_file("lab.pkt")
50
+
51
+ # Or work directly with bytes (e.g. from an upload handler)
52
+ with open("lab.pkt", "rb") as f:
53
+ xml_bytes = securedypkt.decrypt_pkt(f.read())
54
+
55
+ # Re-encrypt XML bytes → .pkt bytes
56
+ pkt_bytes = securedypkt.encrypt_pkt(xml_bytes)
57
+ pkt_bytes = securedypkt.encrypt_file("lab.xml")
58
+ ```
59
+
60
+ ---
61
+
62
+ ## CLI Usage
63
+
64
+ ### Decrypt `.pkt` → XML
65
+
66
+ ```bash
67
+ unpacket lab.pkt
68
+ # Output: lab.xml
69
+
70
+ unpacket lab.pkt -o decrypted.xml
71
+ ```
72
+
73
+ ### Re-encrypt XML → `.pkt`
74
+
75
+ ```bash
76
+ repacket lab.xml
77
+ # Output: lab.pkt
78
+
79
+ repacket lab.xml -o rebuilt.pkt
80
+ ```
81
+
82
+ ### Options
83
+
84
+ | Tool | Option | Description |
85
+ |------|--------|-------------|
86
+ | `unpacket` | `input_file` | Path to `.pkt` file |
87
+ | | `-o, --output` | Output XML path |
88
+ | `repacket` | `input_file` | Path to `.xml` file |
89
+ | | `-o, --output` | Output `.pkt` path |
90
+
91
+ ---
92
+
93
+ ## How It Works
94
+
95
+ Cisco Packet Tracer `.pkt` files are protected by a 4-layer scheme. SecuredyPKT reverses all four layers:
96
+
97
+ ```
98
+ [ .pkt file on disk ]
99
+ |
100
+ v
101
+ [1] Stage-1 Deobfuscation
102
+ Byte-order reversal + positional XOR
103
+ result[i] = data[L-1-i] ^ (L - i*L & 0xFF)
104
+ |
105
+ v
106
+ [2] Twofish/EAX Decryption (authenticated)
107
+ 128-bit Twofish block cipher in EAX mode
108
+ Key: 0x89 * 16 (hardcoded in Packet Tracer)
109
+ IV: 0x10 * 16 (hardcoded in Packet Tracer)
110
+ Last 16 bytes of ciphertext = AEAD authentication tag
111
+ |
112
+ v
113
+ [3] Stage-2 Deobfuscation
114
+ Positional XOR with decreasing counter
115
+ result[i] = b ^ (L - i & 0xFF)
116
+ |
117
+ v
118
+ [4] Qt Decompression
119
+ zlib stream with 4-byte big-endian size prefix (qCompress format)
120
+ |
121
+ v
122
+ [ Plain XML topology ]
123
+ ```
124
+
125
+ `repacket` runs this pipeline in reverse: XML → compress → obfuscate → encrypt → obfuscate → `.pkt`.
126
+
127
+ ---
128
+
129
+ ## Crypto Stack
130
+
131
+ | Module | Purpose |
132
+ |--------|---------|
133
+ | `securedypkt/decipher/twofish.py` | Pure-Python Twofish block cipher (128-bit) |
134
+ | `securedypkt/decipher/cmac.py` | CMAC / OMAC message authentication code |
135
+ | `securedypkt/decipher/ctr.py` | CTR mode keystream engine (big-endian counter) |
136
+ | `securedypkt/decipher/eax.py` | EAX authenticated encryption (AEAD) |
137
+ | `securedypkt/decipher/pt_crypto.py` | Full Packet Tracer decryption pipeline |
138
+
139
+ ---
140
+
141
+ ## Security Notes
142
+
143
+ - The hardcoded key and IV are **Cisco's**, reverse-engineered from the Packet Tracer binary
144
+ - EAX tag verification is enforced — corrupted or tampered `.pkt` files are rejected
145
+ - No network calls; fully offline
146
+ - Known research items for future versions:
147
+ - Input file size guard
148
+ - zlib decompression size cap
149
+ - XML parsing hardening
150
+
151
+ ---
152
+
153
+ ## Legal
154
+
155
+ This software is provided for **security research, educational, and interoperability purposes**.
156
+
157
+ Cisco Packet Tracer and all related trademarks are property of Cisco Systems, Inc.
158
+ SecuredyPKT is not affiliated with or endorsed by Cisco.
159
+
160
+ See [LICENSE](LICENSE) for full terms.
@@ -0,0 +1,138 @@
1
+ # SecuredyPKT
2
+
3
+ **SecuredyPKT** is a pure-Python research tool by **Securedy Labs** for decrypting and re-encrypting Cisco Packet Tracer (`.pkt`) files.
4
+
5
+ It fully reimplements the proprietary cryptographic and obfuscation pipeline used internally by Packet Tracer, enabling offline inspection, diffing, and version-controlling of lab topologies — without requiring the official application.
6
+
7
+ This is an active research and development project. A custom crypto model and extended feature set are planned.
8
+
9
+ ---
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pip install securedypkt
15
+ ```
16
+
17
+ No additional dependencies required. Pure Python 3.8+.
18
+
19
+ ---
20
+
21
+ ## Python API (for agents and integrations)
22
+
23
+ ```python
24
+ import securedypkt
25
+
26
+ # Decrypt a .pkt file → XML bytes
27
+ xml_bytes = securedypkt.decrypt_file("lab.pkt")
28
+
29
+ # Or work directly with bytes (e.g. from an upload handler)
30
+ with open("lab.pkt", "rb") as f:
31
+ xml_bytes = securedypkt.decrypt_pkt(f.read())
32
+
33
+ # Re-encrypt XML bytes → .pkt bytes
34
+ pkt_bytes = securedypkt.encrypt_pkt(xml_bytes)
35
+ pkt_bytes = securedypkt.encrypt_file("lab.xml")
36
+ ```
37
+
38
+ ---
39
+
40
+ ## CLI Usage
41
+
42
+ ### Decrypt `.pkt` → XML
43
+
44
+ ```bash
45
+ unpacket lab.pkt
46
+ # Output: lab.xml
47
+
48
+ unpacket lab.pkt -o decrypted.xml
49
+ ```
50
+
51
+ ### Re-encrypt XML → `.pkt`
52
+
53
+ ```bash
54
+ repacket lab.xml
55
+ # Output: lab.pkt
56
+
57
+ repacket lab.xml -o rebuilt.pkt
58
+ ```
59
+
60
+ ### Options
61
+
62
+ | Tool | Option | Description |
63
+ |------|--------|-------------|
64
+ | `unpacket` | `input_file` | Path to `.pkt` file |
65
+ | | `-o, --output` | Output XML path |
66
+ | `repacket` | `input_file` | Path to `.xml` file |
67
+ | | `-o, --output` | Output `.pkt` path |
68
+
69
+ ---
70
+
71
+ ## How It Works
72
+
73
+ Cisco Packet Tracer `.pkt` files are protected by a 4-layer scheme. SecuredyPKT reverses all four layers:
74
+
75
+ ```
76
+ [ .pkt file on disk ]
77
+ |
78
+ v
79
+ [1] Stage-1 Deobfuscation
80
+ Byte-order reversal + positional XOR
81
+ result[i] = data[L-1-i] ^ (L - i*L & 0xFF)
82
+ |
83
+ v
84
+ [2] Twofish/EAX Decryption (authenticated)
85
+ 128-bit Twofish block cipher in EAX mode
86
+ Key: 0x89 * 16 (hardcoded in Packet Tracer)
87
+ IV: 0x10 * 16 (hardcoded in Packet Tracer)
88
+ Last 16 bytes of ciphertext = AEAD authentication tag
89
+ |
90
+ v
91
+ [3] Stage-2 Deobfuscation
92
+ Positional XOR with decreasing counter
93
+ result[i] = b ^ (L - i & 0xFF)
94
+ |
95
+ v
96
+ [4] Qt Decompression
97
+ zlib stream with 4-byte big-endian size prefix (qCompress format)
98
+ |
99
+ v
100
+ [ Plain XML topology ]
101
+ ```
102
+
103
+ `repacket` runs this pipeline in reverse: XML → compress → obfuscate → encrypt → obfuscate → `.pkt`.
104
+
105
+ ---
106
+
107
+ ## Crypto Stack
108
+
109
+ | Module | Purpose |
110
+ |--------|---------|
111
+ | `securedypkt/decipher/twofish.py` | Pure-Python Twofish block cipher (128-bit) |
112
+ | `securedypkt/decipher/cmac.py` | CMAC / OMAC message authentication code |
113
+ | `securedypkt/decipher/ctr.py` | CTR mode keystream engine (big-endian counter) |
114
+ | `securedypkt/decipher/eax.py` | EAX authenticated encryption (AEAD) |
115
+ | `securedypkt/decipher/pt_crypto.py` | Full Packet Tracer decryption pipeline |
116
+
117
+ ---
118
+
119
+ ## Security Notes
120
+
121
+ - The hardcoded key and IV are **Cisco's**, reverse-engineered from the Packet Tracer binary
122
+ - EAX tag verification is enforced — corrupted or tampered `.pkt` files are rejected
123
+ - No network calls; fully offline
124
+ - Known research items for future versions:
125
+ - Input file size guard
126
+ - zlib decompression size cap
127
+ - XML parsing hardening
128
+
129
+ ---
130
+
131
+ ## Legal
132
+
133
+ This software is provided for **security research, educational, and interoperability purposes**.
134
+
135
+ Cisco Packet Tracer and all related trademarks are property of Cisco Systems, Inc.
136
+ SecuredyPKT is not affiliated with or endorsed by Cisco.
137
+
138
+ See [LICENSE](LICENSE) for full terms.
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "securedypkt"
7
+ version = "0.1.0"
8
+ description = "Pure-Python toolkit for decrypting and re-encrypting Cisco Packet Tracer (.pkt) files"
9
+ readme = "README.md"
10
+ license-files = ["LICENSE"]
11
+ authors = [{ name = "Securedy Labs", email = "nathanrampersaud@gmail.com" }]
12
+ requires-python = ">=3.8"
13
+ dependencies = []
14
+ keywords = ["cisco", "packet-tracer", "pkt", "twofish", "eax", "crypto", "network"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.8",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Operating System :: OS Independent",
24
+ "Topic :: Security :: Cryptography",
25
+ "Topic :: Utilities",
26
+ ]
27
+
28
+ [project.scripts]
29
+ unpacket = "securedypkt._unpacket:main"
30
+ repacket = "securedypkt._repacket:main"
31
+
32
+ [project.urls]
33
+ "Homepage" = "https://github.com/Nathan8044/SecuredyPKT"
34
+
35
+ [tool.setuptools.packages.find]
36
+ where = ["."]
37
+ include = ["securedypkt*"]
@@ -0,0 +1,26 @@
1
+ """
2
+ securedypkt — Cisco Packet Tracer .pkt crypto toolkit.
3
+ Securedy Labs, 2026.
4
+
5
+ Quick start:
6
+ from securedypkt import decrypt_pkt
7
+ xml_bytes = decrypt_pkt(open("lab.pkt", "rb").read())
8
+ """
9
+
10
+ from securedypkt.decipher.pt_crypto import decrypt_pkt
11
+ from securedypkt._repacket import encrypt_pkt
12
+
13
+ __version__ = "0.1.0"
14
+ __all__ = ["decrypt_pkt", "encrypt_pkt", "decrypt_file", "encrypt_file"]
15
+
16
+
17
+ def decrypt_file(path: str) -> bytes:
18
+ """Read a .pkt file and return the decrypted XML as bytes."""
19
+ with open(path, "rb") as fh:
20
+ return decrypt_pkt(fh.read())
21
+
22
+
23
+ def encrypt_file(path: str) -> bytes:
24
+ """Read an XML file and return the encrypted .pkt bytes."""
25
+ with open(path, "rb") as fh:
26
+ return encrypt_pkt(fh.read())
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import sys
4
+ import os
5
+ import zlib
6
+ import struct
7
+ from securedypkt.decipher.eax import EAX
8
+ from securedypkt.decipher.twofish import Twofish
9
+
10
+ def banner():
11
+ print("==============================================")
12
+ print(" SecuredyPKT - Cisco Packet Tracer Encryptor ")
13
+ print(" Securedy Labs ")
14
+ print("==============================================")
15
+
16
+ def compress_qt(xml_data: bytes) -> bytes:
17
+ size = len(xml_data)
18
+ header = struct.pack(">I", size)
19
+ compressed = zlib.compress(xml_data)
20
+ return header + compressed
21
+
22
+ def obf_stage2(data: bytes) -> bytes:
23
+ L = len(data)
24
+ return bytes(b ^ (L - i & 0xFF) for i, b in enumerate(data))
25
+
26
+ def obf_stage1(data: bytes) -> bytes:
27
+ L = len(data)
28
+ output = bytearray(L)
29
+ for i in range(L):
30
+ key_byte = (L - i*L) & 0xFF
31
+ val = data[i] ^ key_byte
32
+ output[L-1-i] = val
33
+ return bytes(output)
34
+
35
+ def encrypt_pkt(data: bytes) -> bytes:
36
+ key = bytes([137])*16
37
+ iv = bytes([16])*16
38
+ tf = Twofish(key)
39
+ eax = EAX(tf.encrypt)
40
+ ciphertext, tag = eax.encrypt(nonce=iv, plaintext=data)
41
+ return ciphertext + tag
42
+
43
+ def main():
44
+ banner()
45
+
46
+ parser = argparse.ArgumentParser(
47
+ description="Encrypts XML files back to Cisco Packet Tracer (.pkt) format."
48
+ )
49
+ parser.add_argument(
50
+ "input_file",
51
+ help="Path to the .xml file to encrypt."
52
+ )
53
+ parser.add_argument(
54
+ "-o", "--output",
55
+ help="Path to the output .pkt file. Defaults to <input_file>.pkt",
56
+ default=None
57
+ )
58
+
59
+ args = parser.parse_args()
60
+ input_path = args.input_file
61
+
62
+ if not os.path.exists(input_path):
63
+ print(f"[-] Error: Input file '{input_path}' not found.")
64
+ sys.exit(1)
65
+
66
+ if args.output:
67
+ output_path = args.output
68
+ else:
69
+ if input_path.lower().endswith(".xml"):
70
+ output_path = input_path[:-4] + ".pkt"
71
+ else:
72
+ output_path = input_path + ".pkt"
73
+
74
+ print(f"[*] Reading XML '{input_path}'...")
75
+ try:
76
+ with open(input_path, "rb") as f:
77
+ xml_data = f.read()
78
+ except IOError as e:
79
+ print(f"[-] Error reading file: {e}")
80
+ sys.exit(1)
81
+
82
+ try:
83
+ print("[*] Compressing data (zlib)...")
84
+ stage2_input = compress_qt(xml_data)
85
+
86
+ print("[*] Applying Stage 2 Obfuscation...")
87
+ decrypted_blob = obf_stage2(stage2_input)
88
+
89
+ print("[*] Encrypting (Twofish/EAX)...")
90
+ stage1_input = encrypt_pkt(decrypted_blob)
91
+
92
+ print("[*] Applying Stage 1 Obfuscation...")
93
+ final_pkt_data = obf_stage1(stage1_input)
94
+
95
+ except Exception as e:
96
+ print(f"[-] Encryption/Obfuscation failed: {e}")
97
+ sys.exit(1)
98
+
99
+ print(f"[*] Writing encrypted data to '{output_path}'...")
100
+ try:
101
+ with open(output_path, "wb") as f:
102
+ f.write(final_pkt_data)
103
+ except IOError as e:
104
+ print(f"[-] Error writing output file: {e}")
105
+ sys.exit(1)
106
+
107
+ print("[+] Success! File repacketed.")
108
+
109
+ if __name__ == "__main__":
110
+ main()
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+ import argparse
3
+ import sys
4
+ import os
5
+ import xml.etree.ElementTree as ET
6
+ from securedypkt.decipher.pt_crypto import decrypt_pkt
7
+
8
+ def banner():
9
+ print("==============================================")
10
+ print(" SecuredyPKT - Cisco Packet Tracer Decryptor ")
11
+ print(" Securedy Labs ")
12
+ print("==============================================")
13
+
14
+ def main():
15
+ banner()
16
+
17
+ parser = argparse.ArgumentParser(
18
+ description="Decrypts Cisco Packet Tracer (.pkt) files to XML format."
19
+ )
20
+ parser.add_argument(
21
+ "input_file",
22
+ help="Path to the .pkt file to decrypt."
23
+ )
24
+ parser.add_argument(
25
+ "-o", "--output",
26
+ help="Path to the output XML file. Defaults to <input_file>.xml",
27
+ default=None
28
+ )
29
+
30
+ args = parser.parse_args()
31
+
32
+ input_path = args.input_file
33
+
34
+ if not os.path.exists(input_path):
35
+ print(f"[-] Error: Input file '{input_path}' not found.")
36
+ sys.exit(1)
37
+
38
+ if args.output:
39
+ output_path = args.output
40
+ else:
41
+ if input_path.lower().endswith(".pkt"):
42
+ output_path = input_path[:-4] + ".xml"
43
+ else:
44
+ output_path = input_path + ".xml"
45
+
46
+ print(f"[*] Reading '{input_path}'...")
47
+ try:
48
+ with open(input_path, "rb") as f:
49
+ data = f.read()
50
+ except IOError as e:
51
+ print(f"[-] Error reading file: {e}")
52
+ sys.exit(1)
53
+
54
+ print("[*] Decrypting...")
55
+ try:
56
+ xml_data = decrypt_pkt(data)
57
+ except Exception as e:
58
+ print(f"[-] Decryption failed: {e}")
59
+ sys.exit(1)
60
+
61
+ print("[*] Parsing XML...")
62
+ try:
63
+ root = ET.fromstring(xml_data)
64
+ tree = ET.ElementTree(root)
65
+ if hasattr(ET, "indent"):
66
+ ET.indent(tree, space=" ")
67
+ except ET.ParseError as e:
68
+ print(f"[-] XML Parsing failed: {e}")
69
+ sys.exit(1)
70
+
71
+ print(f"[*] Writing decrypted data to '{output_path}'...")
72
+ try:
73
+ tree.write(output_path, encoding="utf-8", xml_declaration=True)
74
+ except IOError as e:
75
+ print(f"[-] Error writing output file: {e}")
76
+ sys.exit(1)
77
+
78
+ print("[+] Success! Decryption complete.")
79
+
80
+ if __name__ == "__main__":
81
+ main()
File without changes
@@ -0,0 +1,58 @@
1
+ from typing import Callable
2
+
3
+ BLOCK_SIZE = 16 # 128 bit for Twofish
4
+
5
+ def xor_bytes(a: bytes, b: bytes) -> bytes:
6
+ return bytes(x ^ y for x, y in zip(a, b))
7
+
8
+ def left_shift_one(bitstring: bytes) -> bytes:
9
+ out = bytearray(len(bitstring))
10
+ carry = 0
11
+ for i in reversed(range(len(bitstring))):
12
+ new = (bitstring[i] << 1) & 0xFF
13
+ out[i] = new | carry
14
+ carry = (bitstring[i] & 0x80) >> 7
15
+ return bytes(out)
16
+
17
+ def generate_subkeys(encrypt_block: Callable[[bytes], bytes]):
18
+ const_rb = 0x87
19
+ zero = bytes(BLOCK_SIZE)
20
+ L = encrypt_block(zero)
21
+
22
+ K1 = left_shift_one(L)
23
+ if L[0] & 0x80:
24
+ K1 = xor_bytes(K1, b'\x00' * 15 + bytes([const_rb]))
25
+
26
+ K2 = left_shift_one(K1)
27
+ if K1[0] & 0x80:
28
+ K2 = xor_bytes(K2, b'\x00' * 15 + bytes([const_rb]))
29
+
30
+ return K1, K2
31
+
32
+ def pad(block: bytes) -> bytes:
33
+ padded = block + b'\x80'
34
+ return padded + b'\x00' * (BLOCK_SIZE - len(padded))
35
+
36
+ class CMAC:
37
+ def __init__(self, encrypt_block: Callable[[bytes], bytes]):
38
+ self.encrypt_block = encrypt_block
39
+ self.K1, self.K2 = generate_subkeys(encrypt_block)
40
+
41
+ def digest(self, data: bytes) -> bytes:
42
+ if len(data) == 0:
43
+ last = xor_bytes(pad(b''), self.K2)
44
+ blocks = []
45
+ else:
46
+ blocks = [data[i:i+BLOCK_SIZE] for i in range(0, len(data), BLOCK_SIZE)]
47
+ if len(blocks[-1]) == BLOCK_SIZE:
48
+ last = xor_bytes(blocks[-1], self.K1)
49
+ blocks = blocks[:-1]
50
+ else:
51
+ last = xor_bytes(pad(blocks[-1]), self.K2)
52
+ blocks = blocks[:-1]
53
+
54
+ X = bytes(BLOCK_SIZE)
55
+ for block in blocks:
56
+ X = self.encrypt_block(xor_bytes(X, block))
57
+
58
+ return self.encrypt_block(xor_bytes(X, last))
@@ -0,0 +1,33 @@
1
+ from typing import Callable
2
+
3
+ BLOCK_SIZE = 16 # 128 bit
4
+
5
+
6
+ def inc_counter_be(counter: bytearray):
7
+ """Incrementa un contatore big-endian a 128 bit (come Crypto++)."""
8
+ for i in range(BLOCK_SIZE - 1, -1, -1):
9
+ counter[i] = (counter[i] + 1) & 0xFF
10
+ if counter[i] != 0:
11
+ break
12
+
13
+
14
+ class CTR:
15
+ def __init__(self, encrypt_block: Callable[[bytes], bytes], initial_counter: bytes):
16
+ assert len(initial_counter) == BLOCK_SIZE
17
+ self.encrypt_block = encrypt_block
18
+ self.counter = bytearray(initial_counter)
19
+
20
+ def process(self, data: bytes) -> bytes:
21
+ out = bytearray()
22
+ offset = 0
23
+
24
+ while offset < len(data):
25
+ keystream = self.encrypt_block(bytes(self.counter))
26
+ inc_counter_be(self.counter)
27
+
28
+ block = data[offset:offset + BLOCK_SIZE]
29
+ ks = keystream[:len(block)]
30
+ out.extend(b ^ k for b, k in zip(block, ks))
31
+ offset += BLOCK_SIZE
32
+
33
+ return bytes(out)
@@ -0,0 +1,53 @@
1
+ from typing import Callable
2
+ from .cmac import CMAC, xor_bytes, BLOCK_SIZE
3
+ from .ctr import CTR
4
+
5
+
6
+ def _omac_with_prefix(cmac: CMAC, prefix: int, data: bytes) -> bytes:
7
+ # Prefisso di 16 byte: [0, 0, ..., prefix]
8
+ P = b'\x00' * (BLOCK_SIZE - 1) + bytes([prefix])
9
+ return cmac.digest(P + data)
10
+
11
+
12
+ class EAX:
13
+ def __init__(self, encrypt_block: Callable[[bytes], bytes]):
14
+ self.encrypt_block = encrypt_block
15
+ self.cmac = CMAC(encrypt_block)
16
+
17
+ def encrypt(self, nonce: bytes, plaintext: bytes, aad: bytes = b''):
18
+ # OMAC_0 = CMAC(0x00 || nonce)
19
+ n_tag = _omac_with_prefix(self.cmac, 0x00, nonce)
20
+
21
+ # OMAC_1 = CMAC(0x01 || aad)
22
+ h_tag = _omac_with_prefix(self.cmac, 0x01, aad)
23
+
24
+ # CTR parte da n_tag
25
+ ctr = CTR(self.encrypt_block, n_tag)
26
+ ciphertext = ctr.process(plaintext)
27
+
28
+ # OMAC_2 = CMAC(0x02 || ciphertext)
29
+ c_tag = _omac_with_prefix(self.cmac, 0x02, ciphertext)
30
+
31
+ # TAG finale = n_tag ⊕ h_tag ⊕ c_tag
32
+ tag = xor_bytes(xor_bytes(n_tag, h_tag), c_tag)
33
+
34
+ return ciphertext, tag
35
+
36
+ def decrypt(self, nonce: bytes, ciphertext: bytes, tag: bytes, aad: bytes = b''):
37
+ # Ricalcolo OMAC_0
38
+ n_tag = _omac_with_prefix(self.cmac, 0x00, nonce)
39
+
40
+ # CTR
41
+ ctr = CTR(self.encrypt_block, n_tag)
42
+ plaintext = ctr.process(ciphertext)
43
+
44
+ # Ricalcolo OMAC_1 e OMAC_2
45
+ h_tag = _omac_with_prefix(self.cmac, 0x01, aad)
46
+ c_tag = _omac_with_prefix(self.cmac, 0x02, ciphertext)
47
+
48
+ # Verifica TAG
49
+ expected_tag = xor_bytes(xor_bytes(n_tag, h_tag), c_tag)
50
+ if expected_tag != tag:
51
+ raise ValueError("EAX authentication failed")
52
+
53
+ return plaintext
@@ -0,0 +1,46 @@
1
+ from .eax import EAX
2
+ from .twofish import Twofish
3
+ import zlib
4
+ import struct
5
+
6
+ def deobf_stage1(data: bytes) -> bytes:
7
+ L = len(data)
8
+ return bytes(data[L-1-i] ^ (L - i*L & 0xFF) for i in range(L))
9
+
10
+ def deobf_stage2(data: bytes) -> bytes:
11
+ L = len(data)
12
+ return bytes(b ^ (L - i & 0xFF) for i, b in enumerate(data))
13
+
14
+ def uncompress_qt(blob: bytes) -> bytes:
15
+ size = struct.unpack(">I", blob[:4])[0]
16
+ return zlib.decompress(blob[4:])[:size]
17
+
18
+ def decrypt_pkt(pkt: bytes) -> bytes:
19
+ # Stage 1 deobfuscation
20
+ stage1 = deobf_stage1(pkt)
21
+
22
+ # Chiave e IV per i file .pkt
23
+ key = bytes([137])*16
24
+ iv = bytes([16])*16
25
+
26
+ # Twofish block cipher
27
+ tf = Twofish(key)
28
+ encrypt_block = tf.encrypt
29
+
30
+ # EAX con nonce = iv
31
+ eax = EAX(encrypt_block)
32
+
33
+ # Supponiamo che negli .pkt il tag sia alla fine
34
+ ciphertext = stage1[:-16]
35
+ tag = stage1[-16:]
36
+
37
+ # Decrypt usando nonce fisso
38
+ decrypted = eax.decrypt(nonce=iv, ciphertext=ciphertext, tag=tag)
39
+
40
+ # Stage 2 deobfuscation
41
+ stage2 = deobf_stage2(decrypted)
42
+
43
+ # Decompressione
44
+ xml = uncompress_qt(stage2)
45
+
46
+ return xml
@@ -0,0 +1,393 @@
1
+ ## twofish.py - pure Python implementation of the Twofish algorithm.
2
+ ## Bjorn Edstrom <be@bjrn.se> 13 december 2007.
3
+ ##
4
+ ## Copyrights
5
+ ## ==========
6
+ ##
7
+ ## This code is a derived from an implementation by Dr Brian Gladman
8
+ ## (gladman@seven77.demon.co.uk) which is subject to the following license.
9
+ ## This Python implementation is not subject to any other license.
10
+ ##
11
+ ##/* This is an independent implementation of the encryption algorithm: */
12
+ ##/* */
13
+ ##/* Twofish by Bruce Schneier and colleagues */
14
+ ##/* */
15
+ ##/* which is a candidate algorithm in the Advanced Encryption Standard */
16
+ ##/* programme of the US National Institute of Standards and Technology. */
17
+ ##/* */
18
+ ##/* Copyright in this implementation is held by Dr B R Gladman but I */
19
+ ##/* hereby give permission for its free direct or derivative use subject */
20
+ ##/* to acknowledgment of its origin and compliance with any conditions */
21
+ ##/* that the originators of t he algorithm place on its exploitation. */
22
+ ##/* */
23
+ ##/* My thanks to Doug Whiting and Niels Ferguson for comments that led */
24
+ ##/* to improvements in this implementation. */
25
+ ##/* */
26
+ ##/* Dr Brian Gladman (gladman@seven77.demon.co.uk) 14th January 1999 */
27
+ ##
28
+ ## The above copyright notice must not be removed.
29
+ ##
30
+ ## Information
31
+ ## ===========
32
+ ##
33
+ ## Anyone thinking of using this code should reconsider. It's slow.
34
+ ## Try python-mcrypt instead. In case a faster library is not installed
35
+ ## on the target system, this code can be used as a portable fallback.
36
+
37
+ # pylint: disable-all
38
+
39
+ block_size = 16
40
+ key_size = 32
41
+
42
+ class Twofish:
43
+
44
+ def __init__(self, key=None):
45
+ """Twofish."""
46
+
47
+ if key:
48
+ self.set_key(key)
49
+
50
+
51
+ def set_key(self, key):
52
+ """Init."""
53
+
54
+ key_len = len(key)
55
+ if key_len not in [16, 24, 32]:
56
+ # XXX: add padding?
57
+ raise KeyError("key must be 16, 24 or 32 bytes")
58
+ if key_len % 4:
59
+ # XXX: add padding?
60
+ raise KeyError("key not a multiple of 4")
61
+ if key_len > 32:
62
+ # XXX: prune?
63
+ raise KeyError("key_len > 32")
64
+
65
+ self.context = TWI()
66
+
67
+ key_word32 = [0] * 32
68
+ i = 0
69
+ while key:
70
+ key_word32[i] = struct.unpack("<L", key[0:4])[0]
71
+ key = key[4:]
72
+ i += 1
73
+
74
+ set_key(self.context, key_word32, key_len)
75
+
76
+
77
+ def decrypt(self, block):
78
+ """Decrypt blocks."""
79
+
80
+ if len(block) % 16:
81
+ raise ValueError("block size must be a multiple of 16")
82
+
83
+ plaintext = b''
84
+
85
+ while block:
86
+ a, b, c, d = struct.unpack("<4L", block[:16])
87
+ temp = [a, b, c, d]
88
+ decrypt(self.context, temp)
89
+ plaintext += struct.pack("<4L", *temp)
90
+ block = block[16:]
91
+
92
+ return plaintext
93
+
94
+
95
+ def encrypt(self, block):
96
+ """Encrypt blocks."""
97
+
98
+ if len(block) % 16:
99
+ raise ValueError("block size must be a multiple of 16")
100
+
101
+ ciphertext = b''
102
+
103
+ while block:
104
+ a, b, c, d = struct.unpack("<4L", block[0:16])
105
+ temp = [a, b, c, d]
106
+ encrypt(self.context, temp)
107
+ ciphertext += struct.pack("<4L", *temp)
108
+ block = block[16:]
109
+
110
+ return ciphertext
111
+
112
+
113
+ def get_name(self):
114
+ """Return the name of the cipher."""
115
+
116
+ return "Twofish"
117
+
118
+
119
+ def get_block_size(self):
120
+ """Get cipher block size in bytes."""
121
+
122
+ return 16
123
+
124
+
125
+ def get_key_size(self):
126
+ """Get cipher key size in bytes."""
127
+
128
+ return 32
129
+
130
+
131
+ #
132
+ # Private.
133
+ #
134
+
135
+ import struct
136
+ import sys
137
+
138
+ WORD_BIGENDIAN = 0
139
+ if sys.byteorder == 'big':
140
+ WORD_BIGENDIAN = 1
141
+
142
+ def rotr32(x, n):
143
+ return (x >> n) | ((x << (32 - n)) & 0xFFFFFFFF)
144
+
145
+ def rotl32(x, n):
146
+ return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))
147
+
148
+ def byteswap32(x):
149
+ return ((x & 0xff) << 24) | (((x >> 8) & 0xff) << 16) | \
150
+ (((x >> 16) & 0xff) << 8) | ((x >> 24) & 0xff)
151
+
152
+ class TWI:
153
+ def __init__(self):
154
+ self.k_len = 0 # word32
155
+ self.l_key = [0]*40 # word32
156
+ self.s_key = [0]*4 # word32
157
+ self.qt_gen = 0 # word32
158
+ self.q_tab = [[0]*256, [0]*256] # byte
159
+ self.mt_gen = 0 # word32
160
+ self.m_tab = [[0]*256, [0]*256, [0]*256, [0]*256] # word32
161
+ self.mk_tab = [[0]*256, [0]*256, [0]*256, [0]*256] # word32
162
+
163
+ def byte(x, n):
164
+ return (x >> (8 * n)) & 0xff
165
+
166
+ tab_5b = [0, 90, 180, 238]
167
+ tab_ef = [0, 238, 180, 90]
168
+ ror4 = [0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15]
169
+ ashx = [0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, 5, 14, 7]
170
+ qt0 = [[8, 1, 7, 13, 6, 15, 3, 2, 0, 11, 5, 9, 14, 12, 10, 4],
171
+ [2, 8, 11, 13, 15, 7, 6, 14, 3, 1, 9, 4, 0, 10, 12, 5]]
172
+ qt1 = [[14, 12, 11, 8, 1, 2, 3, 5, 15, 4, 10, 6, 7, 0, 9, 13],
173
+ [1, 14, 2, 11, 4, 12, 3, 7, 6, 13, 10, 5, 15, 9, 0, 8]]
174
+ qt2 = [[11, 10, 5, 14, 6, 13, 9, 0, 12, 8, 15, 3, 2, 4, 7, 1],
175
+ [4, 12, 7, 5, 1, 6, 9, 10, 0, 14, 13, 8, 2, 11, 3, 15]]
176
+ qt3 = [[13, 7, 15, 4, 1, 2, 6, 14, 9, 11, 3, 0, 8, 5, 12, 10],
177
+ [11, 9, 5, 1, 12, 3, 13, 14, 6, 4, 7, 15, 2, 0, 8, 10]]
178
+
179
+ def qp(n, x): # word32, byte
180
+ n %= 0x100000000
181
+ x %= 0x100
182
+ a0 = x >> 4;
183
+ b0 = x & 15;
184
+ a1 = a0 ^ b0;
185
+ b1 = ror4[b0] ^ ashx[a0];
186
+ a2 = qt0[n][a1];
187
+ b2 = qt1[n][b1];
188
+ a3 = a2 ^ b2;
189
+ b3 = ror4[b2] ^ ashx[a2];
190
+ a4 = qt2[n][a3];
191
+ b4 = qt3[n][b3];
192
+ return (b4 << 4) | a4;
193
+
194
+ def gen_qtab(pkey):
195
+ for i in range(256):
196
+ pkey.q_tab[0][i] = qp(0, i)
197
+ pkey.q_tab[1][i] = qp(1, i)
198
+
199
+ def gen_mtab(pkey):
200
+ for i in range(256):
201
+ f01 = pkey.q_tab[1][i]
202
+ f01 = pkey.q_tab[1][i];
203
+ f5b = ((f01) ^ ((f01) >> 2) ^ tab_5b[(f01) & 3]);
204
+ fef = ((f01) ^ ((f01) >> 1) ^ ((f01) >> 2) ^ tab_ef[(f01) & 3]);
205
+ pkey.m_tab[0][i] = f01 + (f5b << 8) + (fef << 16) + (fef << 24);
206
+ pkey.m_tab[2][i] = f5b + (fef << 8) + (f01 << 16) + (fef << 24);
207
+
208
+ f01 = pkey.q_tab[0][i];
209
+ f5b = ((f01) ^ ((f01) >> 2) ^ tab_5b[(f01) & 3]);
210
+ fef = ((f01) ^ ((f01) >> 1) ^ ((f01) >> 2) ^ tab_ef[(f01) & 3]);
211
+ pkey.m_tab[1][i] = fef + (fef << 8) + (f5b << 16) + (f01 << 24);
212
+ pkey.m_tab[3][i] = f5b + (f01 << 8) + (fef << 16) + (f5b << 24);
213
+
214
+ def gen_mk_tab(pkey, key):
215
+ if pkey.k_len == 2:
216
+ for i in range(256):
217
+ by = i % 0x100
218
+ pkey.mk_tab[0][i] = pkey.m_tab[0][pkey.q_tab[0][pkey.q_tab[0][by] ^ byte(key[1],0)] ^ byte(key[0],0)];
219
+ pkey.mk_tab[1][i] = pkey.m_tab[1][pkey.q_tab[0][pkey.q_tab[1][by] ^ byte(key[1],1)] ^ byte(key[0],1)];
220
+ pkey.mk_tab[2][i] = pkey.m_tab[2][pkey.q_tab[1][pkey.q_tab[0][by] ^ byte(key[1],2)] ^ byte(key[0],2)];
221
+ pkey.mk_tab[3][i] = pkey.m_tab[3][pkey.q_tab[1][pkey.q_tab[1][by] ^ byte(key[1],3)] ^ byte(key[0],3)];
222
+ if pkey.k_len == 3:
223
+ for i in range(256):
224
+ by = i % 0x100
225
+ pkey.mk_tab[0][i] = pkey.m_tab[0][pkey.q_tab[0][pkey.q_tab[0][pkey.q_tab[1][by] ^ byte(key[2], 0)] ^ byte(key[1], 0)] ^ byte(key[0], 0)];
226
+ pkey.mk_tab[1][i] = pkey.m_tab[1][pkey.q_tab[0][pkey.q_tab[1][pkey.q_tab[1][by] ^ byte(key[2], 1)] ^ byte(key[1], 1)] ^ byte(key[0], 1)];
227
+ pkey.mk_tab[2][i] = pkey.m_tab[2][pkey.q_tab[1][pkey.q_tab[0][pkey.q_tab[0][by] ^ byte(key[2], 2)] ^ byte(key[1], 2)] ^ byte(key[0], 2)];
228
+ pkey.mk_tab[3][i] = pkey.m_tab[3][pkey.q_tab[1][pkey.q_tab[1][pkey.q_tab[0][by] ^ byte(key[2], 3)] ^ byte(key[1], 3)] ^ byte(key[0], 3)];
229
+ if pkey.k_len == 4:
230
+ for i in range(256):
231
+ by = i % 0x100
232
+ pkey.mk_tab[0][i] = pkey.m_tab[0][pkey.q_tab[0][pkey.q_tab[0][pkey.q_tab[1][pkey.q_tab[1][by] ^ byte(key[3], 0)] ^ byte(key[2], 0)] ^ byte(key[1], 0)] ^ byte(key[0], 0)];
233
+ pkey.mk_tab[1][i] = pkey.m_tab[1][pkey.q_tab[0][pkey.q_tab[1][pkey.q_tab[1][pkey.q_tab[0][by] ^ byte(key[3], 1)] ^ byte(key[2], 1)] ^ byte(key[1], 1)] ^ byte(key[0], 1)];
234
+ pkey.mk_tab[2][i] = pkey.m_tab[2][pkey.q_tab[1][pkey.q_tab[0][pkey.q_tab[0][pkey.q_tab[0][by] ^ byte(key[3], 2)] ^ byte(key[2], 2)] ^ byte(key[1], 2)] ^ byte(key[0], 2)];
235
+ pkey.mk_tab[3][i] = pkey.m_tab[3][pkey.q_tab[1][pkey.q_tab[1][pkey.q_tab[0][pkey.q_tab[1][by] ^ byte(key[3], 3)] ^ byte(key[2], 3)] ^ byte(key[1], 3)] ^ byte(key[0], 3)];
236
+
237
+ def h_fun(pkey, x, key):
238
+ b0 = byte(x, 0);
239
+ b1 = byte(x, 1);
240
+ b2 = byte(x, 2);
241
+ b3 = byte(x, 3);
242
+ if pkey.k_len >= 4:
243
+ b0 = pkey.q_tab[1][b0] ^ byte(key[3], 0);
244
+ b1 = pkey.q_tab[0][b1] ^ byte(key[3], 1);
245
+ b2 = pkey.q_tab[0][b2] ^ byte(key[3], 2);
246
+ b3 = pkey.q_tab[1][b3] ^ byte(key[3], 3);
247
+ if pkey.k_len >= 3:
248
+ b0 = pkey.q_tab[1][b0] ^ byte(key[2], 0);
249
+ b1 = pkey.q_tab[1][b1] ^ byte(key[2], 1);
250
+ b2 = pkey.q_tab[0][b2] ^ byte(key[2], 2);
251
+ b3 = pkey.q_tab[0][b3] ^ byte(key[2], 3);
252
+ if pkey.k_len >= 2:
253
+ b0 = pkey.q_tab[0][pkey.q_tab[0][b0] ^ byte(key[1], 0)] ^ byte(key[0], 0);
254
+ b1 = pkey.q_tab[0][pkey.q_tab[1][b1] ^ byte(key[1], 1)] ^ byte(key[0], 1);
255
+ b2 = pkey.q_tab[1][pkey.q_tab[0][b2] ^ byte(key[1], 2)] ^ byte(key[0], 2);
256
+ b3 = pkey.q_tab[1][pkey.q_tab[1][b3] ^ byte(key[1], 3)] ^ byte(key[0], 3);
257
+ return pkey.m_tab[0][b0] ^ pkey.m_tab[1][b1] ^ pkey.m_tab[2][b2] ^ pkey.m_tab[3][b3];
258
+
259
+ def mds_rem(p0, p1):
260
+ i, t, u = 0, 0, 0
261
+ for i in range(8):
262
+ t = p1 >> 24
263
+ p1 = ((p1 << 8) & 0xffffffff) | (p0 >> 24)
264
+ p0 = (p0 << 8) & 0xffffffff
265
+ u = (t << 1) & 0xffffffff
266
+ if t & 0x80:
267
+ u ^= 0x0000014d
268
+ p1 ^= t ^ ((u << 16) & 0xffffffff)
269
+ u ^= (t >> 1)
270
+ if t & 0x01:
271
+ u ^= 0x0000014d >> 1
272
+ p1 ^= ((u << 24) & 0xffffffff) | ((u << 8) & 0xffffffff)
273
+ return p1
274
+
275
+ def set_key(pkey, in_key, key_len):
276
+ pkey.qt_gen = 0
277
+ if not pkey.qt_gen:
278
+ gen_qtab(pkey)
279
+ pkey.qt_gen = 1
280
+ pkey.mt_gen = 0
281
+ if not pkey.mt_gen:
282
+ gen_mtab(pkey)
283
+ pkey.mt_gen = 1
284
+ pkey.k_len = (key_len * 8) // 64
285
+
286
+ a = 0
287
+ b = 0
288
+ me_key = [0,0,0,0]
289
+ mo_key = [0,0,0,0]
290
+ for i in range(pkey.k_len):
291
+ if WORD_BIGENDIAN:
292
+ a = byteswap32(in_key[i + 1])
293
+ me_key[i] = a
294
+ b = byteswap32(in_key[i + i + 1])
295
+ else:
296
+ a = in_key[i + i]
297
+ me_key[i] = a
298
+ b = in_key[i + i + 1]
299
+ mo_key[i] = b
300
+ pkey.s_key[pkey.k_len - i - 1] = mds_rem(a, b);
301
+ for i in range(0, 40, 2):
302
+ a = (0x01010101 * i) % 0x100000000;
303
+ b = (a + 0x01010101) % 0x100000000;
304
+ a = h_fun(pkey, a, me_key);
305
+ b = rotl32(h_fun(pkey, b, mo_key), 8);
306
+ pkey.l_key[i] = (a + b) % 0x100000000;
307
+ pkey.l_key[i + 1] = rotl32((a + 2 * b) % 0x100000000, 9);
308
+ gen_mk_tab(pkey, pkey.s_key)
309
+
310
+ def encrypt(pkey, in_blk):
311
+ blk = [0, 0, 0, 0]
312
+
313
+ if WORD_BIGENDIAN:
314
+ blk[0] = byteswap32(in_blk[0]) ^ pkey.l_key[0];
315
+ blk[1] = byteswap32(in_blk[1]) ^ pkey.l_key[1];
316
+ blk[2] = byteswap32(in_blk[2]) ^ pkey.l_key[2];
317
+ blk[3] = byteswap32(in_blk[3]) ^ pkey.l_key[3];
318
+ else:
319
+ blk[0] = in_blk[0] ^ pkey.l_key[0];
320
+ blk[1] = in_blk[1] ^ pkey.l_key[1];
321
+ blk[2] = in_blk[2] ^ pkey.l_key[2];
322
+ blk[3] = in_blk[3] ^ pkey.l_key[3];
323
+
324
+ for i in range(8):
325
+ t1 = ( pkey.mk_tab[0][byte(blk[1],3)] ^ pkey.mk_tab[1][byte(blk[1],0)] ^ pkey.mk_tab[2][byte(blk[1],1)] ^ pkey.mk_tab[3][byte(blk[1],2)] );
326
+ t0 = ( pkey.mk_tab[0][byte(blk[0],0)] ^ pkey.mk_tab[1][byte(blk[0],1)] ^ pkey.mk_tab[2][byte(blk[0],2)] ^ pkey.mk_tab[3][byte(blk[0],3)] );
327
+
328
+ blk[2] = rotr32(blk[2] ^ ((t0 + t1 + pkey.l_key[4 * (i) + 8]) % 0x100000000), 1);
329
+ blk[3] = rotl32(blk[3], 1) ^ ((t0 + 2 * t1 + pkey.l_key[4 * (i) + 9]) % 0x100000000);
330
+
331
+ t1 = ( pkey.mk_tab[0][byte(blk[3],3)] ^ pkey.mk_tab[1][byte(blk[3],0)] ^ pkey.mk_tab[2][byte(blk[3],1)] ^ pkey.mk_tab[3][byte(blk[3],2)] );
332
+ t0 = ( pkey.mk_tab[0][byte(blk[2],0)] ^ pkey.mk_tab[1][byte(blk[2],1)] ^ pkey.mk_tab[2][byte(blk[2],2)] ^ pkey.mk_tab[3][byte(blk[2],3)] );
333
+
334
+ blk[0] = rotr32(blk[0] ^ ((t0 + t1 + pkey.l_key[4 * (i) + 10]) % 0x100000000), 1);
335
+ blk[1] = rotl32(blk[1], 1) ^ ((t0 + 2 * t1 + pkey.l_key[4 * (i) + 11]) % 0x100000000);
336
+
337
+ if WORD_BIGENDIAN:
338
+ in_blk[0] = byteswap32(blk[2] ^ pkey.l_key[4]);
339
+ in_blk[1] = byteswap32(blk[3] ^ pkey.l_key[5]);
340
+ in_blk[2] = byteswap32(blk[0] ^ pkey.l_key[6]);
341
+ in_blk[3] = byteswap32(blk[1] ^ pkey.l_key[7]);
342
+ else:
343
+ in_blk[0] = blk[2] ^ pkey.l_key[4];
344
+ in_blk[1] = blk[3] ^ pkey.l_key[5];
345
+ in_blk[2] = blk[0] ^ pkey.l_key[6];
346
+ in_blk[3] = blk[1] ^ pkey.l_key[7];
347
+
348
+ return
349
+
350
+ def decrypt(pkey, in_blk):
351
+ blk = [0, 0, 0, 0]
352
+
353
+ if WORD_BIGENDIAN:
354
+ blk[0] = byteswap32(in_blk[0]) ^ pkey.l_key[4];
355
+ blk[1] = byteswap32(in_blk[1]) ^ pkey.l_key[5];
356
+ blk[2] = byteswap32(in_blk[2]) ^ pkey.l_key[6];
357
+ blk[3] = byteswap32(in_blk[3]) ^ pkey.l_key[7];
358
+ else:
359
+ blk[0] = in_blk[0] ^ pkey.l_key[4];
360
+ blk[1] = in_blk[1] ^ pkey.l_key[5];
361
+ blk[2] = in_blk[2] ^ pkey.l_key[6];
362
+ blk[3] = in_blk[3] ^ pkey.l_key[7];
363
+
364
+ for i in range(7, -1, -1):
365
+ t1 = ( pkey.mk_tab[0][byte(blk[1],3)] ^ pkey.mk_tab[1][byte(blk[1],0)] ^ pkey.mk_tab[2][byte(blk[1],1)] ^ pkey.mk_tab[3][byte(blk[1],2)] )
366
+ t0 = ( pkey.mk_tab[0][byte(blk[0],0)] ^ pkey.mk_tab[1][byte(blk[0],1)] ^ pkey.mk_tab[2][byte(blk[0],2)] ^ pkey.mk_tab[3][byte(blk[0],3)] )
367
+
368
+ blk[2] = rotl32(blk[2], 1) ^ ((t0 + t1 + pkey.l_key[4 * (i) + 10]) % 0x100000000)
369
+ blk[3] = rotr32(blk[3] ^ ((t0 + 2 * t1 + pkey.l_key[4 * (i) + 11]) % 0x100000000), 1)
370
+
371
+ t1 = ( pkey.mk_tab[0][byte(blk[3],3)] ^ pkey.mk_tab[1][byte(blk[3],0)] ^ pkey.mk_tab[2][byte(blk[3],1)] ^ pkey.mk_tab[3][byte(blk[3],2)] )
372
+ t0 = ( pkey.mk_tab[0][byte(blk[2],0)] ^ pkey.mk_tab[1][byte(blk[2],1)] ^ pkey.mk_tab[2][byte(blk[2],2)] ^ pkey.mk_tab[3][byte(blk[2],3)] )
373
+
374
+ blk[0] = rotl32(blk[0], 1) ^ ((t0 + t1 + pkey.l_key[4 * (i) + 8]) % 0x100000000)
375
+ blk[1] = rotr32(blk[1] ^ ((t0 + 2 * t1 + pkey.l_key[4 * (i) + 9]) % 0x100000000), 1)
376
+
377
+ if WORD_BIGENDIAN:
378
+ in_blk[0] = byteswap32(blk[2] ^ pkey.l_key[0]);
379
+ in_blk[1] = byteswap32(blk[3] ^ pkey.l_key[1]);
380
+ in_blk[2] = byteswap32(blk[0] ^ pkey.l_key[2]);
381
+ in_blk[3] = byteswap32(blk[1] ^ pkey.l_key[3]);
382
+ else:
383
+ in_blk[0] = blk[2] ^ pkey.l_key[0];
384
+ in_blk[1] = blk[3] ^ pkey.l_key[1];
385
+ in_blk[2] = blk[0] ^ pkey.l_key[2];
386
+ in_blk[3] = blk[1] ^ pkey.l_key[3];
387
+ return
388
+
389
+ __testkey = b'\xD4\x3B\xB7\x55\x6E\xA3\x2E\x46\xF2\xA2\x82\xB7\xD4\x5B\x4E\x0D\x57\xFF\x73\x9D\x4D\xC9\x2C\x1B\xD7\xFC\x01\x70\x0C\xC8\x21\x6F'
390
+ __testdat = b'\x90\xAF\xE9\x1B\xB2\x88\x54\x4F\x2C\x32\xDC\x23\x9B\x26\x35\xE6'
391
+ assert b'l\xb4V\x1c@\xbf\n\x97\x05\x93\x1c\xb6\xd4\x08\xe7\xfa' == Twofish(__testkey).encrypt(__testdat)
392
+ assert __testdat == Twofish(__testkey).decrypt(b'l\xb4V\x1c@\xbf\n\x97\x05\x93\x1c\xb6\xd4\x08\xe7\xfa')
393
+
@@ -0,0 +1,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: securedypkt
3
+ Version: 0.1.0
4
+ Summary: Pure-Python toolkit for decrypting and re-encrypting Cisco Packet Tracer (.pkt) files
5
+ Author-email: Securedy Labs <nathanrampersaud@gmail.com>
6
+ Project-URL: Homepage, https://github.com/Nathan8044/SecuredyPKT
7
+ Keywords: cisco,packet-tracer,pkt,twofish,eax,crypto,network
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Topic :: Security :: Cryptography
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ # SecuredyPKT
24
+
25
+ **SecuredyPKT** is a pure-Python research tool by **Securedy Labs** for decrypting and re-encrypting Cisco Packet Tracer (`.pkt`) files.
26
+
27
+ It fully reimplements the proprietary cryptographic and obfuscation pipeline used internally by Packet Tracer, enabling offline inspection, diffing, and version-controlling of lab topologies — without requiring the official application.
28
+
29
+ This is an active research and development project. A custom crypto model and extended feature set are planned.
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install securedypkt
37
+ ```
38
+
39
+ No additional dependencies required. Pure Python 3.8+.
40
+
41
+ ---
42
+
43
+ ## Python API (for agents and integrations)
44
+
45
+ ```python
46
+ import securedypkt
47
+
48
+ # Decrypt a .pkt file → XML bytes
49
+ xml_bytes = securedypkt.decrypt_file("lab.pkt")
50
+
51
+ # Or work directly with bytes (e.g. from an upload handler)
52
+ with open("lab.pkt", "rb") as f:
53
+ xml_bytes = securedypkt.decrypt_pkt(f.read())
54
+
55
+ # Re-encrypt XML bytes → .pkt bytes
56
+ pkt_bytes = securedypkt.encrypt_pkt(xml_bytes)
57
+ pkt_bytes = securedypkt.encrypt_file("lab.xml")
58
+ ```
59
+
60
+ ---
61
+
62
+ ## CLI Usage
63
+
64
+ ### Decrypt `.pkt` → XML
65
+
66
+ ```bash
67
+ unpacket lab.pkt
68
+ # Output: lab.xml
69
+
70
+ unpacket lab.pkt -o decrypted.xml
71
+ ```
72
+
73
+ ### Re-encrypt XML → `.pkt`
74
+
75
+ ```bash
76
+ repacket lab.xml
77
+ # Output: lab.pkt
78
+
79
+ repacket lab.xml -o rebuilt.pkt
80
+ ```
81
+
82
+ ### Options
83
+
84
+ | Tool | Option | Description |
85
+ |------|--------|-------------|
86
+ | `unpacket` | `input_file` | Path to `.pkt` file |
87
+ | | `-o, --output` | Output XML path |
88
+ | `repacket` | `input_file` | Path to `.xml` file |
89
+ | | `-o, --output` | Output `.pkt` path |
90
+
91
+ ---
92
+
93
+ ## How It Works
94
+
95
+ Cisco Packet Tracer `.pkt` files are protected by a 4-layer scheme. SecuredyPKT reverses all four layers:
96
+
97
+ ```
98
+ [ .pkt file on disk ]
99
+ |
100
+ v
101
+ [1] Stage-1 Deobfuscation
102
+ Byte-order reversal + positional XOR
103
+ result[i] = data[L-1-i] ^ (L - i*L & 0xFF)
104
+ |
105
+ v
106
+ [2] Twofish/EAX Decryption (authenticated)
107
+ 128-bit Twofish block cipher in EAX mode
108
+ Key: 0x89 * 16 (hardcoded in Packet Tracer)
109
+ IV: 0x10 * 16 (hardcoded in Packet Tracer)
110
+ Last 16 bytes of ciphertext = AEAD authentication tag
111
+ |
112
+ v
113
+ [3] Stage-2 Deobfuscation
114
+ Positional XOR with decreasing counter
115
+ result[i] = b ^ (L - i & 0xFF)
116
+ |
117
+ v
118
+ [4] Qt Decompression
119
+ zlib stream with 4-byte big-endian size prefix (qCompress format)
120
+ |
121
+ v
122
+ [ Plain XML topology ]
123
+ ```
124
+
125
+ `repacket` runs this pipeline in reverse: XML → compress → obfuscate → encrypt → obfuscate → `.pkt`.
126
+
127
+ ---
128
+
129
+ ## Crypto Stack
130
+
131
+ | Module | Purpose |
132
+ |--------|---------|
133
+ | `securedypkt/decipher/twofish.py` | Pure-Python Twofish block cipher (128-bit) |
134
+ | `securedypkt/decipher/cmac.py` | CMAC / OMAC message authentication code |
135
+ | `securedypkt/decipher/ctr.py` | CTR mode keystream engine (big-endian counter) |
136
+ | `securedypkt/decipher/eax.py` | EAX authenticated encryption (AEAD) |
137
+ | `securedypkt/decipher/pt_crypto.py` | Full Packet Tracer decryption pipeline |
138
+
139
+ ---
140
+
141
+ ## Security Notes
142
+
143
+ - The hardcoded key and IV are **Cisco's**, reverse-engineered from the Packet Tracer binary
144
+ - EAX tag verification is enforced — corrupted or tampered `.pkt` files are rejected
145
+ - No network calls; fully offline
146
+ - Known research items for future versions:
147
+ - Input file size guard
148
+ - zlib decompression size cap
149
+ - XML parsing hardening
150
+
151
+ ---
152
+
153
+ ## Legal
154
+
155
+ This software is provided for **security research, educational, and interoperability purposes**.
156
+
157
+ Cisco Packet Tracer and all related trademarks are property of Cisco Systems, Inc.
158
+ SecuredyPKT is not affiliated with or endorsed by Cisco.
159
+
160
+ See [LICENSE](LICENSE) for full terms.
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ securedypkt/__init__.py
6
+ securedypkt/_repacket.py
7
+ securedypkt/_unpacket.py
8
+ securedypkt.egg-info/PKG-INFO
9
+ securedypkt.egg-info/SOURCES.txt
10
+ securedypkt.egg-info/dependency_links.txt
11
+ securedypkt.egg-info/entry_points.txt
12
+ securedypkt.egg-info/top_level.txt
13
+ securedypkt/decipher/__init__.py
14
+ securedypkt/decipher/cmac.py
15
+ securedypkt/decipher/ctr.py
16
+ securedypkt/decipher/eax.py
17
+ securedypkt/decipher/pt_crypto.py
18
+ securedypkt/decipher/twofish.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ repacket = securedypkt._repacket:main
3
+ unpacket = securedypkt._unpacket:main
@@ -0,0 +1 @@
1
+ securedypkt
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from setuptools import setup
2
+ setup()