├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── misc
├── control
├── ent.plist
└── template.tar
└── src
├── lib
├── arch.h
├── debug.c
├── debug.h
├── libkern.c
├── libkern.h
└── mach-o.h
└── tools
├── kdump.c
├── kinfo.c
├── kmap.c
├── kmem.c
├── kpatch.c
└── nvpatch.c
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /bin
3 | /obj
4 | /pkg
5 | /*.a
6 | /*.tar.xz
7 | /*.deb
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Samuel Groß
4 | Copyright (c) 2016 Pupyshev Nikita
5 | Copyright (c) 2016-2017 Siguza
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy of
8 | this software and associated documentation files (the "Software"), to deal in
9 | the Software without restriction, including without limitation the rights to
10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11 | the Software, and to permit persons to whom the Software is furnished to do so,
12 | subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | VERSION = 1.4.1
2 | PKGNAME = net.siguza.ios-kern-utils
3 | BINDIR = bin
4 | OBJDIR = obj
5 | SRCDIR = src
6 | ALL = $(patsubst $(SRCDIR)/tools/%.c,%,$(wildcard $(SRCDIR)/tools/*.c))
7 | LIB = kutil
8 | PKG = pkg
9 | XZ = ios-kern-utils.tar.xz
10 | DEB = $(PKGNAME)_$(VERSION)_iphoneos-arm.deb
11 |
12 | # Constants
13 | GCC_FLAGS = -std=gnu99 -O3 -Wall -I$(SRCDIR)/lib
14 | LD_FLAGS = -L. -l$(LIB) -framework CoreFoundation -Wl,-dead_strip
15 |
16 | # Universal defaults
17 | LIBTOOL_FLAGS ?= -static
18 | IOS_GCC_ARCH ?= -arch armv7 -arch arm64 -miphoneos-version-min=6.0
19 | MACOS_GCC_ARCH ?= -arch x86_64 -mmacosx-version-min=10.10
20 | IOS_GCC_FLAGS ?= $(GCC_FLAGS)
21 | MACOS_GCC_FLAGS ?= $(GCC_FLAGS)
22 | IOS_LD_FLAGS ?= $(LD_FLAGS)
23 | MACOS_LD_FLAGS ?= $(LD_FLAGS)
24 |
25 | # Host-specific defaults
26 | # H_{HOST}_{TARGET}_{THING}
27 | H_MACOS_LIBTOOL = libtool
28 | H_MACOS_LIPO = lipo
29 | H_MACOS_STRIP = strip
30 | H_MACOS_SIGN = codesign
31 | H_MACOS_SIGN_FLAGS = -s - --entitlements misc/ent.plist
32 | H_MACOS_IOS_GCC = xcrun -sdk iphoneos gcc
33 | H_MACOS_MACOS_GCC = xcrun -sdk macosx gcc
34 |
35 | H_IOS_LIBTOOL = libtool
36 | H_IOS_LIPO = lipo
37 | H_IOS_STRIP = strip
38 | H_IOS_SIGN = ldid
39 | H_IOS_SIGN_FLAGS = -Smisc/ent.plist
40 | H_IOS_IOS_GCC = clang
41 |
42 | H_UNIX_SIGN = ldid
43 | H_UNIX_SIGN_FLAGS = -Smisc/ent.plist
44 |
45 | ifeq ($(shell uname -s),Darwin)
46 | ifneq ($(HOSTTYPE),arm)
47 | HOST = MACOS
48 | else
49 | HOST = IOS
50 | endif
51 | else
52 | HOST = UNIX
53 | endif
54 |
55 | # Employ defaults if desirable
56 | LIBTOOL ?= $(H_$(HOST)_LIBTOOL)
57 | LIPO ?= $(H_$(HOST)_LIPO)
58 | STRIP ?= $(H_$(HOST)_STRIP)
59 | SIGN ?= $(H_$(HOST)_SIGN)
60 | SIGN_FLAGS ?= $(H_$(HOST)_SIGN_FLAGS)
61 | IOS_GCC ?= $(H_$(HOST)_IOS_GCC)
62 | MACOS_GCC ?= $(H_$(HOST)_MACOS_GCC)
63 |
64 | #ifndef IGCC
65 | # ifeq ($(shell uname -s),Darwin)
66 | # ifneq ($(HOSTTYPE),arm)
67 | # IGCC = xcrun -sdk iphoneos gcc
68 | # else
69 | # IGCC = clang
70 | # endif
71 | # LD_FLAGS += -Wl,-dead_strip
72 | # else
73 | # IGCC = ios-clang
74 | # LD_FLAGS += -Wl,--gc-sections
75 | # endif
76 | #endif
77 | #ifndef IGCC_ARCH
78 | # IGCC_ARCH = -arch armv7 -arch arm64 -miphoneos-version-min=6.0
79 | #endif
80 | ## We need libtool here because ar can't deal with fat libraries
81 | #ifndef LIBTOOL
82 | # ifeq ($(shell uname -s),Darwin)
83 | # ifneq ($(HOSTTYPE),arm)
84 | # LIBTOOL = xcrun -sdk iphoneos libtool
85 | # else
86 | # LIBTOOL = libtool
87 | # endif
88 | # else
89 | # LIBTOOL = ios-libtool
90 | # endif
91 | #endif
92 | #ifndef STRIP
93 | # ifeq ($(shell uname -s),Darwin)
94 | # ifneq ($(HOSTTYPE),arm)
95 | # STRIP = xcrun -sdk iphoneos strip
96 | # else
97 | # STRIP = $(shell which strip 2>/dev/null)
98 | # endif
99 | # else
100 | # STRIP := $(shell which ios-strip 2>/dev/null)
101 | # endif
102 | #endif
103 | #ifndef SIGN
104 | # ifeq ($(shell uname -s),Darwin)
105 | # ifneq ($(HOSTTYPE),arm)
106 | # SIGN = codesign
107 | # else
108 | # SIGN = ldid
109 | # endif
110 | # else
111 | # SIGN = ldid
112 | # endif
113 | #endif
114 | #ifndef SIGN_FLAGS
115 | # ifeq ($(SIGN),codesign)
116 | # SIGN_FLAGS = -s - --entitlements misc/ent.plist
117 | # else
118 | # ifeq ($(SIGN),ldid)
119 | # SIGN_FLAGS = -Smisc/ent.plist
120 | # endif
121 | # endif
122 | #endif
123 |
124 | #SUFFIXES =
125 | #ifdef IOS_GCC
126 | # SUFFIXES := $(SUFFIXES) ios
127 | #endif
128 | #ifdef MACOS_GCC
129 | # SUFFIXES := $(SUFFIXES) macos
130 | #endif
131 |
132 | SUF_all = ios macos
133 | SUF_ios = ios
134 | SUF_macos = macos
135 |
136 | SUFFIXES :=
137 | ifdef TARGET
138 | SUFFIXES := $(SUF_$(TARGET))
139 | endif
140 | ifndef SUFFIXES
141 | ifdef IOS_GCC
142 | SUFFIXES := $(SUFFIXES) $(SUF_ios)
143 | endif
144 | ifdef MACOS_GCC
145 | SUFFIXES := $(SUFFIXES) $(SUF_macos)
146 | endif
147 | endif
148 |
149 | .PHONY: help all lib dist xz deb clean
150 |
151 | all: $(addprefix $(BINDIR)/, $(ALL))
152 |
153 | lib: lib$(LIB).a
154 |
155 | help:
156 | @echo 'Usage:'
157 | @echo ' TARGET=all make Build for all architectures'
158 | @echo ' TARGET=ios make Build for iOS only'
159 | @echo ' TARGET=macos make Build for macOS only'
160 | @echo ''
161 | @echo 'Targets:'
162 | @echo ' all Build everything'
163 | @echo ' lib Build lib$(LIB) only'
164 | @echo ' dist xz + deb'
165 | @echo ' xz Create xz tarball'
166 | @echo ' deb Create deb for dpkg/Cydia'
167 | @echo ' clean Clean up'
168 | @echo ''
169 | @echo 'Variables:'
170 | @echo ' CFLAGS Passed during all phases of compilation'
171 | @echo ' LDFLAGS Passed during linking phase only'
172 | @echo ' IOS_GCC Compiler targeting iOS'
173 | @echo ' IOS_GCC_FLAGS Passed to iOS compiler only'
174 | @echo ' IOS_LD_FLAGS Passed to iOS linker only'
175 | @echo ' MACOS_GCC Compiler targeting macOS'
176 | @echo ' MACOS_GCC_FLAGS Passed to macOS compiler only'
177 | @echo ' MACOS_LD_FLAGS Passed to macOS linker only'
178 | @echo ' LIBTOOL Not to be confused with GNU libtool'
179 | @echo ' LIBTOOL_FLAGS'
180 | @echo ' LIPO'
181 | @echo ' STRIP'
182 | @echo ' SIGN Code signing utility (only used for iOS)'
183 | @echo ' SIGN_FLAGS Must include path to entitlements file'
184 | @echo ' '
185 | @echo 'Variables you should never have to touch:'
186 | @echo ' IOS_GCC_ARCH iOS architecture flags'
187 | @echo ' MACOS_GCC_ARCH macOS architecture flags'
188 |
189 | $(BINDIR)/%: $(addprefix $(OBJDIR)/%., $(SUFFIXES)) | $(BINDIR)
190 | $(LIPO) -create -output $@ $^
191 |
192 | #$(BINDIR)/%: lib$(LIB).a $(SRCDIR)/tools/%.c | $(BINDIR)
193 | # $(IGCC) -o $@ $(IGCC_FLAGS) $(IGCC_ARCH) $(LD_FLAGS) $(LD_LIBS) $(SRCDIR)/tools/$(@F).c
194 | #ifdef STRIP
195 | # $(STRIP) $@
196 | #endif
197 | # $(SIGN) $(SIGN_FLAGS) $@
198 |
199 | $(OBJDIR)/%.ios: $(SRCDIR)/tools/%.c lib$(LIB).a | $(OBJDIR)
200 | $(IOS_GCC) -o $@ $(IOS_GCC_FLAGS) $(CFLAGS) $(IOS_LD_FLAGS) $(LDFLAGS) $(IOS_GCC_ARCH) $<
201 | $(STRIP) $@
202 | $(SIGN) $(SIGN_FLAGS) $@
203 |
204 | $(OBJDIR)/%.macos: $(SRCDIR)/tools/%.c lib$(LIB).a | $(OBJDIR)
205 | $(MACOS_GCC) -o $@ $(MACOS_GCC_FLAGS) $(CFLAGS) $(MACOS_LD_FLAGS) $(LDFLAGS) $(MACOS_GCC_ARCH) $<
206 | $(STRIP) $@
207 |
208 | lib$(LIB).a: $(patsubst $(SRCDIR)/lib/%.c,$(OBJDIR)/%.o,$(wildcard $(SRCDIR)/lib/*.c))
209 | $(LIBTOOL) $(LIBTOOL_FLAGS) -o $@ $^
210 |
211 | #$(OBJDIR)/%.o: $(SRCDIR)/lib/%.c | $(OBJDIR)
212 | # $(IGCC) -c -o $@ $(IGCC_FLAGS) $(IGCC_ARCH) $<
213 |
214 | $(OBJDIR)/%.ios.o: $(SRCDIR)/lib/%.c | $(OBJDIR)
215 | $(IOS_GCC) -c -o $@ $(IOS_GCC_FLAGS) $(CFLAGS) $(IOS_GCC_ARCH) $<
216 |
217 | $(OBJDIR)/%.macos.o: $(SRCDIR)/lib/%.c | $(OBJDIR)
218 | $(MACOS_GCC) -c -o $@ $(MACOS_GCC_FLAGS) $(CFLAGS) $(MACOS_GCC_ARCH) $<
219 |
220 | $(OBJDIR)/%.o: $(addsuffix .o, $(addprefix $(OBJDIR)/%., $(SUFFIXES)))
221 | $(LIPO) -create -output $@ $^
222 |
223 | $(BINDIR):
224 | mkdir -p $(BINDIR)
225 |
226 | $(OBJDIR):
227 | mkdir -p $(OBJDIR)
228 |
229 | dist: xz deb
230 |
231 | xz: $(XZ)
232 |
233 | deb: $(DEB)
234 |
235 | $(XZ): $(addprefix $(BINDIR)/, $(ALL))
236 | tar -cJf $(XZ) -C $(BINDIR) $(ALL)
237 |
238 | $(DEB): $(PKG)/control.tar.gz $(PKG)/data.tar.lzma $(PKG)/debian-binary
239 | ( cd "$(PKG)"; ar -cr "../$(DEB)" 'debian-binary' 'control.tar.gz' 'data.tar.lzma'; )
240 |
241 | $(PKG)/control.tar.gz: $(PKG)/control
242 | tar -czf '$(PKG)/control.tar.gz' --exclude '.DS_Store' --exclude '._*' --exclude 'control.tar.gz' --include '$(PKG)' --include '$(PKG)/control' -s '%^$(PKG)%.%' $(PKG)
243 |
244 | $(PKG)/data.tar.lzma: $(addprefix $(BINDIR)/, $(ALL)) | $(PKG) #misc/template.tar
245 | tar -c --lzma -f '$(PKG)/data.tar.lzma' --exclude '.DS_Store' --exclude '._*' -s '%^$(BINDIR)%./usr/bin%' @misc/template.tar $(BINDIR)
246 |
247 | $(PKG)/debian-binary: $(addprefix $(BINDIR)/, $(ALL)) | $(PKG)
248 | echo '2.0' > "$(PKG)/debian-binary"
249 |
250 | $(PKG)/control: misc/control | $(PKG)
251 | ( echo "Version: $(VERSION)"; cat misc/control; ) > $(PKG)/control
252 |
253 | $(PKG):
254 | mkdir -p $(PKG)
255 |
256 | clean:
257 | rm -rf $(BINDIR) $(OBJDIR) lib$(LIB).a $(PKG) $(XZ) $(PKGNAME)_*_iphoneos-arm.deb
258 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iOS Kernel Utilities
2 |
3 | Beware, chances are the device will panic and reboot.
4 |
5 | ### Download
6 |
7 | Just want the binaries?
8 | Head over to [Releases](https://github.com/Siguza/ios-kern-utils/releases). :)
9 |
10 | ### Prerequisites
11 |
12 | * Jailbroken Device
13 | * `tfp0` kernel patch (see below)
14 | * If you don't have XCode:
15 | * GNU make
16 | * C compiler for iOS
17 | * Code signing utility
18 |
19 | ### `tfp0` compatibility
20 |
21 | A kernel patch is required for these tools to work, since access to kernel memory is natively unavailable for obvious reasons.
22 | That patch is normally referred to as `task-for-pid-zero` (short `tfp0`), and is included in almost every public jailbreak.
23 |
24 | The latest release of these tools is confirmed to work with:
25 |
26 | * p0sixspwn on 6.1.x
27 | * Pangu on 7.1.x
28 | * TaiG on 8.4
29 | * Pangu9 on 9.1
30 | * [qwertyoruiop's jailbreakme](https://jbme.qwertyoruiop.com/) on 9.3.x
31 | * extra_recipe on 10.0-10.2
32 | * Yalu102 (beta4 or later) on 10.0.1-10.2
33 |
34 | Jailbreaks that **DO NOT** seem to enable `tfp0`, and thus **DO NOT** work with kern-utils:
35 |
36 | * Pangu9 on 9.0.x (but can be enabled with [cl0ver](https://github.com/Siguza/cl0ver))
37 | * Pangu9 on 9.2-9.3.3 (but see qwertyoruiop's jailbreakme)
38 | * YaluX on 10.0.1-10.1.1
39 |
40 | If you have information about how the kernel task port can be obtained in these versions, please [open a ticket](https://github.com/Siguza/ios-kern-utils/issues/new) and tell me.
41 |
42 | ### Tools
43 |
44 | Name | Function
45 | :-------: | :------------------------------------------------
46 | `kdump` | Dump a running iOS kernel to a file
47 | `kinfo` | Display various kernel information
48 | `kmap` | Visualize the kernel address space
49 | `kmem` | Dump kernel memory to the console
50 | `kpatch` | Apply patches to a running kernel
51 | `nvpatch` | Display and patch NVRAM variables permissions
52 |
53 | ### Building
54 |
55 | git clone https://github.com/Siguza/ios-kern-utils
56 | cd ios-kern-utils
57 | make # build just the binaries
58 | make deb # build a deb file for Cydia
59 | make xz # package binaries to a .tar.xz
60 | make dist # deb && xz
61 |
62 | For `make` you may also specify the following environment variables:
63 |
64 |
65 |
66 | Name |
67 | Function |
68 | Default value |
69 |
70 |
71 | OS X |
72 | iOS |
73 | Linux |
74 |
75 |
76 | IGCC |
77 | iOS compiler |
78 | xcrun -sdk iphoneos gcc |
79 | clang |
80 | ios-clang |
81 |
82 |
83 | IGCC_ARCH |
84 | Target architecture(s) |
85 | -arch armv7 -arch arm64 |
86 |
87 |
88 | IGCC_FLAGS |
89 | Custom compiler flags |
90 | none |
91 |
92 |
93 | LIBTOOL |
94 | Archive manipulation utility |
95 | xcrun -sdk iphoneos libtool |
96 | libtool |
97 | ios-libtool |
98 |
99 |
100 | STRIP |
101 | Symbol remover utility |
102 | xcrun -sdk iphoneos strip |
103 | strip |
104 | ios-strip |
105 |
106 |
107 | SIGN |
108 | Code signing utility |
109 | codesign |
110 | ldid |
111 |
112 |
113 | SIGN_FLAGS |
114 | Code signing flags |
115 | -s - --entitlements misc/ent.xml |
116 | -Smisc/ent.xml |
117 |
118 |
119 |
120 | ### macOS
121 |
122 | As of late, kern-utils can also be compiled for and used on macOS.
123 | Compile with:
124 |
125 | IGCC=gcc IGCC_ARCH='-arch x86_64' SIGN=true STRIP=strip LIBTOOL=libtool make clean all
126 |
127 | The `SIGN=true` is a dirty hack to skip signing, which is necessary because Sierra and later will not allow self-signed binaries with restricted entitlements to run. However, entitlements aren't needed on macOS since the kernel task port is obtained via a different API [very much thanks to Jonathan Levin](http://newosxbook.com/articles/PST2.html).
128 |
129 | In order to use kern-utils, SIP needs to be at least partially disabled. If you don't want to disable it completely, you can use:
130 |
131 | csrutil enable --without debug
132 |
133 | ### License
134 |
135 | [MIT](https://github.com/Siguza/cl0ver/blob/master/LICENSE).
136 |
137 | [Original project](https://github.com/saelo/ios-kern-utils) by [Samuel Groß](https://github.com/saelo).
138 | `nvpatch` is largely based on [`nvram_patcher`](https://github.com/realnp/nvram_patcher) by [Pupyshev Nikita](https://github.com/realnp).
139 | Maintained and updated for iOS 8 and later by [Siguza](https://github.com/Siguza).
140 |
141 | ### TODO
142 |
143 | * Test on Linux
144 | * Keep up with the original repo
145 |
--------------------------------------------------------------------------------
/misc/control:
--------------------------------------------------------------------------------
1 | Package: net.siguza.ios-kern-utils
2 | Name: kern-utils
3 | Architecture: iphoneos-arm
4 | Description: kdump & friends
5 | Utilities for playing with the kernel. Requires tfp0 patch.
6 | Run with -h for help.
7 | Free and open source, forked from saelo.
8 | Maintainer: Siguza
9 | Author: Siguza
10 | Homepage: https://github.com/Siguza/ios-kern-utils
11 | Section: Development
12 |
--------------------------------------------------------------------------------
/misc/ent.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | task_for_pid-allow
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/misc/template.tar:
--------------------------------------------------------------------------------
1 | ./ 000755 000765 000000 00000000000 13001240310 011304 5 ustar 00molt wheel 000000 000000 ./usr/ 000755 000765 000000 00000000000 13001240260 012121 5 ustar 00molt wheel 000000 000000
--------------------------------------------------------------------------------
/src/lib/arch.h:
--------------------------------------------------------------------------------
1 | /*
2 | * arch.h - Code to deal with different architectures.
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #ifndef ARCH_H
9 | #define ARCH_H
10 |
11 | #include // TARGET_OS_IPHONE
12 | #include // mach_header, mach_header_64, segment_command, segment_command_64
13 |
14 | #include // kCFCoreFoundationVersionNumber
15 |
16 | #ifndef TARGET_OS_IPHONE
17 | # error "TARGET_OS_IPHONE not defined"
18 | #endif
19 |
20 | #if !(TARGET_OS_IPHONE)
21 | # define TARGET_MACOS
22 | #endif
23 |
24 | // 1199 = kCFCoreFoundationVersionNumber_iOS_8_x_Max or kCFCoreFoundationVersionNumber10_10_Max
25 | #define HAVE_TAGGED_REGIONS (kCFCoreFoundationVersionNumber <= 1199)
26 |
27 | #if __LP64__
28 | # ifdef TARGET_MACOS
29 | # define IMAGE_OFFSET 0
30 | # define MACH_TYPE CPU_TYPE_X86_64
31 | # else
32 | # define IMAGE_OFFSET 0x2000
33 | # define MACH_TYPE CPU_TYPE_ARM64
34 | # endif
35 | # define ADDR "%016lx"
36 | # define SIZE "%lu"
37 | # define MACH_HEADER_MAGIC MH_MAGIC_64
38 | # define MACH_LC_SEGMENT LC_SEGMENT_64
39 | # define MACH_LC_SEGMENT_NAME "LC_SEGMENT_64"
40 | # define KERNEL_SPACE 0x8000000000000000
41 | typedef struct mach_header_64 mach_hdr_t;
42 | typedef struct segment_command_64 mach_seg_t;
43 | typedef struct section_64 mach_sec_t;
44 | #else
45 | # ifdef TARGET_MACOS
46 | # error "Unsupported architecture"
47 | # else
48 | # define IMAGE_OFFSET 0x1000
49 | # define MACH_TYPE CPU_TYPE_ARM
50 | # endif
51 | # define ADDR "%08x"
52 | # define SIZE "%u"
53 | # define MACH_HEADER_MAGIC MH_MAGIC
54 | # define MACH_LC_SEGMENT LC_SEGMENT
55 | # define MACH_LC_SEGMENT_NAME "LC_SEGMENT"
56 | # define KERNEL_SPACE 0x80000000
57 | typedef struct mach_header mach_hdr_t;
58 | typedef struct segment_command mach_seg_t;
59 | typedef struct section mach_sec_t;
60 | #endif
61 | typedef struct load_command mach_lc_t;
62 |
63 | #endif
64 |
--------------------------------------------------------------------------------
/src/lib/debug.c:
--------------------------------------------------------------------------------
1 | /*
2 | * debug.c
3 | *
4 | * Copyright (c) 2016 Siguza
5 | */
6 |
7 | #include // bool, false
8 |
9 | #include "debug.h"
10 |
11 | bool verbose = false;
12 | bool slow = false;
13 |
--------------------------------------------------------------------------------
/src/lib/debug.h:
--------------------------------------------------------------------------------
1 | /*
2 | * debug.h - Well, debugging.
3 | *
4 | * Copyright (c) 2016 Siguza
5 | */
6 |
7 | #ifndef DEBUG_H
8 | #define DEBUG_H
9 |
10 | #include // bool
11 | #include // fprintf, stderr
12 | #include // usleep
13 |
14 | #define BUGTRACKER_URL "https://github.com/Siguza/ios-kern-utils/issues/new"
15 |
16 | #define DEBUG(str, args...) \
17 | do \
18 | { \
19 | if(verbose) \
20 | { \
21 | fprintf(stderr, "[DEBUG] " str " [" __FILE__ ":%u]\n", ##args, __LINE__); \
22 | } \
23 | if(slow) \
24 | { \
25 | usleep(100); \
26 | } \
27 | } while(0)
28 |
29 | extern bool verbose;
30 | extern bool slow;
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/src/lib/libkern.c:
--------------------------------------------------------------------------------
1 | /*
2 | * libkern.c - Everything that touches the kernel.
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #include // RTLD_*, dl*
9 | #include // UINT_MAX
10 | #include // fprintf, snprintf
11 | #include // free, malloc, random, srandom
12 | #include // memmem
13 | #include // time
14 |
15 | #include // Everything mach
16 | #include // MH_EXECUTE
17 | #include // struct nlist_64
18 | #include // mmap, munmap, MAP_FAILED
19 | #include // fstat, struct stat
20 | #include // syscall
21 |
22 | #include "arch.h" // TARGET_MACOS, IMAGE_OFFSET, MACH_TYPE, MACH_HEADER_MAGIC, mach_hdr_t
23 | #include "debug.h" // DEBUG
24 | #include "mach-o.h" // CMD_ITERATE
25 |
26 | #include "libkern.h"
27 |
28 | #define MAX_CHUNK_SIZE 0xFFF /* MIG limitation */
29 |
30 | #define VERIFY_PORT(port, ret) \
31 | do \
32 | { \
33 | if(MACH_PORT_VALID(port)) \
34 | { \
35 | if(ret == KERN_SUCCESS) \
36 | { \
37 | DEBUG("Success!"); \
38 | } \
39 | else \
40 | { \
41 | DEBUG("Got a valid port, but return value is 0x%08x (%s)", ret, mach_error_string(ret)); \
42 | ret = KERN_SUCCESS; \
43 | } \
44 | } \
45 | else \
46 | { \
47 | if(ret == KERN_SUCCESS) \
48 | { \
49 | DEBUG("Returned success, but port is invalid (0x%08x)", port); \
50 | ret = KERN_FAILURE; \
51 | } \
52 | else \
53 | { \
54 | DEBUG("Failure. Port: 0x%08x, return value: 0x%08x (%s)", port, ret, mach_error_string(ret)); \
55 | } \
56 | } \
57 | } while(0)
58 |
59 | #define VERIFY_TASK(task, ret) \
60 | do \
61 | { \
62 | if(ret == KERN_SUCCESS) \
63 | { \
64 | DEBUG("Checking if port is restricted..."); \
65 | mach_port_array_t __arr; \
66 | mach_msg_type_number_t __num; \
67 | ret = mach_ports_lookup(task, &__arr, &__num); \
68 | if(ret == KERN_SUCCESS) \
69 | { \
70 | task_t __self = mach_task_self(); \
71 | for(size_t __i = 0; __i < __num; ++__i) \
72 | { \
73 | mach_port_deallocate(__self, __arr[__i]); \
74 | } \
75 | } \
76 | else \
77 | { \
78 | DEBUG("Failure: task port 0x%08x is restricted.", task); \
79 | ret = KERN_NO_ACCESS; \
80 | } \
81 | } \
82 | } while(0)
83 |
84 | kern_return_t get_kernel_task(task_t *task)
85 | {
86 | static task_t kernel_task = MACH_PORT_NULL;
87 | static bool initialized = false;
88 | if(!initialized)
89 | {
90 | DEBUG("Getting kernel task...");
91 | kern_return_t ret;
92 | kernel_task = MACH_PORT_NULL;
93 | host_t host = mach_host_self();
94 |
95 | // Try common workaround first
96 | DEBUG("Trying host_get_special_port(4)...");
97 | ret = host_get_special_port(host, HOST_LOCAL_NODE, 4, &kernel_task);
98 | VERIFY_PORT(kernel_task, ret);
99 | VERIFY_TASK(kernel_task, ret);
100 |
101 | if(ret != KERN_SUCCESS)
102 | {
103 | kernel_task = MACH_PORT_NULL;
104 | #ifdef TARGET_MACOS
105 | // Huge props to Jonathan Levin for this method!
106 | // Who needs task_for_pid anyway? :P
107 | // ...or "needed", as of mid-Sierra. :/
108 | DEBUG("Trying processor_set_tasks()...");
109 | mach_port_t name = MACH_PORT_NULL,
110 | priv = MACH_PORT_NULL;
111 | DEBUG("Getting default processor set name port...");
112 | ret = processor_set_default(host, &name);
113 | VERIFY_PORT(name, ret);
114 | if(ret == KERN_SUCCESS)
115 | {
116 | DEBUG("Getting default processor set priv port...");
117 | ret = host_processor_set_priv(host, name, &priv);
118 | VERIFY_PORT(priv, ret);
119 | if(ret == KERN_SUCCESS)
120 | {
121 | DEBUG("Getting processor tasks...");
122 | task_array_t tasks;
123 | mach_msg_type_number_t num;
124 | ret = processor_set_tasks(priv, &tasks, &num);
125 | if(ret != KERN_SUCCESS)
126 | {
127 | DEBUG("Failed: %s", mach_error_string(ret));
128 | }
129 | else
130 | {
131 | DEBUG("Got %u tasks, looking for kernel task...", num);
132 | for(size_t i = 0; i < num; ++i)
133 | {
134 | int pid = 0;
135 | ret = pid_for_task(tasks[i], &pid);
136 | if(ret != KERN_SUCCESS)
137 | {
138 | DEBUG("Failed to get pid for task %lu (%08x): %s", i, tasks[i], mach_error_string(ret));
139 | break;
140 | }
141 | else if(pid == 0)
142 | {
143 | kernel_task = tasks[i];
144 | break;
145 | }
146 | }
147 | if(kernel_task == MACH_PORT_NULL)
148 | {
149 | DEBUG("Kernel task is not in set.");
150 | ret = KERN_FAILURE;
151 | }
152 | }
153 | }
154 | }
155 | #else
156 | DEBUG("Trying task_for_pid(0)...");
157 | ret = task_for_pid(mach_task_self(), 0, &kernel_task);
158 | VERIFY_PORT(kernel_task, ret);
159 | #endif
160 | }
161 | VERIFY_TASK(kernel_task, ret);
162 |
163 | if(ret != KERN_SUCCESS)
164 | {
165 | DEBUG("Returning failure.");
166 | return ret;
167 | }
168 | DEBUG("Success, caching returned port.");
169 | initialized = true;
170 | DEBUG("kernel_task = 0x%08x", kernel_task);
171 | }
172 | *task = kernel_task;
173 | return KERN_SUCCESS;
174 | }
175 |
176 | // Kernel Base: This is a long story.
177 | //
178 | // Obtaining the kernel slide/base address is non-trivial, even with access to
179 | // the kernel task port. Using the vm_region_* APIs, however, one can iterate
180 | // over its memory regions, which provides a starting point. Additionally, there
181 | // is a special region (I call it the "base region"), within which the kernel is
182 | // located.
183 | //
184 | //
185 | // Some history:
186 | //
187 | // In Saelo's original code (working up to and including iOS 7), the base region
188 | // would be uniquely identified by being larger than 1 GB. The kernel had a
189 | // simple offset from the region's base address of 0x1000 bytes on 32-bit, and
190 | // 0x2000 bytes on 64-bit.
191 | //
192 | // With iOS 8, the property of being larger than 1GB was no longer unique.
193 | // Additionally, one had to check for ---/--- access permissions, which would
194 | // again uniquely identify the base region. The kernel offset from its base
195 | // address remained the same.
196 | //
197 | // With iOS 9, the kernel's offset from the region's base address was doubled
198 | // for most (but seemingly not all) devices. I simply checked both 0x1000 and
199 | // 0x2000 for 32-bit and 0x2000 and 0x4000 for 64-bit.
200 | //
201 | // Somewhere between iOS 9.0 and 9.1, the kernel started not having a fixed
202 | // offset from the region's base address anymore. In addition to the fixed
203 | // offset, it could have a multiple of 0x100000 added to its address, seemingly
204 | // uncorrelated to the region's base address, as if it had an additional KASLR
205 | // slide applied within the region. Also, the kernel's offset from the region's
206 | // base address could be much larger than the kernel itself.
207 | // I worked around this by first locating the base region, checking for the
208 | // Mach-O magic, and simply adding 0x100000 to the address until I found it.
209 | //
210 | // With iOS 10 (and seemingly even 9 on some devices), the base address
211 | // identification was no longer sufficient, as another null mapping of 64GB size
212 | // had popped up. So in addition to the other two, I added the criterium of a
213 | // size smaller than 16GB.
214 | // In addition to that, the part of the base region between its base address and
215 | // the kernel base does no longer have to be mapped (that is, it's still part of
216 | // the memory region, but trying to access it will cause a panic). This
217 | // completely broke my workaround for iOS 9, and it's also the reason why both
218 | // nonceEnabler and nvram_patcher don't work reliably. It's still possible to
219 | // get it to work through luck, but that chance is pretty small.
220 | //
221 | //
222 | // Current implementation:
223 | //
224 | // The base region still exists, still contains the kernel, and is still
225 | // uniquely identifiable, but more information is required before one should
226 | // attempt to access it. This "more information" can only be obtained from
227 | // other memory regions.
228 | // Now, kernel heap allocations larger than two page sizes go to either the
229 | // kalloc_map or the kernel_map rather than zalloc, meaning they will directly
230 | // pop up on the list of memory regions, and be identifiable by having a
231 | // user_tag of VM_KERN_MEMORY_LIBKERN.
232 | //
233 | // So the current idea is to find a size of which no allocation with user_tag
234 | // VM_KERN_MEMORY_LIBKERN exists, and to subsequently make such an allocation,
235 | // which will then be uniquely identifiable. The allocation further incorporates
236 | // OSObjects, which will contain vtable pointers, which are valid pointers to
237 | // the kernel's base region. From there, we simply search backwards until we
238 | // find the kernel header.
239 |
240 |
241 | // true = continue, false = abort
242 | typedef bool (*kernel_region_callback_t) (vm_address_t, vm_size_t, vm_region_submap_info_data_64_t*, void*);
243 |
244 | // true = success, false = failure
245 | static bool foreach_kernel_region(kernel_region_callback_t cb, void *arg)
246 | {
247 | DEBUG("Looping over kernel memory regions...");
248 | task_t kernel_task;
249 | if(get_kernel_task(&kernel_task) != KERN_SUCCESS)
250 | {
251 | return false;
252 | }
253 |
254 | vm_region_submap_info_data_64_t info;
255 | vm_size_t size;
256 | mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
257 | unsigned int depth;
258 | for(vm_address_t addr = 0; 1; addr += size)
259 | {
260 | DEBUG("Searching for next region at " ADDR "...", addr);
261 | depth = UINT_MAX;
262 | if(vm_region_recurse_64(kernel_task, &addr, &size, &depth, (vm_region_info_t)&info, &info_count) != KERN_SUCCESS)
263 | {
264 | break;
265 | }
266 | if(!cb(addr, size, &info, arg))
267 | {
268 | return false;
269 | }
270 | }
271 |
272 | return true;
273 | }
274 |
275 | typedef struct
276 | {
277 | char magic[16];
278 | uint32_t segoff;
279 | uint32_t nsegs;
280 | uint32_t _unused32[2];
281 | uint64_t _unused64[5];
282 | uint64_t localoff;
283 | uint64_t nlocals;
284 | } dysc_hdr_t;
285 |
286 | typedef struct
287 | {
288 | uint64_t addr;
289 | uint64_t size;
290 | uint64_t fileoff;
291 | vm_prot_t maxprot;
292 | vm_prot_t initprot;
293 | } dysc_seg_t;
294 |
295 | typedef struct
296 | {
297 | uint32_t nlistOffset;
298 | uint32_t nlistCount;
299 | uint32_t stringsOffset;
300 | uint32_t stringsSize;
301 | uint32_t entriesOffset;
302 | uint32_t entriesCount;
303 | } dysc_local_info_t;
304 |
305 | typedef struct
306 | {
307 | uint32_t dylibOffset;
308 | uint32_t nlistStartIndex;
309 | uint32_t nlistCount;
310 | } dysc_local_entry_t;
311 |
312 | enum
313 | {
314 | kOSSerializeDictionary = 0x01000000U,
315 | kOSSerializeArray = 0x02000000U,
316 | kOSSerializeSet = 0x03000000U,
317 | kOSSerializeNumber = 0x04000000U,
318 | kOSSerializeSymbol = 0x08000000U,
319 | kOSSerializeString = 0x09000000U,
320 | kOSSerializeData = 0x0a000000U,
321 | kOSSerializeBoolean = 0x0b000000U,
322 | kOSSerializeObject = 0x0c000000U,
323 |
324 | kOSSerializeTypeMask = 0x7F000000U,
325 | kOSSerializeDataMask = 0x00FFFFFFU,
326 |
327 | kOSSerializeEndCollection = 0x80000000U,
328 |
329 | kOSSerializeMagic = 0x000000d3U,
330 | };
331 |
332 | #define IOKIT_PATH "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit"
333 |
334 | static mach_port_t libkern_allocate(vm_size_t size)
335 | {
336 | mach_port_t port = MACH_PORT_NULL;
337 | void *IOKit = NULL;
338 | #if defined(__LP64__) && !defined(TARGET_MACOS)
339 | int fd = 0;
340 | void *cache = NULL;
341 | struct stat s = {0};
342 | #endif
343 |
344 | mach_port_t master = MACH_PORT_NULL;
345 | kern_return_t ret = host_get_io_master(mach_host_self(), &master);
346 | if(ret != KERN_SUCCESS)
347 | {
348 | DEBUG("Failed to get IOKit master port: %s", mach_error_string(ret));
349 | goto out;
350 | }
351 |
352 | IOKit = dlopen(IOKIT_PATH, RTLD_LAZY | RTLD_LOCAL | RTLD_FIRST);
353 | if(IOKit == NULL)
354 | {
355 | DEBUG("Failed to load IOKit.");
356 | goto out;
357 | }
358 |
359 | // Ye olde MIG
360 | kern_return_t (*io_service_add_notification_ool)(mach_port_t, const char*, void*, mach_msg_type_number_t, mach_port_t, void*, mach_msg_type_number_t, kern_return_t*, mach_port_t*) = NULL;
361 | #ifdef __LP64__
362 | // 64-bit IOKit doesn't export the MIG function, but still has a symbol for it.
363 | // We go through all this trouble rather than statically linking against MIG because
364 | // that becomes incompatible every now and then, while IOKit is always up to date.
365 |
366 | char *IOServiceOpen = dlsym(IOKit, "IOServiceOpen"); // char for pointer arithmetic
367 | if(IOServiceOpen == NULL)
368 | {
369 | DEBUG("Failed to find IOServiceOpen.");
370 | goto out;
371 | }
372 |
373 | mach_hdr_t *IOKit_hdr = NULL;
374 | uintptr_t addr_IOServiceOpen = 0,
375 | addr_io_service_add_notification_ool = 0;
376 | struct nlist_64 *symtab = NULL;
377 | const char *strtab = NULL;
378 | uintptr_t cache_base = 0;
379 |
380 | #ifdef TARGET_MACOS
381 | Dl_info IOKit_info;
382 | if(dladdr(IOServiceOpen, &IOKit_info) == 0)
383 | {
384 | DEBUG("Failed to find IOKit header.");
385 | goto out;
386 | }
387 | IOKit_hdr = IOKit_info.dli_fbase;
388 | if(syscall(294, &cache_base) != 0) // shared_region_check_np
389 | {
390 | DEBUG("Failed to find dyld_shared_cache: %s", strerror(errno));
391 | goto out;
392 | }
393 | DEBUG("dyld_shared_cache is at " ADDR, cache_base);
394 | dysc_hdr_t *cache_hdr = (dysc_hdr_t*)cache_base;
395 | dysc_seg_t *cache_segs = (dysc_seg_t*)(cache_base + cache_hdr->segoff);
396 | dysc_seg_t *cache_base_seg = NULL;
397 | for(size_t i = 0; i < cache_hdr->nsegs; ++i)
398 | {
399 | if(cache_segs[i].fileoff == 0 && cache_segs[i].size > 0)
400 | {
401 | cache_base_seg = &cache_segs[i];
402 | break;
403 | }
404 | }
405 | if(cache_base_seg == NULL)
406 | {
407 | DEBUG("No segment maps to cache base");
408 | goto out;
409 | }
410 | #else
411 | // TODO: This will have to be reworked once there are more 64-bit sub-archs than just arm64.
412 | // It's probably gonna be easiest to use PROC_PIDREGIONPATHINFO, at least that gives the full path on iOS.
413 | fd = open("/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64", O_RDONLY);
414 | if(fd == -1)
415 | {
416 | DEBUG("Failed to open dyld_shared_cache_arm64 for reading: %s", strerror(errno));
417 | goto out;
418 | }
419 | if(fstat(fd, &s) != 0)
420 | {
421 | DEBUG("Failed to stat(dyld_shared_cache_arm64): %s", strerror(errno));
422 | goto out;
423 | }
424 | cache = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
425 | if(cache == MAP_FAILED)
426 | {
427 | DEBUG("Failed to map dyld_shared_cache_arm64 to memory: %s", strerror(errno));
428 | goto out;
429 | }
430 | cache_base = (uintptr_t)cache;
431 | DEBUG("dyld_shared_cache is at " ADDR, cache_base);
432 |
433 | dysc_hdr_t *cache_hdr = cache;
434 | if(cache_hdr->nlocals == 0)
435 | {
436 | DEBUG("Cache contains no local symbols.");
437 | goto out;
438 | }
439 | dysc_local_info_t *local_info = (dysc_local_info_t*)(cache_base + cache_hdr->localoff);
440 | dysc_local_entry_t *local_entries = (dysc_local_entry_t*)((uintptr_t)local_info + local_info->entriesOffset);
441 | DEBUG("cache_hdr: " ADDR ", local_info: " ADDR ", local_entries: " ADDR, (uintptr_t)cache_hdr, (uintptr_t)local_info, (uintptr_t)local_entries);
442 | dysc_local_entry_t *local_entry = NULL;
443 | struct nlist_64 *local_symtab = (struct nlist_64*)((uintptr_t)local_info + local_info->nlistOffset);
444 | const char *local_strtab = (const char*)((uintptr_t)local_info + local_info->stringsOffset);
445 | for(size_t i = 0; i < local_info->entriesCount; ++i)
446 | {
447 | mach_hdr_t *dylib_hdr = (mach_hdr_t*)(cache_base + local_entries[i].dylibOffset);
448 | CMD_ITERATE(dylib_hdr, cmd)
449 | {
450 | if(cmd->cmd == LC_ID_DYLIB && strcmp((char*)cmd + ((struct dylib_command*)cmd)->dylib.name.offset, IOKIT_PATH) == 0)
451 | {
452 | IOKit_hdr = dylib_hdr;
453 | local_entry = &local_entries[i];
454 | local_symtab = &local_symtab[local_entries[i].nlistStartIndex];
455 | goto found;
456 | }
457 | }
458 | }
459 | DEBUG("Failed to find local symbols for IOKit.");
460 | goto out;
461 |
462 | found:;
463 | DEBUG("IOKit header: " ADDR ", local_symtab: " ADDR ", local_strtab: " ADDR, (uintptr_t)IOKit_hdr, (uintptr_t)local_symtab, (uintptr_t)local_strtab);
464 | for(size_t i = 0; i < local_entry->nlistCount; ++i)
465 | {
466 | const char *name = &local_strtab[local_symtab[i].n_un.n_strx];
467 | if(strcmp(name, "_io_service_add_notification_ool") == 0)
468 | {
469 | addr_io_service_add_notification_ool = local_symtab[i].n_value;
470 | break;
471 | }
472 | }
473 | #endif
474 | struct symtab_command *symcmd = NULL;
475 | CMD_ITERATE(IOKit_hdr, cmd)
476 | {
477 | if(cmd->cmd == LC_SYMTAB)
478 | {
479 | symcmd = (struct symtab_command*)cmd;
480 | #ifdef TARGET_MACOS
481 | for(size_t i = 0; i < cache_hdr->nsegs; ++i)
482 | {
483 | if(cache_segs[i].fileoff <= symcmd->symoff && cache_segs[i].fileoff + cache_segs[i].size > symcmd->symoff)
484 | {
485 | symtab = (struct nlist_64*)(cache_base - cache_base_seg->addr + cache_segs[i].addr + symcmd->symoff - cache_segs[i].fileoff);
486 | }
487 | if(cache_segs[i].fileoff <= symcmd->stroff && cache_segs[i].fileoff + cache_segs[i].size > symcmd->stroff)
488 | {
489 | strtab = (const char*)(cache_base - cache_base_seg->addr + cache_segs[i].addr + symcmd->stroff - cache_segs[i].fileoff);
490 | }
491 | }
492 | #else
493 | symtab = (struct nlist_64*)(cache_base + symcmd->symoff);
494 | strtab = (const char*)(cache_base + symcmd->stroff);
495 | #endif
496 | break;
497 | }
498 | }
499 | DEBUG("symcmd: " ADDR ", symtab: " ADDR ", strtab: " ADDR, (uintptr_t)symcmd, (uintptr_t)symtab, (uintptr_t)strtab);
500 | if(symcmd == NULL || symtab == NULL || strtab == NULL)
501 | {
502 | DEBUG("Failed to find IOKit symtab.");
503 | goto out;
504 | }
505 | for(size_t i = 0; i < symcmd->nsyms; ++i)
506 | {
507 | const char *name = &strtab[symtab[i].n_un.n_strx];
508 | if(strcmp(name, "_IOServiceOpen") == 0)
509 | {
510 | addr_IOServiceOpen = symtab[i].n_value;
511 | }
512 | #ifdef TARGET_MACOS
513 | else if(strcmp(name, "_io_service_add_notification_ool") == 0)
514 | {
515 | addr_io_service_add_notification_ool = symtab[i].n_value;
516 | }
517 | #endif
518 | }
519 | DEBUG("IOServiceOpen: " ADDR, addr_IOServiceOpen);
520 | DEBUG("io_service_add_notification_ool: " ADDR, addr_io_service_add_notification_ool);
521 | if(addr_IOServiceOpen == 0 || addr_io_service_add_notification_ool == 0)
522 | {
523 | goto out;
524 | }
525 | io_service_add_notification_ool = (void*)(IOServiceOpen - addr_IOServiceOpen + addr_io_service_add_notification_ool);
526 | #else
527 | // 32-bit just exports the function
528 | io_service_add_notification_ool = dlsym(IOKit, "io_service_add_notification_ool");
529 | if(io_service_add_notification_ool == NULL)
530 | {
531 | DEBUG("Failed to find io_service_add_notification_ool.");
532 | goto out;
533 | }
534 | #endif
535 |
536 | uint32_t dict[] =
537 | {
538 | kOSSerializeMagic,
539 | kOSSerializeEndCollection | kOSSerializeDictionary | (size / (2 * sizeof(void*))),
540 | kOSSerializeSymbol | 4,
541 | 0x636261, // "abc"
542 | kOSSerializeEndCollection | kOSSerializeBoolean | 1,
543 | };
544 | kern_return_t err;
545 | ret = io_service_add_notification_ool(master, "IOServiceTerminate", dict, sizeof(dict), MACH_PORT_NULL, NULL, 0, &err, &port);
546 | if(ret == KERN_SUCCESS)
547 | {
548 | ret = err;
549 | }
550 | if(ret != KERN_SUCCESS)
551 | {
552 | DEBUG("Failed to create IONotification: %s", mach_error_string(ret));
553 | port = MACH_PORT_NULL; // Just in case
554 | goto out;
555 | }
556 |
557 | out:;
558 | if(IOKit != NULL)
559 | {
560 | dlclose(IOKit);
561 | }
562 | #if defined(__LP64__) && !defined(TARGET_MACOS)
563 | if(cache != NULL)
564 | {
565 | munmap(cache, s.st_size);
566 | }
567 | if(fd != 0 )
568 | {
569 | close(fd);
570 | }
571 | #endif
572 | return port;
573 | }
574 |
575 | typedef struct
576 | {
577 | uint32_t num_of_size[16];
578 | vm_size_t page_size;
579 | vm_size_t alloc_size;
580 | vm_address_t vtab;
581 | } get_kernel_base_ios9_cb_args_t;
582 |
583 | // Memory tag
584 | #define VM_KERN_MEMORY_LIBKERN 4
585 |
586 | // Amount of pages that are too large for zalloc
587 | #define KALLOC_DIRECT_THRESHOLD 3
588 |
589 | static bool count_libkern_allocations(vm_address_t addr, vm_size_t size, vm_region_submap_info_data_64_t *info, void *arg)
590 | {
591 | get_kernel_base_ios9_cb_args_t *args = arg;
592 | if(info->user_tag == VM_KERN_MEMORY_LIBKERN)
593 | {
594 | DEBUG("Found libkern region " ADDR "-" ADDR "...", addr, addr + size);
595 | size_t idx = (size + args->page_size - 1) / args->page_size;
596 | if(idx < KALLOC_DIRECT_THRESHOLD)
597 | {
598 | DEBUG("Too small, skipping...");
599 | }
600 | else
601 | {
602 | idx -= KALLOC_DIRECT_THRESHOLD;
603 | if(idx >= sizeof(args->num_of_size)/sizeof(args->num_of_size[0]))
604 | {
605 | DEBUG("Too large, skipping...");
606 | }
607 | else
608 | {
609 | ++(args->num_of_size[idx]);
610 | }
611 | }
612 | }
613 | return true;
614 | }
615 |
616 | static bool get_kernel_base_ios9_cb(vm_address_t addr, vm_size_t size, vm_region_submap_info_data_64_t *info, void *arg)
617 | {
618 | get_kernel_base_ios9_cb_args_t *args = arg;
619 | if(info->user_tag == VM_KERN_MEMORY_LIBKERN && size == args->alloc_size)
620 | {
621 | DEBUG("Found matching libkern region " ADDR "-" ADDR ", dumping it...", addr, addr + size);
622 | vm_address_t obj = 0;
623 | if(kernel_read(addr, sizeof(void*), &obj) != sizeof(void*))
624 | {
625 | DEBUG("Kernel I/O error, aborting.");
626 | return false;
627 | }
628 | DEBUG("Found object: " ADDR, obj);
629 | if(obj < KERNEL_SPACE)
630 | {
631 | return false;
632 | }
633 | vm_address_t vtab = 0;
634 | if(kernel_read(obj, sizeof(void*), &vtab) != sizeof(void*))
635 | {
636 | DEBUG("Kernel I/O error, aborting.");
637 | return false;
638 | }
639 | DEBUG("Found vtab: " ADDR, vtab);
640 | if(vtab < KERNEL_SPACE)
641 | {
642 | return false;
643 | }
644 | args->vtab = vtab;
645 | return false; // just to short-circuit, we ignore the return value in the calling func
646 | }
647 | return true;
648 | }
649 |
650 | static vm_address_t get_kernel_base_ios9(vm_address_t regstart, vm_address_t regend)
651 | {
652 | get_kernel_base_ios9_cb_args_t args =
653 | {
654 | .num_of_size = {0},
655 | .page_size = 0,
656 | .alloc_size = 0,
657 | .vtab = 0,
658 | };
659 |
660 | host_t host = mach_host_self();
661 | kern_return_t ret = host_page_size(host, &args.page_size);
662 | if(ret != KERN_SUCCESS)
663 | {
664 | DEBUG("Failed to get host page size: %s", mach_error_string(ret));
665 | return 0;
666 | }
667 |
668 | DEBUG("Enumerating libkern allocations...");
669 | if(!foreach_kernel_region(&count_libkern_allocations, &args))
670 | {
671 | return 0;
672 | }
673 | for(size_t i = 0; i < sizeof(args.num_of_size)/sizeof(args.num_of_size[0]); ++i)
674 | {
675 | if(args.num_of_size[i] == 0)
676 | {
677 | args.alloc_size = (i + KALLOC_DIRECT_THRESHOLD) * args.page_size;
678 | break;
679 | }
680 | }
681 | if(args.alloc_size == 0)
682 | {
683 | DEBUG("Failed to find a suitable size for injection, returning 0.");
684 | return 0;
685 | }
686 |
687 | DEBUG("Making allocation of size " SIZE "...", args.alloc_size);
688 | mach_port_t port = libkern_allocate(args.alloc_size);
689 | if(port == MACH_PORT_NULL)
690 | {
691 | return 0;
692 | }
693 | foreach_kernel_region(&get_kernel_base_ios9_cb, &args); // don't care about return value
694 | mach_port_deallocate(mach_task_self(), port);
695 |
696 | if(args.vtab == 0)
697 | {
698 | DEBUG("Failed to get any vtab, returning 0.");
699 | return 0;
700 | }
701 |
702 | DEBUG("Starting at " ADDR ", searching backwards...", args.vtab);
703 | for(vm_address_t addr = (args.vtab & ~0xfffff) +
704 | #if TARGET_OSX
705 | 0 // no offset for macOS
706 | #else
707 | # ifdef __LP64__
708 | 2 * IMAGE_OFFSET // 0x4000 for 64-bit on >=9.0
709 | # else
710 | IMAGE_OFFSET // 0x1000 for 32-bit, regardless of OS version
711 | # endif
712 | #endif
713 | ; addr > regstart; addr -= 0x100000)
714 | {
715 | mach_hdr_t hdr;
716 | DEBUG("Looking for mach header at " ADDR "...", addr);
717 | if(kernel_read(addr, sizeof(hdr), &hdr) != sizeof(hdr))
718 | {
719 | DEBUG("Kernel I/O error, returning 0.");
720 | return 0;
721 | }
722 | if(hdr.magic == MACH_HEADER_MAGIC && hdr.filetype == MH_EXECUTE)
723 | {
724 | DEBUG("Found Mach-O of type MH_EXECUTE at " ADDR ", returning success.", addr);
725 | return addr;
726 | }
727 | }
728 |
729 | DEBUG("Found no mach header, returning 0.");
730 | return 0;
731 | }
732 |
733 | static vm_address_t get_kernel_base_ios8(vm_address_t regstart)
734 | {
735 | // things used to be so simple...
736 | vm_address_t addr = regstart + IMAGE_OFFSET + 0x200000;
737 |
738 | mach_hdr_t hdr;
739 | DEBUG("Looking for mach header at " ADDR "...", addr);
740 | if(kernel_read(addr, sizeof(hdr), &hdr) != sizeof(hdr))
741 | {
742 | DEBUG("Kernel I/O error, returning 0.");
743 | return 0;
744 | }
745 | if(hdr.magic == MACH_HEADER_MAGIC && hdr.filetype == MH_EXECUTE)
746 | {
747 | DEBUG("Success!");
748 | }
749 | else
750 | {
751 | DEBUG("Not a Mach-O header there, subtracting 0x200000.");
752 | addr -= 0x200000;
753 | }
754 | return addr;
755 | }
756 |
757 | typedef struct
758 | {
759 | vm_address_t regstart;
760 | vm_address_t regend;
761 | } get_kernel_base_cb_args_t;
762 |
763 | static bool get_kernel_base_cb(vm_address_t addr, vm_size_t size, vm_region_submap_info_data_64_t *info, void *arg)
764 | {
765 | get_kernel_base_cb_args_t *args = arg;
766 | DEBUG("Found region " ADDR "-" ADDR " with %c%c%c", addr, addr + size, (info->protection) & VM_PROT_READ ? 'r' : '-', (info->protection) & VM_PROT_WRITE ? 'w' : '-', (info->protection) & VM_PROT_EXECUTE ? 'x' : '-');
767 | if
768 | (
769 | (info->protection & (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)) == 0 &&
770 | #ifdef TARGET_MACOS
771 | addr == 0xffffff8000000000 &&
772 | #else
773 | size > 1024*1024*1024 &&
774 | # ifdef __LP64__
775 | size <= 16ULL * 1024*1024*1024 && // this is always true for 32-bit
776 | # endif
777 | #endif
778 | info->share_mode == SM_EMPTY
779 | )
780 | {
781 | if(args->regstart == 0 && args->regend == 0)
782 | {
783 | DEBUG("Found a matching memory region.");
784 | args->regstart = addr;
785 | args->regend = addr + size;
786 | }
787 | else
788 | {
789 | DEBUG("Found more than one matching memory region, aborting.");
790 | return false;
791 | }
792 | }
793 |
794 | return true;
795 | }
796 |
797 | vm_address_t get_kernel_base(void)
798 | {
799 | static vm_address_t kbase = 0;
800 | static bool initialized = false;
801 | if(!initialized)
802 | {
803 | DEBUG("Getting kernel base address...");
804 |
805 | DEBUG("Getting base region address...");
806 | get_kernel_base_cb_args_t args =
807 | {
808 | .regstart = 0,
809 | .regend = 0,
810 | };
811 | if(!foreach_kernel_region(&get_kernel_base_cb, &args))
812 | {
813 | return 0;
814 | }
815 | if(args.regstart == 0)
816 | {
817 | DEBUG("Failed to find base region, returning 0.");
818 | return 0;
819 | }
820 | if(args.regend < args.regstart)
821 | {
822 | DEBUG("Base region has overflowing size, returning 0.");
823 | return 0;
824 | }
825 | DEBUG("Base region is at " ADDR "-" ADDR ".", args.regstart, args.regend);
826 |
827 | vm_address_t addr = HAVE_TAGGED_REGIONS ? get_kernel_base_ios8(args.regstart) : get_kernel_base_ios9(args.regstart, args.regend);
828 | if(addr == 0)
829 | {
830 | return 0;
831 | }
832 |
833 | DEBUG("Got address " ADDR ", doing sanity checks...", addr);
834 | mach_hdr_t hdr;
835 | if(kernel_read(addr, sizeof(hdr), &hdr) != sizeof(hdr))
836 | {
837 | DEBUG("Kernel I/O error, returning 0.");
838 | return 0;
839 | }
840 | if(hdr.magic != MACH_HEADER_MAGIC)
841 | {
842 | DEBUG("Header has wrong magic, returning 0 (%08x)", hdr.magic);
843 | return 0;
844 | }
845 | if(hdr.filetype != MH_EXECUTE)
846 | {
847 | DEBUG("Header has wrong filetype, returning 0 (%u)", hdr.filetype);
848 | return 0;
849 | }
850 | if(hdr.cputype != MACH_TYPE)
851 | {
852 | DEBUG("Header has wrong architecture, returning 0 (%u)", hdr.cputype);
853 | return 0;
854 | }
855 | void *cmds = malloc(hdr.sizeofcmds);
856 | if(cmds == NULL)
857 | {
858 | DEBUG("Memory allocation error, returning 0.");
859 | return 0;
860 | }
861 | if(kernel_read(addr + sizeof(hdr), hdr.sizeofcmds, cmds) != hdr.sizeofcmds)
862 | {
863 | DEBUG("Kernel I/O error, returning 0.");
864 | free(cmds);
865 | return 0;
866 | }
867 | bool has_userland_address = false,
868 | has_linking = false,
869 | has_unixthread = false,
870 | has_exec = false;
871 | for
872 | (
873 | struct load_command *cmd = cmds, *end = (struct load_command*)((char*)cmds + hdr.sizeofcmds);
874 | cmd < end;
875 | cmd = (struct load_command*)((char*)cmd + cmd->cmdsize)
876 | )
877 | {
878 | switch(cmd->cmd)
879 | {
880 | case MACH_LC_SEGMENT:
881 | {
882 | mach_seg_t *seg = (mach_seg_t*)cmd;
883 | if(seg->vmaddr < KERNEL_SPACE)
884 | {
885 | has_userland_address = true;
886 | goto end;
887 | }
888 | if(seg->initprot & VM_PROT_EXECUTE)
889 | {
890 | has_exec = true;
891 | }
892 | break;
893 | }
894 | case LC_UNIXTHREAD:
895 | has_unixthread = true;
896 | break;
897 | case LC_LOAD_DYLIB:
898 | case LC_ID_DYLIB:
899 | case LC_LOAD_DYLINKER:
900 | case LC_ID_DYLINKER:
901 | case LC_PREBOUND_DYLIB:
902 | case LC_LOAD_WEAK_DYLIB:
903 | case LC_REEXPORT_DYLIB:
904 | case LC_LAZY_LOAD_DYLIB:
905 | case LC_DYLD_INFO:
906 | case LC_DYLD_INFO_ONLY:
907 | case LC_DYLD_ENVIRONMENT:
908 | case LC_MAIN:
909 | has_linking = true;
910 | goto end;
911 | }
912 | }
913 | end:;
914 | free(cmds);
915 | if(has_userland_address)
916 | {
917 | DEBUG("Found segment with userland address, returning 0.");
918 | return 0;
919 | }
920 | if(has_linking)
921 | {
922 | DEBUG("Found linking-related load command, returning 0.");
923 | return 0;
924 | }
925 | if(!has_unixthread)
926 | {
927 | DEBUG("Binary is missing LC_UNIXTHREAD, returning 0.");
928 | return 0;
929 | }
930 | if(!has_exec)
931 | {
932 | DEBUG("Binary has no executable segment, returning 0.");
933 | return 0;
934 | }
935 |
936 | DEBUG("Confirmed base address " ADDR ", caching it.", addr);
937 | kbase = addr;
938 | initialized = true;
939 | }
940 | return kbase;
941 | }
942 |
943 | vm_size_t kernel_read(vm_address_t addr, vm_size_t size, void *buf)
944 | {
945 | DEBUG("Reading kernel bytes " ADDR "-" ADDR, addr, addr + size);
946 | kern_return_t ret;
947 | task_t kernel_task;
948 | vm_size_t remainder = size,
949 | bytes_read = 0;
950 |
951 | ret = get_kernel_task(&kernel_task);
952 | if(ret != KERN_SUCCESS)
953 | {
954 | return -1;
955 | }
956 |
957 | // The vm_* APIs are part of the mach_vm subsystem, which is a MIG thing
958 | // and therefore has a hard limit of 0x1000 bytes that it accepts. Due to
959 | // this, we have to do both reading and writing in chunks smaller than that.
960 | for(vm_address_t end = addr + size; addr < end; remainder -= size)
961 | {
962 | size = remainder > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : remainder;
963 | ret = vm_read_overwrite(kernel_task, addr, size, (vm_address_t)&((char*)buf)[bytes_read], &size);
964 | if(ret != KERN_SUCCESS || size == 0)
965 | {
966 | DEBUG("vm_read error: %s", mach_error_string(ret));
967 | break;
968 | }
969 | bytes_read += size;
970 | addr += size;
971 | }
972 |
973 | return bytes_read;
974 | }
975 |
976 | vm_size_t kernel_write(vm_address_t addr, vm_size_t size, void *buf)
977 | {
978 | DEBUG("Writing to kernel at " ADDR "-" ADDR, addr, addr + size);
979 | kern_return_t ret;
980 | task_t kernel_task;
981 | vm_size_t remainder = size,
982 | bytes_written = 0;
983 |
984 | ret = get_kernel_task(&kernel_task);
985 | if(ret != KERN_SUCCESS)
986 | {
987 | return -1;
988 | }
989 |
990 | for(vm_address_t end = addr + size; addr < end; remainder -= size)
991 | {
992 | size = remainder > MAX_CHUNK_SIZE ? MAX_CHUNK_SIZE : remainder;
993 | ret = vm_write(kernel_task, addr, (vm_offset_t)&((char*)buf)[bytes_written], size);
994 | if(ret != KERN_SUCCESS)
995 | {
996 | DEBUG("vm_write error: %s", mach_error_string(ret));
997 | break;
998 | }
999 | bytes_written += size;
1000 | addr += size;
1001 | }
1002 |
1003 | return bytes_written;
1004 | }
1005 |
1006 | vm_address_t kernel_find(vm_address_t addr, vm_size_t len, void *buf, size_t size)
1007 | {
1008 | vm_address_t ret = 0;
1009 | unsigned char* b = malloc(len);
1010 | if(b)
1011 | {
1012 | // TODO reading in chunks would probably be better
1013 | if(kernel_read(addr, len, b))
1014 | {
1015 | void *ptr = memmem(b, len, buf, size);
1016 | if(ptr)
1017 | {
1018 | ret = addr + ((char*)ptr - (char*)b);
1019 | }
1020 | }
1021 | free(b);
1022 | }
1023 | return ret;
1024 | }
1025 |
--------------------------------------------------------------------------------
/src/lib/libkern.h:
--------------------------------------------------------------------------------
1 | /*
2 | * libkern.h - Everything that touches the kernel.
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #ifndef LIBKERN_H
9 | #define LIBKERN_H
10 |
11 | #include // fprintf, stderr
12 | #include // geteuid
13 |
14 | #include // kern_return_t
15 | #include // mach_error_string
16 | #include // task_t
17 | #include // MACH_PORT_NULL, MACH_PORT_VALID
18 | #include // vm_address_t, vm_size_t
19 |
20 | /*
21 | * Functions and macros to interact with the kernel address space.
22 | *
23 | * If not otherwise stated the following functions are 'unsafe', meaning
24 | * they are likely to panic the device if given invalid kernel addresses.
25 | *
26 | * You have been warned.
27 | */
28 |
29 | /*
30 | * Get the kernel task port.
31 | *
32 | * This function should be safe.
33 | */
34 |
35 | kern_return_t get_kernel_task(task_t *task);
36 |
37 | /*
38 | * Return the base address of the running kernel.
39 | *
40 | * This function should be safe at least on iOS 9 and earlier.
41 | */
42 | vm_address_t get_kernel_base(void);
43 |
44 | /*
45 | * Read data from the kernel address space.
46 | *
47 | * Returns the number of bytes read.
48 | */
49 | vm_size_t kernel_read(vm_address_t addr, vm_size_t size, void *buf);
50 |
51 | /*
52 | * Write data into the kernel address space.
53 | *
54 | * Returns the number of bytes written.
55 | */
56 | vm_size_t kernel_write(vm_address_t addr, vm_size_t size, void *buf);
57 |
58 | /*
59 | * Find the given byte sequence in the kernel address space between start and end.
60 | *
61 | * Returns the address of the first occurance of bytes if found, otherwise 0.
62 | */
63 | vm_address_t kernel_find(vm_address_t addr, vm_size_t len, void *buf, size_t size);
64 |
65 | /*
66 | * Test for kernel task access and return -1 on failure.
67 | *
68 | * To be embedded in a main() function.
69 | *
70 | * If parameters are given, the last one will be assigned the kernel task, all others are ignored.
71 | */
72 | #define KERNEL_TASK_OR_GTFO(args...) \
73 | do \
74 | { \
75 | task_t _kernel_task = MACH_PORT_NULL; \
76 | kern_return_t _ret = get_kernel_task(&_kernel_task); \
77 | _kernel_task, ##args = _kernel_task; /* mad haxx */ \
78 | if(_ret != KERN_SUCCESS || !MACH_PORT_VALID(_kernel_task)) \
79 | { \
80 | fprintf(stderr, "[!] Failed to get kernel task (%s, kernel_task = %x)\n", mach_error_string(_ret), _kernel_task); \
81 | if(_ret != KERN_SUCCESS && geteuid() != 0) \
82 | { \
83 | fprintf(stderr, "[!] But you are not root, either. Try again as root.\n"); \
84 | } \
85 | return -1; \
86 | } \
87 | } while(0)
88 |
89 | /*
90 | * Test for kernel base address and return -1 if it cannot be determined.
91 | *
92 | * To be embedded in a main() function.
93 | */
94 | #define KERNEL_BASE_OR_GTFO(base) \
95 | do \
96 | { \
97 | KERNEL_TASK_OR_GTFO(); \
98 | if((base = get_kernel_base()) == 0) \
99 | { \
100 | fprintf(stderr, "[!] Failed to locate kernel\n"); \
101 | return -1; \
102 | } \
103 | } while(0)
104 |
105 | #endif
106 |
--------------------------------------------------------------------------------
/src/lib/mach-o.h:
--------------------------------------------------------------------------------
1 | /*
2 | * mach-o.h - Code that deals with the Mach-O file format
3 | *
4 | * Copyright (c) 2012 comex
5 | * Copyright (c) 2016 Siguza
6 | */
7 |
8 | #ifndef MACH_O_H
9 | #define MACH_O_H
10 |
11 | #include // load_command
12 |
13 | /*
14 | * Iterate over all load commands in a Mach-O header
15 | */
16 | #define CMD_ITERATE(hdr, cmd) \
17 | for(struct load_command *cmd = (struct load_command *) ((hdr) + 1), \
18 | *end = (struct load_command *) ((char *) cmd + (hdr)->sizeofcmds); \
19 | cmd < end; \
20 | cmd = (struct load_command *) ((char *) cmd + cmd->cmdsize))
21 |
22 | #endif
23 |
--------------------------------------------------------------------------------
/src/tools/kdump.c:
--------------------------------------------------------------------------------
1 | /*
2 | * kdump.c - Dump the kernel
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #include // errno
9 | #include // FILE, fopen, fwrite, fclose, fprintf, stderr
10 | #include // free, malloc
11 | #include // memcpy, memset, strerror
12 |
13 | #include // KERN_SUCCESS, kern_return_t
14 | #include // task_t
15 | #include // vm_address_t
16 |
17 | #include "arch.h" // ADDR, mach_*
18 | #include "debug.h" // slow, verbose
19 | #include "libkern.h" // KERNEL_BASE_OR_GTFO, kernel_read
20 | #include "mach-o.h" // CMD_ITERATE
21 |
22 | #define max(a, b) (a) > (b) ? (a) : (b)
23 |
24 | static void print_usage(const char *self)
25 | {
26 | fprintf(stderr, "Usage: %s [-h] [-v [-d]] [kernel.bin]\n"
27 | " -d Debug mode (sleep between function calls, gives\n"
28 | " sshd time to deliver output before kernel panic)\n"
29 | " -h Print this help\n"
30 | " -v Verbose (debug output)\n"
31 | , self);
32 | }
33 |
34 | int main(int argc, const char **argv)
35 | {
36 | vm_address_t kbase;
37 | FILE* f;
38 | size_t filesize = 0;
39 | unsigned char *binary;
40 | mach_hdr_t hdr_buf;
41 | size_t hdr_size;
42 | mach_hdr_t *orig_hdr, *hdr;
43 | mach_seg_t *seg;
44 | const char *outfile = "kernel.bin";
45 |
46 | int aoff;
47 | for(aoff = 1; aoff < argc; ++aoff)
48 | {
49 | if(argv[aoff][0] != '-')
50 | {
51 | break;
52 | }
53 | if(strcmp(argv[aoff], "-h") == 0)
54 | {
55 | print_usage(argv[0]);
56 | return 0;
57 | }
58 | if(strcmp(argv[aoff], "-d") == 0)
59 | {
60 | slow = true;
61 | }
62 | else if(strcmp(argv[aoff], "-v") == 0)
63 | {
64 | verbose = true;
65 | }
66 | else
67 | {
68 | fprintf(stderr, "[!] Unrecognized option: %s\n\n", argv[aoff]);
69 | print_usage(argv[0]);
70 | return -1;
71 | }
72 | }
73 | if(argc - aoff > 1)
74 | {
75 | fprintf(stderr, "[!] Too many arguments\n\n");
76 | print_usage(argv[0]);
77 | return -1;
78 | }
79 | else if(argc - aoff == 1)
80 | {
81 | outfile = argv[aoff];
82 | }
83 |
84 | KERNEL_BASE_OR_GTFO(kbase);
85 | fprintf(stderr, "[*] Found kernel base at address 0x" ADDR "\n", kbase);
86 |
87 | if(kernel_read(kbase, sizeof(hdr_buf), &hdr_buf) != sizeof(hdr_buf))
88 | {
89 | fprintf(stderr, "[!] Kernel I/O error\n");
90 | return -1;
91 | }
92 | hdr_size = sizeof(hdr_buf) + hdr_buf.sizeofcmds;
93 |
94 | orig_hdr = malloc(hdr_size);
95 | hdr = malloc(hdr_size);
96 | if(orig_hdr == NULL || hdr == NULL)
97 | {
98 | fprintf(stderr, "[!] Failed to allocate header buffer: %s\n", strerror(errno));
99 | return -1;
100 | }
101 | memset(hdr, 0, hdr_size);
102 |
103 | fprintf(stderr, "[*] Reading kernel header...\n");
104 | if(kernel_read(kbase, hdr_size, orig_hdr) != hdr_size)
105 | {
106 | fprintf(stderr, "[!] Kernel I/O error\n");
107 | return -1;
108 | }
109 | memcpy(hdr, orig_hdr, sizeof(*hdr));
110 | hdr->ncmds = 0;
111 | hdr->sizeofcmds = 0;
112 |
113 | /*
114 | * We now have the mach-o header with the LC_SEGMENT
115 | * load commands in it.
116 | * Next we are going to redo the loading process,
117 | * parse each load command and read the data from
118 | * vmaddr into fileoff.
119 | * Some parts of the mach-o can not be restored (e.g. LC_SYMTAB).
120 | * The load commands for these parts will be removed from the final
121 | * executable.
122 | */
123 |
124 | // loop through all segments once to determine file size
125 | CMD_ITERATE(orig_hdr, cmd)
126 | {
127 | switch(cmd->cmd)
128 | {
129 | case MACH_LC_SEGMENT:
130 | seg = (mach_seg_t*)cmd;
131 | filesize = max(filesize, seg->fileoff + seg->filesize);
132 | break;
133 | }
134 | }
135 | binary = malloc(filesize);
136 | if(binary == NULL)
137 | {
138 | fprintf(stderr, "[!] Failed to allocate dump buffer: %s\n", strerror(errno));
139 | return -1;
140 | }
141 | memset(binary, 0, filesize);
142 |
143 | // loop again to restore everything
144 | fprintf(stderr, "[*] Restoring segments...\n");
145 | CMD_ITERATE(orig_hdr, cmd)
146 | {
147 | switch(cmd->cmd)
148 | {
149 | case MACH_LC_SEGMENT:
150 | seg = (mach_seg_t*)cmd;
151 | fprintf(stderr, "[+] Found segment %s\n", seg->segname);
152 | if(kernel_read(seg->vmaddr, seg->filesize, binary + seg->fileoff) != seg->filesize)
153 | {
154 | fprintf(stderr, "[!] Kernel I/O error\n");
155 | return -1;
156 | }
157 | case LC_UUID:
158 | case LC_UNIXTHREAD:
159 | case LC_SOURCE_VERSION:
160 | case LC_FUNCTION_STARTS:
161 | case LC_VERSION_MIN_MACOSX:
162 | case LC_VERSION_MIN_IPHONEOS:
163 | case LC_VERSION_MIN_TVOS:
164 | case LC_VERSION_MIN_WATCHOS:
165 | memcpy((char*)(hdr + 1) + hdr->sizeofcmds, cmd, cmd->cmdsize);
166 | hdr->sizeofcmds += cmd->cmdsize;
167 | hdr->ncmds++;
168 | break;
169 | }
170 | }
171 |
172 | // now replace the old header with the new one ...
173 | memcpy(binary, hdr, sizeof(*hdr) + orig_hdr->sizeofcmds);
174 |
175 | // ... and write the final binary to file
176 | f = fopen(outfile, "wb");
177 | if(f == NULL)
178 | {
179 | fprintf(stderr, "[!] Failed to open %s for writing: %s\n", outfile, strerror(errno));
180 | return -1;
181 | }
182 | fwrite(binary, filesize, 1, f);
183 |
184 | fprintf(stderr, "[*] Done, wrote %lu bytes to %s\n", filesize, outfile);
185 | fclose(f);
186 |
187 | free(binary);
188 | free(hdr);
189 | free(orig_hdr);
190 |
191 | return 0;
192 | }
193 |
--------------------------------------------------------------------------------
/src/tools/kinfo.c:
--------------------------------------------------------------------------------
1 | /*
2 | * kinfo.c - Print various kernel info
3 | *
4 | * Copyright (c) 2017 Siguza
5 | */
6 |
7 | #include // true
8 | #include // printf, fprintf, stderr
9 | #include // strcmp
10 |
11 | #include // vm_address_t
12 | #include // arm_unified_thread_state_t
13 |
14 | #include "arch.h" // ADDR
15 | #include "debug.h" // slow, verbose
16 | #include "libkern.h" // KERNEL_BASE_OR_GTFO
17 | #include "mach-o.h" // CMD_ITERATE
18 |
19 | static void print_usage(const char *self)
20 | {
21 | fprintf(stderr, "Usage: %s [-h] [-v [-d]]\n"
22 | " -b Print the kernel text base\n"
23 | " -d Debug mode (sleep between function calls, gives\n"
24 | " sshd time to deliver output before kernel panic)\n"
25 | " -h Print this help\n"
26 | " -l Print the kernel load commands (kernel header)\n"
27 | " -v Verbose (debug output)\n"
28 | , self);
29 | }
30 |
31 | typedef struct
32 | {
33 | uint32_t c : 8;
34 | uint32_t b : 8;
35 | uint32_t a : 16;
36 | } version32_t;
37 |
38 | typedef struct
39 | {
40 | uint64_t e : 10;
41 | uint64_t d : 10;
42 | uint64_t c : 10;
43 | uint64_t b : 10;
44 | uint64_t a : 24;
45 | } version64_t;
46 |
47 | typedef struct
48 | {
49 | uint64_t e : 48;
50 | uint64_t d : 16;
51 | uint64_t c : 16;
52 | uint64_t b : 16;
53 | uint64_t a : 32;
54 | } my_uuid_t;
55 |
56 | typedef struct {
57 | uint32_t cmd;
58 | uint32_t cmdsize;
59 | #ifdef TARGET_MACOS
60 | x86_thread_state_t state;
61 | #else
62 | arm_unified_thread_state_t state;
63 | #endif
64 | } thread_cmd_t;
65 |
66 | int main(int argc, const char **argv)
67 | {
68 | bool base = false,
69 | header = false;
70 |
71 | for(int i = 1; i < argc; ++i)
72 | {
73 | if(strcmp(argv[i], "-h") == 0)
74 | {
75 | print_usage(argv[0]);
76 | return 0;
77 | }
78 | if(strcmp(argv[i], "-d") == 0)
79 | {
80 | slow = true;
81 | }
82 | else if(strcmp(argv[i], "-v") == 0)
83 | {
84 | verbose = true;
85 | }
86 | else if(strcmp(argv[i], "-b") == 0)
87 | {
88 | base = true;
89 | }
90 | else if(strcmp(argv[i], "-l") == 0)
91 | {
92 | header = true;
93 | }
94 | else
95 | {
96 | fprintf(stderr, "[!] Unrecognized option: %s\n\n", argv[i]);
97 | print_usage(argv[0]);
98 | return -1;
99 | }
100 | }
101 |
102 | size_t sum = base + header;
103 | if(sum != 1)
104 | {
105 | if(sum != 0)
106 | {
107 | fprintf(stderr, "[!] More than one action given\n\n");
108 | }
109 | print_usage(argv[0]);
110 | return sum == 0 ? 0 : -1;
111 | }
112 |
113 | vm_address_t kbase;
114 | KERNEL_BASE_OR_GTFO(kbase);
115 | if(base)
116 | {
117 | printf(ADDR "\n", kbase);
118 | }
119 | else if(header)
120 | {
121 | mach_hdr_t hdr_buf;
122 | if(kernel_read(kbase, sizeof(hdr_buf), &hdr_buf) != sizeof(hdr_buf))
123 | {
124 | fprintf(stderr, "[!] Kernel I/O error\n");
125 | return -1;
126 | }
127 | size_t hdr_size = sizeof(hdr_buf) + hdr_buf.sizeofcmds;
128 |
129 | mach_hdr_t *hdr = malloc(hdr_size);
130 | if(hdr == NULL)
131 | {
132 | fprintf(stderr, "[!] Failed to allocate header buffer: %s\n", strerror(errno));
133 | return -1;
134 | }
135 | if(kernel_read(kbase, hdr_size, hdr) != hdr_size)
136 | {
137 | fprintf(stderr, "[!] Kernel I/O error\n");
138 | return -1;
139 | }
140 |
141 | CMD_ITERATE(hdr, cmd)
142 | {
143 | switch(cmd->cmd)
144 | {
145 | case MACH_LC_SEGMENT:
146 | {
147 | mach_seg_t *seg = (mach_seg_t*)cmd;
148 | printf(MACH_LC_SEGMENT_NAME ": Mem: " ADDR "-" ADDR " File: " ADDR "-" ADDR " %c%c%c/%c%c%c %s\n"
149 | , (vm_address_t)seg->vmaddr, (vm_address_t)(seg->vmaddr + seg->vmsize), (vm_address_t)seg->fileoff, (vm_address_t)(seg->fileoff + seg->filesize)
150 | , (seg->initprot & VM_PROT_READ) ? 'r' : '-', (seg->initprot & VM_PROT_WRITE) ? 'w' : '-', (seg->initprot & VM_PROT_EXECUTE) ? 'x' : '-'
151 | , (seg->maxprot & VM_PROT_READ) ? 'r' : '-', (seg->maxprot & VM_PROT_WRITE) ? 'w' : '-', (seg->maxprot & VM_PROT_EXECUTE) ? 'x' : '-'
152 | , seg->segname);
153 | mach_sec_t *sec = (mach_sec_t*)(seg + 1);
154 | for(size_t i = 0; i < seg->nsects; ++i)
155 | {
156 | if(sec[i].flags == S_ZEROFILL)
157 | {
158 | printf(" Mem: " ADDR "-" ADDR " File: %-*s %s.%s\n"
159 | , (vm_address_t)sec[i].addr, (vm_address_t)(sec[i].addr + sec[i].size), (int)(4 * sizeof(void*) + 1), "Not mapped to file"
160 | , sec[i].segname, sec[i].sectname);
161 | }
162 | else
163 | {
164 | printf(" Mem: " ADDR "-" ADDR " File: " ADDR "-" ADDR " %s.%s\n"
165 | , (vm_address_t)sec[i].addr, (vm_address_t)(sec[i].addr + sec[i].size), (vm_address_t)sec[i].offset, (vm_address_t)(sec[i].offset + sec[i].size)
166 | , sec[i].segname, sec[i].sectname);
167 | }
168 | }
169 | }
170 | break;
171 | case LC_SYMTAB:
172 | {
173 | struct symtab_command *stab = (struct symtab_command*)cmd;
174 | printf("LC_SYMTAB:\n"
175 | " Symbol table: Offset 0x%x, %u entries\n"
176 | " String table: Offset 0x%x, %u bytes\n"
177 | , stab->symoff, stab->nsyms, stab->stroff, stab->strsize);
178 | }
179 | break;
180 | case LC_DYSYMTAB:
181 | {
182 | struct dysymtab_command *dstab = (struct dysymtab_command*)cmd;
183 | printf("LC_DYSYMTAB:\n"
184 | " Local symbols: Offset 0x%x, %u entries\n"
185 | " External symbols: Offset 0x%x, %u entries\n"
186 | " Undefined symbols: Offset 0x%x, %u entries\n"
187 | " Table of contents: Offset 0x%x, %u entries\n"
188 | " Module table: Offset 0x%x, %u entries\n"
189 | " Referenced symbols: Offset 0x%x, %u entries\n"
190 | " Indirect symbols: Offset 0x%x, %u entries\n"
191 | " External reloc entries: Offset 0x%x, %u entries\n"
192 | " Local reloc entries: Offset 0x%x, %u entries\n"
193 | , dstab->ilocalsym, dstab->nlocalsym, dstab->iextdefsym, dstab->nextdefsym, dstab->iundefsym, dstab->nundefsym
194 | , dstab->tocoff, dstab->ntoc, dstab->modtaboff, dstab->nmodtab, dstab->extrefsymoff, dstab->nextrefsyms
195 | , dstab->indirectsymoff, dstab->nindirectsyms, dstab->extreloff, dstab->nextrel, dstab->locreloff, dstab->nlocrel);
196 | }
197 | break;
198 | case LC_UUID:
199 | {
200 | my_uuid_t *uuid = (my_uuid_t*)&((struct uuid_command*)cmd)->uuid;
201 | printf("LC_UUID: UUID: %08llX-%04llX-%04llX-%04llX-%012llX\n"
202 | , uuid->a, uuid->b, uuid->c, uuid->d, uuid->e);
203 | }
204 | break;
205 | case LC_VERSION_MIN_MACOSX:
206 | case LC_VERSION_MIN_IPHONEOS:
207 | case LC_VERSION_MIN_TVOS:
208 | case LC_VERSION_MIN_WATCHOS:
209 | {
210 | struct version_min_command *vers = (struct version_min_command*)cmd;
211 | version32_t *version = (version32_t*)&vers->version,
212 | *sdkvers = (version32_t*)&vers->sdk;
213 | const char *str = cmd->cmd == LC_VERSION_MIN_MACOSX ? "LC_VERSION_MIN_MACOSX" :
214 | cmd->cmd == LC_VERSION_MIN_IPHONEOS ? "LC_VERSION_MIN_IPHONEOS" :
215 | cmd->cmd == LC_VERSION_MIN_TVOS ? "LC_VERSION_MIN_TVOS" : "LC_VERSION_MIN_WATCHOS";
216 | printf("%s: %-*sMinimum version: %u.%u.%u, Built with SDK: %u.%u.%u\n"
217 | , str, (int)(30 - strlen(str)), ""
218 | , version->a, version->b, version->c
219 | , sdkvers->a, sdkvers->b, sdkvers->c);
220 | }
221 | break;
222 | case LC_SOURCE_VERSION:
223 | {
224 | struct source_version_command *vers = (struct source_version_command*)cmd;
225 | version64_t *version = (version64_t*)&vers->version;
226 | printf("LC_SOURCE_VERSION: Source version: %llu.%llu.%llu.%llu.%llu\n"
227 | , version->a, version->b, version->c, version->d, version->e);
228 | }
229 | break;
230 | case LC_FUNCTION_STARTS:
231 | {
232 | struct linkedit_data_command *link = (struct linkedit_data_command*)cmd;
233 | printf("LC_FUNCTION_STARTS: Offset 0x%x, %u bytes\n"
234 | , link->dataoff, link->datasize);
235 | }
236 | break;
237 | case LC_UNIXTHREAD:
238 | {
239 | thread_cmd_t *thread = (thread_cmd_t*)cmd;
240 | #ifdef TARGET_MACOS
241 | if(thread->state.tsh.flavor == x86_THREAD_STATE64)
242 | {
243 | x86_thread_state64_t *t = &thread->state.uts.ts64;
244 | printf("LC_UNIXTHREAD:\n"
245 | " rax: 0x%016llx rbx: 0x%016llx rcx: 0x%016llx rdx: 0x%016llx\n"
246 | " rdi: 0x%016llx rsi: 0x%016llx rbp: 0x%016llx rsp: 0x%016llx\n"
247 | " r8: 0x%016llx r9: 0x%016llx r10: 0x%016llx r11: 0x%016llx\n"
248 | " r12: 0x%016llx r13: 0x%016llx r14: 0x%016llx r15: 0x%016llx\n"
249 | " rip: 0x%016llx rfl: 0x%016llx\n"
250 | " cs: 0x%016llx fs: 0x%016llx gs: 0x%016llx\n"
251 | , t->__rax, t->__rbx, t->__rcx, t->__rdx
252 | , t->__rdi, t->__rsi, t->__rbp, t->__rsp
253 | , t->__r8 , t->__r9 , t->__r10, t->__r11
254 | , t->__r12, t->__r13, t->__r14, t->__r15
255 | , t->__rip, t->__rflags
256 | , t->__cs , t->__fs , t->__gs);
257 | }
258 | #else
259 | if(thread->state.ash.flavor == ARM_THREAD_STATE)
260 | {
261 | arm_thread_state32_t *t = &thread->state.ts_32;
262 | printf("LC_UNIXTHREAD:\n"
263 | " r0: 0x%08x r1: 0x%08x r2: 0x%08x r3: 0x%08x\n"
264 | " r4: 0x%08x r5: 0x%08x r6: 0x%08x r7: 0x%08x\n"
265 | " r8: 0x%08x r9: 0x%08x r10: 0x%08x r11: 0x%08x\n"
266 | " r12: 0x%08x sp: 0x%08x lr: 0x%08x pc: 0x%08x\n"
267 | " cpsr: 0x%08x\n"
268 | , t->__r[ 0], t->__r[ 1], t->__r[ 2], t->__r[ 3]
269 | , t->__r[ 4], t->__r[ 5], t->__r[ 6], t->__r[ 7]
270 | , t->__r[ 8], t->__r[ 9], t->__r[10], t->__r[11]
271 | , t->__r[12], t->__sp , t->__lr , t->__pc
272 | , t->__cpsr);
273 | }
274 | else if(thread->state.ash.flavor == ARM_THREAD_STATE64)
275 | {
276 | arm_thread_state64_t *t = &thread->state.ts_64;
277 | printf("LC_UNIXTHREAD:\n"
278 | " x0: 0x%016llx x1: 0x%016llx x2: 0x%016llx x3: 0x%016llx\n"
279 | " x4: 0x%016llx x5: 0x%016llx x6: 0x%016llx x7: 0x%016llx\n"
280 | " x8: 0x%016llx x9: 0x%016llx x10: 0x%016llx x11: 0x%016llx\n"
281 | " x12: 0x%016llx x13: 0x%016llx x14: 0x%016llx x15: 0x%016llx\n"
282 | " x16: 0x%016llx x17: 0x%016llx x18: 0x%016llx x19: 0x%016llx\n"
283 | " x20: 0x%016llx x21: 0x%016llx x22: 0x%016llx x23: 0x%016llx\n"
284 | " x24: 0x%016llx x25: 0x%016llx x26: 0x%016llx x27: 0x%016llx\n"
285 | " x28: 0x%016llx fp: 0x%016llx lr: 0x%016llx sp: 0x%016llx\n"
286 | " pc: 0x%016llx cpsr: 0x%08x\n"
287 | , t->__x[ 0], t->__x[ 1], t->__x[ 2], t->__x[ 3]
288 | , t->__x[ 4], t->__x[ 5], t->__x[ 6], t->__x[ 7]
289 | , t->__x[ 8], t->__x[ 9], t->__x[10], t->__x[11]
290 | , t->__x[12], t->__x[13], t->__x[14], t->__x[15]
291 | , t->__x[16], t->__x[17], t->__x[18], t->__x[19]
292 | , t->__x[20], t->__x[21], t->__x[22], t->__x[23]
293 | , t->__x[24], t->__x[25], t->__x[26], t->__x[27]
294 | , t->__x[28], t->__fp , t->__lr , t->__sp
295 | , t->__pc , t->__cpsr);
296 | }
297 | #endif
298 | else
299 | {
300 | printf("Cannot parse LC_UNIXTHREAD: Unknown flavor\n");
301 | }
302 | }
303 | break;
304 | default:
305 | printf("Unknown load command: 0x%x\n", cmd->cmd);
306 | break;
307 | }
308 | }
309 |
310 | free(hdr);
311 | }
312 |
313 | return 0;
314 | }
315 |
--------------------------------------------------------------------------------
/src/tools/kmap.c:
--------------------------------------------------------------------------------
1 | /*
2 | * kmap.c - Display a listing of the kernel memory mappings
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #include // UINT_MAX
9 | #include // bool, true, false
10 | #include // printf, fprintf, stderr
11 |
12 | #include // KERN_SUCCESS, kern_return_t
13 | #include // task_t
14 | #include // mach_msg_type_number_t
15 | #include // VM_INHERIT_*
16 | #include // vm_region_recurse_64
17 | #include // VM_PROT_READ, VM_PROT_WRITE, VM_PROT_EXECUTE
18 | #include // VM_REGION_SUBMAP_INFO_COUNT_64, vm_region_info_t, vm_region_submap_info_data_64_t
19 | #include // vm_address_t, vm_size_t
20 |
21 | #include "arch.h" // ADDR
22 | #include "debug.h" // slow, verbose
23 | #include "libkern.h" // get_kernel_task
24 |
25 | #define VM_KERN_MEMORY_NONE 0
26 | #define VM_KERN_MEMORY_OSFMK 1
27 | #define VM_KERN_MEMORY_BSD 2
28 | #define VM_KERN_MEMORY_IOKIT 3
29 | #define VM_KERN_MEMORY_LIBKERN 4
30 | #define VM_KERN_MEMORY_OSKEXT 5
31 | #define VM_KERN_MEMORY_KEXT 6
32 | #define VM_KERN_MEMORY_IPC 7
33 | #define VM_KERN_MEMORY_STACK 8
34 | #define VM_KERN_MEMORY_CPU 9
35 | #define VM_KERN_MEMORY_PMAP 10
36 | #define VM_KERN_MEMORY_PTE 11
37 | #define VM_KERN_MEMORY_ZONE 12
38 | #define VM_KERN_MEMORY_KALLOC 13
39 | #define VM_KERN_MEMORY_COMPRESSOR 14
40 | #define VM_KERN_MEMORY_COMPRESSED_DATA 15
41 | #define VM_KERN_MEMORY_PHANTOM_CACHE 16
42 | #define VM_KERN_MEMORY_WAITQ 17
43 | #define VM_KERN_MEMORY_DIAG 18
44 | #define VM_KERN_MEMORY_LOG 19
45 | #define VM_KERN_MEMORY_FILE 20
46 | #define VM_KERN_MEMORY_MBUF 21
47 | #define VM_KERN_MEMORY_UBC 22
48 | #define VM_KERN_MEMORY_SECURITY 23
49 | #define VM_KERN_MEMORY_MLOCK 24
50 | #define VM_KERN_MEMORY_REASON 25
51 | #define VM_KERN_MEMORY_SKYWALK 26
52 | #define VM_KERN_MEMORY_LTABLE 27
53 | #define VM_KERN_MEMORY_FIRST_DYNAMIC 28
54 |
55 | static const char* kern_tag(uint32_t tag)
56 | {
57 | switch(tag)
58 | {
59 | case VM_KERN_MEMORY_NONE: return "";
60 | case VM_KERN_MEMORY_OSFMK: return "OSFMK";
61 | case VM_KERN_MEMORY_BSD: return "BSD";
62 | case VM_KERN_MEMORY_IOKIT: return "IOKit";
63 | case VM_KERN_MEMORY_LIBKERN: return "Libkern";
64 | case VM_KERN_MEMORY_OSKEXT: return "OSKext";
65 | case VM_KERN_MEMORY_KEXT: return "Kext";
66 | case VM_KERN_MEMORY_IPC: return "IPC";
67 | case VM_KERN_MEMORY_STACK: return "Stack";
68 | case VM_KERN_MEMORY_CPU: return "CPU management";
69 | case VM_KERN_MEMORY_PMAP: return "Page map";
70 | case VM_KERN_MEMORY_PTE: return "Page table";
71 | case VM_KERN_MEMORY_ZONE: return "Zalloc";
72 | case VM_KERN_MEMORY_KALLOC: return "Kalloc";
73 | case VM_KERN_MEMORY_COMPRESSOR: return "VM compressor";
74 | case VM_KERN_MEMORY_COMPRESSED_DATA: return "TODO";
75 | case VM_KERN_MEMORY_PHANTOM_CACHE: return "Phantom (thrashing)";
76 | case VM_KERN_MEMORY_WAITQ: return "TODO";
77 | case VM_KERN_MEMORY_DIAG: return "Diagnostics";
78 | case VM_KERN_MEMORY_LOG: return "TODO";
79 | case VM_KERN_MEMORY_FILE: return "File handling";
80 | case VM_KERN_MEMORY_MBUF: return "TODO";
81 | case VM_KERN_MEMORY_UBC: return "TODO";
82 | case VM_KERN_MEMORY_SECURITY: return "Security/MACF";
83 | case VM_KERN_MEMORY_MLOCK: return "TODO";
84 | case VM_KERN_MEMORY_REASON: return "TODO";
85 | case VM_KERN_MEMORY_SKYWALK: return "Skywalk";
86 | case VM_KERN_MEMORY_LTABLE: return "TODO";
87 | }
88 |
89 | return tag >= VM_KERN_MEMORY_FIRST_DYNAMIC ? "(dynamic)" : "???";
90 | }
91 |
92 | static const char* share_mode(char mode)
93 | {
94 | switch(mode)
95 | {
96 | case SM_COW: return "cow";
97 | case SM_PRIVATE: return "prv";
98 | case SM_EMPTY: return "nul";
99 | case SM_SHARED: return "shm";
100 | case SM_TRUESHARED: return "tru";
101 | case SM_PRIVATE_ALIASED: return "p/a";
102 | case SM_SHARED_ALIASED: return "s/a";
103 | case SM_LARGE_PAGE: return "big";
104 | }
105 | return "???";
106 | }
107 |
108 | static const char* inheritance(vm_inherit_t inh)
109 | {
110 | switch(inh)
111 | {
112 | case VM_INHERIT_SHARE: return "sh";
113 | case VM_INHERIT_COPY: return "cp";
114 | case VM_INHERIT_NONE: return "--";
115 | case VM_INHERIT_DONATE_COPY: return "dn";
116 | }
117 | return "??";
118 | }
119 |
120 | static void print_usage(const char *self)
121 | {
122 | fprintf(stderr, "Usage: %s [-h] [-v [-d]] [-e]\n"
123 | " -d Debug mode (sleep between function calls, gives\n"
124 | " sshd time to deliver output before kernel panic)\n"
125 | " -e Extended output (print all information available)\n"
126 | " -g Show gaps between regions\n"
127 | " -h Print this help\n"
128 | " -v Verbose (debug output)\n"
129 | , self);
130 | }
131 |
132 | static void print_range(task_t kernel_task, bool extended, bool gaps, unsigned int level, vm_address_t min, vm_address_t max)
133 | {
134 | vm_region_submap_info_data_64_t info;
135 | vm_address_t last_addr = min;
136 | vm_size_t size, last_size;
137 | mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64;
138 | unsigned int depth;
139 | size_t displaysize, last_displaysize;
140 | char scale, last_scale;
141 | char curA, curR, curW, curX, maxA, maxR, maxW, maxX;
142 |
143 | for(vm_address_t addr = min; 1; addr += size)
144 | {
145 | // get next memory region
146 | depth = level;
147 | if(vm_region_recurse_64(kernel_task, &addr, &size, &depth, (vm_region_info_t)&info, &info_count) != KERN_SUCCESS)
148 | {
149 | break;
150 | }
151 | if(addr >= max)
152 | {
153 | addr = max;
154 | }
155 |
156 | if(gaps)
157 | {
158 | if(last_addr != 0)
159 | {
160 | last_size = addr - last_addr;
161 | if(last_size > 0)
162 | {
163 | last_scale = 'K';
164 | last_displaysize = last_size / 1024;
165 | if(last_displaysize > 4096)
166 | {
167 | last_scale = 'M';
168 | last_displaysize /= 1024;
169 | if(last_displaysize > 4096)
170 | {
171 | last_scale = 'G';
172 | last_displaysize /= 1024;
173 | }
174 | }
175 | printf("%*s [%4zu%c]\n"
176 | , (int)(4 * sizeof(void*) + 1 + (extended ? 4 : 0)), ""
177 | , last_displaysize, last_scale
178 | );
179 | }
180 | }
181 | last_addr = addr + size;
182 | }
183 |
184 | if(addr >= max)
185 | {
186 | break;
187 | }
188 |
189 | // size
190 | scale = 'K';
191 | displaysize = size / 1024;
192 | if(displaysize > 4096)
193 | {
194 | scale = 'M';
195 | displaysize /= 1024;
196 | if(displaysize > 4096)
197 | {
198 | scale = 'G';
199 | displaysize /= 1024;
200 | }
201 | }
202 |
203 | // protection
204 | curA = (info.protection) & ~(VM_PROT_ALL) ? '+' : '-';
205 | curR = (info.protection) & VM_PROT_READ ? 'r' : '-';
206 | curW = (info.protection) & VM_PROT_WRITE ? 'w' : '-';
207 | curX = (info.protection) & VM_PROT_EXECUTE ? 'x' : '-';
208 | maxA = (info.max_protection) & ~(VM_PROT_ALL) ? '+' : '-';
209 | maxR = (info.max_protection) & VM_PROT_READ ? 'r' : '-';
210 | maxW = (info.max_protection) & VM_PROT_WRITE ? 'w' : '-';
211 | maxX = (info.max_protection) & VM_PROT_EXECUTE ? 'x' : '-';
212 |
213 | if(extended)
214 | {
215 | printf("%*s" ADDR "-" ADDR "%*s" " [%4zu%c] %c%c%c%c/%c%c%c%c [%s %s %s] %016llx [%u %u %hu %hhu %hu] %08x/%08x:<%10u> %u,%u {%10u,%10u} %s\n"
216 | , 4 * level, "", addr, addr+size, 4 * (1 - level), ""
217 | , displaysize, scale
218 | , curA, curR, curW, curX
219 | , maxA, maxR, maxW, maxX
220 | , info.is_submap ? "map" : depth > 0 ? "sub" : "mem", share_mode(info.share_mode), inheritance(info.inheritance), info.offset
221 | , info.behavior, info.pages_reusable, info.user_wired_count, info.external_pager, info.shadow_depth // these should all be 0
222 | , info.user_tag, info.object_id, info.ref_count
223 | , info.pages_swapped_out, info.pages_shared_now_private, info.pages_resident, info.pages_dirtied
224 | , kern_tag(info.user_tag)
225 | );
226 | }
227 | else
228 | {
229 | printf(ADDR "-" ADDR " [%4zu%c] %c%c%c/%c%c%c\n"
230 | , addr, addr + size, displaysize, scale
231 | , curR, curW, curX, maxR, maxW, maxX);
232 | }
233 |
234 | if(info.is_submap)
235 | {
236 | print_range(kernel_task, extended, gaps, level + 1, addr, addr + size);
237 | }
238 | }
239 | }
240 |
241 | int main(int argc, const char **argv)
242 | {
243 | bool extended = false,
244 | gaps = false;
245 |
246 | for(int i = 1; i < argc; ++i)
247 | {
248 | if(strcmp(argv[i], "-h") == 0)
249 | {
250 | print_usage(argv[0]);
251 | return 0;
252 | }
253 | if(strcmp(argv[i], "-d") == 0)
254 | {
255 | slow = true;
256 | }
257 | else if(strcmp(argv[i], "-v") == 0)
258 | {
259 | verbose = true;
260 | }
261 | else if(strcmp(argv[i], "-e") == 0)
262 | {
263 | extended = true;
264 | }
265 | else if(strcmp(argv[i], "-g") == 0)
266 | {
267 | gaps = true;
268 | }
269 | else
270 | {
271 | fprintf(stderr, "[!] Unrecognized option: %s\n\n", argv[i]);
272 | print_usage(argv[0]);
273 | return -1;
274 | }
275 | }
276 |
277 | task_t kernel_task;
278 | KERNEL_TASK_OR_GTFO(kernel_task);
279 |
280 | print_range(kernel_task, extended, gaps, 0, 0, ~0);
281 |
282 | return 0;
283 | }
284 |
--------------------------------------------------------------------------------
/src/tools/kmem.c:
--------------------------------------------------------------------------------
1 | /*
2 | * kmem.c - Read kernel memory and dump it to the console
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #include // errno
9 | #include // bool, true, false
10 | #include // printf, fprintf
11 | #include // free, malloc, strtoull
12 | #include // memset, strlen
13 | #include // getopt, write, STDOUT_FILENO
14 |
15 | #include // task_t
16 | #include // vm_address_t, vm_size_t
17 |
18 | #include "arch.h" // ADDR
19 | #include "libkern.h" // KERNEL_TASK_OR_GTFO, kernel_read
20 |
21 | static void hexdump(unsigned char *data, size_t size)
22 | {
23 | int i;
24 | char cs[17];
25 | memset(cs, 0, 17);
26 |
27 | for(i = 0; i < size; i++)
28 | {
29 | if(i != 0 && i % 0x10 == 0)
30 | {
31 | printf(" |%s|\n", cs);
32 | memset(cs, 0, 17);
33 | }
34 | else if(i != 0 && i % 0x8 == 0)
35 | {
36 | printf(" ");
37 | }
38 | printf("%02X ", data[i]);
39 | cs[(i % 0x10)] = (data[i] >= 0x20 && data[i] <= 0x7e) ? data[i] : '.';
40 | }
41 |
42 | i = i % 0x10;
43 | if(i != 0)
44 | {
45 | if(i <= 0x8)
46 | {
47 | printf(" ");
48 | }
49 | while(i++ < 0x10)
50 | {
51 | printf(" ");
52 | }
53 | }
54 | printf(" |%s|\n", cs);
55 | }
56 |
57 | static void print_usage(const char *self)
58 | {
59 | fprintf(stderr, "Usage: %s [-r] [-h] addr length\n"
60 | "0x for hex, no prefix for decimal\n"
61 | "\n"
62 | "Options:\n"
63 | " -h Help\n"
64 | " -r Raw (binary) output (defaults to hex)\n"
65 | , self);
66 | }
67 |
68 | static void too_few_args(const char *self)
69 | {
70 | fprintf(stderr, "[!] Too few arguments\n");
71 | print_usage(self);
72 | }
73 |
74 | int main(int argc, char **argv)
75 | {
76 | bool raw = false; // print raw bytes instead of a hexdump
77 | vm_address_t addr;
78 | vm_size_t size;
79 | char c, *end;
80 |
81 | while((c = getopt(argc, argv, "rh")) != -1)
82 | {
83 | switch (c)
84 | {
85 | case 'r':
86 | raw = true;
87 | break;
88 | case 'h':
89 | print_usage(argv[0]);
90 | return 0;
91 | }
92 | }
93 |
94 | if(argc < optind + 2)
95 | {
96 | too_few_args(argv[0]);
97 | return -1;
98 | }
99 |
100 | // addr
101 | errno = 0;
102 | addr = strtoull(argv[optind], &end, 0);
103 | if(argv[optind][0] == '\0' || end[0] != '\0' || errno != 0)
104 | {
105 | fprintf(stderr, "[!] Failed to parse \"%s\": %s\n", argv[optind], argv[optind][0] == '\0' ? "zero characters given" : strerror(errno));
106 | return -1;
107 | }
108 |
109 | // size
110 | errno = 0;
111 | size = strtoull(argv[optind + 1], &end, 0);
112 | if(argv[optind + 1][0] == '\0' || end[0] != '\0' || errno != 0)
113 | {
114 | fprintf(stderr, "[!] Failed to parse \"%s\": %s\n", argv[optind + 1], argv[optind + 1][0] == '\0' ? "zero characters given" : strerror(errno));
115 | return -1;
116 | }
117 | if(size == 0)
118 | {
119 | fprintf(stderr, "[!] Size must be > 0\n");
120 | return -1;
121 | }
122 |
123 | KERNEL_TASK_OR_GTFO();
124 |
125 | if(!raw)
126 | {
127 | fprintf(stderr, "[*] Reading " SIZE " bytes from 0x" ADDR "\n", size, addr);
128 | }
129 | unsigned char* buf = malloc(size);
130 | kernel_read(addr, size, buf);
131 |
132 | if(raw)
133 | {
134 | write(STDOUT_FILENO, buf, size);
135 | }
136 | else
137 | {
138 | hexdump(buf, size);
139 | }
140 |
141 | free(buf);
142 | return 0;
143 | }
144 |
--------------------------------------------------------------------------------
/src/tools/kpatch.c:
--------------------------------------------------------------------------------
1 | /*
2 | * kpatch.c - Apply patches to a running kenel
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016-2017 Siguza
6 | */
7 |
8 | #include // errno
9 | #include // fprintf, stderr
10 | #include // free, malloc, strtoull
11 | #include // strcmp
12 |
13 | #include // vm_address_t, vm_size_t
14 |
15 | #include "arch.h" // SIZE
16 | #include "debug.h" // slow, verbose
17 | #include "libkern.h" // kernel_write
18 |
19 | static void print_usage(const char *self)
20 | {
21 | fprintf(stderr, "Usage:\n"
22 | " %s [options] -f addr file\n"
23 | " %s [options] -w/-q addr 0x...\n"
24 | " %s [options] -x addr ...\n"
25 | "\n"
26 | "Options:\n"
27 | " -d Debug mode (sleep between function calls, gives\n"
28 | " sshd time to deliver output before kernel panic)\n"
29 | " -f Read patch from file\n"
30 | " -h Print this help\n"
31 | " -q Patch uint64 from immediate\n"
32 | " (Requires addr to be 8-byte aligned)\n"
33 | " -v Verbose (debug output)\n"
34 | " -w Patch uint32 from immediate\n"
35 | " (Requires addr to be 4-byte aligned)\n"
36 | " -x Patch from immediate hex string\n"
37 | " (little endian, must have even amount of chars)\n"
38 | , self, self, self);
39 | }
40 |
41 | int main(int argc, const char **argv)
42 | {
43 | char *end;
44 | bool file = false,
45 | wide = false,
46 | quad = false,
47 | hex = false;
48 |
49 | int aoff;
50 | for(aoff = 1; aoff < argc; ++aoff)
51 | {
52 | if(argv[aoff][0] != '-')
53 | {
54 | break;
55 | }
56 | if(strcmp(argv[aoff], "-h") == 0)
57 | {
58 | print_usage(argv[0]);
59 | return 0;
60 | }
61 | if(strcmp(argv[aoff], "-d") == 0)
62 | {
63 | slow = true;
64 | }
65 | else if(strcmp(argv[aoff], "-v") == 0)
66 | {
67 | verbose = true;
68 | }
69 | else if(strcmp(argv[aoff], "-f") == 0)
70 | {
71 | file = true;
72 | }
73 | else if(strcmp(argv[aoff], "-w") == 0)
74 | {
75 | wide = true;
76 | }
77 | else if(strcmp(argv[aoff], "-q") == 0)
78 | {
79 | quad = true;
80 | }
81 | else if(strcmp(argv[aoff], "-x") == 0)
82 | {
83 | hex = true;
84 | }
85 | else
86 | {
87 | fprintf(stderr, "[!] Unrecognized option: %s\n\n", argv[aoff]);
88 | print_usage(argv[0]);
89 | return -1;
90 | }
91 | }
92 |
93 | size_t sum = file + wide + quad + hex;
94 | if(sum != 1)
95 | {
96 | if(sum != 0)
97 | {
98 | fprintf(stderr, "[!] More than one action given\n\n");
99 | }
100 | print_usage(argv[0]);
101 | return sum == 0 ? 0 : -1;
102 | }
103 |
104 | if(argc - aoff != 2)
105 | {
106 | fprintf(stderr, "[!] Too %s arguments.\n\n", (argc - aoff) < 2 ? "few" : "many");
107 | print_usage(argv[0]);
108 | return -1;
109 | }
110 |
111 | errno = 0;
112 | vm_address_t addr = strtoull(argv[argc - 2], &end, 0);
113 | if(argv[argc - 2][0] == '\0' || end[0] != '\0' || errno != 0)
114 | {
115 | fprintf(stderr, "[!] Failed to parse \"%s\": %s\n", argv[argc - 2], argv[argc - 2][0] == '\0' ? "zero characters given" : strerror(errno));
116 | return -1;
117 | }
118 |
119 | // before we do any memory allocation
120 | KERNEL_TASK_OR_GTFO();
121 |
122 | uint64_t imm;
123 | void *patch = NULL;
124 | vm_size_t len = 0;
125 | if(file)
126 | {
127 | FILE *f = fopen(argv[argc - 1], "r");
128 | if(f == NULL)
129 | {
130 | fprintf(stderr, "[!] Failed to open %s: %s\n", argv[argc - 1], strerror(errno));
131 | return -1;
132 | }
133 | if(fseek(f, 0, SEEK_END) != 0)
134 | {
135 | fprintf(stderr, "[!] fseek(SEEK_END) failed: %s\n", strerror(errno));
136 | return -1;
137 | }
138 | long off = ftell(f);
139 | if(off == -1)
140 | {
141 | fprintf(stderr, "[!] Failed to get stream position: %s\n", strerror(errno));
142 | return -1;
143 | }
144 | if(fseek(f, 0, SEEK_SET) != 0)
145 | {
146 | fprintf(stderr, "[!] fseek(SEEK_SET) failed: %s\n", strerror(errno));
147 | return -1;
148 | }
149 | len = off;
150 | patch = malloc(len);
151 | if(patch == NULL)
152 | {
153 | fprintf(stderr, "[!] Failed to allocate memory: %s\n", strerror(errno));
154 | return -1;
155 | }
156 | if(fread(patch, len, 1, f) != 1)
157 | {
158 | fprintf(stderr, "[!] Failed to read from file: %s\n", strerror(errno));
159 | return -1;
160 | }
161 | fclose(f);
162 | }
163 | else if(hex)
164 | {
165 | size_t size = strlen(argv[argc - 1]);
166 | if(size % 2 != 0)
167 | {
168 | fprintf(stderr, "[!] Hex string must have even number of chars\n");
169 | return -1;
170 | }
171 | len = size / 2;
172 | patch = malloc(len);
173 | if(patch == NULL)
174 | {
175 | fprintf(stderr, "[!] Failed to allocate memory: %s\n", strerror(errno));
176 | return -1;
177 | }
178 | for(size_t i = 0; i < len; ++i)
179 | {
180 | char c = argv[argc - 1][2*i ],
181 | d = argv[argc - 1][2*i + 1];
182 | #define check(digit) \
183 | do \
184 | { \
185 | if(((digit) < '0' || (digit) > '9') && ((digit) < 'a' || (digit) > 'f') && ((digit) < 'A' || (digit) > 'F')) \
186 | { \
187 | fprintf(stderr, "[!] Invalid hex digit: %c\n", (digit)); \
188 | return -1; \
189 | } \
190 | } while(0)
191 | #define parse(digit) ((digit) - (((digit) >= '0' && (digit) <= '9') ? '0' : ((((digit) >= 'a' && (digit) <= 'f') ? 'a' : 'A') - 10)))
192 | check(c);
193 | check(d);
194 | ((uint8_t*)patch)[i] = (parse(c) << 4) | parse(d);
195 | #undef check
196 | #undef parse
197 | }
198 | }
199 | else if(wide || quad)
200 | {
201 | errno = 0;
202 | imm = strtoull(argv[argc - 1], &end, 0);
203 | if(argv[argc - 1][0] == '\0' || end[0] != '\0' || errno != 0)
204 | {
205 | fprintf(stderr, "[!] Failed to parse \"%s\": %s\n", argv[argc - 1], argv[argc - 1][0] == '\0' ? "zero characters given" : strerror(errno));
206 | return -1;
207 | }
208 |
209 | patch = &imm;
210 | len = quad ? sizeof(uint64_t) : sizeof(uint32_t);
211 |
212 | if(addr % len != 0)
213 | {
214 | fprintf(stderr, "[!] Address is not " SIZE "-byte aligned\n", len);
215 | return -1;
216 | }
217 | }
218 |
219 | if(patch == NULL || len == 0)
220 | {
221 | fprintf(stderr, "[!] Failed to parse patch\n");
222 | return -1;
223 | }
224 |
225 | vm_size_t written = kernel_write(addr, len, patch);
226 | if(written != len)
227 | {
228 | fprintf(stderr, "[!] Error, wrote " SIZE " bytes instead of " SIZE "\n", written, len);
229 | return -1;
230 | }
231 |
232 | if(file || hex)
233 | {
234 | free(patch);
235 | }
236 |
237 | fprintf(stderr, "[*] Done\n");
238 | return 0;
239 | }
240 |
--------------------------------------------------------------------------------
/src/tools/nvpatch.c:
--------------------------------------------------------------------------------
1 | /*
2 | * nvpatch.c - Patch kernel to unrestrict NVRAM variables
3 | *
4 | * Copyright (c) 2014 Samuel Groß
5 | * Copyright (c) 2016 Pupyshev Nikita
6 | * Copyright (c) 2017 Siguza
7 | */
8 |
9 | #include // errno
10 | #include // fprintf, stderr
11 | #include // free, malloc
12 | #include // memmem, strcmp, strnlen
13 |
14 | #include "arch.h" // ADDR, MACH_*, mach_*
15 | #include "debug.h" // DEBUG, slow, verbose
16 | #include "libkern.h" // KERNEL_BASE_OR_GTFO, kernel_read
17 | #include "mach-o.h" // CMD_ITERATE
18 |
19 | #define MAX_HEADER_SIZE 0x4000
20 |
21 | #define STRING_SEG "__TEXT"
22 | #define STRING_SEC "__cstring"
23 | #define OFVAR_SEG "__DATA"
24 | #define OFVAR_SEC "__data"
25 |
26 | enum
27 | {
28 | kOFVarTypeBoolean = 1,
29 | kOFVarTypeNumber,
30 | kOFVarTypeString,
31 | kOFVarTypeData,
32 | };
33 |
34 | enum
35 | {
36 | kOFVarPermRootOnly = 0,
37 | kOFVarPermUserRead,
38 | kOFVarPermUserWrite,
39 | kOFVarPermKernelOnly,
40 | };
41 |
42 | typedef struct
43 | {
44 | vm_address_t name;
45 | uint32_t type;
46 | uint32_t perm;
47 | int32_t offset;
48 | } OFVar;
49 |
50 | typedef struct
51 | {
52 | vm_address_t addr;
53 | vm_size_t len;
54 | char *buf;
55 | } segment_t;
56 |
57 | #define MAX_TYPELEN 6
58 | static const char* type_name(uint32_t type)
59 | {
60 | switch(type)
61 | {
62 | case kOFVarTypeBoolean: return "bool";
63 | case kOFVarTypeNumber: return "number";
64 | case kOFVarTypeString: return "string";
65 | case kOFVarTypeData: return "data";
66 | }
67 | return "???";
68 | }
69 |
70 | #define MAX_PERMLEN 5
71 | static const char* perm_name(uint32_t perm)
72 | {
73 | switch(perm)
74 | {
75 | case kOFVarPermUserWrite: return "rw/rw";
76 | case kOFVarPermUserRead: return "rw/r-";
77 | case kOFVarPermRootOnly: return "rw/--";
78 | case kOFVarPermKernelOnly: return "--/--";
79 | }
80 | return "???";
81 | }
82 |
83 | static void print_usage(const char *self)
84 | {
85 | fprintf(stderr, "DISCLAIMER: YOU ARE MESSING WITH NVRAM AT YOUR OWN RISK!\n"
86 | "\n"
87 | "Usage:\n"
88 | " %s [options]\n"
89 | " %s [options] variable-name\n"
90 | "\n"
91 | "The first form lists all registered NVRAM variables with type and permission (as root/anyone).\n"
92 | "The second form patches the kernel to unrestrict the given variable.\n"
93 | "\n"
94 | "Options:\n"
95 | " -d Debug mode (sleep between function calls, gives\n"
96 | " sshd time to deliver output before kernel panic)\n"
97 | " -h Print this help\n"
98 | " -v Verbose (debug output)\n"
99 | , self, self);
100 | }
101 |
102 | int main(int argc, const char **argv)
103 | {
104 | const char *target = NULL;
105 |
106 | int aoff;
107 | for(aoff = 1; aoff < argc; ++aoff)
108 | {
109 | if(argv[aoff][0] != '-')
110 | {
111 | break;
112 | }
113 | if(strcmp(argv[aoff], "-h") == 0)
114 | {
115 | print_usage(argv[0]);
116 | return 0;
117 | }
118 | if(strcmp(argv[aoff], "-d") == 0)
119 | {
120 | slow = true;
121 | }
122 | else if(strcmp(argv[aoff], "-v") == 0)
123 | {
124 | verbose = true;
125 | }
126 | else
127 | {
128 | fprintf(stderr, "[!] Unrecognized option: %s\n\n", argv[aoff]);
129 | print_usage(argv[0]);
130 | return -1;
131 | }
132 | }
133 | if(argc - aoff > 1)
134 | {
135 | fprintf(stderr, "[!] Too many arguments\n\n");
136 | print_usage(argv[0]);
137 | return -1;
138 | }
139 | else if(argc - aoff == 1)
140 | {
141 | target = argv[aoff];
142 | }
143 |
144 | vm_address_t kbase;
145 | KERNEL_BASE_OR_GTFO(kbase);
146 |
147 | mach_hdr_t *hdr = malloc(MAX_HEADER_SIZE);
148 | if(hdr == NULL)
149 | {
150 | fprintf(stderr, "[!] Failed to allocate header buffer (%s)\n", strerror(errno));
151 | return -1;
152 | }
153 | memset(hdr, 0, MAX_HEADER_SIZE);
154 |
155 | DEBUG("Reading kernel header... ");
156 | if(kernel_read(kbase, MAX_HEADER_SIZE, hdr) != MAX_HEADER_SIZE)
157 | {
158 | fprintf(stderr, "[!] Kernel I/O error\n");
159 | return -1;
160 | }
161 |
162 | segment_t
163 | cstring =
164 | {
165 | .addr = 0,
166 | .len = 0,
167 | .buf = NULL,
168 | },
169 | data =
170 | {
171 | .addr = 0,
172 | .len = 0,
173 | .buf = NULL,
174 | };
175 | CMD_ITERATE(hdr, cmd)
176 | {
177 | switch(cmd->cmd)
178 | {
179 | case MACH_LC_SEGMENT:
180 | {
181 | mach_seg_t *seg = (mach_seg_t*)cmd;
182 | mach_sec_t *sec = (mach_sec_t*)(seg + 1);
183 | for(size_t i = 0; i < seg->nsects; ++i)
184 | {
185 | if(strcmp(sec[i].segname, STRING_SEG) == 0 && strcmp(sec[i].sectname, STRING_SEC) == 0)
186 | {
187 | DEBUG("Found " STRING_SEG "." STRING_SEC " section at " ADDR, (vm_address_t)sec[i].addr);
188 | cstring.addr = sec[i].addr;
189 | cstring.len = sec[i].size;
190 | cstring.buf = malloc(cstring.len);
191 | if(cstring.buf == NULL)
192 | {
193 | fprintf(stderr, "[!] Failed to allocate section buffer (%s)\n", strerror(errno));
194 | return -1;
195 | }
196 | if(kernel_read(cstring.addr, cstring.len, cstring.buf) != cstring.len)
197 | {
198 | fprintf(stderr, "[!] Kernel I/O error\n");
199 | return -1;
200 | }
201 | }
202 | else if(strcmp(sec[i].segname, OFVAR_SEG) == 0 && strcmp(sec[i].sectname, OFVAR_SEC) == 0)
203 | {
204 | DEBUG("Found " OFVAR_SEG "." OFVAR_SEC " section at " ADDR, (vm_address_t)sec[i].addr);
205 | data.addr = sec[i].addr;
206 | data.len = sec[i].size;
207 | data.buf = malloc(data.len);
208 | if(data.buf == NULL)
209 | {
210 | fprintf(stderr, "[!] Failed to allocate section buffer (%s)\n", strerror(errno));
211 | return -1;
212 | }
213 | if(kernel_read(data.addr, data.len, data.buf) != data.len)
214 | {
215 | fprintf(stderr, "[!] Kernel I/O error\n");
216 | return -1;
217 | }
218 | }
219 | }
220 | }
221 | break;
222 | }
223 | }
224 | if(cstring.buf == NULL)
225 | {
226 | fprintf(stderr, "[!] Failed to find " STRING_SEG "." STRING_SEC " section\n");
227 | return -1;
228 | }
229 | if(data.buf == NULL)
230 | {
231 | fprintf(stderr, "[!] Failed to find " OFVAR_SEG "." OFVAR_SEC " section\n");
232 | return -1;
233 | }
234 |
235 | // This is the name of the first NVRAM variable
236 | char first[] = "little-endian?";
237 | char *str = memmem(cstring.buf, cstring.len, first, sizeof(first));
238 | if(str == NULL)
239 | {
240 | fprintf(stderr, "[!] Failed to find string \"%s\"\n", first);
241 | return -1;
242 | }
243 | vm_address_t str_addr = (str - cstring.buf) + cstring.addr;
244 | DEBUG("Found string \"%s\" at " ADDR, first, str_addr);
245 |
246 | // Now let's find a reference to it
247 | OFVar *gOFVars = NULL;
248 | for(vm_address_t *ptr = (vm_address_t*)data.buf, *end = (vm_address_t*)&data.buf[data.len]; ptr < end; ++ptr)
249 | {
250 | if(*ptr == str_addr)
251 | {
252 | gOFVars = (OFVar*)ptr;
253 | break;
254 | }
255 | }
256 | if(gOFVars == NULL)
257 | {
258 | fprintf(stderr, "[!] Failed to find gOFVariables\n");
259 | return -1;
260 | }
261 | vm_address_t gOFAddr = ((char*)gOFVars - data.buf) + data.addr;
262 | DEBUG("Found gOFVariables at " ADDR, gOFAddr);
263 |
264 | // Sanity checks
265 | size_t numvars = 0,
266 | longest_name = 0;
267 | for(OFVar *var = gOFVars; (char*)var < &data.buf[data.len]; ++var)
268 | {
269 | if(var->name == 0) // End marker
270 | {
271 | break;
272 | }
273 | if(var->name < cstring.addr || var->name >= cstring.addr + cstring.len)
274 | {
275 | fprintf(stderr, "[!] gOFVariables[%lu].name is out of bounds\n", numvars);
276 | return -1;
277 | }
278 | char *name = &cstring.buf[var->name - cstring.addr];
279 | size_t maxlen = cstring.len - (name - cstring.buf),
280 | namelen = strnlen(name, maxlen);
281 | if(namelen == maxlen)
282 | {
283 | fprintf(stderr, "[!] gOFVariables[%lu].name exceeds __cstring size\n", numvars);
284 | return -1;
285 | }
286 | for(size_t i = 0; i < namelen; ++i)
287 | {
288 | if(name[i] < 0x20 || name[i] >= 0x7f)
289 | {
290 | fprintf(stderr, "[!] gOFVariables[%lu].name contains non-printable character: 0x%02x\n", numvars, name[i]);
291 | return -1;
292 | }
293 | }
294 | longest_name = namelen > longest_name ? namelen : longest_name;
295 | switch(var->type)
296 | {
297 | case kOFVarTypeBoolean:
298 | case kOFVarTypeNumber:
299 | case kOFVarTypeString:
300 | case kOFVarTypeData:
301 | break;
302 | default:
303 | fprintf(stderr, "[!] gOFVariables[%lu] has unknown type: 0x%x\n", numvars, var->type);
304 | return -1;
305 | }
306 | switch(var->perm)
307 | {
308 | case kOFVarPermRootOnly:
309 | case kOFVarPermUserRead:
310 | case kOFVarPermUserWrite:
311 | case kOFVarPermKernelOnly:
312 | break;
313 | default:
314 | fprintf(stderr, "[!] gOFVariables[%lu] has unknown permissions: 0x%x\n", numvars, var->perm);
315 | return -1;
316 | }
317 | ++numvars;
318 | }
319 | if(numvars < 1)
320 | {
321 | fprintf(stderr, "[!] gOFVariables contains zero entries\n");
322 | return -1;
323 | }
324 |
325 | if(target == NULL) // Print list
326 | {
327 | DEBUG("gOFVariables:");
328 |
329 | for(size_t i = 0; i < numvars; ++i)
330 | {
331 | char *name = &cstring.buf[gOFVars[i].name - cstring.addr];
332 | printf("%-*s %-*s %-*s\n", (int)longest_name, name, MAX_TYPELEN, type_name(gOFVars[i].type), MAX_PERMLEN, perm_name(gOFVars[i].perm));
333 | }
334 | }
335 | else // Patch target
336 | {
337 | for(size_t i = 0; i < numvars; ++i)
338 | {
339 | char *name = &cstring.buf[gOFVars[i].name - cstring.addr];
340 | if(strcmp(name, target) == 0)
341 | {
342 | if(gOFVars[i].perm != kOFVarPermKernelOnly)
343 | {
344 | fprintf(stderr, "[*] Variable \"%s\" is already writable for %s\n", target, gOFVars[i].perm == kOFVarPermUserWrite ? "everyone" : "root");
345 | goto done;
346 | }
347 | vm_size_t off = ((char*)&gOFVars[i].perm) - data.buf;
348 | uint32_t newperm = kOFVarPermRootOnly;
349 | if(kernel_write(data.addr + off, sizeof(newperm), &newperm) != sizeof(newperm))
350 | {
351 | fprintf(stderr, "[!] Kernel I/O error\n");
352 | return -1;
353 | }
354 | fprintf(stderr, "[*] Successfully patched permissions for variable \"%s\"\n", target);
355 | goto done;
356 | }
357 | }
358 | fprintf(stderr, "[!] Failed to find variable \"%s\"\n", target);
359 |
360 | const char *assign = strchr(target, '=');
361 | if(assign != NULL)
362 | {
363 | size_t len = assign - target;
364 | fprintf(stderr, "[!] WARNING!\n"
365 | "[!] Your variable name contains a '=' character, which is almost certainly wrong.\n"
366 | "[!] If you meant to patch a variable and assign it a value, run the following:\n"
367 | "\n"
368 | "%s %.*s\n"
369 | "nvram %s\n"
370 | "\n"
371 | , argv[0], (int)len, target, target);
372 | }
373 | return -1;
374 |
375 | done:;
376 | }
377 |
378 | free(cstring.buf);
379 | free(data.buf);
380 | free(hdr);
381 |
382 | return 0;
383 | }
384 |
--------------------------------------------------------------------------------