├── .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 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 |
NameFunctionDefault value
OS XiOSLinux
IGCCiOS compilerxcrun -sdk iphoneos gccclangios-clang
IGCC_ARCHTarget architecture(s)-arch armv7 -arch arm64
IGCC_FLAGSCustom compiler flagsnone
LIBTOOLArchive manipulation utilityxcrun -sdk iphoneos libtoollibtoolios-libtool
STRIPSymbol remover utilityxcrun -sdk iphoneos stripstripios-strip
SIGNCode signing utilitycodesignldid
SIGN_FLAGSCode signing flags-s - --entitlements misc/ent.xml-Smisc/ent.xml
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 5ustar00moltwheel000000 000000 ./usr/000755 000765 000000 00000000000 13001240260 012121 5ustar00moltwheel000000 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 | --------------------------------------------------------------------------------