hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-files-folders-and-binaries/universal-binaries-and-mach-o-format.md
2024-02-11 01:46:25 +00:00

16 KiB

Uniwersalne pliki binarne macOS i format Mach-O

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

Inne sposoby wsparcia HackTricks:

Podstawowe informacje

Binarki systemu Mac OS zazwyczaj są kompilowane jako uniwersalne pliki binarne. Uniwersalny plik binarny może obsługiwać wiele architektur w tym samym pliku.

Te binarki mają strukturę Mach-O, która składa się z:

  • Nagłówek
  • Polecenia ładowania
  • Dane

https://alexdremov.me/content/images/2022/10/6XLCD.gif

Nagłówek Fat

Wyszukaj plik za pomocą polecenia: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"

#define FAT_MAGIC	0xcafebabe
#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC or FAT_MAGIC_64 */
	uint32_t	nfat_arch;	/* liczba struktur, które następują */
};

struct fat_arch {
cpu_type_t	cputype;	/* określacz CPU (int) */
cpu_subtype_t	cpusubtype;	/* określacz maszyny (int) */
uint32_t	offset;		/* przesunięcie pliku do tego pliku obiektowego */
uint32_t	size;		/* rozmiar tego pliku obiektowego */
uint32_t	align;		/* wyrównanie jako potęga liczby 2 */
};

Nagłówek zawiera bajty magiczne, a następnie liczbę architektur, które plik zawiera (nfat_arch), a każda architektura będzie miała strukturę fat_arch.

Sprawdź to za pomocą:

% file /bin/ls
/bin/ls: Mach-O uniwersalny plik binarny z 2 architekturami: [x86_64:Mach-O 64-bitowy plik wykonywalny x86_64] [arm64e:Mach-O 64-bitowy plik wykonywalny arm64e]
/bin/ls (dla architektury x86_64):	Mach-O 64-bitowy plik wykonywalny x86_64
/bin/ls (dla architektury arm64e):	Mach-O 64-bitowy plik wykonywalny arm64e

% otool -f -v /bin/ls
Nagłówki Fat
fat_magic FAT_MAGIC
nfat_arch 2
architektura x86_64
    cputype CPU_TYPE_X86_64
cpusubtype CPU_SUBTYPE_X86_64_ALL
capabilities 0x0
    przesunięcie 16384
    rozmiar 72896
    wyrównanie 2^14 (16384)
architektura arm64e
    cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64E
capabilities PTR_AUTH_VERSION USERSPACE 0
    przesunięcie 98304
    rozmiar 88816
    wyrównanie 2^14 (16384)

lub za pomocą narzędzia Mach-O View:

Jak możesz sobie wyobrazić, uniwersalny plik binarny skompilowany dla 2 architektur podwaja rozmiar w porównaniu do pliku skompilowanego tylko dla 1 architektury.

Nagłówek Mach-O

Nagłówek zawiera podstawowe informacje o pliku, takie jak bajty magiczne identyfikujące go jako plik Mach-O oraz informacje o docelowej architekturze. Możesz go znaleźć w: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"

#define	MH_MAGIC	0xfeedface	/* the mach magic number */
#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */
struct mach_header {
uint32_t	magic;		/* mach magic number identifier */
cpu_type_t	cputype;	/* cpu specifier (e.g. I386) */
cpu_subtype_t	cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file (usage and alignment for the file) */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
};

#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
struct mach_header_64 {
uint32_t	magic;		/* mach magic number identifier */
int32_t		cputype;	/* cpu specifier */
int32_t		cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
uint32_t	reserved;	/* reserved */
};

Typy plików:

  • MH_EXECUTE (0x2): Standardowy plik wykonywalny Mach-O
  • MH_DYLIB (0x6): Biblioteka dynamiczna Mach-O (np. .dylib)
  • MH_BUNDLE (0x8): Pakiet Mach-O (np. .bundle)
# Checking the mac header of a binary
otool -arch arm64e -hv /bin/ls
Mach header
magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64          E USR00     EXECUTE    19       1728   NOUNDEFS DYLDLINK TWOLEVEL PIE

Lub używając Mach-O View:

Polecenia ładowania Mach-O

Tutaj określona jest układ pliku w pamięci, szczegółowo opisujący lokalizację tabeli symboli, kontekst głównego wątku podczas rozpoczęcia wykonywania oraz wymagane biblioteki współdzielone. Instrukcje są dostarczane do dynamicznego ładowacza (dyld) w procesie ładowania binarnego do pamięci.

Używana jest struktura load_command, zdefiniowana w wspomnianym pliku loader.h:

struct load_command {
uint32_t cmd;           /* type of load command */
uint32_t cmdsize;       /* total size of command in bytes */
};

Istnieje około 50 różnych typów poleceń ładowania, które system obsługuje w inny sposób. Najczęściej spotykane to: LC_SEGMENT_64, LC_LOAD_DYLINKER, LC_MAIN, LC_LOAD_DYLIB i LC_CODE_SIGNATURE.

LC_SEGMENT/LC_SEGMENT_64

{% hint style="success" %} W zasadzie ten rodzaj polecenia ładowania definiuje, jak załadować segmenty __TEXT (kod wykonywalny) i __DATA (dane dla procesu) zgodnie z przesunięciami w sekcji danych podczas wykonywania binarnego pliku. {% endhint %}

Te polecenia definiują segmenty, które są mapowane do przestrzeni pamięci wirtualnej procesu podczas jego wykonywania.

Istnieją różne typy segmentów, takie jak segment __TEXT, który przechowuje kod wykonywalny programu, oraz segment __DATA, który zawiera dane używane przez proces. Te segmenty znajdują się w sekcji danych pliku Mach-O.

Każdy segment może być dalej podzielony na wiele sekcji. Struktura polecenia ładowania zawiera informacje na temat tych sekcji w odpowiednim segmencie.

W nagłówku najpierw znajduje się nagłówek segmentu:

struct segment_command_64 { /* dla architektur 64-bitowych */
uint32_t	cmd;		/* LC_SEGMENT_64 */
uint32_t	cmdsize;	/* zawiera sizeof section_64 structs */
char		segname[16];	/* nazwa segmentu */
uint64_t	vmaddr;		/* adres pamięci tego segmentu */
uint64_t	vmsize;		/* rozmiar pamięci tego segmentu */
uint64_t	fileoff;	/* przesunięcie pliku tego segmentu */
uint64_t	filesize;	/* ilość do zmapowania z pliku */
int32_t		maxprot;	/* maksymalna ochrona VM */
int32_t		initprot;	/* początkowa ochrona VM */
	uint32_t	nsects;		/* liczba sekcji w segmencie */
	uint32_t	flags;		/* flagi */
};

Przykład nagłówka segmentu:

Ten nagłówek definiuje liczbę sekcji, których nagłówki po nim się pojawiają:

struct section_64 { /* for 64-bit architectures */
char		sectname[16];	/* name of this section */
char		segname[16];	/* segment this section goes in */
uint64_t	addr;		/* memory address of this section */
uint64_t	size;		/* size in bytes of this section */
uint32_t	offset;		/* file offset of this section */
uint32_t	align;		/* section alignment (power of 2) */
uint32_t	reloff;		/* file offset of relocation entries */
uint32_t	nreloc;		/* number of relocation entries */
uint32_t	flags;		/* flags (section type and attributes)*/
uint32_t	reserved1;	/* reserved (for offset or index) */
uint32_t	reserved2;	/* reserved (for count or sizeof) */
uint32_t	reserved3;	/* reserved */
};

Przykład nagłówka sekcji:

Jeśli dodasz przesunięcie sekcji (0x37DC) + przesunięcie, gdzie arch zaczyna się, w tym przypadku 0x18000 --> 0x37DC + 0x18000 = 0x1B7DC

Można również uzyskać informacje o nagłówkach z wiersza poleceń za pomocą:

otool -lv /bin/ls

Wspólne segmenty ładowane przez to polecenie:

  • __PAGEZERO: Instruuje jądro, aby mapowało adres zero, więc nie można go odczytywać, zapisywać ani wykonywać. Zmienne maxprot i minprot w strukturze są ustawione na zero, co oznacza, że na tej stronie nie ma praw do odczytu-zapisu-wykonania.
  • Ta alokacja jest ważna w celu zmniejszenia podatności na odwołania do wskaźników NULL.
  • __TEXT: Zawiera wykonywalny kod z uprawnieniami do odczytu i wykonania (bez możliwości zapisu). Wspólne sekcje tego segmentu to:
  • __text: Skompilowany kod binarny
  • __const: Stałe dane
  • __cstring: Stałe ciągi znaków
  • __stubs i __stubs_helper: Zaangażowane w proces dynamicznego ładowania bibliotek
  • __DATA: Zawiera dane, które są odczytywalne i zapisywalne (bez możliwości wykonania).
  • __data: Zmienne globalne (które zostały zainicjalizowane)
  • __bss: Zmienne statyczne (które nie zostały zainicjalizowane)
  • __objc_* (__objc_classlist, __objc_protolist, itp.): Informacje używane przez środowisko uruchomieniowe Objective-C
  • __LINKEDIT: Zawiera informacje dla łącznika (dyld), takie jak "wpisy do tabel symboli, ciągów i relokacji".
  • __OBJC: Zawiera informacje używane przez środowisko uruchomieniowe Objective-C. Choć te informacje mogą być również znalezione w segmencie __DATA, w różnych sekcjach __objc_*.

