354 lines
14 KiB
C
354 lines
14 KiB
C
#ifdef __APPLE__
|
|
#include "running_kernel.h"
|
|
#include "find.h"
|
|
#include "mach-o/link.h"
|
|
#include "mach-o/binary.h"
|
|
#include "mach-o/headers/loader.h"
|
|
#include "mach-o/headers/nlist.h"
|
|
#include <mach/mach.h>
|
|
#include <assert.h>
|
|
|
|
struct proc;
|
|
typedef int32_t sy_call_t(struct proc *, void *, int *);
|
|
typedef void sy_munge_t(const void *, void *);
|
|
|
|
struct sysent { /* system call table */
|
|
int16_t sy_narg; /* number of args */
|
|
int8_t sy_resv; /* reserved */
|
|
int8_t sy_flags; /* flags */
|
|
sy_call_t *sy_call; /* implementing function */
|
|
sy_munge_t *sy_arg_munge32; /* system call arguments munger for 32-bit process */
|
|
sy_munge_t *sy_arg_munge64; /* system call arguments munger for 64-bit process */
|
|
int32_t sy_return_type; /* system call return types */
|
|
uint16_t sy_arg_bytes; /* Total size of arguments in bytes for
|
|
* 32-bit system calls
|
|
*/
|
|
};
|
|
#define _SYSCALL_RET_INT_T 1
|
|
|
|
// end copied
|
|
|
|
kern_return_t kr_assert_(kern_return_t kr, const char *name, int line) {
|
|
if(kr) {
|
|
die("result=%08x on line %d:\n%s", kr, line, name);
|
|
}
|
|
return kr;
|
|
}
|
|
#define kr_assert(x) kr_assert_((x), #x, __LINE__)
|
|
|
|
mach_port_t get_kernel_task() {
|
|
static mach_port_t kernel_task;
|
|
if(!kernel_task) {
|
|
kr_assert(task_for_pid(mach_task_self(), 0, &kernel_task));
|
|
}
|
|
return kernel_task;
|
|
}
|
|
|
|
uint32_t b_allocate_from_running_kernel(const struct binary *binary) {
|
|
mach_port_t kernel_task = get_kernel_task();
|
|
if(b_mach_hdr(binary)->flags & MH_PREBOUND) {
|
|
CMD_ITERATE(b_mach_hdr(binary), cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg = (void *) cmd;
|
|
if(seg->vmsize == 0) continue;
|
|
vm_address_t address = seg->vmaddr;
|
|
printf("prebound allocate %08x %08x\n", (unsigned int) address, (unsigned int) seg->vmsize);
|
|
kr_assert(vm_allocate(kernel_task,
|
|
&address,
|
|
seg->vmsize,
|
|
VM_FLAGS_FIXED));
|
|
|
|
assert(address == seg->vmaddr);
|
|
kr_assert(vm_wire(mach_host_self(),
|
|
kernel_task,
|
|
address,
|
|
seg->vmsize,
|
|
VM_PROT_READ));
|
|
}
|
|
}
|
|
return 0;
|
|
} else {
|
|
// try to reserve some space
|
|
uint32_t slide;
|
|
for(slide = 0xf0000000; slide < 0xf0000000 + 0x01000000; slide += 0x10000) {
|
|
CMD_ITERATE(b_mach_hdr(binary), cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg = (void *) cmd;
|
|
if(seg->vmsize == 0) continue;
|
|
vm_address_t address = seg->vmaddr + slide;
|
|
printf("allocate %08x %08x for %.16s (slide=%x)\n", (int) address, (int) seg->vmsize, seg->segname, (int) slide);
|
|
kern_return_t kr = vm_allocate(kernel_task,
|
|
&address,
|
|
seg->vmsize,
|
|
VM_FLAGS_FIXED);
|
|
if(!kr) {
|
|
assert(address == seg->vmaddr + slide);
|
|
kr_assert(vm_wire(mach_host_self(),
|
|
kernel_task,
|
|
address,
|
|
seg->vmsize,
|
|
VM_PROT_READ));
|
|
continue;
|
|
}
|
|
// Bother, it didn't work. So we need to increase the slide...
|
|
// But first we need to get rid of the gunk we did manage to allocate.
|
|
CMD_ITERATE(b_mach_hdr(binary), cmd2) {
|
|
if(cmd2 == cmd) break;
|
|
if(cmd2->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg2 = (void *) cmd2;
|
|
printf("deallocate %08x %08x\n", (int) (seg2->vmaddr + slide), (int) seg2->vmsize);
|
|
kr_assert(vm_deallocate(kernel_task,
|
|
seg2->vmaddr + slide,
|
|
seg2->vmsize));
|
|
}
|
|
}
|
|
goto try_another_slide;
|
|
}
|
|
}
|
|
// If we got this far, it worked!
|
|
goto it_worked;
|
|
try_another_slide:;
|
|
}
|
|
// But if we got this far, we ran out of slides to try.
|
|
die("we couldn't find anywhere to put this thing and that is ridiculous");
|
|
it_worked:;
|
|
return slide;
|
|
}
|
|
}
|
|
|
|
|
|
void b_inject_into_running_kernel(struct binary *to_load, uint32_t sysent) {
|
|
// save sysent so unload can have it
|
|
b_mach_hdr(to_load)->filetype = sysent;
|
|
|
|
mach_port_t kernel_task = get_kernel_task();
|
|
|
|
CMD_ITERATE(b_mach_hdr(to_load), cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg = (void *) cmd;
|
|
uint32_t fs = seg->filesize;
|
|
if(seg->vmsize < fs) fs = seg->vmsize;
|
|
// if prebound, slide = 0
|
|
vm_offset_t of = (vm_offset_t) rangeconv_off((range_t) {to_load, seg->fileoff, seg->filesize}, MUST_FIND).start;
|
|
vm_address_t ad = seg->vmaddr;
|
|
while(fs > 0) {
|
|
// complete headbang.
|
|
//printf("(%.16s) reading %x %08x -> %08x\n", seg->segname, fs, (uint32_t) of, (uint32_t) ad);
|
|
uint32_t tocopy = 0xfff;
|
|
if(fs < tocopy) tocopy = fs;
|
|
kr_assert(vm_write(kernel_task,
|
|
ad,
|
|
of,
|
|
tocopy));
|
|
fs -= tocopy;
|
|
of += tocopy;
|
|
ad += tocopy;
|
|
}
|
|
if(seg->vmsize > 0) {
|
|
// This really depends on nx_disabled...
|
|
kr_assert(vm_protect(kernel_task,
|
|
seg->vmaddr,
|
|
seg->vmsize,
|
|
true,
|
|
seg->maxprot & ~VM_PROT_EXECUTE));
|
|
kr_assert(vm_protect(kernel_task,
|
|
seg->vmaddr,
|
|
seg->vmsize,
|
|
false,
|
|
seg->initprot & ~VM_PROT_EXECUTE));
|
|
|
|
vm_machine_attribute_val_t val = MATTR_VAL_CACHE_FLUSH;
|
|
kr_assert(vm_machine_attribute(kernel_task,
|
|
seg->vmaddr,
|
|
seg->vmsize,
|
|
MATTR_CACHE,
|
|
&val));
|
|
}
|
|
}
|
|
}
|
|
|
|
// okay, now do the fancy syscall stuff
|
|
// how do I safely dispose of this file?
|
|
int lockfd = open("/tmp/.syscall-11", O_RDWR | O_CREAT);
|
|
assert(lockfd > 0);
|
|
assert(!flock(lockfd, LOCK_EX));
|
|
|
|
struct sysent orig_sysent;
|
|
vm_size_t whatever;
|
|
kr_assert(vm_read_overwrite(kernel_task,
|
|
sysent + 11 * sizeof(struct sysent),
|
|
sizeof(struct sysent),
|
|
(vm_offset_t) &orig_sysent,
|
|
&whatever));
|
|
|
|
CMD_ITERATE(b_mach_hdr(to_load), cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg = (void *) cmd;
|
|
struct section *sections = (void *) (seg + 1);
|
|
for(uint32_t i = 0; i < seg->nsects; i++) {
|
|
struct section *sect = §ions[i];
|
|
|
|
if((sect->flags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS) {
|
|
void **things = rangeconv_off((range_t) {to_load, sect->offset, sect->size}, MUST_FIND).start;
|
|
for(uint32_t i = 0; i < sect->size / 4; i++) {
|
|
struct sysent my_sysent = { 1, 0, 0, things[i], NULL, NULL, _SYSCALL_RET_INT_T, 0 };
|
|
printf("--> %p\n", things[i]);
|
|
kr_assert(vm_write(kernel_task,
|
|
(vm_address_t) sysent + 11 * sizeof(struct sysent),
|
|
(vm_offset_t) &my_sysent,
|
|
sizeof(struct sysent)));
|
|
syscall(11);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
kr_assert(vm_write(kernel_task,
|
|
sysent + 11 * sizeof(struct sysent),
|
|
(vm_offset_t) &orig_sysent,
|
|
sizeof(struct sysent)));
|
|
|
|
assert(!flock(lockfd, LOCK_UN));
|
|
}
|
|
|
|
void unload_from_running_kernel(uint32_t addr) {
|
|
mach_port_t kernel_task = get_kernel_task();
|
|
|
|
vm_size_t whatever;
|
|
|
|
autofree struct mach_header *hdr = malloc(0x1000);
|
|
if(vm_read_overwrite(kernel_task,
|
|
(vm_address_t) addr,
|
|
0x1000,
|
|
(vm_offset_t) hdr,
|
|
&whatever) == KERN_INVALID_ADDRESS) {
|
|
die("invalid address %08x", addr);
|
|
}
|
|
kr_assert(vm_read_overwrite(kernel_task,
|
|
(vm_address_t) addr,
|
|
0xfff,
|
|
(vm_offset_t) hdr,
|
|
&whatever));
|
|
if(hdr->magic != MH_MAGIC) {
|
|
die("invalid header (wrong address?)");
|
|
}
|
|
CMD_ITERATE(hdr, cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg = (void *) cmd;
|
|
struct section *sections = (void *) (seg + 1);
|
|
for(uint32_t i = 0; i < seg->nsects; i++) {
|
|
struct section *sect = §ions[i];
|
|
|
|
if((sect->flags & SECTION_TYPE) == S_MOD_TERM_FUNC_POINTERS) {
|
|
uint32_t sysent = hdr->filetype; // hurf durf
|
|
assert(sysent);
|
|
autofree void **things = malloc(sect->size);
|
|
kr_assert(vm_read_overwrite(kernel_task,
|
|
(vm_address_t) sect->addr,
|
|
sect->size,
|
|
(vm_offset_t) things,
|
|
&whatever));
|
|
for(uint32_t i = 0; i < sect->size / 4; i++) {
|
|
struct sysent my_sysent = { 1, 0, 0, things[i], NULL, NULL, _SYSCALL_RET_INT_T, 0 };
|
|
printf("--> %p\n", things[i]);
|
|
kr_assert(vm_write(kernel_task,
|
|
(vm_address_t) sysent + 11 * sizeof(struct sysent),
|
|
(vm_offset_t) &my_sysent,
|
|
sizeof(struct sysent)));
|
|
syscall(11);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CMD_ITERATE(hdr, cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *seg = (void *) cmd;
|
|
if(seg->vmsize > 0) {
|
|
kr_assert(vm_deallocate(kernel_task,
|
|
seg->vmaddr,
|
|
seg->vmsize));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void b_running_kernel_load_macho(struct binary *binary) {
|
|
kern_return_t kr;
|
|
|
|
mach_port_t kernel_task = get_kernel_task();
|
|
|
|
char hdr_buf[0xfff];
|
|
struct mach_header *const hdr = (void *) hdr_buf;
|
|
|
|
addr_t mh_addr;
|
|
vm_size_t size;
|
|
for(addr_t hugebase = 0x80000000; hugebase; hugebase += 0x40000000) {
|
|
for(addr_t pagebase = 0x1000; pagebase < 0x10000; pagebase += 0x1000) {
|
|
mh_addr = (vm_address_t) (hugebase + pagebase);
|
|
size = 0x1000;
|
|
// This will return either KERN_PROTECTION_FAILURE if it's a good address, and KERN_INVALID_ADDRESS otherwise.
|
|
// But if we use a shorter size, it will read if it's a good address, and /crash/ otherwise.
|
|
// So we do two.
|
|
kr = vm_read_overwrite(kernel_task, (vm_address_t) mh_addr, size, (vm_address_t) hdr_buf, &size);
|
|
if(kr == KERN_INVALID_ADDRESS) {
|
|
continue;
|
|
} else if(kr && kr != KERN_PROTECTION_FAILURE) {
|
|
die("unexpected error from vm_read_overwrite: %d", kr);
|
|
}
|
|
// ok, it's valid, but is it the actual header?
|
|
size = 0xfff;
|
|
kr_assert(vm_read_overwrite(kernel_task, (vm_address_t) mh_addr, size, (vm_address_t) hdr_buf, &size));
|
|
if(hdr->magic == MH_MAGIC) {
|
|
printf("found running kernel at 0x%08llx\n", (long long) mh_addr);
|
|
goto ok;
|
|
}
|
|
}
|
|
}
|
|
die("didn't find the kernel anywhere");
|
|
|
|
ok:;
|
|
|
|
binary->cputype = b_mach_hdr(binary)->cputype;
|
|
binary->cpusubtype = b_mach_hdr(binary)->cpusubtype;
|
|
|
|
if(b_mach_hdr(binary)->sizeofcmds > size - sizeof(*b_mach_hdr(binary))) {
|
|
die("sizeofcmds is too big");
|
|
}
|
|
addr_t maxoff = 0;
|
|
CMD_ITERATE(b_mach_hdr(binary), cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *scmd = (void *) cmd;
|
|
addr_t newmax = scmd->fileoff + scmd->filesize;
|
|
if(newmax > maxoff) maxoff = newmax;
|
|
}
|
|
}
|
|
|
|
char *buf = malloc(maxoff);
|
|
|
|
CMD_ITERATE(b_mach_hdr(binary), cmd) {
|
|
if(cmd->cmd == LC_SEGMENT) {
|
|
struct segment_command *scmd = (void *) cmd;
|
|
addr_t off = scmd->fileoff;
|
|
addr_t addr = scmd->vmaddr;
|
|
vm_size_t size = scmd->filesize;
|
|
// Well, uh, this sucks. But there's some block on reading. In fact, it's probably a bug that this works.
|
|
while(size > 0) {
|
|
vm_size_t this_size = (vm_size_t) size;
|
|
if(this_size > 0xfff) this_size = 0xfff;
|
|
kr_assert(vm_read_overwrite(kernel_task, (vm_address_t) addr, this_size, (vm_address_t) (buf + off), &this_size));
|
|
|
|
off += (addr_t) this_size;
|
|
addr += (addr_t) this_size;
|
|
size -= this_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
b_prange_load_macho(binary, (prange_t) {buf, maxoff}, 0, "<running kernel>");
|
|
}
|
|
|
|
#endif
|