aboutsummaryrefslogtreecommitdiff
path: root/lib/darwin/unrestrict.c
blob: 65260198998f9078677bf0a2a919bc450e0ddd30 (plain) (blame)
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#include "substitute.h"
#include "substitute-internal.h"
#include "darwin/mach-decls.h"
#include <unistd.h>
#include <mach/vm_region.h>

static int unrestrict_macho_header(void *header, size_t size, bool *did_modify_p,
                                   char **error) {
    *did_modify_p = false;
    struct mach_header *mh = header;
    if (mh->magic != MH_MAGIC && mh->magic != MH_MAGIC_64)
        return SUBSTITUTE_ERR_MISC;
    size_t off = mh->magic == MH_MAGIC_64 ? sizeof(struct mach_header_64)
                                          : sizeof(struct mach_header);
    for (uint32_t i = 0; i < mh->ncmds; i++) {
        if (off > size || size - off < sizeof(struct load_command))
            break; /* whatever */
        struct load_command *lc = header + off;
        if (lc->cmdsize > size - off)
            break;
        #define CASES(code...) \
            if (lc->cmd == LC_SEGMENT) { \
                typedef struct segment_command segment_command_y; \
                typedef struct section section_y; \
                code \
            } else if (lc->cmd == LC_SEGMENT_64) { \
                typedef struct segment_command_64 segment_command_y; \
                typedef struct section_64 section_y; \
                code \
            }
        CASES(
            segment_command_y *sc = (void *) lc;
            if (lc->cmdsize < sizeof(*sc) ||
                sc->nsects > (lc->cmdsize - sizeof(*sc)) / sizeof(struct section)) {
                asprintf(error, "bad segment_command");
                return SUBSTITUTE_ERR_MISC;
            }
            if (!strncmp(sc->segname, "__RESTRICT", 16)) {
                section_y *sect = (void *) (sc + 1);
                for (uint32_t i = 0; i < sc->nsects; i++, sect++) {
                    if (!strncmp(sect->sectname, "__restrict", 16)) {
                        strcpy(sect->sectname, "\xf0\x9f\x92\xa9");
                        *did_modify_p =true;
                    }
                }
            }
        )
        #undef CASES

        if (off + lc->cmdsize < off) {
            asprintf(error, "overflowing lc->cmdsize");
            return SUBSTITUTE_ERR_MISC;
        }
        off += lc->cmdsize;
    }
    return SUBSTITUTE_OK;
}

EXPORT
int substitute_ios_unrestrict(pid_t pid, bool should_resume, char **error) {
    *error = NULL;
    mach_port_t task;
    kern_return_t kr = task_for_pid(mach_task_self(), pid, &task);
    if (kr)
        return SUBSTITUTE_ERR_TASK_FOR_PID;

    int ret;
    vm_address_t header_addr = 0;

    int retries = 0;
    int wait_us = 1;
setback:
    while (1) {
        /* if calling from unrestrict-me, the process might not have transitioned
         * yet.  if it has, then TASK_DYLD_INFO will be filled with 0. */
        struct task_dyld_info tdi;
        mach_msg_type_number_t cnt = TASK_DYLD_INFO_COUNT;

        kern_return_t kr = task_info(task, TASK_DYLD_INFO, (void *) &tdi, &cnt);
        if (kr || cnt != TASK_DYLD_INFO_COUNT) {
            asprintf(error, "task_info: %x", kr);
            ret = SUBSTITUTE_ERR_MISC;
            goto fail;
        }
        printf("=>%llx\n", tdi.all_image_info_addr);
        printf("=>%llx\n", tdi.all_image_info_size);
        if (tdi.all_image_info_size == 0)
            break;
        if (retries++ == 20) {
            asprintf(error, "all_image_info_size was not 0 after 20 retries");
            ret = SUBSTITUTE_ERR_MISC;
            goto fail;
        }
        wait_us *= 2;
        if (wait_us > 100000)
            wait_us = 100000;
        while (usleep(wait_us))
            ;
    }

    /* alrighty then, let's look at the damage.  find the first readable
     * segment */
    mach_vm_address_t segm_addr = 0;
    mach_vm_size_t segm_size = 0;
    vm_region_basic_info_data_64_t info;
    mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
    mach_port_t object_name;
    while (1) {
        kr = mach_vm_region(task, &segm_addr, &segm_size, VM_REGION_BASIC_INFO_64,
                            (vm_region_info_t) &info, &info_count, &object_name);
        if (kr == KERN_INVALID_ADDRESS) {
            /* nothing! maybe it's not there *yet*? this actually is possible */
            goto setback;
        } else if (kr) {
            asprintf(error, "mach_vm_region(%lx): %x", (long) segm_addr, kr);
            ret = SUBSTITUTE_ERR_VM;
            goto fail;
        }
        if (info.protection)
            break;

        segm_addr++;
    }

    size_t toread = 0x4000;
    if (segm_size < toread)
        toread = segm_size;
    if ((kr = vm_allocate(mach_task_self(), &header_addr, toread,
                          VM_FLAGS_ANYWHERE))) {
        asprintf(error, "vm_allocate(%zx): %x", toread, kr);
        ret = SUBSTITUTE_ERR_MISC;
        goto fail;
    }
    mach_vm_size_t actual = toread;
    kr = mach_vm_read_overwrite(task, segm_addr, toread, header_addr,
                                &actual);
    if (kr || actual != toread) {
        asprintf(error, "mach_vm_read_overwrite: %x", kr);
        ret = SUBSTITUTE_ERR_MISC;
        goto fail;
    }

    bool did_modify;
    if ((ret = unrestrict_macho_header((void *) header_addr, toread,
                                       &did_modify, error)))
        goto fail;

    if (did_modify) {
        if ((kr = vm_protect(mach_task_self(), header_addr, toread,
                             FALSE, info.protection))) {
            asprintf(error, "vm_protect(%lx=>%d): %x",
                     (long) header_addr, info.protection, kr);
            ret = SUBSTITUTE_ERR_VM;
            goto fail;
        }
        vm_prot_t cur, max;
        if ((kr = mach_vm_remap(task, &segm_addr, toread, 0,
                                VM_FLAGS_OVERWRITE,
                                mach_task_self(), header_addr, FALSE,
                                &cur, &max, info.inheritance))) {
            asprintf(error, "mach_vm_remap(%lx=>%lx * %zx): %x",
                     (long) header_addr, (long) segm_addr, toread, kr);
            ret = SUBSTITUTE_ERR_VM;
            goto fail;
        }
    }

    ret = SUBSTITUTE_OK;
fail:
    if (should_resume) {
        if ((kr = task_resume(task))) {
            asprintf(error, "task_resume: %x", kr);
            ret = SUBSTITUTE_ERR_MISC;
        }
    }
    mach_port_deallocate(mach_task_self(), task);
    if (header_addr)
        vm_deallocate(mach_task_self(), header_addr, toread);
    return ret;
}