Hacktricks-skills macos-gcd-analysis
Analyze Grand Central Dispatch (GCD) usage in macOS/iOS applications for security research and reverse engineering. Use this skill whenever the user needs to understand GCD queues, blocks, or parallelism in Objective-C/Swift code, hook GCD functions with Frida, or reverse engineer GCD structures in Ghidra. Trigger on mentions of dispatch_async, dispatch_sync, DispatchQueue, libdispatch, GCD queues, or any macOS/iOS concurrency analysis.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/macos-hardening/macos-security-and-privilege-escalation/macos-gcd-grand-central-dispatch/SKILL.MDmacOS GCD Analysis Skill
This skill helps you analyze Grand Central Dispatch (GCD) usage in macOS and iOS applications for security research, reverse engineering, and understanding concurrent execution patterns.
What is GCD?
Grand Central Dispatch (GCD), also known as libdispatch (
libdispatch.dylib), is Apple's technology for managing concurrent (multithreaded) execution on multicore hardware. Key concepts:
- FIFO Queues: GCD provides and manages queues to which applications submit tasks as block objects
- Thread Pool Management: GCD automatically creates and manages threads for executing tasks
- No Manual Thread Creation: Processes don't create new threads; GCD executes code with its own thread pool
Why GCD Matters for Security Research
- Parallelism for attacks: GCD is ideal for tasks requiring great parallelism (e.g., brute-forcing)
- Non-blocking operations: Background tasks (network, file I/O, searches) run on GCD queues to avoid blocking the main thread
- Race conditions: Concurrent queues can introduce race conditions that may be exploitable
- Hidden execution paths: GCD blocks can contain logic that's harder to trace in static analysis
Block Structure
Blocks are self-contained sections of code (like functions with arguments returning values). At the compiler level, blocks are
os_objects formed by two structures:
Block Literal Structure
struct Block { void *isa; // Points to block's class int flags; // Indicates fields present in block descriptor int reserved; void *invoke; // Function pointer to call struct BlockDescriptor *descriptor; // captured variables go here };
Block Classes (isa field):
- Blocks fromNSConcreteGlobalBlock__DATA.__const
- Blocks in the heapNSConcreteMallocBlock
- Blocks in the stackNSConcreteStackBlock
Block Descriptor
The descriptor size depends on data present (indicated by flags):
- Reserved bytes
- Size of the descriptor
- Pointer to Objective-C style signature (if
flag is set)BLOCK_HAS_SIGNATURE - Copy helper pointer (if variables are referenced)
- Dispose helper pointer (for freeing variables)
Dispatch Queues
A dispatch queue is a named object providing FIFO ordering of blocks for execution.
Queue Types
| Type | Description | Race Conditions |
|---|---|---|
| Blocks execute one at a time | No |
| Multiple blocks can execute simultaneously | Yes |
Default Queues
| Queue | Function/Constant | Priority |
|---|---|---|
| | Main thread |
| | Highest |
| | Default |
| | Utility |
| | Background |
| - | Lowest |
Note: The system decides which threads handle which queues at each time. Multiple threads might work in the same queue, or the same thread might work in different queues.
Queue Attributes
When creating a queue with
dispatch_queue_create, the third argument is dispatch_queue_attr_t:
(actually NULL)DISPATCH_QUEUE_SERIAL
(pointer toDISPATCH_QUEUE_CONCURRENT
struct)dispatch_queue_attr_t
Dispatch Objects
Libdispatch uses several object types, creatable with
dispatch_object_create:
- Code blocksblock
- Data blocksdata
- Group of blocksgroup
- Async I/O requestsio
- Mach portsmach
- Mach messagesmach_msg
- Queue with pthread thread poolpthread_root_queue
- Dispatch queuesqueue
- Semaphoressemaphore
- Event sourcessource
Objective-C GCD Functions
Core Functions
| Function | Behavior | Returns |
|---|---|---|
| Submits block for async execution | Immediately |
| Submits block, waits for completion | After block finishes |
| Executes block only once | After block finishes |
| Submits work item, waits for completion | After block finishes |
All functions expect:
dispatch_queue_t queue, dispatch_block_t block
Example: Parallel Execution
#import <Foundation/Foundation.h> // Define a block void (^backgroundTask)(void) = ^{ for (int i = 0; i < 10; i++) { NSLog(@"Background task %d", i); sleep(1); } }; int main(int argc, const char * argv[]) { @autoreleasepool { // Create a serial dispatch queue dispatch_queue_t backgroundQueue = dispatch_queue_create( "com.example.backgroundQueue", NULL); // Submit the block for asynchronous execution dispatch_async(backgroundQueue, backgroundTask); // Continue with other work on the main queue for (int i = 0; i < 10; i++) { NSLog(@"Main task %d", i); sleep(1); } } return 0; }
Swift GCD Usage
provides Swift bindings to GCD, wrapping C GCD APIs in a Swift-friendly interface.libswiftDispatch
Swift Syntax
| Pattern | Description |
|---|---|
| Synchronous execution on global queue |
| Asynchronous execution on global queue |
| Execute once |
| Modern async/await pattern |
Example: Swift Parallel Execution
import Foundation // Define a closure (Swift equivalent of a block) let backgroundTask: () -> Void = { for i in 0..<10 { print("Background task \(i)") sleep(1) } } // Entry point autoreleasepool { // Create a dispatch queue let backgroundQueue = DispatchQueue(label: "com.example.backgroundQueue") // Submit the closure for asynchronous execution backgroundQueue.async(execute: backgroundTask) // Continue with other work on the main queue for i in 0..<10 { print("Main task \(i)") sleep(1) } }
Frida Hooking for GCD
Use Frida to hook into dispatch functions and extract queue names, backtraces, and blocks.
Hooking Script
Use the libdispatch.js script to hook dispatch functions.
Usage
frida -U <prog_name> -l libdispatch.js
Example Output
dispatch_sync Calling queue: com.apple.UIKit._UIReusePool.reuseSetAccess Callback function: 0x19e3a6488 UIKitCore!__26-[_UIReusePool addObject:]_block_invoke Backtrace: 0x19e3a6460 UIKitCore!-[_UIReusePool addObject:] 0x19e3a5db8 UIKitCore!-[UIGraphicsRenderer _enqueueContextForReuse:] 0x19e3a57fc UIKitCore!+[UIGraphicsRenderer _destroyCGContext:withRenderer:]
What to Look For
- Queue names: Identify which system or custom queues are being used
- Callback functions: Find the actual block code being executed
- Backtraces: Understand the call chain leading to GCD execution
- Timing: When blocks are submitted vs. when they execute
Ghidra Reverse Engineering
Ghidra doesn't natively understand
dispatch_block_t or swift_dispatch_block structures. You need to declare them manually.
Step 1: Declare Block Structures
Create the block structure in Ghidra:
struct Block { void *isa; int flags; int reserved; void *invoke; struct BlockDescriptor *descriptor; }; struct BlockDescriptor { int reserved; int size; void *copy_helper; void *dispose_helper; void *signature; // if BLOCK_HAS_SIGNATURE };
Step 2: Find Block Usage
Look for references to "block" in the code to understand how the structure is being used.
Step 3: Retype Variables
- Right-click on the variable
- Select "Retype Variable"
- Choose
orswift_dispatch_blockdispatch_block_t
Ghidra will automatically rewrite the decompilation to use the correct structure.
What to Look For
- Block creation: Where blocks are allocated/created
- Block submission: Where blocks are submitted to queues
- Captured variables: What data blocks capture from their environment
- Queue types: Serial vs. concurrent queue usage
Security Analysis Checklist
When analyzing GCD usage for security:
- Identify all dispatch queues used (serial vs. concurrent)
- Map block execution paths and dependencies
- Look for race conditions in concurrent queues
- Check for sensitive data in captured block variables
- Analyze queue priorities and potential DoS vectors
- Hook GCD functions with Frida to trace execution
- Verify block lifecycle (copy/dispose helpers)
- Check for
misuse (initialization races)dispatch_once
Common Patterns to Investigate
- Brute-force operations: GCD is often used for parallel password cracking or enumeration
- Background network requests: API calls, data fetching hidden in background queues
- File operations: Reading/writing sensitive files asynchronously
- UI updates: Main queue blocks that update UI based on background results
- Initialization code:
blocks that set up critical statedispatch_once