├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── KernelPatchfinder │ └── KernelPatchfinder.swift └── KernelPatchfinderTester │ └── main.swift └── Tests └── KernelPatchfinderTests └── KernelPatchfinderTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/ 8 | /Package.resolved 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pinauten GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "KernelPatchfinder", 8 | platforms: [ 9 | .iOS(.v14), 10 | .macOS(.v11) 11 | ], 12 | products: [ 13 | // Products define the executables and libraries a package produces, and make them visible to other packages. 14 | .library( 15 | name: "KernelPatchfinder", 16 | targets: ["KernelPatchfinder"]), 17 | .executable(name: "KernelPatchfinderTester", targets: ["KernelPatchfinderTester"]) 18 | ], 19 | dependencies: [ 20 | // Dependencies declare other packages that this package depends on. 21 | .package(name: "SwiftUtils", url: "https://github.com/pinauten/SwiftUtils", .branch("master")), 22 | .package(name: "SwiftMachO", url: "https://github.com/pinauten/SwiftMachO", .branch("master")), 23 | .package(name: "PatchfinderUtils", url: "https://github.com/pinauten/PatchfinderUtils", .branch("master")) 24 | ], 25 | targets: [ 26 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 27 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 28 | .target( 29 | name: "KernelPatchfinder", 30 | dependencies: ["SwiftUtils", "SwiftMachO", "PatchfinderUtils"]), 31 | .testTarget( 32 | name: "KernelPatchfinderTests", 33 | dependencies: ["KernelPatchfinder"]), 34 | .target( 35 | name: "KernelPatchfinderTester", 36 | dependencies: ["SwiftUtils", "SwiftMachO", "KernelPatchfinder"]), 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KernelPatchfinder 2 | 3 | An iOS Kernel Patchfinder, supporting iOS 15. Used by [Fugu15](https://github.com/pinauten/Fugu15). 4 | -------------------------------------------------------------------------------- /Sources/KernelPatchfinder/KernelPatchfinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KernelPatchfinder.swift 3 | // KernelPatchfinder 4 | // 5 | // Created by Linus Henze. 6 | // Copyright © 2022 Pinauten GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftMachO 11 | import PatchfinderUtils 12 | import Darwin 13 | 14 | open class KernelPatchfinder { 15 | public let kernel: MachO 16 | 17 | /// Virtual base address of the kernel image 18 | public let baseAddress: UInt64 19 | 20 | /// Kernel entry point 21 | public let entryPoint: UInt64 22 | 23 | /** 24 | * Whether or not the kernel is running under Piranha 25 | * 26 | * - Warning: Patchfinder results might be wrong when running under Piranha - You should dump the kernel from RAM in this case 27 | */ 28 | public let runningUnderPiranha: Bool 29 | 30 | /// `__TEXT_EXEC,__text` section 31 | public let textExec: PatchfinderSegment 32 | 33 | /// `__TEXT,__cstring` section 34 | public let cStrSect: PatchfinderSegment 35 | 36 | /// `__DATA,__data` section 37 | public let dataSect: PatchfinderSegment 38 | 39 | /// `__DATA_CONST,__const` section 40 | public let constSect: PatchfinderSegment 41 | 42 | /// `__PPLTEXT,__text` section 43 | public let pplText: PatchfinderSegment 44 | 45 | /// Address of allproc 46 | public lazy var allproc: UInt64? = { 47 | // First find ref to string "shutdownwait" 48 | guard let shutdownwait = cStrSect.addrOf("shutdownwait") else { 49 | return nil 50 | } 51 | 52 | // Get an xref to shutdownwait 53 | guard let reboot_kernel = textExec.findNextXref(to: shutdownwait, optimization: .noBranches) else { 54 | return nil 55 | } 56 | 57 | // allproc should be first adrp ldr 58 | for i in 1..<20 { 59 | let pc = reboot_kernel + UInt64(i * 4) 60 | let adrp = textExec.instruction(at: pc) ?? 0 61 | let ldr = textExec.instruction(at: pc + 4) ?? 0 62 | if let target = AArch64Instr.Emulate.adrpLdr(adrp: adrp, ldr: ldr, pc: pc) { 63 | return target 64 | } 65 | } 66 | 67 | return nil 68 | }() 69 | 70 | /// Address of the kernel's root translation table 71 | public lazy var cpu_ttep: UInt64? = { 72 | // First follow the jump in start 73 | guard let start_first_cpu = AArch64Instr.Emulate.b(textExec.instruction(at: entryPoint) ?? 0, pc: entryPoint) else { 74 | return nil 75 | } 76 | 77 | // Find cbz x21, something 78 | guard let cpu_ttep_pre = textExec.addrOf([0xB40000B5], startAt: start_first_cpu) else { 79 | return nil 80 | } 81 | 82 | let adrp = textExec.instruction(at: cpu_ttep_pre + 4) 83 | let add = textExec.instruction(at: cpu_ttep_pre + 8) 84 | 85 | return AArch64Instr.Emulate.adrpAdd(adrp: adrp ?? 0, add: add ?? 0, pc: cpu_ttep_pre + 4) 86 | }() 87 | 88 | /// Address of the `ppl_bootstrap_dispatch` function 89 | public lazy var ppl_bootstrap_dispatch: UInt64? = { 90 | guard let ppl_dispatch_failed = dataSect.addrOf("ppl_dispatch: failed") else { 91 | return nil 92 | } 93 | 94 | var ppl_bootstrap_dispatch: UInt64! 95 | var pc: UInt64! = nil 96 | while true { 97 | guard let found = textExec.findNextXref(to: ppl_dispatch_failed, startAt: pc, optimization: .noBranches) else { 98 | return nil 99 | } 100 | 101 | if AArch64Instr.isAutibsp(textExec.instruction(at: found - 4) ?? 0) { 102 | ppl_bootstrap_dispatch = found 103 | break 104 | } 105 | 106 | pc = found + 4 107 | } 108 | 109 | // Find the start of ppl_bootstrap_dispatch 110 | // Search up to 20 instructions 111 | var ppl_bootstrap_dispatch_start: UInt64? 112 | for i in 1..<50 { 113 | let pc = ppl_bootstrap_dispatch - UInt64(i * 4) 114 | let instr = textExec.instruction(at: pc) ?? 0 115 | if let args = AArch64Instr.Args.cmp(instr) { 116 | if args.regA == 15 { 117 | ppl_bootstrap_dispatch_start = pc 118 | break 119 | } 120 | } 121 | } 122 | 123 | return ppl_bootstrap_dispatch_start 124 | }() 125 | 126 | /// Address of the `gxf_ppl_enter` function 127 | public lazy var gxf_ppl_enter: UInt64? = { 128 | guard let ppl_bootstrap_dispatch = ppl_bootstrap_dispatch else { 129 | return nil 130 | } 131 | 132 | // Find gxf_ppl_enter 133 | guard let gxf_ppl_enter = textExec.findNextXref(to: ppl_bootstrap_dispatch, optimization: .onlyBranches) else { 134 | return nil 135 | } 136 | 137 | // Find start of gxf_ppl_enter 138 | // Search up to 20 instructions 139 | var gxf_ppl_enter_start: UInt64? 140 | for i in 1..<20 { 141 | let pc = gxf_ppl_enter - UInt64(i * 4) 142 | if AArch64Instr.isPacibsp(textExec.instruction(at: pc) ?? 0) { 143 | gxf_ppl_enter_start = pc 144 | break 145 | } 146 | } 147 | 148 | return gxf_ppl_enter_start 149 | }() 150 | 151 | /// Address of the `pmap_enter_options_addr` function 152 | public lazy var pmap_enter_options_addr: UInt64? = { 153 | guard let pmap_enter_options_ppl = pplDispatchFunc(forOperation: 0xA) else { 154 | return nil 155 | } 156 | 157 | // Now the hard part: xref pmap_enter_options_ppl and find out which one is pmap_enter_options_addr 158 | // pmap_enter_options does an 'or' and an 'and' before the call, but no left shift 159 | var candidate = textExec.findNextXref(to: pmap_enter_options_ppl, optimization: .onlyBranches) 160 | var pmap_enter_options_addr: UInt64! 161 | while candidate != nil { 162 | // Check 20 instructions before 163 | var foundOr = false 164 | var foundAnd = false 165 | for i in 1..<20 { 166 | let inst = textExec.instruction(at: candidate! - UInt64(i * 4)) ?? 0 167 | if inst & 0x7F800000 == 0x12000000 { 168 | foundAnd = true 169 | } else if inst & 0x7F800000 == 0x32000000 { 170 | foundOr = true 171 | } else if inst & 0x7F800000 == 0x53000000 { 172 | // Nope, that's a lsl 173 | foundAnd = false 174 | foundOr = false 175 | break 176 | } 177 | } 178 | 179 | if foundOr && foundAnd { 180 | // Should be it 181 | pmap_enter_options_addr = candidate 182 | break 183 | } 184 | 185 | candidate = textExec.findNextXref(to: pmap_enter_options_ppl, startAt: candidate! + 4, optimization: .onlyBranches) 186 | } 187 | 188 | guard pmap_enter_options_addr != nil else { 189 | return nil 190 | } 191 | 192 | // Find the start of pmap_enter_options_addr 193 | while !AArch64Instr.isPacibsp(textExec.instruction(at: pmap_enter_options_addr.unsafelyUnwrapped) ?? 0) { 194 | pmap_enter_options_addr -= 4 195 | } 196 | 197 | return pmap_enter_options_addr 198 | }() 199 | 200 | /// Address of the signed part of the `hw_lck_ticket_reserve_orig_allow_invalid` function 201 | public lazy var hw_lck_ticket_reserve_orig_allow_invalid_signed: UInt64? = { 202 | var pc: UInt64? 203 | while true { 204 | guard let candidate = textExec.addrOf([0x52800000, 0xD65F03C0], startAt: pc) else { 205 | return nil 206 | } 207 | 208 | if let args = AArch64Instr.Args.str(textExec.instruction(at: candidate - 4) ?? 0) { 209 | if args.regSrc == 10 && args.regDst == 16 { 210 | if textExec.instruction(at: candidate - 8) != 0xD503205F { 211 | return candidate - 4 212 | } 213 | } 214 | } 215 | 216 | pc = candidate + 4 217 | } 218 | }() 219 | 220 | /// Address of the `hw_lck_ticket_reserve_orig_allow_invalid` function 221 | public lazy var hw_lck_ticket_reserve_orig_allow_invalid: UInt64? = { 222 | guard let signed = hw_lck_ticket_reserve_orig_allow_invalid_signed else { 223 | return nil 224 | } 225 | 226 | for i in 0..<50 { 227 | let pc = signed - UInt64(i * 4) 228 | if AArch64Instr.Emulate.adr(textExec.instruction(at: pc) ?? 0, pc: pc) != nil { 229 | return pc 230 | } 231 | } 232 | 233 | return nil 234 | }() 235 | 236 | /// Address of a `br x22` gadget (first signs, then branches) 237 | public lazy var br_x22_gadget: UInt64? = { 238 | var pc: UInt64? 239 | while true { 240 | guard let candidate = textExec.addrOf([0xD71F0ADF], startAt: pc) else { 241 | return nil 242 | } 243 | 244 | for i in 0..<50 { 245 | let pc = candidate - UInt64(i * 4) 246 | if textExec.instruction(at: pc) == 0xDAC103F6 { 247 | return pc 248 | } 249 | } 250 | 251 | pc = candidate + 4 252 | } 253 | }() 254 | 255 | /// Address of `thread_exception_return` 256 | public lazy var exception_return: UInt64? = { 257 | return textExec.addrOf([0xD5034FDF, 0xD538D083, 0x910002BF]) 258 | }() 259 | 260 | /// Address of `thread_exception_return` after checking the signed state 261 | public lazy var exception_return_after_check: UInt64? = { 262 | guard let exception_return = exception_return else { 263 | return nil 264 | } 265 | 266 | return textExec.addrOf([0xAA0303FE, 0xAA1603E3, 0xAA1703E4, 0xAA1803E5], startAt: exception_return) 267 | }() 268 | 269 | /// Address of `thread_exception_return` after checking the signed state, without restoring lr and others 270 | public lazy var exception_return_after_check_no_restore: UInt64? = { 271 | guard let exception_return_after_check = exception_return_after_check else { 272 | return nil 273 | } 274 | 275 | return textExec.addrOf([0xD5184021], startAt: exception_return_after_check) 276 | }() 277 | 278 | /// Address of a `ldp x0, x1, [x8]` gadget 279 | public lazy var ldp_x0_x1_x8_gadget: UInt64? = { 280 | return textExec.addrOf([0xA9400500, 0xD65F03C0]) 281 | }() 282 | 283 | /// Address of a `str x8, [x9]` gadget 284 | public lazy var str_x8_x9_gadget: UInt64? = { 285 | return textExec.addrOf([0xF9000128, 0xD65F03C0]) 286 | }() 287 | 288 | /// Address of a `str x0, [x19]; ldr x?, [x20, #?]` gadget 289 | public lazy var str_x0_x19_ldr_x20: UInt64? = { 290 | var pc: UInt64? 291 | while true { 292 | guard let candidate = textExec.addrOf([0xF9000260], startAt: pc) else { 293 | return nil 294 | } 295 | 296 | if let vals = AArch64Instr.Args.ldr(textExec.instruction(at: candidate + 4) ?? 0) { 297 | if vals.regSrc == 20 { 298 | return candidate 299 | } 300 | } 301 | 302 | pc = candidate + 4 303 | } 304 | }() 305 | 306 | /// Address of the `pmap_set_nested` function 307 | public lazy var pmap_set_nested: UInt64? = { 308 | return pplDispatchFunc(forOperation: 0x1A) 309 | }() 310 | 311 | /// Address of the `pmap_nest` function 312 | public lazy var pmap_nest: UInt64? = { 313 | guard let pmap_nest_ppl = pplDispatchFunc(forOperation: 0x11) else { 314 | return nil 315 | } 316 | 317 | guard var pmap_nest = textExec.findNextXref(to: pmap_nest_ppl, optimization: .onlyBranches) else { 318 | return nil 319 | } 320 | 321 | while !AArch64Instr.isPacibsp(textExec.instruction(at: pmap_nest) ?? 0) { 322 | pmap_nest -= 4 323 | } 324 | 325 | return pmap_nest 326 | }() 327 | 328 | /// Address of the `pmap_remove_options` function 329 | public lazy var pmap_remove_options: UInt64? = { 330 | guard let pmap_remove_ppl = pplDispatchFunc(forOperation: 0x17) else { 331 | return nil 332 | } 333 | 334 | var pc: UInt64? 335 | while true { 336 | guard var candidate = textExec.findNextXref(to: pmap_remove_ppl, startAt: pc, optimization: .onlyBranches) else { 337 | return nil 338 | } 339 | 340 | if textExec.instruction(at: candidate - 4) != 0x52802003 { 341 | while !AArch64Instr.isPacibsp(textExec.instruction(at: candidate) ?? 0) { 342 | candidate -= 4 343 | } 344 | 345 | return candidate 346 | } 347 | 348 | pc = candidate + 4 349 | } 350 | }() 351 | 352 | /// Address of the `pmap_mark_page_as_ppl_page` function 353 | public lazy var pmap_mark_page_as_ppl_page: UInt64? = { 354 | return pplDispatchFunc(forOperation: 0x10) 355 | }() 356 | 357 | /// Address of the `pmap_create_options` function 358 | public lazy var pmap_create_options: UInt64? = { 359 | guard let pmap_create_options_ppl = pplDispatchFunc(forOperation: 0x8) else { 360 | return nil 361 | } 362 | 363 | var pc: UInt64? 364 | while true { 365 | guard var candidate = textExec.findNextXref(to: pmap_create_options_ppl, startAt: pc, optimization: .onlyBranches) else { 366 | return nil 367 | } 368 | 369 | if textExec.instruction(at: candidate - 4) != 0x52800002 { 370 | if textExec.instruction(at: candidate - 4) != 0x52800102 { 371 | while !AArch64Instr.isPacibsp(textExec.instruction(at: candidate) ?? 0) { 372 | candidate -= 4 373 | } 374 | 375 | return candidate 376 | } 377 | } 378 | 379 | pc = candidate + 4 380 | } 381 | }() 382 | 383 | /// Address of the `gIOCatalogue` object 384 | public lazy var gIOCatalogue: UInt64? = { 385 | guard let kConfigTablesStr = cStrSect.addrOf("KernelConfigTables syntax error: %s") else { 386 | return nil 387 | } 388 | 389 | // Xref that to find IOCatalogue::initialize 390 | guard let ioCatalogueInitialize = textExec.findNextXref(to: kConfigTablesStr, optimization: .noBranches) else { 391 | return nil 392 | } 393 | 394 | // Find the end of that function 395 | guard let ioCatalogueInitializeEnd = textExec.addrOf([0xD65F0FFF], startAt: ioCatalogueInitialize) else { 396 | return nil 397 | } 398 | 399 | // Go back to the first adrp ldr 400 | var gIOCatalogue: UInt64! 401 | for i in 1..<100 { 402 | let pos = ioCatalogueInitializeEnd - UInt64(i * 4) 403 | let instr1 = textExec.instruction(at: pos) ?? 0 404 | let instr2 = textExec.instruction(at: pos + 4) ?? 0 405 | let val = AArch64Instr.Emulate.adrpLdr(adrp: instr1, ldr: instr2, pc: pos) 406 | if val != nil { 407 | gIOCatalogue = val 408 | break 409 | } 410 | } 411 | 412 | return gIOCatalogue 413 | }() 414 | 415 | /// Address of the `IOCatalogue::terminateDriversForModule(const char * moduleName, bool unload)` function 416 | public lazy var terminateDriversForModule: UInt64? = { 417 | guard let cantRemoveKextStr = cStrSect.addrOf("Can't remove kext %s - not found.") else { 418 | return nil 419 | } 420 | 421 | // Xref str to find OSKext::removeKextWithIdentifier 422 | guard let removeKextWithIdentifier = textExec.findNextXref(to: cantRemoveKextStr, optimization: .noBranches) else { 423 | return nil 424 | } 425 | 426 | // Find the start of removeKextWithIdentifier 427 | var removeKextWithIdentifierStart: UInt64! 428 | for i in 1..<100 { 429 | let pos = removeKextWithIdentifier - UInt64(i * 4) 430 | if AArch64Instr.isPacibsp(textExec.instruction(at: pos) ?? 0) { 431 | removeKextWithIdentifierStart = pos 432 | break 433 | } 434 | } 435 | 436 | guard removeKextWithIdentifierStart != nil else { 437 | return nil 438 | } 439 | 440 | // Xref to find the function that does a bl 441 | var terminateOSString: UInt64! = textExec.findNextXref(to: removeKextWithIdentifierStart, optimization: .onlyBranches) 442 | while let pc = terminateOSString, 443 | AArch64Instr.Emulate.bl(textExec.instruction(at: pc) ?? 0, pc: pc) == nil { 444 | terminateOSString = textExec.findNextXref(to: removeKextWithIdentifierStart, startAt: pc + 4, optimization: .onlyBranches) 445 | } 446 | 447 | guard terminateOSString != nil else { 448 | return nil 449 | } 450 | 451 | // Now we just find the start of this... 452 | var terminateOSStringStart: UInt64! 453 | for i in 1..<300 { 454 | let pos = terminateOSString - UInt64(i * 4) 455 | if AArch64Instr.isPacibsp(textExec.instruction(at: pos) ?? 0, alsoAllowNop: false) { 456 | terminateOSStringStart = pos 457 | break 458 | } 459 | } 460 | 461 | guard terminateOSStringStart != nil else { 462 | return nil 463 | } 464 | 465 | // ...xref it... 466 | guard let terminateDriversForModuleBL = textExec.findNextXref(to: terminateOSStringStart, optimization: .onlyBranches) else { 467 | return nil 468 | } 469 | 470 | // ...and find start 471 | var terminateDriversForModule: UInt64! 472 | for i in 1..<300 { 473 | let pos = terminateDriversForModuleBL - UInt64(i * 4) 474 | if AArch64Instr.isPacibsp(textExec.instruction(at: pos) ?? 0) { 475 | terminateDriversForModule = pos 476 | break 477 | } 478 | } 479 | 480 | return terminateDriversForModule 481 | }() 482 | 483 | /// Address of the `kalloc_data_external` function 484 | public lazy var kalloc_data_external: UInt64? = { 485 | // For kalloc, find "AMFI: %s: Failed to allocate memory for fatal error message, cannot produce a crash reason." 486 | // The first bl in the function will be to kalloc_data_external 487 | guard let amfi_fatal_err_str = cStrSect.addrOf("AMFI: %s: Failed to allocate memory for fatal error message, cannot produce a crash reason.") else { 488 | return nil 489 | } 490 | 491 | guard var amfi_fatal_err_func = textExec.findNextXref(to: amfi_fatal_err_str, optimization: .noBranches) else { 492 | return nil 493 | } 494 | 495 | var amfi_fatal_err_func_start: UInt64! 496 | for i in 1..<300 { 497 | let pos = amfi_fatal_err_func - UInt64(i * 4) 498 | if AArch64Instr.isPacibsp(textExec.instruction(at: pos) ?? 0) { 499 | amfi_fatal_err_func_start = pos 500 | break 501 | } 502 | } 503 | 504 | guard amfi_fatal_err_func_start != nil else { 505 | return nil 506 | } 507 | 508 | var kalloc_external: UInt64! 509 | for i in 1..<20 { 510 | let pc = amfi_fatal_err_func_start + UInt64(i * 4) 511 | let target = AArch64Instr.Emulate.bl(textExec.instruction(at: pc) ?? 0, pc: pc) 512 | if target != nil { 513 | kalloc_external = target 514 | break 515 | } 516 | } 517 | 518 | return kalloc_external 519 | }() 520 | 521 | /// Address of the `ml_sign_thread_state` function 522 | public lazy var ml_sign_thread_state: UInt64? = { 523 | return textExec.addrOf([0x9AC03021, 0x9262F842, 0x9AC13041, 0x9AC13061, 0x9AC13081, 0x9AC130A1, 0xF9009401, 0xD65F03C0]) 524 | }() 525 | 526 | /// Address of the ppl handler table 527 | public lazy var ppl_handler_table: UInt64? = { 528 | guard let ppl_bootstrap_dispatch = ppl_bootstrap_dispatch else { 529 | return nil 530 | } 531 | 532 | var ppl_handler_table: UInt64? 533 | for i in 1..<20 { 534 | let pc = ppl_bootstrap_dispatch + UInt64(i * 4) 535 | let adrp = textExec.instruction(at: pc) ?? 0 536 | let ldr = textExec.instruction(at: pc + 4) ?? 0 537 | let tbl = AArch64Instr.Emulate.adrpAdd(adrp: adrp, add: ldr, pc: pc) 538 | if tbl != nil { 539 | ppl_handler_table = tbl 540 | break 541 | } 542 | } 543 | 544 | return ppl_handler_table 545 | }() 546 | 547 | /// Address of `pmap_image4_trust_caches` 548 | public lazy var pmap_image4_trust_caches: UInt64? = { 549 | guard let ppl_handler_table = ppl_handler_table else { 550 | return nil 551 | } 552 | 553 | guard var pmap_lookup_in_loaded_trust_caches_internal = constSect.r64(at: ppl_handler_table + 0x148) else { 554 | return nil 555 | } 556 | 557 | if (pmap_lookup_in_loaded_trust_caches_internal >> 48) == 0x8011 { 558 | // Relocation, on-disk kernel 559 | pmap_lookup_in_loaded_trust_caches_internal &= 0xFFFFFFFFFFFF 560 | pmap_lookup_in_loaded_trust_caches_internal += 0xFFFFFFF007004000 561 | } else { 562 | // Probably live kernel 563 | // Strip pointer authentication code 564 | pmap_lookup_in_loaded_trust_caches_internal |= 0xFFFFFF8000000000 565 | } 566 | 567 | var pmap_image4_trust_caches: UInt64? 568 | for i in 1..<20 { 569 | let pc = pmap_lookup_in_loaded_trust_caches_internal + UInt64(i * 4) 570 | let emu = AArch64Instr.Emulate.ldr(pplText.instruction(at: pc) ?? 0, pc: pc) 571 | if emu != nil { 572 | pmap_image4_trust_caches = emu 573 | break 574 | } 575 | } 576 | 577 | return pmap_image4_trust_caches 578 | }() 579 | 580 | /// Get the EL level the kernel runs at 581 | public lazy var kernel_el: UInt64? = { 582 | // Get start 583 | guard let realStart = AArch64Instr.Emulate.b(textExec.instruction(at: entryPoint) ?? 0, pc: entryPoint) else { 584 | return nil 585 | } 586 | 587 | let targetInstructionAddr = realStart + 0x10 588 | let instr = textExec.instruction(at: targetInstructionAddr) ?? 0 589 | if instr == 0xD5384240 { 590 | return 2 591 | } else if AArch64Instr.Emulate.adrp(instr, pc: targetInstructionAddr) != nil { 592 | return 1 593 | } else { 594 | return nil 595 | } 596 | }() 597 | 598 | /// Offset of `TH_RECOVER` in thread struct 599 | public lazy var TH_RECOVER: UInt64? = { 600 | guard let lckFunc = hw_lck_ticket_reserve_orig_allow_invalid_signed else { 601 | return nil 602 | } 603 | 604 | guard let args = AArch64Instr.Args.str(textExec.instruction(at: lckFunc) ?? 0) else { 605 | return nil 606 | } 607 | 608 | return UInt64(args.imm) 609 | }() 610 | 611 | /// Offset of `TH_KSTACKPTR` in thread struct 612 | public lazy var TH_KSTACKPTR: UInt64? = { 613 | var pc: UInt64? 614 | while true { 615 | guard let candidate = textExec.addrOf([0xD538D08A], startAt: pc) else { 616 | return nil 617 | } 618 | 619 | if let args = AArch64Instr.Args.ldr(textExec.instruction(at: candidate + 4) ?? 0) { 620 | if (textExec.instruction(at: candidate + 8) ?? 0) == 0xD503233F { 621 | return UInt64(args.imm) 622 | } 623 | } 624 | 625 | pc = candidate + 4 626 | } 627 | }() 628 | 629 | /// Offset of `ACT_CONTEXT` in thread struct 630 | public lazy var ACT_CONTEXT: UInt64? = { 631 | var pc: UInt64? 632 | while true { 633 | guard let candidate = textExec.addrOf([0xD5184100, 0xA8C107E0, 0xD50040BF], startAt: pc) else { 634 | return nil 635 | } 636 | 637 | if let args = AArch64Instr.Args.addImm(textExec.instruction(at: candidate - 12) ?? 0) { 638 | return UInt64(args.imm) 639 | } 640 | 641 | pc = candidate + 4 642 | } 643 | }() 644 | 645 | /// Offset of `ACT_CPUDATAP` in thread struct 646 | public lazy var ACT_CPUDATAP: UInt64? = { 647 | var pc: UInt64? 648 | while true { 649 | guard let candidate = textExec.addrOf([0xD50343DF], startAt: pc) else { 650 | return nil 651 | } 652 | 653 | if let args = AArch64Instr.Args.ldr(textExec.instruction(at: candidate + 4) ?? 0) { 654 | if args.regDst == 11 && args.regSrc == 10 { 655 | return UInt64(args.imm) 656 | } 657 | } 658 | 659 | pc = candidate + 4 660 | } 661 | }() 662 | 663 | /// Offset of `ITK_SPACE` in task struct 664 | public lazy var ITK_SPACE: UInt64? = { 665 | guard let task_dealloc_str = cStrSect.addrOf("task_deallocate(%p): volatile_objects=%d nonvolatile_objects=%d") else { 666 | return nil 667 | } 668 | 669 | guard var task_deallocate_internal = textExec.findNextXref(to: task_dealloc_str, optimization: .noBranches) else { 670 | return nil 671 | } 672 | 673 | var pc = task_deallocate_internal 674 | while true { 675 | guard let candidate = textExec.findNextXref(to: pc, optimization: .onlyBranches) else { 676 | pc = pc - 4 677 | continue 678 | } 679 | 680 | // Scan downward for a bl 681 | pc = candidate 682 | while true { 683 | pc += 4 684 | if let args = AArch64Instr.Emulate.bl(textExec.instruction(at: pc) ?? 0, pc: pc) { 685 | break 686 | } 687 | } 688 | 689 | // Scan for our ldr 690 | while true { 691 | pc += 4 692 | if let args = AArch64Instr.Args.ldr(textExec.instruction(at: pc) ?? 0) { 693 | return UInt64(args.imm) 694 | } 695 | } 696 | } 697 | }() 698 | 699 | /// Offset of `PMAP` in vm\_map struct 700 | public lazy var VM_MAP_PMAP: UInt64? = { 701 | guard let control_access_str = cStrSect.addrOf("userspace has control access to a kernel") else { 702 | return nil 703 | } 704 | 705 | guard var task_check_func = textExec.findNextXref(to: control_access_str, optimization: .noBranches) else { 706 | return nil 707 | } 708 | 709 | var pc = task_check_func 710 | while true { 711 | pc = pc - 4 712 | 713 | if let args = AArch64Instr.Args.ldr(textExec.instruction(at: pc) ?? 0) { 714 | if AArch64Instr.Emulate.compareBranch(textExec.instruction(at: pc + 4) ?? 0, pc: pc + 4) != nil { 715 | if AArch64Instr.Emulate.adrp(textExec.instruction(at: pc - 4) ?? 0, pc: pc - 4) == nil { 716 | return UInt64(args.imm) 717 | } 718 | } 719 | } 720 | } 721 | }() 722 | 723 | /// Offset of `LABEL` in mach\_port struct 724 | public lazy var PORT_LABEL: UInt64? = { 725 | guard let label_check_str = cStrSect.addrOf("ipc_kobject_label_check: attempted receive right copyout for labeled kobject") else { 726 | return nil 727 | } 728 | 729 | guard var label_check_func = textExec.findNextXref(to: label_check_str, optimization: .noBranches) else { 730 | return nil 731 | } 732 | 733 | var pc = label_check_func 734 | while true { 735 | pc = pc - 4 736 | 737 | guard let br = textExec.findNextXref(to: pc, optimization: .onlyBranches) else { 738 | continue 739 | } 740 | 741 | pc = br 742 | 743 | while true { 744 | pc += 4 745 | if let args = AArch64Instr.Args.ldr(textExec.instruction(at: pc) ?? 0) { 746 | return UInt64(args.imm) 747 | } 748 | } 749 | } 750 | }() 751 | 752 | /// Return patchfinder for the currently running kernel. 753 | public static var running: KernelPatchfinder? = { 754 | if let krnl = MachO.runningKernel { 755 | return KernelPatchfinder(kernel: krnl) 756 | } 757 | 758 | // Try libgrabkernel (if available) 759 | typealias grabKernelType = @convention(c) (_ path: UnsafePointer?, _ isResearchDevice: Int32) -> Int32 760 | guard let grabKernelRaw = dlsym(dlopen(nil, 0), "grabkernel") else { 761 | return nil 762 | } 763 | 764 | let grabkernel = unsafeBitCast(grabKernelRaw, to: grabKernelType.self) 765 | 766 | let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path 767 | let kernel = documents + "/kernel.img4" 768 | if !FileManager.default.fileExists(atPath: kernel) { 769 | let status = grabkernel(kernel, 0) 770 | guard status == 0 else { 771 | return nil 772 | } 773 | } 774 | 775 | guard let k = loadImg4Kernel(path: kernel) else { 776 | return nil 777 | } 778 | 779 | guard let machO = try? MachO(fromData: k, okToLoadFAT: false) else { 780 | return nil 781 | } 782 | 783 | return KernelPatchfinder(kernel: machO) 784 | }() 785 | 786 | /// Initialize patchfinder for the given kernel. 787 | public required init?(kernel: MachO) { 788 | self.kernel = kernel 789 | 790 | guard let textExec = kernel.pfSection(segment: "__TEXT_EXEC", section: "__text") else { 791 | return nil 792 | } 793 | 794 | guard let cStrSect = kernel.pfSection(segment: "__TEXT", section: "__cstring") else { 795 | return nil 796 | } 797 | 798 | guard let dataSect = kernel.pfSection(segment: "__DATA", section: "__data") else { 799 | return nil 800 | } 801 | 802 | guard let constSect = kernel.pfSection(segment: "__DATA_CONST", section: "__const") else { 803 | return nil 804 | } 805 | 806 | guard let pplText = kernel.pfSection(segment: "__PPLTEXT", section: "__text") else { 807 | return nil 808 | } 809 | 810 | self.textExec = textExec 811 | self.cStrSect = cStrSect 812 | self.dataSect = dataSect 813 | self.constSect = constSect 814 | self.pplText = pplText 815 | 816 | var baseAddress: UInt64 = UInt64.max 817 | var entryPoint: UInt64? 818 | var runningUnderPiranha = false 819 | for lc in kernel.cmds { 820 | if let seg = lc as? Segment64LoadCommand { 821 | if seg.vmAddr < baseAddress && seg.vmAddr > 0 { 822 | baseAddress = seg.vmAddr 823 | } 824 | } else if let uCmd = lc as? UnixThreadLoadCommand { 825 | /*guard let state = uCmd.threadStates[0].state.tryGetGeneric(type: arm_thread_state64_t.self) else { 826 | return nil 827 | } 828 | 829 | #if arch(arm64) && __DARWIN_OPAQUE_ARM_THREAD_STATE64 830 | let s = UInt64(UInt(bitPattern: state.__opaque_pc)) 831 | #else 832 | let s = state.__pc 833 | #endif*/ 834 | 835 | let state = uCmd.threadStates[0].state 836 | guard let s = state.tryGetGeneric(type: UInt64.self, offset: UInt(state.count - 0x10)) else { 837 | return nil 838 | } 839 | 840 | entryPoint = s 841 | 842 | // Check the start instruction 843 | if AArch64Instr.Emulate.b(textExec.instruction(at: s) ?? 0, pc: s) == nil { 844 | // Not a branch? 845 | // Either a bad kernel or we're running under Piranha 846 | // Piranha always adds three instructions 847 | guard AArch64Instr.Emulate.b(textExec.instruction(at: s + 12) ?? 0, pc: s + 12) != nil else { 848 | // Nope, bad kernel 849 | return nil 850 | } 851 | 852 | // Running under Piranha 853 | // Kernel is probably patched, patchfinder results might be wrong 854 | entryPoint = s + 12 855 | runningUnderPiranha = true 856 | } 857 | } 858 | } 859 | 860 | guard baseAddress != UInt64.max else { 861 | return nil 862 | } 863 | 864 | guard let entryPoint = entryPoint else { 865 | return nil 866 | } 867 | 868 | self.baseAddress = baseAddress 869 | self.entryPoint = entryPoint 870 | self.runningUnderPiranha = runningUnderPiranha 871 | } 872 | 873 | public func pplDispatchFunc(forOperation op: UInt16) -> UInt64? { 874 | guard let gxf_ppl_enter = gxf_ppl_enter else { 875 | return nil 876 | } 877 | 878 | var pc: UInt64! = nil 879 | while true { 880 | guard let ref = textExec.findNextXref(to: gxf_ppl_enter, startAt: pc, optimization: .onlyBranches) else { 881 | return nil 882 | } 883 | 884 | if let args = AArch64Instr.Args.movz(textExec.instruction(at: ref - 4) ?? 0) { 885 | if args.regDst == 15 && args.imm == op { 886 | return ref - 4 887 | } 888 | } 889 | 890 | pc = ref + 4 891 | } 892 | } 893 | } 894 | -------------------------------------------------------------------------------- /Sources/KernelPatchfinderTester/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KernelPatchfinderTests.swift 3 | // KernelPatchfinder 4 | // 5 | // Created by Linus Henze. 6 | // Copyright © 2022 Pinauten GmbH. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import KernelPatchfinder 11 | import SwiftMachO 12 | 13 | guard CommandLine.arguments.count == 2 else { 14 | print("Usage: KernelPatchfinderTester ") 15 | exit(-1) 16 | } 17 | 18 | guard let pf = KernelPatchfinder(kernel: try MachO(fromFile: CommandLine.arguments[1], okToLoadFAT: false)) else { 19 | print("Failed to create KernelPatchfinder instance!") 20 | exit(-1) 21 | } 22 | 23 | if pf.allproc == nil { 24 | print("Failed: pf.allproc") 25 | } 26 | 27 | if pf.cpu_ttep == nil { 28 | print("Failed: pf.cpu_ttep") 29 | } 30 | 31 | if pf.pmap_enter_options_addr == nil { 32 | print("Failed: pf.pmap_enter_options_addr") 33 | } 34 | 35 | if pf.hw_lck_ticket_reserve_orig_allow_invalid_signed == nil { 36 | print("Failed: pf.hw_lck_ticket_reserve_orig_allow_invalid_signed") 37 | } 38 | 39 | if pf.hw_lck_ticket_reserve_orig_allow_invalid == nil { 40 | print("Failed: pf.hw_lck_ticket_reserve_orig_allow_invalid") 41 | } 42 | 43 | if pf.br_x22_gadget == nil { 44 | print("Failed: pf.br_x22_gadget") 45 | } 46 | 47 | if pf.exception_return == nil { 48 | print("Failed: pf.exception_return") 49 | } 50 | 51 | if pf.ldp_x0_x1_x8_gadget == nil { 52 | print("Failed: pf.ldp_x0_x1_x8_gadget") 53 | } 54 | 55 | if pf.exception_return_after_check == nil { 56 | print("Failed: pf.exception_return_after_check") 57 | } 58 | 59 | if pf.exception_return_after_check_no_restore == nil { 60 | print("Failed: pf.exception_return_after_check_no_restore") 61 | } 62 | 63 | if pf.str_x8_x9_gadget == nil { 64 | print("Failed: pf.str_x8_x9_gadget") 65 | } 66 | 67 | if pf.str_x0_x19_ldr_x20 == nil { 68 | print("Failed: pf.str_x0_x19_ldr_x20") 69 | } 70 | 71 | if pf.pmap_set_nested == nil { 72 | print("Failed: pf.pmap_set_nested") 73 | } 74 | 75 | if pf.pmap_nest == nil { 76 | print("Failed: pf.pmap_nest") 77 | } 78 | 79 | if pf.pmap_remove_options == nil { 80 | print("Failed: pf.pmap_remove_options") 81 | } 82 | 83 | if pf.pmap_mark_page_as_ppl_page == nil { 84 | print("Failed: pf.pmap_mark_page_as_ppl_page") 85 | } 86 | 87 | if pf.pmap_create_options == nil { 88 | print("Failed: pf.pmap_create_options") 89 | } 90 | 91 | if pf.ml_sign_thread_state == nil { 92 | print("Failed: pf.ml_sign_thread_state") 93 | } 94 | 95 | if pf.kernel_el == nil { 96 | print("Failed to find out if kernel runs at EL1 or EL2!") 97 | } 98 | 99 | if pf.TH_RECOVER == nil { 100 | print("Failed: pf.TH_RECOVER") 101 | } 102 | 103 | if pf.TH_KSTACKPTR == nil { 104 | print("Failed: pf.TH_KSTACKPTR") 105 | } 106 | 107 | if pf.ACT_CONTEXT == nil { 108 | print("Failed: pf.ACT_CONTEXT") 109 | } 110 | 111 | if pf.ACT_CPUDATAP == nil { 112 | print("Failed: pf.ACT_CPUDATAP") 113 | } 114 | 115 | if pf.ITK_SPACE == nil { 116 | print("Failed: pf.ITK_SPACE") 117 | } 118 | 119 | if pf.VM_MAP_PMAP == nil { 120 | print("Failed: pf.VM_MAP_PMAP") 121 | } 122 | 123 | if pf.PORT_LABEL == nil { 124 | print("Failed: pf.PORT_LABEL") 125 | } 126 | -------------------------------------------------------------------------------- /Tests/KernelPatchfinderTests/KernelPatchfinderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KernelPatchfinderTests.swift 3 | // KernelPatchfinder 4 | // 5 | // Created by Linus Henze. 6 | // Copyright © 2022 Pinauten GmbH. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import KernelPatchfinder 11 | import SwiftMachO 12 | import PatchfinderUtils 13 | 14 | final class KernelPatchfinderTests: XCTestCase { 15 | func testPatchfinder() throws { 16 | /*guard let pf = KernelPatchfinder.running else { 17 | XCTFail("KernelPatchfinder.running == nil!") 18 | return 19 | }*/ 20 | 21 | // /Users/linus/kernelcache.release.iphone11.raw 22 | // /Users/linus/Desktop/Fugu15_OBTS/Server/kernelcache.release.iphone14.raw 23 | guard let pf = KernelPatchfinder(kernel: try! MachO(fromFile: "/Users/linus/kernelcache.release.iphone11.raw", okToLoadFAT: false)) else { 24 | XCTFail("KernelPatchfinder.running == nil!") 25 | return 26 | } 27 | 28 | XCTAssertNotNil(pf.allproc) 29 | XCTAssertNotNil(pf.cpu_ttep) 30 | XCTAssertNotNil(pf.pmap_enter_options_addr) 31 | XCTAssertNotNil(pf.hw_lck_ticket_reserve_orig_allow_invalid_signed) 32 | XCTAssertNotNil(pf.hw_lck_ticket_reserve_orig_allow_invalid) 33 | XCTAssertNotNil(pf.br_x22_gadget) 34 | XCTAssertNotNil(pf.exception_return) 35 | XCTAssertNotNil(pf.ldp_x0_x1_x8_gadget) 36 | XCTAssertNotNil(pf.exception_return_after_check) 37 | XCTAssertNotNil(pf.exception_return_after_check_no_restore) 38 | XCTAssertNotNil(pf.str_x8_x9_gadget) 39 | XCTAssertNotNil(pf.str_x0_x19_ldr_x20) 40 | XCTAssertNotNil(pf.pmap_set_nested) 41 | XCTAssertNotNil(pf.pmap_nest) 42 | XCTAssertNotNil(pf.pmap_remove_options) 43 | XCTAssertNotNil(pf.pmap_mark_page_as_ppl_page) 44 | XCTAssertNotNil(pf.pmap_create_options) 45 | XCTAssertNotNil(pf.gIOCatalogue) 46 | XCTAssertNotNil(pf.terminateDriversForModule) 47 | XCTAssertNotNil(pf.kalloc_data_external) 48 | XCTAssertNotNil(pf.ml_sign_thread_state) 49 | XCTAssertNotNil(pf.ppl_handler_table) 50 | XCTAssertNotNil(pf.pmap_image4_trust_caches) 51 | XCTAssertNotNil(pf.kernel_el) 52 | XCTAssertNotNil(pf.TH_RECOVER) 53 | XCTAssertNotNil(pf.TH_KSTACKPTR) 54 | XCTAssertNotNil(pf.ACT_CONTEXT) 55 | XCTAssertNotNil(pf.ACT_CPUDATAP) 56 | XCTAssertNotNil(pf.ITK_SPACE) 57 | XCTAssertNotNil(pf.VM_MAP_PMAP) 58 | XCTAssertNotNil(pf.PORT_LABEL) 59 | } 60 | } 61 | --------------------------------------------------------------------------------