Skip to content

Commit 0686ca8

Browse files
authored
Merge pull request #1171 from HackTricks-wiki/research_update_src_macos-hardening_macos-security-and-privilege-escalation_macos-proces-abuse_macos-ipc-inter-process-communication_macos-thread-injection-via-task-port_20250722_162611
Research Update Enhanced src/macos-hardening/macos-security-...
2 parents 73ef9d5 + 21e8b7a commit 0686ca8

File tree

1 file changed

+77
-66
lines changed

1 file changed

+77
-66
lines changed

src/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md

Lines changed: 77 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99

1010
## 1. Thread Hijacking
1111

12-
Initially, the **`task_threads()`** function is invoked on the task port to obtain a thread list from the remote task. A thread is selected for hijacking. This approach diverges from conventional code injection methods as creating a new remote thread is prohibited due to the new mitigation blocking `thread_create_running()`.
12+
Initially, the `task_threads()` function is invoked on the task port to obtain a thread list from the remote task. A thread is selected for hijacking. This approach diverges from conventional code-injection methods as creating a new remote thread is prohibited due to the mitigation that blocks `thread_create_running()`.
1313

14-
To control the thread, **`thread_suspend()`** is called, halting its execution.
14+
To control the thread, `thread_suspend()` is called, halting its execution.
1515

16-
The only operations permitted on the remote thread involve **stopping** and **starting** it, **retrieving** and **modifying** its register values. Remote function calls are initiated by setting registers `x0` to `x7` to the **arguments**, configuring **`pc`** to target the desired function, and activating the thread. Ensuring the thread does not crash after the return necessitates detection of the return.
16+
The only operations permitted on the remote thread involve **stopping** and **starting** it and **retrieving**/**modifying** its register values. Remote function calls are initiated by setting registers `x0` to `x7` to the **arguments**, configuring `pc` to target the desired function, and resuming the thread. Ensuring the thread does not crash after the return necessitates detection of the return.
1717

18-
One strategy involves **registering an exception handler** for the remote thread using `thread_set_exception_ports()`, setting the `lr` register to an invalid address before the function call. This triggers an exception post-function execution, sending a message to the exception port, enabling state inspection of the thread to recover the return value. Alternatively, as adopted from Ian Beer’s triple_fetch exploit, `lr` is set to loop infinitely. The thread's registers are then continuously monitored until **`pc` points to that instruction**.
18+
One strategy involves registering an **exception handler** for the remote thread using `thread_set_exception_ports()`, setting the `lr` register to an invalid address before the function call. This triggers an exception post-function execution, sending a message to the exception port, enabling state inspection of the thread to recover the return value. Alternatively, as adopted from Ian Beer’s *triple_fetch* exploit, `lr` is set to loop infinitely; the threads registers are then continuously monitored until `pc` points to that instruction.
1919

2020
## 2. Mach ports for communication
2121

22-
The subsequent phase involves establishing Mach ports to facilitate communication with the remote thread. These ports are instrumental in transferring arbitrary send and receive rights between tasks.
22+
The subsequent phase involves establishing Mach ports to facilitate communication with the remote thread. These ports are instrumental in transferring arbitrary send/receive rights between tasks.
2323

2424
For bidirectional communication, two Mach receive rights are created: one in the local and the other in the remote task. Subsequently, a send right for each port is transferred to the counterpart task, enabling message exchange.
2525

@@ -33,27 +33,27 @@ Completion of these steps results in the establishment of Mach ports, laying the
3333

3434
## 3. Basic Memory Read/Write Primitives
3535

36-
In this section, the focus is on utilizing the execute primitive to establish basic memory read and write primitives. These initial steps are crucial for gaining more control over the remote process, though the primitives at this stage won't serve many purposes. Soon, they will be upgraded to more advanced versions.
36+
In this section, the focus is on utilizing the execute primitive to establish basic memory read/write primitives. These initial steps are crucial for gaining more control over the remote process, though the primitives at this stage won't serve many purposes. Soon, they will be upgraded to more advanced versions.
3737

38-
### Memory Reading and Writing Using Execute Primitive
38+
### Memory reading and writing using the execute primitive
3939

40-
The goal is to perform memory reading and writing using specific functions. For reading memory, functions resembling the following structure are used:
40+
The goal is to perform memory reading and writing using specific functions. For **reading memory**:
4141

4242
```c
4343
uint64_t read_func(uint64_t *address) {
4444
return *address;
4545
}
4646
```
4747
48-
And for writing to memory, functions similar to this structure are used:
48+
For **writing memory**:
4949
5050
```c
5151
void write_func(uint64_t *address, uint64_t value) {
5252
*address = value;
5353
}
5454
```
5555

56-
These functions correspond to the given assembly instructions:
56+
These functions correspond to the following assembly:
5757

5858
```
5959
_read_func:
@@ -64,117 +64,128 @@ _write_func:
6464
ret
6565
```
6666

67-
### Identifying Suitable Functions
67+
### Identifying suitable functions
6868

6969
A scan of common libraries revealed appropriate candidates for these operations:
7070

71-
1. **Reading Memory:**
72-
The `property_getName()` function from the [Objective-C runtime library](https://opensource.apple.com/source/objc4/objc4-723/runtime/objc-runtime-new.mm.auto.html) is identified as a suitable function for reading memory. The function is outlined below:
71+
1. **Reading memory — `property_getName()`** (libobjc):
7372

7473
```c
7574
const char *property_getName(objc_property_t prop) {
76-
return prop->name;
75+
return prop->name;
7776
}
7877
```
7978
80-
This function effectively acts like the `read_func` by returning the first field of `objc_property_t`.
81-
82-
2. **Writing Memory:**
83-
Finding a pre-built function for writing memory is more challenging. However, the `_xpc_int64_set_value()` function from libxpc is a suitable candidate with the following disassembly:
79+
2. **Writing memory — `_xpc_int64_set_value()`** (libxpc):
8480
8581
```c
8682
__xpc_int64_set_value:
8783
str x1, [x0, #0x18]
8884
ret
8985
```
9086

91-
To perform a 64-bit write at a specific address, the remote call is structured as:
87+
To perform a 64-bit write at an arbitrary address:
9288

9389
```c
94-
_xpc_int64_set_value(address - 0x18, value)
90+
_xpc_int64_set_value(address - 0x18, value);
9591
```
9692
9793
With these primitives established, the stage is set for creating shared memory, marking a significant progression in controlling the remote process.
9894
9995
## 4. Shared Memory Setup
10096
101-
The objective is to establish shared memory between local and remote tasks, simplifying data transfer and facilitating the calling of functions with multiple arguments. The approach involves leveraging `libxpc` and its `OS_xpc_shmem` object type, which is built upon Mach memory entries.
102-
103-
### Process Overview:
97+
The objective is to establish shared memory between local and remote tasks, simplifying data transfer and facilitating the calling of functions with multiple arguments. The approach leverages `libxpc` and its `OS_xpc_shmem` object type, which is built upon Mach memory entries.
10498
105-
1. **Memory Allocation**:
99+
### Process overview
106100
107-
- Allocate the memory for sharing using `mach_vm_allocate()`.
108-
- Use `xpc_shmem_create()` to create an `OS_xpc_shmem` object for the allocated memory region. This function will manage the creation of the Mach memory entry and store the Mach send right at offset `0x18` of the `OS_xpc_shmem` object.
101+
1. **Memory allocation**
102+
* Allocate memory for sharing using `mach_vm_allocate()`.
103+
* Use `xpc_shmem_create()` to create an `OS_xpc_shmem` object for the allocated region.
104+
2. **Creating shared memory in the remote process**
105+
* Allocate memory for the `OS_xpc_shmem` object in the remote process (`remote_malloc`).
106+
* Copy the local template object; fix-up of the embedded Mach send right at offset `0x18` is still required.
107+
3. **Correcting the Mach memory entry**
108+
* Insert a send right with `thread_set_special_port()` and overwrite the `0x18` field with the remote entry’s name.
109+
4. **Finalising**
110+
* Validate the remote object and map it with a remote call to `xpc_shmem_remote()`.
109111
110-
2. **Creating Shared Memory in Remote Process**:
112+
## 5. Achieving Full Control
111113
112-
- Allocate memory for the `OS_xpc_shmem` object in the remote process with a remote call to `malloc()`.
113-
- Copy the contents of the local `OS_xpc_shmem` object to the remote process. However, this initial copy will have incorrect Mach memory entry names at offset `0x18`.
114+
Once arbitrary execution and a shared-memory back-channel are available you effectively own the target process:
114115
115-
3. **Correcting the Mach Memory Entry**:
116+
* **Arbitrary memory R/W** — use `memcpy()` between local & shared regions.
117+
* **Function calls with > 8 args** — place the extra arguments on the stack following the arm64 calling convention.
118+
* **Mach port transfer** — pass rights in Mach messages via the established ports.
119+
* **File-descriptor transfer** — leverage fileports (see *triple_fetch*).
116120
117-
- Utilize the `thread_set_special_port()` method to insert a send right for the Mach memory entry into the remote task.
118-
- Correct the Mach memory entry field at offset `0x18` by overwriting it with the remote memory entry's name.
121+
All of this is wrapped in the [`threadexec`](https://github.com/bazad/threadexec) library for easy re-use.
119122
120-
4. **Finalizing Shared Memory Setup**:
121-
- Validate the remote `OS_xpc_shmem` object.
122-
- Establish the shared memory mapping with a remote call to `xpc_shmem_remote()`.
123+
---
123124
124-
By following these steps, shared memory between the local and remote tasks will be efficiently set up, allowing for straightforward data transfers and the execution of functions requiring multiple arguments.
125+
## 6. Apple Silicon (arm64e) Nuances
125126
126-
## Additional Code Snippets
127+
On Apple Silicon devices (arm64e) **Pointer Authentication Codes (PAC)** protect all return addresses and many function pointers. Thread-hijacking techniques that *reuse existing code* continue to work because the original values in `lr`/`pc` already carry valid PAC signatures. Problems arise when you try to jump to attacker-controlled memory:
127128
128-
For memory allocation and shared memory object creation:
129+
1. Allocate executable memory inside the target (remote `mach_vm_allocate` + `mprotect(PROT_EXEC)`).
130+
2. Copy your payload.
131+
3. Inside the *remote* process sign the pointer:
129132
130133
```c
131-
mach_vm_allocate();
132-
xpc_shmem_create();
134+
uint64_t ptr = (uint64_t)payload;
135+
ptr = ptrauth_sign_unauthenticated((void*)ptr, ptrauth_key_asia, 0);
133136
```
134137

135-
For creating and correcting the shared memory object in the remote process:
138+
4. Set `pc = ptr` in the hijacked thread state.
136139

137-
```c
138-
malloc(); // for allocating memory remotely
139-
thread_set_special_port(); // for inserting send right
140-
```
141-
142-
Remember to handle the details of Mach ports and memory entry names correctly to ensure that the shared memory setup functions properly.
140+
Alternatively, stay PAC-compliant by chaining existing gadgets/functions (traditional ROP).
143141

144-
## 5. Achieving Full Control
142+
## 7. Detection & Hardening with EndpointSecurity
145143

146-
Upon successfully establishing shared memory and gaining arbitrary execution capabilities, we have essentially gained full control over the target process. The key functionalities enabling this control are:
144+
The **EndpointSecurity (ES)** framework exposes kernel events that allow defenders to observe or block thread-injection attempts:
147145

148-
1. **Arbitrary Memory Operations**:
146+
* `ES_EVENT_TYPE_AUTH_GET_TASK` – fired when a process requests another task’s port (e.g. `task_for_pid()`).
147+
* `ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE` – emitted whenever a thread is created in a *different* task.
148+
* `ES_EVENT_TYPE_NOTIFY_THREAD_SET_STATE` (added in macOS 14 Sonoma) – indicates register manipulation of an existing thread.
149149

150-
- Perform arbitrary memory reads by invoking `memcpy()` to copy data from the shared region.
151-
- Execute arbitrary memory writes by using `memcpy()` to transfer data to the shared region.
150+
Minimal Swift client that prints remote-thread events:
152151

153-
2. **Handling Function Calls with Multiple Arguments**:
152+
```swift
153+
import EndpointSecurity
154154

155-
- For functions requiring more than 8 arguments, arrange the additional arguments on the stack in compliance with the calling convention.
155+
let client = try! ESClient(subscriptions: [.notifyRemoteThreadCreate]) {
156+
(_, msg) in
157+
if let evt = msg.remoteThreadCreate {
158+
print("[ALERT] remote thread in pid \(evt.target.pid) by pid \(evt.thread.pid)")
159+
}
160+
}
161+
RunLoop.main.run()
162+
```
156163

157-
3. **Mach Port Transfer**:
164+
Querying with **osquery** ≥ 5.8:
158165

159-
- Transfer Mach ports between tasks through Mach messages via previously established ports.
166+
```sql
167+
SELECT target_pid, source_pid, target_path
168+
FROM es_process_events
169+
WHERE event_type = 'REMOTE_THREAD_CREATE';
170+
```
160171

161-
4. **File Descriptor Transfer**:
162-
- Transfer file descriptors between processes using fileports, a technique highlighted by Ian Beer in `triple_fetch`.
172+
### Hardened-runtime considerations
163173

164-
This comprehensive control is encapsulated within the [threadexec](https://github.com/bazad/threadexec) library, providing a detailed implementation and a user-friendly API for interaction with the victim process.
174+
Distributing your application **without** the `com.apple.security.get-task-allow` entitlement prevents non-root attackers from obtaining its task-port. System Integrity Protection (SIP) still blocks access to many Apple binaries, but third-party software must opt-out explicitly.
165175

166-
## Important Considerations:
176+
## 8. Recent Public Tooling (2023-2025)
167177

168-
- Ensure proper use of `memcpy()` for memory read/write operations to maintain system stability and data integrity.
169-
- When transferring Mach ports or file descriptors, follow proper protocols and handle resources responsibly to prevent leaks or unintended access.
178+
| Tool | Year | Remarks |
179+
|------|------|---------|
180+
| [`task_vaccine`](https://github.com/rodionovd/task_vaccine) | 2023 | Compact PoC that demonstrates PAC-aware thread hijacking on Ventura/Sonoma |
181+
| `remote_thread_es` | 2024 | EndpointSecurity helper used by several EDR vendors to surface `REMOTE_THREAD_CREATE` events |
170182

171-
By adhering to these guidelines and utilizing the `threadexec` library, one can efficiently manage and interact with processes at a granular level, achieving full control over the target process.
183+
> Reading these projects’ source code is useful to understand API changes introduced in macOS 13/14 and to stay compatible across Intel ↔ Apple Silicon.
172184
173185
## References
174186

175187
- [https://bazad.github.io/2018/10/bypassing-platform-binary-task-threads/](https://bazad.github.io/2018/10/bypassing-platform-binary-task-threads/)
188+
- [https://github.com/rodionovd/task_vaccine](https://github.com/rodionovd/task_vaccine)
189+
- [https://developer.apple.com/documentation/endpointsecurity/es_event_type_notify_remote_thread_create](https://developer.apple.com/documentation/endpointsecurity/es_event_type_notify_remote_thread_create)
176190

177191
{{#include ../../../../banners/hacktricks-training.md}}
178-
179-
180-

0 commit comments

Comments
 (0)