Last active
July 18, 2023 10:18
-
-
Save qtc-de/b3ec429317247f644f101269e2d623bc to your computer and use it in GitHub Desktop.
XOR All The Things! Python script that searches for byte representations within the specified file or input and xors them with the specified key.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import re | |
import sys | |
import argparse | |
class Xor: | |
''' | |
Helper class for performing xor operations. | |
''' | |
def __init__(self, key: str, verbose: bool) -> None: | |
''' | |
An Xor object is initialized with the key that is used to xor. | |
The key is expected to be a string that contains a hex sequence. | |
Parameters: | |
key xor key as hex sequence (e.g. aabbff) | |
verbose print verbose warning messages on xor errors | |
Returns: | |
None | |
''' | |
self.ctr = 0 | |
self.key = bytes.fromhex(key) | |
self.key_len = len(self.key) | |
self.verbose = verbose | |
def apply_xor(self, byte: int) -> int: | |
''' | |
Obtains the current xor byte within the key and xors the given input | |
with it. Each xor iteration increments the internal counter and uses | |
the next byte within the key. | |
Parameters: | |
byte byte to xor with | |
Returns: | |
xor_byte current xor byte to use | |
''' | |
if byte < 0x00 or byte > 0xff: | |
raise ValueError() | |
xor_byte = self.key[self.ctr % self.key_len] | |
self.ctr += 1 | |
return xor_byte ^ byte | |
def apply_match(self, re_match) -> str: | |
''' | |
Applies the xor operation to a regular expression match. The function | |
first determines what is contained within the match and applies the | |
corresponding apply function afterwards. | |
Parameters: | |
re_match regular expression match from the re library | |
Returns: | |
xor_replace xored replacement for the match | |
''' | |
stuff = re_match.group() | |
try: | |
if stuff.startswith("0x") or stuff.startswith(r"\x"): | |
return self.apply_hex(stuff) | |
elif stuff.startswith("0o"): | |
return self.apply_oct(stuff) | |
elif stuff.startswith("0b"): | |
return self.apply_bin(stuff) | |
else: | |
return self.apply_dec(stuff) | |
except ValueError: | |
if self.verbose: | |
sys.stderr.write(f"[!] Ignoring invalid byte: {stuff}\n") | |
return stuff | |
def apply_hex(self, string: str) -> str: | |
''' | |
Applies the xor operation to an incoming string that is expected to | |
be hex formatted. | |
Parameters: | |
string hex formatted input string | |
Returns: | |
xor_replace xored replacement for the string | |
''' | |
prefix = string[0:2] | |
value = int(string[2:], 16) | |
xor = self.apply_xor(value) | |
return '{}{:02x}'.format(prefix, xor) | |
def apply_oct(self, string: str) -> str: | |
''' | |
Applies the xor operation to an incoming string that is expected to | |
be octal formatted. | |
Parameters: | |
string oct formatted input string | |
Returns: | |
xor_replace xored replacement for the string | |
''' | |
prefix = string[0:2] | |
value = int(string[2:], 8) | |
xor = self.apply_xor(value) | |
return '{}{:03o}'.format(prefix, xor) | |
def apply_bin(self, string: str) -> str: | |
''' | |
Applies the xor operation to an incoming string that is expected to | |
be binary formatted. | |
Parameters: | |
string binary formatted input string | |
Returns: | |
xor_replace xored replacement for the string | |
''' | |
prefix = string[0:2] | |
value = int(string[2:], 2) | |
xor = self.apply_xor(value) | |
return '{}{:08b}'.format(prefix, xor) | |
def apply_dec(self, string: str) -> str: | |
''' | |
Applies the xor operation to an incoming string that is expected to | |
contain plain numbers. | |
Parameters: | |
string string containing numbers | |
Returns: | |
xor_replace xored replacement for the string | |
''' | |
value = int(string, 10) | |
xor = self.apply_xor(value) | |
return '{}'.format(xor) | |
def apply_char(self, char: str) -> int: | |
''' | |
Applies the xor operation to an incoming character. This is currently | |
only used then the --raw option is specified. | |
Parameters: | |
char incoming character | |
Returns: | |
xor_replace xored replacement for the character | |
''' | |
value = char | |
xor = self.apply_xor(value) | |
return bytes([xor]) | |
parser = argparse.ArgumentParser(description='''xor v1.1.0 - xor encode the given file or input. When --raw is used, the whole | |
input is treated as raw bytes and gets encoded. If not specified, the script | |
searches for byte representations like 0xab, 0o111, 0b10101, 111, etc. and | |
applies the xor operation on them.''') | |
parser.add_argument('infile', nargs='?', default=sys.stdin, type=argparse.FileType('rb'), help='filepath (default: stdin)') | |
parser.add_argument('--key', default='ae', help='xor key to use (default: ae)') | |
parser.add_argument('--raw', action='store_true', help='encode raw input byte by byte') | |
parser.add_argument('-v', '--verbose', action='store_true', help='show xor warning on invalid bytes') | |
args = parser.parse_args() | |
try: | |
content = args.infile.read() | |
xor = Xor(args.key, args.verbose) | |
if args.raw: | |
output = b'' | |
for char in content: | |
output += xor.apply_char(char) | |
sys.stdout.buffer.write(output) | |
else: | |
content = content.decode() | |
regex = re.compile(r"(?:(?<=[ ,.:|+-])|^)" + "(?:" + # Match bytes if prefixed by a separator (or at start) | |
r"0x[0-9a-f]{1,2}|\\x[0-9a-f]{1,2}" + "|" + # Hex pattern | |
r"0b[01]{1,8}" + "|" + # Bin pattern | |
r"0o[0-7]{1,3}" + "|" + # Oct pattern | |
r"\d{1,3}" + # Dec pattern | |
r")" + "(?=[ ,:.|+-]|$)") # Match bytes if suffixed by a separator (or at end) | |
output = regex.sub(xor.apply_match, content) | |
print(output, end='') | |
except KeyboardInterrupt: | |
print("[-] Aborted.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment