macOS GCD - Grand Central Dispatch
Last updated
Last updated
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Grand Central Dispatch (GCD), também conhecido como libdispatch (libdispatch.dyld
), está disponível tanto no macOS quanto no iOS. É uma tecnologia desenvolvida pela Apple para otimizar o suporte de aplicativos para execução concorrente (multithreaded) em hardware multicore.
GCD fornece e gerencia filas FIFO para as quais seu aplicativo pode enviar tarefas na forma de objetos de bloco. Blocos enviados para filas de despacho são executados em um pool de threads totalmente gerenciado pelo sistema. O GCD cria automaticamente threads para executar as tarefas nas filas de despacho e agenda essas tarefas para serem executadas nos núcleos disponíveis.
Em resumo, para executar código em paralelo, os processos podem enviar blocos de código para o GCD, que cuidará de sua execução. Portanto, os processos não criam novas threads; o GCD executa o código fornecido com seu próprio pool de threads (que pode aumentar ou diminuir conforme necessário).
Isso é muito útil para gerenciar a execução paralela com sucesso, reduzindo significativamente o número de threads que os processos criam e otimizando a execução paralela. Isso é ideal para tarefas que requerem grande paralelismo (força bruta?) ou para tarefas que não devem bloquear a thread principal: Por exemplo, a thread principal no iOS lida com interações de UI, então qualquer outra funcionalidade que possa fazer o aplicativo travar (pesquisa, acesso a uma web, leitura de um arquivo...) é gerenciada dessa maneira.
Um bloco é uma seção de código autocontida (como uma função com argumentos que retorna um valor) e também pode especificar variáveis vinculadas.
No entanto, no nível do compilador, os blocos não existem, eles são os_object
s. Cada um desses objetos é formado por duas estruturas:
literal de bloco:
Começa pelo campo isa
, apontando para a classe do bloco:
NSConcreteGlobalBlock
(blocos de __DATA.__const
)
NSConcreteMallocBlock
(blocos na heap)
NSConcreateStackBlock
(blocos na pilha)
Possui flags
(indicando campos presentes no descritor do bloco) e alguns bytes reservados
O ponteiro da função a ser chamada
Um ponteiro para o descritor do bloco
Variáveis importadas do bloco (se houver)
descritor de bloco: Seu tamanho depende dos dados presentes (conforme indicado nas flags anteriores)
Possui alguns bytes reservados
O tamanho dele
Normalmente terá um ponteiro para uma assinatura de estilo Objective-C para saber quanto espaço é necessário para os parâmetros (flag BLOCK_HAS_SIGNATURE
)
Se variáveis são referenciadas, este bloco também terá ponteiros para um auxiliar de cópia (copiando o valor no início) e um auxiliar de liberação (liberando-o).
Uma fila de despacho é um objeto nomeado que fornece a ordenação FIFO de blocos para execuções.
Blocos são definidos em filas para serem executados, e estas suportam 2 modos: DISPATCH_QUEUE_SERIAL
e DISPATCH_QUEUE_CONCURRENT
. Claro que o serial não terá problemas de condição de corrida pois um bloco não será executado até que o anterior tenha terminado. Mas o outro tipo de fila pode ter.
Filas padrão:
.main-thread
: De dispatch_get_main_queue()
.libdispatch-manager
: Gerenciador de filas do GCD
.root.libdispatch-manager
: Gerenciador de filas do GCD
.root.maintenance-qos
: Tarefas de menor prioridade
.root.maintenance-qos.overcommit
.root.background-qos
: Disponível como DISPATCH_QUEUE_PRIORITY_BACKGROUND
.root.background-qos.overcommit
.root.utility-qos
: Disponível como DISPATCH_QUEUE_PRIORITY_NON_INTERACTIVE
.root.utility-qos.overcommit
.root.default-qos
: Disponível como DISPATCH_QUEUE_PRIORITY_DEFAULT
.root.background-qos.overcommit
.root.user-initiated-qos
: Disponível como DISPATCH_QUEUE_PRIORITY_HIGH
.root.background-qos.overcommit
.root.user-interactive-qos
: Maior prioridade
.root.background-qos.overcommit
Observe que será o sistema quem decide quais threads manipulam quais filas em cada momento (múltiplas threads podem trabalhar na mesma fila ou a mesma thread pode trabalhar em filas diferentes em algum momento)
Ao criar uma fila com dispatch_queue_create
o terceiro argumento é um dispatch_queue_attr_t
, que geralmente é DISPATCH_QUEUE_SERIAL
(que na verdade é NULL) ou DISPATCH_QUEUE_CONCURRENT
que é um ponteiro para uma estrutura dispatch_queue_attr_t
que permite controlar alguns parâmetros da fila.
Existem vários objetos que o libdispatch usa e filas e blocos são apenas 2 deles. É possível criar esses objetos com dispatch_object_create
:
bloco
dados
: Blocos de dados
grupo
: Grupo de blocos
io
: Solicitações de E/S assíncronas
mach
: Portas Mach
mach_msg
: Mensagens Mach
pthread_root_queue
: Uma fila com um pool de threads pthread e sem workqueues
fila
semáforo
fonte
: Fonte de evento
Em Objetive-C existem diferentes funções para enviar um bloco para ser executado em paralelo:
dispatch_async: Submete um bloco para execução assíncrona em uma fila de despacho e retorna imediatamente.
dispatch_sync: Submete um objeto de bloco para execução e retorna após a conclusão desse bloco.
dispatch_once: Executa um objeto de bloco apenas uma vez durante a vida útil de um aplicativo.
dispatch_async_and_wait: Submete um item de trabalho para execução e retorna somente após a conclusão. Ao contrário de dispatch_sync
, esta função respeita todos os atributos da fila ao executar o bloco.
Essas funções esperam esses parâmetros: dispatch_queue_t
queue,
dispatch_block_t
block
Esta é a estrutura de um Bloco:
E este é um exemplo de como usar paralelismo com dispatch_async
:
A libswiftDispatch
é uma biblioteca que fornece ligações Swift para o framework Grand Central Dispatch (GCD) que é originalmente escrito em C.
A biblioteca libswiftDispatch
encapsula as APIs C do GCD em uma interface mais amigável ao Swift, tornando mais fácil e intuitivo para os desenvolvedores Swift trabalharem com o 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"))
Exemplo de código:
O seguinte script Frida pode ser usado para interceptar várias funções dispatch
e extrair o nome da fila, o rastreamento de pilha e o bloco: https://github.com/seemoo-lab/frida-scripts/blob/main/scripts/libdispatch.js
Atualmente, o Ghidra não entende nem a estrutura dispatch_block_t
do ObjectiveC, nem a swift_dispatch_block
.
Portanto, se você deseja que ele as entenda, você pode simplesmente declará-las:
Em seguida, encontre um local no código onde elas são usadas:
Observe todas as referências feitas a "block" para entender como você poderia descobrir que a estrutura está sendo usada.
Clique com o botão direito na variável -> Redefinir Tipo de Variável e selecione neste caso swift_dispatch_block
:
O Ghidra irá reescrever automaticamente tudo: