├── Makefile ├── README.md ├── kloader ├── kloader.c └── tfp0.plist /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | xcrun -sdk iphoneos clang kloader.c -arch armv7 -arch arm64 -framework IOKit -framework CoreFoundation -Wall -miphoneos-version-min=4.0 -o kloader 3 | ldid -Stfp0.plist kloader 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kloader (ios-kexec-utils) 2 | 3 | ## Features 4 | 5 | * made by winocm and improved by axi0mX 6 | 7 | * armv7 support for iOS 4.0-9.3.5 (requires a jailbreak with tfp0 kernel patch) 8 | 9 | * arm64 support for iOS 7.0-8.4.1 (requires a jailbreak with tfp0 kernel patch and patched LLB/iBoot/iBSS/iBEC) 10 | 11 | * will automatically wake up device after 2 seconds (no button press required, should work 99.9% of the time) 12 | 13 | Use the provided binary if you do not need to change kloader source code. 14 | 15 | Please report issues on GitHub and I will do my best to fix them. 16 | 17 | 18 | ## Dependencies 19 | 20 | These dependencies are only needed if you wish to compile kloader. 21 | 22 | * Xcode 23 | 24 | Latest version will work, but the following 2 files must be copied from a DMG of an older version of Xcode. I used Xcode_6.4.dmg. 25 | 26 | ``` 27 | Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/libgcc_s.1.dylib 28 | Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/crt1.3.1.o 29 | ``` 30 | 31 | * ldid 32 | 33 | Homebrew can be used to install ldid on macOS. 34 | 35 | ``` 36 | brew install ldid 37 | ``` 38 | -------------------------------------------------------------------------------- /kloader: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/axi0mX/ios-kexec-utils/14e7edbe282eab095b3c4556311f36195ec19b14/kloader -------------------------------------------------------------------------------- /kloader.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014, winocm. 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License 6 | * as published by the Free Software Foundation; either version 2 7 | * of the License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | * 18 | * $Id$ 19 | */ 20 | /* 21 | * kloader 22 | * Requires: kernel patch for tfp0 23 | * Supports: armv7 iOS 4.0 - 9.3.5 24 | * arm64 iOS 7.0 - 8.4.1 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | extern mach_port_t IOPMFindPowerManagement(mach_port_t); 36 | extern kern_return_t IOPMSchedulePowerEvent(CFDateRef time_to_wake, CFStringRef my_id, CFStringRef type); 37 | extern kern_return_t IOPMSleepSystem(mach_port_t); 38 | 39 | #define KERNEL_DUMP_SIZE 0xd00000 40 | #define CHUNK_SIZE 2048 41 | #define SLEEP_DELAY 2.0 42 | #define kIOPMSystemPowerStateNotify "com.apple.powermanagement.systempowerstate" 43 | #define kIOPMSettingDebugWakeRelativeKey "WakeRelativeToSleep" 44 | 45 | #ifdef __arm64__ 46 | #define IMAGE_OFFSET 0x2000 47 | #define MACHO_HEADER_MAGIC MH_MAGIC_64 48 | #define KERNEL_SEARCH_ADDRESS 0xffffff8000000000 49 | 50 | static uint32_t arm64_branch_instruction(uintptr_t from, uintptr_t to) { 51 | return from > to ? 0x18000000 - (from - to) / 4 : 0x14000000 + (to - from) / 4; 52 | } 53 | #else 54 | #define IMAGE_OFFSET 0x1000 55 | #define MACHO_HEADER_MAGIC MH_MAGIC 56 | #define KERNEL_SEARCH_ADDRESS 0x81200000 57 | #endif 58 | 59 | /* Buggy, but re-implemented because some old versions of iOS don't have memmem */ 60 | static void * buggy_memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { 61 | if (haystack == NULL || haystacklen == 0 || needle == NULL || needlelen == 0) { 62 | printf("ERROR: Invalid arguments for buggy_memmem.\n"); 63 | exit(1); 64 | } 65 | 66 | for (size_t i = 0; i < haystacklen; i++) 67 | if (*(uint8_t *)(haystack + i) == *(uint8_t *)needle && i + needlelen <= haystacklen && 0 == memcmp(((uint8_t *)haystack) + i, needle, needlelen)) 68 | return (void *)(((uint8_t *)haystack) + i); 69 | 70 | return NULL; 71 | } 72 | 73 | static uintptr_t find_larm_init_tramp(uint8_t *kdata, size_t ksize) { 74 | #ifdef __arm64__ 75 | // B +0x4; MSR DAIFSET, #0xF 76 | const uint8_t search[] = { /* ??, ??, ??, ??, ??, ??, ??, ?? */ 0x01, 0x00, 0x00, 0x14, 0xDF, 0x4F, 0x03, 0xD5 }; 77 | void *ptr = buggy_memmem(kdata, ksize, search, sizeof(search)); 78 | if (ptr) 79 | return ((uintptr_t)ptr) - 8 - ((uintptr_t)kdata); 80 | #else 81 | // LDR LR, [PC, LR]; B +0x0; CPSID IF 82 | const uint8_t search[] = { 0x0E, 0xE0, 0x9F, 0xE7, 0xFF, 0xFF, 0xFF, 0xEA, 0xC0, 0x00, 0x0C, 0xF1 }; 83 | void *ptr = buggy_memmem(kdata, ksize, search, sizeof(search)); 84 | if (ptr) 85 | return ((uintptr_t)ptr) - ((uintptr_t)kdata); 86 | 87 | // LDR LR, [PC, #imm]; B +0x0; CPSID IF 88 | const uint8_t search2[] = { /* ??, ?? */ 0x9F, 0xE5, 0xFF, 0xFF, 0xFF, 0xEA, 0xC0, 0x00, 0x0C, 0xF1 }; 89 | ptr = buggy_memmem(kdata, ksize, search2, sizeof(search2)); 90 | if (ptr) 91 | return ((uintptr_t)ptr) - 2 - ((uintptr_t)kdata); 92 | #endif 93 | 94 | printf("ERROR: Failed to locate larm_init_tramp.\n"); 95 | exit(1); 96 | } 97 | 98 | static void hook_kernel_wakeup(task_t kernel_task, uintptr_t kernel_base, uint8_t *kernel_dump, uintptr_t gPhysBase, uintptr_t load_address) { 99 | uintptr_t larm_init_tramp = kernel_base + IMAGE_OFFSET + find_larm_init_tramp(kernel_dump, KERNEL_DUMP_SIZE); 100 | printf("larm_init_tramp: 0x%lx\n", larm_init_tramp); 101 | 102 | #ifdef __arm64__ 103 | uint32_t expected_page_offset = 0xC; 104 | 105 | if (larm_init_tramp % 0x1000 != expected_page_offset) { 106 | printf("ERROR: larm_init_tramp is not at +0x%x offset on a 0x1000 boundary.\n", expected_page_offset); 107 | exit(1); 108 | } 109 | 110 | // TODO: Make sure only NOP instructions are overwritten by shellcode. 111 | uint64_t shellcode[3] = { 0x5800007ed50041bf, 0xd503201fd65f03c0, load_address }; 112 | uint32_t branch_back = arm64_branch_instruction(larm_init_tramp, larm_init_tramp - expected_page_offset - sizeof(shellcode)); 113 | vm_write(kernel_task, larm_init_tramp, (vm_offset_t)&branch_back, sizeof(branch_back)); 114 | vm_write(kernel_task, larm_init_tramp - expected_page_offset - sizeof(shellcode), (vm_offset_t)&shellcode, sizeof(shellcode)); 115 | #else 116 | uint32_t shellcode[2] = { 0xe51ff004, load_address }; 117 | vm_write(kernel_task, larm_init_tramp, (vm_offset_t)&shellcode, sizeof(shellcode)); 118 | #endif 119 | } 120 | 121 | static int get_cpid() { 122 | size_t size; 123 | sysctlbyname("kern.version", NULL, &size, NULL, 0); 124 | char *kern_version = malloc(size); 125 | if (sysctlbyname("kern.version", kern_version, &size, NULL, 0) == -1) { 126 | printf("ERROR: Failed to get kern.version sysctl.\n"); 127 | exit(1); 128 | } 129 | printf("kern.version: %s\n", kern_version); 130 | 131 | uint32_t cpid = -1; 132 | if (strcasestr(kern_version, "S5L8920X")) { 133 | cpid = 0x8920; 134 | } else if (strcasestr(kern_version, "S5L8922X")) { 135 | cpid = 0x8922; 136 | } else if (strcasestr(kern_version, "S5L8930X")) { 137 | cpid = 0x8930; 138 | } else if (strcasestr(kern_version, "S5L8940X")) { 139 | cpid = 0x8940; 140 | } else if (strcasestr(kern_version, "S5L8942X")) { 141 | cpid = 0x8942; 142 | } else if (strcasestr(kern_version, "S5L8945X")) { 143 | cpid = 0x8945; 144 | } else if (strcasestr(kern_version, "S5L8947X")) { 145 | cpid = 0x8947; 146 | } else if (strcasestr(kern_version, "S5L8950X")) { 147 | cpid = 0x8950; 148 | } else if (strcasestr(kern_version, "S5L8955X")) { 149 | cpid = 0x8955; 150 | } else if (strcasestr(kern_version, "S5L8960X")) { 151 | cpid = 0x8960; 152 | } else if (strcasestr(kern_version, "T7000")) { 153 | cpid = 0x7000; 154 | } else if (strcasestr(kern_version, "T7001")) { 155 | cpid = 0x7001; 156 | } else if (strcasestr(kern_version, "S7002")) { 157 | cpid = 0x7002; 158 | } else if (strcasestr(kern_version, "S8000")) { 159 | cpid = 0x8000; 160 | } else if (strcasestr(kern_version, "S8001")) { 161 | cpid = 0x8001; 162 | } else if (strcasestr(kern_version, "T8002")) { 163 | cpid = 0x8002; 164 | } else if (strcasestr(kern_version, "S8003")) { 165 | cpid = 0x8003; 166 | } else if (strcasestr(kern_version, "T8010")) { 167 | cpid = 0x8010; 168 | } else if (strcasestr(kern_version, "T8011")) { 169 | cpid = 0x8011; 170 | } else { 171 | printf("ERROR: Failed to recognize chip from kern.version.\n"); 172 | exit(1); 173 | } 174 | 175 | free(kern_version); 176 | return cpid; 177 | } 178 | 179 | static task_t get_kernel_task() { 180 | task_t kernel_task; 181 | if (KERN_SUCCESS == task_for_pid(mach_task_self(), 0, &kernel_task)) 182 | return kernel_task; 183 | 184 | printf("ERROR: task_for_pid 0 failed.\n"); 185 | exit(1); 186 | } 187 | 188 | static vm_address_t get_kernel_base(task_t kernel_task) { 189 | vm_region_submap_info_data_64_t info; 190 | vm_size_t size; 191 | mach_msg_type_number_t info_count = VM_REGION_SUBMAP_INFO_COUNT_64; 192 | unsigned int depth = 0; 193 | uintptr_t addr = KERNEL_SEARCH_ADDRESS; 194 | 195 | while (1) { 196 | if (KERN_SUCCESS != vm_region_recurse_64(kernel_task, (vm_address_t *)&addr, &size, &depth, (vm_region_info_t) &info, &info_count)) 197 | break; 198 | if (size > 1024 * 1024 * 1024) { 199 | /* 200 | * https://code.google.com/p/iphone-dataprotection/ 201 | * hax, sometimes on iOS7 kernel starts at +0x200000 in the 1Gb region 202 | */ 203 | pointer_t buf; 204 | mach_msg_type_number_t sz = 0; 205 | addr += 0x200000; 206 | vm_read(kernel_task, addr + IMAGE_OFFSET, 512, &buf, &sz); 207 | if (*((uint32_t *)buf) != MACHO_HEADER_MAGIC) { 208 | addr -= 0x200000; 209 | vm_read(kernel_task, addr + IMAGE_OFFSET, 512, &buf, &sz); 210 | if (*((uint32_t*)buf) != MACHO_HEADER_MAGIC) { 211 | break; 212 | } 213 | } 214 | printf("kernel_base: 0x%08lx\n", (uintptr_t)addr); 215 | return addr; 216 | } 217 | addr += size; 218 | } 219 | 220 | printf("ERROR: Failed to find kernel base.\n"); 221 | exit(1); 222 | } 223 | 224 | static void dump_kernel(task_t kernel_task, vm_address_t kernel_base, uint8_t *dest) { 225 | for (vm_address_t addr = kernel_base + IMAGE_OFFSET, e = 0; addr < kernel_base + KERNEL_DUMP_SIZE; addr += CHUNK_SIZE, e += CHUNK_SIZE) { 226 | pointer_t buf = 0; 227 | mach_msg_type_number_t sz = 0; 228 | vm_read(kernel_task, addr, CHUNK_SIZE, &buf, &sz); 229 | if (buf == 0 || sz == 0) 230 | continue; 231 | bcopy((uint8_t *)buf, dest + e, CHUNK_SIZE); 232 | } 233 | } 234 | 235 | static void load_image_to_memory(task_t kernel_task, vm_address_t load_address, const char *filename) { 236 | FILE *f = fopen(filename, "rb"); 237 | if (!f) { 238 | printf("ERROR: Failed to open the image file.\n"); 239 | exit(1); 240 | } 241 | 242 | fseek(f, 0, SEEK_END); 243 | int length = ftell(f); 244 | fseek(f, 0, SEEK_SET); 245 | 246 | uint8_t *image = malloc(length); 247 | if (!image) { 248 | printf("ERROR: Failed to allocate memory for image.\n"); 249 | exit(1); 250 | } 251 | 252 | fread(image, length, 1, f); 253 | fclose(f); 254 | printf("Read image into buffer: %p length: %d\n", image, length); 255 | 256 | if (*(uint32_t *)image == 'Img3') { 257 | printf("ERROR: This is an IMG3 file. For now, only unpacked raw images are supported. IMG3 support is coming soon.\n"); 258 | exit(1); 259 | } 260 | 261 | if (!strcmp((const char *)image + 7, "IM4P")) { 262 | printf("ERROR: This is an IM4P file. For now, only unpacked raw images are supported. IM4P support is coming soon.\n"); 263 | exit(1); 264 | } 265 | 266 | for (vm_size_t i = 0; i < length; i += CHUNK_SIZE) { 267 | vm_size_t part_size = (length - i < CHUNK_SIZE) ? length - i : CHUNK_SIZE; 268 | if (KERN_SUCCESS != vm_write(kernel_task, load_address + i, (vm_offset_t)image + i, part_size)) { 269 | printf("ERROR: vm_write in load_image_to_memory failed.\n"); 270 | exit(1); 271 | } 272 | } 273 | 274 | if (*(uint64_t *)image != 0x9100000090000000 && *(uint32_t *)image != 0xea00000e) { 275 | printf("This doesn't appear to be an ARM bootloader image. Continuing though.\n"); 276 | } 277 | 278 | printf("Image information: %s\n", image + 0x200); 279 | printf("Image information: %s\n", image + 0x240); 280 | printf("Image information: %s\n", image + 0x280); 281 | 282 | free(image); 283 | } 284 | 285 | static void schedule_autowake_during_sleep_notification(CFTimeInterval autowake_delay) { 286 | int out_token; 287 | uint32_t status = notify_register_dispatch(kIOPMSystemPowerStateNotify, &out_token, dispatch_get_main_queue(), ^(int token) { 288 | CFDateRef date = CFDateCreate(0, CFAbsoluteTimeGetCurrent() + autowake_delay); 289 | kern_return_t kr = IOPMSchedulePowerEvent(date, NULL, CFSTR(kIOPMSettingDebugWakeRelativeKey)); 290 | if (kr) { 291 | printf("ERROR: IOPMSchedulePowerEvent returned %x.\n", kr); 292 | exit(1); 293 | } 294 | /* Stop the runloop */ 295 | CFRunLoopStop(CFRunLoopGetCurrent()); 296 | }); 297 | 298 | if (status != 0) { 299 | printf("ERROR: Failed to register for kIOPMSystemPowerStateNotify: %x\n", status); 300 | exit(1); 301 | } 302 | } 303 | 304 | static void request_sleep() { 305 | mach_port_t fb = IOPMFindPowerManagement(MACH_PORT_NULL); 306 | if (fb == MACH_PORT_NULL) { 307 | printf("ERROR: Failed to get PowerManagement root port.\n"); 308 | exit(1); 309 | } 310 | 311 | for (int i = 0; i < 10; i++) { 312 | kern_return_t kr = IOPMSleepSystem(fb); 313 | if (!kr) 314 | return; 315 | 316 | printf("WARNING: IOPMSleepSystem returned %x. Trying again.\n", kr); 317 | sleep(1); 318 | } 319 | 320 | printf("ERROR: IOPMSleepSystem failed.\n"); 321 | exit(1); 322 | } 323 | 324 | int main(int argc, char *argv[]) { 325 | if (argc != 2) { 326 | printf("usage: %s [loadfile]\n", argv[0]); 327 | printf("This will destroy the current running OS instance and fire up the loaded image.\n"); 328 | printf("You have been warned.\n"); 329 | exit(1); 330 | } 331 | 332 | uintptr_t gPhysBase = 0xdeadbeef; 333 | uintptr_t load_address = 0xdeadbeef; 334 | switch (get_cpid()) { 335 | 336 | #ifndef __arm64__ 337 | case 0x8920: 338 | case 0x8922: 339 | gPhysBase = 0x40000000; 340 | load_address = 0x4f000000; 341 | break; 342 | 343 | case 0x8930: 344 | gPhysBase = 0x40000000; 345 | load_address = 0x5f000000; 346 | break; 347 | 348 | case 0x8940: 349 | case 0x8942: 350 | case 0x8947: 351 | gPhysBase = 0x80000000; 352 | load_address = 0x9f000000; 353 | break; 354 | 355 | case 0x8945: 356 | case 0x8950: 357 | case 0x8955: 358 | gPhysBase = 0x80000000; 359 | load_address = 0xbf000000; 360 | break; 361 | #endif 362 | 363 | #ifdef __arm64__ 364 | case 0x8960: 365 | gPhysBase = 0x800800000; 366 | load_address = 0x83d100000; 367 | break; 368 | 369 | case 0x7000: 370 | gPhysBase = 0x800e00000; 371 | load_address = 0x83eb00000; 372 | break; 373 | 374 | case 0x7001: 375 | gPhysBase = 0x800e00000; 376 | load_address = 0x85eb00000; 377 | break; 378 | #endif 379 | 380 | default: 381 | printf("ERROR: Failed to recognize the chip.\n"); 382 | exit(1); 383 | } 384 | 385 | /* Sync disks */ 386 | for (int i = 0; i < 10; i++) 387 | sync(); 388 | 389 | static uint8_t kernel_dump[KERNEL_DUMP_SIZE + IMAGE_OFFSET] = {0}; 390 | task_t kernel_task = get_kernel_task(); 391 | vm_address_t kernel_base = get_kernel_base(kernel_task); 392 | dump_kernel(kernel_task, kernel_base, kernel_dump); 393 | schedule_autowake_during_sleep_notification(SLEEP_DELAY); 394 | request_sleep(); 395 | 396 | CFRunLoopRun(); 397 | 398 | if (signal(SIGINT, SIG_IGN) != SIG_IGN) 399 | signal(SIGINT, SIG_IGN); 400 | 401 | load_image_to_memory(kernel_task, kernel_base - gPhysBase + load_address, argv[1]); 402 | hook_kernel_wakeup(kernel_task, kernel_base, kernel_dump, gPhysBase, load_address); 403 | 404 | /* Sync disks */ 405 | for (int i = 0; i < 10; i++) 406 | sync(); 407 | 408 | printf("Magic happening now. Device should wake up in %.1f seconds.\n", SLEEP_DELAY); 409 | 410 | return 0; 411 | } 412 | -------------------------------------------------------------------------------- /tfp0.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | get-task-allow 6 | 7 | run-unsigned-code 8 | 9 | task_for_pid-allow 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------