Scott Knight

### Tags

In the previous post we looked at different ways to inject code into tasks on macOS. The goal being to create increased awareness of the type of methods attackers writing malicious code on macOS might use. In this post I wanted to focus in on the same issue of code injection but from a defenders point of view.

While starting this investigation I happened to notice the following information on some of the crash reports that macOS generates.

External Modification Summary:
Calls made by other processes targeting this process:
Calls made by this process:
Calls made by all processes on this machine:


The Calls made by other processes targeting this process looked interesting and seemed potentially related to what I was trying to figure out. If we could get this same information then we could potentially detect when an outside process has attempted to inject or hijack a thread. I started by searching my system to see what generated this section of the crash report. I used ripgrep to search for anything mentioning the text above:

rg -a -l -M 80 "Calls made by other processes targeting this process" 2>/dev/null /System/Library/CoreServices/ReportCrash  Opening ReportCrash in Hopper and locating the reference to the string took me to the CrashReport Objective-C class which has a method called _extractExternalMods. This method starts out and does the following: 0000000100009a85 lea rcx, qword [rbp+var_74] 0000000100009a89 mov dword [rcx], 0x10 ; argument "task_info_outCnt" for method imp___stubs__task_info 0000000100009a8f mov rax, qword [objc_ivar_offset_CrashReport__task] ; objc_ivar_offset_CrashReport__task 0000000100009a96 mov edi, dword [rdi+rax] ; argument "target_task" for method imp___stubs__task_info 0000000100009a99 lea rdx, qword [rbp+var_70] ; argument "task_info_out" for method imp___stubs__task_info 0000000100009a9d mov esi, 0x13 ; argument "flavor" for method imp___stubs__task_info 0000000100009aa2 call imp___stubs__task_info ; task_info 0000000100009aa7 test eax, eax 0000000100009aa9 jne loc_100009d4f  Looking at the XNU source code to see what flavor 0x13 is, we see that this is the TASK_EXTMOD_INFO constant. It looks like this information has been available since macOS 10.7 in xnu-1699.22.73. Translating this code to C we see that it’s doing the following: struct task_extmod_info *info; mach_msg_type_number_t count = TASK_EXTMOD_INFO_COUNT; kern_return_t kr; kr = task_info(self.task, TASK_EXTMOD_INFO, (task_info_t)info, &count);  If we can get a reference to a task port we can call task_info passing in TASK_EXTMOD_INFO and get back out the following information: struct vm_extmod_statistics { int64_t task_for_pid_count; /* # of times task port was looked up */ int64_t task_for_pid_caller_count; /* # of times this task called task_for_pid */ int64_t thread_creation_count; /* # of threads created in task */ int64_t thread_creation_caller_count; /* # of threads created by task */ int64_t thread_set_state_count; /* # of register state sets in task */ int64_t thread_set_state_caller_count; /* # of register state sets by task */ } __attribute__((aligned(8))); typedef struct vm_extmod_statistics vm_extmod_statistics_data_t; struct task_extmod_info { unsigned char task_uuid[16]; vm_extmod_statistics_data_t extmod_statistics; };  If thread_creation_count or thread_set_state_count is greater than zero it means an external user task has modified the task we’re inspecting. Since we need to pass a task reference into task_info you might be thinking that our own program won’t be able to call into this function. (Apple goes to great lengths to lock down task_for_pid). If you look at how the MIG call for task_info works you’ll notice the following logic: kern_return_t task_info_from_user( mach_port_t task_port, task_flavor_t flavor, task_info_t task_info_out, mach_msg_type_number_t *task_info_count) { task_t task; kern_return_t ret; if (flavor == TASK_DYLD_INFO) task = convert_port_to_task(task_port); else task = convert_port_to_task_name(task_port); ret = task_info(task, flavor, task_info_out, task_info_count); task_deallocate(task); return ret; }  Unless we’re passing in a flavor of TASK_DYLD_INFO we don’t need a full task port. We only need to have a task name port. As a regular user we can retrieve a task name port using task_name_for_pid. There is one drawback with task_name_for_pid though, it requires being called from a privileged process or a processes with the same user ID. So if we put all these pieces together along with a call to proc_listallpids we come up with the following code: If you call this code as a regular user it will inspect all processes running as that user. If you call the code with sudo then it will inspect all processes running on the machine. Here’s an example of injecting a dylib into Slack and then using the code above to detect the modifications.  sudo ./inject 1634 libinjectme.dylib
Allocated remote stack @0x10b1e0000
$./psx PID: 1634 Name: Slack External Modification Summary: Calls made by other processes targeting this process: task_for_pid: 397 thread_create: 1 thread_set_state: 0  Alternatively, looking through the XNU source code, creating remote threads in another process does get logged. You can search the logs for thread injection with the following command: $ syslog -F raw -d /var/log/DiagnosticMessages | grep "com.apple.kernel.external_modification"