├── 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 |
--------------------------------------------------------------------------------