You cannot stop someone from modifying a copy of your iOS app, but you can stop that copy from working against your service. Every mod must be re-signed, and re-signing changes the app’s identity: a different Team ID, a provisioning profile that App Store builds do not have, and keys that do not match your App ID. Use App Attest so your server refuses requests that do not come from your genuine app. Add in-app checks on Team ID, bundle ID and provisioning, and a response policy for what the app does when they fail. Then record your own legitimate signing changes so they are never mistaken for mods.
How re-signed mods reach devices
An App Store binary is encrypted and signed by Apple for your Team ID. To modify it, an attacker decrypts a copy, changes code or injects a library, and signs the result with a certificate they control. Common routes:
- Free developer accounts. Anyone with an Apple ID can sign an app for their own device. The profile expires after seven days, so sideloading tools re-sign it repeatedly.
- Paid developer certificates. Ad hoc profiles install on registered devices; some mod services sell device registrations.
- Enterprise certificates. Certificates meant for in-house distribution are misused to install mods on any device until Apple revokes them.
- Desktop sideloading tools. These automate signing with the user’s own Apple ID and refresh the app before the profile expires.
EU alternative distribution
In the European Union, iOS also allows alternative app marketplaces and web distribution. These apps are still signed with the developer’s own certificate and notarised by Apple, so a marketplace cannot ship a modded copy under your Team ID. Expect legitimate installs from these channels if you use them, and record them as approved. On iOS in the EU, MarketplaceKit’s AppDistributor reports whether the App Store, TestFlight, a marketplace or web distribution installed the app.
Refuse re-signed copies on the server with App Attest
App Attest is the strongest control because it runs outside the device’s reach. The app generates a key with DCAppAttestService, Apple attests it, and your server records the public key. The attestation includes a hash of your App ID, which is your Team ID plus bundle ID. A re-signed copy runs under a different Team ID, so its attestation fails your check, or it cannot obtain one at all.
- Check
DCAppAttestService.shared.isSupported. If it is false, apply your unavailable-evidence policy. - Call
generateKey(), thenattestKey(_:clientDataHash:)with a hash of a single-use server challenge. - On the server, validate the attestation as Apple documents, including the RP ID hash of your exact App ID and the production environment.
- For each sensitive request, verify an assertion from
generateAssertion(_:clientDataHash:)that covers the request and a fresh challenge, and require the counter to increase.
Requests without a valid assertion do not reach sensitive operations. The full procedure is in how to verify mobile app attestation.
Detect re-signing inside the app
In-app checks catch a mod early and let the app respond. A mod author can patch them, so protect the code that runs them and never rely on them alone.
| Check | What a genuine App Store install shows | What a re-signed copy usually shows |
|---|---|---|
| Team ID | Your Team ID as the prefix of the keychain access group | Another Team ID |
| Bundle ID | Bundle.main.bundleIdentifier equals your ID | Same or changed ID |
embedded.mobileprovision | Not present in the app bundle | Present, naming another team |
| Binary encryption | cryptid set to 1 in the LC_ENCRYPTION_INFO_64 load command | cryptid 0 after decryption |
| Associated domains | Universal links and shared web credentials work | Fail, because your apple-app-site-association file lists only your App ID |
To read the Team ID, add a keychain item and query its kSecAttrAccessGroup attribute; the value starts with the Team ID. Development, ad hoc and enterprise builds of your own app contain embedded.mobileprovision, so run the provisioning check only in App Store builds.
Otukan detects app tampering and unauthorised re-signing in protected iOS applications within the configured control scope, and the app applies the response you set per flow. For the product boundary, see repackaging and re-signing detection and iOS app anti-tampering.
Record your own signing authority
Create a release identity record: app owner, channel, App ID, signing certificate, profile and entitlements, including every app extension. Keep private keys inside your approved signing environment. Otukan fits your existing build and release pipeline while signing keys stay with your team.
| Item | Record | Avoid assuming |
|---|---|---|
| Signing authority | Who may approve and sign a release | Any valid signature means you approved it |
| Provisioning | Profile purpose and validity per channel | One profile serves every channel |
| Entitlements | App Attest environment and other capabilities, per target | A capability in the project is in the signed output |
| Distribution | Channel and final delivered artefact | A development build stands in for the store build |
Inspect the final signed app with codesign and compare entitlements and Team ID with the record.
Test with an authorised lab
Use an app you own, test accounts and non-production services. Write the expected result before each run.
- Install the App Store build on physical devices and run sign-in, update, extension entry points and the sensitive action.
- Re-sign a lab copy with a different team’s development certificate. Record whether it installs and launches, separately from any detection.
- If it reaches the flow, record the in-app signal, the configured response and the server’s App Attest decision.
- Repeat with an ad hoc copy and with a copy that changes only the bundle ID.
A copy that iOS refuses to install is platform evidence, not a detection result. Simulator runs are recorded separately and do not replace signed-device tests.
Rehearse legitimate changes
Renew a distribution certificate, add a new channel and ship a corrective release. Confirm installed versions keep working and App Attest keys stay valid. Identify where an expired or mismatched profile fails: packaging, install, launch or a protected operation.
- App Attest assertions are required for every sensitive endpoint.
- In-app checks run only in the builds they apply to.
- Approved channels, including EU distribution if used, are recorded.
- Certificate and profile changes have an owner and a review trail.
- Install, update, sign-in and sensitive-flow regressions pass.
For Android, see how to detect repackaging in Android apps. For the wider platform, see iOS app security.
Further reading
- Establishing your app’s integrity — Apple Developer Documentation
- Validating apps that connect to your server — Apple Developer Documentation
- TN3125: Inside Code Signing: Provisioning Profiles — Apple Developer Documentation
- Supporting associated domains — Apple Developer Documentation
- MarketplaceKit — Apple Developer Documentation