diff --git a/README.md b/README.md index 9d17692..3d9e391 100644 --- a/README.md +++ b/README.md @@ -2,44 +2,68 @@ Python Obfuscation Framework. -Combine/chain obfuscation methods on a single Python source file. +Combine and chain obfuscation methods on a single Python source file. -## Install +## Goals -```bash -git clone https://github.com/2O4/pof -cd pof -python -m venv venv -source ./venv/bin/activate -./setup.py install -``` +The goals of this project is to create a toolkit to obfuscate Python source code to create payload for offensive security: + +- **Payloads**: store the code inside images, have multiple stages, use LOTL techniques +- **Stager**: easily create multi stages payloads +- **Evasion**: AV, EDR, DPI, sandbox and other analysis techniques +- **Human**: slow down human analysis of the payload +- **Automation**: automate the whole process, to produce numerous variant of the payload +- **Fun**: because it's always fun to see what's possible to do with Pythonnd -This will install POF inside a virtual env. +Python is not exactly the best language to create payloads with, especially for Windows if it's not already installed. This project was made for learning, and discovering new ways of bypassing security, of obfuscating the same code, it's a great way to test obfuscations techniques. + +This project could also give you ideas to implement in other languages, such as powershell where it would make sens to obfuscate the source code. Or in C, C#, C++, Go or Rust where it would make sens to stage payloads, compress them, encrypt them and obfuscate strings. + +You could also use most of the stagers to stage payload that are not built in Python. ## Usage +You can either pipe or give a file for input, same for output. + ```bash -pof $SOURCE -o $OUT +echo "print('Hello, world')" | pof ``` -Using the default chained obfuscator +Output: -```bash -pof in.py -o out.py +```python +from base64 import b64decode as CRT_ASSERT +from base64 import b85decode as _45802 +UserClassSlots=__builtins__.__dict__.__getitem__(_45802(''[::-1]).decode().join([__builtins__.__getattribute__("".join([chr(ord(i)-3)for i in'ukf'[::-1]]))(__builtins__.__getattribute__('\u006f\u0072\u0064')(i)-(__name__.__eq__.__call__(__name__)+__name__.__eq__.__call__(__name__)+__name__.__eq__(__name__)))for i in CRT_ASSERT('c3VscXc=').decode()])) +UserClassSlots(CRT_ASSERT('').decode().join([__builtins__.__getattribute__("".join([chr(ord(i)-3)for i in'']).join([chr(ord(i)-3)for i in'parse_intermixed_argsu'.replace('parse_intermixed_args','fk')]))(__builtins__.__dict__.__getitem__(_45802('L}g}jVP{fhb9HQVa%2').decode().replace("".join([chr(ord(i)-3)for i in'GhiudjUhvxow']),'o'[::-1]))(i)-(__name__.__eq__.__call__(__name__)+globals()["".join([chr(ord(i)-3)for i in'bbvqlwolxebb'])[::-1]].__dict__["".join([chr(ord(i)-3)for i in'']).join([chr(ord(i)-3)for i in'Wuxsimple_stmt'.replace('simple_stmt','h')])]+(type(1)==type(1))))for i in'gourz#/r_pfK'[::-1].replace(_45802('W^i9}').decode(),CRT_ASSERT('aG9vcg==').decode())])) ``` -Using a specific obfuscator +And yes, this is a valid Python script, that actually runs and only output `Hello world` ! And you'll get a different output each times. + +Using a specific obfuscator: ```bash pof in.py -o out.py -f obfuscator BuiltinsObfuscator ``` -Using a specific payload +Using a specific payload: ```bash pof inf.py -o out.py -f payload GzipPayload ``` +## Install + +```bash +git clone https://github.com/2O4/pof +cd pof +python -m venv venv +source ./venv/bin/activate +./setup.py install +``` + +This will install pof inside a virtual env, so you'll need to activate it every times you want to use it. + ## Examples These are examples of obfuscators of the script `print('Hello, world')`. @@ -58,23 +82,6 @@ To test the validity of the output you can simply pipe it to Python: echo "print('Hello, world')" | pof -f obfuscator -k UUIDObfuscator | python ``` -If no obfuscator is selected, a default chain obfuscator is used. - -```bash -echo "print('Hello, world')" | pof -``` - -Output: - -```python -from base64 import b64decode as CRT_ASSERT -from base64 import b85decode as _45802 -UserClassSlots=__builtins__.__dict__.__getitem__(_45802(''[::-1]).decode().join([__builtins__.__getattribute__("".join([chr(ord(i)-3)for i in'ukf'[::-1]]))(__builtins__.__getattribute__('\u006f\u0072\u0064')(i)-(__name__.__eq__.__call__(__name__)+__name__.__eq__.__call__(__name__)+__name__.__eq__(__name__)))for i in CRT_ASSERT('c3VscXc=').decode()])) -UserClassSlots(CRT_ASSERT('').decode().join([__builtins__.__getattribute__("".join([chr(ord(i)-3)for i in'']).join([chr(ord(i)-3)for i in'parse_intermixed_argsu'.replace('parse_intermixed_args','fk')]))(__builtins__.__dict__.__getitem__(_45802('L}g}jVP{fhb9HQVa%2').decode().replace("".join([chr(ord(i)-3)for i in'GhiudjUhvxow']),'o'[::-1]))(i)-(__name__.__eq__.__call__(__name__)+globals()["".join([chr(ord(i)-3)for i in'bbvqlwolxebb'])[::-1]].__dict__["".join([chr(ord(i)-3)for i in'']).join([chr(ord(i)-3)for i in'Wuxsimple_stmt'.replace('simple_stmt','h')])]+(type(1)==type(1))))for i in'gourz#/r_pfK'[::-1].replace(_45802('W^i9}').decode(),CRT_ASSERT('aG9vcg==').decode())])) -``` - -And yes, this is a valid Python script, that actually runs and only output `Hello world` ! - ### Obfuscator #### BuiltinsObfuscator @@ -140,6 +147,8 @@ print(b85decode( b'NM&qnZ!92pZ*pv8').decode()) #### RC4Obfuscator +Warning: the RC4 obfuscator (and other cipher obfuscators) will combine both, the cipher text and the key in the same file, this is obviously not secure, and should never be used for security purposes. The idea behind this obfuscator is to fool humans, AV, EDR, network TAP etc. not to be secured and safe. + ```python import codecs def rc4decrypt(key,ciphertext): @@ -186,6 +195,8 @@ exec("".join([chr(ord(i)-3)for i in'sulqw+*Khoor/#zruog*,\r'])) #### XORObfuscator +Warning: like for the RC4 cipher the XOR obfuscator shouldn't be used for security purposes, its main goal is to evade common security tools, not protect the information! Plus the XOR cipher is really weak and easy to crack. + ```python from base64 import b64decode @@ -389,6 +400,81 @@ def quine(): exec(b64decode(esource)) ``` +## Python API + +The true power of pof is in chaining multiple different obfuscation techniques easily, there is a prety simple Python API to do so. + +For example this is a snippet of the default obfuscator: + +```python +def obfuscate(source): + tokens = get_tokens(source) + + # get all the names and add them to the RESERVED_WORDS for the + # generators + reserved_words_add = NameExtract.get_names(tokens) + BaseGenerator.extend_reserved(reserved_words_add) + + tokens = CommentsObfuscator().obfuscate_tokens(tokens) + tokens = LoggingObfuscator().obfuscate_tokens(tokens) + tokens = PrintObfuscator().obfuscate_tokens(tokens) + ex_generator = BasicGenerator.number_name_generator() + tokens = ExceptionObfuscator( + add_codes=True, + generator=ex_generator, + ).obfuscate_tokens(tokens) + + # configure generator + gen_dict = { + 86: AdvancedGenerator.realistic_generator(), + 10: BasicGenerator.alphabet_generator(), + 4: BasicGenerator.number_name_generator(length=random.randint(2, 5)), + } + generator = AdvancedGenerator.multi_generator(gen_dict) + + # core obfuscation + tokens = ConstantsObfuscator( + generator=generator, + obf_number_rate=0.7, + obf_string_rate=0.1, + obf_string_rate=0.1, + obf_builtins_rate=0.3, + ).obfuscate_tokens(tokens) + + tokens = NamesObfuscator(generator=generator).obfuscate_tokens(tokens) + + tokens = GlobalsObfuscator().obfuscate_tokens(tokens) + tokens = BuiltinsObfuscator().obfuscate_tokens(tokens) + + b64decode_name = next(generator) + b85decode_name = next(generator) + string_obfuscator = StringsObfuscator( + import_b64decode=True, + import_b85decode=True, + b64decode_name=b64decode_name, + b85decode_name=b85decode_name, + ) + tokens = string_obfuscator.obfuscate_tokens(tokens) + string_obfuscator.import_b64decode = False + string_obfuscator.import_b85decode = False + + for _ in range(2): + tokens = NumberObfuscator().obfuscate_tokens(tokens) + tokens = BuiltinsObfuscator().obfuscate_tokens(tokens) + for _ in range(2): + tokens = string_obfuscator.obfuscate_tokens(tokens) + + return untokenize(tokens) +``` + +In this example we can see that first we remove comments, logging, print statements, and change the content of exceptions, and then we start to obfuscate constants, names, globals, builtins, strings, then strings and numbers multiple times, and we finnaly convert the tokens back to code. + +By chaining multiple ofuscations techniques we can create very complexe and custom output. + +Pof also provide evasions methods, detailed below, they are useful for quick and easy evasions, and can be used and customized to fit the need. + +For more example of how to use the pof Python API check the [examples/](./examples) directory. + ## Yara Yara rules can be used to detect malware, they can also be used to find interesting strings in Python source code. To check rules against source files and/or obfuscated files run: @@ -454,8 +540,6 @@ ruff . - Resolve every ruff errors - Increase test coverage - Add pre-commit hooks -- Setup .gitignore -- Create repository on GitHub - Setup package - Publish package on pypi - Write a doc diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..1a9bc50 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,7 @@ +# Pof examples + +In this directory you'll find 2 files, the source and then generator file. The source file `source.py` contains the Python source file to obfuscate, and the generator file `gen.py` contain the obfuscator code. All outputs are produce to the `out/` directory. + +This is useful to test pof, get a feel for the Python API, and how to use it, but also with some outputs and test the result. + +Note that you'll need to install pof first. diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/gen.py b/examples/gen.py new file mode 100644 index 0000000..8112d54 --- /dev/null +++ b/examples/gen.py @@ -0,0 +1,32 @@ +import pathlib + +import pof + + +class ExampleObfuscator(pof.BaseObfuscator): + def constant_obf(self, source): + tokens = self._get_tokens(source) + tokens = pof.obfuscator.ConstantsObfuscator().obfuscate_tokens(tokens) + return self._untokenize(tokens) + + +def obfuscate_to_file(obf_class, func_name, source): + out = getattr(obf_class, func_name)(source) + file_name = func_name + ".py" + file = pathlib.Path(__file__).parent / "out" / file_name + with file.open("w") as f: + f.write(out) + + +def run_all(): + obf = ExampleObfuscator() + + file = pathlib.Path(__file__).parent / "source.py" + with file.open() as f: + source = f.read() + + obfuscate_to_file(obf, "constant_obf", source) + + +if __name__ == "__main__": + run_all() diff --git a/examples/out/constant_obf.py b/examples/out/constant_obf.py new file mode 100644 index 0000000..95b4c2b --- /dev/null +++ b/examples/out/constant_obf.py @@ -0,0 +1,30 @@ +TB_="My pet is name: " +LoIC1lyQ=1 +Mm9IyM=print +tkBdI=8 +B6iSi8_=range +# source file that will be obfuscated +import random +import string + + +def get_random_letter(): + """This is a docstring.""" + return random.choice(string.ascii_lowercase) + + +def get_random_name(name_len): +# this is a comment + name=get_random_letter().upper() + for _ in B6iSi8_(name_len-LoIC1lyQ): + name+=get_random_letter() + return name + + +def present_my_pet(): + pet_name=get_random_name(tkBdI) + message=TB_+pet_name + Mm9IyM(message) + + +present_my_pet() diff --git a/examples/source.py b/examples/source.py new file mode 100644 index 0000000..0d25fd6 --- /dev/null +++ b/examples/source.py @@ -0,0 +1,25 @@ +# source file that will be obfuscated +import random +import string + + +def get_random_letter(): + """This is a docstring.""" + return random.choice(string.ascii_lowercase) + + +def get_random_name(name_len): + # this is a comment + name = get_random_letter().upper() + for _ in range(name_len - 1): + name += get_random_letter() + return name + + +def present_my_pet(): + pet_name = get_random_name(8) + message = "My pet is name: " + pet_name + print(message) + + +present_my_pet() diff --git a/pof/__init__.py b/pof/__init__.py index 572e782..b9e1b9f 100644 --- a/pof/__init__.py +++ b/pof/__init__.py @@ -14,8 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from pof.main import Obfuscator +from pof.main import BaseObfuscator, Obfuscator -__version__ = "1.3.6" +__version__ = "1.3.7" -__all__ = ("Obfuscator", "__version__") +__all__ = ("Obfuscator", "BaseObfuscator", "__version__") diff --git a/pof/cli.py b/pof/cli.py index 1fa96e4..e268839 100644 --- a/pof/cli.py +++ b/pof/cli.py @@ -161,7 +161,7 @@ def _cli(): ) parser.add_argument( "--logging", - help="obfuscation function", + help="logging level, INFO, DEBUG, ERROR, CRITICAL", default="INFO", ) parser.add_argument( diff --git a/pof/evasion/README.md b/pof/evasion/README.md index 1101c67..750d5c8 100644 --- a/pof/evasion/README.md +++ b/pof/evasion/README.md @@ -4,12 +4,12 @@ ## TODO -- file size evasion -- file content evasion -- screen resolution size -- integrity, check the hash of the current script to see if it has been tampered with -- screen resolution -- check if at least one monitor is plugged in -- check hard drive and free space -- check uptime -- make multiple versions, for different OS and move them into specific folders, or have 2 classes in the same file when it's different evasion +- File size evasion +- File content evasion +- Screen resolution size +- Integrity, check the hash of the current script to see if it has been tampered with +- Screen resolution +- Check if at least one monitor is plugged in +- Check hard drive and free space +- Check uptime +- Make multiple versions, for different OS and move them into specific folders, or have 2 classes in the same file when it's different evasion diff --git a/pof/main.py b/pof/main.py index 04ac034..0328040 100644 --- a/pof/main.py +++ b/pof/main.py @@ -73,7 +73,9 @@ def obfuscate( # generators reserved_words_add = NameExtract.get_names(tokens) BaseGenerator.extend_reserved(reserved_words_add) - logging.info("reserved {lt} names", extra={"lt": len(tokens)}) + + msg = f"reserved {len(tokens)} names" + logging.info(msg) # clean input tokens = CommentsObfuscator().obfuscate_tokens(tokens) @@ -207,7 +209,7 @@ def basic(self, source): tokens = NewlineObfuscator().obfuscate_tokens(tokens) return self._untokenize(tokens) - def full_evasion( + def full_evasion( # noqa: C901 PLR0913 PLR0912 self, source, hostname: str | None = None, @@ -312,7 +314,7 @@ def image(self, source): return self._untokenize(tokens) def advanced_image(self, source): - """Encrypt and store the payload code inside an image.""" + """Obfuscate, encrypt, and store the payload code inside an image.""" tokens = self._get_tokens(source) generator = BasicGenerator.alphabet_generator() diff --git a/pof/obfuscator/constants.py b/pof/obfuscator/constants.py index dcb6167..61fb300 100644 --- a/pof/obfuscator/constants.py +++ b/pof/obfuscator/constants.py @@ -234,42 +234,32 @@ def obfuscate_tokens(self, tokens): # obfuscation if ( - toknum == NAME - and tokval in self.BUILTINS - and prev_tokval != "." # avoid changing class/imports functions - and ( - parenthesis_depth == 0 - or (parenthesis_depth > 0 and next_tokval != "=") + ( + toknum == NAME + and tokval in self.BUILTINS + and prev_tokval != "." # avoid changing class/imports functions + and ( + parenthesis_depth == 0 + or (parenthesis_depth > 0 and next_tokval != "=") + ) + and (random.randint(0, 100) / 100) <= self.obf_builtins_rate ) - and (random.randint(0, 100) / 100) <= self.obf_builtins_rate - ): - new_tokens, variables = self.obfuscate_variable( - toknum, - tokval, - variables, + or ( + # don't obfuscate docstrings + toknum == STRING + and prev_toknum + not in [ + NEWLINE, + DEDENT, + INDENT, + ENCODING, + ] + and (random.randint(0, 100) / 100) <= self.obf_string_rate ) - - # don't obfuscate docstrings - elif ( - toknum == STRING - and prev_toknum - not in [ - NEWLINE, - DEDENT, - INDENT, - ENCODING, - ] - and (random.randint(0, 100) / 100) <= self.obf_string_rate - ): - new_tokens, variables = self.obfuscate_variable( - toknum, - tokval, - variables, + or ( + toknum == NUMBER + and (random.randint(0, 100) / 100) <= self.obf_number_rate ) - - elif ( - toknum == NUMBER - and (random.randint(0, 100) / 100) <= self.obf_number_rate ): new_tokens, variables = self.obfuscate_variable( toknum, diff --git a/pof/obfuscator/numbers.py b/pof/obfuscator/numbers.py index 7fbbe72..283a457 100644 --- a/pof/obfuscator/numbers.py +++ b/pof/obfuscator/numbers.py @@ -121,7 +121,8 @@ def obf_string_conversion(tokval, token_type): elif token_type is float: t = "float" else: - logging.error(f"{token_type=} not supported") + msg = f"{token_type=} not supported" + logging.error(msg) return [ (NAME, t), (LPAR, "("), @@ -184,7 +185,8 @@ def verify_number_obfuscation(tokval, tokens): # logging.debug("verifying that {}={}".format(tokval, result)) if str(result) == tokval: return True - logging.error(f"error verifying that {tokval}={result}") + msg = f"error verifying that {tokval}={result}" + logging.error(msg) return False def obfuscate_number(self, toknum, tokval): diff --git a/pof/obfuscator/strings.py b/pof/obfuscator/strings.py index 1686cc4..e87bc01 100644 --- a/pof/obfuscator/strings.py +++ b/pof/obfuscator/strings.py @@ -2,6 +2,7 @@ # - work with f"" strings, r"" strings etc. # - can't split `\` !!! # - add variable to import or not b64decode +# TODO (2O4): replace eval with ast.literal_eval: https://beta.ruff.rs/docs/rules/suspicious-eval-usage/ import logging import random from base64 import b64encode, b85encode @@ -45,7 +46,7 @@ class Strats(Enum): Strats.REVERSE, ) - def __init__( + def __init__( # noqa: PLR0913 self, import_b64decode: bool = False, import_b85decode: bool = False, diff --git a/pof/stager/image.py b/pof/stager/image.py index 76131e1..a6b973d 100644 --- a/pof/stager/image.py +++ b/pof/stager/image.py @@ -68,9 +68,12 @@ def set_last_bit(self, number, last_bit): return int(number_bin, 2) def encode(self, code, im_in, im_out): - """Code str: code to store - im_in str: path to image input - im_out str: path to image output. + """Encode into image. + + Args: + code: code to store + im_in: path to image input + im_out: path to image output. """ msg_bin = bin(int.from_bytes(code, "big")).replace("0b", "") # mark the end of the message diff --git a/pof/stager/lots/cl1pnet.py b/pof/stager/lots/cl1pnet.py index 465efbd..7686b94 100644 --- a/pof/stager/lots/cl1pnet.py +++ b/pof/stager/lots/cl1pnet.py @@ -1,6 +1,7 @@ import random from urllib import request +from pof.errors import PofError from pof.stager import DownloadStager @@ -15,7 +16,12 @@ class Cl1pNetStager(DownloadStager): No account is required to use cl1p.net, but it's limited to 10 paste per days. """ - def __init__(self, api_token="EXAMPLE_TOKEN", *args, **kwargs) -> None: + def __init__( + self, + api_token="EXAMPLE_TOKEN", # noqa: S107 + *args, + **kwargs, + ) -> None: self.api_token = api_token super().__init__(*args, **kwargs) @@ -33,8 +39,12 @@ def upload(self, code, path=None): "Content-Type": "text/html; charset=UTF-8", }, ) - r = request.urlopen(req) - if r.code != 201: + + r = request.urlopen(req) # noqa: S310 + + success_code = 201 + if r.code != success_code: msg = f"Failed to upload to cl1p.net, got code {r.code}" - raise Exception(msg) + raise PofError(msg) + return url diff --git a/pof/stager/lots/pastebin.py b/pof/stager/lots/pastebin.py index d55851d..dc03f4e 100644 --- a/pof/stager/lots/pastebin.py +++ b/pof/stager/lots/pastebin.py @@ -27,6 +27,9 @@ def upload(self, code): } post_data = parse.urlencode(dumps_params).encode() req = request.Request("https://pastebin.com/api/api_post.php", data=post_data) - r = request.urlopen(req) + + # TODO (2O4): check return code + r = request.urlopen(req) # noqa: S310 + link = r.read().decode() return "https://pastebin.com/raw/" + link.split("/")[-1] diff --git a/pof/stager/lots/pasters.py b/pof/stager/lots/pasters.py index 6f3eab5..9501e2a 100644 --- a/pof/stager/lots/pasters.py +++ b/pof/stager/lots/pasters.py @@ -17,9 +17,13 @@ class PasteRsStager(DownloadStager): def upload(self, code): req = request.Request("https://paste.rs", data=code) + r = request.urlopen(req) + raw_link = r.read().decode() - if r.code != 201: + valid_code = 201 + if r.code != valid_code: msg = f"Failed to upload to paste.rs, got code {r.code}" raise PofError(msg) + return raw_link diff --git a/pof/utils/stegano/ipv6encoding.py b/pof/utils/stegano/ipv6encoding.py index e0c25e7..03855d2 100644 --- a/pof/utils/stegano/ipv6encoding.py +++ b/pof/utils/stegano/ipv6encoding.py @@ -6,6 +6,7 @@ class IPv6Encoding: """Encode the string in a list of valid IPv6.""" + IPV6_LEN = 32 padding_byte = b"0" @classmethod @@ -14,12 +15,15 @@ def encode(cls, string): hex_string = binascii.b2a_hex(string) - string_chunks = [hex_string[i : i + 32] for i in range(0, len(hex_string), 32)] + string_chunks = [ + hex_string[i : i + cls.IPV6_LEN] + for i in range(0, len(hex_string), cls.IPV6_LEN) + ] for sc in string_chunks: string_chunk = sc - if len(string_chunk) < 32: - padding = 32 - len(string_chunk) + if len(string_chunk) < cls.IPV6_LEN: + padding = cls.IPV6_LEN - len(string_chunk) # TODO (204): choose this randomly (anything BUT the padding_byte) string_chunk += b"1" string_chunk += cls.padding_byte * (padding - 1) diff --git a/pof/utils/stegano/macencoding.py b/pof/utils/stegano/macencoding.py index b4b5a56..d33bb57 100644 --- a/pof/utils/stegano/macencoding.py +++ b/pof/utils/stegano/macencoding.py @@ -5,6 +5,7 @@ class MACEncoding: """Encode the string in a list of valid MAC.""" + MAC_LEN = 12 padding_byte = b"0" @classmethod @@ -13,12 +14,15 @@ def encode(cls, string): hex_string = binascii.b2a_hex(string) - string_chunks = [hex_string[i : i + 12] for i in range(0, len(hex_string), 12)] + string_chunks = [ + hex_string[i : i + cls.MAC_LEN] + for i in range(0, len(hex_string), cls.MAC_LEN) + ] for sc in string_chunks: string_chunk = sc - if len(string_chunk) < 12: - padding = 12 - len(string_chunk) + if len(string_chunk) < cls.MAC_LEN: + padding = cls.MAC_LEN - len(string_chunk) # TODO (204): choose this randomly (anything BUT the padding_byte) string_chunk += b"1" string_chunk += cls.padding_byte * (padding - 1) diff --git a/pof/utils/stegano/uuidencoding.py b/pof/utils/stegano/uuidencoding.py index d7bd650..3d911f7 100644 --- a/pof/utils/stegano/uuidencoding.py +++ b/pof/utils/stegano/uuidencoding.py @@ -1,8 +1,6 @@ import binascii from tokenize import NAME, NUMBER, OP, STRING -UUID_LEN = 32 - class UUIDEncoding: """Encode the data in a list of valid UUID. @@ -10,6 +8,7 @@ class UUIDEncoding: Idea from: https://github.com/Bl4ckM1rror/FUD-UUID-Shellcode """ + UUID_LEN = 32 padding_byte = b"0" @classmethod @@ -19,13 +18,14 @@ def encode(cls, string): hex_string = binascii.b2a_hex(string) string_chunks = [ - hex_string[i : i + UUID_LEN] for i in range(0, len(hex_string), UUID_LEN) + hex_string[i : i + cls.UUID_LEN] + for i in range(0, len(hex_string), cls.UUID_LEN) ] for string_chunk in string_chunks: sc = string_chunk - if len(sc) < UUID_LEN: - padding = UUID_LEN - len(sc) + if len(sc) < cls.UUID_LEN: + padding = cls.UUID_LEN - len(sc) # TODO (204): choose this randomly (anything BUT the padding_byte) sc += b"1" sc += cls.padding_byte * (padding - 1) diff --git a/pof/utils/tokens.py b/pof/utils/tokens.py index a95941b..dcfa486 100644 --- a/pof/utils/tokens.py +++ b/pof/utils/tokens.py @@ -15,7 +15,7 @@ class NoSpaceUntokenizer(Untokenizer): """Custom Untokenizer that remove useless spaces after every NAME or NUMBER.""" - def compat(self, token, iterable): + def compat(self, token, iterable): # noqa: C901 indents = [] toks_append = self.tokens.append startline = token[0] in (NEWLINE, NL) diff --git a/setup.py b/setup.py index b275481..832bd4a 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ setup( name="pof", - version="1.3.6", + version="1.3.7", author="2O4", author_email="", description="Python Obfuscation Framework.", diff --git a/wip/rope/a.py b/wip/rope/a.py index 0344dae..3b6f904 100644 --- a/wip/rope/a.py +++ b/wip/rope/a.py @@ -10,3 +10,10 @@ while True: nqru += 1 print(nqru) + + +class Hello: + pass + + +h = Hello() diff --git a/wip/rope/rope_test_2.py b/wip/rope/rope_test_2.py new file mode 100644 index 0000000..fa66b85 --- /dev/null +++ b/wip/rope/rope_test_2.py @@ -0,0 +1,19 @@ +import rope +from rope.base.project import Project + +proj = Project(".") +mod = proj.get_module("a") +name = mod.get_attribute("Hello") +pymod, lineno = name.get_definition_location() +lineno_start, lineno_end = pymod.logical_lines.logical_line_in(lineno) + +offset = pymod.resource.read().index( + name.pyobject.get_name(), + pymod.lines.get_line_start(lineno), +) + +changes = rope.refactor.rename.Rename(proj, pymod.get_resource(), offset).get_changes( + "Hola", +) + +proj.do(changes)