Last active
February 24, 2026 16:49
-
-
Save iccir/5ada6d3d9d325e8419e4b2f6e1498717 to your computer and use it in GitHub Desktop.
Use EndpointSecurity to dump stack of newly launched process
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| 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, ®ionAddr, ®ionSize, | |
| 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(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?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