macOS Apps - Inspecting, debugging and Fuzzing

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Static Analysis


otool -L /bin/ls #List dynamically linked libraries
otool -tv /bin/ps #Decompile application


objdump -m --dylibs-used /bin/ls #List dynamically linked libraries
objdump -m -h /bin/ls # Get headers information
objdump -m --syms /bin/ls # Check if the symbol table exists to get function names
objdump -m --full-contents /bin/ls # Dump every section
objdump -d /bin/ls # Dissasemble the binary
objdump --disassemble-symbols=_hello --x86-asm-syntax=intel toolsdemo #Disassemble a function using intel flavour


The tool can be used as a replacement for codesign, otool, and objdump, and provides a few additional features. Download it here or install it with brew.

# Install
brew install --cask jtool2

jtool2 -l /bin/ls # Get commands (headers)
jtool2 -L /bin/ls # Get libraries
jtool2 -S /bin/ls # Get symbol info
jtool2 -d /bin/ls # Dump binary
jtool2 -D /bin/ls # Decompile binary

# Get signature information
ARCH=x86_64 jtool2 --sig /System/Applications/

# Get MIG information
jtool2 -d __DATA.__const myipc_server | grep MIG

Codesign / ldid

Codesign can be found in macOS while ldid can be found in iOS

# Get signer
codesign -vv -d /bin/ls 2>&1 | grep -E "Authority|TeamIdentifier"

# Check if the app’s contents have been modified
codesign --verify --verbose /Applications/

# Get entitlements from the binary
codesign -d --entitlements :- /System/Applications/ # Check the TCC perms

# Check if the signature is valid
spctl --assess --verbose /Applications/

# Sign a binary
codesign -s <cert-name-keychain> toolsdemo

# Get signature info
ldid -h <binary>

# Get entitlements
ldid -e <binary>

# Change entilements
## /tmp/entl.xml is a XML file with the new entitlements to add
ldid -S/tmp/entl.xml <binary>


SuspiciousPackage is a tool useful to inspect .pkg files (installers) and see what is inside before installing it. These installers have preinstall and postinstall bash scripts that malware authors usually abuse to persist the malware.


This tool allows to mount Apple disk images (.dmg) files to inspect them before running anything:

hdiutil attach ~/Downloads/Firefox\ 58.0.2.dmg

It will be mounted in /Volumes



Note that programs written in Objective-C retain their class declarations when compiled into Mach-O binaries. Such class declarations include the name and type of:

  • The class

  • The class methods

  • The class instance variables

You can get this information using class-dump:


Note that this names could be obfuscated to make the reversing of the binary more difficult.

Function calling

When a function is called in a binary that uses objective-C, the compiled code instead of calling that function, it will call objc_msgSend. Which will be calling the final function:

The params this function expects are:

  • The first parameter (self) is "a pointer that points to the instance of the class that is to receive the message". Or more simply put, it’s the object that the method is being invoked upon. If the method is a class method, this will be an instance of the class object (as a whole), whereas for an instance method, self will point to an instantiated instance of the class as an object.

  • The second parameter, (op), is "the selector of the method that handles the message". Again, more simply put, this is just the name of the method.

  • The remaining parameters are any values that are required by the method (op).

See how to get this info easily with lldb in ARM64 in this page:

pageIntroduction to ARM64v8




(for) objc_msgSend

1st argument


self: object that the method is being invoked upon

2nd argument


op: name of the method

3rd argument


1st argument to the method

4th argument


2nd argument to the method

5th argument


3rd argument to the method

6th argument


4th argument to the method

7th+ argument

rsp+ (on the stack)

5th+ argument to the method


With Swift binaries, since there is Objective-C compatibility, sometimes you can extract declarations using class-dump but not always.

With the jtool -l or otool -l command lines it's possible ti find several sections that start with __swift5 prefix:

jtool2 -l /Applications/
LC 00: LC_SEGMENT_64              Mem: 0x000000000-0x100000000    __PAGEZERO
LC 01: LC_SEGMENT_64              Mem: 0x100000000-0x100028000    __TEXT
    Mem: 0x100026630-0x100026d54        __TEXT.__swift5_typeref
    Mem: 0x100026d60-0x100027061        __TEXT.__swift5_reflstr
    Mem: 0x100027064-0x1000274cc        __TEXT.__swift5_fieldmd
    Mem: 0x1000274cc-0x100027608        __TEXT.__swift5_capture

You can find further information about the information stored in these section in this blog post.

Moreover, Swift binaries might have symbols (for example libraries need to store symbols so its functions can be called). The symbols usually have the info about the function name and attr in a ugly way, so they are very useful and there are "demanglers" that can get the original name:

# Ghidra plugin

# Swift cli
swift demangle

Packed binaries

  • Check for high entropy

  • Check the strings (is there is almost no understandable string, packed)

  • The UPX packer for MacOS generates a section called "__XHDR"

Dynamic Analysis

Note that in order to debug binaries, SIP needs to be disabled (csrutil disable or csrutil enable --without debug) or to copy the binaries to a temporary folder and remove the signature with codesign --remove-signature <binary-path> or allow the debugging of the binary (you can use this script)

Note that in order to instrument system binaries, (such as cloudconfigurationd) on macOS, SIP must be disabled (just removing the signature won't work).

Unified Logs

MacOS generates a lot of logs that can be very useful when running an application trying to understand what is it doing.

Moreover, the are some logs that will contain the tag <private> to hide some user or computer identifiable information. However, it's possible to install a certificate to disclose this information. Follow the explanations from here.


Left panel

In the left panel of hopper it's possible to see the symbols (Labels) of the binary, the list of procedures and functions (Proc) and the strings (Str). Those aren't all the strings but the ones defined in several parts of the Mac-O file (like cstring or objc_methname).

Middle panel

In the middle panel you can see the dissasembled code. And you can see it a raw disassemble, as graph, as decompiled and as binary by clicking on the respective icon:

Right clicking in a code object you can see references to/from that object or even change its name (this doesn't work in decompiled pseudocode):

Moreover, in the middle down you can write python commands.

Right panel

In the right panel you can see interesting information such as the navigation history (so you know how you arrived at the current situation), the call graph where you can see all the functions that call this function and all the functions that this function calls, and local variables information.


It allows users access to applications at an extremely low level and provides a way for users to trace programs and even change their execution flow. Dtrace uses probes which are placed throughout the kernel and are at locations such as the beginning and end of system calls.

DTrace uses the dtrace_probe_create function to create a probe for each system call. These probes can be fired in the entry and exit point of each system call. The interaction with DTrace occur through /dev/dtrace which is only available for the root user.

To enable Dtrace without fully disabling SIP protection you could execute on recovery mode: csrutil enable --without dtrace

You can also dtrace or dtruss binaries that you have compiled.

The available probes of dtrace can be obtained with:

dtrace -l | head
   ID   PROVIDER            MODULE                          FUNCTION NAME
    1     dtrace                                                     BEGIN
    2     dtrace                                                     END
    3     dtrace                                                     ERROR
   43    profile                                                     profile-97
   44    profile                                                     profile-199

The probe name consists of four parts: the provider, module, function, and name (fbt:mach_kernel:ptrace:entry). If you not specifies some part of the name, Dtrace will apply that part as a wildcard.

To configure DTrace to activate probes and to specify what actions to perform when they fire, we will need to use the D language.

A more detailed explanation and more examples can be found in


Run man -k dtrace to list the DTrace scripts available. Example: sudo dtruss -n binary

  • In line

#Count the number of syscalls of each running process
sudo dtrace -n 'syscall:::entry {@[execname] = count()}'
  • script

/pid == $1/

#Log every syscall of a PID
sudo dtrace -s script.d 1234 
    printf("%s(%s)", probefunc, copyinstr(arg0));
        printf("%s(%d)\n", probefunc, arg0);

#Log files opened and closed by a process
sudo dtrace -s b.d -c "cat /etc/hosts"
        printf("=%d\n", arg1);

#Log sys calls with values
sudo dtrace -s syscalls_info.d -c "cat /etc/hosts"


dtruss -c ls #Get syscalls of ls
dtruss -c -p 1000 #get syscalls of PID 1000


You can use this one even with SIP activated

ktrace trace -s -S -t c -c ls | grep "ls("


ProcessMonitor is a very useful tool to check the process related actions a process is performing (for example, monitor which new processes a process is creating).


SpriteTree is a tool to prints the relations between processes. You need to monitor your mac with a command like sudo eslogger fork exec rename create > cap.json (the terminal launching this required FDA). And then you can load the json in this tool to viwe all the relations:


FileMonitor allows to monitor file events (such as creation, modifications, and deletions) providing detailed information about such events.


Crescendo is a GUI tool with the look and feel Windows users may know from Microsoft Sysinternal’s Procmon. This tool allows the recording of various event types to be started and stopped, allows for the filtering of these events by categories such as file, process, network, etc., and provides the functionality to save the events recorded in a json format.

Apple Instruments

Apple Instruments are part of Xcode’s Developer tools – used for monitoring application performance, identifying memory leaks and tracking filesystem activity.


Allows to follow actions performed by processes:

fs_usage -w -f filesys ls #This tracks filesystem actions of proccess names containing ls
fs_usage -w -f network curl #This tracks network actions


Taskexplorer is useful to see the libraries used by a binary, the files it's using and the network connections. It also checks the binary processes against virustotal and show information about the binary.


In this blog post you can find an example about how to debug a running daemon that used PT_DENY_ATTACH to prevent debugging even if SIP was disabled.


lldb is the de facto tool for macOS binary debugging.

lldb ./malware.bin
lldb -p 1122
lldb -n malware.bin
lldb -n malware.bin --waitfor

You can set intel flavour when using lldb creating a file called .lldbinit in your home folder with the following line:

settings set target.x86-disassembly-flavor intel

Inside lldb, dump a process with process save-core

(lldb) Command


run (r)

Starting execution, which will continue unabated until a breakpoint is hit or the process terminates.

continue (c)

Continue execution of the debugged process.

nexti (n / ni)

Execute the next instruction. This command will skip over function calls.

stepi (s / si)

Execute the next instruction. Unlike the nexti command, this command will step into function calls.

finish (f)

Execute the rest of the instructions in the current function (“frame”) return and halt.

control + c

Pause execution. If the process has been run (r) or continued (c), this will cause the process to halt ...wherever it is currently executing.

breakpoint (b)

b main #Any func called main

b <binname>`main #Main func of the bin

b set -n main --shlib <lib_name> #Main func of the indicated bin

b -[NSDictionary objectForKey:]

b -a 0x0000000100004bd9

br l #Breakpoint list

br e/dis <num> #Enable/Disable breakpoint

breakpoint delete <num>


help breakpoint #Get help of breakpoint command

help memory write #Get help to write into the memory


reg read

reg read $rax

reg read $rax --format <format>

reg write $rip 0x100035cc0

x/s <reg/memory address>

Display the memory as a null-terminated string.

x/i <reg/memory address>

Display the memory as assembly instruction.

x/b <reg/memory address>

Display the memory as byte.

print object (po)

This will print the object referenced by the param

po $raw


dnsChanger = {

"affiliate" = "";

"blacklist_dns" = ();

Note that most of Apple’s Objective-C APIs or methods return objects, and thus should be displayed via the “print object” (po) command. If po doesn't produce a meaningful output use x/b


memory read 0x000.... memory read $x0+0xf2a memory write 0x100600000 -s 4 0x41414141 #Write AAAA in that address memory write -f s $rip+0x11f+7 "AAAA" #Write AAAA in the addr


dis #Disas current function

dis -n <funcname> #Disas func

dis -n <funcname> -b <basename> #Disas func dis -c 6 #Disas 6 lines dis -c 0x100003764 -e 0x100003768 # From one add until the other dis -p -c 4 # Start in current address disassembling


parray 3 (char **)$x1 # Check array of 3 components in x1 reg

When calling the objc_sendMsg function, the rsi register holds the name of the method as a null-terminated (“C”) string. To print the name via lldb do:

(lldb) x/s $rsi: 0x1000f1576: "startMiningWithPort:password:coreCount:slowMemory:currency:"

(lldb) print (char*)$rsi: (char *) $1 = 0x00000001000f1576 "startMiningWithPort:password:coreCount:slowMemory:currency:"

(lldb) reg read $rsi: rsi = 0x00000001000f1576 "startMiningWithPort:password:coreCount:slowMemory:currency:"

Anti-Dynamic Analysis

VM detection

  • The command sysctl hw.model returns "Mac" when the host is a MacOS but something different when it's a VM.

  • Playing with the values of hw.logicalcpu and hw.physicalcpu some malwares try to detect if it's a VM.

  • Some malwares can also detect if the machine is VMware based on the MAC address (00:50:56).

  • It's also possible to find if a process is being debugged with a simple code such us:

    • if(P_TRACED == (info.kp_proc.p_flag & P_TRACED)){ //process being debugged }

  • It can also invoke the ptrace system call with the PT_DENY_ATTACH flag. This prevents a debugger from attaching and tracing.

    • You can check if the sysctl or ptrace function is being imported (but the malware could import it dynamically)

    • As noted in this writeup, “Defeating Anti-Debug Techniques: macOS ptrace variants” : “The message Process # exited with status = 45 (0x0000002d) is usually a tell-tale sign that the debug target is using PT_DENY_ATTACH


ReportCrash analyzes crashing processes and saves a crash report to disk. A crash report contains information that can help a developer diagnose the cause of a crash. For applications and other processes running in the per-user launchd context, ReportCrash runs as a LaunchAgent and saves crash reports in the user's ~/Library/Logs/DiagnosticReports/ For daemons, other processes running in the system launchd context and other privileged processes, ReportCrash runs as a LaunchDaemon and saves crash reports in the system's /Library/Logs/DiagnosticReports

If you are worried about crash reports being sent to Apple you can disable them. If not, crash reports can be useful to figure out how a server crashed.

#To disable crash reporting:
launchctl unload -w /System/Library/LaunchAgents/
sudo launchctl unload -w /System/Library/LaunchDaemons/

#To re-enable crash reporting:
launchctl load -w /System/Library/LaunchAgents/
sudo launchctl load -w /System/Library/LaunchDaemons/


While fuzzing in a MacOS it's important to not allow the Mac to sleep:

SSH Disconnect

If you are fuzzing via a SSH connection it's important to make sure the session isn't going to day. So change the sshd_config file with:

  • TCPKeepAlive Yes

  • ClientAliveInterval 0

  • ClientAliveCountMax 0

sudo launchctl unload /System/Library/LaunchDaemons/ssh.plist
sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist

Internal Handlers

Checkout the following page to find out how you can find which app is responsible of handling the specified scheme or protocol:

pagemacOS File Extension & URL scheme app handlers

Enumerating Network Processes

This interesting to find processes that are managing network data:

dtrace -n 'syscall::recv*:entry { printf("-> %s (pid=%d)", execname, pid); }' >> recv.log
#wait some time
sort -u recv.log > procs.txt
cat procs.txt

Or use netstat or lsof


lldb -o "target create `which some-binary`" -o "settings set target.env-vars DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib" -o "run arg1 arg2" -o "bt" -o "reg read" -o "dis -s \$pc-32 -c 24 -m -F intel" -o "quit"


Works for CLI tools

It "just works" with macOS GUI tools. Note some some macOS apps have some specific requirements like unique filenames, the right extension, need to read the files from the sandbox (~/Library/Containers/

Some examples:

# iBooks
litefuzz -l -c "/System/Applications/ FUZZ" -i files/epub -o crashes/ibooks -t /Users/test/Library/Containers/ -x 10 -n 100000 -ez

# -l : Local
# -c : cmdline with FUZZ word (if not stdin is used)
# -i : input directory or file
# -o : Dir to output crashes
# -t : Dir to output runtime fuzzing artifacts
# -x : Tmeout for the run (default is 1)
# -n : Num of fuzzing iterations (default is 1)
# -e : enable second round fuzzing where any crashes found are reused as inputs
# -z : enable malloc debug helpers

# Font Book
litefuzz -l -c "/System/Applications/Font Book FUZZ" -i input/fonts -o crashes/font-book -x 2 -n 500000 -ez

# smbutil (using pcap capture)
litefuzz -lk -c "smbutil view smb://localhost:4455" -a tcp://localhost:4455 -i input/mac-smb-resp -p -n 100000 -z

# screensharingd (using pcap capture)
litefuzz -s -a tcp://localhost:5900 -i input/screenshared-session --reportcrash screensharingd -p -n 100000

More Fuzzing MacOS Info


Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

Last updated