diff --git a/FrameworkClientApp/FishHook.swift b/FrameworkClientApp/FishHook.swift index 1e8439d..6d2dffd 100644 --- a/FrameworkClientApp/FishHook.swift +++ b/FrameworkClientApp/FishHook.swift @@ -44,6 +44,7 @@ private func replaceSymbolAtImage( ) { var linkeditCmd: UnsafeMutablePointer! var dataCmd: UnsafeMutablePointer! + var dataConstCmd: UnsafeMutablePointer! var symtabCmd: UnsafeMutablePointer! var dynamicSymtabCmd: UnsafeMutablePointer! @@ -56,10 +57,14 @@ private func replaceSymbolAtImage( let curCmdNameOffset = MemoryLayout.size(ofValue: curCmd.pointee.cmd) + MemoryLayout.size(ofValue: curCmd.pointee.cmdsize) let curCmdNamePointer = curCmdPointer.advanced(by: curCmdNameOffset).assumingMemoryBound(to: Int8.self) let curCmdName = String(cString: curCmdNamePointer) - if (curCmdName == SEG_LINKEDIT) { + switch curCmdName { + case SEG_LINKEDIT: linkeditCmd = curCmd - } else if (curCmdName == SEG_DATA) { + case SEG_DATA: dataCmd = curCmd + case "__DATA_CONST": + dataConstCmd = curCmd + default: break } } else if curCmd.pointee.cmd == LC_SYMTAB { symtabCmd = UnsafeMutablePointer(OpaquePointer(curCmd)) @@ -70,7 +75,7 @@ private func replaceSymbolAtImage( curCmdPointer += Int(curCmd.pointee.cmdsize) } - if linkeditCmd == nil || symtabCmd == nil || dynamicSymtabCmd == nil || dataCmd == nil { + if linkeditCmd == nil || symtabCmd == nil || dynamicSymtabCmd == nil || (dataCmd == nil && dataConstCmd == nil) { return } @@ -83,15 +88,18 @@ private func replaceSymbolAtImage( return } - for tmp in 0...size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) - - // symbol_pointers sections - if curSection.pointee.flags == S_LAZY_SYMBOL_POINTERS { - replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) - } - if curSection.pointee.flags == S_NON_LAZY_SYMBOL_POINTERS { - replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) + for segment in [dataCmd, dataConstCmd] { + guard let segment else { continue } + for tmp in 0...size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) + + // symbol_pointers sections + if curSection.pointee.flags == S_LAZY_SYMBOL_POINTERS { + replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) + } + if curSection.pointee.flags == S_NON_LAZY_SYMBOL_POINTERS { + replaceSymbolPointerAtSection(curSection, symtab: symtab!, strtab: strtab!, indirectsym: indirectsym!, slide: slide, symbolName: symbol, newMethod: newMethod, oldMethod: &oldMethod) + } } } } @@ -116,14 +124,23 @@ private func replaceSymbolPointerAtSection( for tmp in 0...size { let curIndirectSym = indirectSymVmAddr.advanced(by: tmp) - if curIndirectSym.pointee == INDIRECT_SYMBOL_ABS || curIndirectSym.pointee == INDIRECT_SYMBOL_LOCAL { + if curIndirectSym.pointee & UInt32(INDIRECT_SYMBOL_ABS) != 0 || curIndirectSym.pointee & ~UInt32(INDIRECT_SYMBOL_ABS) == INDIRECT_SYMBOL_LOCAL { continue } let curStrTabOff = symtab.advanced(by: Int(curIndirectSym.pointee)).pointee.n_un.n_strx let curSymbolName = strtab.advanced(by: Int(curStrTabOff+1)) if String(cString: curSymbolName) == symbolName { oldMethod = sectionVmAddr!.advanced(by: tmp).pointee - sectionVmAddr!.advanced(by: tmp).initialize(to: newMethod) + let err = vm_protect( + mach_task_self_, + .init(bitPattern: sectionVmAddr), + numericCast(section.pointee.size), + 0, + VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY + ) + if err == KERN_SUCCESS { + sectionVmAddr!.advanced(by: tmp).initialize(to: newMethod) + } break } } diff --git a/IOSSecuritySuite/FishHookChecker.swift b/IOSSecuritySuite/FishHookChecker.swift index e521113..5f36ea0 100644 --- a/IOSSecuritySuite/FishHookChecker.swift +++ b/IOSSecuritySuite/FishHookChecker.swift @@ -146,6 +146,7 @@ internal class SymbolFound { // target cmd var linkeditCmd: UnsafeMutablePointer! var dyldInfoCmd: UnsafeMutablePointer! + var dyldChainedFixUpsCmd: UnsafeMutablePointer! var allLoadDylds = [String]() guard var curCmdPointer = UnsafeMutableRawPointer(bitPattern: UInt(bitPattern: image)+UInt(MemoryLayout.size)) else { @@ -164,6 +165,8 @@ internal class SymbolFound { } case LC_DYLD_INFO_ONLY, UInt32(LC_DYLD_INFO): dyldInfoCmd = curCmdPointer.assumingMemoryBound(to: dyld_info_command.self) + case LC_DYLD_CHAINED_FIXUPS: + dyldChainedFixUpsCmd = curCmdPointer.assumingMemoryBound(to: linkedit_data_command.self) case UInt32(LC_LOAD_DYLIB), LC_LOAD_WEAK_DYLIB, LC_LOAD_UPWARD_DYLIB, LC_REEXPORT_DYLIB: let loadDyldCmd = curCmdPointer.assumingMemoryBound(to: dylib_command.self) let loadDyldNameOffset = Int(loadDyldCmd.pointee.dylib.name.offset) @@ -177,33 +180,43 @@ internal class SymbolFound { curCmdPointer += Int(curCmd.pointee.cmdsize) } - if linkeditCmd == nil || dyldInfoCmd == nil { return false } + if linkeditCmd == nil || (dyldInfoCmd == nil && dyldChainedFixUpsCmd == nil) { return false } let linkeditBase = UInt64(slide + Int(linkeditCmd.pointee.vmaddr) - Int(linkeditCmd.pointee.fileoff)) - // look by LazyBindInfo - let lazyBindSize = Int(dyldInfoCmd.pointee.lazy_bind_size) - if (lazyBindSize > 0) { - if let lazyBindInfoCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.lazy_bind_off))), - lookLazyBindSymbol(symbol, symbolAddr: &symbolAddress, lazyBindInfoCmd: lazyBindInfoCmd, lazyBindInfoSize: lazyBindSize, allLoadDylds: allLoadDylds) { - return true - } + let chainedFixUpsSize = Int(dyldChainedFixUpsCmd?.pointee.datasize ?? 0) + if let dyldChainedFixUpsCmd, + chainedFixUpsSize > 0, + let dataStart = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldChainedFixUpsCmd.pointee.dataoff))), + lookDyldChainedFixUps(symbol, symbolAddr: &symbolAddress, chainedFixUpsCmd: dataStart, chainedFixUpsSize: chainedFixUpsSize, imageSlide: slide, allLoadDylds: allLoadDylds) { + return true } - - // look by NonLazyBindInfo - let bindSize = Int(dyldInfoCmd.pointee.bind_size) - if (bindSize > 0) { - if let bindCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.bind_off))), - lookBindSymbol(symbol, symbolAddr: &symbolAddress, bindInfoCmd: bindCmd, bindInfoSize: bindSize, allLoadDylds: allLoadDylds) { - return true + + if let dyldInfoCmd { + // look by LazyBindInfo + let lazyBindSize = Int(dyldInfoCmd.pointee.lazy_bind_size) + if (lazyBindSize > 0) { + if let lazyBindInfoCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.lazy_bind_off))), + lookLazyBindSymbol(symbol, symbolAddr: &symbolAddress, lazyBindInfoCmd: lazyBindInfoCmd, lazyBindInfoSize: lazyBindSize, imageSlide: slide, allLoadDylds: allLoadDylds) { + return true + } + } + + // look by NonLazyBindInfo + let bindSize = Int(dyldInfoCmd.pointee.bind_size) + if (bindSize > 0) { + if let bindCmd = UnsafeMutablePointer(bitPattern: UInt(linkeditBase + UInt64(dyldInfoCmd.pointee.bind_off))), + lookBindSymbol(symbol, symbolAddr: &symbolAddress, bindInfoCmd: bindCmd, bindInfoSize: bindSize, imageSlide: slide, allLoadDylds: allLoadDylds) { + return true + } } } - + return false } // LazySymbolBindInfo @inline(__always) - private static func lookLazyBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, lazyBindInfoCmd: UnsafeMutablePointer, lazyBindInfoSize: Int, allLoadDylds: [String]) -> Bool { + private static func lookLazyBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, lazyBindInfoCmd: UnsafeMutablePointer, lazyBindInfoSize: Int, imageSlide slide: Int, allLoadDylds: [String]) -> Bool { var ptr = lazyBindInfoCmd let lazyBindingInfoEnd = lazyBindInfoCmd.advanced(by: Int(lazyBindInfoSize)) var ordinal: Int = -1 @@ -265,7 +278,15 @@ internal class SymbolFound { assert(ordinal <= allLoadDylds.count) if (foundSymbol && ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { - let imageName = allLoadDylds[ordinal-1] + let imageName: String + if ordinal == SELF_LIBRARY_ORDINAL { + guard let selfImageName = Self.imageName(for: slide) else { + fatalError() + } + imageName = selfImageName + } else { + imageName = allLoadDylds[ordinal-1] + } var tmpSymbolAddress: UnsafeMutableRawPointer? if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { symbolAddr = symbolPointer + addend @@ -278,7 +299,7 @@ internal class SymbolFound { // NonLazySymbolBindInfo @inline(__always) - private static func lookBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, bindInfoCmd: UnsafeMutablePointer, bindInfoSize: Int, allLoadDylds: [String]) -> Bool { + private static func lookBindSymbol(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, bindInfoCmd: UnsafeMutablePointer, bindInfoSize: Int, imageSlide slide: Int, allLoadDylds: [String]) -> Bool { var ptr = bindInfoCmd let bindingInfoEnd = bindInfoCmd.advanced(by: Int(bindInfoSize)) var ordinal: Int = -1 @@ -365,7 +386,15 @@ internal class SymbolFound { assert(ordinal <= allLoadDylds.count) if (foundSymbol && ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count, type != BindTypeThreadedRebase { - let imageName = allLoadDylds[ordinal-1] + let imageName: String + if ordinal == SELF_LIBRARY_ORDINAL { + guard let selfImageName = Self.imageName(for: slide) else { + fatalError() + } + imageName = selfImageName + } else { + imageName = allLoadDylds[ordinal-1] + } var tmpSymbolAddress: UnsafeMutableRawPointer? if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { symbolAddr = symbolPointer + addend @@ -375,7 +404,68 @@ internal class SymbolFound { return false } - + + // DyldChainedFixUps + @inline(__always) + private static func lookDyldChainedFixUps(_ symbol: String, symbolAddr: inout UnsafeMutableRawPointer?, chainedFixUpsCmd: UnsafeMutablePointer, chainedFixUpsSize: Int, imageSlide slide: Int, allLoadDylds: [String]) -> Bool { + let header = UnsafeRawPointer(chainedFixUpsCmd) + .assumingMemoryBound(to: DyldChainedFixupsHeader.self) + .pointee + let importsOffset: Int = numericCast(header.imports_offset) + let importsCount: Int = numericCast(header.imports_count) + + let importsStart = UnsafeRawPointer(chainedFixUpsCmd).advanced(by: importsOffset) + + let imports: [any DyldChainedImportProtocol] + switch header.importsFormat { + case .general: + imports = UnsafeBufferPointer( + start: importsStart + .assumingMemoryBound(to: DyldChainedImportGeneral.self), + count: importsCount + ).map { $0 } + case .addend: + imports = UnsafeBufferPointer( + start: importsStart + .assumingMemoryBound(to: DyldChainedImportAddend.self), + count: importsCount + ).map { $0 } + case .addend64: + imports = UnsafeBufferPointer( + start: importsStart + .assumingMemoryBound(to: DyldChainedImportAddend64.self), + count: importsCount + ).map { $0 } + } + + for `import` in imports { + let namePtr = UnsafeRawPointer(chainedFixUpsCmd) + .advanced(by: numericCast(header.symbols_offset)) + .advanced(by: `import`.nameOffset) + .assumingMemoryBound(to: CChar.self) + let name = String(cString: namePtr + 1) + if name == symbol { + let ordinal = `import`.libraryOrdinal + assert(ordinal <= allLoadDylds.count) + if (ordinal >= 0 && allLoadDylds.count > 0), ordinal <= allLoadDylds.count { + var s: String? + for index in 0..<_dyld_image_count() { + if _dyld_get_image_vmaddr_slide(index) == slide { + s = String(cString: _dyld_get_image_name(index)) + } + } + let imageName = ordinal == 0 ? s!: allLoadDylds[ordinal-1] + var tmpSymbolAddress: UnsafeMutableRawPointer? + if lookExportedSymbol(symbol, exportImageName: imageName, symbolAddress: &tmpSymbolAddress), let symbolPointer = tmpSymbolAddress { + symbolAddr = symbolPointer + `import`.symbolAddend + return true + } + } + } + } + return false + } + // ExportSymbol @inline(__always) private static func lookExportedSymbol(_ symbol: String, exportImageName: String, symbolAddress: inout UnsafeMutableRawPointer?) -> Bool { @@ -526,7 +616,7 @@ internal class SymbolFound { terminalSize = readUleb128(ptr: &ptr, end: end) } if terminalSize != 0 { - return currentSymbol == targetSymbol ? ptr : nil + return currentSymbol == "_" + targetSymbol ? ptr : nil } // children @@ -552,7 +642,7 @@ internal class SymbolFound { // node if let nodeSymbol = String(cString: nodeLabel, encoding: .utf8) { let tmpCurrentSymbol = currentSymbol + nodeSymbol - if !targetSymbol.contains(tmpCurrentSymbol) { + if !("_" + targetSymbol).contains(tmpCurrentSymbol) { continue } if nodeOffset != 0 && (start + nodeOffset <= end) { @@ -566,6 +656,15 @@ internal class SymbolFound { } return nil } + + static private func imageName(for slide: Int) -> String? { + for index in 0..<_dyld_image_count() { + if _dyld_get_image_vmaddr_slide(index) == slide { + return String(cString: _dyld_get_image_name(index)) + } + } + return nil + } } // MARK: - FishHook @@ -634,7 +733,7 @@ private class FishHook { for segment in [dataCmd, dataConstCmd] { guard let segment else { continue } for tmp in 0...size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) + let curSection = UnsafeMutableRawPointer(segment).advanced(by: MemoryLayout.size + MemoryLayout.size*Int(tmp)).assumingMemoryBound(to: section_64.self) // symbol_pointers sections if curSection.pointee.flags == S_LAZY_SYMBOL_POINTERS { @@ -665,7 +764,7 @@ private class FishHook { for tmp in 0...size { let curIndirectSym = indirectSymVmAddr.advanced(by: tmp) - if curIndirectSym.pointee == INDIRECT_SYMBOL_ABS || curIndirectSym.pointee == INDIRECT_SYMBOL_LOCAL { + if curIndirectSym.pointee & UInt32(INDIRECT_SYMBOL_ABS) != 0 || curIndirectSym.pointee & ~UInt32(INDIRECT_SYMBOL_ABS) == INDIRECT_SYMBOL_LOCAL { continue } let curStrTabOff = symtab.advanced(by: Int(curIndirectSym.pointee)).pointee.n_un.n_strx @@ -688,5 +787,95 @@ private class FishHook { } } } + +// https://github.com/apple-oss-distributions/xnu/blob/1031c584a5e37aff177559b9f69dbd3c8c3fd30a/EXTERNAL_HEADERS/mach-o/fixup-chains.h +struct DyldChainedFixupsHeader { + let fixups_version: UInt32 // 0 + let starts_offset: UInt32 // offset of dyld_chained_starts_in_image in chain_data + let imports_offset: UInt32 // offset of imports table in chain_data + let symbols_offset: UInt32 // offset of symbol strings in chain_data + let imports_count: UInt32 // number of imported symbol names + let imports_format: UInt32 // DYLD_CHAINED_IMPORT* + let symbols_format: UInt32 // 0 => uncompressed, 1 => zlib compressed +} + +extension DyldChainedFixupsHeader { + var importsFormat: DyldChainedImportFormat { + .init(rawValue: imports_format)! + } +} + +public enum DyldChainedImportFormat: UInt32 { + /// DYLD_CHAINED_IMPORT + case general = 1 + /// DYLD_CHAINED_IMPORT_ADDEND + case addend + /// DYLD_CHAINED_IMPORT_ADDEND64 + case addend64 +} + +protocol DyldChainedImportProtocol { + var libraryOrdinal: Int { get } + var isWeakImport: Bool { get } + var nameOffset: Int { get } + var symbolAddend: Int { get } +} + +struct DyldChainedImportGeneral: DyldChainedImportProtocol { + let rawValue: UInt32 + + var libraryOrdinal: Int { + numericCast(rawValue & 0xFF) + } + + var isWeakImport: Bool { + (rawValue & 0x100) != 0 + } + + var nameOffset: Int { + numericCast((rawValue >> 9) & 0x7FFFFF) + } + + var symbolAddend: Int { 0 } +} + +struct DyldChainedImportAddend: DyldChainedImportProtocol { + var addend: Int32 + var rawValue: UInt32 + + var libraryOrdinal: Int { + numericCast(rawValue & 0xFF) + } + + var isWeakImport: Bool { + (rawValue & 0x100) != 0 + } + + var nameOffset: Int { + numericCast((rawValue >> 9) & 0x7FFFFF) + } + + var symbolAddend: Int { numericCast(addend) } +} + +struct DyldChainedImportAddend64: DyldChainedImportProtocol { + var addend: UInt64 + var rawValue: UInt64 + + var libraryOrdinal: Int { + numericCast(rawValue & 0xFFFF) + } + + var isWeakImport: Bool { + (rawValue & 0x10000) != 0 + } + + var nameOffset: Int { + numericCast((rawValue >> 17) & 0xFFFFFFFF) + } + + var symbolAddend: Int { numericCast(addend) } +} + #endif // swiftlint:enable all