LC_MAIN

Zawiera punkt wejścia w atrybucie entryoff. Podczas ładowania, dyld po prostu dodaje tę wartość do (w pamięci) bazowego adresu binarnego, a następnie przechodzi do tej instrukcji, aby rozpocząć wykonywanie kodu binarnego.

LC_CODE_SIGNATURE

Zawiera informacje na temat podpisu kodu pliku Mach-O. Zawiera tylko przesunięcie, które wskazuje na blok podpisu. Zazwyczaj znajduje się na samym końcu pliku.
Jednak można znaleźć pewne informacje na temat tej sekcji w tym wpisie na blogu i tym gists.

LC_LOAD_DYLINKER

Zawiera ścieżkę do dynamicznego łącznika wykonywalnego, który mapuje biblioteki współdzielone do przestrzeni adresowej procesu. Wartość zawsze jest ustawiona na /usr/lib/dyld. Ważne jest zauważenie, że w macOS mapowanie dylibów odbywa się w trybie użytkownika, a nie w trybie jądra.

LC_LOAD_DYLIB

To polecenie ładowania opisuje zależność od dynamicznej biblioteki, której ładowanie i połączenie jest instruowane przez ładowacz (dyld). Istnieje polecenie ładowania LC_LOAD_DYLIB dla każdej biblioteki, którą wymaga plik Mach-O.

  • To polecenie ładowania jest strukturą typu dylib_command (która zawiera strukturę dylib, opisującą właściwą zależną dynamiczną bibliotekę):
struct dylib_command {
uint32_t        cmd;            /* LC_LOAD_{,WEAK_}DYLIB */
uint32_t        cmdsize;        /* includes pathname string */
struct dylib    dylib;          /* the library identification */
};

struct dylib {
union lc_str  name;                 /* library's path name */
uint32_t timestamp;                 /* library's build time stamp */
uint32_t current_version;           /* library's current version number */
uint32_t compatibility_version;     /* library's compatibility vers number*/
};

Możesz również uzyskać te informacje za pomocą wiersza poleceń, wpisując:

otool -L /bin/ls
/bin/ls:
/usr/lib/libutil.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

Niektóre potencjalnie złośliwe biblioteki to:

  • DiskArbitration: Monitorowanie dysków USB
  • AVFoundation: Przechwytywanie dźwięku i obrazu
  • CoreWLAN: Skanowanie sieci Wi-Fi.

{% hint style="info" %} Plik Mach-O może zawierać jeden lub więcej konstruktorów, które zostaną wykonane przed adresem określonym w LC_MAIN.
Przesunięcia dowolnych konstruktorów są przechowywane w sekcji __mod_init_func segmentu __DATA_CONST. {% endhint %}

Dane Mach-O

W centrum pliku znajduje się region danych, który składa się z kilku segmentów zdefiniowanych w regionie komend ładowania. W każdym segmencie może znajdować się wiele sekcji danych, z których każda sekcja zawiera kod lub dane specyficzne dla danego typu.

{% hint style="success" %} Dane to w zasadzie część zawierająca wszystkie informacje, które są ładowane przez komendy ładowania LC_SEGMENTS_64 {% endhint %}

https://www.oreilly.com/api/v2/epubs/9781785883378/files/graphics/B05055_02_38.jpg

Obejmuje to:

  • Tabela funkcji: Która zawiera informacje o funkcjach programu.
  • Tabela symboli: Która zawiera informacje o zewnętrznych funkcjach używanych przez plik binarny.
  • Może również zawierać wewnętrzne funkcje, nazwy zmiennych i wiele innych.

Aby to sprawdzić, można użyć narzędzia Mach-O View:

Lub z wiersza poleceń:

size -m /bin/ls
Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks: