Ivan's blog

System and network musings

Mac OS X and Task_for_pid() Mach Call

If you never heard of mach system calls and specifically task_for_pid() call on Mac OS X, you can consider yourself lucky. If you want to stay that way – stop reading now! Still here? In that case let’s start with disclaimer – author of this text is not and can not be in any way responsible for damage produced or influenced by this article.

Prior to the Mac OS X 10.4.X (Tiger), it was completely legal for one process to control another for the purpose of influencing its execution (single stepping, resuming, stopping etc) and inspecting or modifying its memory and registers. In one of the patches for Tiger, this policy was changed so that only a process owned by root or with a “primary effective group of procmod or procview” has this privilege. In Leopard (Mac OS X 10.5), this policy was changed again (that much about consistent security policy – nice work Apple) such that an inspector process now depends on the security framework to authorize use of the task_for_pid system service which gives a process the capability to control another process.

To build a utility that will use task_for_pid(), you need to do the following:

1. Create Info.plist file which will be embedded to your executable 
   file and will enable code signing 
2. Create self-signed code signing certificate using Keychain access 
3. Write your program that uses security framework to obtain rights 
   to execute task\_for_pid() 
4. Compile your program and code-sign it.

So let’s get started.

Step 1 – Create Info.plist

I used one of the standard Info.plist files I could find in Xcode and changed some particular parts as can be seen in following example:

(task_for_pid_info_plist.xml) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleIdentifier</key>
    <string>net.os-tres.tfpexample</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>tfpexample</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>SecTaskAccess</key>
    <array>
      <string>allowed</string>
    </array>
  </dict>
</plist>

The important part is key “SecTaskAccess” with value “allowed”.

Step 2 – Create self-signed code signing certificate

Open your Keychain Access and do the following:

When created – this certificate will be untrusted by default – change “When using this certificate” to “Always Trust” and you should be OK and ready to go for the next step.

Step 3 – Write your program

I wrote a very simple program that takes PID of a process you want to investigate (ran by your UID), connects to it and writes current register values for it. Code is pretty self-explaining so I won’t go into nifty details:

(task_for_pid_prog.c) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <stdio.h> 
#include <unistd.h> 
#include <sys/types.h> 
#include <sys/ptrace.h> 
#include <mach/mach.h> 
#include <errno.h> 
#include <stdlib.h> 
#include <Security/Authorization.h>

int acquireTaskportRight()
{
  OSStatus stat;
  AuthorizationItem taskport_item[] = {{"system.privilege.taskport:"}};
  AuthorizationRights rights = {1, taskport_item}, *out_rights = NULL;
  AuthorizationRef author;
  int retval = 0;

  AuthorizationFlags auth_flags = kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed | ( 1 << 5);

  stat = AuthorizationCreate (NULL, kAuthorizationEmptyEnvironment,auth_flags,&amp;amp;author);
  if (stat != errAuthorizationSuccess)
    {
      return 0;
    }

  stat = AuthorizationCopyRights ( author, &amp;amp;rights, kAuthorizationEmptyEnvironment, auth_flags,&amp;amp;out_rights);
  if (stat != errAuthorizationSuccess)
    {
      printf("fail");
      return 1;
    }
  return 0;
}

void check(int cond, char* msg)
{
  if (!cond)
    {
      printf("%s\n", msg);
      exit(-1);
    }
}

int main()
{
  int infoPid;
  kern_return_t kret;
  mach_port_t task;
  thread_act_port_array_t threadList;
  mach_msg_type_number_t threadCount;
  x86_thread_state32_t state;

  printf("Enter pid: \n");
  scanf("%d", &infoPid);
  if (acquireTaskportRight() != 0)
    {
      printf("acquireTaskportRight() failed!\n");
      exit(0);
    }

  kret = task_for_pid(mach_task_self(), infoPid, &task);
  if (kret!=KERN_SUCCESS)
    {
      printf("task_for_pid() failed with message %s!\n",mach_error_string(kret));
      exit(0);
    }

  kret = task_threads(task, &threadList, &threadCount);
  if (kret!=KERN_SUCCESS)
    {
      printf("task_threads() failed with message %s!\n", mach_error_string(kret));
      exit(0);
    }

  mach_msg_type_number_t stateCount = x86_THREAD_STATE32_COUNT;
  kret = thread_get_state( threadList[0], x86_THREAD_STATE32, (thread_state_t)&state, &stateCount);
  if (kret!=KERN_SUCCESS)
    {
      printf("thread_get_state() failed with message %s!\n", mach_error_string(kret));
      exit(0);
    }

  printf("Thread %d has %d threads. Thread 0 state: \n", infoPid, threadCount);
  printf("EIP: %lx\nEAX: %lx\nEBX: %lx\nECX: %lx\nEDX: %lx\nSS: %lx\n", state.__eip, state.__eax, state.__ebx, state.__ecx, state.__edx, state.__ss);
  return 0;
}

Step 4 – Compile and sign

To compile the program I used following command line:

1
gcc tfpexample.c -sectcreate __TEXT __info_plist ./Info.plist -o tfpexample -framework Security - framework CoreFoundation

To sign the code with certificate we prepared before – do this:

1
codesign -s tfpexample ./tfpexample

We can check if everything went OK:

1
2
3
4
5
6
7
8
9
10
11
12
13
[ivan]~/hoby/debuger/blog-tfp > codesign -dvvv ./tfpexample

Executable=/Users/ivan/hoby/debuger/blog-tfp/tfpexample 
Identifier=net.os-tres.tfpexample 
Format=Mach-O thin (i386)
CodeDirectory v=20001 size=187 flags=0!0(none) hashes=4+2 location=embedded 
CDHash=30c98f962fc9a2b1a2f73cff55d4584bd053aa3a 
Signature size=1454 
Authority=tfpexample
Signed Time=Feb 17, 2010 8:40:04 PM 
Info.plist entries=6 
Sealed Resources=none 
Internal requirements count=0 size=12

This looks good – let’s test it.

Step 5 – Test program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[ivan]~/hoby/debuger/blog-tfp > 
[ivan]~/hoby/debuger/blog-tfp > ps 

PID TTY TIME CMD 
2511 ttys000 0:00.02 -zsh
2104 ttys001 0:00.08 -zsh 

[ivan]~/hoby/debuger/blog-tfp > 
[ivan]~/hoby/debuger/blog-tfp > ./tfpexample 

Enter pid: 2104 

Thread 2104 has 1 threads. Thread 0 state: 
EIP: 953d1562 
EAX: 4006f 
EBX: 2ad74 
ECX: bffff9cc 
EDX: 953d1562 SS: 1f 

[ivan]~/hoby/debuger/blog-tfp >

It works.

Comments