├── test.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcuserdata │ └── eyakovlev.xcuserdatad │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── test.xcscheme └── project.pbxproj ├── test ├── test.h ├── resolver.h ├── Info.plist ├── sysent.h ├── resolver.c └── test.c ├── load.sh └── victim └── victim.c /test.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | // 2 | // test.h 3 | // test 4 | // 5 | // Created by eyakovlev on 16.02.16. 6 | // Copyright © 2016 acme. All rights reserved. 7 | // 8 | 9 | #ifndef test_h 10 | #define test_h 11 | 12 | // kext-wide malloc tag 13 | extern OSMallocTag g_tag; 14 | 15 | // kext-wide lock group 16 | extern lck_grp_t* g_lock_group; 17 | 18 | #endif /* test_h */ 19 | -------------------------------------------------------------------------------- /test/resolver.h: -------------------------------------------------------------------------------- 1 | // 2 | // resolver.h 3 | // test 4 | // 5 | // Created by eyakovlev on 16.02.16. 6 | // Copyright © 2016 acme. All rights reserved. 7 | // 8 | // Resolve private kernel symbols 9 | // 10 | 11 | #ifndef resolver_h 12 | #define resolver_h 13 | 14 | /** 15 | * \brief Find kernel segment with name 16 | */ 17 | struct segment_command_64* find_segment_64(const struct mach_header_64* mh, const char* segname); 18 | 19 | /** 20 | * \brief Resolve private kernel symbol for loaded kernel image 21 | */ 22 | void* resolve_kernel_symbol(const char* name, uintptr_t loaded_kernel_base); 23 | 24 | #endif /* resolver_h */ 25 | -------------------------------------------------------------------------------- /load.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ $# < 1 ]]; then 4 | echo "$0 load|unload|reload|setpid "; 5 | exit 0; 6 | fi 7 | 8 | build_dir="Build/Debug" 9 | kext_name=test.kext 10 | command="$1" 11 | 12 | case $command in 13 | 14 | "load") 15 | chown -R root $build_dir/$kext_name 16 | chgrp -R wheel $build_dir/$kext_name 17 | kextutil $build_dir/$kext_name 18 | ;; 19 | 20 | "unload") 21 | sysctl -w debug.killhook.unhook=1 22 | kextunload -b acme.test 23 | ;; 24 | 25 | "reload") 26 | sysctl -w debug.killhook.unhook=1 27 | kextunload -b acme.test 28 | chown -R root $build_dir/$kext_name 29 | chgrp -R wheel $build_dir/$kext_name 30 | kextutil $build_dir/$kext_name 31 | ;; 32 | 33 | "setpid") 34 | sysctl -w debug.killhook.pid=$2 35 | ;; 36 | esac -------------------------------------------------------------------------------- /test.xcodeproj/xcuserdata/eyakovlev.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | test.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | victim.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 3F98FC961C6D1260006671EE 21 | 22 | primary 23 | 24 | 25 | 3F9A4BAB1C6612AD0013F9B1 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | KEXT 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSHumanReadableCopyright 24 | Copyright © 2016 acme. All rights reserved. 25 | OSBundleLibraries 26 | 27 | com.apple.kpi.bsd 28 | 8.0 29 | com.apple.kpi.libkern 30 | 8.0 31 | com.apple.kpi.mach 32 | 8.0 33 | com.apple.kpi.unsupported 34 | 8.0 35 | com.apple.kpi.iokit 36 | 8.0 37 | com.apple.kpi.dsep 38 | 8.0 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /victim/victim.c: -------------------------------------------------------------------------------- 1 | // 2 | // victim.c 3 | // test 4 | // 5 | // Created by eyakovlev on 11.02.16. 6 | // Copyright © 2016 acme. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // 2 88 mach_msg_trap:entry victim enters mach_msg_trap(7fff5fbffad8, 3, 24, 44, 607, 0, 0) 16 | 17 | static int DoListen(void) 18 | { 19 | mach_msg_return_t err; 20 | printf("%d\n", getpid()); 21 | 22 | /* Allocate a port. */ 23 | mach_port_t port; 24 | err = mach_port_allocate (mach_task_self (), 25 | MACH_PORT_RIGHT_RECEIVE, &port); 26 | if (err) { 27 | printf("mach_port_allocate failed with 0x%x\n", err); 28 | return err; 29 | } 30 | 31 | volatile int f = 0; 32 | while(!f) { 33 | uint8_t recv_buf[4096]; 34 | mach_msg_header_t* hdr = (mach_msg_header_t*)recv_buf; 35 | mach_msg_return_t err = mach_msg(hdr, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(recv_buf), port, 5000, MACH_PORT_NULL); 36 | if (err == MACH_RCV_TIMED_OUT) { 37 | continue; 38 | } 39 | 40 | if (err) { 41 | printf("mach_msg failed with 0x%x\n", err); 42 | continue; 43 | } 44 | 45 | printf("Recv message:\n"); 46 | printf("size = %d\n", hdr->msgh_size); 47 | } 48 | 49 | return EXIT_SUCCESS; 50 | } 51 | 52 | int main(int argc, char** argv) 53 | { 54 | kern_return_t rc = KERN_SUCCESS; 55 | task_t task; 56 | 57 | if (argc < 2) { 58 | return EXIT_FAILURE; 59 | } 60 | 61 | int listen = 0; 62 | if (0 == strcmp(argv[1], "listen")) { 63 | listen = 1; 64 | } 65 | 66 | if (listen) { 67 | return DoListen(); 68 | } 69 | 70 | printf("%d\n", getpid()); 71 | 72 | int pid = atoi(argv[1]); 73 | printf("Terminating pid %d\n", pid); 74 | 75 | rc = task_for_pid(mach_task_self(), pid, &task);//mach_task_self(); 76 | if (rc != KERN_SUCCESS) { 77 | printf("task_for_pid failed: %d\n", rc); 78 | return rc; 79 | } 80 | 81 | do { 82 | printf("Terminating task at port %d\n", task); 83 | rc = task_terminate(task); 84 | if (rc != KERN_SUCCESS) { 85 | printf("task_terminate failed: %d\n", rc); 86 | //return rc; 87 | } 88 | 89 | sleep(5); 90 | } while (rc != KERN_SUCCESS); 91 | 92 | 93 | return 0; 94 | } -------------------------------------------------------------------------------- /test/sysent.h: -------------------------------------------------------------------------------- 1 | // 2 | // sysent.h 3 | // test 4 | // 5 | // Created by eyakovlev on 09.02.16. 6 | // Copyright © 2016 acme. All rights reserved. 7 | // 8 | 9 | #ifndef sysent_h 10 | #define sysent_h 11 | 12 | /* 13 | * System call prototypes. 14 | * 15 | * Derived from FreeBSD's syscalls.master by Landon Fuller, original RCS IDs below: 16 | * 17 | * $FreeBSD: src/sys/sys/sysproto.h,v 1.216 2008/01/08 22:01:26 jhb Exp $ 18 | * created from FreeBSD: src/sys/kern/syscalls.master,v 1.235 2008/01/08 21:58:15 jhb 19 | */ 20 | 21 | /* 22 | * Modified by me to support yosemite 23 | */ 24 | 25 | #define PAD_(t) (sizeof(uint64_t) <= sizeof(t) ? 0 : sizeof(uint64_t) - sizeof(t)) 26 | 27 | #if BYTE_ORDER == LITTLE_ENDIAN 28 | # define PADL_(t) 0 29 | # define PADR_(t) PAD_(t) 30 | #else 31 | # define PADL_(t) PAD_(t) 32 | # define PADR_(t) 0 33 | #endif 34 | 35 | #define PAD_ARG_(arg_type, arg_name) \ 36 | char arg_name##_l_[PADL_(arg_type)]; arg_type arg_name; char arg_name##_r_[PADR_(arg_type)]; 37 | 38 | #define PAD_ARG_8 39 | 40 | /** ptrace request */ 41 | #define PT_DENY_ATTACH 31 42 | 43 | /* nosys syscall */ 44 | #define SYS_syscall 0 45 | 46 | /* exit syscall */ 47 | #define SYS_exit 1 48 | 49 | /* fork syscall */ 50 | #define SYS_fork 2 51 | 52 | /* read syscall */ 53 | #define SYS_read 3 54 | 55 | /* wait4 syscall */ 56 | #define SYS_wait4 7 57 | 58 | /* ptrace() syscall */ 59 | #define SYS_ptrace 26 60 | 61 | #define SYS_kill 37 62 | 63 | typedef int32_t sy_call_t (struct proc *, void *, int *); 64 | typedef void sy_munge_t (const void *, void *); 65 | 66 | // 10.8 and prior */ 67 | struct sysent { 68 | int16_t sy_narg; /* number of arguments */ 69 | int8_t reserved; /* unused value */ 70 | int8_t sy_flags; /* call flags */ 71 | sy_call_t *sy_call; /* implementing function */ 72 | sy_munge_t *sy_arg_munge32; /* munge system call arguments for 32-bit processes */ 73 | sy_munge_t *sy_arg_munge64; /* munge system call arguments for 64-bit processes */ 74 | int32_t sy_return_type; /* return type */ 75 | uint16_t sy_arg_bytes; /* The size of all arguments for 32-bit system calls, in bytes */ 76 | }; 77 | 78 | // 10.9 79 | struct sysent_mavericks { /* system call table */ 80 | sy_call_t *sy_call; /* implementing function */ 81 | sy_munge_t *sy_arg_munge32; /* system call arguments munger for 32-bit process */ 82 | sy_munge_t *sy_arg_munge64; /* system call arguments munger for 64-bit process */ 83 | int32_t sy_return_type; /* system call return types */ 84 | int16_t sy_narg; /* number of args */ 85 | uint16_t sy_arg_bytes; /* Total size of arguments in bytes for 86 | * 32-bit system calls 87 | */ 88 | }; 89 | 90 | // 10.10+ 91 | struct sysent_yosemite { /* system call table */ 92 | sy_call_t *sy_call; /* implementing function */ 93 | sy_munge_t *sy_arg_munge32; /* system call arguments munger for 32-bit process */ 94 | int32_t sy_return_type; /* system call return types */ 95 | int16_t sy_narg; /* number of args */ 96 | uint16_t sy_arg_bytes; /* Total size of arguments in bytes for 97 | * 32-bit system calls 98 | */ 99 | }; 100 | 101 | 102 | #define MACH_MSG_TRAP 31 103 | #define MACH_MSG_OVERWRITE_TRAP 32 104 | 105 | // 10.8 106 | typedef struct { 107 | int mach_trap_arg_count; 108 | void* mach_trap_function; 109 | } mach_trap_t; 110 | 111 | // 10.9+ 112 | typedef struct { 113 | int mach_trap_arg_count; /* Number of trap arguments (Arch independant) */ 114 | void* mach_trap_function; 115 | void* mach_trap_arg_munge32; /* system call argument munger routine for 32-bit */ 116 | int mach_trap_u32_words; /* number of 32-bit words to copyin for U32 */ 117 | } mach_trap_mavericks_t; 118 | 119 | 120 | #endif /* sysent_h */ 121 | -------------------------------------------------------------------------------- /test.xcodeproj/xcuserdata/eyakovlev.xcuserdatad/xcschemes/test.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 69 | 75 | 76 | 77 | 78 | 79 | 80 | 86 | 87 | 93 | 94 | 95 | 96 | 98 | 99 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /test/resolver.c: -------------------------------------------------------------------------------- 1 | // 2 | // resolver.c 3 | // test 4 | // 5 | // Created by eyakovlev on 16.02.16. 6 | // Copyright © 2016 acme. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include "test.h" 21 | 22 | // 23 | // Original KernelResolver code by snare: 24 | // https://github.com/snare/KernelResolver 25 | // 26 | // 27 | // Since 10.8 apple removed kernel symbols from loaded image 28 | // So we will have to load kernel image from disk and parse that 29 | // Modified to parse static kernel images 30 | // 31 | 32 | /* Borrowed from kernel source. It doesn't exist in Kernel.framework. */ 33 | struct nlist_64 { 34 | union { 35 | uint32_t n_strx; /* index into the string table */ 36 | } n_un; 37 | uint8_t n_type; /* type flag, see below */ 38 | uint8_t n_sect; /* section number or NO_SECT */ 39 | uint16_t n_desc; /* see */ 40 | uint64_t n_value; /* value of this symbol (or stab offset) */ 41 | }; 42 | 43 | struct segment_command_64* find_segment_64(const struct mach_header_64* mh, const char* segname) 44 | { 45 | if (!mh) { 46 | return NULL; 47 | } 48 | 49 | if (mh->magic != MH_MAGIC_64) { 50 | return NULL; 51 | } 52 | 53 | if (!segname) { 54 | return NULL; 55 | } 56 | 57 | struct load_command *lc; 58 | struct segment_command_64 *seg, *foundseg = NULL; 59 | 60 | /* First LC begins straight after the mach header */ 61 | lc = (struct load_command *)((uint64_t)mh + sizeof(struct mach_header_64)); 62 | while ((uint64_t)lc < (uint64_t)mh + (uint64_t)mh->sizeofcmds) { 63 | if (lc->cmd == LC_SEGMENT_64) { 64 | /* Check load command's segment name */ 65 | seg = (struct segment_command_64 *)lc; 66 | if (strcmp(seg->segname, segname) == 0) { 67 | foundseg = seg; 68 | break; 69 | } 70 | } 71 | 72 | /* Next LC */ 73 | lc = (struct load_command *)((uint64_t)lc + (uint64_t)lc->cmdsize); 74 | } 75 | 76 | /* Return the segment (NULL if we didn't find it) */ 77 | return foundseg; 78 | } 79 | 80 | struct section_64* find_section_64(struct segment_command_64 *seg, const char *name) 81 | { 82 | struct section_64 *sect, *foundsect = NULL; 83 | u_int i = 0; 84 | 85 | /* First section begins straight after the segment header */ 86 | for (i = 0, sect = (struct section_64 *)((uint64_t)seg + (uint64_t)sizeof(struct segment_command_64)); 87 | i < seg->nsects; 88 | i++, sect = (struct section_64 *)((uint64_t)sect + sizeof(struct section_64))) 89 | { 90 | /* Check section name */ 91 | if (strcmp(sect->sectname, name) == 0) { 92 | foundsect = sect; 93 | break; 94 | } 95 | } 96 | 97 | /* Return the section (NULL if we didn't find it) */ 98 | return foundsect; 99 | } 100 | 101 | struct load_command * 102 | find_load_command(struct mach_header_64 *mh, uint32_t cmd) 103 | { 104 | struct load_command *lc, *foundlc; 105 | 106 | /* First LC begins straight after the mach header */ 107 | lc = (struct load_command *)((uint64_t)mh + sizeof(struct mach_header_64)); 108 | while ((uint64_t)lc < (uint64_t)mh + (uint64_t)mh->sizeofcmds) { 109 | if (lc->cmd == cmd) { 110 | foundlc = (struct load_command *)lc; 111 | break; 112 | } 113 | 114 | /* Next LC */ 115 | lc = (struct load_command *)((uint64_t)lc + (uint64_t)lc->cmdsize); 116 | } 117 | 118 | /* Return the load command (NULL if we didn't find it) */ 119 | return foundlc; 120 | } 121 | 122 | void *find_symbol(struct mach_header_64 *mh, const char *name, uint64_t loaded_base) 123 | { 124 | /* 125 | * Check header 126 | */ 127 | if (mh->magic != MH_MAGIC_64) { 128 | printf("magic number doesn't match - 0x%x\n", mh->magic); 129 | return NULL; 130 | } 131 | 132 | /* 133 | * Find __TEXT - we need it for fixed kernel base 134 | */ 135 | struct segment_command_64 *seg_text = find_segment_64(mh, SEG_TEXT); 136 | if (!seg_text) { 137 | printf("couldn't find __TEXT\n"); 138 | return NULL; 139 | } 140 | 141 | uint64_t fixed_base = seg_text->vmaddr; 142 | 143 | /* 144 | * Find the LINKEDIT and SYMTAB sections 145 | */ 146 | struct segment_command_64 *seg_linkedit = find_segment_64(mh, SEG_LINKEDIT); 147 | if (!seg_linkedit) { 148 | printf("couldn't find __LINKEDIT\n"); 149 | return NULL; 150 | } 151 | 152 | struct symtab_command *lc_symtab = (struct symtab_command *)find_load_command(mh, LC_SYMTAB); 153 | if (!lc_symtab) { 154 | printf("couldn't find SYMTAB\n"); 155 | return NULL; 156 | } 157 | 158 | /* 159 | * Enumerate symbols until we find the one we're after 160 | */ 161 | uintptr_t base = (uintptr_t)mh; 162 | void* strtab = (void*)(base + lc_symtab->stroff); 163 | void* symtab = (void*)(base + lc_symtab->symoff); 164 | 165 | //printf("Symbol table offset 0x%x (%p)\n", lc_symtab->symoff, symtab); 166 | //printf("String table offset 0x%x (%p)\n", lc_symtab->stroff, strtab); 167 | 168 | struct nlist_64* nl = (struct nlist_64 *)(symtab); 169 | for (uint64_t i = 0; i < lc_symtab->nsyms; i++, nl = (struct nlist_64 *)((uint64_t)nl + sizeof(struct nlist_64))) 170 | { 171 | const char* str = (const char *)strtab + nl->n_un.n_strx; 172 | if (strcmp(str, name) == 0) { 173 | /* Return relocated address */ 174 | return (void*) (nl->n_value - fixed_base + loaded_base); 175 | } 176 | } 177 | 178 | /* Return the address (NULL if we didn't find it) */ 179 | return NULL; 180 | } 181 | 182 | void* resolve_kernel_symbol(const char* name, uintptr_t loaded_kernel_base) 183 | { 184 | void* addr = NULL; 185 | errno_t err = 0; 186 | 187 | if (!name) { 188 | return NULL; 189 | } 190 | 191 | const char* image_path = "/mach_kernel"; 192 | if (version_major >= 14) { 193 | image_path = "/System/Library/Kernels/kernel"; // Since yosemite mach_kernel is moved 194 | } 195 | 196 | vfs_context_t context = vfs_context_create(NULL); 197 | if(!context) { 198 | printf("vfs_context_create failed: %d\n", err); 199 | return NULL; 200 | } 201 | 202 | char* data = NULL; 203 | uio_t uio = NULL; 204 | vnode_t vnode = NULL; 205 | err = vnode_lookup(image_path, 0, &vnode, context); 206 | if (err) { 207 | printf("vnode_lookup(%s) failed: %d\n", image_path, err); 208 | goto done; 209 | } 210 | 211 | // Read whole kernel file into memory. 212 | // It is not very efficient but it is the easiest way to adapt existing parsing code 213 | // For production builds we need to use less memory 214 | 215 | struct vnode_attr attr; 216 | err = vnode_getattr(vnode, &attr, context); 217 | if (err) { 218 | printf("can't get vnode attr: %d\n", err); 219 | goto done; 220 | } 221 | 222 | uint32_t data_size = (uint32_t)attr.va_data_size; 223 | data = OSMalloc(data_size, g_tag); 224 | if (!data) { 225 | printf("Could not allocate kernel buffer\n"); 226 | goto done; 227 | } 228 | 229 | uio = uio_create(1, 0, UIO_SYSSPACE, UIO_READ); 230 | if (!uio) { 231 | printf("uio_create failed: %d\n", err); 232 | goto done; 233 | } 234 | 235 | err = uio_addiov(uio, CAST_USER_ADDR_T(data), data_size); 236 | if (err) { 237 | printf("uio_addiov failed: %d\n", err); 238 | goto done; 239 | } 240 | 241 | err = VNOP_READ(vnode, uio, 0, context); 242 | if (err) { 243 | printf("VNOP_READ failed: %d\n", err); 244 | goto done; 245 | } 246 | 247 | addr = find_symbol((struct mach_header_64*)data, name, loaded_kernel_base); 248 | 249 | 250 | done: 251 | uio_free(uio); 252 | OSFree(data, data_size, g_tag); 253 | vnode_put(vnode); 254 | vfs_context_rele(context); 255 | 256 | return addr; 257 | } 258 | -------------------------------------------------------------------------------- /test.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3F0295911C7277A500982EAC /* resolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F0295901C7277A500982EAC /* resolver.h */; }; 11 | 3F0295931C7277F400982EAC /* resolver.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F0295921C7277F400982EAC /* resolver.c */; }; 12 | 3F0295951C7281A400982EAC /* test.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F0295941C7281A400982EAC /* test.h */; }; 13 | 3F276F1D1C734B570028A378 /* load.sh in Resources */ = {isa = PBXBuildFile; fileRef = 3F276F1C1C734B570028A378 /* load.sh */; }; 14 | 3F98FC9F1C6D1280006671EE /* victim.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F98FC9E1C6D1280006671EE /* victim.c */; }; 15 | 3F9A4BB01C6612AD0013F9B1 /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = 3F9A4BAF1C6612AD0013F9B1 /* test.c */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 3F98FC951C6D1260006671EE /* CopyFiles */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = /usr/share/man/man1/; 23 | dstSubfolderSpec = 0; 24 | files = ( 25 | ); 26 | runOnlyForDeploymentPostprocessing = 1; 27 | }; 28 | /* End PBXCopyFilesBuildPhase section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 3F0295901C7277A500982EAC /* resolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = resolver.h; sourceTree = ""; }; 32 | 3F0295921C7277F400982EAC /* resolver.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = resolver.c; sourceTree = ""; }; 33 | 3F0295941C7281A400982EAC /* test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = test.h; sourceTree = ""; }; 34 | 3F276F1C1C734B570028A378 /* load.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = load.sh; sourceTree = ""; }; 35 | 3F98FC971C6D1260006671EE /* victim */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = victim; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 3F98FC9E1C6D1280006671EE /* victim.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = victim.c; sourceTree = ""; }; 37 | 3F9A4BAC1C6612AD0013F9B1 /* test.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = test.kext; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 3F9A4BAF1C6612AD0013F9B1 /* test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = test.c; sourceTree = ""; }; 39 | 3F9A4BB11C6612AD0013F9B1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 3FAA884C1C6A83650079CDE6 /* sysent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sysent.h; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 3F98FC941C6D1260006671EE /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | 3F9A4BA81C6612AD0013F9B1 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 3F98FC981C6D1260006671EE /* victim */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 3F98FC9E1C6D1280006671EE /* victim.c */, 65 | ); 66 | path = victim; 67 | sourceTree = ""; 68 | }; 69 | 3F9A4BA21C6612AD0013F9B1 = { 70 | isa = PBXGroup; 71 | children = ( 72 | 3F276F1C1C734B570028A378 /* load.sh */, 73 | 3F9A4BAE1C6612AD0013F9B1 /* test */, 74 | 3F98FC981C6D1260006671EE /* victim */, 75 | 3F9A4BAD1C6612AD0013F9B1 /* Products */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | 3F9A4BAD1C6612AD0013F9B1 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 3F9A4BAC1C6612AD0013F9B1 /* test.kext */, 83 | 3F98FC971C6D1260006671EE /* victim */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | 3F9A4BAE1C6612AD0013F9B1 /* test */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 3F9A4BAF1C6612AD0013F9B1 /* test.c */, 92 | 3F9A4BB11C6612AD0013F9B1 /* Info.plist */, 93 | 3FAA884C1C6A83650079CDE6 /* sysent.h */, 94 | 3F0295901C7277A500982EAC /* resolver.h */, 95 | 3F0295921C7277F400982EAC /* resolver.c */, 96 | 3F0295941C7281A400982EAC /* test.h */, 97 | ); 98 | path = test; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXHeadersBuildPhase section */ 104 | 3F9A4BA91C6612AD0013F9B1 /* Headers */ = { 105 | isa = PBXHeadersBuildPhase; 106 | buildActionMask = 2147483647; 107 | files = ( 108 | 3F0295951C7281A400982EAC /* test.h in Headers */, 109 | 3F0295911C7277A500982EAC /* resolver.h in Headers */, 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXHeadersBuildPhase section */ 114 | 115 | /* Begin PBXNativeTarget section */ 116 | 3F98FC961C6D1260006671EE /* victim */ = { 117 | isa = PBXNativeTarget; 118 | buildConfigurationList = 3F98FC9D1C6D1260006671EE /* Build configuration list for PBXNativeTarget "victim" */; 119 | buildPhases = ( 120 | 3F98FC931C6D1260006671EE /* Sources */, 121 | 3F98FC941C6D1260006671EE /* Frameworks */, 122 | 3F98FC951C6D1260006671EE /* CopyFiles */, 123 | ); 124 | buildRules = ( 125 | ); 126 | dependencies = ( 127 | ); 128 | name = victim; 129 | productName = victim; 130 | productReference = 3F98FC971C6D1260006671EE /* victim */; 131 | productType = "com.apple.product-type.tool"; 132 | }; 133 | 3F9A4BAB1C6612AD0013F9B1 /* test */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = 3F9A4BB41C6612AD0013F9B1 /* Build configuration list for PBXNativeTarget "test" */; 136 | buildPhases = ( 137 | 3F9A4BA71C6612AD0013F9B1 /* Sources */, 138 | 3F9A4BA81C6612AD0013F9B1 /* Frameworks */, 139 | 3F9A4BA91C6612AD0013F9B1 /* Headers */, 140 | 3F9A4BAA1C6612AD0013F9B1 /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = test; 147 | productName = test; 148 | productReference = 3F9A4BAC1C6612AD0013F9B1 /* test.kext */; 149 | productType = "com.apple.product-type.kernel-extension"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 3F9A4BA31C6612AD0013F9B1 /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastUpgradeCheck = 0720; 158 | ORGANIZATIONNAME = acme; 159 | TargetAttributes = { 160 | 3F98FC961C6D1260006671EE = { 161 | CreatedOnToolsVersion = 7.2.1; 162 | }; 163 | 3F9A4BAB1C6612AD0013F9B1 = { 164 | CreatedOnToolsVersion = 7.2; 165 | DevelopmentTeam = 4D8GDH2BVF; 166 | }; 167 | }; 168 | }; 169 | buildConfigurationList = 3F9A4BA61C6612AD0013F9B1 /* Build configuration list for PBXProject "test" */; 170 | compatibilityVersion = "Xcode 3.2"; 171 | developmentRegion = English; 172 | hasScannedForEncodings = 0; 173 | knownRegions = ( 174 | en, 175 | ); 176 | mainGroup = 3F9A4BA21C6612AD0013F9B1; 177 | productRefGroup = 3F9A4BAD1C6612AD0013F9B1 /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 3F9A4BAB1C6612AD0013F9B1 /* test */, 182 | 3F98FC961C6D1260006671EE /* victim */, 183 | ); 184 | }; 185 | /* End PBXProject section */ 186 | 187 | /* Begin PBXResourcesBuildPhase section */ 188 | 3F9A4BAA1C6612AD0013F9B1 /* Resources */ = { 189 | isa = PBXResourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 3F276F1D1C734B570028A378 /* load.sh in Resources */, 193 | ); 194 | runOnlyForDeploymentPostprocessing = 0; 195 | }; 196 | /* End PBXResourcesBuildPhase section */ 197 | 198 | /* Begin PBXSourcesBuildPhase section */ 199 | 3F98FC931C6D1260006671EE /* Sources */ = { 200 | isa = PBXSourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 3F98FC9F1C6D1280006671EE /* victim.c in Sources */, 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | }; 207 | 3F9A4BA71C6612AD0013F9B1 /* Sources */ = { 208 | isa = PBXSourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 3F9A4BB01C6612AD0013F9B1 /* test.c in Sources */, 212 | 3F0295931C7277F400982EAC /* resolver.c in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXSourcesBuildPhase section */ 217 | 218 | /* Begin XCBuildConfiguration section */ 219 | 3F98FC9B1C6D1260006671EE /* Debug */ = { 220 | isa = XCBuildConfiguration; 221 | buildSettings = { 222 | PRODUCT_NAME = "$(TARGET_NAME)"; 223 | }; 224 | name = Debug; 225 | }; 226 | 3F98FC9C1C6D1260006671EE /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | PRODUCT_NAME = "$(TARGET_NAME)"; 230 | }; 231 | name = Release; 232 | }; 233 | 3F9A4BB21C6612AD0013F9B1 /* Debug */ = { 234 | isa = XCBuildConfiguration; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 238 | CLANG_CXX_LIBRARY = "libc++"; 239 | CLANG_ENABLE_MODULES = YES; 240 | CLANG_ENABLE_OBJC_ARC = YES; 241 | CLANG_WARN_BOOL_CONVERSION = YES; 242 | CLANG_WARN_CONSTANT_CONVERSION = YES; 243 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INT_CONVERSION = YES; 247 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 248 | CLANG_WARN_UNREACHABLE_CODE = YES; 249 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 250 | CODE_SIGN_IDENTITY = "-"; 251 | COPY_PHASE_STRIP = NO; 252 | DEBUG_INFORMATION_FORMAT = dwarf; 253 | ENABLE_STRICT_OBJC_MSGSEND = YES; 254 | ENABLE_TESTABILITY = YES; 255 | GCC_C_LANGUAGE_STANDARD = gnu99; 256 | GCC_DYNAMIC_NO_PIC = NO; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_OPTIMIZATION_LEVEL = 0; 259 | GCC_PREPROCESSOR_DEFINITIONS = ( 260 | "DEBUG=1", 261 | "$(inherited)", 262 | ); 263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 265 | GCC_WARN_UNDECLARED_SELECTOR = YES; 266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 267 | GCC_WARN_UNUSED_FUNCTION = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | MACOSX_DEPLOYMENT_TARGET = 10.10; 270 | MTL_ENABLE_DEBUG_INFO = YES; 271 | ONLY_ACTIVE_ARCH = YES; 272 | SDKROOT = macosx; 273 | }; 274 | name = Debug; 275 | }; 276 | 3F9A4BB31C6612AD0013F9B1 /* Release */ = { 277 | isa = XCBuildConfiguration; 278 | buildSettings = { 279 | ALWAYS_SEARCH_USER_PATHS = NO; 280 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 281 | CLANG_CXX_LIBRARY = "libc++"; 282 | CLANG_ENABLE_MODULES = YES; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_WARN_BOOL_CONVERSION = YES; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 287 | CLANG_WARN_EMPTY_BODY = YES; 288 | CLANG_WARN_ENUM_CONVERSION = YES; 289 | CLANG_WARN_INT_CONVERSION = YES; 290 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 291 | CLANG_WARN_UNREACHABLE_CODE = YES; 292 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 293 | CODE_SIGN_IDENTITY = "-"; 294 | COPY_PHASE_STRIP = NO; 295 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 296 | ENABLE_NS_ASSERTIONS = NO; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | GCC_C_LANGUAGE_STANDARD = gnu99; 299 | GCC_NO_COMMON_BLOCKS = YES; 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | MACOSX_DEPLOYMENT_TARGET = 10.10; 307 | MTL_ENABLE_DEBUG_INFO = NO; 308 | SDKROOT = macosx; 309 | }; 310 | name = Release; 311 | }; 312 | 3F9A4BB51C6612AD0013F9B1 /* Debug */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | CODE_SIGN_IDENTITY = "Mac Developer"; 316 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; 317 | INFOPLIST_FILE = test/Info.plist; 318 | MODULE_NAME = acme.test; 319 | MODULE_START = test_start; 320 | MODULE_STOP = test_stop; 321 | MODULE_VERSION = 1.0.0d1; 322 | PRODUCT_BUNDLE_IDENTIFIER = acme.test; 323 | PRODUCT_NAME = "$(TARGET_NAME)"; 324 | PROVISIONING_PROFILE = ""; 325 | WRAPPER_EXTENSION = kext; 326 | }; 327 | name = Debug; 328 | }; 329 | 3F9A4BB61C6612AD0013F9B1 /* Release */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | CODE_SIGN_IDENTITY = "Mac Developer"; 333 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; 334 | INFOPLIST_FILE = test/Info.plist; 335 | MODULE_NAME = acme.test; 336 | MODULE_START = test_start; 337 | MODULE_STOP = test_stop; 338 | MODULE_VERSION = 1.0.0d1; 339 | PRODUCT_BUNDLE_IDENTIFIER = acme.test; 340 | PRODUCT_NAME = "$(TARGET_NAME)"; 341 | PROVISIONING_PROFILE = ""; 342 | WRAPPER_EXTENSION = kext; 343 | }; 344 | name = Release; 345 | }; 346 | /* End XCBuildConfiguration section */ 347 | 348 | /* Begin XCConfigurationList section */ 349 | 3F98FC9D1C6D1260006671EE /* Build configuration list for PBXNativeTarget "victim" */ = { 350 | isa = XCConfigurationList; 351 | buildConfigurations = ( 352 | 3F98FC9B1C6D1260006671EE /* Debug */, 353 | 3F98FC9C1C6D1260006671EE /* Release */, 354 | ); 355 | defaultConfigurationIsVisible = 0; 356 | defaultConfigurationName = Release; 357 | }; 358 | 3F9A4BA61C6612AD0013F9B1 /* Build configuration list for PBXProject "test" */ = { 359 | isa = XCConfigurationList; 360 | buildConfigurations = ( 361 | 3F9A4BB21C6612AD0013F9B1 /* Debug */, 362 | 3F9A4BB31C6612AD0013F9B1 /* Release */, 363 | ); 364 | defaultConfigurationIsVisible = 0; 365 | defaultConfigurationName = Release; 366 | }; 367 | 3F9A4BB41C6612AD0013F9B1 /* Build configuration list for PBXNativeTarget "test" */ = { 368 | isa = XCConfigurationList; 369 | buildConfigurations = ( 370 | 3F9A4BB51C6612AD0013F9B1 /* Debug */, 371 | 3F9A4BB61C6612AD0013F9B1 /* Release */, 372 | ); 373 | defaultConfigurationIsVisible = 0; 374 | defaultConfigurationName = Release; 375 | }; 376 | /* End XCConfigurationList section */ 377 | }; 378 | rootObject = 3F9A4BA31C6612AD0013F9B1 /* Project object */; 379 | } 380 | -------------------------------------------------------------------------------- /test/test.c: -------------------------------------------------------------------------------- 1 | // 2 | // test.c 3 | // test 4 | // 5 | // Created by eyakovlev on 06.02.16. 6 | // Copyright © 2016 acme. All rights reserved. 7 | // 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include "test.h" 32 | #include "sysent.h" 33 | #include "resolver.h" 34 | 35 | #define MSR_EFER 0xc0000080 /* extended feature register */ 36 | #define MSR_STAR 0xc0000081 /* legacy mode SYSCALL target */ 37 | #define MSR_LSTAR 0xc0000082 /* long mode SYSCALL target */ 38 | #define MSR_CSTAR 0xc0000083 /* compat mode SYSCALL target */ 39 | 40 | #define INVALID_VADDR ((uintptr_t)-1) 41 | 42 | #if !defined(assert) 43 | # define assert(cond) \ 44 | ((void) ((cond) ? 0 : panic("assertion failed: %s", # cond))) 45 | #endif 46 | 47 | OSMallocTag g_tag = NULL; 48 | lck_grp_t* g_lock_group = NULL; 49 | 50 | // Traced task context 51 | static task_t g_task = NULL; 52 | static int32_t g_pid = 0; // PID we will protect, set through sysctl node 53 | static int g_unhook = 0; // Dummy sysctl node var to unhook everything before exiting 54 | 55 | // Hooked syscall tables 56 | static void* g_sysent_table = NULL; 57 | static void* g_mach_trap_table = NULL; 58 | 59 | // Private kernel symbols manually resolved on kext start 60 | static task_t(*proc_task)(proc_t) = NULL; 61 | static ipc_space_t(*get_task_ipcspace)(task_t) = NULL; 62 | static task_t(*port_name_to_task)(mach_port_name_t) = NULL; 63 | 64 | static lck_mtx_t* g_task_lock = NULL; 65 | 66 | static void* sysent_get_call(int callnum) { 67 | switch(version_major) { 68 | case 14: return ((struct sysent_yosemite*)g_sysent_table)[callnum].sy_call; 69 | case 13: return ((struct sysent_mavericks*)g_sysent_table)[callnum].sy_call; 70 | default: return ((struct sysent*)g_sysent_table)[callnum].sy_call; 71 | } 72 | } 73 | 74 | static void sysent_set_call(int callnum, void* sy_call) { 75 | switch(version_major) { 76 | case 14: ((struct sysent_yosemite*)g_sysent_table)[callnum].sy_call = sy_call; break; 77 | case 13: ((struct sysent_mavericks*)g_sysent_table)[callnum].sy_call = sy_call; break; 78 | default: ((struct sysent*)g_sysent_table)[callnum].sy_call = sy_call; break; 79 | } 80 | } 81 | 82 | static void* sysent_hook_call(int callnum, void* hook) { 83 | void* orig = sysent_get_call(callnum); 84 | sysent_set_call(callnum, hook); 85 | return orig; 86 | } 87 | 88 | static void* mach_table_get_trap(int trapnum) { 89 | if (version_major >= 13) { 90 | return ((mach_trap_mavericks_t*)g_mach_trap_table)[trapnum].mach_trap_function; 91 | } else { 92 | return ((mach_trap_t*)g_mach_trap_table)[trapnum].mach_trap_function; 93 | } 94 | } 95 | 96 | static void mach_table_set_trap(int trapnum, void* trap_function) { 97 | if (version_major >= 13) { 98 | ((mach_trap_mavericks_t*)g_mach_trap_table)[trapnum].mach_trap_function = trap_function; 99 | } else { 100 | ((mach_trap_t*)g_mach_trap_table)[trapnum].mach_trap_function = trap_function; 101 | } 102 | } 103 | 104 | static void* mach_table_hook_trap(int trapnum, void* hook) { 105 | void* orig = mach_table_get_trap(trapnum); 106 | mach_table_set_trap(trapnum, hook); 107 | return orig; 108 | } 109 | 110 | static uint64_t rdmsr(uint32_t index) 111 | { 112 | uint32_t lo=0, hi=0; 113 | __asm__ __volatile__ ("rdmsr" : "=a"(lo), "=d"(hi) : "c"(index)); 114 | return (((uint64_t)hi) << 32) | ((uint64_t)lo); 115 | } 116 | 117 | // Clear CR0 page read only protection bit 118 | static void disable_vm_protection(void) 119 | { 120 | __asm__ __volatile__( 121 | "cli \n\t" \ 122 | "mov %%cr0, %%rax \n\t" \ 123 | "and $0xfffffffffffeffff, %%rax \n\t" \ 124 | "mov %%rax, %%cr0 \n\t" \ 125 | "sti \n\t" 126 | :::"rax" 127 | ); 128 | } 129 | 130 | // Set CR0 page read only protection bit 131 | static void enable_vm_protection(void) 132 | { 133 | __asm__ __volatile__( 134 | "cli \n\t" \ 135 | "mov %%cr0, %%rax \n\t" \ 136 | "or $0x10000, %%rax \n\t" \ 137 | "mov %%rax, %%cr0 \n\t" \ 138 | "sti \n\t" 139 | :::"rax" 140 | ); 141 | } 142 | 143 | // Finds and returns 64bit loaded kernel base address or INVALID_VADDR if failed 144 | static uintptr_t find_kernel_base(void) 145 | { 146 | // In case of ASLR kernel find real kernel base. 147 | // For that dump MSR_LSTAR which contains a pointer to kernel syscall handler 148 | uint64_t ptr = rdmsr(MSR_LSTAR); 149 | 150 | // Round up to next page boundary - kernel should start at a page boundary ASLR or no ALSR 151 | ptr = ptr & ~PAGE_MASK_64; 152 | while (ptr) { 153 | if (*(uint32_t*)ptr == MH_MAGIC_64) { 154 | return ptr; 155 | } 156 | 157 | ptr -= PAGE_SIZE; 158 | } 159 | 160 | return INVALID_VADDR; 161 | } 162 | 163 | // Matches sysent table in memory at given address 164 | static int is_sysent_table(uintptr_t addr) 165 | { 166 | #define sysent_verify(_sysent) \ 167 | ((_sysent)[SYS_exit].sy_narg == 1 && \ 168 | (_sysent)[SYS_fork].sy_narg == 0 && \ 169 | (_sysent)[SYS_read].sy_narg == 3 && \ 170 | (_sysent)[SYS_wait4].sy_narg == 4 && \ 171 | (_sysent)[SYS_ptrace].sy_narg == 4) 172 | 173 | if (version_major == 14) { 174 | struct sysent_yosemite* sysent = (struct sysent_yosemite*)addr; 175 | return sysent_verify(sysent); 176 | } else if (version_major == 13) { 177 | struct sysent_mavericks* sysent = (struct sysent_mavericks*)addr; 178 | return sysent_verify(sysent); 179 | } else { 180 | struct sysent* sysent = (struct sysent*)addr; 181 | return sysent_verify(sysent); 182 | } 183 | 184 | #undef sysent_verify 185 | return FALSE; 186 | } 187 | 188 | // Matches mach trap table in memory at given address 189 | static int is_mach_trap_table(uintptr_t addr) 190 | { 191 | #define traps_verify(_traps) \ 192 | ((_traps)[0].mach_trap_arg_count == 0 && \ 193 | (_traps)[1].mach_trap_arg_count == 0 && \ 194 | (_traps)[MACH_MSG_TRAP].mach_trap_arg_count == 7 && \ 195 | (_traps)[MACH_MSG_OVERWRITE_TRAP].mach_trap_arg_count == 8) 196 | 197 | if (version_major >= 13) { 198 | mach_trap_mavericks_t* res = (mach_trap_mavericks_t*)addr; 199 | return traps_verify(res); 200 | } else { 201 | mach_trap_t* res = (mach_trap_t*)addr; 202 | return traps_verify(res); 203 | } 204 | 205 | #undef traps_verify 206 | return FALSE; 207 | } 208 | 209 | // Search kernel data segment for BSD sysent table and mach trap table 210 | static int find_syscall_tables(const struct segment_command_64* dataseg, void** psysent, void** pmach_traps) 211 | { 212 | assert(dataseg); 213 | assert(psysent); 214 | assert(pmach_traps); 215 | 216 | void* sysent = NULL; 217 | void* mach_traps = NULL; 218 | 219 | uintptr_t addr = dataseg->vmaddr; 220 | uint64_t size = dataseg->vmsize; 221 | 222 | while(size != 0) { 223 | 224 | if (!sysent && is_sysent_table(addr)) { 225 | sysent = (void*)addr; 226 | } 227 | 228 | if (!mach_traps && is_mach_trap_table(addr)) { 229 | mach_traps = (void*)addr; 230 | } 231 | 232 | if (sysent && mach_traps) { 233 | *psysent = sysent; 234 | *pmach_traps = mach_traps; 235 | return TRUE; 236 | } 237 | 238 | addr++; 239 | size--; 240 | } 241 | 242 | return FALSE; 243 | } 244 | 245 | // 246 | // Mach hooks 247 | // 248 | 249 | static mach_msg_return_t (*g_mach_msg_trap)(void* args) = NULL; 250 | static mach_msg_return_t (*g_mach_msg_overwrite_trap)(void* args) = NULL; 251 | 252 | #define MIG_TASK_TERMINATE_ID 3401 /* Taken from osfmk/mach/task.defs */ 253 | 254 | struct mach_msg_overwrite_trap_args { 255 | PAD_ARG_(user_addr_t, msg); 256 | PAD_ARG_(mach_msg_option_t, option); 257 | PAD_ARG_(mach_msg_size_t, send_size); 258 | PAD_ARG_(mach_msg_size_t, rcv_size); 259 | PAD_ARG_(mach_port_name_t, rcv_name); 260 | PAD_ARG_(mach_msg_timeout_t, timeout); 261 | PAD_ARG_(mach_port_name_t, notify); 262 | PAD_ARG_8 263 | PAD_ARG_(user_addr_t, rcv_msg); /* Unused on mach_msg_trap */ 264 | }; 265 | 266 | // User mode message header definition differs from in-kernel one 267 | typedef struct 268 | { 269 | mach_msg_bits_t msgh_bits; 270 | mach_msg_size_t msgh_size; 271 | __darwin_natural_t msgh_remote_port; 272 | __darwin_natural_t msgh_local_port; 273 | __darwin_natural_t msgh_voucher_port; 274 | mach_msg_id_t msgh_id; 275 | } mach_user_msg_header_t; 276 | 277 | mach_msg_return_t mach_msg_trap_common(struct mach_msg_overwrite_trap_args *args, mach_msg_return_t(*orig_handler)(void* args)) 278 | { 279 | if (!g_task || !(args->option & MACH_SEND_MSG)) { 280 | return orig_handler(args); 281 | } 282 | 283 | mach_user_msg_header_t hdr; 284 | if (args->send_size < sizeof(hdr)) { 285 | return MACH_SEND_MSG_TOO_SMALL; // "Sorry, your message is too small for this rootkit to process correctly" 286 | } 287 | 288 | copyin(args->msg, &hdr, sizeof(hdr)); 289 | task_t remote_task = port_name_to_task(hdr.msgh_remote_port); 290 | if (g_task == remote_task) { 291 | // TODO: also check if this is a task kernel port 292 | printf("my_mach_msg_trap: blocking task_terminate\n"); 293 | return MACH_SEND_INVALID_RIGHT; 294 | } 295 | 296 | return orig_handler(args); 297 | } 298 | 299 | // mach_msg_trap hook 300 | mach_msg_return_t my_mach_msg_trap(struct mach_msg_overwrite_trap_args *args) 301 | { 302 | return mach_msg_trap_common(args, g_mach_msg_trap); 303 | } 304 | 305 | // mach_msg_overwrite_trap hook 306 | mach_msg_return_t my_mach_msg_overwrite_trap(struct mach_msg_overwrite_trap_args *args) 307 | { 308 | return mach_msg_trap_common(args, g_mach_msg_overwrite_trap); 309 | } 310 | 311 | // 312 | // BSD kill(2) hook 313 | // 314 | 315 | static int(*g_orig_kill)(proc_t cp, void *uap, __unused int32_t *retval) = NULL; 316 | 317 | struct kill_args { 318 | char pid_l_[PADL_(int)]; int pid; char pid_r_[PADR_(int)]; 319 | char signum_l_[PADL_(int)]; int signum; char signum_r_[PADR_(int)]; 320 | char posix_l_[PADL_(int)]; int posix; char posix_r_[PADR_(int)]; 321 | }; 322 | 323 | int my_kill(proc_t cp, struct kill_args *uap, __unused int32_t *retval) 324 | { 325 | // Negative pid is a killpg case 326 | pid_t pid = (uap->pid > 0 ? uap->pid : -uap->pid); 327 | 328 | if (!g_pid || (pid != g_pid)) { 329 | return g_orig_kill(cp, uap, retval); 330 | } 331 | 332 | printf("signal %d from pid %d to pid %d, posix %d\n", uap->signum, proc_pid(cp), uap->pid, uap->posix); 333 | 334 | // TODO: process cannot ignore or handle SIGKILL so we intercept it here. 335 | // However there are other signals that will terminate a process if it doesn't handle or ignore these signals (i.e. SIGTERM) 336 | // We don't handle those here for now. 337 | if (uap->signum == SIGKILL || uap->signum == SIGTERM) { 338 | printf("blocking SIGKILL\n"); 339 | return EPERM; 340 | } 341 | 342 | return g_orig_kill(cp, uap, retval); 343 | } 344 | 345 | // 346 | // Entry and init 347 | // 348 | 349 | // kext uses sysctl nodes to communicate with the client: 350 | // 'debug.killhook.pid' - set 32bit pid value for process to protect 351 | // 'debug.killhook.unhook' - set to 1 to unhook all syscalls before unloading kext 352 | 353 | static int sysctl_killhook_pid SYSCTL_HANDLER_ARGS; 354 | static int sysctl_killhook_unhook SYSCTL_HANDLER_ARGS; 355 | 356 | SYSCTL_NODE(_debug, OID_AUTO, killhook, CTLFLAG_RW, 0, "kill hook API"); 357 | SYSCTL_PROC(_debug_killhook, OID_AUTO, pid, (CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE), &g_pid, 0, sysctl_killhook_pid, "I", ""); 358 | SYSCTL_PROC(_debug_killhook, OID_AUTO, unhook, (CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_SECURE), &g_unhook, 0, sysctl_killhook_unhook, "I", ""); 359 | 360 | static int sysctl_killhook_pid(struct sysctl_oid *oidp, void *arg1, int arg2, struct sysctl_req *req) 361 | { 362 | int32_t curPid = g_pid; 363 | int res = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); 364 | 365 | if (g_pid != curPid) { 366 | 367 | proc_t proc = proc_find(g_pid); 368 | if (proc) { 369 | g_task = proc_task(proc); 370 | proc_rele(proc); 371 | printf("PID changed to %d, task %p\n", g_pid, g_task); 372 | } 373 | } 374 | 375 | return res; 376 | } 377 | 378 | static int syscalls_hooked(void) 379 | { 380 | return ((mach_table_get_trap(MACH_MSG_OVERWRITE_TRAP) == my_mach_msg_overwrite_trap) && 381 | (mach_table_get_trap(MACH_MSG_TRAP) == my_mach_msg_trap) && 382 | (sysent_get_call(SYS_kill) == my_kill)); 383 | } 384 | 385 | static int sysctl_killhook_unhook(struct sysctl_oid *oidp, void *arg1, int arg2, struct sysctl_req *req) 386 | { 387 | int res = sysctl_handle_int(oidp, oidp->oid_arg1, oidp->oid_arg2, req); 388 | if (g_unhook && syscalls_hooked()) 389 | { 390 | // Unhook syscalls 391 | // See comments in test_stop why this is done in sysctl handler 392 | disable_vm_protection(); 393 | { 394 | mach_table_set_trap(MACH_MSG_OVERWRITE_TRAP, g_mach_msg_overwrite_trap); 395 | mach_table_set_trap(MACH_MSG_TRAP, g_mach_msg_trap); 396 | sysent_set_call(SYS_kill, (sy_call_t*)g_orig_kill); 397 | } 398 | enable_vm_protection(); 399 | } 400 | 401 | return res; 402 | } 403 | 404 | kern_return_t test_start(kmod_info_t * ki, void *d) 405 | { 406 | g_tag = OSMalloc_Tagalloc("test.kext", OSMT_DEFAULT); 407 | if (!g_tag) { 408 | printf("Failed to allocate OSMalloc tag\n"); 409 | return KERN_FAILURE; 410 | } 411 | 412 | g_lock_group = lck_grp_alloc_init("test.kext", LCK_GRP_ATTR_NULL); 413 | if (!g_lock_group) { 414 | printf("Failed to create lock group\n"); 415 | return KERN_FAILURE; 416 | } 417 | 418 | g_task_lock = lck_mtx_alloc_init(g_lock_group, LCK_ATTR_NULL); 419 | if (!g_task_lock) { 420 | printf("Failed to create lock group\n"); 421 | return KERN_FAILURE; 422 | } 423 | 424 | // 425 | // We will attempt to hook sysent table to intercept syscalls we are interested in 426 | // For that we will find kernel base address, find data segment in kernel mach-o headers 427 | // and finally search for sysent pattern in data segment 428 | // 429 | 430 | uintptr_t kernel_base = find_kernel_base(); 431 | if (kernel_base == INVALID_VADDR) { 432 | printf("Can't find kernel base address\n"); 433 | return KERN_FAILURE; 434 | } 435 | 436 | struct mach_header_64* kernel_hdr = (struct mach_header_64*)kernel_base; 437 | if (kernel_hdr->magic != MH_MAGIC_64) { 438 | printf("Wrong kernel header\n"); 439 | return KERN_FAILURE; 440 | } 441 | 442 | printf("kernel base @ %p\n", kernel_hdr); 443 | 444 | // Resolve some private symbols we're going to need 445 | proc_task = resolve_kernel_symbol("_proc_task", kernel_base); 446 | get_task_ipcspace = resolve_kernel_symbol("_get_task_ipcspace", kernel_base); 447 | port_name_to_task = resolve_kernel_symbol("_port_name_to_task", kernel_base); 448 | if (!proc_task || !get_task_ipcspace || !port_name_to_task) { 449 | printf("Could not resolve private symbols\n"); 450 | return KERN_FAILURE; 451 | } 452 | 453 | struct segment_command_64* dataseg = find_segment_64(kernel_hdr, SEG_DATA); 454 | if (!dataseg) { 455 | printf("Can't find kernel data segment\n"); 456 | return KERN_FAILURE; 457 | } 458 | 459 | printf("kernel data segment @ 0x%llx, %llu bytes\n", dataseg->vmaddr, dataseg->vmsize); 460 | 461 | // TODO: non-yosemite structures 462 | if (!find_syscall_tables(dataseg, &g_sysent_table, &g_mach_trap_table)) { 463 | printf("Can't find syscall tables\n"); 464 | return KERN_FAILURE; 465 | } 466 | 467 | printf("sysent @ %p\n", g_sysent_table); 468 | printf("mach trap table @ %p\n", g_mach_trap_table); 469 | 470 | // sysent is in read-only memory since 10.8. 471 | // good thing that intel architecture allows us to disable vm write protection completely from ring0 with a CR0 bit 472 | disable_vm_protection(); 473 | { 474 | g_orig_kill = sysent_hook_call(SYS_kill, (sy_call_t*)my_kill); 475 | g_mach_msg_trap = mach_table_hook_trap(MACH_MSG_TRAP, my_mach_msg_trap); 476 | g_mach_msg_overwrite_trap = mach_table_hook_trap(MACH_MSG_OVERWRITE_TRAP, my_mach_msg_overwrite_trap); 477 | } 478 | enable_vm_protection(); 479 | 480 | sysctl_register_oid(&sysctl__debug_killhook); 481 | sysctl_register_oid(&sysctl__debug_killhook_pid); 482 | sysctl_register_oid(&sysctl__debug_killhook_unhook); 483 | 484 | return KERN_SUCCESS; 485 | } 486 | 487 | kern_return_t test_stop(kmod_info_t *ki, void *d) 488 | { 489 | // At this point a pointer to one of our hooked syscall may already be loaded by unix_syscall64 490 | // which leads to a race condition with our unload process (in-flight syscall may execute unloaded kext code) 491 | // This is a bad situation we can't really do anything about since we're not a part of syscall implementation path. 492 | // Disabling interrupts won't help since syscalls can be in flight on another core. 493 | // For now the best thing i can think of is to do unhook separetely, using a sysctl node and then unloading 494 | if (syscalls_hooked()) { 495 | printf("Please unhook syscalls before unloading (debug.killhook.unhook)\n"); 496 | return KERN_ABORTED; 497 | } 498 | 499 | sysctl_unregister_oid(&sysctl__debug_killhook); 500 | sysctl_unregister_oid(&sysctl__debug_killhook_pid); 501 | sysctl_unregister_oid(&sysctl__debug_killhook_unhook); 502 | 503 | lck_mtx_free(g_task_lock, g_lock_group); 504 | lck_grp_free(g_lock_group); 505 | 506 | OSMalloc_Tagfree(g_tag); 507 | 508 | return KERN_SUCCESS; 509 | } 510 | --------------------------------------------------------------------------------