Skip to content

Instantly share code, notes, and snippets.

@joe-desimone
Created March 31, 2026 03:28
Show Gist options
  • Select an option

  • Save joe-desimone/f9b205b6a5c2a826987e27b6ecc84c05 to your computer and use it in GitHub Desktop.

Select an option

Save joe-desimone/f9b205b6a5c2a826987e27b6ecc84c05 to your computer and use it in GitHub Desktop.

macOS Stage-2 Payload Analysis: Mach-O RAT

SHA256: 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a SHA1: 13ab317c5dcab9af2d1bdb22118b9f09f8a4038e MD5: 7a9ddef00f69477b96252ca234fcbeeb File Size: 657,424 bytes File Type: Mach-O 64-bit x86_64 executable Malware Type: Remote Access Trojan (RAT) Code Signing: Not signed with valid certificate

Libraries

  • libcurl (HTTP communication)
  • libc++ (C++ standard library)
  • libSystem (system calls)
  • nlohmann JSON (JSON parsing)

Execution Flow

main() [0x100007A60]
    ├── argv[1] = C2 URL (required argument)
    ├── GenerateUID() → 16-char unique victim ID
    ├── GetOS() → macOS version
    ├── InitDirInfo() → directory enumeration
    ├── Build BaseInfo JSON (system fingerprint)
    ├── Report() → initial beacon POST to C2
    └── Loop (every 60 seconds):
        └── DoWork() → fetch and execute commands

C2 Protocol

  • Transport: HTTP/HTTPS via libcurl
  • Encoding: Base64 for all transmitted data
  • User-Agent: mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
  • C2 URL: Not hardcoded — passed via argv[1] at runtime

Initial Beacon (BaseInfo)

Field Description
hostname System hostname
username Current username
version macOS version
timezone UTC offset
installTimeString OS install date (from /var/db/.AppleSetupDone)
currentTimeString Current timestamp
bootTimeString Last boot time
cpuType mac_arm or mac_x64
modelName CPU brand string
processList Output of ps -eo user,pid,command
FirstInfo Directory listing of key paths

RAT Commands

Command Handler Description
kill Terminate the RAT
peinject DoActionIjt() Write and execute a binary payload
runscript DoActionScpt() Execute shell command or AppleScript
rundir InitDirInfo() Enumerate directories

peinject — Binary Payload Injection

Function: DoActionIjt() at 0x100002ECE

  1. Receives Base64-encoded binary in IjtBin field
  2. Decodes and writes to /private/tmp/.XXXXXX (hidden temp file, random name)
  3. chmod 755
  4. Ad-hoc code signs: codesign --force --deep --sign - "%s"
  5. Executes with optional parameters from Param field

runscript — Script Execution

Function: DoActionScpt() at 0x1000042FE

Two modes:

  • Shell (Script field empty): executes Param as shell command via /bin/sh
  • AppleScript (Script field populated): Base64-decodes script, writes to /tmp/.XXXXXX.scpt, executes via /usr/bin/osascript, cleans up

Process execution via RunProcess() at 0x100003356: pipe()fork() → child: dup2(), execv() / parent: read output, waitpid()

rundir — Directory Enumeration

Function: InitDirInfo() at 0x1000070EF

Collects per-entry: Name, IsDir, SizeBytes, Created, Modified, HasItems, parent, children

Target paths: /Applications, ~/Library, ~/Application Support

Key Functions

Address Function Purpose
0x100007A60 main Entry point, C2 beacon loop
0x100004CF9 DoWork Command dispatcher
0x100001164 Report C2 HTTP POST
0x100002ECE DoActionIjt Binary payload injection
0x1000042FE DoActionScpt Script execution
0x100003ED6 DoRunScpt AppleScript execution
0x100003356 RunProcess fork/exec subprocess
0x1000014B3 GenerateUID Random ID generation
0x100000CF0 Base64Decode Payload decoding

API Imports

Process: fork, execv, waitpid, pipe, dup2, exit, _exit File I/O: fopen, fclose, fwrite, chmod, stat$INODE64, mkstemps, unlink Network: curl_easy_init, curl_easy_setopt, curl_easy_perform, curl_easy_cleanup System Info: sysctl, sysctlbyname, gethostname, getpwuid, getuid, getenv Time: time, localtime_r, strftime, sleep

Indicators of Compromise

Filesystem

  • /private/tmp/.XXXXXX — hidden injected executables
  • /tmp/.XXXXXX.scpt — transient AppleScript temp files
  • /Library/Caches/com.apple.act.mond — drop path used by the plain-crypto-js installer

Network

  • User-Agent: mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
  • HTTP POST with Base64-encoded JSON body
  • C2 URL passed at runtime via argv[1]

Host

  • ps -eo user,pid,command execution
  • codesign --force --deep --sign - execution
  • Read access to /var/db/.AppleSetupDone

C2 JSON Keys

  • Commands: type, data, args, cmdid
  • System info: hostname, username, version, timezone, cpuType
  • Command types: kill, peinject, runscript, rundir
  • Response types: CmdResult, rsp_kill, rsp_peinject, rsp_runscript, rsp_rundir

Strings of Interest

/private/tmp/.%s
/usr/bin/osascript
codesign --force --deep --sign - "%s"
mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)
ps -eo user,pid,command
/var/db/.AppleSetupDone
kill, peinject, runscript, rundir

Sections

Section Size Entropy Notes
__TEXT,__text 96,927 6.22 Main code
__TEXT,__cstring 7,628 5.18 String literals
__DATA,__bss 50 0.00 Uninitialized data

All sections have normal permissions (no RWX).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment