macOS GCD - Grand Central Dispatch
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Grand Central Dispatch (GCD), also known as libdispatch (libdispatch.dyld
), is available in both macOS and iOS. It's a technology developed by Apple to optimize application support for concurrent (multithreaded) execution on multicore hardware.
GCD provides and manages FIFO queues to which your application can submit tasks in the form of block objects. Blocks submitted to dispatch queues are executed on a pool of threads fully managed by the system. GCD automatically creates threads for executing the tasks in the dispatch queues and schedules those tasks to run on the available cores.
In summary, to execute code in parallel, processes can send blocks of code to GCD, which will take care of their execution. Therefore, processes don't create new threads; GCD executes the given code with its own pool of threads (which might increase or decrease as necessary).
This is very helpful to manage parallel execution successfully, greatly reducing the number of threads processes create and optimising the parallel execution. This is ideal for tasks that require great parallelism (brute-forcing?) or for tasks that shouldn't block the main thread: For example, the main thread on iOS handles UI interactions, so any other functionality that could make the app hang (searching, accessing a web, reading a file...) is managed this way.
A block is a self contained section of code (like a function with arguments returning a value) and can also specify bound variables.
However, at compiler level blocks doesn't exist, they are os_object
s. Each of these objects is formed by two structures:
block literal:
It starts by the isa
field, pointing to the block's class:
NSConcreteGlobalBlock
(blocks from __DATA.__const
)
NSConcreteMallocBlock
(blocks in the heap)
NSConcreateStackBlock
(blocks in stack)
It has flags
(indicating fields present in the block descriptor) and some reserved bytes
The function pointer to call
A pointer to the block descriptor
Block imported variables (if any)
block descriptor: It's size depends on the data that is present (as indicated in the previous flags)
It has some reserved bytes
The size of it
It'll usually have a pointer to an Objective-C style signature to know how much space is needed for the params (flag BLOCK_HAS_SIGNATURE
)
If variables are referenced, this block will also have pointers to a copy helper (copying the value at the begining) and dispose helper (freeing it).
A dispatch queue is a named object providing FIFO ordering of blocks for executions.
Blocks a set in queues to be executed, and these support 2 modes: DISPATCH_QUEUE_SERIAL
and DISPATCH_QUEUE_CONCURRENT
. Of course the serial one won't have race condition problems as a block won't be executed until the previous one has finished. But the other type of queue might have it.
Default queues:
.main-thread
: From dispatch_get_main_queue()
.libdispatch-manager
: GCD's queue manager
.root.libdispatch-manager
: GCD's queue manager
.root.maintenance-qos
: Lowest priority tasks
.root.maintenance-qos.overcommit
.root.background-qos
: Available as DISPATCH_QUEUE_PRIORITY_BACKGROUND
.root.background-qos.overcommit
.root.utility-qos
: Available as DISPATCH_QUEUE_PRIORITY_NON_INTERACTIVE
.root.utility-qos.overcommit
.root.default-qos
: Available as DISPATCH_QUEUE_PRIORITY_DEFAULT
.root.background-qos.overcommit
.root.user-initiated-qos
: Available as DISPATCH_QUEUE_PRIORITY_HIGH
.root.background-qos.overcommit
.root.user-interactive-qos
: Highest priority
.root.background-qos.overcommit
Notice that it will be the system who 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 at some point)
When creating a queue with dispatch_queue_create
the third argument is a dispatch_queue_attr_t
, which usually is either DISPATCH_QUEUE_SERIAL
(which is actually NULL) or DISPATCH_QUEUE_CONCURRENT
which is a pointer to a dispatch_queue_attr_t
struct which allow to control some parameters of the queue.
There are several objects that libdispatch uses and queues and blocks are just 2 of them. It's possible to create these objects with dispatch_object_create
:
block
data
: Data blocks
group
: Group of blocks
io
: Async I/O requests
mach
: Mach ports
mach_msg
: Mach messages
pthread_root_queue
:A queue with a pthread thread pool and not workqueues
queue
semaphore
source
: Event source
In Objetive-C there are different functions to send a block to be executed in parallel:
dispatch_async: Submits a block for asynchronous execution on a dispatch queue and returns immediately.
dispatch_sync: Submits a block object for execution and returns after that block finishes executing.
dispatch_once: Executes a block object only once for the lifetime of an application.
dispatch_async_and_wait: Submits a work item for execution and returns only after it finishes executing. Unlike dispatch_sync
, this function respects all attributes of the queue when it executes the block.
These functions expect these parameters: dispatch_queue_t
queue,
dispatch_block_t
block
This is the struct of a Block:
And this is an example to use parallelism with dispatch_async
:
libswiftDispatch
is a library that provides Swift bindings to the Grand Central Dispatch (GCD) framework which is originally written in C.
The libswiftDispatch
library wraps the C GCD APIs in a more Swift-friendly interface, making it easier and more intuitive for Swift developers to work with GCD.
DispatchQueue.global().sync{ ... }
DispatchQueue.global().async{ ... }
let onceToken = DispatchOnce(); onceToken.perform { ... }
async await
var (data, response) = await URLSession.shared.data(from: URL(string: "https://api.example.com/getData"))
Code example:
The following Frida script can be used to hook into several dispatch
functions and extract the queue name, the backtrace and the block: https://github.com/seemoo-lab/frida-scripts/blob/main/scripts/libdispatch.js
Currently Ghidra doesn't understand neither the ObjectiveC dispatch_block_t
structure, neither the swift_dispatch_block
one.
So if you want it to understand them, you could just declare them:
Then, find a place in the code where they are used:
Note all of references made to "block" to understand how you could figure out that the struct is being used.
Right click on the variable -> Retype Variable and select in this case swift_dispatch_block
:
Ghidra will automatically rewrite everything:
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)