Skip to content

Instantly share code, notes, and snippets.

@khanhduytran0
Last active August 10, 2026 10:17
Show Gist options
  • Select an option

  • Save khanhduytran0/e3ae92673a1acfe85ce5a50b59d28361 to your computer and use it in GitHub Desktop.

Select an option

Save khanhduytran0/e3ae92673a1acfe85ce5a50b59d28361 to your computer and use it in GitHub Desktop.
Bypass revoke and expired certs for iOS 26.x

This is for iOS 26+ only. Read mineek's secret.txt for more info.

I didn't plan to release this that early, but as iOS 27 has already nuked partial restore; and A12/A13 being jailbreakable (including A12 iPads EOL at 26) as soon as a new kernel exploit drops, there is not much to lose.

MissedRevoke

(the "secret.txt" for iOS 26)

iOS has had a long time storing revocation details in separate plists, it was trivially bypassable by zeroing and chflags immutable on them. Since iOS 26.0b2, all of these files have been migrated to mis.db. Unexpectedly this not only fail to stop us from bypassing revocation checks, but it also opens up a method to bypass expiration checks.

Using Database trigger, it is possible to intercept attempts to modify the SQL and overwrite the changes with our own data. This means we can effectively "mis"s revocations and expirations by ignoring any attempts to modify them in mis.db. Moreover we can make this to automatically trust dev certs. Enterprise ones cannot work since they take a different trust path via PreBoard (and some checks in TXM, PPL?).

At first it might look like there is no way to place our modified mis.db. Thanks to @mineek, we discovered that mis.db can also be included in a partial restore, specifically MobileDeviceDomain.

Now put the things together:

misdb_path = Path.joinpath(Path.cwd(), "files/mis.db")
os.remove(misdb_path) if misdb_path.exists() else None
conn = sqlite3.connect(misdb_path)
cursor = conn.cursor()
with open("misdb.sql", "r") as f:
    sql = f.read()
cursor.executescript(sql)
conn.commit()

misdb_contents = open(misdb_path, "rb").read()
misdb_shm_contents = open(misdb_path.with_suffix(".db-shm"), "rb").read()
misdb_wal_contents = open(misdb_path.with_suffix(".db-wal"), "rb").read()
conn.close()
files += [
    backup.ConcreteFile("ProvisioningProfiles/mis.db", "MobileDeviceDomain", contents=misdb_contents),
    backup.ConcreteFile("ProvisioningProfiles/mis.db-shm", "MobileDeviceDomain", contents=misdb_shm_contents),
    backup.ConcreteFile("ProvisioningProfiles/mis.db-wal", "MobileDeviceDomain", contents=misdb_wal_contents),
]

Installing and launching apps with expired certs

iOS checks if a cert is expired when you try to install or launch an app. It uses local system time to do so. So all you need is to set the system time back before the given cert expires, install or launch the app, and then reset the time to current.

For this to actually work, this SQL script also includes relevant triggers to overwrite expires to avoid the daemons from removing expired profiles later on.

This method can also be used to rescue the 7-day expired free cert apps. You can just set the date back, launch SideStore for example, then reset the date and finally perform refresh.

Known issues

  • This alone does not bypass blacklist. You need to pair it with some OCSP blacklist bypass like DNS-based blocking profile that you can find on mineek's gist. Even so the traffic can still leak to OCSP servers, which will update the ocspcache.sqlite3 with blacklist, apps signed with blacklisted certs will still refuse to launch.
  • Blocking PPQ causes delay when installing/updating apps. While this is not required, if you are signing your apps with a dev cert, it is recommended to block PPQ to avoid getting your dev account banned.
  • Provisioning profiles will be cached over time. The SQL triggers prevents removing anything to keep expired apps launchable (though still requires setting the system time back).
