├── .gitignore ├── LICENSE ├── README.md ├── apple-location-services ├── .gitignore ├── Pipfile ├── Pipfile.lock ├── README.md ├── als_cli.py ├── core-location-als.proto ├── experiment │ ├── .gitignore │ ├── README.md │ ├── __init__.py │ ├── cell_csv.py │ ├── database_size.py │ └── nearby_cells.py └── lib │ ├── __init__.py │ ├── apple_location_service.py │ ├── core_location_als_pb2.py │ └── core_location_als_pb2.pyi ├── libqmi-ios-ext ├── README.md ├── qmi-service-apps.json ├── qmi-service-at.json ├── qmi-service-audio.json ├── qmi-service-awd.json ├── qmi-service-bsp.json ├── qmi-service-cat.json ├── qmi-service-coex.json ├── qmi-service-dfs.json ├── qmi-service-dms.json ├── qmi-service-dsd.json ├── qmi-service-elqm.json ├── qmi-service-mavims.json ├── qmi-service-mfse.json ├── qmi-service-ms.json ├── qmi-service-nas.json ├── qmi-service-p2p.json ├── qmi-service-pbm.json ├── qmi-service-pds.json ├── qmi-service-qos.json ├── qmi-service-rtp.json ├── qmi-service-sft.json ├── qmi-service-ssctl.json ├── qmi-service-uim.json ├── qmi-service-vinyl.json ├── qmi-service-vs.json ├── qmi-service-wda.json ├── qmi-service-wds.json └── qmi-service-wms.json ├── logo.svg ├── qmi-dissect ├── Pipfile ├── Pipfile.lock ├── README.md ├── agent │ ├── index.ts │ ├── send.ts │ ├── tools.ts │ └── write.ts ├── dissector │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── doc │ │ └── dtl_user_configuration.png │ ├── generate_app_json.py │ ├── generate_lua.py │ ├── import_from_ghidra.py │ ├── qmi_dissector_template.lua │ ├── qmi_services.py │ ├── qmi_structures.py │ └── research │ │ ├── .gitignore │ │ ├── ExtractCommandDriversService.java │ │ ├── ExtractDebugFunctionNames.java │ │ ├── ExtractQMIMessageIDs.java │ │ ├── README.md │ │ ├── explore_frida.ts │ │ ├── explore_frida_constructors.ts │ │ └── utils │ │ ├── CursorLocation.java │ │ ├── FunctionNames.java │ │ └── InstructionUtils.java ├── logarchive │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── package-lock.json ├── package.json ├── tsconfig.json ├── watch_cellguard.py ├── watch_frida.py ├── watch_logarchive.py ├── watch_syslog.py └── wireshark.py └── qmi-inject ├── README.md ├── agent ├── index.ts ├── receive.ts ├── send.ts └── tools.ts ├── glue.py ├── package-lock.json ├── package.json ├── scripts ├── install-frida.sh ├── install-libqmi.sh └── install-nodejs.sh └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | __pycache__ 4 | node_modules 5 | _agent.js 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BaseTrace 2 | 3 | ![BaseTrace Logo](./logo.svg) 4 | 5 | A framework of tools for researching the interface connecting the iPhone's application processor with its baseband chipset. 6 | 7 | Read more about the different baseband chips installed in iPhones 8 | on [The Apple Wiki](https://theapplewiki.com/wiki/Baseband_Device). 9 | 10 | The [CellGuard](https://github.com/seemoo-lab/CellGuard) iOS app for rogue base station detection builds upon our insights gained from applying BaseTrace. 11 | The app is stored in a dedicated GitHub repository. 12 | 13 | ## Tools 14 | 15 | ### Location Databases 16 | 17 | A location database stores approximate locations for a given Wi-Fi access point or cell of the cellular network. 18 | Read more about how different open location databases compare with Apple's database in the Catch You Cause I Can paper. 19 | 20 | #### [Apple Location Services](./apple-location-services) 21 | 22 | A standalone client for Apple's location database. 23 | 24 | ### Qualcomm Basebands 25 | 26 | iPhones with Qualcomm basebands use the **Qualcomm MSM Interface (QMI)** protocol for iOS-baseband-communication. 27 | Read more about the iPhone's baseband architecture in the Catch You Cause I Can paper. 28 | 29 | #### [libqmi iOS Extensions](./libqmi-ios-ext) 30 | 31 | iOS-specific protocol extension for the library [libqmi](https://gitlab.freedesktop.org/mobile-broadband/libqmi) used by QMI Dissect and CellGuard. 32 | 33 | #### [QMI Dissect](./qmi-dissect) 34 | 35 | A Wireshark dissector for iPhones with a Qualcomm baseboard. 36 | 37 | Works with all iPhones. 38 | 39 | #### [QMI Inject](./qmi-inject) 40 | 41 | A tool to establish a direct communication link with the iPhone's baseband, enabling you to inject custom packets and receive the baseband's responses. 42 | 43 | Requires a jailbroken iPhone. 44 | 45 | ### Intel Basebands 46 | 47 | iPhones with Intels basebands use the **Apple Remote Invocation (ARI)** protocol for iOS-baseband-communication. 48 | Read more about the protocol in Tobias' bachelor thesis and his paper ARIstoteles. 49 | 50 | #### [ARIstoles](https://github.com/seemoo-lab/aristoteles/tree/master) 51 | 52 | A Wireshark dissector for iPhones with an Intel baseband. 53 | 54 | Works with all iPhones. 55 | 56 | ## Publications 57 | 58 | - [Arnold L., Hollick M., Classen J. (2024): "Catch You Cause I Can: Busting Rogue Base Stations using CellGuard and the Apple Cell Location Database"](https://doi.org/10.1145/3678890.3678898) 59 | - [Kröll T., Kleber S., Kargl F., Hollick M., Classen J. (2021): "ARIstoteles – Dissecting Apple’s Baseband Interface"](https://doi.org/10.1007/978-3-030-88418-5_7) 60 | - [Kröll T. (2021): "ARIstoteles: iOS Baseband Interface Protocol Analysis"](https://tuprints.ulb.tu-darmstadt.de/id/eprint/19397) 61 | -------------------------------------------------------------------------------- /apple-location-services/.gitignore: -------------------------------------------------------------------------------- 1 | *.pickle 2 | *.kml 3 | swift/ 4 | -------------------------------------------------------------------------------- /apple-location-services/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | protobuf = "*" 8 | pandas = "*" 9 | numpy = "*" 10 | tqdm = "*" 11 | geopy = "*" 12 | matplotlib = "*" 13 | simplekml = "*" 14 | 15 | [dev-packages] 16 | 17 | [requires] 18 | python_version = "3" 19 | 20 | [scripts] 21 | proto = "protoc -I . --python_out=lib --pyi_out=lib core-location-als.proto" 22 | -------------------------------------------------------------------------------- /apple-location-services/README.md: -------------------------------------------------------------------------------- 1 | # Apple Location Service 2 | 3 | A library and a command line client for querying cells stored in Apple's Location Service database. 4 | 5 | ## Command Line Client 6 | 7 | You can query for nearby cells using the command line client: 8 | ```bash 9 | # Install pipenv 10 | pip3 install pipenv 11 | 12 | # Create virtual environment and sync all dependencies 13 | pipenv sync 14 | 15 | # ./als_cli.py [-h] {gsm,scdma,umts,lte,nr,cdma} country network area cell 16 | # Search for nearby UMTS / LTE cell towers of a cell tower in Germany (262) 17 | # from Vodafone (2) in area 46452 with the cell tower id 15669002. 18 | pipenv run python3 als_cli.py lte 262 2 46452 15669002 19 | # Search for nearby GSM cell towers of a cell tower in Germany (262) 20 | # from Vodafone (2) in area 566 with the cell tower id 4461. 21 | pipenv run python3 als_cli.py gsm 262 2 566 4461 22 | ``` 23 | 24 | ## Library 25 | 26 | The Python library for accessing Apple's Location Service can be found in the [`lib`](./lib) folder. 27 | 28 | ## Experiment 29 | 30 | An experiment comparing Apple's Location Service database with open cell databases can be found in the [`experiment`](./experiment) folder. 31 | 32 | ## Development 33 | 34 | For development, install the Python dependencies using pipenv as shown above. 35 | 36 | If you want to edit the Protocol Buffer file, you'll also need [Google's Protobuf](https://developers.google.com/protocol-buffers) compiler `protoc`: 37 | - Linux: `apt install protobuf-compiler` 38 | - Mac: `brew install protoc` 39 | - Windows: `scoop install protobuf` 40 | 41 | Once, you've installed everything, you can run the proto script to update the generated Python files. 42 | ```bash 43 | pipenv run proto 44 | ``` 45 | 46 | Good resources on Protocol Buffers: 47 | - https://developers.google.com/protocol-buffers/docs/proto 48 | - https://developers.google.com/protocol-buffers/docs/pythontutorial 49 | 50 | ## CellGuard Protobuf 51 | 52 | The iOS app CellGuard also depends on the Apple Location Service and therefore makes use of the protobuf file. 53 | 54 | In order to generate the related Swift file, a additional protoc plugin for Swift must be installed. 55 | More information can be found at [apple/swift-protobuf](https://github.com/apple/swift-protobuf). 56 | 57 | ```bash 58 | # Install the additional Swift protobuf plugin 59 | brew install swift-protobuf 60 | # Generate the Swift in the folder swift 61 | protoc --swift_out=swift core-location-als.proto 62 | ``` 63 | 64 | ## CellGuard Evaluation 65 | 66 | The directory [`cellguard`](./cellguard) contains a Python script to evaluate cell files exported by the CellGuard iOS app. 67 | 68 | ## References 69 | 70 | - https://github.com/zadewg/GS-LOC 71 | - https://www.appelsiini.net/2017/reverse-engineering-location-services/ 72 | -------------------------------------------------------------------------------- /apple-location-services/als_cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import random 3 | import time 4 | from datetime import datetime 5 | from pathlib import Path 6 | 7 | import simplekml 8 | 9 | import lib.apple_location_service as als 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser( 14 | prog='./als_cli.py', 15 | description='Command line interface for Apple\'s Location Service' 16 | ) 17 | parser.add_argument( 18 | 'technology', help='Cell Technology', type=str, choices=['gsm', 'scdma', 'umts', 'lte', 'nr', 'cdma']) 19 | parser.add_argument( 20 | 'country', help='Mobile Country Code (MCC)', type=int) 21 | parser.add_argument( 22 | 'network', help='Mobile Network Code (MNC) / System Identification (SID) ', type=int) 23 | parser.add_argument( 24 | 'area', help='Tracking Area Code (TAC) / Location Area Code (LAC) / Network Identification (NID)', type=int) 25 | parser.add_argument( 26 | 'cell', help='Cell ID / Basestation Identification (BSID)', type=int) 27 | 28 | parser.add_argument( 29 | '-kml', '--export-kml', type=Path, help='Export results into a KML file.') 30 | parser.add_argument( 31 | '-w', '--watch', action='store_true', help='Constantly request the cell (every 5 minutes).') 32 | 33 | args = parser.parse_args() 34 | 35 | if args.watch: 36 | while True: 37 | print(f"== {datetime.today()} ==") 38 | try: 39 | request(args) 40 | except Exception as e: 41 | print(f"Request failed: {e}") 42 | print() 43 | time.sleep(5 * 60 + random.randint(0, 15)) 44 | else: 45 | request(args) 46 | 47 | 48 | def request(args): 49 | technology = args.technology 50 | if technology == 'umts': 51 | technology = 'gsm' 52 | 53 | request_cell = als.ALSCell( 54 | technology=als.ALSTechnology[technology.upper()], 55 | country=args.country, 56 | network=args.network, 57 | cell=args.cell, 58 | area=args.area, 59 | location=None 60 | ) 61 | 62 | print(f'Requesting cell: {request_cell}') 63 | 64 | response_cells = als.AppleLocationService().request_cells(request_cell) 65 | 66 | print(f'Got {len(response_cells)} cells:') 67 | for cell in response_cells: 68 | print(cell) 69 | 70 | kml_path = args.export_kml 71 | if kml_path: 72 | if len(response_cells) > 0 and response_cells[0].is_valid(): 73 | kml = simplekml.Kml() 74 | kml.document.name = "ALS Cells Export" 75 | kml.document.description = f"Date: {datetime.today()}\n" \ 76 | f"Request Cell: {request_cell}" 77 | for cell in response_cells: 78 | cell.to_kml_point(kml) 79 | kml.save(kml_path) 80 | print(f'Created KML file {kml_path}') 81 | else: 82 | print(f'Did not export KML file as there were no cells or the first cell wasn\'t valid.') 83 | 84 | 85 | if __name__ == '__main__': 86 | main() 87 | -------------------------------------------------------------------------------- /apple-location-services/experiment/.gitignore: -------------------------------------------------------------------------------- 1 | experiment*/als-cells.csv 2 | experiment*/sample.csv 3 | experiment*/state.json 4 | experiment*/log.txt 5 | als-experiment.tar.gz -------------------------------------------------------------------------------- /apple-location-services/experiment/README.md: -------------------------------------------------------------------------------- 1 | # Nearby Cells Experiment 2 | 3 | An experiment comparing Apple's Location Service (ALS) database with open cell databases by 4 | sampling a number of cells from the open database and requesting nearby cells using ALS. 5 | It is then checked if the nearby cells are included in the open database. 6 | 7 | ## Usage 8 | 9 | First, download a CSV cell database in 10 | the [Ichnaea format](https://ichnaea.readthedocs.io/en/latest/import_export.html): 11 | 12 | * [Mozilla Location Service](https://location.services.mozilla.com/downloads) 13 | * [OpenCellid](https://opencellid.org/downloads.php) 14 | 15 | Next, choose a country which you want to research and find its mobile country code (mcc) from 16 | [Wikipedia](https://de.wikipedia.org/wiki/Mobile_Country_Code). 17 | 18 | In this example, we're picking 1000 random samples from the database `MLS-full-cell-export-2022-11-24T000000.csv` 19 | They are used as request parameters for Apple's Location Service (ALS). 20 | It answers with a number of neighbouring cells. 21 | Then, we check how much of Apple's cells are contained in the open database and print the result as a percentage. 22 | Furthermore, we group the results by mobile network operators. 23 | 24 | The whole process should take around 15 minutes. 25 | 26 | ```bash 27 | python3 ./nearby_cells.py -n 1000 -mcc 262 -d ./MLS-full-cell-export-2022-11-24T000000.csv 28 | ``` 29 | 30 | The seed parameter can be useful if you want to resume or repeat an experiment. 31 | For example if the data collection was aborted midway, or you've improved the data analysis. 32 | Ensure that the experiment folder still exists and pass its seed as a parameter. 33 | 34 | ```bash 35 | python3 ./nearby_cells.py -n 1000 -mcc 262 -d ./MLS-full-cell-export-2022-11-24T000000.csv -s 0x33 36 | ``` 37 | 38 | ## Parameters 39 | 40 | | Name | Description | References | Required | 41 | |--------|-----------------------|------------------------------------------------------------------------------------------------------------|----------| 42 | | `-mcc` | Mobile Country Code | [Wikipedia](https://de.wikipedia.org/wiki/Mobile_Country_Code) | Yes | 43 | | `-d` | Path to Cell Database | [MLS](https://location.services.mozilla.com/downloads), [OpenCellid](https://opencellid.org/downloads.php) | Yes | 44 | | `-n` | Sample Size | Number of samples which are checked | Yes | 45 | | `-s` | Sample Seed | Repeat or resume experiments | No | 46 | | `-v` | Verbose | | No | 47 | | `-gsm` | Include GSM Cells | Also consider GSM cells for sampling | No | 48 | -------------------------------------------------------------------------------- /apple-location-services/experiment/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seemoo-lab/BaseTrace/bec1a2e9731696315dec67393894561e112e0096/apple-location-services/experiment/__init__.py -------------------------------------------------------------------------------- /apple-location-services/experiment/cell_csv.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from enum import Enum 3 | from pathlib import Path 4 | 5 | import pandas 6 | 7 | 8 | class CSVRadio(Enum): 9 | """ An enum listing all possible radio techniques stored in the common cell CSV format. """ 10 | GSM = 1 11 | CDMA = 2 12 | UMTS = 3 13 | LTE = 4 14 | 15 | 16 | @dataclass 17 | class CSVCell: 18 | """ An object representing a row (cell tower) in the common cell CSV file format. """ 19 | radio: CSVRadio 20 | mcc: int 21 | net: int 22 | area: int 23 | cell: int 24 | lon: float 25 | lat: float 26 | range: int 27 | samples: int 28 | changeable: bool 29 | created: int 30 | updated: int 31 | average_signal: int 32 | 33 | @staticmethod 34 | def from_csv(csv_row: dict) -> 'CSVCell': 35 | """ 36 | Converts a row of a CSV file in form of a dictionary into a CSVCellTower object. 37 | 38 | :param csv_row: a dictionary of a CSV row 39 | :return: a CSVCellTower object with the values of the row 40 | """ 41 | # radio,mcc,net,area,cell,unit,lon,lat,range,samples,changeable,created,updated,averageSignal 42 | return CSVCell( 43 | radio=CSVRadio[csv_row['radio']], 44 | mcc=int(csv_row['mcc']), 45 | net=int(csv_row['net']), 46 | area=int(csv_row['area'] or 0), 47 | cell=int(csv_row['cell']), 48 | lon=float(csv_row['lon']), 49 | lat=float(csv_row['lat']), 50 | range=int(csv_row['range']), 51 | samples=int(csv_row['samples']), 52 | changeable=bool(csv_row['changeable']), 53 | created=int(csv_row['created']), 54 | updated=int(csv_row['updated']), 55 | average_signal=int(csv_row['averageSignal'] or 0) 56 | ) 57 | 58 | 59 | class CSVCellDatabase: 60 | """ A reader for the common cell tower CSV format. Only reading the entries for one country. """ 61 | file: Path 62 | mcc: list[int] 63 | 64 | def __init__(self, file: Path, mcc: list[int]) -> None: 65 | self.file = file 66 | self.mcc = mcc 67 | 68 | def read(self) -> pandas.DataFrame: 69 | df = pandas.read_csv(self.file) 70 | return df[df['mcc'].isin(self.mcc)] 71 | -------------------------------------------------------------------------------- /apple-location-services/experiment/database_size.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from pathlib import Path 3 | 4 | import pandas 5 | import pandas as pd 6 | from matplotlib import pyplot 7 | 8 | 9 | def main(): 10 | """ The main function parses arguments and determines the size of the given database. """ 11 | 12 | parser = argparse.ArgumentParser(description='Database Size') 13 | parser.add_argument( 14 | '-d', '--database', type=Path, required=True, 15 | help='Path to a Mozilla Location Service- / OpenCelliD-compliant database.') 16 | 17 | args = parser.parse_args() 18 | database_path: Path = args.database.absolute() 19 | 20 | if not database_path.exists() or not database_path.is_file(): 21 | print(f'The given database path {database_path} does not point to a valid file') 22 | return 23 | 24 | if not database_path.suffix == '.csv': 25 | print(f'The given database path {database_path} is no csv file') 26 | return 27 | 28 | print('Reading database...') 29 | df = pandas.read_csv(database_path) 30 | 31 | print() 32 | print('Count cells by RAT:') 33 | # https://sparkbyexamples.com/pandas/pandas-groupby-count-examples/ 34 | print(df.groupby(['radio'])['radio'].count().sort_values().to_string()) 35 | print() 36 | 37 | countries = { 38 | 'USA': [310, 311, 312, 313, 314, 315, 316], 39 | 'Germany': [262], 40 | 'South Korea': [450], 41 | 'Japan': [440], 42 | 'India': [404], 43 | } 44 | for country, mcc in countries.items(): 45 | print(f'Count cells by RAT for {country}:') 46 | print(df[df['mcc'].isin(mcc)].groupby(['radio'])['radio'].count().sort_values().to_string()) 47 | print() 48 | 49 | print() 50 | print('Count cells by MCC:') 51 | # https://sparkbyexamples.com/pandas/pandas-groupby-count-examples/ 52 | print(df.groupby(['mcc'])['mcc'].count().sort_values().to_string()) 53 | 54 | print() 55 | print('Cells per Year:') 56 | # https://stackoverflow.com/a/19231939 57 | time_series = pd.to_datetime(df['updated'], unit='s') 58 | # https://stackoverflow.com/a/29036738 59 | year_count = time_series.groupby([time_series.dt.year]).count() 60 | print(year_count) 61 | year_count.plot(kind='bar') 62 | # https://stackoverflow.com/a/46965602 63 | pyplot.show() 64 | 65 | print() 66 | print(f'Total: {len(df.index)}') 67 | 68 | 69 | if __name__ == '__main__': 70 | main() 71 | -------------------------------------------------------------------------------- /apple-location-services/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seemoo-lab/BaseTrace/bec1a2e9731696315dec67393894561e112e0096/apple-location-services/lib/__init__.py -------------------------------------------------------------------------------- /apple-location-services/lib/core_location_als_pb2.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by the protocol buffer compiler. DO NOT EDIT! 3 | # NO CHECKED-IN PROTOBUF GENCODE 4 | # source: core-location-als.proto 5 | # Protobuf Python Version: 5.28.2 6 | """Generated protocol buffer code.""" 7 | from google.protobuf import descriptor as _descriptor 8 | from google.protobuf import descriptor_pool as _descriptor_pool 9 | from google.protobuf import runtime_version as _runtime_version 10 | from google.protobuf import symbol_database as _symbol_database 11 | from google.protobuf.internal import builder as _builder 12 | _runtime_version.ValidateProtobufRuntimeVersion( 13 | _runtime_version.Domain.PUBLIC, 14 | 5, 15 | 28, 16 | 2, 17 | '', 18 | 'core-location-als.proto' 19 | ) 20 | # @@protoc_insertion_point(imports) 21 | 22 | _sym_db = _symbol_database.Default() 23 | 24 | 25 | 26 | 27 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x63ore-location-als.proto\x12\tals_proto\"\xb3\x01\n\x08Location\x12\x10\n\x08latitude\x18\x01 \x01(\x03\x12\x11\n\tlongitude\x18\x02 \x01(\x03\x12\x10\n\x08\x61\x63\x63uracy\x18\x03 \x01(\x05\x12\x14\n\x0clocationType\x18\x04 \x01(\x05\x12\x10\n\x08\x61ltitude\x18\x05 \x01(\x05\x12\x18\n\x10verticalAccuracy\x18\x06 \x01(\x05\x12\r\n\x05reach\x18\x0b \x01(\r\x12\r\n\x05score\x18\x0c \x01(\x05\x12\x10\n\x08infoMask\x18\r \x01(\r\"S\n\nWirelessAP\x12\r\n\x05macID\x18\x01 \x01(\t\x12%\n\x08location\x18\x02 \x01(\x0b\x32\x13.als_proto.Location\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\r\"{\n\x08Nr5GCell\x12\x0b\n\x03mcc\x18\x01 \x01(\x05\x12\x0b\n\x03mnc\x18\x02 \x01(\x05\x12\x0e\n\x06\x63\x65llID\x18\x03 \x01(\x03\x12\r\n\x05tacID\x18\x04 \x01(\x05\x12%\n\x08location\x18\x05 \x01(\x0b\x32\x13.als_proto.Location\x12\x0f\n\x07nrarfcn\x18\x06 \x01(\x05\"\x87\x01\n\tScdmaCell\x12\x0b\n\x03mcc\x18\x01 \x01(\x05\x12\x0b\n\x03mnc\x18\x02 \x01(\x05\x12\x0e\n\x06\x63\x65llID\x18\x03 \x01(\x05\x12\r\n\x05lacID\x18\x04 \x01(\x05\x12%\n\x08location\x18\x05 \x01(\x0b\x32\x13.als_proto.Location\x12\r\n\x05\x61rfcn\x18\x06 \x01(\x05\x12\x0b\n\x03psc\x18\x07 \x01(\x05\"\x86\x01\n\x07LteCell\x12\x0b\n\x03mcc\x18\x01 \x01(\x05\x12\x0b\n\x03mnc\x18\x02 \x01(\x05\x12\x0e\n\x06\x63\x65llID\x18\x03 \x01(\x05\x12\r\n\x05tacID\x18\x04 \x01(\x05\x12%\n\x08location\x18\x05 \x01(\x0b\x32\x13.als_proto.Location\x12\x0e\n\x06uarfcn\x18\x06 \x01(\x05\x12\x0b\n\x03pid\x18\x07 \x01(\x05\"\x85\x01\n\x07GsmCell\x12\x0b\n\x03mcc\x18\x01 \x01(\x05\x12\x0b\n\x03mnc\x18\x02 \x01(\x05\x12\x0e\n\x06\x63\x65llID\x18\x03 \x01(\x03\x12\r\n\x05lacID\x18\x04 \x01(\x05\x12%\n\x08location\x18\x05 \x01(\x0b\x32\x13.als_proto.Location\x12\r\n\x05\x61rfcn\x18\x06 \x01(\x05\x12\x0b\n\x03psc\x18\x07 \x01(\x05\"\xac\x01\n\x08\x43\x64maCell\x12\x0b\n\x03mcc\x18\x01 \x01(\x05\x12\x0b\n\x03sid\x18\x02 \x01(\x05\x12\x0b\n\x03nid\x18\x03 \x01(\x05\x12\x0c\n\x04\x62sid\x18\x04 \x01(\x05\x12%\n\x08location\x18\x05 \x01(\x0b\x32\x13.als_proto.Location\x12\x0e\n\x06zoneid\x18\x06 \x01(\x05\x12\x11\n\tbandclass\x18\x07 \x01(\x05\x12\x0f\n\x07\x63hannel\x18\x08 \x01(\x05\x12\x10\n\x08pnoffset\x18\t \x01(\x05\"\xeb\x04\n\x12\x41LSLocationRequest\x12$\n\x08gsmCells\x18\x01 \x03(\x0b\x32\x12.als_proto.GsmCell\x12*\n\x0bwirelessAPs\x18\x02 \x03(\x0b\x32\x15.als_proto.WirelessAP\x12 \n\x18numberOfSurroundingCells\x18\x03 \x01(\x05\x12 \n\x18numberOfSurroundingWifis\x18\x04 \x01(\x05\x12\x13\n\x0b\x61ppBundleId\x18\x05 \x01(\t\x12&\n\tcdmaCells\x18\x15 \x03(\x0b\x32\x13.als_proto.CdmaCell\x12*\n\rcdmaEvdoCells\x18\x16 \x03(\x0b\x32\x13.als_proto.CdmaCell\x12$\n\x1cnumberOfSurroundingCdmaCells\x18\x17 \x01(\x05\x12(\n numberOfSurroundingCdmaEvdoCells\x18\x18 \x01(\x05\x12$\n\x08lteCells\x18\x19 \x03(\x0b\x32\x12.als_proto.LteCell\x12#\n\x1bnumberOfSurroundingLteCells\x18\x1a \x01(\x05\x12(\n\nscdmaCells\x18\x1b \x03(\x0b\x32\x14.als_proto.ScdmaCell\x12%\n\x1dnumberOfSurroundingScdmaCells\x18\x1c \x01(\x05\x12&\n\tnr5GCells\x18\x1d \x03(\x0b\x32\x13.als_proto.Nr5GCell\x12$\n\x1cnumberOfSurroundingNr5GCells\x18\x1e \x01(\x05\x12\x1c\n\x14surroundingWifiBands\x18\x1f \x03(\x05\"\x87\x02\n\x13\x41LSLocationResponse\x12$\n\x08gsmCells\x18\x01 \x03(\x0b\x32\x12.als_proto.GsmCell\x12*\n\x0bwirelessAPs\x18\x02 \x03(\x0b\x32\x15.als_proto.WirelessAP\x12&\n\tcdmaCells\x18\x15 \x03(\x0b\x32\x13.als_proto.CdmaCell\x12$\n\x08lteCells\x18\x16 \x03(\x0b\x32\x12.als_proto.LteCell\x12(\n\nscdmaCells\x18\x17 \x03(\x0b\x32\x14.als_proto.ScdmaCell\x12&\n\tnr5GCells\x18\x18 \x03(\x0b\x32\x13.als_proto.Nr5GCell') 28 | 29 | _globals = globals() 30 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 31 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'core_location_als_pb2', _globals) 32 | if not _descriptor._USE_C_DESCRIPTORS: 33 | DESCRIPTOR._loaded_options = None 34 | _globals['_LOCATION']._serialized_start=39 35 | _globals['_LOCATION']._serialized_end=218 36 | _globals['_WIRELESSAP']._serialized_start=220 37 | _globals['_WIRELESSAP']._serialized_end=303 38 | _globals['_NR5GCELL']._serialized_start=305 39 | _globals['_NR5GCELL']._serialized_end=428 40 | _globals['_SCDMACELL']._serialized_start=431 41 | _globals['_SCDMACELL']._serialized_end=566 42 | _globals['_LTECELL']._serialized_start=569 43 | _globals['_LTECELL']._serialized_end=703 44 | _globals['_GSMCELL']._serialized_start=706 45 | _globals['_GSMCELL']._serialized_end=839 46 | _globals['_CDMACELL']._serialized_start=842 47 | _globals['_CDMACELL']._serialized_end=1014 48 | _globals['_ALSLOCATIONREQUEST']._serialized_start=1017 49 | _globals['_ALSLOCATIONREQUEST']._serialized_end=1636 50 | _globals['_ALSLOCATIONRESPONSE']._serialized_start=1639 51 | _globals['_ALSLOCATIONRESPONSE']._serialized_end=1902 52 | # @@protoc_insertion_point(module_scope) 53 | -------------------------------------------------------------------------------- /libqmi-ios-ext/README.md: -------------------------------------------------------------------------------- 1 | # libqmi iOS Extensions 2 | 3 | Apple-custom QMI extensions provided in the [libqmi](https://gitlab.freedesktop.org/mobile-broadband/libqmi) data format. 4 | 5 | The extensions are used by [QMI Dissect](../qmi-dissect) and [CellGuard](https://github.com/seemoo-lab/CellGuard). 6 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-apps.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "APPS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Get Wake Reason", 8 | "id": "0xD008", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "APPS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Config MHI Log Payload", 16 | "id": "0xD009", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "APPS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Set MHI Log Level", 24 | "id": "0xD00A", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "APPS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Set PCIe Log Level", 32 | "id": "0xD00B", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "APPS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Get PCIe Logs", 40 | "id": "0xD00D", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "APPS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Get PCIe Info", 48 | "id": "0xD00E", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "APPS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Get MHI Logs", 56 | "id": "0xD00c", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "APPS", 60 | "vendor": "Apple" 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-at.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "AT", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Received Command", 8 | "id": "0x26", 9 | "type": "Indication", 10 | "since": "1.33", 11 | "service": "AT", 12 | "vendor": "Apple" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-audio.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "AUDIO", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Start Tone", 8 | "id": "0x2A", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "AUDIO", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Stop Tone", 16 | "id": "0x2B", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "AUDIO", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Audio Tone Done", 24 | "id": "0x2C", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "AUDIO", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Audio Interface Status", 32 | "id": "0x2D", 33 | "type": "Indication", 34 | "since": "1.33", 35 | "service": "AUDIO", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Burst DTMF Tone", 40 | "id": "0x2E", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "AUDIO", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Get Vocoder Type", 48 | "id": "0x2F", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "AUDIO", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Vocoder Type Info", 56 | "id": "0x30", 57 | "type": "Indication", 58 | "since": "1.33", 59 | "service": "AUDIO", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Send Call Event", 64 | "id": "0x33", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "AUDIO", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Codec Change CB Register", 72 | "id": "0x33", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "AUDIO", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "CodecChangeCbRegister", 80 | "id": "0x34", 81 | "type": "Message", 82 | "service": "AUDIO", 83 | "vendor": "Apple (Ghidra)" 84 | }, 85 | { 86 | "name": "Audio Statistics", 87 | "id": "0x35", 88 | "type": "Indication", 89 | "since": "1.33", 90 | "service": "AUDIO", 91 | "vendor": "Apple" 92 | }, 93 | { 94 | "name": "Audio Distortion", 95 | "id": "0x36", 96 | "type": "Indication", 97 | "since": "1.33", 98 | "service": "AUDIO", 99 | "vendor": "Apple" 100 | } 101 | ] 102 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-awd.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "AWD", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Set Config", 8 | "id": "0x1000", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "AWD", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Set Queriable Metrics", 16 | "id": "0x1001", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "AWD", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Register Metric Indication", 24 | "id": "0x1002", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "AWD", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Metric Submission", 32 | "id": "0x1010", 33 | "type": "Indication", 34 | "since": "1.33", 35 | "service": "AWD", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Metric Submission End", 40 | "id": "0x1011", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "AWD", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Submit Trigger", 48 | "id": "0x1012", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "AWD", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "PII Location Used", 56 | "id": "0x1013", 57 | "type": "Indication", 58 | "since": "1.33", 59 | "service": "AWD", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Configuration Success", 64 | "id": "0x1020", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "AWD", 68 | "vendor": "Apple" 69 | } 70 | ] 71 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-cat.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "CAT", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Set Event Report Register", 8 | "id": "0x01", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "CAT", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Set Event Report Indication / Setup Call", 16 | "id": "0x01", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "CAT", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Send Decoded Envelope", 24 | "id": "0x25", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "CAT", 28 | "vendor": "Apple" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-coex.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "COEX", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "WWAN State", 8 | "id": "0x21", 9 | "type": "Indication", 10 | "since": "1.33", 11 | "service": "COEX", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Get WWAN State", 16 | "id": "0x22", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "COEX", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Set WLAN State", 24 | "id": "0x23", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "COEX", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Get WLAN Scan State", 32 | "id": "0x24", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "COEX", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Get WLAN Connection State", 40 | "id": "0x25", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "COEX", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Set Policy", 48 | "id": "0x26", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "COEX", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "LER Start", 56 | "id": "0x28", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "COEX", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "LER Stats", 64 | "id": "0x29", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "COEX", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "LER Start", 72 | "id": "0x2A", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "COEX", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "SINR Start", 80 | "id": "0x2B", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "COEX", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "SINR Read", 88 | "id": "0x2C", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "COEX", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "SINR Stop", 96 | "id": "0x2D", 97 | "type": "Message", 98 | "since": "1.33", 99 | "service": "COEX", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Set Band Filter Info", 104 | "id": "0x2E", 105 | "type": "Message", 106 | "since": "1.33", 107 | "service": "COEX", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Condition Fail", 112 | "id": "0x30", 113 | "type": "Indication", 114 | "since": "1.33", 115 | "service": "COEX", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Condition Success", 120 | "id": "0x31", 121 | "type": "Indication", 122 | "since": "1.33", 123 | "service": "COEX", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "Set Scan Freq Band Filter", 128 | "id": "0x3D", 129 | "type": "Message", 130 | "since": "1.33", 131 | "service": "COEX", 132 | "vendor": "Apple" 133 | }, 134 | { 135 | "name": "Get Scan Freq Band Filter", 136 | "id": "0x3E", 137 | "type": "Message", 138 | "since": "1.33", 139 | "service": "COEX", 140 | "vendor": "Apple" 141 | }, 142 | { 143 | "name": "Config Time Share Request", 144 | "id": "0x48", 145 | "type": "Message", 146 | "since": "1.33", 147 | "service": "COEX", 148 | "vendor": "Apple" 149 | }, 150 | { 151 | "name": "Set CC1 Request", 152 | "id": "0x4B", 153 | "type": "Message", 154 | "since": "1.33", 155 | "service": "COEX", 156 | "vendor": "Apple" 157 | }, 158 | { 159 | "name": "Set Ant Blocking Request", 160 | "id": "0x4C", 161 | "type": "Message", 162 | "since": "1.33", 163 | "service": "COEX", 164 | "vendor": "Apple" 165 | }, 166 | { 167 | "name": "Set WCI2 Tx Ant Map", 168 | "id": "0x4D", 169 | "type": "Message", 170 | "since": "1.33", 171 | "service": "COEX", 172 | "vendor": "Apple" 173 | }, 174 | { 175 | "name": "Set CC2 Request", 176 | "id": "0x4E", 177 | "type": "Message", 178 | "since": "1.33", 179 | "service": "COEX", 180 | "vendor": "Apple" 181 | }, 182 | { 183 | "name": "Set Laa Params Request", 184 | "id": "0x4F", 185 | "type": "Message", 186 | "since": "1.33", 187 | "service": "COEX", 188 | "vendor": "Apple" 189 | }, 190 | { 191 | "name": "Set Gnss Band Id", 192 | "id": "0x51", 193 | "type": "Message", 194 | "since": "1.33", 195 | "service": "COEX", 196 | "vendor": "Apple" 197 | }, 198 | { 199 | "name": "Send Transparent Message", 200 | "id": "0x55", 201 | "type": "Message", 202 | "since": "1.33", 203 | "service": "COEX", 204 | "vendor": "Apple" 205 | } 206 | ] 207 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-dfs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "DFS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Bind Client", 8 | "id": "0x21", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "DFS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Add Powersave Filters", 16 | "id": "0x28", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "DFS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Set Powersave Filter Mode", 24 | "id": "0x29", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "DFS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Remove All Powersave Filters", 32 | "id": "0x2D", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "DFS", 36 | "vendor": "Apple" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-dms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "DMS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Get Current PLR Info", 8 | "id": "0x53", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "DMS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Bind Subscription", 16 | "id": "0x54", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "DMS", 20 | "vendor": "Apple" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-dsd.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "DSD", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Register Indication", 8 | "id": "0x38", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "DSD", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Switch Data Subscription", 16 | "id": "0x4E", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "DSD", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Current Data Subscription Info", 24 | "id": "0x50", 25 | "type": "Indication", 26 | "since": "1.33", 27 | "service": "DSD", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Get UI Info", 32 | "id": "0x68", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "DSD", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Register UI Info Change", 40 | "id": "0x69", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "DSD", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "UI Info", 48 | "id": "0x6A", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "DSD", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Enable 5G", 56 | "id": "0xA5", 57 | "type": "Indication", 58 | "since": "1.33", 59 | "service": "DSD", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Get Bandwidth Info", 64 | "id": "0x5550", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "DSD", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "WirelessRadioManager Indication", 72 | "id": "0x5551", 73 | "type": "Indication", 74 | "since": "1.33", 75 | "service": "DSD", 76 | "vendor": "Apple" 77 | } 78 | ] 79 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-elqm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "ELQM", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Register", 8 | "id": "0x1", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "ELQM", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "BB Indication", 16 | "id": "0x2", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "ELQM", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Send Traffic Info", 24 | "id": "0x3", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "ELQM", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Query", 32 | "id": "0x4", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "ELQM", 36 | "vendor": "Apple" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-mavims.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "MAVIMS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Send Sip Packet Info", 8 | "id": "0x1", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "MAVIMS", 12 | "vendor": "Apple" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-mfse.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "MFSE", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Sync Event", 8 | "id": "0x0800", 9 | "type": "Indication", 10 | "since": "1.33", 11 | "service": "MFSE", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Enable Sync Event", 16 | "id": "0x0800", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "MFSE", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Sync No Wait", 24 | "id": "0x20", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "MFSE", 28 | "vendor": "Apple" 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-ms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "MS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Service Bind", 8 | "id": "0x3E", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "MS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Service Initialize", 16 | "id": "0x3F", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "MS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Service Initialize", 24 | "id": "0x3F", 25 | "type": "Indication", 26 | "since": "1.33", 27 | "service": "MS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Service Uninitialize", 32 | "id": "0x40", 33 | "type": "Indication", 34 | "since": "1.33", 35 | "service": "MS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Session Initialize", 40 | "id": "0x41", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "MS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Session Initialize", 48 | "id": "0x41", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "MS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Session Uninitialize", 56 | "id": "0x42", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "MS", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Session Uninitialize", 64 | "id": "0x42", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "MS", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Session Configure", 72 | "id": "0x43", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "MS", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Session Configure", 80 | "id": "0x43", 81 | "type": "Indication", 82 | "since": "1.33", 83 | "service": "MS", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Session Set Stream Direction", 88 | "id": "0x44", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "MS", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Session Set Stream Direction", 96 | "id": "0x44", 97 | "type": "Indication", 98 | "since": "1.33", 99 | "service": "MS", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Session Configure RTCP Reports", 104 | "id": "0x47", 105 | "type": "Message", 106 | "since": "1.33", 107 | "service": "MS", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Session Configure Link Monitor", 112 | "id": "0x48", 113 | "type": "Message", 114 | "since": "1.33", 115 | "service": "MS", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Session Inactivity", 120 | "id": "0x48", 121 | "type": "Indication", 122 | "since": "1.33", 123 | "service": "MS", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "Session Subscribe Notifications", 128 | "id": "0x48", 129 | "type": "Message", 130 | "since": "1.33", 131 | "service": "MS", 132 | "vendor": "Apple" 133 | }, 134 | { 135 | "name": "ServiceSubscribeNotifications", 136 | "id": "0x49", 137 | "type": "Message", 138 | "since": "1.33", 139 | "service": "MS", 140 | "vendor": "Apple (Ghidra)" 141 | }, 142 | { 143 | "name": "Service Error", 144 | "id": "0x4A", 145 | "type": "Indication", 146 | "since": "1.33", 147 | "service": "MS", 148 | "vendor": "Apple" 149 | }, 150 | { 151 | "name": "Service RCTP Reports", 152 | "id": "0x4B", 153 | "type": "Indication", 154 | "since": "1.33", 155 | "service": "MS", 156 | "vendor": "Apple" 157 | }, 158 | { 159 | "name": "Sessions Send DTMF", 160 | "id": "0x4C", 161 | "type": "Message", 162 | "since": "1.33", 163 | "service": "MS", 164 | "vendor": "Apple" 165 | }, 166 | { 167 | "name": "Service Uninitialize All Sessions", 168 | "id": "0x4F", 169 | "type": "Message", 170 | "since": "1.33", 171 | "service": "MS", 172 | "vendor": "Apple" 173 | }, 174 | { 175 | "name": "Service Uninitialize All Sessions", 176 | "id": "0x4F", 177 | "type": "Indication", 178 | "since": "1.33", 179 | "service": "MS", 180 | "vendor": "Apple" 181 | } 182 | ] 183 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-nas.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "NAS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "System Info (?)", 8 | "id": "0x34", 9 | "type": "Indication", 10 | "since": "1.33", 11 | "service": "NAS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Get PLMN Mode Bit", 16 | "id": "0x3B", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "NAS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "PLMN Mode Bit", 24 | "id": "0x3C", 25 | "type": "Indication", 26 | "since": "1.33", 27 | "service": "NAS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Bind Subscription", 32 | "id": "0x45", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "NAS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Subscription Info", 40 | "id": "0x48", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "NAS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Current PLMN Name", 48 | "id": "0x61", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "NAS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Get eMBMS Status", 56 | "id": "0x63", 57 | "type": "Indication", 58 | "since": "1.33", 59 | "service": "NAS", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "eMBMS Status", 64 | "id": "0x64", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "NAS", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Config Signal Info 2", 72 | "id": "0x6C", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "NAS", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Update IMS Status", 80 | "id": "0x72", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "NAS", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Access Barring Handler", 88 | "id": "0x72", 89 | "type": "Indication", 90 | "since": "1.33", 91 | "service": "NAS", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Network Registration (IMS Preference Update)", 96 | "id": "0x74", 97 | "type": "Indication", 98 | "since": "1.33", 99 | "service": "NAS", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Enhanced 911 state ready", 104 | "id": "0x7B", 105 | "type": "Indication", 106 | "since": "1.33", 107 | "service": "NAS", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Get Subscription Info", 112 | "id": "0x7C", 113 | "type": "Message", 114 | "since": "1.33", 115 | "service": "NAS", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Config eMBMS", 120 | "id": "0x81", 121 | "type": "Message", 122 | "since": "1.33", 123 | "service": "NAS", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "Network List (Incremental Scan)", 128 | "id": "0x85", 129 | "type": "Indication", 130 | "since": "1.33", 131 | "service": "NAS", 132 | "vendor": "Apple" 133 | }, 134 | { 135 | "name": "Perform Incremental Network Scan", 136 | "id": "0x85", 137 | "type": "Message", 138 | "since": "1.33", 139 | "service": "NAS", 140 | "vendor": "Apple" 141 | }, 142 | { 143 | "name": "Subscription Change", 144 | "id": "0x86", 145 | "type": "Indication", 146 | "since": "1.33", 147 | "service": "NAS", 148 | "vendor": "Apple" 149 | }, 150 | { 151 | "name": "SSAC Info", 152 | "id": "0x90", 153 | "type": "Indication", 154 | "since": "1.33", 155 | "service": "NAS", 156 | "vendor": "Apple" 157 | }, 158 | { 159 | "name": "Get SSAC Info", 160 | "id": "0x91", 161 | "type": "Message", 162 | "since": "1.33", 163 | "service": "NAS", 164 | "vendor": "Apple" 165 | }, 166 | { 167 | "name": "T3402 Timer Changed", 168 | "id": "0x93", 169 | "type": "Indication", 170 | "since": "1.33", 171 | "service": "NAS", 172 | "vendor": "Apple" 173 | }, 174 | { 175 | "name": "Get ACB Info", 176 | "id": "0x94", 177 | "type": "Message", 178 | "since": "1.33", 179 | "service": "NAS", 180 | "vendor": "Apple" 181 | }, 182 | { 183 | "name": "ACB Info", 184 | "id": "0x94", 185 | "type": "Indication", 186 | "since": "1.33", 187 | "service": "NAS", 188 | "vendor": "Apple" 189 | }, 190 | { 191 | "name": "Set Geo MCCs", 192 | "id": "0x99", 193 | "type": "Message", 194 | "since": "1.33", 195 | "service": "NAS", 196 | "vendor": "Apple" 197 | }, 198 | { 199 | "name": "Call State Notification", 200 | "id": "0xA1", 201 | "type": "Message", 202 | "since": "1.33", 203 | "service": "NAS", 204 | "vendor": "Apple" 205 | }, 206 | { 207 | "name": "MM Tel Response (IP Telephony)", 208 | "id": "0xE4", 209 | "type": "Indication", 210 | "since": "1.33", 211 | "service": "NAS", 212 | "vendor": "Apple" 213 | }, 214 | { 215 | "name": "UAC Alleviation (IP Telephony)", 216 | "id": "0xE5", 217 | "type": "Indication", 218 | "since": "1.33", 219 | "service": "NAS", 220 | "vendor": "Apple" 221 | }, 222 | { 223 | "name": "Set IMS Proc Type (IP Telephony)", 224 | "id": "0xE6", 225 | "type": "Message", 226 | "since": "1.33", 227 | "service": "NAS", 228 | "vendor": "Apple" 229 | }, 230 | { 231 | "name": "Disable ICCID", 232 | "id": "0xEB", 233 | "type": "Message", 234 | "since": "1.33", 235 | "service": "NAS", 236 | "vendor": "Apple" 237 | }, 238 | { 239 | "name": "Get NR Disable Status", 240 | "id": "0x0110", 241 | "type": "Message", 242 | "since": "1.33", 243 | "service": "NAS", 244 | "vendor": "Apple" 245 | }, 246 | { 247 | "name": "NR Disable Status Changed", 248 | "id": "0x0111", 249 | "type": "Indication", 250 | "since": "1.33", 251 | "service": "NAS", 252 | "vendor": "Apple" 253 | }, 254 | { 255 | "name": "Get Enhanced 911 Sub", 256 | "id": "0x1002", 257 | "type": "Message", 258 | "since": "1.33", 259 | "service": "NAS", 260 | "vendor": "Apple" 261 | }, 262 | { 263 | "name": "Get Cell Info", 264 | "id": "0x5556", 265 | "type": "Message", 266 | "since": "1.33", 267 | "service": "NAS", 268 | "vendor": "Apple" 269 | }, 270 | { 271 | "name": "Cell Info", 272 | "id": "0x5556", 273 | "type": "Indication", 274 | "since": "1.33", 275 | "service": "NAS", 276 | "vendor": "Apple" 277 | }, 278 | { 279 | "name": "Send WiFi Network Info", 280 | "id": "0x5562", 281 | "type": "Message", 282 | "since": "1.33", 283 | "service": "NAS", 284 | "vendor": "Apple" 285 | }, 286 | { 287 | "name": "Registration Hints", 288 | "id": "0x5557", 289 | "type": "Message", 290 | "since": "1.33", 291 | "service": "NAS", 292 | "vendor": "Apple" 293 | }, 294 | { 295 | "name": "Set Frequency Report Mode", 296 | "id": "0x555B", 297 | "type": "Message", 298 | "since": "1.33", 299 | "service": "NAS", 300 | "vendor": "Apple" 301 | }, 302 | { 303 | "name": "Frequency Report", 304 | "id": "0x555B", 305 | "type": "Indication", 306 | "since": "1.33", 307 | "service": "NAS", 308 | "vendor": "Apple" 309 | }, 310 | { 311 | "name": "Get Provisioning Mode", 312 | "id": "0x555E", 313 | "type": "Message", 314 | "since": "1.33", 315 | "service": "NAS", 316 | "vendor": "Apple" 317 | }, 318 | { 319 | "name": "LAPS Fetch", 320 | "id": "0x555F", 321 | "type": "Indication", 322 | "since": "1.33", 323 | "service": "NAS", 324 | "vendor": "Apple" 325 | }, 326 | { 327 | "name": "PLMN Mode", 328 | "id": "0x5560", 329 | "type": "Indication", 330 | "since": "1.33", 331 | "service": "NAS", 332 | "vendor": "Apple" 333 | }, 334 | { 335 | "name": "Trigger Closed Subscriber Group", 336 | "id": "0x5561", 337 | "type": "Message", 338 | "since": "1.33", 339 | "service": "NAS", 340 | "vendor": "Apple" 341 | }, 342 | { 343 | "name": "Call / Lock / AV State", 344 | "id": "0x5568", 345 | "type": "Message", 346 | "since": "1.33", 347 | "service": "NAS", 348 | "vendor": "Apple" 349 | }, 350 | { 351 | "name": "Geo PLMN", 352 | "id": "0x5572", 353 | "type": "Indication", 354 | "since": "1.33", 355 | "service": "NAS", 356 | "vendor": "Apple" 357 | } 358 | ] 359 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-p2p.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "P2P", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Proximity Notification", 8 | "id": "0x01", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "P2P", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "P2P Message", 16 | "id": "0x02", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "P2P", 20 | "vendor": "Apple" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-pbm.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "PBM", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Write Entry", 8 | "id": "0x05", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "PBM", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Record Update", 16 | "id": "0x09", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "PBM", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Refresh", 24 | "id": "0x0A", 25 | "type": "Indication", 26 | "since": "1.33", 27 | "service": "PBM", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Ready", 32 | "id": "0x0B", 33 | "type": "Indication", 34 | "since": "1.33", 35 | "service": "PBM", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Emergency List", 40 | "id": "0x0C", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "PBM", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Phonebook Init Done", 48 | "id": "0x0D", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "PBM", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Get PB State", 56 | "id": "0x11", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "PBM", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Bind Subscription", 64 | "id": "0x1A", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "PBM", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Read Record Unencoded", 72 | "id": "0x22", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "PBM", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Read Record", 80 | "id": "0x22", 81 | "type": "Indication", 82 | "since": "1.33", 83 | "service": "PBM", 84 | "vendor": "Apple" 85 | } 86 | ] 87 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-pds.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "PDS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Set NMEA Config", 8 | "id": "0x27", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "PDS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Set Default Tracking Session", 16 | "id": "0x2A", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "PDS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Force XTRA Download", 24 | "id": "0x2D", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "PDS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Set Auto Tracking State", 32 | "id": "0x31", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "PDS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Reset GPS Service State", 40 | "id": "0x34", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "PDS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Inject XTRA Data", 48 | "id": "0x37", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "PDS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Inject Position Data", 56 | "id": "0x38", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "PDS", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "NI Response", 64 | "id": "0x3C", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "PDS", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Inject Absolute Time Reference", 72 | "id": "0x3D", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "PDS", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Set DPO Config", 80 | "id": "0x40", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "PDS", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Force Receiver Off", 88 | "id": "0x49", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "PDS", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Inject Time Sync Data", 96 | "id": "0x53", 97 | "type": "Message", 98 | "since": "1.33", 99 | "service": "PDS", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Set Navigation Config", 104 | "id": "0x57", 105 | "type": "Message", 106 | "since": "1.33", 107 | "service": "PDS", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Set Cell Database Control Mask", 112 | "id": "0x5F", 113 | "type": "Message", 114 | "since": "1.33", 115 | "service": "PDS", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Set Inject Motion Data", 120 | "id": "0x61", 121 | "type": "Message", 122 | "since": "1.33", 123 | "service": "PDS", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "L1 Rf Config", 128 | "id": "0x65", 129 | "type": "Message", 130 | "since": "1.33", 131 | "service": "PDS", 132 | "vendor": "Apple" 133 | }, 134 | { 135 | "name": "Inject Direction Of Travel", 136 | "id": "0x66", 137 | "type": "Message", 138 | "since": "1.33", 139 | "service": "PDS", 140 | "vendor": "Apple" 141 | }, 142 | { 143 | "name": "Inject Supl Cert", 144 | "id": "0x67", 145 | "type": "Message", 146 | "since": "1.33", 147 | "service": "PDS", 148 | "vendor": "Apple" 149 | }, 150 | { 151 | "name": "Delete Supl Cert", 152 | "id": "0x68", 153 | "type": "Message", 154 | "since": "1.33", 155 | "service": "PDS", 156 | "vendor": "Apple" 157 | }, 158 | { 159 | "name": "Set User Plane Position Modes", 160 | "id": "0x74", 161 | "type": "Message", 162 | "since": "1.33", 163 | "service": "PDS", 164 | "vendor": "Apple" 165 | }, 166 | { 167 | "name": "Send APN Config", 168 | "id": "0x78", 169 | "type": "Message", 170 | "since": "1.33", 171 | "service": "PDS", 172 | "vendor": "Apple" 173 | }, 174 | { 175 | "name": "Get LPP Config", 176 | "id": "0x7D", 177 | "type": "Message", 178 | "since": "1.33", 179 | "service": "PDS", 180 | "vendor": "Apple" 181 | }, 182 | { 183 | "name": "Set LPP Config", 184 | "id": "0x7E", 185 | "type": "Message", 186 | "since": "1.33", 187 | "service": "PDS", 188 | "vendor": "Apple" 189 | }, 190 | { 191 | "name": "Supl UDP Port Config", 192 | "id": "0x83", 193 | "type": "Message", 194 | "since": "1.33", 195 | "service": "PDS", 196 | "vendor": "Apple" 197 | }, 198 | { 199 | "name": "Cancel Ongoing Session", 200 | "id": "0x84", 201 | "type": "Message", 202 | "since": "1.33", 203 | "service": "PDS", 204 | "vendor": "Apple" 205 | }, 206 | { 207 | "name": "Set Time Transfer Config", 208 | "id": "0x85", 209 | "type": "Message", 210 | "since": "1.33", 211 | "service": "PDS", 212 | "vendor": "Apple" 213 | }, 214 | { 215 | "name": "Send Client Pulse Time", 216 | "id": "0x87", 217 | "type": "Message", 218 | "since": "1.33", 219 | "service": "PDS", 220 | "vendor": "Apple" 221 | }, 222 | { 223 | "name": "Set Emergency Support Config", 224 | "id": "0x8A", 225 | "type": "Message", 226 | "since": "1.33", 227 | "service": "PDS", 228 | "vendor": "Apple" 229 | }, 230 | { 231 | "name": "Set Black List", 232 | "id": "0x8B", 233 | "type": "Message", 234 | "since": "1.33", 235 | "service": "PDS", 236 | "vendor": "Apple" 237 | }, 238 | { 239 | "name": "Inject Speed Data", 240 | "id": "0x8E", 241 | "type": "Message", 242 | "since": "1.33", 243 | "service": "PDS", 244 | "vendor": "Apple" 245 | }, 246 | { 247 | "name": "Set OTDOA Config", 248 | "id": "0x93", 249 | "type": "Message", 250 | "since": "1.33", 251 | "service": "PDS", 252 | "vendor": "Apple" 253 | }, 254 | { 255 | "name": "Set Signal Environment Config", 256 | "id": "0x96", 257 | "type": "Message", 258 | "since": "1.33", 259 | "service": "PDS", 260 | "vendor": "Apple" 261 | }, 262 | { 263 | "name": "Inject Speed Inequality Constraint", 264 | "id": "0x97", 265 | "type": "Message", 266 | "since": "1.33", 267 | "service": "PDS", 268 | "vendor": "Apple" 269 | }, 270 | { 271 | "name": "Set XO Calibration Config", 272 | "id": "0x98", 273 | "type": "Message", 274 | "since": "1.33", 275 | "service": "PDS", 276 | "vendor": "Apple" 277 | }, 278 | { 279 | "name": "Set Spoofing Report Config", 280 | "id": "0x9F", 281 | "type": "Message", 282 | "since": "1.33", 283 | "service": "PDS", 284 | "vendor": "Apple" 285 | }, 286 | { 287 | "name": "Set GNSS Engine Monitor Config", 288 | "id": "0xA1", 289 | "type": "Message", 290 | "since": "1.33", 291 | "service": "PDS", 292 | "vendor": "Apple" 293 | }, 294 | { 295 | "name": "Set GNSS Reporting Config", 296 | "id": "0xA3", 297 | "type": "Message", 298 | "since": "1.33", 299 | "service": "PDS", 300 | "vendor": "Apple" 301 | }, 302 | { 303 | "name": "Set Emergency Mode Lockout Config", 304 | "id": "0xA9", 305 | "type": "Message", 306 | "since": "1.33", 307 | "service": "PDS", 308 | "vendor": "Apple" 309 | }, 310 | { 311 | "name": "Multiband Config", 312 | "id": "0xAD", 313 | "type": "Message", 314 | "since": "1.33", 315 | "service": "PDS", 316 | "vendor": "Apple" 317 | }, 318 | { 319 | "name": "Register PDS Indications", 320 | "id": "0xB1", 321 | "type": "Indication", 322 | "since": "1.33", 323 | "service": "PDS", 324 | "vendor": "Apple" 325 | }, 326 | { 327 | "name": "SV Use In Fix Report / Set PDO Status Report", 328 | "id": "0x5557", 329 | "type": "Message", 330 | "since": "1.33", 331 | "service": "PDS", 332 | "vendor": "Apple" 333 | } 334 | ] 335 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-qos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "QOS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Set Event Report", 8 | "id": "0x01", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "QOS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Event Report", 16 | "id": "0x01", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "QOS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Request QoS", 24 | "id": "0x20", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "QOS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Release QoS", 32 | "id": "0x21", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "QOS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Bind Data Port", 40 | "id": "0x2B", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "QOS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Indication Register", 48 | "id": "0x2F", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "QOS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Global QoS Flow", 56 | "id": "0x31", 57 | "type": "Indication", 58 | "since": "1.33", 59 | "service": "QOS", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Get QoS Info", 64 | "id": "0x33", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "QOS", 68 | "vendor": "Apple" 69 | } 70 | ] 71 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-rtp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "RTP", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Session Initialize", 8 | "id": "0x22", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "RTP", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Session Initialize", 16 | "id": "0x22", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "RTP", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Session Uninitialize", 24 | "id": "0x23", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "RTP", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Session Uninitialize", 32 | "id": "0x23", 33 | "type": "Indication", 34 | "since": "1.33", 35 | "service": "RTP", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Session Uninitialize", 40 | "id": "0x24", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "RTP", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Session Uninitialize", 48 | "id": "0x24", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "RTP", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Session Start", 56 | "id": "0x25", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "RTP", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Session Start", 64 | "id": "0x25", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "RTP", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Session Start", 72 | "id": "0x26", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "RTP", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Session Start", 80 | "id": "0x26", 81 | "type": "Indication", 82 | "since": "1.33", 83 | "service": "RTP", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Session Pause", 88 | "id": "0x27", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "RTP", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Session Resume", 96 | "id": "0x27", 97 | "type": "Message", 98 | "since": "1.33", 99 | "service": "RTP", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "SessionResume", 104 | "id": "0x28", 105 | "type": "Message", 106 | "service": "RTP", 107 | "vendor": "Apple (Ghidra)" 108 | }, 109 | { 110 | "name": "Session Configure RTCP", 111 | "id": "0x29", 112 | "type": "Message", 113 | "since": "1.33", 114 | "service": "RTP", 115 | "vendor": "Apple" 116 | }, 117 | { 118 | "name": "Session Configure RTCP", 119 | "id": "0x29", 120 | "type": "Indication", 121 | "since": "1.33", 122 | "service": "RTP", 123 | "vendor": "Apple" 124 | }, 125 | { 126 | "name": "Session Configure RTP Link Monitor", 127 | "id": "0x2A", 128 | "type": "Message", 129 | "since": "1.33", 130 | "service": "RTP", 131 | "vendor": "Apple" 132 | }, 133 | { 134 | "name": "Session Configure RTP Link Monitor", 135 | "id": "0x2A", 136 | "type": "Indication", 137 | "since": "1.33", 138 | "service": "RTP", 139 | "vendor": "Apple" 140 | }, 141 | { 142 | "name": "Session Configure RTCP Link Monitor", 143 | "id": "0x2B", 144 | "type": "Message", 145 | "since": "1.33", 146 | "service": "RTP", 147 | "vendor": "Apple" 148 | }, 149 | { 150 | "name": "Session Configure RTCP Link Monitor", 151 | "id": "0x2B", 152 | "type": "Indication", 153 | "since": "1.33", 154 | "service": "RTP", 155 | "vendor": "Apple" 156 | }, 157 | { 158 | "name": "Session Send DTMF", 159 | "id": "0x2C", 160 | "type": "Message", 161 | "since": "1.33", 162 | "service": "RTP", 163 | "vendor": "Apple" 164 | }, 165 | { 166 | "name": "Register Services", 167 | "id": "0x30", 168 | "type": "Message", 169 | "since": "1.33", 170 | "service": "RTP", 171 | "vendor": "Apple" 172 | }, 173 | { 174 | "name": "Session Get Status", 175 | "id": "0x31", 176 | "type": "Indication", 177 | "since": "1.33", 178 | "service": "RTP", 179 | "vendor": "Apple" 180 | }, 181 | { 182 | "name": "Session Error", 183 | "id": "0x32", 184 | "type": "Indication", 185 | "since": "1.33", 186 | "service": "RTP", 187 | "vendor": "Apple" 188 | }, 189 | { 190 | "name": "Send Audio Call Event", 191 | "id": "0x33", 192 | "type": "Message", 193 | "since": "1.33", 194 | "service": "RTP", 195 | "vendor": "Apple" 196 | }, 197 | { 198 | "name": "RTCP Reports", 199 | "id": "0x33", 200 | "type": "Indication", 201 | "since": "1.33", 202 | "service": "RTP", 203 | "vendor": "Apple" 204 | }, 205 | { 206 | "name": "Register Audio Codec Change CB", 207 | "id": "0x33", 208 | "type": "Message", 209 | "since": "1.33", 210 | "service": "RTP", 211 | "vendor": "Apple" 212 | }, 213 | { 214 | "name": "Uninitialize All Sessions", 215 | "id": "0x3D", 216 | "type": "Message", 217 | "since": "1.33", 218 | "service": "RTP", 219 | "vendor": "Apple" 220 | }, 221 | { 222 | "name": "Uninitialize All Sessions", 223 | "id": "0x3D", 224 | "type": "Indication", 225 | "since": "1.33", 226 | "service": "RTP", 227 | "vendor": "Apple" 228 | }, 229 | { 230 | "name": "Service Bind", 231 | "id": "0x3E", 232 | "type": "Message", 233 | "since": "1.33", 234 | "service": "RTP", 235 | "vendor": "Apple" 236 | }, 237 | { 238 | "name": "Service Initialize", 239 | "id": "0x3E", 240 | "type": "Message", 241 | "since": "1.33", 242 | "service": "RTP", 243 | "vendor": "Apple" 244 | }, 245 | { 246 | "name": "Session Initialize", 247 | "id": "0x41", 248 | "type": "Message", 249 | "since": "1.33", 250 | "service": "RTP", 251 | "vendor": "Apple" 252 | }, 253 | { 254 | "name": "Session Uninitialize", 255 | "id": "0x42", 256 | "type": "Message", 257 | "since": "1.33", 258 | "service": "RTP", 259 | "vendor": "Apple" 260 | }, 261 | { 262 | "name": "Session Set Stream Direction", 263 | "id": "0x44", 264 | "type": "Message", 265 | "since": "1.33", 266 | "service": "RTP", 267 | "vendor": "Apple" 268 | }, 269 | { 270 | "name": "Configure RTCP Reports", 271 | "id": "0x47", 272 | "type": "Message", 273 | "since": "1.33", 274 | "service": "RTP", 275 | "vendor": "Apple" 276 | }, 277 | { 278 | "name": "Configure RTP / RTCP Link Monitor", 279 | "id": "0x48", 280 | "type": "Message", 281 | "since": "1.33", 282 | "service": "RTP", 283 | "vendor": "Apple" 284 | }, 285 | { 286 | "name": "Subscribe Notifications", 287 | "id": "0x49", 288 | "type": "Message", 289 | "since": "1.33", 290 | "service": "RTP", 291 | "vendor": "Apple" 292 | }, 293 | { 294 | "name": "Send DTMF", 295 | "id": "0x4C", 296 | "type": "Message", 297 | "since": "1.33", 298 | "service": "RTP", 299 | "vendor": "Apple" 300 | }, 301 | { 302 | "name": "Uninitialize All Sessions", 303 | "id": "0x4F", 304 | "type": "Message", 305 | "since": "1.33", 306 | "service": "RTP", 307 | "vendor": "Apple" 308 | } 309 | ] 310 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-sft.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "SFT", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Activation", 8 | "id": "0x1001", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "SFT", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Deactivate", 16 | "id": "0x1002", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "SFT", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Deactivation Complete", 24 | "id": "0x1003", 25 | "type": "Indication", 26 | "since": "1.33", 27 | "service": "SFT", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Suspend", 32 | "id": "0x1004", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "SFT", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Request State Change", 40 | "id": "0x1005", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "SFT", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Suspend", 48 | "id": "0x1006", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "SFT", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Security Config", 56 | "id": "0x1101", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "SFT", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Security Config Update Needed", 64 | "id": "0x1102", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "SFT", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Security Config Usage", 72 | "id": "0x1103", 73 | "type": "Indication", 74 | "since": "1.33", 75 | "service": "SFT", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Request Service Info", 80 | "id": "0x1201", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "SFT", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Service Info", 88 | "id": "0x1202", 89 | "type": "Indication", 90 | "since": "1.33", 91 | "service": "SFT", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Send Message", 96 | "id": "0x1301", 97 | "type": "Message", 98 | "since": "1.33", 99 | "service": "SFT", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Message TX Status", 104 | "id": "0x1302", 105 | "type": "Indication", 106 | "since": "1.33", 107 | "service": "SFT", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Message RX", 112 | "id": "0x1303", 113 | "type": "Message", 114 | "since": "1.33", 115 | "service": "SFT", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Ack Received Message", 120 | "id": "0x1303", 121 | "type": "Message", 122 | "since": "1.33", 123 | "service": "SFT", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "S4 Config Segement", 128 | "id": "0x1401", 129 | "type": "Message", 130 | "since": "1.33", 131 | "service": "SFT", 132 | "vendor": "Apple" 133 | }, 134 | { 135 | "name": "Initiate Registration", 136 | "id": "0x1501", 137 | "type": "Message", 138 | "since": "1.33", 139 | "service": "SFT", 140 | "vendor": "Apple" 141 | }, 142 | { 143 | "name": "Concurrency Config", 144 | "id": "0x2001", 145 | "type": "Message", 146 | "since": "1.33", 147 | "service": "SFT", 148 | "vendor": "Apple" 149 | }, 150 | { 151 | "name": "GPS Data Update", 152 | "id": "0x2101", 153 | "type": "Message", 154 | "since": "1.33", 155 | "service": "SFT", 156 | "vendor": "Apple" 157 | }, 158 | { 159 | "name": "Get Capabilities", 160 | "id": "0x2202", 161 | "type": "Message", 162 | "since": "1.33", 163 | "service": "SFT", 164 | "vendor": "Apple" 165 | }, 166 | { 167 | "name": "Update Orientation", 168 | "id": "0x2204", 169 | "type": "Message", 170 | "since": "1.33", 171 | "service": "SFT", 172 | "vendor": "Apple" 173 | }, 174 | { 175 | "name": "Get Cellular Tx Defer Timer", 176 | "id": "0x2205", 177 | "type": "Message", 178 | "since": "1.33", 179 | "service": "SFT", 180 | "vendor": "Apple" 181 | }, 182 | { 183 | "name": "Send Diag Cmd", 184 | "id": "0x3001", 185 | "type": "Message", 186 | "since": "1.33", 187 | "service": "SFT", 188 | "vendor": "Apple" 189 | }, 190 | { 191 | "name": "Send File", 192 | "id": "0xA000", 193 | "type": "Message", 194 | "since": "1.33", 195 | "service": "SFT", 196 | "vendor": "Apple" 197 | }, 198 | { 199 | "name": "File Transfer Status", 200 | "id": "0xA001", 201 | "type": "Indication", 202 | "since": "1.33", 203 | "service": "SFT", 204 | "vendor": "Apple" 205 | } 206 | ] 207 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-ssctl.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "SSCTL", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Get Failure Reason", 8 | "id": "0x22", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "SSCTL", 12 | "vendor": "Apple" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-uim.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "UIM", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Write Transparent", 8 | "id": "0x22", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "UIM", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Authenticate", 16 | "id": "0x34", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "UIM", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Send APDU", 24 | "id": "0x3B", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "UIM", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Logical Channel", 32 | "id": "0x3F", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "UIM", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Card Prov Status Change", 40 | "id": "0x5556", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "UIM", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Card Prov Status Change", 48 | "id": "0x5556", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "UIM", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Select UIM For Single Sub Config", 56 | "id": "0x5557", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "UIM", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Card Debounce Status", 64 | "id": "0x5558", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "UIM", 68 | "vendor": "Apple" 69 | } 70 | ] 71 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-vinyl.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "VINYL", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Vinyl Message", 8 | "id": "0x1000", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "VINYL", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Vinyl Info", 16 | "id": "0x1001", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "VINYL", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Tape Message", 24 | "id": "0x1002", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "VINYL", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Tape Info", 32 | "id": "0x1003", 33 | "type": "Indication", 34 | "since": "1.33", 35 | "service": "VINYL", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Tape Get EID Message", 40 | "id": "0x1004", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "VINYL", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Tape Get Device Capabilities Message", 48 | "id": "0x1005", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "VINYL", 52 | "vendor": "Apple" 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-vs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "VOICE", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "OTASP Status", 8 | "id": "0x25", 9 | "type": "Indication", 10 | "since": "1.33", 11 | "service": "VOICE", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Voice Info Rec", 16 | "id": "0x26", 17 | "type": "Indication", 18 | "since": "1.33", 19 | "service": "VOICE", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Set SUPS Service", 24 | "id": "0x33", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "VOICE", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Fetch Call Waiting", 32 | "id": "0x34", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "VOICE", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Fetch Call Barring", 40 | "id": "0x35", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "VOICE", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Set Call Barring Password", 48 | "id": "0x35", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "VOICE", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Fetch CLIPEN", 56 | "id": "0x36", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "VOICE", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Fetch Call Forwarding", 64 | "id": "0x38", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "VOICE", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Bind Subscription", 72 | "id": "0x44", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "VOICE", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Fetch COLREN", 80 | "id": "0x4C", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "VOICE", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Fetch CNAPEN", 88 | "id": "0x4C", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "VOICE", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Voice Handover", 96 | "id": "0x54", 97 | "type": "Indication", 98 | "since": "1.33", 99 | "service": "VOICE", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Call Ready", 104 | "id": "0xE3", 105 | "type": "Indication", 106 | "since": "1.33", 107 | "service": "VOICE", 108 | "vendor": "Apple" 109 | } 110 | ] 111 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-wda.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "WDA", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Set QMAP Settings", 8 | "id": "0x2B", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "WDA", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Get QMAP Settings", 16 | "id": "0x2C", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "WDA", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Set Power Save Config", 24 | "id": "0x2D", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "WDA", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Set Capability", 32 | "id": "0x30", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "WDA", 36 | "vendor": "Apple" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-wds.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "WDS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Read MIP Profile", 8 | "id": "0x3E", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "WDS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Get Current Data System Status", 16 | "id": "0x6B", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "WDS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "3GPP Parameters", 24 | "id": "0xA5", 25 | "type": "Indication", 26 | "since": "1.33", 27 | "service": "WDS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Update VoLTE Data Call Type", 32 | "id": "0xB1", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "WDS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "LTE Attach Params", 40 | "id": "0xC6", 41 | "type": "Indication", 42 | "since": "1.33", 43 | "service": "WDS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Reset And Modify Profile Settings", 48 | "id": "0xC7", 49 | "type": "Message", 50 | "since": "1.33", 51 | "service": "WDS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "Delete All Profiles", 56 | "id": "0xCD", 57 | "type": "Message", 58 | "since": "1.33", 59 | "service": "WDS", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Delete All Profiles", 64 | "id": "0xCD", 65 | "type": "Indication", 66 | "since": "1.33", 67 | "service": "WDS", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Shutdown ANBR Handle", 72 | "id": "0xE6", 73 | "type": "Message", 74 | "since": "1.33", 75 | "service": "WDS", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Free PDU Session ID", 80 | "id": "0x0108", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "WDS", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Set ANBR Filter", 88 | "id": "0x0128", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "WDS", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Link Stats", 96 | "id": "0x555B", 97 | "type": "Message", 98 | "since": "1.33", 99 | "service": "WDS", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Enable High Priority Data", 104 | "id": "0x555D", 105 | "type": "Message", 106 | "since": "1.33", 107 | "service": "WDS", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Drop IP Packets", 112 | "id": "0x555E", 113 | "type": "Message", 114 | "since": "1.33", 115 | "service": "WDS", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Dad Complete", 120 | "id": "0x5561", 121 | "type": "Indication", 122 | "since": "1.33", 123 | "service": "WDS", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "FD Backoff Time", 128 | "id": "0x5563", 129 | "type": "Indication", 130 | "since": "1.33", 131 | "service": "WDS", 132 | "vendor": "Apple" 133 | }, 134 | { 135 | "name": "Push VOIP App Info", 136 | "id": "0x5565", 137 | "type": "Message", 138 | "since": "1.33", 139 | "service": "WDS", 140 | "vendor": "Apple" 141 | }, 142 | { 143 | "name": "Update Cellular Data Status Info", 144 | "id": "0x5566", 145 | "type": "Message", 146 | "since": "1.33", 147 | "service": "WDS", 148 | "vendor": "Apple" 149 | }, 150 | { 151 | "name": "Notify Cellular Data Switching Allowed", 152 | "id": "0x5568", 153 | "type": "Message", 154 | "since": "1.33", 155 | "service": "WDS", 156 | "vendor": "Apple" 157 | }, 158 | { 159 | "name": "Update Current Data SIM", 160 | "id": "0x5569", 161 | "type": "Message", 162 | "since": "1.33", 163 | "service": "WDS", 164 | "vendor": "Apple" 165 | }, 166 | { 167 | "name": "Notify BB of Data Service", 168 | "id": "0x5570", 169 | "type": "Message", 170 | "since": "1.33", 171 | "service": "WDS", 172 | "vendor": "Apple" 173 | } 174 | ] 175 | -------------------------------------------------------------------------------- /libqmi-ios-ext/qmi-service-wms.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "WMS", 4 | "type": "Service" 5 | }, 6 | { 7 | "name": "Get SMSC Subscriber Sim Slot", 8 | "id": "0x34", 9 | "type": "Message", 10 | "since": "1.33", 11 | "service": "WMS", 12 | "vendor": "Apple" 13 | }, 14 | { 15 | "name": "Set SMSC Address", 16 | "id": "0x35", 17 | "type": "Message", 18 | "since": "1.33", 19 | "service": "WMS", 20 | "vendor": "Apple" 21 | }, 22 | { 23 | "name": "Set DC Disconnect Timer", 24 | "id": "0x3A", 25 | "type": "Message", 26 | "since": "1.33", 27 | "service": "WMS", 28 | "vendor": "Apple" 29 | }, 30 | { 31 | "name": "Get Broadcast Config", 32 | "id": "0x3E", 33 | "type": "Message", 34 | "since": "1.33", 35 | "service": "WMS", 36 | "vendor": "Apple" 37 | }, 38 | { 39 | "name": "Fetch Voicemail Notifications", 40 | "id": "0x43", 41 | "type": "Message", 42 | "since": "1.33", 43 | "service": "WMS", 44 | "vendor": "Apple" 45 | }, 46 | { 47 | "name": "Message Waiting", 48 | "id": "0x44", 49 | "type": "Indication", 50 | "since": "1.33", 51 | "service": "WMS", 52 | "vendor": "Apple" 53 | }, 54 | { 55 | "name": "SMSC Info", 56 | "id": "0x46", 57 | "type": "Indication", 58 | "since": "1.33", 59 | "service": "WMS", 60 | "vendor": "Apple" 61 | }, 62 | { 63 | "name": "Indication Register", 64 | "id": "0x47", 65 | "type": "Message", 66 | "since": "1.33", 67 | "service": "WMS", 68 | "vendor": "Apple" 69 | }, 70 | { 71 | "name": "Transport Layer Registration", 72 | "id": "0x49", 73 | "type": "Indication", 74 | "since": "1.33", 75 | "service": "WMS", 76 | "vendor": "Apple" 77 | }, 78 | { 79 | "name": "Bind Subscription", 80 | "id": "0x4C", 81 | "type": "Message", 82 | "since": "1.33", 83 | "service": "WMS", 84 | "vendor": "Apple" 85 | }, 86 | { 87 | "name": "Get Service Ready Status", 88 | "id": "0x5C", 89 | "type": "Message", 90 | "since": "1.33", 91 | "service": "WMS", 92 | "vendor": "Apple" 93 | }, 94 | { 95 | "name": "Service Ready Status", 96 | "id": "0x5D", 97 | "type": "Indication", 98 | "since": "1.33", 99 | "service": "WMS", 100 | "vendor": "Apple" 101 | }, 102 | { 103 | "name": "Set Message Waiting", 104 | "id": "0x5F", 105 | "type": "Message", 106 | "since": "1.33", 107 | "service": "WMS", 108 | "vendor": "Apple" 109 | }, 110 | { 111 | "name": "Register Indication", 112 | "id": "0x69", 113 | "type": "Message", 114 | "since": "1.33", 115 | "service": "WMS", 116 | "vendor": "Apple" 117 | }, 118 | { 119 | "name": "Set Broadcast Activation", 120 | "id": "0x3C", 121 | "type": "Message", 122 | "since": "1.33", 123 | "service": "WMS", 124 | "vendor": "Apple" 125 | }, 126 | { 127 | "name": "Set Broadcast Config 3GPP2", 128 | "id": "0x3D", 129 | "type": "Message", 130 | "since": "1.33", 131 | "service": "WMS", 132 | "vendor": "Apple" 133 | } 134 | ] 135 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /qmi-dissect/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | frida = "*" 8 | tqdm = "*" 9 | pandas = "*" 10 | ijson = "*" 11 | protobuf = "*" 12 | 13 | [dev-packages] 14 | 15 | [requires] 16 | python_version = "3" 17 | -------------------------------------------------------------------------------- /qmi-dissect/README.md: -------------------------------------------------------------------------------- 1 | # iPhone QMI Wireshark 2 | 3 | Dissect QMI packets sent to and received by a iPhone Qualcomm baseband chip. 4 | 5 | Heavily inspired by [seemoo-lab/aristoteles](https://github.com/seemoo-lab/aristoteles/blob/master/tools/watch_frida.py). 6 | 7 | ## Setup 8 | 9 | ### Install the QMI Dissector 10 | 11 | Install the dissector by following the steps in the [dissector's README.md](./dissector) 12 | 13 | ### Install Requirements for Monitoring Packets 14 | First, build the Frida agent using 15 | ```bash 16 | npm install 17 | npm run build 18 | ``` 19 | 20 | Then, install the required Python packages 21 | ```bash 22 | # If you've installed Python via homebrew on macOS, use `brew install pipenv` 23 | pip install pipenv 24 | # Install required Python packages using pipenv 25 | pipenv sync 26 | ``` 27 | 28 | Next, install libimobiledevice by following their [instructions](https://libimobiledevice.org/#downloads). 29 | 30 | ### Install Baseband Profile 31 | 32 | Install the developer profile *Baseband for iOS* available from 33 | [Apple](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=baseband) on your target device. 34 | 35 | This is especially important for monitoring packets using idevicesyslog. 36 | 37 | ## Usage 38 | ### Monitor Live QMI Packets 39 | 40 | ```bash 41 | # On jailbroken devices (using Frida) 42 | pipenv run python3 watch_frida.py 43 | # On non-jailbroken devices (using idevicesyslog) 44 | pipenv run python3 watch_syslog.py 45 | ``` 46 | 47 | ### Read QMI packets from Sysdiagnose 48 | 49 | You can import QMI packets from an iOS system diagnose. 50 | 51 | First, create a sysdiagnose on your iPhone by following the steps laid out in the 52 | [instructions for the baseband debug profile](https://download.developer.apple.com/iOS/iOS_Logs/Baseband_Logging_Instructions.pdf). 53 | 54 | Copy the `sysdiagnose_<...>.tar.gz` file to your Mac and extract it. 55 | Inside you'll find a `system_logs.logarchive` file. 56 | Its path is the argument required for the tool. 57 | 58 | If you are not a Mac, you have to install additional tooling. 59 | First install the [Rust toolchain](https://www.rust-lang.org/learn/get-started) on your system. 60 | Then, you can compile the library [macos-unifiedlogs](https://github.com/mandiant/macos-UnifiedLogs/blob/main/BUILDING.md). 61 | ```bash 62 | # Install Rust (on UNIX-like systems) 63 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 64 | # Clone the library 65 | cd logarchive 66 | # Build the library 67 | cargo build --release 68 | ``` 69 | 70 | ```bash 71 | # Import the QMI packets from the specified sysdiagnose into Wireshark (Mac) 72 | pipenv run python3 watch_logarchive.py \ 73 | -f ./sysdiagnose_2022.10.19_13-05-32+0200_iPhone_OS_iPhone_16H71/system_logs.logarchive 74 | 75 | # Import the QMI packets from the specified sysdiagnose into Wireshark (Mac & macOS-UnifiedLogs) 76 | pipenv run python3 watch_logarchive.py \ 77 | -f ./sysdiagnose_2022.10.19_13-05-32+0200_iPhone_OS_iPhone_16H71/system_logs.logarchive \ 78 | -p 79 | 80 | # Import the QMI packets from the specified sysdiagnose into Wireshark (Linux & macOS-UnifiedLogs) 81 | pipenv run python3 watch_logarchive.py \ 82 | -f ./sysdiagnose_2022.10.19_13-05-32+0200_iPhone_OS_iPhone_16H71/system_logs.logarchive 83 | ``` 84 | 85 | ### Read QMI packets from CellGuard Exports 86 | 87 | You can export packets collected by the CellGuard iOS app into a `.cells` file. 88 | Our script reads this file and imports its QMI packets into Wireshark. 89 | 90 | ```bash 91 | # Import all packets collected within the given time period 92 | pipenv run python3 watch_cellguard.py \ 93 | -f ./export-2024-06-12_16-50-25.cells2 \ 94 | --start 1686780000 \ 95 | --end 1686823200 96 | ``` 97 | -------------------------------------------------------------------------------- /qmi-dissect/agent/index.ts: -------------------------------------------------------------------------------- 1 | import './send' 2 | import './write' -------------------------------------------------------------------------------- /qmi-dissect/agent/send.ts: -------------------------------------------------------------------------------- 1 | import { ghidraAddress, log, LogLevel } from "./tools"; 2 | 3 | // *** Sending direction *** 4 | // iPhone -> Chip -> Air 5 | 6 | // libPCITransport.dylib 7 | // bool pci::transport::th::writeAsync(*th this, byte[] data, uint length, void (*)(callback*)); 8 | // -> Found with using https://github.com/seemoo-lab/frida-scripts/blob/main/scripts/libdispatch.js 9 | 10 | const writeAsyncAddr = ghidraAddress('libPCITransport.dylib', '0x1c8474000', '0x1c84868b4', 'ia'); 11 | 12 | Interceptor.attach(writeAsyncAddr, { 13 | onEnter: function(args) { 14 | const state = args[0]; 15 | const buffer = args[1]; 16 | const length = args[2]; 17 | const callback = args[3]; 18 | 19 | const bufferData = buffer.readByteArray(parseInt(length.toString())); 20 | 21 | log(LogLevel.DEBUG, "libPCITransport::pci::transport::th::writeAsync (onEnter)"); 22 | log(LogLevel.DEBUG, `writeAsync: ${writeAsyncAddr}`); 23 | log(LogLevel.DEBUG, `x0: writeAsyncState: ${state}`); 24 | log(LogLevel.DEBUG, `x1: payloadBuffer: ${buffer}`); 25 | log(LogLevel.DEBUG, bufferData!); 26 | log(LogLevel.DEBUG, `x2: payloadLength: ${length}`); 27 | log(LogLevel.DEBUG, `x3: writeAsyncCallback: ${callback}`); 28 | log(LogLevel.DEBUG, ` -> ${DebugSymbol.fromAddress(callback)}`); 29 | log(LogLevel.DEBUG, ''); 30 | 31 | send('qmi_send', bufferData); 32 | }, 33 | onLeave: function(returnValue) { 34 | log(LogLevel.DEBUG, "libPCITransport::pci::transport::th::writeAsync (onLeave)"); 35 | log(LogLevel.DEBUG, `Return Value: ${returnValue}`); 36 | log(LogLevel.DEBUG, ''); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /qmi-dissect/agent/tools.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a Ghidra pointer to an absolute address in memory which can be used for function interception and invocation. 3 | * 4 | * @param library the name of the library e.g. `libPCITransport.dylib` 5 | * @param baseAddress the base address of the library in Ghidra 6 | * @param functionAddress the target address in Ghidra 7 | * @param sign how the resulting pointer should be signed with a PAC (can be omitted) 8 | * @returns the absolute address targeting the specified function 9 | */ 10 | function ghidraAddress(library: string, baseAddress: string, functionAddress: string, sign?: PointerAuthenticationKey): NativePointer { 11 | // Get the base address of the library in memory and throw an exception if the library couldn't be found 12 | const memoryBaseAddress = Module.findBaseAddress(library); 13 | if (memoryBaseAddress == null) { 14 | throw `function at Ghidra address ${functionAddress} not found in ${library}`; 15 | } 16 | 17 | // Calculate the relative address in Ghidra and add it to the memory base address of the library 18 | const ghidraRelativeAddress = new NativePointer(functionAddress).sub(baseAddress); 19 | const absoluteAddress = memoryBaseAddress.add(ghidraRelativeAddress); 20 | 21 | // Sign the pointer if specified by the parameter 22 | if (sign) { 23 | return absoluteAddress.sign(sign); 24 | } else { 25 | return absoluteAddress; 26 | } 27 | } 28 | 29 | enum LogLevel { 30 | DEBUG, 31 | INFO, 32 | WARN, 33 | ERROR 34 | } 35 | 36 | const DEBUG = false; 37 | 38 | function log(level: LogLevel, message: string | object): void { 39 | if (typeof message === 'string') { 40 | message = `[iPhone] ${message}`; 41 | } 42 | switch (level) { 43 | case LogLevel.DEBUG: 44 | if (DEBUG) console.log(message); 45 | break; 46 | case LogLevel.INFO: 47 | console.log(message); 48 | break; 49 | case LogLevel.WARN: 50 | console.warn(message); 51 | break; 52 | case LogLevel.ERROR: 53 | console.error(message); 54 | break; 55 | } 56 | } 57 | 58 | export {ghidraAddress, LogLevel, log} -------------------------------------------------------------------------------- /qmi-dissect/agent/write.ts: -------------------------------------------------------------------------------- 1 | import { log, LogLevel } from "./tools"; 2 | 3 | // *** Receiving direction *** 4 | // Air -> Chip -> iPhone 5 | 6 | // libATCommandStudioDynamic.dylib 7 | // QMux::State::handleReadData(QMux::State *__hidden this, const unsigned __int8 *, unsigned int) 8 | // -> Part of the ICEPicker repository 9 | 10 | const handleReadData = Module.getExportByName('libATCommandStudioDynamic.dylib', '_ZN4QMux5State14handleReadDataEPKhj'); 11 | 12 | Interceptor.attach(handleReadData, { 13 | onEnter: function (args) { 14 | const state = args[0]; 15 | const buffer = args[1]; 16 | const length = args[2]; 17 | 18 | const bufferData = buffer.readByteArray(parseInt(length.toString())); 19 | 20 | log(LogLevel.DEBUG, "libATCommandStudioDynamic:QMux::State::handleReadData (onEnter)"); 21 | log(LogLevel.DEBUG, `handleReadData: ${handleReadData}`); 22 | log(LogLevel.DEBUG, `x0: writeAsyncState: ${state}`); 23 | log(LogLevel.DEBUG, `x1: payloadBuffer: ${buffer}`); 24 | log(LogLevel.DEBUG, bufferData!); 25 | log(LogLevel.DEBUG, `x2: payloadLength: ${length}`); 26 | log(LogLevel.DEBUG, ''); 27 | 28 | send('qmi_read', bufferData); 29 | }, 30 | onLeave: function(returnValue) { 31 | log(LogLevel.DEBUG, "libATCommandStudioDynamic:QMux::State::handleReadData (onLeave)"); 32 | log(LogLevel.DEBUG, `Return Value: ${returnValue}`); 33 | log(LogLevel.DEBUG, ''); 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | libqmi 3 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/README.md: -------------------------------------------------------------------------------- 1 | # Wireshark QMI Dissector 2 | Analyze QMI packets of iPhone basebands in Wireshark. 3 | 4 | ## Installation 5 | 1. Clone libqmi repository 6 | ```sh 7 | git clone https://gitlab.freedesktop.org/mobile-broadband/libqmi.git 8 | ``` 9 | 2. Generate the dissector with 10 | ```sh 11 | # If you're using Python < 3.4, please install pathlib using `pip3 install pathlib` 12 | python3 generate_lua.py 13 | ``` 14 | 3. Copy the generated dissector `build/qmi_dissector_gen.lua` to the [Wireshark plugin directory](https://www.wireshark.org/docs/wsug_html_chunked/ChPluginFolders.html) 15 | * Unix-like systems: `cp build/qmi_dissector_gen.lua ~/.local/lib/wireshark/plugins/` 16 | * Windows systems: `cp build\qmi_dissector_gen.lua %APPDATA%\Wireshark\plugins` 17 | 4. Configure the `DLT_USER` protocol: Open the Wireshark preferences -> Protocols -> DLT_USER -> Edit encapsulation table 18 | ![](./doc/dtl_user_configuration.png) 19 | 20 | ## Research 21 | 22 | You can discover new QMI message identifiers using the tools in the [research](research) directory. 23 | 24 | ## Development 25 | 26 | To get familiar with the development of Lua Wireshark dissectors, I recommend the following resources: 27 | * Mika's Wireshark Guide 28 | * [Part 1](https://mika-s.github.io/wireshark/lua/dissector/2017/11/04/creating-a-wireshark-dissector-in-lua-1.html) 29 | * [Part 2](https://mika-s.github.io/wireshark/lua/dissector/2017/11/06/creating-a-wireshark-dissector-in-lua-2.html) 30 | * [Part 3](https://mika-s.github.io/wireshark/lua/dissector/2017/11/08/creating-a-wireshark-dissector-in-lua-3.html) 31 | * [Part 4](https://mika-s.github.io/wireshark/lua/dissector/2018/12/16/creating-a-wireshark-dissector-in-lua-4.html) 32 | * Wireshark Documentation 33 | * [9.2. Adding a basic dissector](https://www.wireshark.org/docs/wsdg_html_chunked/ChDissectAdd.html) 34 | * [11. Wireshark’s Lua API Reference Manual](https://www.wireshark.org/docs/wsdg_html_chunked/wsluarm_modules.html) 35 | * [11.3. Functions For New Protocols And Dissectors](https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Proto.html#lua_class_Proto) 36 | * [11.5. Obtaining Packet Information](https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Pinfo.html#lua_class_Pinfo) 37 | * [11.20. User DLTs protocol table](https://www.wireshark.org/docs/wsug_html_chunked/ChUserDLTsSection.html) 38 | 39 | ## Contributors 40 | ### Wireshark Dissector for Qualcomm MSM Interface (QMI) Protocol v0.3 41 | 42 | Optimized for analyzing QMI packets captured from iPhones. 43 | 44 | Copyright (c) 2022 Lukas Arnold 45 | 46 | ### Wireshark Dissector for Qualcomm MSM Interface (QMI) Protocol v0.2 47 | 48 | Hosted on https://github.com/dnlplm/WiresharkQMIDissector 49 | 50 | Copyright (c) 2017 Daniele Palmas 51 | 52 | ### Based on: 53 | 54 | - Wireshark Dissector for Qualcomm MSM Interface (QMI) Protocol v0.1 55 | Copyright (c) 2012 Ilya Voronin 56 | https://gist.github.com/ivoronin/2641557 57 | 58 | - Code Aurora Forum's BSD/GPL licensed code: 59 | http://www.codeaurora.org/contribute/projects/gobi/ 60 | 61 | - freedesktop.org libqmi 62 | https://www.freedesktop.org/wiki/Software/libqmi/ 63 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/doc/dtl_user_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seemoo-lab/BaseTrace/bec1a2e9731696315dec67393894561e112e0096/qmi-dissect/dissector/doc/dtl_user_configuration.png -------------------------------------------------------------------------------- /qmi-dissect/dissector/generate_app_json.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import re 4 | import sys 5 | from pathlib import Path 6 | 7 | from qmi_services import iphone_services 8 | from qmi_structures import QMIService, LibQMIJson 9 | 10 | 11 | class AppDefinitions: 12 | """ 13 | A class used to generate the QMI definition files used by the CellGuard app 14 | based on the provided of the libqmi data JSON files. 15 | """ 16 | 17 | build_dir: Path 18 | data_dir: Path 19 | data_ios_dir: Path 20 | services: list[QMIService] 21 | 22 | def __init__(self, build_dir: Path, data_dir: Path, data_ios_dir: Path, services: list[QMIService]) -> None: 23 | self.build_dir = build_dir 24 | self.data_dir = data_dir 25 | self.data_ios_dir = data_ios_dir 26 | self.services = services 27 | 28 | def setup_build_dir(self) -> Path: 29 | """ Ensure that the build directory exists. """ 30 | if not self.build_dir.exists(): 31 | self.build_dir.mkdir() 32 | return self.build_dir 33 | 34 | def generate(self) -> Path: 35 | """ Generate a JSON definition file based on the class properties and return its location. """ 36 | self.setup_build_dir() 37 | 38 | json_service_list = [] 39 | 40 | services_name_map = {s.short_name: s for s in self.services} 41 | data_files = LibQMIJson.read_data_files([self.data_dir, self.data_ios_dir]) 42 | 43 | # Iterate through all data files provided by libqmi 44 | for file_name, file_texts in data_files.items(): 45 | # The file's name must match a given pattern 46 | match = re.match("qmi-service-(\\w+)\\.json", file_name) 47 | if not match: 48 | continue 49 | 50 | # Its service must be known to our application 51 | service: QMIService = services_name_map.get(match.group(1)) 52 | if not service: 53 | print(f"Excluded libqmi service {match.group(1)} because it is not in self.services") 54 | continue 55 | 56 | # We collect all message and indication data present in them 57 | messages = [] 58 | indications = [] 59 | 60 | # For that, we iterate through all top-level elements defined in the file 61 | for element in LibQMIJson.read_json_data(file_name, file_texts): 62 | if not element.type or not element.name: 63 | continue 64 | 65 | # For now, we only store the numeric identifier and the name of messages and indications 66 | if element.type == "Message": 67 | messages.append({ 68 | 'identifier': int(element.id, 16), 69 | 'name': element.name 70 | }) 71 | elif element.type == "Indication": 72 | indications.append({ 73 | 'identifier': int(element.id, 16), 74 | 'name': element.name 75 | }) 76 | 77 | # We put all data for the service into a list, that is written into a JSON file 78 | json_service_list.append({ 79 | 'identifier': service.identifier, 80 | 'short_name': service.short_name, 81 | 'long_name': service.long_name, 82 | 'messages': messages, 83 | 'indications': indications 84 | }) 85 | 86 | output_path = self.build_dir.joinpath('qmi-definitions.json') 87 | with open(output_path, "w") as output_file: 88 | json.dump(json_service_list, output_file) 89 | 90 | print(f'Collected {len(json_service_list)} QMI services') 91 | 92 | return output_path 93 | 94 | 95 | def main(): 96 | """ The main function composing all the work. """ 97 | parser = argparse.ArgumentParser( 98 | prog="generate_lua.py", 99 | description="Generate a Wireshark Dissector based on the class properties and return its location" 100 | ) 101 | parser.add_argument( 102 | '--libqmi-data', 103 | type=Path, 104 | default=Path(__file__).parent / "libqmi" / "data", 105 | help='Path to libqmi data files' 106 | ) 107 | parser.add_argument( 108 | '--libqmi-ios-extension', 109 | type=Path, 110 | default=Path(__file__).parent.parent.parent / "libqmi-ios-ext", 111 | help='Path to libqmi iOS extension data files' 112 | ) 113 | args = parser.parse_args() 114 | 115 | build_dir = Path(__file__).parent / "build" 116 | data_dir = Path(args.libqmi_data) 117 | data_ios_dir = Path(args.libqmi_ios_extension) 118 | 119 | if not data_dir.is_dir(): 120 | sys.stderr.write("Specified libqmi data directory does not exists or is not a directory!\n") 121 | sys.exit(1) 122 | 123 | if not data_ios_dir.is_dir(): 124 | sys.stderr.write("Specified libqmi iOS extension data directory does not exists or is not a directory!\n") 125 | sys.exit(1) 126 | 127 | definitions = AppDefinitions(build_dir, data_dir, data_ios_dir, iphone_services) 128 | output_path = definitions.generate() 129 | 130 | print(f"Successfully generated CellGuard JSON definition file {output_path.name}") 131 | 132 | 133 | if __name__ == "__main__": 134 | main() 135 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/qmi_dissector_template.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Wireshark Dissector for Qualcomm MSM Interface (QMI) Protocol v0.3 3 | 4 | Copyright (c) 2022 Lukas Arnold 5 | 6 | Based on: 7 | 8 | - Wireshark Dissector for Qualcomm MSM Interface (QMI) Protocol v0.2 9 | Copyright (c) 2017 Daniele Palmas 10 | available at: https://github.com/dnlplm/WiresharkQMIDissector 11 | 12 | - Wireshark Dissector for Qualcomm MSM Interface (QMI) Protocol v0.1 13 | Copyright (c) 2012 Ilya Voronin 14 | available at: https://gist.github.com/ivoronin/2641557 15 | 16 | - Code Aurora Forum's BSD/GPL licensed code: 17 | http://www.codeaurora.org/contribute/projects/gobi/ 18 | 19 | - freedesktop.org libqmi 20 | https://www.freedesktop.org/wiki/Software/libqmi/ 21 | --]] 22 | 23 | --- 24 | --- Proto declaration 25 | --- 26 | 27 | qmi_proto = Proto("qmi", "Qualcomm MSM Interface") 28 | 29 | -- 30 | -- Fields 31 | -- 32 | 33 | -- QMI fields 34 | 35 | local f = qmi_proto.fields 36 | 37 | -- QMUX Header 38 | f.tf = ProtoField.uint8("qmi.tf", "T/F", base.DEC) 39 | f.len = ProtoField.uint16("qmi.len", "Length", base.DEC) 40 | f.flag = ProtoField.uint8("qmi.flag", "Flag", base.HEX) 41 | f.cid = ProtoField.uint8("qmi.client_id", "Client ID", base.HEX) 42 | -- Transaction Header 43 | f.resp_ctl = ProtoField.uint8("qmi.trans_response", "Transaction Response Bit", 44 | base.DEC, nil, 1) 45 | f.ind_ctl = ProtoField.uint8("qmi.trans_indication", "Transaction Indication Bit", 46 | base.DEC, nil, 2) 47 | f.comp_svc = ProtoField.uint8("qmi.trans_compound", "Transaction Compound Bit", 48 | base.DEC, nil, 1) 49 | f.resp_svc = ProtoField.uint8("qmi.trans_response", "Transaction Response Bit", 50 | base.DEC, nil, 2) 51 | f.ind_svc = ProtoField.uint8("qmi.trans_indication", "Transaction Indication Bit", 52 | base.DEC, nil, 4) 53 | f.tid_ctl = ProtoField.uint8("qmi.trans_id", "Transaction ID", base.HEX) 54 | f.tid_svc = ProtoField.uint16("qmi.trans_id", "Transaction ID", base.HEX) 55 | -- Message Header 56 | f.msgid = ProtoField.uint16("qmi.message_id", "Message ID", base.HEX) 57 | f.indid = ProtoField.uint16("qmi.indication_id", "Indication ID", base.HEX) 58 | 59 | 60 | -- GENERATE(QMI_MESSAGE_STRUCTURES) 61 | 62 | 63 | f.msglen = ProtoField.uint16("qmi.message_len", "Message Length", base.DEC) 64 | -- TLVs 65 | f.tlvt = ProtoField.uint8("qmi.tlv_type", "TLV Type", base.HEX) 66 | f.tlvl = ProtoField.uint16("qmi.tlv_len", "TLV Length", base.DEC) 67 | f.tlvv = ProtoField.bytes("qmi.tlv_value", "TLV Value") 68 | 69 | local awd_proto_prefix = "qmi.tlv.awd.0x1010.0x34." 70 | local protbuf_dissector = Dissector.get("protobuf") 71 | f.tlvv_awd_app = ProtoField.uint32(awd_proto_prefix .. "app", "App ID") 72 | f.tlvv_awd_component = ProtoField.uint32(awd_proto_prefix .. "component", "Component ID") 73 | f.tlvv_awd_trigger = ProtoField.uint32(awd_proto_prefix .. "trigger", "App ID") 74 | f.tlvv_awd_profile = ProtoField.uint32(awd_proto_prefix .. "profile", "Profile ID") 75 | f.tlvv_awd_metric = ProtoField.uint32(awd_proto_prefix .. "metric", "Metric ID") 76 | f.tlvv_awd_submission = ProtoField.uint32(awd_proto_prefix .. "submission", "Submission ID") 77 | f.tlvv_awd_other = ProtoField.uint16(awd_proto_prefix .. "other", "Other ID") 78 | f.tlvv_awd_payload_length = ProtoField.uint16(awd_proto_prefix .. "payload_length", "Payload Length") 79 | f.tlvv_awd_payload = ProtoField.bytes(awd_proto_prefix .. "payload", "Payload") 80 | 81 | -- 82 | -- Utils Functions 83 | -- 84 | 85 | compare_tvb = function(a1, a2, a_len) 86 | for i = 0, a_len - 1 87 | do 88 | if a1(i, 1):uint() ~= a2(i, 1):uint() then 89 | return false 90 | end 91 | end 92 | 93 | return true 94 | end 95 | 96 | local function getstring(finfo) 97 | local ok, val = pcall(tostring, finfo) 98 | if not ok then 99 | val = "(unknown)" 100 | end 101 | return val 102 | end 103 | 104 | -- 105 | -- Dissector Function 106 | -- 107 | function qmi_proto.dissector(buffer, pinfo, tree) 108 | -- Change this variable manually to build the dissector with support for direction information. 109 | -- Warning: This only works in combination with the command 'watch_frida.py --directionbit' 110 | local direction_bit = false 111 | 112 | -- Set offset according to operating system 113 | local off = 0 114 | if direction_bit then 115 | off = 1 116 | end 117 | 118 | if buffer:len() - off < 12 then 119 | -- No payload or too short (12 is a min size) 120 | return 121 | end 122 | 123 | -- QMUX Header (6 bytes), see GobiNet/QMI.h, should always start with 0x01 124 | local tf = buffer(off, 1) 125 | if tf:uint() ~= 1 then 126 | -- Not a QMI packet 127 | return 128 | end 129 | local len = buffer(off + 1, 2) -- Length 130 | if len:le_uint() ~= buffer:len() - off - 1 then 131 | -- Length does not match 132 | return 133 | end 134 | -- We could also use this flag to determine the packet's direction. 135 | -- Nevertheless, we should translate its binary values. 136 | -- Furthermore, Its value seems to be always equal to the properties req -> 0x00 and ind & resp -> 0x80 137 | local flag = buffer(off + 3, 1) -- Always 0x00 (out) or 0x80 (in) 138 | if flag:uint() ~= 0x00 and flag:uint() ~= 0x80 then 139 | -- Not a QMI packet 140 | return 141 | end 142 | local svcid = buffer(off + 4, 1) -- Service ID 143 | local cid = buffer(off + 5, 1) -- Client ID 144 | 145 | -- Setup protocol subtree 146 | local qmitree = tree:add(qmi_proto, buffer(off, buffer:len() - off), "Qualcomm MSM Interface") 147 | local hdrtree = qmitree:add(qmi_proto, buffer(off, 6), "QMUX Header") 148 | hdrtree:add(f.tf, tf) 149 | hdrtree:add_le(f.len, len) 150 | hdrtree:add(f.flag, flag) 151 | hdrtree:add(f.svcid, svcid) 152 | hdrtree:add(f.svcname, service_names[svcid:uint()] and service_names[svcid:uint()] or "unknown"):set_generated(true) 153 | hdrtree:add(f.cid, cid) 154 | off = off + 6 155 | 156 | -- Transaction Header (2 or 3 bytes), see GobiAPI/Core/QMIBuffers.h 157 | local responsebit 158 | local indicationbit 159 | if svcid:uint() == 0 then 160 | responsebit = buffer(off, 1):bitfield(7) 161 | indicationbit = buffer(off, 1):bitfield(6) 162 | local thdrtree = qmitree:add(qmi_proto, buffer(off, 2), "Transaction Header") 163 | tid = buffer(off + 1, 1) 164 | thdrtree:add(f.resp_ctl, buffer(off, 1)) 165 | thdrtree:add(f.ind_ctl, buffer(off, 1)) 166 | thdrtree:add(f.tid_ctl, tid) 167 | off = off + 2 168 | else 169 | responsebit = buffer(off, 1):bitfield(6) 170 | indicationbit = buffer(off, 1):bitfield(5) 171 | local thdrtree = qmitree:add(qmi_proto, buffer(off, 3), "Transaction Header") 172 | tid = buffer(off + 1, 2) 173 | thdrtree:add(f.comp_svc, buffer(off, 1)) 174 | thdrtree:add(f.resp_svc, buffer(off, 1)) 175 | thdrtree:add(f.ind_svc, buffer(off, 1)) 176 | thdrtree:add_le(f.tid_svc, tid) 177 | off = off + 3 178 | end 179 | 180 | -- iPhone: Get direction of packet by inspecting whether it is a indication 181 | -- We want to use Address.string() for display, but is not implemented for Lua, so we're using Address.eth() 182 | -- https://gitlab.com/wireshark/wireshark/-/blob/master/epan/wslua/wslua_address.c#L137 183 | local appleA14Eth = 'A9:91:E0:0A:14:00' 184 | local basebandEth = 'BA:8E:BA:9D:00:00' 185 | if direction_bit then 186 | -- This is the exact approach. 187 | local direction = buffer(0,1) 188 | if direction:uint() == 0 then 189 | -- Packet originates from the baseband and is sent to the iPhone's application processor 190 | pinfo.src = Address.ether(basebandEth) 191 | pinfo.dst = Address.ether(appleA14Eth) 192 | elseif direction:uint() == 1 then 193 | -- Packet originates from the iPhone's application processor and is sent the the baseband 194 | pinfo.src = Address.ether(appleA14Eth) 195 | pinfo.dst = Address.ether(basebandEth) 196 | else 197 | -- Invalid direction bit 198 | end 199 | else 200 | -- This is a rough approximation but works for 99% of all cases. 201 | -- If accuracy is required, use the watch_frida.py script with the --directionbit flag and 202 | -- enable the direction_bit setting in this dissector above. 203 | if responsebit == 1 or indicationbit == 1 then 204 | -- Packet originates from the baseband and is sent to the iPhone's application processor 205 | pinfo.src = Address.ether(basebandEth) 206 | pinfo.dst = Address.ether(appleA14Eth) 207 | else 208 | -- Packet originates from the iPhone's application processor and is sent the the baseband 209 | pinfo.src = Address.ether(appleA14Eth) 210 | pinfo.dst = Address.ether(basebandEth) 211 | end 212 | end 213 | 214 | -- Message Header (4 bytes), see GobiAPI/Core/QMIBuffers.h 215 | local msgstr 216 | msgid = buffer(off, 2) 217 | msglen = buffer(off + 2, 2) 218 | local mhdrtree = qmitree:add(qmi_proto, buffer(off, 4), "Message Header") 219 | 220 | 221 | -- GENERATE(TLV_LINK) 222 | 223 | 224 | mhdrtree:add_le(f.msglen, msglen) 225 | off = off + 4 226 | 227 | -- TLVs, see GobiAPI/Core/QMIBuffers.h 228 | local msgend = off + msglen:le_uint() 229 | while off < msgend do 230 | local tlvt = buffer(off, 1) 231 | local tlvl = buffer(off + 1, 2) 232 | local tlvv = buffer(off + 3, tlvl:le_uint()) 233 | local tlv_name_available = pcall(function() 234 | tlv_name = tlv_description[msgid:le_uint()][tlvt:uint()] 235 | end) 236 | if not tlv_name_available then 237 | tlv_name = "Unknown TLV" 238 | end 239 | if tlv_name == nil then 240 | tlv_name = "Unknown TLV" 241 | end 242 | local treesize = tlvl:le_uint() + 3 243 | local treename = string.format("TLV 0x%.2x %s", tlvt:uint(), tlv_name) 244 | local tlvtree = qmitree:add(qmi_proto, buffer(off, treesize), treename) 245 | tlvtree:add(f.tlvt, tlvt) 246 | tlvtree:add_le(f.tlvl, tlvl) 247 | tlvtree:add(f.tlvv, tlvv) 248 | off = off + treesize 249 | end 250 | 251 | -- Setup columns 252 | local svcstr = services[svcid:uint()] and 253 | services[svcid:uint()] or string.format("0x%x", svcid:uint()) 254 | local typestr = indicationbit == 1 and 255 | "Indication" or responsebit == 1 and "Response" or "Request" 256 | msgstr = msgstr ~= nil and msgstr or string.format("0x%x", msgid:le_uint()) 257 | pinfo.cols.protocol = "QMI" 258 | pinfo.cols.info = string.format("%s %s: %s", svcstr, typestr, msgstr) 259 | end 260 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/qmi_services.py: -------------------------------------------------------------------------------- 1 | from qmi_structures import QMIService 2 | 3 | # The service list is extracted from libqmi & iPhone firmware. 4 | # If possible, we choose to use the iPhone's naming conventions, 5 | # but fall back if libqmi has to offer additional service data. 6 | # Sources: 7 | # - libATCommandStudioDynamic.dylib!qmi::asShortString 8 | # - libATCommandStudioDynamic.dylib!qmi::asLongString 9 | # - https://gitlab.freedesktop.org/mobile-broadband/libqmi/-/blob/main/src/libqmi-glib/qmi-enums.h 10 | # - https://gitlab.freedesktop.org/mobile-broadband/libqmi/-/tree/main/data 11 | iphone_services: list[QMIService] = [ 12 | QMIService(0x00, "ctl", "Control Service"), 13 | QMIService(0x01, "wds", "Wireless Data Service"), 14 | QMIService(0x02, "dms", "Device Management Service"), 15 | QMIService(0x03, "nas", "Network Access Service"), 16 | QMIService(0x04, "qos", "QoS Service"), 17 | QMIService(0x05, "wms", "Wireless Messaging Service"), 18 | QMIService(0x06, "pds", "Position Determination Service"), 19 | 20 | # iPhone: not listed 21 | QMIService(0x07, "auth", "Authentication service"), 22 | 23 | QMIService(0x08, "at", "Access Terminal Service"), 24 | # libqmi: 'voice' -> We use the iPhone's name to support the extracted definitions 25 | QMIService(0x09, "vs", "Voice Service", "voice"), 26 | # libqmi: 'cat2' -> We use the iPhone's name to support the extracted definitions, 27 | # although cat is defined later by libqmi 28 | QMIService(0x0A, "cat", "Card App Toolkit", "cat2"), 29 | QMIService(0x0B, "uim", "User Identity Module"), 30 | QMIService(0x0C, "pbm", "Phonebook Manager Service"), 31 | 32 | # iPhone: not listed 33 | QMIService(0x0D, "qchat", "QCHAT Service"), 34 | # iPhone: not listed 35 | QMIService(0x0E, "rmtfs", "Remote File System Service"), 36 | # iPhone: not listed 37 | QMIService(0x0F, "test", "Test Service"), 38 | # iPhone: not listed 39 | QMIService(0x10, "loc", "Location Service"), 40 | # iPhone: not listed 41 | QMIService(0x11, "sar", "Specific Absorption Rate"), 42 | # iPhone: not listed 43 | QMIService(0x12, "ims", "IMS Settings Service"), 44 | # iPhone: not listed 45 | QMIService(0x13, "adc", "Analog to Digital Converter Driver Service"), 46 | # iPhone: not listed 47 | QMIService(0x14, "csd", "Core Sound Driver Service"), 48 | # iPhone: not listed 49 | QMIService(0x15, "mfs", "Modem Embedded File System Service"), 50 | # iPhone: not listed 51 | QMIService(0x16, "time", "Time Service"), 52 | # iPhone: not listed 53 | QMIService(0x17, "ts", "Thermal Sensors Service"), 54 | # iPhone: not listed 55 | QMIService(0x18, "tmd", "Thermal Mitigation Device Service"), 56 | # iPhone: not listed 57 | QMIService(0x19, "sap", "Service Access Proxy Service"), 58 | 59 | QMIService(0x1A, "wda", "Wireless Data Administrative Service"), 60 | 61 | # iPhone: not listed 62 | QMIService(0x1B, "tsync", "TSYNC Control Service"), 63 | # iPhone: not listed 64 | QMIService(0x1C, "rfsa", "Remote File System Access Service"), 65 | # iPhone: not listed 66 | QMIService(0x1D, "csvt", "Circuit Switched Videotelephony Service"), 67 | # iPhone: not listed 68 | QMIService(0x1E, "qcmap", "Qualcomm Mobile Access Point Service"), 69 | # IMS = IP Multimedia Subsystem (https://en.wikipedia.org/wiki/IP_Multimedia_Subsystem) 70 | # iPhone: not listed 71 | QMIService(0x1F, "imsp", "IMS Presence Service"), 72 | # iPhone: not listed 73 | QMIService(0x20, "imsvt", "IMS Videotelephony Service"), 74 | # iPhone: not listed 75 | QMIService(0x21, "imsa", "IMS Application Service"), 76 | 77 | QMIService(0x22, "coex", "Coexistence Service"), 78 | # 0x23 reserved for future use 79 | QMIService(0x24, "pdc", "Persistent Device Service"), 80 | # 0x25 reserved for future use 81 | 82 | # iPhone: not listed 83 | QMIService(0x26, "stx", "Simultaneous Transmit Service"), 84 | # iPhone: not listed 85 | QMIService(0x27, "bit", "Bearer Independent Transport Service"), 86 | 87 | # libqmi: "imsrtp" -> "IMS RTP Service" 88 | QMIService(0x28, "787", "5WI 787 Service"), 89 | 90 | # iPhone: not listed 91 | QMIService(0x29, "rfpre", "RF Radiated Performance Enhancement Service"), 92 | 93 | QMIService(0x2A, "dsd", "Data System Determination"), 94 | QMIService(0x2B, "ssctl", "Subsystem Control"), 95 | QMIService(0x2C, "mfse", "Modem File System Extended Service"), 96 | 97 | # iPhone: not listed 98 | QMIService(0x2F, "dpm", "Data Port Mapper Service"), 99 | 100 | QMIService(0x30, "dfs", "Data Filter Service"), 101 | QMIService(0x52, "ms", "Media Service Extension"), 102 | 103 | # libqmi: iPhone: not listed 104 | # QMIService(0xE0, "cat", "Card Application Toolkit Service (v1)"), 105 | 106 | # libqmi: "rms" -> "Remote Management Service" 107 | QMIService(0xE1, "audio", "Audio Service"), 108 | # libqmi: "oma" -> "Open Mobile Alliance device management service" 109 | QMIService(0xE2, "bsp", "Board Support Package Service"), 110 | # libqmi: "fox" -> "Foxconn General Modem Service" 111 | QMIService(0xE3, "ciq", "Carrier IQ Service"), 112 | QMIService(0xE4, "awd", "Apple Wireless Diagnostics"), 113 | QMIService(0xE5, "vinyl", "Vinyl Service"), 114 | # libqmi: "fota" -> "Firmware Over The Air Service" 115 | QMIService(0xE6, "mavims", "Mav 5WI Service"), 116 | # libqmi: "gms" -> "Telit General Modem Service" 117 | # iPhone: Full service name misspelled in binary code: "Enhnaced" 118 | QMIService(0xE7, "elqm", "Enhanced Link Quality Metric Service"), 119 | # libqmi: "gas" -> "Telit General Application Service" 120 | QMIService(0xE8, "p2p", "Mav P2P Service"), 121 | QMIService(0xE9, "apps", "BSP APPS Service"), 122 | # Apple's Satellite Service codenamed Stewie 123 | # https://www.bloomberg.com/news/articles/2021-08-30/apple-plans-to-add-satellite-features-to-iphones-for-emergencies 124 | QMIService(0xEA, "sft", "QMI Stewie Service"), 125 | 126 | # Special unknown service 127 | QMIService(0xFF, "unknown", "Unknown Service"), 128 | ] 129 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/qmi_structures.py: -------------------------------------------------------------------------------- 1 | import json 2 | from dataclasses import dataclass 3 | from itertools import chain 4 | from json import JSONEncoder 5 | from pathlib import Path 6 | from typing import Optional, List, Any 7 | 8 | 9 | @dataclass 10 | class QMIService: 11 | """ A class representing a service in the QMI protocol. """ 12 | 13 | identifier: int 14 | short_name: str 15 | long_name: str 16 | libqmi_name: Optional[str] = None 17 | 18 | def field(self, prefix: str = "", suffix: str = "") -> str: 19 | """ Builds a Lua field name with the given prefix and suffix based on the short_name of the service. """ 20 | field_name = self.short_name 21 | if prefix: 22 | field_name = f"{prefix}_{field_name}" 23 | if suffix: 24 | field_name = f"{field_name}_{suffix}" 25 | return field_name 26 | 27 | 28 | @dataclass 29 | class LibQMIElement: 30 | """ A data class representing an object at every level from the libqmi data JSON files. """ 31 | common_ref: Optional[str] = None 32 | 33 | name: Optional[str] = None 34 | id: Optional[str] = None 35 | type: Optional[str] = None 36 | since: Optional[str] = None 37 | 38 | service: Optional[str] = None 39 | vendor: Optional[str] = None 40 | 41 | input: Optional[List["LibQMIElement"]] = None 42 | output: Optional[List["LibQMIElement"]] = None 43 | 44 | personal_info: Optional[str] = None 45 | format: Optional[str] = None 46 | public_format: Optional[str] = None 47 | fixed_size: Optional[str] = None 48 | max_size: Optional[str] = None 49 | size_prefix_format: Optional[str] = None 50 | guint_size: Optional[str] = None 51 | protobuf_message: Optional[str] = None 52 | 53 | contents: Optional[List["LibQMIElement"]] = None 54 | prerequisites: Optional[List["LibQMIElement"]] = None 55 | array_element: Optional["LibQMIElement"] = None 56 | 57 | def lua_map_id_name(self) -> str: 58 | """ Returns a Lua snippet used in Lua tables to map the elements id to its name """ 59 | if self.name: 60 | return f"[{self.id}] = '{self.name}', " 61 | else: 62 | return f"[{self.id}] = 'unknown name', " 63 | 64 | @staticmethod 65 | def from_json(data: dict) -> "LibQMIElement": 66 | """ Parses a JSON dictionary from json.laods into a LibQMIElement """ 67 | return LibQMIElement( 68 | common_ref=data.get("common-ref"), 69 | 70 | name=data.get("name"), 71 | id=data.get("id"), 72 | type=data.get("type"), 73 | since=data.get("since"), 74 | 75 | service=data.get("service"), 76 | vendor=data.get("vendor"), 77 | 78 | input=data.get("input"), 79 | output=data.get("output"), 80 | 81 | format=data.get("format"), 82 | public_format=data.get("public-format"), 83 | fixed_size=data.get('fixed-size'), 84 | max_size=data.get("max-size"), 85 | size_prefix_format=data.get("size-prefix-format"), 86 | personal_info=data.get("personal-info"), 87 | guint_size=data.get("guint-size"), 88 | protobuf_message=data.get("protobuf-message"), 89 | 90 | contents=data.get("contents"), 91 | prerequisites=data.get("prerequisites"), 92 | array_element=data.get("array-element") 93 | ) 94 | 95 | 96 | class LibQMIElementEncoder(JSONEncoder): 97 | def default(self, o: Any) -> Any: 98 | # Convert the LibQMIElement Python object into a dictionary of its values. 99 | # See: https://stackoverflow.com/a/3768975 100 | values: dict = o.__dict__ 101 | 102 | # Remove the None value to prevent clutter of the final JSON file. 103 | # See: https://stackoverflow.com/a/21412056 104 | values = {k: v for (k, v) in values.items() if v is not None} 105 | 106 | return values 107 | 108 | 109 | class LibQMIJson: 110 | @staticmethod 111 | def polish_json(source: Path) -> str: 112 | """ Remove unwanted lines from a JSON file (source) and return its polished version. """ 113 | bad_words = ["//"] 114 | 115 | with open(source) as src_file: 116 | lines = src_file.readlines() 117 | 118 | # Replace all lines with bad words with empty ones to keep the line numbers correct 119 | for index, line in enumerate(lines): 120 | if any(bad_word in line for bad_word in bad_words): 121 | lines[index] = "\n" 122 | 123 | return "".join(lines) 124 | 125 | @staticmethod 126 | def read_data_files(dirs: list[Path], sub_dirs: bool = True) -> dict[str, list[str]]: 127 | """ Read all JSON files in the data_dir (and by default its subdirectories) and return them as a dictionary. """ 128 | rename_services = { 129 | 'qmi-service-voice.json': 'qmi-service-vs.json', 130 | 'qmi-service-cat2.json': 'qmi-service-cat.json' 131 | } 132 | data_files = {} 133 | for path in chain(*map(lambda dir: dir.glob("**/*.json" if sub_dirs else "*.json"), dirs)): 134 | name = path.name 135 | 136 | # Rename libqmi service definition files to fit with iOS QMI service names 137 | if name in rename_services: 138 | name = rename_services[name] 139 | 140 | if name in data_files: 141 | data_files[name].append(LibQMIJson.polish_json(path)) 142 | else: 143 | data_files[name] = [LibQMIJson.polish_json(path)] 144 | 145 | return data_files 146 | 147 | @staticmethod 148 | def read_json_data(file_name: str, file_texts: list[str]) -> list[LibQMIElement]: 149 | """ Reads the JSON data from data files into LibQMIElements. """ 150 | json_data: list[LibQMIElement] = [] 151 | 152 | for file_text in file_texts: 153 | try: 154 | json_data.extend(json.loads(file_text, object_hook=LibQMIElement.from_json)) 155 | except json.decoder.JSONDecodeError as json_error: 156 | print(f"Unable to decode JSON file {file_name} - {json_error}") 157 | continue 158 | 159 | return json_data 160 | 161 | @staticmethod 162 | def write_data_files(data_dir: Path, data_files: dict[str, list[LibQMIElement]]): 163 | """ Writes the data of all QMI services back to JSON files in the specified directory. """ 164 | for file_name, file_data in data_files.items(): 165 | file_path = data_dir.joinpath(file_name) 166 | file_path.write_text(json.dumps(file_data, indent=4, cls=LibQMIElementEncoder)) 167 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/ExtractCommandDriversService.java: -------------------------------------------------------------------------------- 1 | // Associates QMI Command Drivers with their respective QMI Service. 2 | // Point your 'QMIClientPool::requestClient' 3 | // 4 | // Locate the reference using the scalar 0xEA in libCommCenterMCommandDrivers.dylib in the QMIStewieCommandDriver. 5 | // 6 | // @author: Lukas Arnold 7 | // @category: QMI 8 | 9 | import ghidra.app.script.GhidraScript; 10 | import ghidra.app.tablechooser.*; 11 | import ghidra.program.model.address.Address; 12 | import ghidra.program.model.listing.Function; 13 | import ghidra.program.model.listing.Instruction; 14 | import ghidra.program.model.symbol.Reference; 15 | import utils.CursorLocation; 16 | import utils.InstructionUtils; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | import java.util.Optional; 22 | import java.util.stream.Stream; 23 | 24 | public class ExtractCommandDriversService extends GhidraScript { 25 | 26 | @Override 27 | protected void run() throws Exception { 28 | // TODO: Check that the correct method is selected 29 | 30 | // Check where the cursor is located to correctly identify the reference target address 31 | println("Class of currentLocation: " + currentLocation.getClass().getName()); 32 | Address targetLocation = CursorLocation.findLocation(currentLocation, currentAddress); 33 | 34 | // Get all references to current address 35 | Reference[] references = getReferencesTo(targetLocation); 36 | 37 | // Check if we've got any results at all 38 | if (references.length == 0) { 39 | println("No references to the selected instruction found. Is your pointer correct?"); 40 | return; 41 | } 42 | 43 | println("Collecting command drivers based on references to " + targetLocation); 44 | println("Found " + references.length + " references"); 45 | 46 | // Build an output table 47 | String title = "QMI Services used by Command Drivers (Found " + references.length + ")"; 48 | TableChooserDialog table = createTableChooserDialog(title, new TableChooserExecutor() { 49 | @Override 50 | public String getButtonName() { 51 | return "NOP"; 52 | } 53 | 54 | @Override 55 | public boolean execute(AddressableRowObject rowObject) { 56 | return false; 57 | } 58 | }); 59 | 60 | 61 | table.addCustomColumn(new StringColumnDisplay() { 62 | @Override 63 | public String getColumnValue(AddressableRowObject rowObject) { 64 | return ((CommandDriverReference) rowObject).driverName; 65 | } 66 | 67 | @Override 68 | public String getColumnName() { 69 | return "Driver Name"; 70 | } 71 | }); 72 | 73 | table.addCustomColumn(new AbstractComparableColumnDisplay() { 74 | @Override 75 | public Long getColumnValue(AddressableRowObject rowObject) { 76 | return ((CommandDriverReference) rowObject).serviceId; 77 | } 78 | 79 | @Override 80 | public String getColumnName() { 81 | return "QMI Service"; 82 | } 83 | }); 84 | 85 | // Convert all references and add them to the table 86 | Stream.of(references) 87 | .map(this::extractServiceId) 88 | .forEach(table::add); 89 | 90 | table.show(); 91 | } 92 | 93 | /** 94 | * Tries to extract the QMI service for the source of a given reference. 95 | * 96 | * @param reference the reference to the source address 97 | * @return the annotated reference 98 | */ 99 | private CommandDriverReference extractServiceId(Reference reference) { 100 | // Get the function name the reference source belongs to 101 | Function function = getFunctionBefore(reference.getFromAddress()); 102 | if (function == null) 103 | return new CommandDriverReference(reference, null, "", -1); 104 | 105 | // Extract the full function name starting with '__' as it exposes more information about a given function 106 | // String functionName = FunctionNames.extractFullFunctionName(function, this); 107 | String driverName = extractDriverName(function); 108 | 109 | // Get the instruction before the reference source to find the message id 110 | Instruction instruction = getInstructionBefore(reference.getFromAddress()); 111 | 112 | // Check 4 previous instructions if they provide us with the value in question 113 | for (int i = 0; i < 4; i++) { 114 | if (instruction == null) break; 115 | 116 | if (instruction.getMnemonicString().equals("mov")) { 117 | Optional serviceId = InstructionUtils.extractConstantParameter(instruction, "w1", 1); 118 | 119 | // Check if we've found the right instruction and if yes return 120 | if (serviceId.isPresent()) { 121 | return new CommandDriverReference(reference, function, driverName, serviceId.get()); 122 | } 123 | } 124 | 125 | // Get the instruction before the current one 126 | instruction = instruction.getPrevious(); 127 | } 128 | 129 | // We didn't find the correct instruction 130 | return new CommandDriverReference(reference, function, driverName, -1); 131 | } 132 | 133 | private String extractDriverName(Function function) { 134 | String functionName = function.getName(true); 135 | 136 | if (functionName.startsWith("dispatch")) { 137 | // Extract the crucial information from the long dispatch name 138 | String[] splitOne = functionName.split("dispatch::async", 2); 143 | if (splitTwo.length < 2) { 144 | return splitOne[1]; 145 | } 146 | return splitTwo[0]; 147 | } else { 148 | // Remove the last method but keep other prefixes 149 | List split = new ArrayList(Arrays.asList(functionName.split("::"))); 150 | split.removeLast(); 151 | return String.join("::", split); 152 | } 153 | } 154 | 155 | public record CommandDriverReference( 156 | Reference reference, Function function, String driverName, long serviceId 157 | ) implements AddressableRowObject { 158 | @Override 159 | public Address getAddress() { 160 | return reference.getFromAddress(); 161 | } 162 | 163 | public boolean isResolved() { 164 | return serviceId > 0; 165 | } 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/ExtractDebugFunctionNames.java: -------------------------------------------------------------------------------- 1 | // Extracts C++ class and function names from debug strings. 2 | // This script is intended for the 'locationd' binary, but could also be applied to other binaries without symbols. 3 | // 4 | // @author: Lukas Arnold 5 | // @category: QMI 6 | 7 | import ghidra.app.script.GhidraScript; 8 | import ghidra.app.util.NamespaceUtils; 9 | import ghidra.app.util.XReferenceUtils; 10 | import ghidra.program.model.listing.CircularDependencyException; 11 | import ghidra.program.model.listing.Data; 12 | import ghidra.program.model.listing.Function; 13 | import ghidra.program.model.symbol.SourceType; 14 | import ghidra.program.util.DefinedDataIterator; 15 | import ghidra.util.exception.DuplicateNameException; 16 | import ghidra.util.exception.InvalidInputException; 17 | 18 | import java.util.concurrent.atomic.AtomicInteger; 19 | import java.util.regex.Matcher; 20 | import java.util.regex.Pattern; 21 | 22 | public class ExtractDebugFunctionNames extends GhidraScript { 23 | 24 | private static final Pattern CLASS_FUNCTION_PATTERN = Pattern.compile("(\\w+)::(\\w+)"); 25 | 26 | // We could also create a regular expression for "#bb.e" debug messages, 27 | // but they do not directly refer to the class and 28 | // should be handled with a lower priority than class function patterns. 29 | 30 | @Override 31 | protected void run() throws Exception { 32 | AtomicInteger counter = new AtomicInteger(); 33 | 34 | DefinedDataIterator.definedStrings(currentProgram).forEach(data -> { 35 | String string = (String) data.getValue(); 36 | 37 | // Ignore TLV debug messages 38 | if (string.startsWith("Recvd")) { 39 | return; 40 | } 41 | 42 | // Handle special strings to rename caller functions without proper debug strings in locationd 43 | // "#bbe.Register PDS" -> BasebandEvent::registerPDS 44 | // "#bb.e, registration action" -> BasebandEvent::registrationAction 45 | if (string.equals("{\"msg%{public}.0s\":\"#bb.e,Register PDS\"}")) { 46 | renameReferences(data, "BasebandEvent", "registerPDSIndications"); 47 | return; 48 | } else if (string.equals("{\"msg%{public}.0s\":\"#bb.e,registration action\"}")) { 49 | renameReferences(data, "BasebandEvent", "registrationAction"); 50 | return; 51 | } 52 | 53 | // Try to match the function and class name from the string 54 | Matcher matcher = CLASS_FUNCTION_PATTERN.matcher(string); 55 | if (!matcher.find()) { 56 | return; 57 | } 58 | 59 | // If successful, apply the label to the function of all references 60 | applyMatch(data, matcher); 61 | 62 | // Count the number of successful matches 63 | counter.getAndIncrement(); 64 | }); 65 | 66 | println("Matched " + counter.get() + " strings"); 67 | } 68 | 69 | private void applyMatch(Data data, Matcher matcher) { 70 | // Extract the class and function name from the matched object 71 | String className = matcher.group(1); 72 | String functionName = matcher.group(2); 73 | 74 | // Ignore debug messages regarding the standard C++ library 75 | if (className.equals("std")) { 76 | return; 77 | } 78 | 79 | // println("Match: " + className + "::" + functionName); 80 | 81 | // Apply the label to the function of all references 82 | renameReferences(data, className, functionName); 83 | } 84 | 85 | private void renameReferences(Data data, String className, String functionName) { 86 | // Apply the label to the function of all references 87 | XReferenceUtils.getXReferences(data, -1).forEach(reference -> { 88 | // Get the function for a given reference 89 | Function function = getFunctionBefore(reference.getFromAddress()); 90 | try { 91 | // Assign the function name 92 | function.setName(functionName, SourceType.ANALYSIS); 93 | 94 | // Check if a class name is set and if apply it to the function 95 | if (className != null) { 96 | // Create a new or get the existing namespace for the class and assign it 97 | function.setParentNamespace(NamespaceUtils.createNamespaceHierarchy( 98 | className, null, currentProgram, SourceType.ANALYSIS)); 99 | 100 | // Add a comment of both 101 | function.setComment(className + "::" + functionName); 102 | } 103 | } catch (InvalidInputException | DuplicateNameException | CircularDependencyException e) { 104 | // Print an error if an exception occurred 105 | printerr("Can't apply " + className + "::" + functionName + 106 | "to " + reference.getFromAddress() + ": " + e.getMessage()); 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/README.md: -------------------------------------------------------------------------------- 1 | # QMI Research 2 | 3 | You can discover new QMI message identifier using the tools available in this directory. 4 | You can combine two approaches for this task. 5 | 6 | ## Background 7 | 8 | The function `qmi::MessageBase::validateMsgId` is called by a large number of QMI message, the iPhone sends and receives. 9 | Its two parameters are the instance pointer of the `MessageBase` object and the `message_id` as an unsigned short. 10 | 11 | Thus, we can use it to translate previously unknown message ids to strings and better understand the communication between the iPhone application processor and its baseband processor. 12 | 13 | ## Dynamic 14 | The dynamic approach uses [Frida](https://frida.re) to intercept calls to the function `qmi::MessageBase::validateMsgId` from the library `libQMIParserDynamic.dylib` in real-time. 15 | You can try different things on the iPhone to collect as much message ids as possible. 16 | A jailbroken iPhone is required to execute the script. 17 | It is optimized for an iPhone 12 mini with iOS 14.2.1. 18 | 19 | ```bash 20 | frida -U -l explore_frida.ts CommCenter 21 | ``` 22 | 23 | Messages of the QMI position determination service (PDS) are handled by the `locationd` process. 24 | Its executable can be found in `/usr/libexec/locationd`. 25 | ```bash 26 | frida -U locationd -l explore_frida.ts 27 | ``` 28 | 29 | ## Static 30 | The static approach uses a Ghidra script to scan all references to the function `qmi::MessageBase::validateMsgId` and show respective message ids & calling functions in a table. 31 | 32 | To use it, add this folder as a script directory in Ghidra (so it can detect the file [ExtractQMIMessageIDs.java](./ExtractQMIMessageIDs.java)), point your cursor to the entry point of the function `__auth_stubs::__ZN3qmi11MessageBase13validateMsgIdEt` in your target library and run it using the script manager. 33 | 34 | Good resources to learn Ghidra scripting are 35 | - [Ghidra Javadocs](https://ghidra.re/ghidra_docs/api/ghidra/app/script/GhidraScript.html) 36 | - [sentinelone.com](https://www.sentinelone.com/labs/a-guide-to-ghidra-scripting-development-for-malware-researchers/) 37 | - [HackOvert/GhidraSnippets](https://github.com/HackOvert/GhidraSnippets) 38 | - [garyttierney/intellij-ghidra](https://github.com/garyttierney/intellij-ghidra) 39 | 40 | ### Import 41 | 42 | Based on static approach we can automatically analyze binaries, extract their QMI definitions, and convert them to libqmi data structures which in turn can be used for improving the dissector. 43 | 44 | 1. Get IPSW 45 | 2. `ipsw dyld imports dyld_shared_cache_arm64e /usr/lib/libQMIParserDynamic.dylib` 46 | 3. Put each file in Ghidra 47 | 4. Apply plugin 48 | 5. Run script to import 49 | 50 | Repeat for executables like locationd but apply symbol plugin before 51 | 52 | ## Results 53 | 54 | The results can be used to manually improve the iOS extensions for libqmi, located in the [libqmi-ios-ext](../../../libqmi-ios-ext) directory. 55 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/explore_frida.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // Library: libQMIParserDynamic.dylib 4 | // @ts-ignore 5 | const libraryName = "libQMIParserDynamic.dylib" 6 | 7 | function listenCallback(functionName: string, argumentNames: string[]): InvocationListenerCallbacks { 8 | return { 9 | onEnter: function (args) { 10 | console.log(`${libraryName}:${functionName} (onEnter)`); 11 | 12 | // Print all arguments with their names 13 | for (let i = 0; i < argumentNames.length; i++) { 14 | console.log(`${argumentNames[i]}: ${args[i]}`); 15 | } 16 | 17 | // Print the backtrace causing the call of the initial instruction 18 | console.log('Backtrace:' + 19 | Thread.backtrace(this.context, Backtracer.ACCURATE) 20 | .map(DebugSymbol.fromAddress).join('\n')); 21 | 22 | console.log(''); 23 | }, 24 | onLeave: function (returnValue) { 25 | console.log(`${libraryName}:${functionName} (onLeave)`); 26 | 27 | // Print the return value 28 | console.log(`Return Value: ${returnValue}`); 29 | 30 | console.log(''); 31 | } 32 | } 33 | } 34 | 35 | // Function: qmi::MessageBase::validateMsgId 36 | Interceptor.attach( 37 | Module.findExportByName('libQMIParserDynamic.dylib', '_ZN3qmi11MessageBase13validateMsgIdEt')!, 38 | listenCallback( 39 | 'qmi::MessageBase::validateMessageID', 40 | ['MessageBase (this)', 'message_id'] 41 | ) 42 | ); 43 | 44 | // Function: qmi::MessageBase::MessageBase (constructor) 45 | Interceptor.attach( 46 | Module.findExportByName('libQMIParserDynamic.dylib', '_ZN3qmi11MessageBaseC1EtNS_5ErrorE')!, 47 | listenCallback( 48 | 'qmi::MessageBase::MessageBase', 49 | ['MessageBase (this)', 'message_id', 'error'] 50 | ) 51 | ); 52 | 53 | // Function: qmi::MutableMessageBase::MutableMessageBase (constructor) 54 | Interceptor.attach( 55 | Module.findExportByName('libQMIParserDynamic.dylib', '_ZN3qmi18MutableMessageBaseC2Et')!, 56 | listenCallback( 57 | 'qmi::MutableMessageBase::MutableMessageBase', 58 | ['MessageBase (this)', 'message_id'] 59 | ) 60 | ); 61 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/explore_frida_constructors.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // @ts-ignore 4 | const libraryName = "libQMIParserDynamic.dylib" 5 | 6 | function interceptMessageId(matches: ApiResolverMatch[]) { 7 | // Hook each match 8 | for (let match of matches) { 9 | Interceptor.attach(match.address, { 10 | onEnter: function (args) { 11 | // Store the first argument which is a reference to the object itself 12 | this.objectPointer = args[0]; 13 | }, 14 | onLeave: function (retval) { 15 | // Read the message id from the object (stored at the beginning) after the constructor has finished 16 | const pointer: NativePointer = this.objectPointer 17 | const msgId = pointer.readUShort() 18 | console.log(`${match.name} -> 0x${msgId.toString(16)}`); 19 | } 20 | }) 21 | } 22 | } 23 | 24 | // Hook into all constructors of MessageBase or MutableMessageBase 25 | interceptMessageId(new ApiResolver('module').enumerateMatches(`exports:${libraryName}!*MessageBaseC*`)) 26 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/utils/CursorLocation.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import ghidra.program.model.address.Address; 4 | import ghidra.program.model.listing.Program; 5 | import ghidra.program.util.LabelFieldLocation; 6 | import ghidra.program.util.OperandFieldLocation; 7 | import ghidra.program.util.ProgramLocation; 8 | 9 | public class CursorLocation { 10 | 11 | public static Address findLocation(ProgramLocation currentLocation, Address currentAddress) { 12 | Address targetLocation = findSpecialLocation(currentLocation); 13 | // Ensure that we don't use a refAddress property which was null 14 | return targetLocation != null ? targetLocation : currentAddress; 15 | } 16 | 17 | private static Address findSpecialLocation(ProgramLocation currentLocation) { 18 | if (currentLocation instanceof LabelFieldLocation) { 19 | // The reference address in this case can be sometimes null 20 | return currentLocation.getRefAddress(); 21 | } else if (currentLocation instanceof OperandFieldLocation) { 22 | // In this case the address can also be outside the allocated program space 23 | return currentLocation.getRefAddress(); 24 | } 25 | 26 | return null; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/utils/FunctionNames.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import ghidra.app.script.GhidraScript; 4 | import ghidra.program.model.address.Address; 5 | import ghidra.program.model.listing.Function; 6 | import ghidra.program.model.listing.Program; 7 | import ghidra.program.model.symbol.Symbol; 8 | 9 | import java.util.LinkedList; 10 | 11 | public class FunctionNames { 12 | 13 | /** 14 | * Tries to determine the best available 'underscore' function name for the given function. 15 | * As this name exposes the most amount of information about the function. 16 | * This function also tries to resolve unnamed functions (which names start with FUN_) 17 | * by going up in the call hierarchy. 18 | * 19 | * @param function the function to search the name for 20 | * @param script the instance of the script for logging & the current program 21 | * @return the best-possible function name for the function 22 | */ 23 | public static String extractFullFunctionName(Function function, GhidraScript script) { 24 | String functionName = function.getName(true); 25 | 26 | // Try to resolve unnamed functions (which names start with FUN_) by looking up in the call hierarchy 27 | // or boring function names. 28 | if (functionName.startsWith("FUN_")) { 29 | String nameFromCallers = findNameFromCallers(function); 30 | if (nameFromCallers != null) { 31 | return nameFromCallers; 32 | } 33 | } 34 | 35 | // If the function's name is already in the underscore format, we can return it. 36 | if (functionName.startsWith("__") && !functionName.equals("__invoke")) { 37 | return functionName; 38 | } 39 | 40 | // If not, we'll try to get its underscore representation. 41 | if (function.getEntryPoint() != null) { 42 | String underscoreFunctionName = getUnderscoreSymbol(function.getEntryPoint(), script.getCurrentProgram()); 43 | if (underscoreFunctionName != null) { 44 | return underscoreFunctionName; 45 | } 46 | } else { 47 | script.println("Entry Point of function is null: " + function.getName(true)); 48 | } 49 | 50 | // If nothing of our approaches helped to improve the function name, we just return it as is. 51 | return functionName; 52 | } 53 | 54 | /** 55 | * Tries to resolve unnamed functions (which names start with FUN_) by following the call hierarchy. 56 | *

57 | * This approach is useful for binaries without symbols like 'locationd'. 58 | * To annotate those binaries with some names, it's important to run the ExtractDebugFunctionNames beforehand. 59 | *

60 | * We perform this search up until a depth of 6 levels. 61 | * 62 | * @param firstFunction the function to analyze 63 | * @return the name of a function in the hierarchy or null if no better name could be found 64 | */ 65 | public static String findNameFromCallers(Function firstFunction) { 66 | LinkedList queue = new LinkedList<>(); 67 | 68 | // Add all references to the function to the queue with level zero. 69 | for (Function callingFunction : firstFunction.getCallingFunctions(null)) { 70 | queue.offer(new CallLevel(callingFunction, 0)); 71 | } 72 | 73 | // Loop while there are references to search 74 | while (!queue.isEmpty()) { 75 | // Get the first element from the queue 76 | CallLevel callLevel = queue.poll(); 77 | 78 | // Check if the function has is named and if yes, return its name combined with its namespace. 79 | String name = callLevel.function.getName(); 80 | String namespace = callLevel.function.getParentNamespace().getName(); 81 | if (!name.startsWith("FUN_") && !name.startsWith("thunk_FUN_")) { 82 | return namespace + "::" + name; 83 | } 84 | 85 | // Don't continue searching after six levels of call hierarchy. 86 | if (callLevel.level >= 6) { 87 | continue; 88 | } 89 | 90 | // Add all references to this function to the queue with the level increased by one. 91 | for (Function callingFunction : callLevel.function.getCallingFunctions(null)) { 92 | queue.offer(new CallLevel(callingFunction, callLevel.level + 1)); 93 | } 94 | } 95 | 96 | // If no better name has been found, return null. 97 | return null; 98 | } 99 | 100 | public record CallLevel(Function function, int level) { 101 | } 102 | 103 | /** 104 | * Searches a symbol starting with two underscores for the given address. 105 | * Null is returned if non can be found. 106 | * 107 | * @param address the address 108 | * @return the symbol name for the address starting with two underscores or null if non can be found 109 | */ 110 | public static String getUnderscoreSymbol(Address address, Program program) { 111 | Symbol[] symbols = program.getSymbolTable().getSymbols(address); 112 | for (Symbol symbol : symbols) { 113 | if (symbol.getName().startsWith("__") && !symbol.getName().equals("__invoke")) { 114 | return symbol.getName(); 115 | } 116 | } 117 | 118 | return null; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /qmi-dissect/dissector/research/utils/InstructionUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | import ghidra.program.model.address.Address; 4 | import ghidra.program.model.lang.Register; 5 | import ghidra.program.model.listing.Instruction; 6 | import ghidra.program.model.listing.Program; 7 | import ghidra.program.model.scalar.Scalar; 8 | import ghidra.program.model.symbol.Symbol; 9 | 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | public class InstructionUtils { 14 | 15 | public static Optional

extractBranchTarget(Instruction instruction) { 16 | Object[] opObjects = instruction.getOpObjects(0); 17 | if (opObjects.length < 1) 18 | return Optional.empty(); 19 | 20 | if (!(opObjects[0] instanceof Address address)) 21 | return Optional.empty(); 22 | 23 | return Optional.of(address); 24 | } 25 | 26 | public static List extractBranchTargetSymbols(Instruction instruction, Program program) { 27 | Object[] opObjects = instruction.getOpObjects(0); 28 | if (opObjects.length < 1 || !(opObjects[0] instanceof Address address)) { 29 | return List.of(); 30 | } 31 | 32 | return List.of(program.getSymbolTable().getSymbols(address)); 33 | } 34 | 35 | public static Optional extractParameter(Instruction instruction, String targetRegister, int opIndex) { 36 | // Check that an instruction was found 37 | if (instruction == null) 38 | return Optional.empty(); 39 | 40 | // Check that the first operand points to the correct memory location 41 | if (targetRegister != null) { 42 | Object[] firstOpObjects = instruction.getOpObjects(0); 43 | if (firstOpObjects.length < 1 || !(firstOpObjects[0] instanceof Register register)) { 44 | return Optional.empty(); 45 | } 46 | 47 | if (!register.getName().equals(targetRegister)) { 48 | return Optional.empty(); 49 | } 50 | } 51 | 52 | // Check that the second or third operand exists and is a scalar 53 | Object[] targetOpObject = instruction.getOpObjects(opIndex); 54 | if (targetOpObject.length < 1) { 55 | return Optional.empty(); 56 | } 57 | 58 | // Get the value of the scalar 59 | return Optional.of(targetOpObject[0]); 60 | } 61 | 62 | public static Optional extractConstantParameter(Instruction instruction, String targetRegister, int opIndex) { 63 | Optional o = extractParameter(instruction, targetRegister, opIndex); 64 | 65 | if (o.isPresent() && o.get() instanceof Scalar scalar) { 66 | return Optional.of(scalar.getValue()); 67 | } 68 | 69 | return Optional.empty(); 70 | } 71 | 72 | public static boolean compareStore(Instruction instruction, String readRegName, String targetRegName) { 73 | if (instruction == null) 74 | return false; 75 | 76 | if (readRegName != null) { 77 | Object[] opObjectsFirst = instruction.getOpObjects(0); 78 | if (opObjectsFirst.length < 1 || !(opObjectsFirst[0] instanceof Register readRegister)) { 79 | return false; 80 | } 81 | if (!readRegister.getName().equals(readRegName)) { 82 | return false; 83 | } 84 | } 85 | 86 | if (targetRegName != null) { 87 | Object[] opObjectsSecond = instruction.getOpObjects(1); 88 | if (opObjectsSecond.length < 1 || !(opObjectsSecond[0] instanceof Register targetRegister)) { 89 | return false; 90 | } 91 | if (!targetRegister.getName().equals(targetRegName)) { 92 | return false; 93 | } 94 | } 95 | 96 | return true; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /qmi-dissect/logarchive/.gitignore: -------------------------------------------------------------------------------- 1 | # RustRover 2 | .idea 3 | 4 | # Generated by Cargo 5 | # will have compiled files and executables 6 | debug/ 7 | target/ 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # MSVC Windows builds of rustc generate these, which store debugging information 13 | *.pdb 14 | 15 | # Outputs 16 | *.csv -------------------------------------------------------------------------------- /qmi-dissect/logarchive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unifiedlog_parser" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | simplelog = "0.12.2" 10 | csv = "1.3.0" 11 | chrono = "0.4.38" 12 | log = "0.4.22" 13 | macos-unifiedlogs = { git = "https://github.com/mandiant/macos-UnifiedLogs", branch = "main" } 14 | clap = { version = "4.5.20", features = ["derive"] } -------------------------------------------------------------------------------- /qmi-dissect/logarchive/README.md: -------------------------------------------------------------------------------- 1 | # Logarchive Parser 2 | 3 | This is a parser for .logarchive files focused on QMI packets based on [macos-UnifiedLogs](https://github.com/mandiant/macos-UnifiedLogs). 4 | 5 | The resulting .csv file only contains log messages related to QMI packets. 6 | 7 | ## Build 8 | 9 | Install the [Rust toolchain](https://www.rust-lang.org/tools/install) on your system. 10 | 11 | ```sh 12 | # Build the executable 13 | cargo build --release 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```sh 19 | # Read logarchive from a sysdiagnose into the file parsed-qmi-logarchive.csv. 20 | # The console may show warnings or errors while running this command, however they don't affect the packet log messages. 21 | ./target/release/unifiedlog_parser -i ~/sysdiagnose_2024.04.12_11-19-45+0200_iPhone-OS_iPhone_21E236/system_logs.logarchive -o parsed-qmi-logarchive.csv 22 | ``` 23 | -------------------------------------------------------------------------------- /qmi-dissect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iphone-qmi-wireshark-agent", 3 | "version": "1.0.0", 4 | "description": "Frida agent written in TypeScript", 5 | "private": true, 6 | "main": "agent/index.ts", 7 | "scripts": { 8 | "build": "frida-compile agent/index.ts -o _agent.js -c", 9 | "watch": "frida-compile agent/index.ts -o _agent.js -w" 10 | }, 11 | "devDependencies": { 12 | "@types/frida-gum": "^18.7.1", 13 | "@types/node": "~20.9.5", 14 | "frida-compile": "^10.2.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /qmi-dissect/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["es2020"], 5 | "allowJs": true, 6 | "noEmit": true, 7 | "strict": true, 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /qmi-dissect/watch_cellguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Watch and capture QMI packets from a .cells2 file exported by the CellGuard iOS app 4 | # Inspired / parts from https://github.com/seemoo-lab/aristoteles/blob/master/tools/watch_frida.py (MIT License) 5 | 6 | import argparse 7 | import base64 8 | from datetime import datetime 9 | import tempfile 10 | import zipfile 11 | from enum import Enum 12 | from pathlib import Path 13 | from typing import Optional 14 | 15 | import pandas as pd 16 | from tqdm import tqdm 17 | 18 | from wireshark import Wireshark 19 | 20 | 21 | class Protocol(Enum): 22 | QMI = "QMI" 23 | ARI = "ARI" 24 | 25 | 26 | class WatchCellGuard(Wireshark): 27 | """ Inspect QMI packets in Wireshark extracted from a CellGuard export file. """ 28 | 29 | def __init__(self, verbose: bool, file: Path, start: Optional[int], end: Optional[int], protocols: [Protocol]): 30 | super().__init__(verbose) 31 | self.read_completed = False 32 | self.file_path = file.absolute() 33 | self.parameter_start = datetime.fromtimestamp(start) if start else None 34 | self.parameter_end = datetime.fromtimestamp(end) if end else None 35 | self.protocols = [p.value for p in protocols] 36 | 37 | def start_input_monitor(self) -> bool: 38 | if self.read_completed: 39 | print(f"Input was already read") 40 | return True 41 | 42 | print(f"Reading QMI packets from the CellGuard export...") 43 | 44 | with tempfile.TemporaryDirectory() as tmp_dir: 45 | # Extract packets.csv from cells2 archive 46 | with zipfile.ZipFile(self.file_path) as zf: 47 | try: 48 | packets_in_zip = zf.getinfo('packets.csv') 49 | except KeyError: 50 | print(f'The CellGuard export contains no packets.csv!') 51 | return False 52 | 53 | packets_csv_path = Path(zf.extract(packets_in_zip, tmp_dir)) 54 | print(f"Extracted packets.csv to {packets_csv_path}") 55 | 56 | # Read packets from packet.csv 57 | df: pd.DataFrame = pd.read_csv(packets_csv_path, header=0, index_col=False) 58 | 59 | df['collected'] = pd.to_datetime(df['collected'], unit='s') 60 | 61 | # Filter packets 62 | df = df.loc[df['proto'].isin(self.protocols)] 63 | 64 | if self.parameter_start: 65 | df = df.loc[df['collected'] >= self.parameter_start] 66 | 67 | if self.parameter_end: 68 | df = df.loc[df['collected'] <= self.parameter_end] 69 | 70 | # Check if the export contains any QMI packets 71 | packet_count = len(df.index) 72 | if packet_count == 0: 73 | print(f'The CellGuard export contains no {self.protocols} packets (within the selected parameters)!') 74 | return False 75 | 76 | # Sort by timestamp 77 | df.sort_values(by=['collected'], inplace=True) 78 | 79 | # Get start & end time 80 | start_time = df['collected'].iloc[1] 81 | end_time = df['collected'].iloc[-1] 82 | self.start_time = start_time.timestamp() 83 | 84 | print(f'First packet: {start_time} UTC') 85 | print(f'Last packet: {end_time} UTC') 86 | 87 | for packet in tqdm(df.itertuples(index=False), total=packet_count): 88 | # noinspection PyUnresolvedReferences 89 | data = base64.decodebytes(bytes(packet.data, 'utf-8')) 90 | # noinspection PyUnresolvedReferences 91 | collected = packet.collected 92 | self.feed_wireshark(data, collected.timestamp()) 93 | 94 | self.read_completed = True 95 | print(f"{packet_count} QMI packets have been successfully imported into Wireshark.") 96 | return True 97 | 98 | def check_input_monitor(self) -> bool: 99 | return True 100 | 101 | 102 | def main(): 103 | arg_parser = argparse.ArgumentParser( 104 | description='Reads a .cells2 file exported by CellGuard iOS app ' 105 | 'and redirects the binary QMI packets to Wireshark.') 106 | arg_parser.add_argument('-f', '--file', required=True, type=Path, help='The cells file to process') 107 | arg_parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose logs') 108 | 109 | arg_parser.add_argument('-qmi', '--qmi', action='store_true', help='Only import QMI packets') 110 | arg_parser.add_argument('-ari', '--ari', action='store_true', help='Only import ARI packets') 111 | 112 | arg_parser.add_argument('--start', type=int, help='Only process packets younger than the given UNIX timestamp.') 113 | arg_parser.add_argument('--end', type=int, help='Only process packets older than the given UNIX timestamp.') 114 | 115 | args = arg_parser.parse_args() 116 | 117 | file_path: Path = args.file 118 | if not file_path.is_file(): 119 | print('No file is present at the given path!') 120 | exit(1) 121 | 122 | if file_path.suffix != '.cells2': 123 | print('The given file is not a CellGuard export JSON file as it has the wrong file extension!') 124 | exit(1) 125 | 126 | if args.start and args.end and args.start > args.end: 127 | print('The start timestamp may not be larger than the end timestamp.') 128 | exit(1) 129 | 130 | protocols = [] 131 | if args.qmi: 132 | protocols.append(Protocol.QMI) 133 | if args.ari: 134 | protocols.append(Protocol.ARI) 135 | 136 | # If nothing is selected, use both protocols 137 | if len(protocols) == 0: 138 | protocols.append(Protocol.QMI) 139 | protocols.append(Protocol.ARI) 140 | 141 | watcher = WatchCellGuard(args.verbose, file_path, args.start, args.end, protocols) 142 | watcher.start_monitor() 143 | 144 | 145 | # Call script 146 | if __name__ == "__main__": 147 | main() 148 | -------------------------------------------------------------------------------- /qmi-dissect/watch_frida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Watch and capture QMI packets by intercepting the packets with frida 4 | # You need to have permissions for executing the dumpcap (you have to be part of the "wireshark" group or run this as sudo) 5 | # Inspired / parts from https://github.com/seemoo-lab/aristoteles/blob/master/tools/watch_frida.py (MIT License) 6 | 7 | import argparse 8 | from pathlib import Path 9 | 10 | import frida 11 | 12 | from wireshark import Wireshark 13 | 14 | 15 | class WatchFrida(Wireshark): 16 | """ Inspect QMI packets in Wireshark extracted using Frida. """ 17 | 18 | def __init__(self, verbose: bool, direction_bit: bool): 19 | super().__init__(verbose) 20 | self.frida_script = None 21 | self.direction_bit = direction_bit 22 | 23 | def _spawn_frida_script(self): 24 | frida_session = frida.get_usb_device(1).attach("CommCenter") 25 | 26 | frida_script_file = Path('_agent.js') 27 | if not frida_script_file.exists(): 28 | print("Please compile the agent using 'npm run build'") 29 | return False 30 | 31 | self.frida_script = frida_session.create_script(frida_script_file.read_text()) 32 | self.frida_script.load() 33 | 34 | print(" * Initialized functions with Frida.") 35 | 36 | return True 37 | 38 | def on_msg(self, message, data): 39 | if message['type'] == 'send': 40 | # Baseband --QMI-> iPhone's application processor 41 | if message['payload'] == 'qmi_read': 42 | # If enabled, use the first byte to track the source of the message 43 | if self.direction_bit: 44 | data = b'\x00' + data 45 | hex_str = data.hex() 46 | self.feed_wireshark(hex_str) 47 | if self.verbose: 48 | print("incoming qmi read message:") 49 | print(hex_str) 50 | # iPhone's application processor --QMI-> Baseband 51 | if message['payload'] == 'qmi_send': 52 | # If enabled, use the first byte to track the source of the message 53 | if self.direction_bit: 54 | data = b'\x01' + data 55 | hex_str = data.hex() 56 | self.feed_wireshark(hex_str) 57 | if self.verbose: 58 | print('incoming qmi send message:') 59 | print(hex_str) 60 | 61 | def start_input_monitor(self) -> bool: 62 | if self.frida_script is None: 63 | if not self._spawn_frida_script(): 64 | print("Unable to initialize Frida script") 65 | return False 66 | 67 | self.frida_script.on('message', self.on_msg) 68 | return True 69 | 70 | def check_input_monitor(self) -> bool: 71 | if self.frida_script.is_destroyed: 72 | # Script is destroyed 73 | print("_pollTimer: Frida script has been destroyed") 74 | self.frida_script = None 75 | return False 76 | else: 77 | return True 78 | 79 | def kill_input_monitor(self): 80 | if self.frida_script is not None: 81 | print("Killing Frida script...") 82 | self.frida_script.unload() 83 | self.frida_script = None 84 | 85 | def kill_monitor(self): 86 | super().kill_monitor() 87 | if self.frida_script is not None: 88 | print("Killing Frida script...") 89 | self.frida_script.unload() 90 | self.frida_script = None 91 | 92 | 93 | # Call script 94 | if __name__ == "__main__": 95 | arg_parser = argparse.ArgumentParser( 96 | description="Intercepts QMI messages using frida and pipes the output to wireshark for live monitoring.") 97 | arg_parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose logs') 98 | arg_parser.add_argument( 99 | '--directionbit', 100 | action='store_true', 101 | help='Add a direction bit to the packets sent to Wireshark. ' 102 | 'Warning: Requires a special build of the Wireshark dissector.' 103 | ) 104 | args = arg_parser.parse_args() 105 | 106 | watcher = WatchFrida(args.verbose, args.directionbit) 107 | 108 | watcher.start_monitor() 109 | -------------------------------------------------------------------------------- /qmi-dissect/watch_syslog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Watch and capture QMI packets from the running idevicesyslog 4 | # You need to have permissions for executing the dumpcap (you have to be part of the "wireshark" group or run this as sudo) 5 | # Inspired / parts from https://github.com/seemoo-lab/aristoteles/blob/master/tools/watch_frida.py (MIT License) 6 | 7 | import argparse 8 | import io 9 | import os 10 | import re 11 | import subprocess 12 | 13 | from wireshark import Wireshark 14 | from shutil import which 15 | 16 | 17 | class WatchSyslog(Wireshark): 18 | """ Inspect QMI packets in Wireshark extracted using the idevicesyslog utility. """ 19 | 20 | def __init__(self, verbose: bool): 21 | super().__init__(verbose) 22 | self.syslog_process = None 23 | 24 | def _spawn_device_syslog(self) -> bool: 25 | if which("idevicesyslog") is None: 26 | print("idevicesyslog not found!") 27 | return False 28 | 29 | DEVNULL = open(os.devnull, "wb") 30 | 31 | self.syslog_process = subprocess.Popen( 32 | "idevicesyslog", 33 | stdout=subprocess.PIPE, 34 | stderr=DEVNULL, 35 | ) 36 | 37 | return True 38 | 39 | def check_input_monitor(self) -> bool: 40 | if self.syslog_process.poll() == 0: 41 | print("_pollTimer: Syslog has terminated") 42 | self.syslog_process = None 43 | return False 44 | else: 45 | return True 46 | 47 | def start_input_monitor(self) -> bool: 48 | if self.syslog_process is None: 49 | if not self._spawn_device_syslog(): 50 | print("Unable to start Syslog") 51 | return False 52 | 53 | for line in io.TextIOWrapper(self.syslog_process.stdout, encoding="utf-8"): 54 | bin_content = re.search(r".*CommCenter.*Bin=\['(.*)']", line) 55 | if bin_content is not None: 56 | self.feed_wireshark(bin_content.group(1).lower().replace(" ", "")) 57 | 58 | return True 59 | 60 | def kill_input_monitor(self) -> None: 61 | if self.syslog_process is not None: 62 | print("Killing Syslog process...") 63 | try: 64 | self.syslog_process.terminate() 65 | self.syslog_process.wait() 66 | except OSError: 67 | print("Error during syslog process termination") 68 | self.syslog_process = None 69 | 70 | 71 | # Call script 72 | if __name__ == "__main__": 73 | arg_parser = argparse.ArgumentParser( 74 | description="Attaches to the idevicesyslog and pipes the output to wireshark for live monitoring.") 75 | arg_parser.add_argument('-v', '--verbose', action='store_true', help='Print verbose logs') 76 | args = arg_parser.parse_args() 77 | 78 | watcher = WatchSyslog(args.verbose) 79 | 80 | watcher.start_monitor() 81 | -------------------------------------------------------------------------------- /qmi-dissect/wireshark.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import subprocess 4 | import time 5 | from threading import Timer 6 | from typing import Optional 7 | 8 | 9 | class Wireshark: 10 | """ An abstract class allowing to send binary packets to a Wireshark instance. """ 11 | 12 | def __init__(self, verbose: bool): 13 | """ 14 | Initialize the Wireshark class. 15 | 16 | @param verbose: whether verbose messages should be logged 17 | """ 18 | self.running = False 19 | self.wireshark_process = None 20 | self.poll_timer = None 21 | self.pcap_data_link_type = 147 # Is DLT_USER_0 22 | self.start_time = time.time() 23 | self.verbose = verbose 24 | 25 | def _spawn_wireshark(self) -> bool: 26 | """ 27 | Initializes the pipe to Wireshark and starts it 28 | 29 | @return: whether Wireshark could be started 30 | """ 31 | # Global Header Values 32 | # https://wiki.wireshark.org/Development/LibpcapFileFormat 33 | PCAP_GLOBAL_HEADER_FMT = "@ I H H i I I I " 34 | PCAP_MAGICAL_NUMBER = 2712847316 35 | PCAP_MJ_VERN_NUMBER = 2 36 | PCAP_MI_VERN_NUMBER = 4 37 | PCAP_LOCAL_CORECTIN = 0 38 | PCAP_ACCUR_TIMSTAMP = 0 39 | PCAP_MAX_LENGTH_CAP = 65535 40 | PCAP_DATA_LINK_TYPE = self.pcap_data_link_type 41 | 42 | pcap_header = struct.pack( 43 | PCAP_GLOBAL_HEADER_FMT, 44 | PCAP_MAGICAL_NUMBER, 45 | PCAP_MJ_VERN_NUMBER, 46 | PCAP_MI_VERN_NUMBER, 47 | PCAP_LOCAL_CORECTIN, 48 | PCAP_ACCUR_TIMSTAMP, 49 | PCAP_MAX_LENGTH_CAP, 50 | PCAP_DATA_LINK_TYPE, 51 | ) 52 | 53 | DEVNULL = open(os.devnull, "wb") 54 | 55 | # Check if wireshark or wireshark-gtk is installed. If both are 56 | # present, default to wireshark. 57 | if os.path.isfile("/usr/bin/wireshark"): 58 | wireshark_binary = "wireshark" 59 | elif os.path.isfile("/usr/bin/wireshark-gtk"): 60 | wireshark_binary = "wireshark-gtk" 61 | elif os.path.isfile("/Applications/Wireshark.app/Contents/MacOS/Wireshark"): 62 | wireshark_binary = "/Applications/Wireshark.app/Contents/MacOS/Wireshark" 63 | else: 64 | print("Wireshark not found!") 65 | return False 66 | 67 | self.wireshark_process = subprocess.Popen( 68 | [wireshark_binary, "-k", "-i", "-"], 69 | stdin=subprocess.PIPE, 70 | stderr=DEVNULL, 71 | ) 72 | self.wireshark_process.stdin.write(pcap_header) 73 | 74 | self.poll_timer = Timer(3, self._poll_timer, ()) 75 | self.poll_timer.start() 76 | return True 77 | 78 | def _poll_timer(self) -> None: 79 | """ 80 | A timer to check whether all processes are functioning as expected. 81 | If not, everything is terminated. 82 | 83 | @return: nothing 84 | """ 85 | if self.running and self.wireshark_process is not None: 86 | if self.wireshark_process.poll() == 0: 87 | # Process has ended 88 | print("_pollTimer: Wireshark has terminated") 89 | self.kill_monitor() 90 | self.wireshark_process = None 91 | elif not self.check_input_monitor(): 92 | self.kill_monitor() 93 | else: 94 | # schedule new timer 95 | self.poll_timer = Timer(3, self._poll_timer, ()) 96 | self.poll_timer.start() 97 | 98 | def check_input_monitor(self) -> bool: 99 | """ 100 | An abstract method to check whether input monitor is still running properly. 101 | 102 | @return: whether the input monitor is still running properly 103 | """ 104 | return False 105 | 106 | def start_monitor(self) -> bool: 107 | """ 108 | Starts the monitor 109 | 110 | @return: 111 | """ 112 | if self.running: 113 | print("Monitor already running!") 114 | return False 115 | 116 | if self.wireshark_process is None: 117 | if not self._spawn_wireshark(): 118 | print("Unable to start Wireshark.") 119 | return False 120 | 121 | if not self.start_input_monitor(): 122 | return False 123 | 124 | self.running = True 125 | 126 | print("Monitor started.") 127 | 128 | return True 129 | 130 | def start_input_monitor(self) -> bool: 131 | """ 132 | An abstract method to start the input monitor. 133 | 134 | @return: whether the start of the input monitor was successful 135 | """ 136 | return True 137 | 138 | def feed_wireshark(self, data: str | bytes, packet_time: Optional[float] = None) -> None: 139 | """ 140 | Sends packet data encoding as a hex string to Wireshark. 141 | 142 | @param data: the data of the packet encoded as a hex string 143 | @param packet_time: a UNIX timestamp indicating when the QMI packet was received, defaults to now 144 | @return: nothing 145 | """ 146 | if not packet_time: 147 | packet_time = time.time() 148 | 149 | packet = bytes.fromhex(data) if type(data) == str else data 150 | length = len(packet) 151 | ts_sec = int(packet_time) 152 | ts_usec = int((packet_time % 1) * 1_000_000) 153 | pcap_packet = ( 154 | struct.pack("@ I I I I", ts_sec, ts_usec, length, length) + packet 155 | ) 156 | try: 157 | self.wireshark_process.stdin.write(pcap_packet) 158 | self.wireshark_process.stdin.flush() 159 | except IOError as e: 160 | print("Monitor: broken pipe. terminate." f"{e}") 161 | self.kill_monitor() 162 | 163 | def kill_monitor(self) -> None: 164 | """ 165 | Kills the active monitor and terminates all associated processes. 166 | 167 | @return: nothing 168 | """ 169 | if self.running: 170 | self.running = False 171 | print("Monitor stopped.") 172 | if self.poll_timer is not None: 173 | self.poll_timer.cancel() 174 | self.poll_timer = None 175 | if self.wireshark_process is not None: 176 | print("Killing Wireshark process...") 177 | try: 178 | self.wireshark_process.terminate() 179 | self.wireshark_process.wait() 180 | except OSError: 181 | print("Error during wireshark process termination") 182 | self.wireshark_process = None 183 | self.kill_input_monitor() 184 | 185 | def kill_input_monitor(self) -> None: 186 | """ 187 | An abstract method to kill the input monitor and clean up everything. 188 | 189 | @return: nothing 190 | """ 191 | pass 192 | -------------------------------------------------------------------------------- /qmi-inject/README.md: -------------------------------------------------------------------------------- 1 | # iPhone QMI Glue 2 | 3 | The *glue* acts a connection layer between libqmi and the baseband processor of a smartphone. 4 | 5 | ## Supported devices 6 | 7 | We've tested this tooling with an iPhone 12 mini on iOS 14.2.1. 8 | 9 | You may have to adapt the method signatures for subsequent iOS versions. 10 | 11 | ## Setup 12 | 13 | Due to the build requirements of libqmi, you must use a Linux-based operating system. 14 | We recommend [Debian 11](https://www.debian.org/download) which you can either use as your host operating system or as a virtual machine with a shared network. This is the default setting if you're creating a VM with [UTM](https://mac.getutm.app) on a Mac. 15 | 16 | Install [Node.js for your system](https://github.com/nodesource/distributions/blob/master/README.md), the example script is built for Debian: 17 | ```bash 18 | ./scripts/install-nodejs.sh 19 | ``` 20 | 21 | You can usually install Frida via pip: 22 | ```bash 23 | pip install frida-tools 24 | ``` 25 | If there's an error, you can try to build & install Frida yourself using the provided script: 26 | ```bash 27 | ./scripts/install-frida.sh 28 | ``` 29 | 30 | On all systems, you must install at least version 1.33.3 of libqmi. To build libqmi, you can use the provided script: 31 | ```bash 32 | ./scripts/install-libqmi.sh 33 | ``` 34 | 35 | ## Usage 36 | 37 | ## Smartphone 38 | 39 | 1. Jailbreak the target smartphone: 40 | - iPhone 12 (mini) with iOS 14.2.1: [unc0ver (TrollStore)](https://ios.cfw.guide/installing-unc0ver-trollstore/) 41 | 2. Install [Frida using Cydia](https://frida.re/docs/ios/). 42 | 43 | ## glue 44 | 45 | First compile the agent script with 46 | ```bash 47 | npm install 48 | npm run build 49 | ``` 50 | 51 | ### Linux-based host OS 52 | 53 | If you are running the Linux-based operating system as your host system, you can start the glue application with 54 | ```bash 55 | python3 glue.py -U 56 | ``` 57 | 58 | ### VM 59 | 60 | If you are running the Linux-based operating system inside of a VM, you must relay the Frida TCP port [27042](https://github.com/frida/frida/issues/70#issuecomment-186019188) to the VM. 61 | 1. Install [libimobiledevice](https://libimobiledevice.org) on the host system: 62 | - Mac with [homebrew](https://brew.sh): `brew install libimobiledevice` 63 | 2. Find the IP address of your host system inside the shared network with the VM: `ifconfig` or `ip a` 64 | - Mac with UTM: `192.168.64.1`, which we'll use from now on as an example, replace it if you're host system has another address in the shared network 65 | 3. Make the port available in the shared network: `iproxy 27042:27042 -s 192.168.64.1` 66 | 67 | Now you can start the glue application: 68 | ```bash 69 | python3 glue.py -H 192.168.64.1 70 | ``` 71 | 72 | ## Test 73 | 74 | Use qmicli on your Linux-based OS to test if everything works: 75 | ```bash 76 | qmicli -v -d ./qmux_socket --get-service-version-info 77 | ``` 78 | Between all the packet data, you should see a list of QMI services and not an error. 79 | -------------------------------------------------------------------------------- /qmi-inject/agent/index.ts: -------------------------------------------------------------------------------- 1 | import { injectQMI } from './send'; 2 | import './receive'; 3 | 4 | rpc.exports.injectqmi = injectQMI; -------------------------------------------------------------------------------- /qmi-inject/agent/receive.ts: -------------------------------------------------------------------------------- 1 | // *** Receiving direction *** 2 | // Air -> Chip -> iPhone 3 | 4 | import { log, LogLevel } from "./tools"; 5 | 6 | // libATCommandStudioDynamic.dylib 7 | // QMux::State::handleReadData(QMux::State *__hidden this, const unsigned __int8 *, unsigned int) 8 | // -> Part of the ICEPicker repository 9 | 10 | let lastQmux = 0; // save the last state of x0 11 | 12 | const handleReadData = Module.getExportByName('libATCommandStudioDynamic.dylib', '_ZN4QMux5State14handleReadDataEPKhj'); 13 | Interceptor.attach(handleReadData, { 14 | onEnter: function (args) { 15 | log(LogLevel.DEBUG, 'libATCommandStudioDynamic:QMux::State::handleReadData'); 16 | 17 | const armContext = this.context as Arm64CpuContext; 18 | 19 | // x0 points to __ZTVN4QMux5StateE + 8 (QMux::State) 20 | // there are qmux1 and qmux2 or so, so let's keep track of that. 21 | const currentQmux = parseInt(armContext.x0.toString()); 22 | if (lastQmux != currentQmux) { 23 | log(LogLevel.DEBUG, `qmux pointer changed: ${currentQmux}`); 24 | 25 | send('data', [0x23]); // Indicate with 0x23, QMI always starts with 0x01 26 | lastQmux = currentQmux; 27 | } 28 | 29 | var dst = armContext.x1; 30 | var len = parseInt(armContext.x2.toString()); 31 | var d = dst.readByteArray(len); 32 | log(LogLevel.DEBUG, d!); 33 | 34 | send('data', d); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /qmi-inject/agent/send.ts: -------------------------------------------------------------------------------- 1 | // *** Sending direction *** 2 | // iPhone -> Chip -> Air 3 | 4 | import { ghidraAddress, log, LogLevel } from "./tools"; 5 | 6 | // State required for injecting custom QMI packets 7 | let writeAsyncState: NativePointer | null = null; 8 | 9 | // Allocate 8 bytes of memory allowing a function to store its result 10 | const thTargetMemory = Memory.alloc(8); 11 | 12 | // libPCITransport.dylib!pci::system::info::get()::sInstance 13 | const pciSystemInfoGetSInstance = ghidraAddress('libPCITransport.dylib', '0x1c8474000', '0x1dffe19f0'); 14 | 15 | // libPCITransport.dylib!pci::system::info::getTH 16 | const pciSystemInfoGetTHAddr = ghidraAddress('libPCITransport.dylib', '0x1c8474000', '0x1c8475234', 'ia'); 17 | const pciSystemInfoGetTH = new NativeFunction(pciSystemInfoGetTHAddr, 'void', ['pointer', 'int']); 18 | 19 | function initializeWriteParameters(): void { 20 | // Prepare an interceptor of the function to be invoked 21 | let interceptor = Interceptor.attach(pciSystemInfoGetTH, { 22 | // Before we enter the function, we modify the register X8 23 | // X8 / XR is an indirect result return register: https://developer.arm.com/documentation/102374/0100/Procedure-Call-Standard 24 | // It points to a memory location where the 'this' pointer (result of the function invocation) will be written 25 | onEnter: function (args) { 26 | log(LogLevel.DEBUG, 'getTransportThis(): Interceptor: onEnter()'); 27 | (this.context as Arm64CpuContext).x8 = thTargetMemory; 28 | }, 29 | // After the function is complete, we read the 'this' pointer from the specified memory location, 30 | // detach the interceptor and start sending QMI packets. 31 | onLeave: function (returnValue) { 32 | const transportThis = thTargetMemory.readPointer(); 33 | log(LogLevel.DEBUG, `getTransportThis(): Interceptor: onLeave() with writeAsyncState = ${transportThis}`); 34 | 35 | // Detach the interceptor as we don't require it anymore 36 | interceptor.detach(); 37 | 38 | // Save the state information and signal it to the Python script 39 | writeAsyncState = transportThis; 40 | send('setup', [0x1]); 41 | } 42 | }); 43 | 44 | // Read the first parameter from a static location in memory 45 | const param1 = pciSystemInfoGetSInstance.readPointer(); 46 | 47 | // The second parameter (0x3) is a static value found by observation 48 | const param2 = 0x3; 49 | 50 | // Invoke the function with the two parameters 51 | log(LogLevel.DEBUG, `initializeWriteParameters(): pciSystemInfoGetTH(${param1}, ${param2})`); 52 | pciSystemInfoGetTH(param1, param2); 53 | } 54 | 55 | initializeWriteParameters(); 56 | 57 | // libPCITransport.dylib 58 | // bool pci::transport::th::writeAsync(*th this, byte[] data, uint length, void (*)(callback*)); 59 | // -> Found with using https://github.com/seemoo-lab/frida-scripts/blob/main/scripts/libdispatch.js 60 | 61 | // The maximum packet length is 0x7fff 62 | const payloadBuffer = Memory.alloc(0x8050); 63 | 64 | const writeAsyncAddr = ghidraAddress('libPCITransport.dylib', '0x1c8474000', '0x1c84868b4', 'ia'); 65 | const writeAsync = new NativeFunction(writeAsyncAddr, "bool", ["pointer", "pointer", "uint", "pointer"]); 66 | 67 | // The callback function (4th parameter) used during a normal write operation points to 68 | // libmav_ipc_router_dynamic.dylib!mav_router::device::pci_shim::dtor 69 | const writeAsyncCallback = Module.getExportByName('libmav_ipc_router_dynamic.dylib', '_ZN10mav_router6device8pci_shim4dtorEPv'); 70 | 71 | function injectQMI(payload: string) { 72 | if (!writeAsyncState || !writeAsyncCallback) { 73 | // TODO: Try to resend? 74 | console.warn("inject called although write state is not initialized"); 75 | return 76 | } 77 | 78 | const payloadArray: number[] = []; 79 | const payloadLength = payload.length / 2; 80 | 81 | // Read hex strings from payload and convert to byte array (F4118456 -> [244 17 132 86]) 82 | for (let i = 0; i < payload.length; i += 2) { 83 | payloadArray.push(parseInt(payload.substring(i, i + 2), 16)); 84 | } 85 | 86 | // Write content of array to our payload buffer 87 | payloadBuffer.writeByteArray(payloadArray); 88 | 89 | log(LogLevel.DEBUG, "libPCITransport::pci::transport::th::writeAsync"); 90 | // log(LogLevel.DEBUG, payload); 91 | log(LogLevel.DEBUG, payloadBuffer.readByteArray(payloadLength)!); 92 | log(LogLevel.DEBUG, `writeAsync: ${writeAsync}`); 93 | log(LogLevel.DEBUG, `writeAsyncState: ${writeAsyncState}`); 94 | log(LogLevel.DEBUG, `payloadBuffer: ${payloadBuffer}`); 95 | log(LogLevel.DEBUG, `payloadLength: ${payloadLength}`); 96 | log(LogLevel.DEBUG, `writeAsyncCallback: ${writeAsyncCallback}`); 97 | log(LogLevel.DEBUG, ''); 98 | 99 | // Call the function writeAsync with the correct state and payload 100 | // For now we ignore the writeAsyncCallback as it blocks a write operation and it works perfectly fine without it :) 101 | writeAsync(writeAsyncState, payloadBuffer, payloadLength, new NativePointer("0x0")); 102 | } 103 | 104 | export { injectQMI } -------------------------------------------------------------------------------- /qmi-inject/agent/tools.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a Ghidra pointer to an absolute address in memory which can be used for function interception and invocation. 3 | * 4 | * @param library the name of the library e.g. `libPCITransport.dylib` 5 | * @param baseAddress the base address of the library in Ghidra 6 | * @param functionAddress the target address in Ghidra 7 | * @param sign how the resulting pointer should be signed with a PAC (can be omitted) 8 | * @returns the absolute address targeting the specified function 9 | */ 10 | function ghidraAddress(library: string, baseAddress: string, functionAddress: string, sign?: PointerAuthenticationKey): NativePointer { 11 | // Get the base address of the library in memory and throw an exception if the library couldn't be found 12 | const memoryBaseAddress = Module.findBaseAddress(library); 13 | if (memoryBaseAddress == null) { 14 | throw `function at Ghidra address ${functionAddress} not found in ${library}`; 15 | } 16 | 17 | // Calculate the relative address in Ghidra and add it to the memory base address of the library 18 | const ghidraRelativeAddress = new NativePointer(functionAddress).sub(baseAddress); 19 | const absoluteAddress = memoryBaseAddress.add(ghidraRelativeAddress); 20 | 21 | // Sign the pointer if specified by the parameter 22 | if (sign) { 23 | return absoluteAddress.sign(sign); 24 | } else { 25 | return absoluteAddress; 26 | } 27 | } 28 | 29 | enum LogLevel { 30 | DEBUG, 31 | INFO, 32 | WARN, 33 | ERROR 34 | } 35 | 36 | const DEBUG = false; 37 | 38 | function log(level: LogLevel, message: string | object): void { 39 | if (typeof message === 'string') { 40 | message = `[iPhone] ${message}`; 41 | } 42 | switch (level) { 43 | case LogLevel.DEBUG: 44 | if (DEBUG) console.log(message); 45 | break; 46 | case LogLevel.INFO: 47 | console.log(message); 48 | break; 49 | case LogLevel.WARN: 50 | console.warn(message); 51 | break; 52 | case LogLevel.DEBUG: 53 | console.error(message); 54 | break; 55 | } 56 | } 57 | 58 | export {ghidraAddress, LogLevel, log} -------------------------------------------------------------------------------- /qmi-inject/glue.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import binascii 3 | import os 4 | import socket 5 | import time 6 | import frida 7 | 8 | 9 | def get_argparse(): 10 | parser = argparse.ArgumentParser( 11 | prog='iphone-qmi-glue', 12 | description='Connect an iPhone baseband chip via a unix domain socket to libqmi' 13 | ) 14 | parser.add_argument('-U', '--usb', action='store_true', 15 | help='connect to USB device') 16 | parser.add_argument( 17 | '-H', '--host', help='connect to remote frida-server on HOST') 18 | return parser 19 | 20 | 21 | class IPhoneQMIGlue: 22 | 23 | # Arguments supplied by argparse 24 | args = None 25 | 26 | # Socket connection 27 | socket_connection = None 28 | 29 | # Counts the number of received & sent QMI packets 30 | received = 0 31 | sent = 0 32 | 33 | # We assume that there are two different QMUX queues 34 | qmux1 = False 35 | 36 | # Wait for writeAsync parameters to be initialized 37 | write_state_init = False 38 | 39 | def __init__(self, args) -> None: 40 | self.args = args 41 | 42 | ### FRIDA ### 43 | 44 | # frida Python package documentation: 45 | # https://github.com/frida/frida-python/blob/a2643260742285acd5b19da6837e7b08c528d3e9/frida/__init__.py 46 | # https://github.com/frida/frida-python/blob/a2643260742285acd5b19da6837e7b08c528d3e9/frida/core.py 47 | 48 | def on_message(self, message, data): 49 | try: 50 | if message['payload'] == "setup": 51 | self.write_state_init = True 52 | return 53 | except KeyError: 54 | print("[FRIDA] There's an error in the script, debug it manually!") 55 | return 56 | 57 | # Manage invalid data and data direction 58 | if data is None: 59 | print("[FRIDA] Empty data, did CommCenter crash?") 60 | return 61 | # Data starts with 01 in rx direction so we use other magic bytes 62 | elif data[0] == 0x23: 63 | # alternative qmux queue, I think there are just two queues 64 | self.qmux1 = not self.qmux1 65 | return 66 | 67 | # Relays the received data from the iPhone to libqmi 68 | if self.socket_connection: 69 | self.socket_connection.sendall(data) 70 | 71 | self.received += 1 72 | 73 | def connect_to_device(self): 74 | if self.args.usb: 75 | return frida.get_device_manager().get_usb_device() 76 | elif self.args.host: 77 | # Connects to the FRIDA server on port 27042 running on the device over the network 78 | # e.g. VM (192.168.64.3) --UTM network--> Mac (192.168.64.1) --iproxy--> iPhone 79 | return frida.get_device_manager().add_remote_device(self.args.host) 80 | else: 81 | get_argparse().print_help() 82 | print('Specify a target device either with --usb or --host') 83 | quit(1) 84 | 85 | def load_script(self): 86 | device = self.connect_to_device() 87 | 88 | # Attaches to the CommCenter process 89 | frida_session = device.attach("CommCenter") 90 | 91 | # Loads the script to be injected from an external file 92 | # It uses function-based symbol and works only with Qualcomm chips. 93 | # The symbols work on an iPhone 12 with iOS 14.2.1 94 | with open('_agent.js', 'r') as file: 95 | script_code = file.read() 96 | 97 | script = frida_session.create_script(script_code) 98 | script.on("message", self.on_message) 99 | script.load() 100 | 101 | print("[FRIDA] Collecting write state information...") 102 | # print("[FRIDA] Tip: Unlock your device to collect state information") 103 | while not self.write_state_init: 104 | time.sleep(0.1) 105 | print("[FRIDA] Got all required state information") 106 | 107 | return script 108 | 109 | ### SOCKET ### 110 | 111 | # https://pymotw.com/2/socket/uds.html 112 | 113 | def open_socket(self): 114 | script = self.load_script() 115 | 116 | socket_address = './qmux_socket' 117 | 118 | # Try to delete an existing socket 119 | try: 120 | os.unlink(socket_address) 121 | except OSError: 122 | if os.path.exists(socket_address): 123 | raise 124 | 125 | # Create a UDS sockets 126 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 127 | 128 | # Bind the socket to the port 129 | print(f'[Socket] Opening Socket {socket_address}') 130 | sock.bind(socket_address) 131 | 132 | # Listen for incoming connections 133 | sock.listen(1) 134 | 135 | while True: 136 | # Wait for connections 137 | print('[Socket] Waiting for a connection') 138 | self.socket_connection, client_address = sock.accept() 139 | 140 | # Accept a connection 141 | try: 142 | if client_address == '': 143 | client_address = 'unknown' 144 | print(f'[Socket] Connection from {client_address}') 145 | 146 | while True: 147 | data = self.socket_connection.recv(16) 148 | 149 | if data: 150 | # Send QMI packets data back to the phone 151 | # Convert binary data to hex strings as it needs to be JSON serializable for FRIDA 152 | # print(binascii.hexlify(data).decode('ascii')) 153 | script.exports_sync.injectQMI( 154 | binascii.hexlify(data).decode('ascii')) 155 | else: 156 | print('[Socket] No more data from client') 157 | break 158 | finally: 159 | # Cleanup the connection 160 | self.socket_connection.close() 161 | self.socket_connection = None 162 | 163 | 164 | def main(): 165 | parser = get_argparse() 166 | glue = IPhoneQMIGlue(parser.parse_args()) 167 | glue.open_socket() 168 | 169 | 170 | if __name__ == '__main__': 171 | main() 172 | -------------------------------------------------------------------------------- /qmi-inject/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iphone-qmi-glue", 3 | "version": "1.0.0", 4 | "description": "Connecting an iPhone baseband chip & libqmi", 5 | "private": true, 6 | "main": "agent/index.ts", 7 | "scripts": { 8 | "prepare": "npm run build", 9 | "build": "frida-compile agent/index.ts -o _agent.js -c", 10 | "watch": "frida-compile agent/index.ts -o _agent.js -w" 11 | }, 12 | "devDependencies": { 13 | "@types/frida-gum": "^16.2.0", 14 | "@types/node": "^14.14.10", 15 | "frida-compile": "^10.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /qmi-inject/scripts/install-frida.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | FRIDA_VERSION="16.0.11" 4 | 5 | # Install python3 & pip 6 | sudo apt-get install python3 python3-pip 7 | # Install Python requirements for Frida 8 | pip install colorama prompt-toolkit pygments 9 | 10 | # Clone the Frida git repository 11 | git clone --recurse-submodules https://github.com/frida/frida.git 12 | # Go to the directory 13 | cd frida/ 14 | # Checkout the wanted Frida version 15 | git checkout $FRIDA_VERSION 16 | # Update all git submodules 17 | git submodule update --recursive 18 | 19 | # Build frida & its tools & Python bindings 20 | make tools-linux-arm64 21 | 22 | # Add Frida tools to the $PATH 23 | echo "PATH=\"$(pwd)/build/frida-linux-arm64/bin:\$PATH"\" > ~/.profile 24 | # Add Frida Python bindings to the local installation 25 | echo "$(pwd)/build/frida-linux-arm64/lib/python3.9/site-packages" > ~/.local/lib/python3.9/site-packages/frida.pth 26 | 27 | # Exit the build directory 28 | cd .. 29 | 30 | echo "Frida installed successfully" 31 | echo "Restart your shell to access the Frida tools" -------------------------------------------------------------------------------- /qmi-inject/scripts/install-libqmi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LIBQRTR_VERSION="qrtr-1-2" 4 | LIBQMI_VERSION="1.33.3" 5 | 6 | # Install all required dependencies 7 | sudo apt-get install meson python3 python3-setuptools python-is-python3 \ 8 | libglib2.0-dev libglib2.0-dev libgudev-1.0-dev libmbim-glib-dev \ 9 | libgirepository1.0-dev gtk-doc-tools help2man 10 | 11 | # Install libqrtr-glib 12 | git clone https://gitlab.freedesktop.org/mobile-broadband/libqrtr-glib.git 13 | cd libqrtr-glib 14 | git checkout $(LIBQRTR_VERSION) 15 | meson setup build --prefix=/usr 16 | ninja -C build 17 | sudo ninja -C build install 18 | cd .. 19 | 20 | # Install libqmi 21 | git clone https://gitlab.freedesktop.org/mobile-broadband/libqmi 22 | cd libqmi 23 | git checkout $(LIBQMI_VERSION) 24 | meson setup build --prefix=/usr 25 | ninja -C build 26 | sudo ninja -C build install 27 | cd .. 28 | 29 | echo "libqmi installed successfully" -------------------------------------------------------------------------------- /qmi-inject/scripts/install-nodejs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Sources: 4 | # - https://github.com/nodesource/distributions/blob/master/README.md#using-debian-as-root-1 5 | # - https://www.cyberciti.biz/faq/how-to-run-multiple-commands-in-sudo-under-linux-or-unix/ 6 | sudo -- bash -c 'curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && apt-get install -y nodejs' 7 | -------------------------------------------------------------------------------- /qmi-inject/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["es2020"], 5 | "allowJs": true, 6 | "noEmit": true, 7 | "strict": true, 8 | "esModuleInterop": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------