Post

A Comprehensive Guide to macOS Binary Analysis Tools

⚠️ This document is still a draft. Please note that there might be mistakes or inaccuracies.

When it comes to understanding the inner workings of macOS applications and system processes, binary analysis tools are essential. These tools allow security researchers, developers, and curious minds to dissect binaries, examine code, and gain insights into how macOS software functions.

In this comprehensive guide, we’ll explore various macOS binary analysis tools, both command-line and graphical, to help you navigate the world of binary analysis effectively. In order to discover vulnerabilities and write exploits, we need to conduct both dynamic and static analysis of binaries and understand how to debug applications.

Table of contents

Command Line Static Analysis Tools

codesign

codesign is a powerful command-line tool that helps verify code signatures and certificates associated with macOS binaries. It ensures the integrity and authenticity of executable files, libraries, and bundles. Running codesign with appropriate options allows you to investigate code signing details and identify potential security issues.

To verify the code signature of a macOS binary named “MyApp.app” you can use the following command:

1
codesign -dv --verbose=4 /path/to/MyApp.app

This command will display detailed information about the code signature, including the certificate used for signing.

objdump

objdump is a versatile utility that provides detailed information about binary files, including disassembled code, section headers, and symbol tables. This tool is particularly useful for reverse engineering, allowing you to examine assembly instructions and gain insights into a binary’s functionality.

To disassemble a macOS binary file named “my_binary” you can use the following command:

1
objdump -d /path/to/my_binary

This command will display the disassembled code of the binary, allowing you to analyze its assembly instructions.

jtool

jtool is a Swiss army knife for macOS binary analysis. It offers various functionalities, including disassembly, code signature inspection, and more. Its flexibility and ease of use make it a valuable addition to your binary analysis toolkit.

To inspect the code signature of a macOS binary using jtool, you can use the following command:

1
`jtool` --sign /path/to/my_binary

This command will provide information about the binary’s code signature, similar to codesign.

Static Analysis with Hopper

Hopper is a popular graphical disassembler and decompiler for macOS. It simplifies the process of reverse engineering by providing a user-friendly interface. Here are some key aspects of static analysis with Hopper:

Views in Hopper

Hopperoffers various views, such as the disassembly view, pseudo code view, and data flow view. These views help you explore different aspects of the binary, making it easier to understand its behavior.

Hopperallows you to navigate through the binary code effortlessly. You can jump between functions, follow code paths, and analyze control flow to comprehend the binary’s logic.

External C Function Resolution

When analyzing macOS binaries, you often encounter calls to external C functions. Hopperassists in resolving these external function references, helping you understand how the binary interacts with the macOS API.

Dynamic Analysis

Dynamic analysis involves observing the behavior of a binary while it runs. This approach is valuable for understanding how software behaves in real-world scenarios and can uncover runtime vulnerabilities. Tools like LLDB and Hopperfacilitate dynamic analysis.

The LLDB Debugger

Setting Breakpoints

LLDB is the default debugger for macOS. You can use it to set breakpoints in a binary, allowing you to halt execution at specific points in the code for inspection.

Disassembling with LLDB

LLDB provides disassembly capabilities, enabling you to examine the assembly instructions executed by the binary during debugging sessions.

Reading and Writing Memory, and Registers

LLDB allows you to read and modify memory contents and CPU registers during debugging. This is crucial for understanding and manipulating a binary’s state.

Modifying Code During Debugging

In some cases, you may want to modify the binary’s code while debugging. LLDB enables you to patch binary code on the fly, which can be useful for analyzing and mitigating security vulnerabilities.

Debugging with Hopper

Hopperalso provides debugging capabilities to complement its static analysis features:

Starting the Debugger

You can initiate a debugging session within Hopperto dynamically analyze a binary. This allows you to step through code, set breakpoints, and inspect memory and registers.

Basic Controls and Functionality

Hopper’s debugger offers controls for stepping through code, examining variables, and understanding the binary’s runtime behavior.

Inspecting External Function Resolution

Similar to static analysis, Hopper’s debugger helps you inspect and understand how the binary interacts with external functions during runtime.

Tracing Applications with DTrace

DTrace is a dynamic tracing framework available on macOS. It enables you to monitor and trace various system activities, providing insights into application behavior and performance. Here are some examples of using DTrace for binary analysis:

DTrace Example - Monitoring System Calls

You can create DTrace scripts to trace system calls made by a binary, helping you understand its interactions with the underlying operating system.

DTrace Example - Monitoring Write Calls

DTrace can be used to track write operations, allowing you to observe file and network activity initiated by the binary.

DTrace Example - Creating Aggregation Info

DTrace can aggregate data, making it easier to identify patterns and anomalies in the binary’s behavior over time.

DTrace Probes

DTrace provides a rich set of probes that you can use to trace various events, such as function calls, I/O operations, and more.

System DTrace Scripts

Numerous DTrace scripts and tools are available for specific use cases. You can leverage these scripts to gain deeper insights into binary behavior.

The man -k dtrace command will list all of the DTrace scripts available.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
man -k dtrace

Tcl_CommandTraceInfo(3tcl), Tcl_TraceCommand(3tcl), Tcl_UntraceCommand(3tcl) - monitor renames and deletes of a command
perldtrace(1)            - Perl's support for DTrace
bitesize.d(1m)           - analyse disk I/O size by process. Uses DTrace
cpuwalk.d(1m)            - Measure which CPUs a process runs on. Uses DTrace
creatbyproc.d(1m)        - snoop creat()s by process name. Uses DTrace
dappprof(1m)             - profile user and lib function usage. Uses DTrace
dapptrace(1m)            - trace user and library function usage. Uses DTrace
dispqlen.d(1m)           - dispatcher queue length by CPU. Uses DTrace
dtrace(1)                - dynamic tracing compiler and tracing utility
dtruss(1m)               - process syscall details. Uses DTrace
errinfo(1m)              - print errno for syscall fails. Uses DTrace
execsnoop(1m)            - snoop new process execution. Uses DTrace
fddist(1m)               - file descriptor usage distributions. Uses DTrace
filebyproc.d(1m)         - snoop opens by process name. Uses DTrace
hotspot.d(1m)            - print disk event by location. Uses DTrace
iofile.d(1m)             - I/O wait time by file and process. Uses DTrace
iofileb.d(1m)            - I/O bytes by file and process. Uses DTrace
iopattern(1m)            - print disk I/O pattern. Uses DTrace
iopending(1m)            - plot number of pending disk events. Uses DTrace
iosnoop(1m)              - snoop I/O events as they occur. Uses DTrace
iotop(1m)                - display top disk I/O events by process. Uses DTrace
kill.d(1m)               - snoop process signals as they occur. Uses DTrace
lastwords(1m)            - print syscalls before exit. Uses DTrace
loads.d(1m)              - print load averages. Uses DTrace
newproc.d(1m)            - snoop new processes. Uses DTrace
opensnoop(1m)            - snoop file opens as they occur. Uses DTrace
pathopens.d(1m)          - full pathnames opened ok count. Uses DTrace
perldtrace(1)            - Perl's support for DTrace
pidpersec.d(1m)          - print new PIDs per sec. Uses DTrace
plockstat(1)             - front-end to DTrace to print statistics about POSIX mutexes and read/write locks
priclass.d(1m)           - priority distribution by scheduling class. Uses DTrace
pridist.d(1m)            - process priority distribution. Uses DTrace
procsystime(1m)          - analyse system call times. Uses DTrace
rwbypid.d(1m)            - read/write calls by PID. Uses DTrace
rwbytype.d(1m)           - read/write bytes by vnode type. Uses DTrace
rwsnoop(1m)              - snoop read/write events. Uses DTrace
sampleproc(1m)           - sample processes on the CPUs. Uses DTrace
seeksize.d(1m)           - print disk event seek report. Uses DTrace
setuids.d(1m)            - snoop setuid calls as they occur. Uses DTrace
sigdist.d(1m)            - signal distribution by process. Uses DTrace
syscallbypid.d(1m)       - syscalls by process ID. Uses DTrace
syscallbyproc.d(1m)      - syscalls by process name. Uses DTrace
syscallbysysc.d(1m)      - syscalls by syscall. Uses DTrace
topsyscall(1m)           - top syscalls by syscall name. Uses DTrace
topsysproc(1m)           - top syscalls by process name. Uses DTrace
Tcl_CommandTraceInfo(3tcl), Tcl_TraceCommand(3tcl), Tcl_UntraceCommand(3tcl) - monitor renames and deletes of a command
bitesize.d(1m)           - analyse disk I/O size by process. Uses DTrace
cpuwalk.d(1m)            - Measure which CPUs a process runs on. Uses DTrace
creatbyproc.d(1m)        - snoop creat()s by process name. Uses DTrace
dappprof(1m)             - profile user and lib function usage. Uses DTrace
dapptrace(1m)            - trace user and library function usage. Uses DTrace
dispqlen.d(1m)           - dispatcher queue length by CPU. Uses DTrace
dtrace(1)                - dynamic tracing compiler and tracing utility
dtruss(1m)               - process syscall details. Uses DTrace
errinfo(1m)              - print errno for syscall fails. Uses DTrace
execsnoop(1m)            - snoop new process execution. Uses DTrace
fddist(1m)               - file descriptor usage distributions. Uses DTrace
filebyproc.d(1m)         - snoop opens by process name. Uses DTrace
hotspot.d(1m)            - print disk event by location. Uses DTrace
iofile.d(1m)             - I/O wait time by file and process. Uses DTrace
iofileb.d(1m)            - I/O bytes by file and process. Uses DTrace
iopattern(1m)            - print disk I/O pattern. Uses DTrace
iopending(1m)            - plot number of pending disk events. Uses DTrace
iosnoop(1m)              - snoop I/O events as they occur. Uses DTrace
iotop(1m)                - display top disk I/O events by process. Uses DTrace
kill.d(1m)               - snoop process signals as they occur. Uses DTrace
lastwords(1m)            - print syscalls before exit. Uses DTrace
loads.d(1m)              - print load averages. Uses DTrace
newproc.d(1m)            - snoop new processes. Uses DTrace
opensnoop(1m)            - snoop file opens as they occur. Uses DTrace
pathopens.d(1m)          - full pathnames opened ok count. Uses DTrace
perldtrace(1)            - Perl's support for DTrace
pidpersec.d(1m)          - print new PIDs per sec. Uses DTrace
plockstat(1)             - front-end to DTrace to print statistics about POSIX mutexes and read/write locks
priclass.d(1m)           - priority distribution by scheduling class. Uses DTrace
pridist.d(1m)            - process priority distribution. Uses DTrace
procsystime(1m)          - analyse system call times. Uses DTrace
rwbypid.d(1m)            - read/write calls by PID. Uses DTrace
rwbytype.d(1m)           - read/write bytes by vnode type. Uses DTrace
rwsnoop(1m)              - snoop read/write events. Uses DTrace
sampleproc(1m)           - sample processes on the CPUs. Uses DTrace
seeksize.d(1m)           - print disk event seek report. Uses DTrace
setuids.d(1m)            - snoop setuid calls as they occur. Uses DTrace
sigdist.d(1m)            - signal distribution by process. Uses DTrace
syscallbypid.d(1m)       - syscalls by process ID. Uses DTrace
syscallbyproc.d(1m)      - syscalls by process name. Uses DTrace
syscallbysysc.d(1m)      - syscalls by syscall. Uses DTrace
topsyscall(1m)           - top syscalls by syscall name. Uses DTrace
topsysproc(1m)           - top syscalls by process name. Uses DTrace

Wrapping Up

In this guide, we’ve covered a range of macOS binary analysis tools, from command-line utilities like codesign and objdump to powerful graphical tools like Hopper, as well as dynamic analysis with LLDB and DTrace.

Understanding these tools and how to use them effectively is essential for anyone involved in reverse engineering, security research, or macOS development. With these tools at your disposal, you can gain valuable insights into the inner workings of macOS applications and system processes, uncover vulnerabilities, and enhance your overall macOS binary analysis skills.

Happy analyzing!!

This post is licensed under CC BY 4.0 by the author.