How AMOS macOS stealer avoids detection


Check out the blog post on the orginal site: Kandji Blog (Credit to Christopher Lopez who wrote this article with me)


Atomic macOS Stealer (AMOS) was first spotted in early 2023. It’s a powerful piece of malware that targets Apple users and tricks them into installing the software on their computers. The malware is sold via Telegram; as of January 20, 2024, the price was $3,000 a month.

Once installed, Atomic Stealer can exfiltrate an extensive amount of data, including keychain passwords, user documents, system info, cookies, browser data, credit card information, cryptocurrency wallets, and more.

Telegram post

Adversaries leveraging AMOS direct victims to a website (the URL of which changes constantly) to download an unsigned disk image (DMG). If a user were to go that URL accidentally, they’d see only a benign welcome message.

Telegram post

On that website is a subdirectory where 12 DMG files are available for download: Six in the /name directory, another six in /name/files.

Telegram post
Telegram post

Each of these twelve DMG files gives you a different hash. This is a way to avoid hash-based detection. (We’ll explain how they do that in a bit.) Because of this, hash-based detection for this new version will not work, since every time someone downloads the sample, they’d need to look for a new hash. From our research, this is new: AMOS creators have not previously had a way to iterate small portions of code to change hashes.

If you did download one of these DMG files, a pop-up would appear that looks similar to any other application you download from outside the Apple store.

Telegram post

Inside that DMG is a Mach-O that, if the user right-clicks and opens it, the malware will execute.

XOR Encoding

Since it first appeared, AMOS has continued to evolve. A recent sample included XOR instructions to prevent detections of strings known to be associated with this stealer. The capabilities of the latest sample do not appear to have changed, but its obfuscation tactics differ from previously reported samples.

In the example below, some of the strings are visible even with the XOR encoding. The XOR key for these is 0x90, so we can see how these are separated by null bytes. It appears that, for many of these, only half of the strings were XOR encoded.

Telegram post

In a more recent sample, we can see that no strings are visible in the const section. Something appears to have changed with how the encoding is completed. This further highlights how quickly and often the AMOS malware changes.

Telegram post

Let’s dive into an example of how an XOR function is called, along with the arguments that are passed in the arm64 slice of the Universal binary.

1  10000c7cc  28108052  mov   w8, #0x81
2  10000c7d0  e8430039  strb  w8, [sp, #0x10 {xorKey}]  {0x81}
3  10000c7d4  e8430091  add   x8, sp, #0x10 {xorKey}
(...)
4  10000c84c  29008052  mov   w9, #0x1
5  10000c850  e013813c  stur  q0, [sp, #0x11 {var_af}]
6  10000c854  3f0d02f1  cmp   x9, #0x83
7  10000c858  80000054  b.eq  0x10000c868
8  10000c85c  ea434039  ldrb  w10, [sp, #0x10 {xorKey}]
9  10000c860  b20d0094  bl    _OUTLINED_FUNCTION_1
10 10000c864  fcffff17  b     0x10000c854
11 10000c868  a0110094  bl    _system

Starting at line 1, the value 0x81 is moved to register W8. This will be the XOR key used later to decode the string stored on the stack.

At line 2, the XOR key is stored on the stack at the address SP + 0x10.

Line 3 indicates that a pointer to this offset on the stack is moved to register X8.

Between line 3 and 4, hex values are moved into registers and stored on the stack. This will be the string to decode.

Line 4 shows the value 0x1 is moved into W9. This will serve as the index used to iterate through the string.

Line 5 indicates that the value stored at register Q0 is saved on the stack at SP + 0x11. This is one byte away from where the XOR key is stored.

Line 6 compares the value stored at X9 and 0x83, which would be the length of the string.

Line 7 shows a branch if equals instruction, which would branch to the address 0x10000c82c when X9 = 0x83. The system() function is at 0x10000c82c, which indicates that, once the decoding is complete for 0x82 characters, the output is passed as the argument to the system() function.

Line 8 is a LDRB instruction that loads the byte value stored at SP +0x10 (XOR Key) to W10.

Line 9 is the branch to the function called _OUTLINED_FUNCTION_1, where the XOR instructions occur.

Line 10 indicates that once the XOR function is completed, a branch back to the comparison of the value stored in W9 is completed to continue this loop.

Line 11 is the system() function call after the loop completes. This sets up the call to the system() function after decoding the string using an XOR function.

Let’s now look at the XOR function called _OUTLINED_FUNCTION_1, which we branched to above.

10000ff28  int64_t _OUTLINED_FUNCTION_1(char* arg1 @ x8, int64_t arg2   @ x9, 
10000ff28  char arg3 @ x10)
10000ff28  0b696938  ldrb  w11, [x8, x9]
10000ff2c  6a010a4a  eor  w10, w11, w10
10000ff30  0a692938  strb  w10, [x8, x9]
10000ff34  29050091  add  x9, x9, #0x1
10000ff38  c0035fd6  ret     

The function definition indicates that the values stored in X8, X9, and X10 are passed as arguments: X8 is pointing to the stack at SP +0x10; X9 is our index, which at the start of this loop was given the value 0x1; and X10 has the XOR key byte value 0x81.

Using the value in X9 as the index, the byte stored at the memory address X8 + X9 is loaded into W11. The encoded string starts at the address of SP +0x11, one byte away from what X8 is pointing to. This is why the loop starts with X9 having the value 0x1 as the index.

The EOR (XOR) instruction then uses the key (0x81) stored in W10 to calculate the hex value stored back into W10. The STRB instruction then stores this byte back to the location at [X8, X9] which is the string pointed to by X8 with the index in X9.

This example creates an osascript command that is common in AMOS samples and is passed to the system function to execute.

osascript -e 'display dialog "Some error occurred while running the application." buttons {"OK"} default button 1 with icon stop'

This example is just to demonstrate how one string is decoded in this manner using the 0x81 key. There are many other examples in the binary that leverage similar functionality to decode the strings. To highlight the use of the XOR function described above, we can see that _OUTLINED_FUNCTION_1 is called 120 times by other functions in the binary, further highlighting the use of this encoding.

AMOS is an active malware that is constantly changing and frequently being updated. As we post this, the source website is still live and is being actively updated; on March 1, the site was updated between 2:00 and 8:00 AM PST. Kandji is actively tracking the group that is distributing this malware and will continue to monitor and update our security processes as needed. We suggest that all security devs/teams should do the same.

Indicators Of Compromise

SHA256

  • CrackSetup.dmg/dowload1.php: c988fd14753a8ee73d5c2747e4deeda8ef798deeb747435a112744d243cfb7ba
  • CrackSetup-2.dmg/dowload2.php: 400303250be2414d340675226aea7f78757ea8d1413af8dcd8f2c7a8d3ff8e21
  • CrackSetup-3.dmg/dowload3.php: 0e8aa909c1fefe12fd12f8f1e0073203ef4111f27fefb48568fd6ab02c13fb38
  • CrackSetup-4.dmg/dowload4.php: 9e52d52e53393ac49447a8fa1d0e98b4f7835346b7f7b85df1b9c891b554924c
  • CrackSetup-5.dmg/dowload5.php: 785e96c34123d1b2e30e9cc43972ab165f584815cdb323bd892b78fe7c9980e7
  • CrackSetup-6.dmg/dowload6.php: 64abdcd49e9f12f6f92457a006dc1876f04dc1ad9bcab5d1766f126a87a5c60a
  • CrackInstaller.dmg: ed46d8865491e81d14197eafc71165eb0358086680a604e4bf6fe9c6372e741c
  • hendro.dmg: 469a0176993e13c28b5b8f7f85a382e576f5e93310c8f5dec53a860783b97f0f
  • riesling.dmg: d7c7aad5899fa10e47cd1f7a224c62a44ea2858b1205762e183d69cdf356a6c2
  • vera.dmg: cb6ebdc900d730f844f07d9d66ee4008aeedff1eac0662553f1713fffb29f058
  • whiskey.dmg: 596330886e915d9905d72284dcba6b663d8db95e84c00d25e1e19a3d79b01664
  • zinfandel.dmg: aebd77032f05029f0c6d614d40a5891926daec04fba4254a671b59709dfa625f