macOS GCD - Grand Central Dispatch

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

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.

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).

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_objects. 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: Z dispatch_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 jako DISPATCH_QUEUE_PRIORITY_BACKGROUND

  • .root.background-qos.overcommit

  • .root.utility-qos: Dostępne jako DISPATCH_QUEUE_PRIORITY_NON_INTERACTIVE

  • .root.utility-qos.overcommit

  • .root.default-qos: Dostępne jako DISPATCH_QUEUE_PRIORITY_DEFAULT

  • .root.background-qos.overcommit

  • .root.user-initiated-qos: Dostępne jako DISPATCH_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 danych

  • group: Grupa bloków

  • io: Asynchroniczne żądania wejścia/wyjścia

  • mach: Porty Mach

  • mach_msg: Komunikaty Mach

  • pthread_root_queue: Kolejka z pulą wątków pthread, a nie kolejkami pracy

  • queue

  • 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:

Zauważ wszystkie odniesienia do "block", aby zrozumieć, jak możesz ustalić, że struktura jest używana.

Kliknij prawym przyciskiem na zmienną -> Zmień typ zmiennej i wybierz w tym przypadku swift_dispatch_block:

Ghidra automatycznie przepisze wszystko:

References

Last updated