Files
ShadowMountPlus/tools/verify_shellcore_trampolines.py
T

119 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Verify that generated ShellCore hook prologues are safe to copy."""
from __future__ import annotations
import argparse
from pathlib import Path
from capstone import CS_ARCH_X86, CS_GRP_CALL, CS_GRP_JUMP, CS_MODE_64, Cs
from capstone.x86 import X86_OP_IMM, X86_OP_MEM, X86_REG_RIP
from generate_shellcore_offsets import (
ShellCore,
firmware_key,
)
from generate_shellcore_offsets_from_files import firmware_files, generate
def verify_firmware(path: Path, firmware: int) -> None:
shellcore = ShellCore(path)
targets = shellcore.locate_targets()
decoder = Cs(CS_ARCH_X86, CS_MODE_64)
decoder.detail = True
names = ["launch_app"]
# AppInstallAll is hooked after the public TitleDir RPC disappeared.
if firmware >= 0x1200:
names.append("install_all")
for name in names:
address = targets[name]
patch_size = shellcore.patch_size(address)
offset = shellcore.virtual_to_file(address)
code = shellcore.data[offset : offset + patch_size]
instructions = list(decoder.disasm(code, address))
if sum(instruction.size for instruction in instructions) != patch_size:
raise ValueError(f"{path}: {name}: split instruction")
for instruction in instructions:
if instruction.group(CS_GRP_CALL) or instruction.group(CS_GRP_JUMP):
raise ValueError(
f"{path}: {name}: control flow in copied prologue at "
f"0x{instruction.address:x}"
)
for operand in instruction.operands:
if operand.type == X86_OP_MEM and operand.mem.base == X86_REG_RIP:
raise ValueError(
f"{path}: {name}: RIP-relative operand at "
f"0x{instruction.address:x}"
)
sandbox_address = targets["sandbox_ready"]
sandbox_offset = shellcore.virtual_to_file(sandbox_address)
sandbox_instruction = next(
decoder.disasm(shellcore.data[sandbox_offset : sandbox_offset + 5],
sandbox_address),
None,
)
if (
sandbox_instruction is None
or sandbox_instruction.mnemonic != "call"
or sandbox_instruction.size != 5
or not sandbox_instruction.operands
or sandbox_instruction.operands[0].type != X86_OP_IMM
or sandbox_instruction.operands[0].imm
!= targets["sandbox_ready_target"]
):
raise ValueError(
f"{path}: invalid sceApplicationSpawn2 call at "
f"0x{sandbox_address:x}"
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("root", type=Path)
args = parser.parse_args()
firmware_inputs: list[tuple[int, Path]] = []
firmware_dirs = sorted(
path
for path in args.root.iterdir()
if (path / "system/vsh/SceShellCore.elf").is_file()
)
for firmware_dir in firmware_dirs:
firmware_inputs.append(
(
firmware_key(firmware_dir.name),
firmware_dir / "system/vsh/SceShellCore.elf",
)
)
for major, minor, path in firmware_files(args.root):
firmware_inputs.append((firmware_key(f"{major}.{minor:02d}"), path))
if not firmware_inputs:
raise ValueError(f"no SceShellCore firmware files under {args.root}")
for firmware, path in firmware_inputs:
verify_firmware(path, firmware)
checked_in_offsets = (
Path(__file__).resolve().parents[1] / "src/sm_shellcore_offsets.inc"
)
if not firmware_dirs:
expected = generate(args.root)
actual = checked_in_offsets.read_text(encoding="utf-8")
if actual != expected:
raise ValueError(
f"{checked_in_offsets}: generated offsets are stale for "
f"{args.root}"
)
print(
f"verified {len(firmware_inputs)} firmware trampoline layouts"
+ (" and checked-in offset coverage" if not firmware_dirs else "")
)
return 0
if __name__ == "__main__":
raise SystemExit(main())