12 KiB
macOS GCD - Grand Central Dispatch
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!
Inne sposoby wsparcia HackTricks:
- Jeśli chcesz zobaczyć swoją firmę reklamowaną w HackTricks lub pobrać HackTricks w formacie PDF, sprawdź PLANY SUBSKRYPCYJNE!
- Zdobądź oficjalne gadżety PEASS & HackTricks
- Odkryj Rodzinę PEASS, naszą kolekcję ekskluzywnych NFT
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @carlospolopm.
- Podziel się swoimi sztuczkami hakowania, przesyłając PR-y do HackTricks i HackTricks Cloud na GitHubie.
Podstawowe informacje
Grand Central Dispatch (GCD), znany również jako libdispatch (libdispatch.dyld
), jest dostępny zarówno w macOS, jak i iOS. Jest to technologia opracowana przez Apple do optymalizacji wsparcia aplikacji dla równoczesnego (wielowątkowego) wykonywania na sprzęcie wielordzeniowym.
GCD dostarcza i zarządza kolejkami FIFO, do których twoja aplikacja może przesyłać zadania w postaci bloków kodu. Bloki przesłane do kolejek dystrybucji są wykonywane na puli wątków w pełni zarządzanej przez system. GCD automatycznie tworzy wątki do wykonywania zadań w kolejkach dystrybucji i harmonogramuje te zadania do uruchomienia na dostępnych rdzeniach.
{% hint style="success" %} Podsumowując, aby wykonać kod równolegle, procesy mogą wysyłać bloki kodu do GCD, który zajmie się ich wykonaniem. Dlatego procesy nie tworzą nowych wątków; GCD wykonuje dany kod za pomocą własnej puli wątków (która może się zwiększać lub zmniejszać w miarę potrzeby). {% endhint %}
Jest to bardzo pomocne do skutecznego zarządzania równoczesnym wykonywaniem, znacznie zmniejszając liczbę wątków, które tworzą procesy, i optymalizując równoległe wykonanie. Jest to idealne rozwiązanie dla zadań wymagających dużej równoległości (łamanie haseł?) lub dla zadań, które nie powinny blokować głównego wątku: Na przykład główny wątek w iOS obsługuje interakcje z interfejsem użytkownika, więc wszelkie inne funkcje, które mogą spowodować zawieszenie aplikacji (wyszukiwanie, dostęp do sieci, odczyt pliku...) są obsługiwane w ten sposób.
Bloki
Blokiem jest samodzielny fragment kodu (podobny do funkcji z argumentami zwracającymi wartość) i może również określić zmienne związane.
Jednak na poziomie kompilatora bloki nie istnieją, są to os_object
s. Każdy z tych obiektów składa się z dwóch struktur:
- blok literałowy:
- Rozpoczyna się od pola
isa
, wskazującego na klasę bloku: NSConcreteGlobalBlock
(bloki z__DATA.__const
)NSConcreteMallocBlock
(bloki na stercie)NSConcreateStackBlock
(bloki na stosie)- Posiada
flags
(wskazujące na pola obecne w deskryptorze bloku) oraz kilka zarezerwowanych bajtów - Wskaźnik do funkcji do wywołania
- Wskaźnik do deskryptora bloku
- Zaimportowane zmienne bloku (jeśli takie istnieją)
- deskryptor bloku: Jego rozmiar zależy od danych, które są obecne (zgodnie z flagami podanymi wcześniej)
- Posiada kilka zarezerwowanych bajtów
- Jego rozmiar
- Zazwyczaj będzie miał wskaźnik do sygnatury w stylu Objective-C, aby wiedzieć, ile miejsca jest potrzebne na parametry (flaga
BLOCK_HAS_SIGNATURE
) - Jeśli zmienne są odwoływane, ten blok będzie również zawierał wskaźniki do pomocnika kopiującego (kopiującego wartość na początku) i pomocnika usuwającego (zwalniającego ją).
Kolejki
Kolejka dystrybucji to nazwany obiekt zapewniający kolejność FIFO bloków do wykonania.
Bloki są umieszczane w kolejkach do wykonania, a te obsługują 2 tryby: DISPATCH_QUEUE_SERIAL
i DISPATCH_QUEUE_CONCURRENT
. Oczywiście kolejka szeregowa nie będzie miała problemów z warunkami wyścigowymi, ponieważ blok nie zostanie wykonany, dopóki poprzedni nie zakończy działania. Ale inny typ kolejki może je mieć.
Kolejki domyślne:
.main-thread
: Zdispatch_get_main_queue()
.libdispatch-manager
: Menedżer kolejek GCD.root.libdispatch-manager
: Menedżer kolejek GCD.root.maintenance-qos
: Zadania o najniższym priorytecie.root.maintenance-qos.overcommit
.root.background-qos
: Dostępne jakoDISPATCH_QUEUE_PRIORITY_BACKGROUND
.root.background-qos.overcommit
.root.utility-qos
: Dostępne jakoDISPATCH_QUEUE_PRIORITY_NON_INTERACTIVE
.root.utility-qos.overcommit
.root.default-qos
: Dostępne jakoDISPATCH_QUEUE_PRIORITY_DEFAULT
.root.background-qos.overcommit
.root.user-initiated-qos
: Dostępne jakoDISPATCH_QUEUE_PRIORITY_HIGH
.root.background-qos.overcommit
.root.user-interactive-qos
: Najwyższy priorytet.root.background-qos.overcommit
Zauważ, że to system decyduje, które wątki obsługują które kolejki w danym momencie (wiele wątków może pracować w tej samej kolejce lub ten sam wątek może pracować w różnych kolejkach w pewnym momencie)
Atrybuty
Podczas tworzenia kolejki za pomocą dispatch_queue_create
trzeci argument to dispatch_queue_attr_t
, który zazwyczaj jest albo DISPATCH_QUEUE_SERIAL
(który jest właściwie NULL), albo DISPATCH_QUEUE_CONCURRENT
, który jest wskaźnikiem do struktury dispatch_queue_attr_t
, która pozwala kontrolować niektóre parametry kolejki.
Obiekty dystrybucji
Istnieje kilka obiektów, których używa libdispatch, a kolejki i bloki to tylko 2 z nich. Można tworzyć te obiekty za pomocą dispatch_object_create
:
block
data
: Bloki danychgroup
: Grupa blokówio
: Asynchroniczne żądania wejścia/wyjściamach
: Porty Machmach_msg
: Komunikaty Machpthread_root_queue
: Kolejka z pulą wątków pthread, a nie kolejkami pracyqueue
semaphore
source
: Źródło zdarzeń
Objective-C
W Objective-C istnieją różne funkcje do wysyłania bloku do wykonania równoległego:
- dispatch_async: Przesyła blok do asynchronicznego wykonania w kolejce dystrybucji i natychmiast zwraca.
- dispatch_sync: Przesyła obiekt bloku do wykonania i zwraca po zakończeniu tego bloku.
- dispatch_once: Wykonuje blok tylko raz przez cały czas życia aplikacji.
- dispatch_async_and_wait: Przesyła element roboczy do wykonania i zwraca dopiero po zakończeniu jego wykonania. W przeciwieństwie do
dispatch_sync
, ta funkcja respektuje wszystkie atrybuty kolejki podczas wykonywania bloku.
Te funkcje oczekują tych parametrów: dispatch_queue_t
queue,
dispatch_block_t
block
Oto struktura Bloku:
struct Block {
void *isa; // NSConcreteStackBlock,...
int flags;
int reserved;
void *invoke;
struct BlockDescriptor *descriptor;
// captured variables go here
};
A to przykład użycia równoległości z dispatch_async
:
#import <Foundation/Foundation.h>
// Define a block
void (^backgroundTask)(void) = ^{
// Code to be executed in the background
for (int i = 0; i < 10; i++) {
NSLog(@"Background task %d", i);
sleep(1); // Simulate a long-running task
}
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Create a dispatch queue
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.example.backgroundQueue", NULL);
// Submit the block to the queue for asynchronous execution
dispatch_async(backgroundQueue, backgroundTask);
// Continue with other work on the main queue or thread
for (int i = 0; i < 10; i++) {
NSLog(@"Main task %d", i);
sleep(1); // Simulate a long-running task
}
}
return 0;
}
Swift
libswiftDispatch
to biblioteka zapewniająca powiązania Swift do frameworka Grand Central Dispatch (GCD), który jest pierwotnie napisany w języku C.
Biblioteka libswiftDispatch
owija interfejsy API C GCD w bardziej przyjazny dla Swifta interfejs, ułatwiając i bardziej intuicyjnie dla programistów Swifta pracować z 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"))
Przykład kodu:
import Foundation
// Define a closure (the Swift equivalent of a block)
let backgroundTask: () -> Void = {
for i in 0..<10 {
print("Background task \(i)")
sleep(1) // Simulate a long-running task
}
}
// Entry point
autoreleasepool {
// Create a dispatch queue
let backgroundQueue = DispatchQueue(label: "com.example.backgroundQueue")
// Submit the closure to the queue 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) // Simulate a long-running task
}
}
Frida
Następujący skrypt Frida może być użyty do hookowania kilku funkcji dispatch
i wydobycia nazwy kolejki, śladu stosu i bloku: https://github.com/seemoo-lab/frida-scripts/blob/main/scripts/libdispatch.js
frida -U <prog_name> -l libdispatch.js
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:]
[...]
Ghidra
Obecnie Ghidra nie rozumie ani struktury dispatch_block_t
ObjectiveC, ani struktury swift_dispatch_block
.
Więc jeśli chcesz, aby je zrozumiał, po prostu możesz je zadeklarować:
Następnie znajdź miejsce w kodzie, gdzie są używane:
{% hint style="success" %} Zauważ wszystkie odniesienia do "block", aby zrozumieć, jak możesz ustalić, że struktura jest używana. {% endhint %}
Kliknij prawym przyciskiem na zmienną -> Zmień typ zmiennej i wybierz w tym przypadku swift_dispatch_block
:
Ghidra automatycznie przepisze wszystko: