Skip to content

Instantly share code, notes, and snippets.

@iccir
Last active February 24, 2026 16:49
Show Gist options
  • Select an option

  • Save iccir/5ada6d3d9d325e8419e4b2f6e1498717 to your computer and use it in GitHub Desktop.

Select an option

Save iccir/5ada6d3d9d325e8419e4b2f6e1498717 to your computer and use it in GitHub Desktop.
Use EndpointSecurity to dump stack of newly launched process
/*
Use EndpointSecurity to dump stack from sp -> user_stack on all processes.
To use:
1) Compile with:
clang -arch arm64 -arch arm64e esdumpstack.c -lbsm -lEndpointSecurity -o esdumpstack
2) codesign with com.apple.developer.endpoint-security.client entitlement:
codesign -s - --entitlements esdumpstack.entitlements esdumpstack
3) Run as root:
sudo ./esdumpstack
*/
#include <EndpointSecurity/EndpointSecurity.h>
#include <bsm/libbsm.h>
#include <dispatch/dispatch.h>
#include <mach-o/dyld.h>
#include <string.h>
#include <libgen.h>
static void sLogError(char *format, ...) __printflike(1, 2);
static void sLogError(char *format, ...)
{
va_list v;
va_start(v, format);
vfprintf(stderr, format, v);
fprintf(stderr, "\n");
va_end(v);
}
static void sLogHexDump(vm_address_t address, const void *data, size_t size)
{
const unsigned char *bytes = (const unsigned char *)data;
__block size_t offset = 0;
size_t length = 1024;
char *line = alloca(length);
__auto_type append = ^(char *format, ...) {
if (offset > length) return;
va_list v;
va_start(v, format);
offset += vsnprintf(line + offset, length - offset, format, v);
va_end(v);
};
for (size_t i = 0; i < size; i += 16) {
offset = 0;
// Print offset in first column
append("%016lx ", address + i);
// Print hex bytes in middle area
for (size_t j = 0; j < 16; j++) {
if (i + j < size) {
append("%02x ", bytes[i + j]);
} else {
append(" ");
}
if (j == 7) {
append(" ");
}
}
// Print ASCII area
{
append(" |");
size_t j;
for (j = 0; j < 16 && i + j < size; j++) {
unsigned char c = bytes[i + j];
append("%c", (c >= 0x20 && c < 0x7f) ? c : '.');
}
for ( ; j < 16; j++) {
append(" ");
}
append("|");
}
fprintf(stdout, "%s\n", line);
}
}
static boolean_t sDumpStackForPid(pid_t pid)
{
boolean_t ok = true;
task_port_t task;
thread_act_array_t threads = NULL;
mach_msg_type_number_t threadCount = 0;
arm_thread_state64_t state;
thread_state_flavor_t flavor = ARM_THREAD_STATE64;
mach_msg_type_number_t stateCount = ARM_THREAD_STATE64_COUNT;
void *remoteBuffer = NULL;
vm_size_t remoteLength = 0;
#define Check(__CONDITION__, __FORMAT__, ...) \
if (!(__CONDITION__)) { sLogError(__FORMAT__, __VA_ARGS__); ok = false; goto cleanup; }
// Get task via task_for_pid()
{
kern_return_t err = task_for_pid(mach_task_self(), pid, &task);
Check(err == KERN_SUCCESS, "task_for_pid() failed, error = %ld", (long)err);
}
// Get threads/threadCount via task_threads()
{
kern_return_t err = task_threads(task, &threads, &threadCount);
Check(err == KERN_SUCCESS, "task_threads() failed, error = %ld", (long)err);
Check(threadCount == 1, "threadCount is %ld, expected 1", (long)threadCount);
}
// Fill state/stateCount via thread_get_state()
{
kern_return_t err = thread_get_state(threads[0], flavor, (thread_state_t)&state, &stateCount);
Check(err == KERN_SUCCESS, "thread_get_state() failed, error = %ld", (long)err);
}
// Convert state/stateCount via thread_convert_thread_state()
{
kern_return_t err = thread_convert_thread_state(
threads[0], THREAD_CONVERT_THREAD_STATE_TO_SELF, flavor,
(thread_state_t)&state, stateCount,
(thread_state_t)&state, &stateCount
);
Check(err == KERN_SUCCESS, "thread_convert_thread_state() failed, error = %ld", (long)err);
}
uintptr_t sp = arm_thread_state64_get_sp(state);
vm_address_t user_stack;
vm_address_t regionAddr = (vm_address_t)sp;
vm_size_t regionSize;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t objectName;
kern_return_t err;
err = vm_region_64(
task, &regionAddr, &regionSize,
VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &count,
&objectName
);
Check(err == KERN_SUCCESS, "vm_read() failed, error = %ld", (long)err);
user_stack = (regionAddr + regionSize);
mach_msg_type_number_t bytesToRead = (mach_msg_type_number_t)(user_stack - sp);
mach_msg_type_number_t bytesRead = bytesToRead;
err = vm_read(task, sp, bytesToRead, (vm_offset_t *)&remoteBuffer, &bytesRead);
remoteLength = bytesRead;
fprintf(stdout, "Memory from sp to end of region:\n");
sLogHexDump(sp, remoteBuffer, bytesRead);
Check(err == KERN_SUCCESS, "vm_read() failed, error = %ld", (long)err);
Check(bytesRead == bytesToRead, "vm_read() read wrong size of %u, expected %u", bytesRead, bytesToRead);
cleanup:
if (remoteBuffer) {
kern_return_t err = vm_deallocate(mach_task_self(), (vm_address_t)remoteBuffer, remoteLength);
if (err != KERN_SUCCESS) {
sLogError("vm_deallocate() failed, error = %ld", (long)err);
}
}
if (threads && threadCount > 0) {
for (size_t i = 0; i < threadCount; i++) {
kern_return_t err = mach_port_deallocate(mach_task_self(), threads[i]);
if (err != KERN_SUCCESS) {
sLogError("mach_port_deallocate(...threads[%ld]) failed, error = %ld", (long)i, (long)err);
}
}
{
kern_return_t err = vm_deallocate(mach_task_self(), (vm_address_t)threads, sizeof(*threads));
if (err != KERN_SUCCESS) {
sLogError("vm_deallocate() failed, error = %ld", (long)err);
}
}
}
return ok;
}
int main(int argc, char **argv, char **envp)
{
unsigned long err;
es_client_t *client = NULL;
err = es_new_client(&client, ^(es_client_t *client, const es_message_t *message) {
if (message->event_type != ES_EVENT_TYPE_AUTH_EXEC) {
sLogError("Received es_message_t of type: %u", message->event_type);
return;
}
pid_t pid = audit_token_to_pid(message->process->audit_token);
const char *name = message->event.exec.target->executable->path.data;
printf("--------------------------------------------------------------------------------------\n");
printf("%s (%ld)\n", basename((char *)name), (long)pid);
sDumpStackForPid(pid);
printf("\n");
es_respond_result_t err = es_respond_auth_result(client, message, ES_AUTH_RESULT_ALLOW, false);
if (err) {
sLogError("es_respond_auth_result() failed, error = %ld", (long)err);
}
});
if (err) {
sLogError("es_new_client() failed, error = %ld", (long)err);
return 1;
}
es_event_type_t events[] = { ES_EVENT_TYPE_AUTH_EXEC };
err = es_subscribe(client, events, sizeof(events) / sizeof(es_event_type_t));
if (err) {
sLogError("es_subscribe() failed, error = %ld", (long)err);
return 1;
}
dispatch_main();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
</dict>
</plist>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment