As “Sharing is Caring” I’ve uploaded the malicious binaries Turtle.zip to our public macOS malware collection. The password is: infect3d
A few years ago, @jacse mused that maybe turtles are the secret to avoiding ransomware. And while that may have been true in the past, today it appears that turtles instead may in fact be ransomware harbingers π
How Turtles are the secret to avoiding ransomware
— Jon Sawyer (@jcase) November 21, 2021
Yesterday (Nov 29th), Austin pinged me about a possible new ransomware specimen that was targeting macOS.
Found sample on VT 91a5faa41d19090e1c5c1016254fd22a - [possible] Mac ransomware. ...Couldn't find much on it so wondering your thoughts if you have time! -Austin
…thanks Austin! ππ½
If we pop over to VirusTotal and search for a file with the hash 91a5faa41d19090e1c5c1016254fd22a
, we find the sample:
Though uploaded just two days ago, 24 of the anti-virus engine are already flagging it as malicious. This is unusual, as generally new malware takes a while to get picked up by such engines. However, as we can see in the above screenshot, crowdsource YARA rules flag is on Windows APIs. Also the names the AV engines have assigned the malicious binary are rather vague such as “Other:Malware-gen”, “Trojan.Generic”, or “Possible Threat”. Others flag it as Windows malware (“Win32.Troj.Undef”). At first blush, yes kind of strange, but if you keep reading, you’ll see this actually all makes sense!
One of the neat features of VirusTotal is the ability to map out relationships. Using this, we find the sample came from a zip file, named “TR.zip”. This was also uploaded to VirusTotal (5 days ago):
…a lot of times, we see malware first developed for more popular platforms such as Windows, then ported to macOS. Perhaps (also recalling the YARA hit on Windows APIs), this is why there are already so many AV detections even for the macOS variant).
If we download the archive and unzip it, we find it contains files (prefixed with “TurtleRansom”) that appear to be compiled for common platforms, including, Windows, Linux, and yes, macOS:
Using macOS’s file
utility, can confirm this:
% file TR/TurtleRansom-v0-windows-arm64.exe TR/TurtleRansom-v0-windows-arm64.exe: PE32+ executable Aarch64, for MS Windows % file TR/TurtleRansom-v0-macos-arm64.pkg TR/TurtleRansom-v0-macos-arm64.pkg: Mach-O 64-bit executable arm64 % file TR/TurtleRansom-v0-macos-amd64-softfloat.pkg TR/TurtleRansom-v0-macos-amd64-softfloat.pkg: Mach-O 64-bit executable x86_64
Noting that the macOS .pkg files are not packages, but simple Mach-O executables, one compiled for Intel and one for Arm (aka Apple Silicon).
For the remainder of this analysis, we’ll focus on the Arm build ("TurtleRansom-v0-macos-arm64.pkg
").
First, let’s see if its signed (and notarized?) using macOS’ codesign
utility:
% codesign -dvv TR/TurtleRansom-v0-macos-arm64.pkg TR/TurtleRansom-v0-macos-arm64.pkg Identifier=a.out Format=Mach-O thin (arm64) CodeDirectory v=20400 size=16446 flags=0x20002(adhoc,linker-signed) hashes=511+0 location=embedded Signature=adhoc Info.plist=not bound TeamIdentifier=not set Sealed Resources=none Internal requirements=none
Though it is signed so it can run on macOS, its only signed adhoc (and not notarized). This means Gatekeeper should block it (unless the user explicitly allows it to run, or it is deployed via some exploit).
Next let’s extract any embedded strings (via the aptly named strings
utility), as such strings can often give us a sense of capabilities and guide continued analysis efforts.
% strings - TR/TurtleRansom-v0-macos-arm64.pkg ? Go build ID: "JZS5wyJGq5YWpqV3jo6B/mrrrnHE8O7C4AIP5rWHa/Jwu16Tp63gAWq6z_ByO1/eBbE7TLPTZlqPRrSlUyz" TURTLERANSv0 D:/VirTest/TurmiRansom/main.go ... _main..inittask _main.en0cr0yp0tFile _main.main _main.main.func1 ... .doc .docx .txt ... _crypto.init _crypto/aes.(*KeySizeError).Error _crypto/aes.(*aesCipher).BlockSize _crypto/aes.(*aesCipher).Encrypt _crypto/aes.(*aesCipherAsm).BlockSize _crypto/aes.(*aesCipherAsm).Encrypt _crypto/aes.(*aesCipherGCM).BlockSize _crypto/aes.(*aesCipherGCM).Encrypt _crypto/aes..inittask _crypto/aes.KeySizeError.Error _crypto/aes.NewCipher ... syscall.init syscall.Getwd syscall.Open syscall.read syscall.readdir_r syscall.Rename syscall.Seek syscall.write syscall.execve ...
The binary doesn’t appear to obfuscate any of its strings, hence there are strings a plenty. Some of the ones I’ve included confirm the malware was written in Go, and internally referred to as “TURTLERANS” (aka “Turtle Ransomware”) or “TurmiRansom”. We also see a function named en0cr0yp0tFile
and references to target file extensions (e.g. .docx
), cryptographic routines and file I/O.
Though the strings
command doesn’t support UTF-8 string, we find (for example in a disassembler that does support such strings), various strings in Chinese also related to ransomware operations, such as “ε ε―ζδ»Ά” which translate to “Encrypt files”.
At this point, all signs point to well, yes, ransomware. Let’s now dive in deeper to see how the ransomware goes about encrypting files.
In the strings output we saw a method named: main.en0cr0yp0tFile
.
When analyzing binaries written in Go, you usually find the interesting logic (written by the malware author, vs just some linked in 3rd-party library) in the main.* routines.
Let’s take a look at the disassembly of the en0cr0yp0tFile
function …focusing on the library/API calls it makes:
main.en0cr0yp0tFile:
...
0x0000000100095e04 bl _os.ReadFile
...
0x0000000100095e24 bl _crypto/aes.NewCipher
...
0x0000000100095e5c bl _crypto/cipher.NewCTR
...
0x0000000100095ec4 bl _runtime.concatstring2
...
0x0000000100095ee4 bl _os.rename
...
0x0000000100095f08 bl _os.WriteFile
...
0x0000000100095f18 ret
Pretty easy to see given a file, it reads it into memory, encrypts it with AES (in CTR mode), renames the file, then overwrites the file’s original contents with the encrypted data. Pretty standard ransomware logic.
The en0cr0yp0tFile
function is invoked by the main.func1
. This function is called for each file in the malware’s working directory. (File enumerating is done via a call to the path_filepath_Walk
function). However, it does not encrypt all files …only those matching the extensions .doc
, .docx
, or .txt
(recall, we saw these as embedded strings):
0x0000000100096761 db 0x2e ; '.'
0x0000000100096762 db 0x74 ; 't'
0x0000000100096763 db 0x78 ; 'x'
0x0000000100096764 db 0x74 ; 't'
...
0x0000000100096128 add x1, x1, #0x761 ; 0x100096761 (".txt")
0x000000010009612c orr x2, xzr, #0x4
0x0000000100096130 bl runtime.memequal
Let’s now hop into a virtual machine and execute the ransomware in a directory containing documents and text files. If we run file monitor at the same time, we can observe the ransomware in action:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter TurtleRansom-v0-macos-arm64.pkg { "event" : "ES_EVENT_TYPE_NOTIFY_OPEN", "file" : { "destination" : "/Users/user/Desktop/TR/file.docx", "process" : { "path" : "/Users/user/Desktop/TR/TurtleRansom-v0-macos-arm64.pkg", "name" : "TurtleRansom-v0-macos-arm64.pkg", "pid" : 938 } } } { "event" : "ES_EVENT_TYPE_NOTIFY_RENAME", "file" : { "source" : "/Users/user/Desktop/TR/file.docx", "destination" : "/Users/user/Desktop/TR/file.docx.TURTLERANSv0", "process" : { "path" : "/Users/user/Desktop/TR/TurtleRansom-v0-macos-arm64.pkg", "name" : "TurtleRansom-v0-macos-arm64.pkg", "pid" : 938 } } } { "event" : "ES_EVENT_TYPE_NOTIFY_WRITE", "file" : { "destination" : "/Users/user/Desktop/TR/file.docx.TURTLERANSv0", "process" : { "path" : "/Users/user/Desktop/TR/TurtleRansom-v0-macos-arm64.pkg", "name" : "TurtleRansom-v0-macos-arm64.pkg", "pid" : 938 } } }
Note that the encrypted files are suffixed with the hardcoded extension “TURTLERANSv0
”.
Can we decrypt files encrypted by the ransomware? π€ Good question!
Recall the encryption is done via Go’s crypto/AES library. If we set a breakpoint on the call to the aes.NewCipher
function, which takes as its only argument a key, we should be able to recover the symmetrical encryption/decryption key:
% lldb TurtleRansom-v0-macos-arm64.pkg ... (lldb) b 0x100095e24 Breakpoint 11: where = TurtleRansom-v0-macos-arm64.pkg`main.en0cr0yp0tFile + 84, address = 0x0000000100095e24 (lldb) c Process 1002 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 11.1 frame #0: 0x0000000100095e24 TurtleRansom-v0-macos-arm64.pkg`main.en0cr0yp0tFile + 84 TurtleRansom-v0-macos-arm64.pkg`main.en0cr0yp0tFile: -> 0x100095e24 <+84>: bl 0x100074c70 ; crypto/aes.NewCipher (lldb) x/s $x0 0x14000106cb0: "wugui123wugui123"
Once the breakpoint is hit, we print out the first argument (found in the x0
register). It contains the value, “wugui123wugui123
”, which is ransomware’s key.
ChatGPT says, “‘Wugui’ (δΉιΎ) in Chinese translates to “turtle” or “tortoise” in English.
Back in the disassembly, we can see that address 0x100098545
is passed to the en0cr0yp0tFile
function:
0x00000001000961c0 adrp x5, #0x100098000 ; 0x100098545@PAGE
0x00000001000961c4 add x5, x5, #0x545 ; 0x100098545@PAGEOFF, 0x100098545
0x00000001000961c8 ldp x5, x6, [x5]
0x00000001000961cc stp x5, x6, [sp, #0x50]
...
0x00000001000961e4 bl main.en0cr0yp0tFile
And if we go to the address 0x100098545
we found that this holds the (hard-coded) key “wugui123wugui123
”:
0x0000000100098545 db 0x77 ; 'w' ; DATA XREF=_main.main.func1+292
0x0000000100098546 db 0x75 ; 'u'
0x0000000100098547 db 0x67 ; 'g'
0x0000000100098548 db 0x75 ; 'u'
0x0000000100098549 db 0x69 ; 'i'
0x000000010009854a db 0x31 ; '1'
0x000000010009854b db 0x32 ; '2'
0x000000010009854c db 0x33 ; '3'
0x000000010009854d db 0x77 ; 'w'
0x000000010009854e db 0x75 ; 'u'
0x000000010009854f db 0x67 ; 'g'
0x0000000100098550 db 0x75 ; 'u'
0x0000000100098551 db 0x69 ; 'i'
0x0000000100098552 db 0x31 ; '1'
0x0000000100098553 db 0x32 ; '2'
0x0000000100098554 db 0x33 ; '3'
As AES is symmetrical and here the key is hard-coded, its trivial for us to write a decryptor:
import sys
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
def decryptTurtle(key, iv):
with open(sys.argv[1], 'rb') as file:
ciphertext = file.read()
cipher = Cipher(algorithms.AES(key.encode('utf-8')), modes.CTR(iv), backend=default_backend())
decryptor = cipher.decryptor()
plainText = decryptor.update(ciphertext) + decryptor.finalize()
print(plainText)
iv = b'\x00' * 16
key = "wugui123wugui123"
decryptTurtle(key, iv)
Now, if we create a file with any content, run the ransomware so that it encrypts the file, then pass the now encrypted file to our decryptor it is able to fully decrypt and recover the orginal contents:
% echo "The quick brown fox jumps over the lazy dog" > test.txt % ./TurtleRansom-v0-macos-arm64.pkg ε ε―ζδ»Ά /Users/user/Desktop/TR/test.txt ζε % python3 turtle.py test.txt.TURTLERANSv0 b'The quick brown fox jumps over the lazy dog\n'
Hooray! π₯³
Of course it goes without saying, having your files ransomed sucks! But good news, in this case the average macOS user is unlikely to be impacted by this macOS sample. Still the fact that ransomware authors have set their sights on macOS, should give us pause for concern and also catalyze conversions about detecting and preventing this (and future) samples in the first place!
First, Apple has been fairly proactive about mitigating ransomware attacks on macOS (and maybe this is why we’ve yet seen a major ransomware outbreak on macOS). So, kudos to Cupertino. Specifically Apple has implemented SIP (and now read-only system volumes) to protect OS-level files. This means even if ransomware finds its way onto a macOS system it won’t (easily) be able to mess with core OS files. Apple has also added (TCC) protections to user files founds in (now) protected directories such as ~/Desktop
, ~/Documents
, etc. etc. This means, that without an exploit or explicit user-approval users files will remain protected. (Though TCC is pretty easy to bypass …or worse case you just ask the user, and they’ll likely acquiesce).
Still an additional layer or detection/protection may be warranted, especially as we’ve all probably inadvertently clicked “Allow” on access prompts, while even Apple has been known to notarize malware.
If we stop to think specifically about ransomware, in theory, it should be trivial to detect (at least in most cases). Why? Simply put, ransomware’s actions provide us with a powerful detection heuristic.
In April 2016 (yes, wayyyy back then!) I posted a blog titled, “Towards Generic Ransomware Detection”. In this post, I mused,
“If we can monitor file I/O events and detect the rapid creation of encrypted files by untrusted processes, then ransomware may be generically detected” -(a younger) Patrick Wardle
To back this up, I released a free (and now open-source) tool called “RansomWhere?” that implemented this heuristic-based approach. Specifically it monitors file I/O events and for newly created files asks:
If these are all true, “RansomWhere?” will suspend the process as its likely ransomware and alert the user and handle the response (resume/terminate).
Most notably, “RansomWhere?” is slightly reactive, meaning several files maybe be encrypted (and thus ransomed) before the tool detects and blocks the ransomware.
And though “RansomWhere?” is a bit dated, it appears to be able to generically detect the actions of Turtle Ransomware and thus thwart it …even though it had no a priori knowledge of this malware:
Hooray! Free, open-source tools FTW! π₯³
Does your EDR product provide generic ransomware detection!? π€
Today we dove into a new ransomware sample, internally dubbed “Turtle”. And while in its current state it does not post much of a threat to macOS users, it yet again, shows that ransomware authors continue to set their sites on macOS.
You're in luck, as I've written a book on this topic! It's 100% free online while all royalties from sale of the printed version donated to the Objective-See Foundation.
|
|
Or, come attend our macOS security conference, "Objective by the Sea" v6.0 in sunny Spain! ...where I'm teaching a class on Mac Malware Detection & Analysis
|
You can support them via my Patreon page!