0

Last time I'm trying to write simple genetic fuzzer (using strictly Mac OS, just for fun). My idea is something like that :

-> Main program that control forked process

--> forked process load binary code from disk and jump into it.

-> parent ask for task (task_for_pid(mach_task_self(),childPID,&task))

-> parent try to catch traps (0xcc), checking if we had been there before, just like AFL works (of course simplifying)

--> child loads some raw binary code (in my example has to be System V ABI)

I get error as below :

16:10|domin568[15] ~/Desktop/experiments/Instrumentation $ ./run.sh
PARENT 3866
task_for_pid() failed with message (os/kern) failure !
CHILD 3867

run.sh :

#!/bin/sh
clang -sectcreate __TEXT __info_plist Info.plist -o server server.c
codesign -s instrument ./server
./server

"instrument" is present at my keychain, and Always Trust for code singing, so I think it should't be case.

Info.plist

<?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>SecTaskAccess</key>
    <array>
        <string>allowed</string>
        <string>debug</string>
    </array>
</dict>
</plist>

Of course my code is only useful for specific case, it tries to fuzz one function which input is string and compare it with other string.

server.c :

#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/ptrace.h>
#include <mach/mach.h>
#include <stdlib.h>
int main (int argc,char ** argv)
{
    pid_t pid = fork();
    pid_t parentPID, childPID; //maybe it's not really safe, nevermind
    int status;
    if (pid == 0)
    {
        printf ("CHILD %i\n",getpid());
        childPID = getpid();

        FILE * f = fopen(argv[1],"rb");
        if (f == NULL)
        {
            return -2;
            puts ("Cannot open file specified\n");
        }
        int from,to = 0;
        sscanf(argv[2],"%x",&from);
        sscanf(argv[3],"%x",&to);
        if (from >= to)
        {
            puts ("R u out of your mind ? check your range of bytes within the file... \n");
            return -3;
        }
        int fileSize = to - from;
        void * mem = mmap (NULL,fileSize,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE,fileno(f),0);
        if (mem == MAP_FAILED)
        {
            puts ("[!] Cannot allocate memory for file");
            return -4;
        }
        printf ("[-] File mapped to virtual memory : [%p]\n",mem);

        int (*pFunc)(char * str) = (int(*)(char *))(mem+from);

        int ret = pFunc("AAAAA");
        printf ("Returned : %d\n",ret);


    }
    else 
    {
        printf ("PARENT %i\n",getpid());
        parentPID = getpid();

        kern_return_t kret;
        mach_port_t task;
        mach_port_t target_exception_port;
        kret = task_for_pid (mach_task_self(),childPID,&task);
        if (kret != KERN_SUCCESS)
        {
            printf ("task_for_pid() failed with message %s !\n",mach_error_string(kret));
            sleep(100000);
        }

        //save the set of exception ports registered in the process 

        exception_mask_t       saved_masks[EXC_TYPES_COUNT];
        mach_port_t            saved_ports[EXC_TYPES_COUNT];
        exception_behavior_t   saved_behaviors[EXC_TYPES_COUNT];
        thread_state_flavor_t  saved_flavors[EXC_TYPES_COUNT];
        mach_msg_type_number_t saved_exception_types_count;

        task_get_exception_ports(task,
                        EXC_MASK_ALL,
                        saved_masks,
                        &saved_exception_types_count,
                        saved_ports,
                        saved_behaviors,
                        saved_flavors);

        //allocate and authorize a new port 

        mach_port_allocate(mach_task_self(),
                   MACH_PORT_RIGHT_RECEIVE,
                   &task);

        mach_port_insert_right(mach_task_self(),
                       target_exception_port,
                       target_exception_port,
                       MACH_MSG_TYPE_MAKE_SEND);

        //register the exception port with the target process 

        task_set_exception_ports(task,
                         EXC_MASK_ALL,
                         target_exception_port,
                         EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
                         THREAD_STATE_NONE);

        ptrace (PT_ATTACHEXC, childPID,0,0);
    }

    return 0;
}

Why this outputs error ? Is it case of how fork works under OSX ? What's the problem out there ? I'm not expert of how osx works underneath so maybe I missed something. Thanks for help :) !

Dominik

Domin568
  • 31
  • 8
  • 1. Have you requested the [`SecTaskAccess` privilege](https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man8/taskgated.8.html) in your Info.plist? 2. Is your executable signed? 3. Your process may need to run as root, but I'm not sure about this, as accessing child processes may work without root privileges. – pmdj Dec 07 '17 at 20:49
  • Yes I've done all of this. If I want task for Finder it doesn't produce error. As I suppose it doesn't work because childPID is unknown for parent in time the task_for_pid() is called. I don't know how to do it other way. How can I run some code, and control it as debugger in one program in Mac OS (like the GDB does) ? I tried to read gdb source code, but it's quite hard for me to understand how it's done there. – Domin568 Dec 08 '17 at 16:11
  • Ok, so it's just an experiment but I still think you should check the following before dealing with `task_for_pid` issue. First, the binary which mapped by your child process for execution, you need to make sure that the function started at `from` doesn't call any lazy symbols or unresolved symbols, because dyld wouldn't work here.. notice, that event `printf` is a lazy symbol so refrain from using it. Second, according to your output, it's possible that the child process haven't even created when parent tried the task_for_pid .. consider synching between those 2 processes. – Zohar81 Dec 09 '17 at 13:21

2 Answers2

0

You call pid_t pid = fork(); therefore the PID of the child process in the parent process is stored in pid variable not in the childPID.

The childPID variable is initialized in the child process only according to your example code.

The task_for_pid() call is using childPID - basically you are using an uninitialized memory.

mrdvlpr
  • 526
  • 4
  • 20
  • Thank you very much, it was the my little misunderstanding of how fork() works. Now I can bound to my child process :) – Domin568 Dec 14 '17 at 09:39
0

Besides the problem mentioned in the other answer of using uninitialized pid, it looks like you don't pass the file as input argument, what cause the child process to terminate immediately and become a zombie (since parent doesn't wait for it's pid) and you cannot perform task_for_pid to terminated process (even though it's still listed in the process table)

Basically, you need to synchronize between parent and child more carefully, but as first step, In order to pinpoint the problem, I would recommend to put sleep before checking if the file is properly opened, that should resolve the error in task_for_pid.

Zohar81
  • 4,554
  • 5
  • 29
  • 82
  • @mrdvlpr, that's right, but in the question, it's shown that the run script simply call `server` without any parameters, what cause the child to drop instantly because `argv[1]` is NULL.. – Zohar81 Dec 10 '17 at 21:23