PRAGMA journal_mode=WAL;
PRAGMA foreign_keys = ON;
-- Initialize the database schema
CREATE TABLE settings ( name TEXT, value TEXT, PRIMARY KEY (name) );
CREATE TABLE profiles ( uuid TEXT NOT NULL PRIMARY KEY, team_id TEXT NOT NULL, install_time TEXT DEFAULT CURRENT_TIMESTAMP, name TEXT NOT NULL, expires INTEGER, is_for_all_devices INTEGER, is_apple_internal INTEGER, is_local INTEGER, is_beta INTEGER, cms_blob BLOB NOT NULL, is_der INTEGER DEFAULT (0));
CREATE TABLE certificates ( pk INTEGER PRIMARY KEY AUTOINCREMENT, leaf BLOB UNIQUE NOT NULL);
CREATE UNIQUE INDEX certificate_leaf_index ON certificates (leaf);
CREATE TABLE certificate_provisioning_cache ( pk INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT NOT NULL, leaf_pk INTEGER NOT NULL, UNIQUE(uuid, leaf_pk), CONSTRAINT fk_certificate_leaf FOREIGN KEY (leaf_pk) REFERENCES certificates(pk) ON DELETE CASCADE, CONSTRAINT fk_cert_profile_uuid FOREIGN KEY (uuid) REFERENCES profiles(uuid) ON DELETE CASCADE );
CREATE TABLE entitlements_provisioning_cache ( pk INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT NOT NULL, predicate TEXT NOT NULL, wildcard INTEGER NOT NULL, UNIQUE(uuid, predicate), CONSTRAINT fk_predicate_profile_uuid FOREIGN KEY (uuid) REFERENCES profiles(uuid) ON DELETE CASCADE );
CREATE UNIQUE INDEX entitlements_cache_index ON entitlements_provisioning_cache (uuid, predicate);
CREATE TABLE xml_profiles_cache ( uuid TEXT NOT NULL PRIMARY KEY, cms_blob BLOB NOT NULL, CONSTRAINT fk_xml_profile_cache_uuid FOREIGN KEY (uuid) REFERENCES profiles(uuid) ON DELETE CASCADE );
CREATE TABLE trusted_team_ids ( team_id TEXT PRIMARY KEY, signature BLOB );
CREATE TABLE team_id_info ( team_id TEXT NOT NULL, team_name TEXT NOT NULL, PRIMARY KEY (team_id) );
CREATE TABLE signing_identities ( pk INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT NOT NULL, signing_identity TEXT NOT NULL, UNIQUE(uuid, signing_identity), CONSTRAINT fk_signing_identity_profile_uuid FOREIGN KEY (uuid) REFERENCES profiles(uuid) ON DELETE CASCADE );
CREATE TABLE online_auth ( uuid TEXT NOT NULL, cdhash BLOB NOT NULL, grace_period INT NOT NULL, last_success_monotonic_time INT NOT NULL, last_success_reset_count INT NOT NULL, is_rejected INT NOT NULL DEFAULT (0), is_rejected_by_whole_profile INT NOT NULL DEFAULT (0), PRIMARY KEY (uuid, cdhash), CONSTRAINT fk_online_auth_profile_uuid FOREIGN KEY (uuid) REFERENCES profiles(uuid) ON DELETE CASCADE );
CREATE TABLE banned_profile_uuids ( uuid TEXT NOT NULL );
CREATE TABLE banned_cdhashes ( cdhash BLOB NOT NULL );
-- Initialize settings
INSERT OR REPLACE INTO settings (name, value) VALUES ('databaseSchemaVersion', 9);
-- Block any attempt to insert banned UUIDs or CDHashes
CREATE TRIGGER IF NOT EXISTS trigger_block_insert_banned_uuids
BEFORE INSERT ON banned_profile_uuids
BEGIN
SELECT RAISE(IGNORE);
END;
CREATE TRIGGER IF NOT EXISTS trigger_block_insert_banned_cdhashes
BEFORE INSERT ON banned_cdhashes
BEGIN
SELECT RAISE(IGNORE);
END;
-- Bypass all kind of revokes
CREATE TRIGGER override_online_auth
AFTER INSERT ON online_auth
BEGIN
UPDATE online_auth
SET
grace_period = 2147483647,
last_success_monotonic_time = 9223372036854775807,
last_success_reset_count = 0,
is_rejected = 0,
is_rejected_by_whole_profile = 0
WHERE uuid = NEW.uuid;
-- Fail if it's trying to revoke the profile
SELECT RAISE(FAIL, "Trolled online_auth insert")
WHERE NEW.is_rejected = 1 OR NEW.is_rejected_by_whole_profile = 1;
END;
-- RAISE(FAIL, 'Trolled'); so it would not fail when first installing and not delete the record
CREATE TRIGGER trigger_update_bypass_ppq
BEFORE UPDATE ON online_auth
BEGIN
UPDATE online_auth
SET
grace_period = 2147483647,
last_success_monotonic_time = 9223372036854775807,
last_success_reset_count = 0,
is_rejected = 0,
is_rejected_by_whole_profile = 0
WHERE uuid = NEW.uuid;
-- Fail if it's trying to revoke the profile
SELECT RAISE(FAIL, "Trolled online_auth update")
WHERE NEW.is_rejected = 1 OR NEW.is_rejected_by_whole_profile = 1;
-- Ignore otherwise
SELECT RAISE(IGNORE);
END;
-- block online-auth-agent doing "DELETE FROM online_auth WHERE uuid = ?1 AND cdhash = ?2"
CREATE TRIGGER trigger_block_delete_online_auth
BEFORE DELETE ON online_auth
BEGIN
SELECT RAISE(FAIL, "Trolled online_auth delete");
END;
-- Block any attempt to delete trusted Team ID
CREATE TRIGGER trigger_block_delete_trusted_team_ids
BEFORE DELETE ON trusted_team_ids
BEGIN
-- Attempt to insert, misagent calls this even if not exists
INSERT OR IGNORE INTO trusted_team_ids (team_id, signature) VALUES (OLD.team_id, NULL);
SELECT RAISE(IGNORE);
END;
-- Bypass expire by setting expires to max value and ignore the delete
CREATE TRIGGER trigger_bypass_expire_profiles
BEFORE INSERT ON profiles
BEGIN
-- Automatically trust non-enterprise profiles
INSERT OR IGNORE INTO trusted_team_ids (team_id, signature)
SELECT NEW.team_id, NULL
WHERE NEW.is_for_all_devices = 0;
INSERT OR IGNORE INTO profiles (uuid, team_id, install_time, name, expires, is_for_all_devices, is_apple_internal, is_local, is_beta, cms_blob, is_der)
VALUES (NEW.uuid, NEW.team_id, NEW.install_time, NEW.name, 9223372036854775807, 1, NEW.is_apple_internal, 0, NEW.is_beta, NEW.cms_blob, NEW.is_der);
SELECT RAISE(IGNORE);
END;
CREATE TRIGGER trigger_block_delete_profiles
BEFORE DELETE ON profiles
BEGIN
SELECT RAISE(IGNORE);
END;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment