formatting and import organizing

This commit is contained in:
JJTech0130 2023-04-11 12:23:04 -04:00
parent daf8868bb5
commit b128ac514a
No known key found for this signature in database
GPG key ID: 23C92EBCCF8F93D6
8 changed files with 250 additions and 154 deletions

View file

@ -90,13 +90,16 @@ def generate_push_cert() -> tuple[str, str]:
encoding=serialization.Encoding.PEM, encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL, format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(), encryption_algorithm=serialization.NoEncryption(),
).decode("utf-8").strip(), )
protocol["device-activation"]["activation-record"]["DeviceCertificate"].decode( .decode("utf-8")
"utf-8" .strip(),
).strip(), protocol["device-activation"]["activation-record"]["DeviceCertificate"]
.decode("utf-8")
.strip(),
) )
if __name__ == "__main__": if __name__ == "__main__":
private_key, cert = generate_push_cert() private_key, cert = generate_push_cert()
print(private_key) print(private_key)
print(cert) print(cert)

95
apns.py
View file

@ -1,42 +1,45 @@
from __future__ import annotations from __future__ import annotations
import courier, albert import random
from hashlib import sha1
import threading import threading
import time import time
import random from hashlib import sha1
import albert
import courier
class APNSConnection: class APNSConnection:
incoming_queue = [] incoming_queue = []
def _queue_filler(self): def _queue_filler(self):
while True and not self.sock.closed: while True and not self.sock.closed:
#print(self.sock.closed) # print(self.sock.closed)
#print("QUEUE: Waiting for payload...") # print("QUEUE: Waiting for payload...")
#self.sock.read(1) # self.sock.read(1)
#print("QUEUE: Got payload?") # print("QUEUE: Got payload?")
payload = _deserialize_payload(self.sock) payload = _deserialize_payload(self.sock)
#print("QUEUE: Got payload?") # print("QUEUE: Got payload?")
if payload is not None: if payload is not None:
#print("QUEUE: Received payload: " + str(payload)) # print("QUEUE: Received payload: " + str(payload))
self.incoming_queue.append(payload) self.incoming_queue.append(payload)
#print("QUEUE: Thread ended") # print("QUEUE: Thread ended")
def _pop_by_id(self, id: int) -> tuple[int, list[tuple[int, bytes]]] | None: def _pop_by_id(self, id: int) -> tuple[int, list[tuple[int, bytes]]] | None:
#print("QUEUE: Looking for id " + str(id) + " in " + str(self.incoming_queue)) # print("QUEUE: Looking for id " + str(id) + " in " + str(self.incoming_queue))
for i in range(len(self.incoming_queue)): for i in range(len(self.incoming_queue)):
if self.incoming_queue[i][0] == id: if self.incoming_queue[i][0] == id:
return self.incoming_queue.pop(i) return self.incoming_queue.pop(i)
return None return None
def wait_for_packet(self, id: int) -> tuple[int, list[tuple[int, bytes]]]: def wait_for_packet(self, id: int) -> tuple[int, list[tuple[int, bytes]]]:
payload = self._pop_by_id(id) payload = self._pop_by_id(id)
while payload is None: while payload is None:
payload = self._pop_by_id(id) payload = self._pop_by_id(id)
time.sleep(0.1) time.sleep(0.1)
return payload return payload
def __init__(self, private_key=None, cert=None): def __init__(self, private_key=None, cert=None):
# Generate the private key and certificate if they're not provided # Generate the private key and certificate if they're not provided
if private_key is None or cert is None: if private_key is None or cert is None:
@ -47,26 +50,36 @@ class APNSConnection:
self.sock = courier.connect(self.private_key, self.cert) self.sock = courier.connect(self.private_key, self.cert)
# Start the queue filler thread # Start the queue filler thread
self.queue_filler_thread = threading.Thread(target=self._queue_filler, daemon=True) self.queue_filler_thread = threading.Thread(
target=self._queue_filler, daemon=True
)
self.queue_filler_thread.start() self.queue_filler_thread.start()
def connect(self, root: bool = True, token: bytes = None): def connect(self, root: bool = True, token: bytes = None):
flags = 0b01000001 flags = 0b01000001
if root: if root:
flags |= 0b0100 flags |= 0b0100
if token is None: if token is None:
payload = _serialize_payload(7, [(2, 0x01.to_bytes()), (5, flags.to_bytes(4))]) payload = _serialize_payload(
7, [(2, 0x01.to_bytes()), (5, flags.to_bytes(4))]
)
else: else:
payload = _serialize_payload(7, [(1, token), (2, 0x01.to_bytes()), (5, flags.to_bytes(4))]) payload = _serialize_payload(
7, [(1, token), (2, 0x01.to_bytes()), (5, flags.to_bytes(4))]
)
self.sock.write(payload) self.sock.write(payload)
payload = self.wait_for_packet(8) payload = self.wait_for_packet(8)
if payload == None or payload[0] != 8 or _get_field(payload[1], 1) != 0x00.to_bytes(): if (
payload == None
or payload[0] != 8
or _get_field(payload[1], 1) != 0x00.to_bytes()
):
raise Exception("Failed to connect") raise Exception("Failed to connect")
self.token = _get_field(payload[1], 3) self.token = _get_field(payload[1], 3)
return self.token return self.token
@ -80,32 +93,40 @@ class APNSConnection:
payload = _serialize_payload(9, fields) payload = _serialize_payload(9, fields)
self.sock.write(payload) self.sock.write(payload)
def send_message(self, topic: str, payload: str, id = None): def send_message(self, topic: str, payload: str, id=None):
if id is None: if id is None:
id = random.randbytes(4) id = random.randbytes(4)
payload = _serialize_payload(0x0a, payload = _serialize_payload(
[(4, id), 0x0A,
(1, sha1(topic.encode()).digest()), [
(2, self.token), (4, id),
(3, payload)]) (1, sha1(topic.encode()).digest()),
(2, self.token),
(3, payload),
],
)
self.sock.write(payload) self.sock.write(payload)
payload = self.wait_for_packet(0x0b) payload = self.wait_for_packet(0x0B)
if payload[1][0][1] != 0x00.to_bytes(): if payload[1][0][1] != 0x00.to_bytes():
raise Exception("Failed to send message") raise Exception("Failed to send message")
def set_state(self, state: int): def set_state(self, state: int):
self.sock.write(_serialize_payload(0x14, [(1, state.to_bytes(1)), (2, 0x7FFFFFFF.to_bytes(4))])) self.sock.write(
_serialize_payload(
0x14, [(1, state.to_bytes(1)), (2, 0x7FFFFFFF.to_bytes(4))]
)
)
def keep_alive(self): def keep_alive(self):
self.sock.write(_serialize_payload(0x0c, [])) self.sock.write(_serialize_payload(0x0C, []))
# TODO: Find a way to make this non-blocking # TODO: Find a way to make this non-blocking
#def expect_message(self) -> tuple[int, list[tuple[int, bytes]]] | None: # def expect_message(self) -> tuple[int, list[tuple[int, bytes]]] | None:
# return _deserialize_payload(self.sock) # return _deserialize_payload(self.sock)
@ -129,6 +150,7 @@ def _deserialize_field(stream: bytes) -> tuple[int, bytes]:
value = stream[3 : 3 + length] value = stream[3 : 3 + length]
return id, value return id, value
# Note: Takes a stream, not a buffer, as we do not know the length of the payload # Note: Takes a stream, not a buffer, as we do not know the length of the payload
# WILL BLOCK IF THE STREAM IS EMPTY # WILL BLOCK IF THE STREAM IS EMPTY
def _deserialize_payload(stream) -> tuple[int, list[tuple[int, bytes]]] | None: def _deserialize_payload(stream) -> tuple[int, list[tuple[int, bytes]]] | None:
@ -150,7 +172,10 @@ def _deserialize_payload(stream) -> tuple[int, list[tuple[int, bytes]]] | None:
return id, fields return id, fields
def _deserialize_payload_from_buffer(buffer: bytes) -> tuple[int, list[tuple[int, bytes]]] | None:
def _deserialize_payload_from_buffer(
buffer: bytes,
) -> tuple[int, list[tuple[int, bytes]]] | None:
id = int.from_bytes(buffer[:1], "big") id = int.from_bytes(buffer[:1], "big")
if id == 0x0: if id == 0x0:
@ -178,4 +203,4 @@ def _get_field(fields: list[tuple[int, bytes]], id: int) -> bytes:
for field_id, value in fields: for field_id, value in fields:
if field_id == id: if field_id == id:
return value return value
return None return None

View file

@ -1,23 +1,25 @@
import requests
import plistlib import plistlib
#CONFIG_URL = "http://init-p01st.push.apple.com/bag" import requests
# CONFIG_URL = "http://init-p01st.push.apple.com/bag"
CONFIG_URL = "https://init.push.apple.com/bag" CONFIG_URL = "https://init.push.apple.com/bag"
def get_config():
def get_config():
r = requests.get(CONFIG_URL, verify=False) r = requests.get(CONFIG_URL, verify=False)
if r.status_code != 200: if r.status_code != 200:
raise Exception("Failed to get config") raise Exception("Failed to get config")
# Parse the config as a plist # Parse the config as a plist
config = plistlib.loads(r.content) config = plistlib.loads(r.content)
# Parse the nested "bag" as a plist # Parse the nested "bag" as a plist
#config["bag"] = plistlib.loads(config["bag"]) # config["bag"] = plistlib.loads(config["bag"])
return config return config
if __name__ == "__main__": if __name__ == "__main__":
config = get_config() config = get_config()
print(config) print(config)

View file

@ -1,10 +1,12 @@
import tlslite
import socket import socket
COURIER_HOST = "windows.courier.push.apple.com" # TODO: Get this from config import tlslite
COURIER_HOST = "windows.courier.push.apple.com" # TODO: Get this from config
COURIER_PORT = 5223 COURIER_PORT = 5223
ALPN = [b"apns-security-v2"] ALPN = [b"apns-security-v2"]
# Connect to the courier server # Connect to the courier server
def connect(private_key: str, cert: str) -> tlslite.TLSConnection: def connect(private_key: str, cert: str) -> tlslite.TLSConnection:
# Connect to the courier server # Connect to the courier server
@ -19,9 +21,9 @@ def connect(private_key: str, cert: str) -> tlslite.TLSConnection:
return sock return sock
if __name__ == "__main__": if __name__ == "__main__":
sock = connect() sock = connect()
sock.write(b"Hello World!") sock.write(b"Hello World!")
print(sock.read()) print(sock.read())
sock.close() sock.close()

16
demo.py
View file

@ -1,17 +1,15 @@
import plistlib
import zlib
from base64 import b64decode, b64encode
from hashlib import sha1
import apns import apns
import ids import ids
conn1 = apns.APNSConnection() conn1 = apns.APNSConnection()
conn1.connect() conn1.connect()
conn1.keep_alive()
conn1.set_state(0x01) # Uncomment these for greater parity with apsd
conn1.filter([]) # conn1.keep_alive()
conn1.connect(False) # conn1.set_state(0x01)
# conn1.filter([])
# conn1.connect(False)
conn1.filter(["com.apple.madrid"]) conn1.filter(["com.apple.madrid"])
print(ids.lookup(conn1, ["mailto:jjtech@jjtech.dev"])) print(ids.lookup(conn1, ["mailto:jjtech@jjtech.dev"]))

View file

@ -1,6 +1,7 @@
from base64 import b64encode, b64decode import plistlib
import zlib
from base64 import b64decode, b64encode
from hashlib import sha1 from hashlib import sha1
import plistlib, zlib
# Taken from debug logs of apsd # Taken from debug logs of apsd
enabled_topics = "(com.apple.icloud-container.com.apple.avatarsd, com.icloud.askpermission, com.apple.icloud-container.com.apple.Safari, com.apple.itunesstored, com.apple.icloud-container.clouddocs.com.apple.CloudDocs.health, com.apple.passd.usernotifications, com.apple.icloud-container.com.apple.donotdisturbd, com.apple.icloud-container.clouddocs.iCloud.com.reddit.reddit, com.apple.mobileme.fmf3, com.apple.icloud-container.com.apple.cloudpaird, com.apple.icloud-container.clouddocs.com.apple.Pages, com.apple.appstored-testflight, com.apple.askpermissiond, com.apple.icloud-container.com.apple.willowd, com.me.cal, com.apple.icloud-container.com.apple.suggestd, com.apple.icloud-container.clouddocs.F3LWYJ7GM7.com.apple.garageband10, com.apple.icloud-container.clouddocs.com.apple.CloudDocs.container-metadata, com.apple.icloud-container.com.apple.callhistory.sync-helper, com.apple.icloud-container.com.apple.syncdefaultsd, com.apple.icloud-container.com.apple.SafariShared.Settings, com.apple.pay.services.products.prod, com.apple.icloud-container.com.apple.StatusKitAgent, com.apple.icloud-container.com.apple.siriknowledged, com.me.contacts, com.apple.icloud-container.com.apple.TrustedPeersHelper, com.apple.icloud-container.clouddocs.iCloud.com.apple.iBooks, com.apple.icloud-container.clouddocs.iCloud.dk.simonbs.Scriptable, com.apple.icloud-container.clouddocs.com.apple.ScriptEditor2, com.icloud.family, com.apple.idmsauth, com.apple.watchList, com.apple.icloud-container.clouddocs.com.apple.TextEdit, com.apple.icloud-container.com.apple.VoiceMemos, com.apple.sharedstreams, com.apple.pay.services.apply.prod, com.apple.icloud-container.com.apple.SafariShared.CloudTabs, com.apple.wallet.sharing.qa, com.apple.appstored, com.apple.icloud-container.clouddocs.3L68KQB4HG.com.readdle.CommonDocuments, com.apple.icloud-container.clouddocs.com.apple.CloudDocs.pp-metadata, com.me.setupservice, com.apple.icloud-container.com.apple.amsengagementd, com.apple.icloud-container.com.apple.appleaccount.beneficiary.private, com.apple.icloud-container.com.apple.appleaccount.beneficiary, com.apple.icloud-container.clouddocs.com.apple.mail, com.apple.icloud-container.com.apple.appleaccount.custodian, com.apple.icloud-container.com.apple.securityd, com.apple.icloud-container.com.apple.iBooksX, com.apple.icloud-container.clouddocs.com.apple.QuickTimePlayerX, com.apple.icloud-container.clouddocs.com.apple.TextInput, com.apple.icloud-container.com.apple.icloud.fmfd, com.apple.tv.favoriteTeams, com.apple.pay.services.ownershipTokens.prod, com.apple.icloud-container.com.apple.passd, com.apple.amsaccountsd, com.apple.pay.services.devicecheckin.prod.us, com.apple.storekit, com.apple.icloud-container.com.apple.keyboardservicesd, paymentpass.com.apple, com.apple.aa.setupservice, com.apple.icloud-container.clouddocs.com.apple.shoebox, com.apple.icloud-container.clouddocs.F3LWYJ7GM7.com.apple.mobilegarageband, com.apple.icloud-container.com.apple.icloud.searchpartyuseragent, com.apple.icloud-container.clouddocs.iCloud.com.apple.configurator.ui, com.apple.icloud-container.com.apple.gamed, com.apple.icloud-container.clouddocs.com.apple.Keynote, com.apple.icloud-container.com.apple.willowd.homekit, com.apple.amsengagementd.notifications, com.apple.icloud.presence.mode.status, com.apple.aa.idms, com.apple.icloud-container.clouddocs.iCloud.com.apple.MobileSMS, com.apple.gamed, com.apple.icloud-container.clouddocs.iCloud.is.workflow.my.workflows, com.apple.icloud-container.clouddocs.iCloud.md.obsidian, com.apple.icloud-container.clouddocs.com.apple.CloudDocs, com.apple.wallet.sharing, com.apple.icloud-container.clouddocs.iCloud.com.apple.iBooks.iTunesU, com.apple.icloud.presence.shared.experience, com.apple.icloud-container.com.apple.imagent, com.apple.icloud-container.com.apple.financed, com.apple.pay.services.account.prod, com.apple.icloud-container.com.apple.assistant.assistantd, com.apple.pay.services.ck.zone.prod, com.apple.icloud-container.com.apple.security.cuttlefish, com.apple.icloud-container.clouddocs.com.apple.iBooks.cloudData, com.apple.peerpayment, com.icloud.quota, com.apple.pay.provision, com.apple.icloud-container.com.apple.upload-request-proxy.com.apple.photos.cloud, com.apple.icloud-container.com.apple.appleaccount.custodian.private, com.apple.icloud-container.clouddocs.com.apple.Preview, com.apple.maps.icloud, com.apple.icloud-container.com.apple.reminders, com.apple.icloud-container.com.apple.SafariShared.WBSCloudBookmarksStore, com.apple.idmsauthagent, com.apple.icloud-container.clouddocs.com.apple.Numbers, com.apple.bookassetd, com.apple.pay.auxiliary.registration.requirement.prod, com.apple.icloud.fmip.voiceassistantsync)" enabled_topics = "(com.apple.icloud-container.com.apple.avatarsd, com.icloud.askpermission, com.apple.icloud-container.com.apple.Safari, com.apple.itunesstored, com.apple.icloud-container.clouddocs.com.apple.CloudDocs.health, com.apple.passd.usernotifications, com.apple.icloud-container.com.apple.donotdisturbd, com.apple.icloud-container.clouddocs.iCloud.com.reddit.reddit, com.apple.mobileme.fmf3, com.apple.icloud-container.com.apple.cloudpaird, com.apple.icloud-container.clouddocs.com.apple.Pages, com.apple.appstored-testflight, com.apple.askpermissiond, com.apple.icloud-container.com.apple.willowd, com.me.cal, com.apple.icloud-container.com.apple.suggestd, com.apple.icloud-container.clouddocs.F3LWYJ7GM7.com.apple.garageband10, com.apple.icloud-container.clouddocs.com.apple.CloudDocs.container-metadata, com.apple.icloud-container.com.apple.callhistory.sync-helper, com.apple.icloud-container.com.apple.syncdefaultsd, com.apple.icloud-container.com.apple.SafariShared.Settings, com.apple.pay.services.products.prod, com.apple.icloud-container.com.apple.StatusKitAgent, com.apple.icloud-container.com.apple.siriknowledged, com.me.contacts, com.apple.icloud-container.com.apple.TrustedPeersHelper, com.apple.icloud-container.clouddocs.iCloud.com.apple.iBooks, com.apple.icloud-container.clouddocs.iCloud.dk.simonbs.Scriptable, com.apple.icloud-container.clouddocs.com.apple.ScriptEditor2, com.icloud.family, com.apple.idmsauth, com.apple.watchList, com.apple.icloud-container.clouddocs.com.apple.TextEdit, com.apple.icloud-container.com.apple.VoiceMemos, com.apple.sharedstreams, com.apple.pay.services.apply.prod, com.apple.icloud-container.com.apple.SafariShared.CloudTabs, com.apple.wallet.sharing.qa, com.apple.appstored, com.apple.icloud-container.clouddocs.3L68KQB4HG.com.readdle.CommonDocuments, com.apple.icloud-container.clouddocs.com.apple.CloudDocs.pp-metadata, com.me.setupservice, com.apple.icloud-container.com.apple.amsengagementd, com.apple.icloud-container.com.apple.appleaccount.beneficiary.private, com.apple.icloud-container.com.apple.appleaccount.beneficiary, com.apple.icloud-container.clouddocs.com.apple.mail, com.apple.icloud-container.com.apple.appleaccount.custodian, com.apple.icloud-container.com.apple.securityd, com.apple.icloud-container.com.apple.iBooksX, com.apple.icloud-container.clouddocs.com.apple.QuickTimePlayerX, com.apple.icloud-container.clouddocs.com.apple.TextInput, com.apple.icloud-container.com.apple.icloud.fmfd, com.apple.tv.favoriteTeams, com.apple.pay.services.ownershipTokens.prod, com.apple.icloud-container.com.apple.passd, com.apple.amsaccountsd, com.apple.pay.services.devicecheckin.prod.us, com.apple.storekit, com.apple.icloud-container.com.apple.keyboardservicesd, paymentpass.com.apple, com.apple.aa.setupservice, com.apple.icloud-container.clouddocs.com.apple.shoebox, com.apple.icloud-container.clouddocs.F3LWYJ7GM7.com.apple.mobilegarageband, com.apple.icloud-container.com.apple.icloud.searchpartyuseragent, com.apple.icloud-container.clouddocs.iCloud.com.apple.configurator.ui, com.apple.icloud-container.com.apple.gamed, com.apple.icloud-container.clouddocs.com.apple.Keynote, com.apple.icloud-container.com.apple.willowd.homekit, com.apple.amsengagementd.notifications, com.apple.icloud.presence.mode.status, com.apple.aa.idms, com.apple.icloud-container.clouddocs.iCloud.com.apple.MobileSMS, com.apple.gamed, com.apple.icloud-container.clouddocs.iCloud.is.workflow.my.workflows, com.apple.icloud-container.clouddocs.iCloud.md.obsidian, com.apple.icloud-container.clouddocs.com.apple.CloudDocs, com.apple.wallet.sharing, com.apple.icloud-container.clouddocs.iCloud.com.apple.iBooks.iTunesU, com.apple.icloud.presence.shared.experience, com.apple.icloud-container.com.apple.imagent, com.apple.icloud-container.com.apple.financed, com.apple.pay.services.account.prod, com.apple.icloud-container.com.apple.assistant.assistantd, com.apple.pay.services.ck.zone.prod, com.apple.icloud-container.com.apple.security.cuttlefish, com.apple.icloud-container.clouddocs.com.apple.iBooks.cloudData, com.apple.peerpayment, com.icloud.quota, com.apple.pay.provision, com.apple.icloud-container.com.apple.upload-request-proxy.com.apple.photos.cloud, com.apple.icloud-container.com.apple.appleaccount.custodian.private, com.apple.icloud-container.clouddocs.com.apple.Preview, com.apple.maps.icloud, com.apple.icloud-container.com.apple.reminders, com.apple.icloud-container.com.apple.SafariShared.WBSCloudBookmarksStore, com.apple.idmsauthagent, com.apple.icloud-container.clouddocs.com.apple.Numbers, com.apple.bookassetd, com.apple.pay.auxiliary.registration.requirement.prod, com.apple.icloud.fmip.voiceassistantsync)"
@ -17,16 +18,18 @@ topics = enabled_topics + opportunistic_topics + paused_topics
# Calculate the SHA1 hash of each topic # Calculate the SHA1 hash of each topic
topics_lookup = [(topic, sha1(topic.encode()).digest()) for topic in topics] topics_lookup = [(topic, sha1(topic.encode()).digest()) for topic in topics]
class bcolors: class bcolors:
HEADER = '\033[95m' HEADER = "\033[95m"
OKBLUE = '\033[94m' OKBLUE = "\033[94m"
OKCYAN = '\033[96m' OKCYAN = "\033[96m"
OKGREEN = '\033[92m' OKGREEN = "\033[92m"
WARNING = '\033[93m' WARNING = "\033[93m"
FAIL = '\033[91m' FAIL = "\033[91m"
ENDC = '\033[0m' ENDC = "\033[0m"
BOLD = '\033[1m' BOLD = "\033[1m"
UNDERLINE = '\033[4m' UNDERLINE = "\033[4m"
def _lookup_topic(hash: bytes): def _lookup_topic(hash: bytes):
for topic_lookup in topics_lookup: for topic_lookup in topics_lookup:
@ -34,6 +37,7 @@ def _lookup_topic(hash: bytes):
return topic_lookup[0] return topic_lookup[0]
return None return None
# Returns the value of the first field with the given id # Returns the value of the first field with the given id
def _get_field(fields: list[tuple[int, bytes]], id: int) -> bytes: def _get_field(fields: list[tuple[int, bytes]], id: int) -> bytes:
for field_id, value in fields: for field_id, value in fields:
@ -41,6 +45,7 @@ def _get_field(fields: list[tuple[int, bytes]], id: int) -> bytes:
return value return value
return None return None
def _p_filter(prefix, fields: list[tuple[int, bytes]]): def _p_filter(prefix, fields: list[tuple[int, bytes]]):
enabled = [] enabled = []
ignored = [] ignored = []
@ -52,7 +57,7 @@ def _p_filter(prefix, fields: list[tuple[int, bytes]]):
for field in fields: for field in fields:
if field[0] == 1: if field[0] == 1:
token = b64encode(field[1]) token = b64encode(field[1])
#print(f"Push Token: {b64encode(field[1])}") # print(f"Push Token: {b64encode(field[1])}")
elif field[0] == 2: elif field[0] == 2:
enabled.append(_lookup_topic(field[1])) enabled.append(_lookup_topic(field[1]))
elif field[0] == 3: elif field[0] == 3:
@ -62,8 +67,8 @@ def _p_filter(prefix, fields: list[tuple[int, bytes]]):
elif field[0] == 5: elif field[0] == 5:
paused.append(_lookup_topic(field[1])) paused.append(_lookup_topic(field[1]))
else: else:
pass # whatever, there's a 6 but it's not documented pass # whatever, there's a 6 but it's not documented
#print(f"Unknown field ID: {field[0]}") # print(f"Unknown field ID: {field[0]}")
# Remove None values # Remove None values
enabled = [topic.strip() for topic in enabled if topic is not None] enabled = [topic.strip() for topic in enabled if topic is not None]
@ -95,11 +100,17 @@ def _p_filter(prefix, fields: list[tuple[int, bytes]]):
if len(paused) > 100: if len(paused) > 100:
paused = paused[:100] + "..." paused = paused[:100] + "..."
# (Token: {token.decode()}) # (Token: {token.decode()})
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Filter{bcolors.ENDC} {bcolors.WARNING}Enabled{bcolors.ENDC}: {enabled} {bcolors.FAIL}Ignored{bcolors.ENDC}: {ignored} {bcolors.OKBLUE}Oppertunistic{bcolors.ENDC}: {oppertunistic} {bcolors.OKGREEN}Paused{bcolors.ENDC}: {paused}") print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Filter{bcolors.ENDC} {bcolors.WARNING}Enabled{bcolors.ENDC}: {enabled} {bcolors.FAIL}Ignored{bcolors.ENDC}: {ignored} {bcolors.OKBLUE}Oppertunistic{bcolors.ENDC}: {oppertunistic} {bcolors.OKGREEN}Paused{bcolors.ENDC}: {paused}"
)
import apns import apns
def pretty_print_payload(prefix, payload: tuple[int, list[tuple[int, bytes]]]) -> bytes | None:
def pretty_print_payload(
prefix, payload: tuple[int, list[tuple[int, bytes]]]
) -> bytes | None:
id = payload[0] id = payload[0]
if id == 9: if id == 9:
@ -108,54 +119,78 @@ def pretty_print_payload(prefix, payload: tuple[int, list[tuple[int, bytes]]]) -
token_str = "" token_str = ""
if _get_field(payload[1], 3): if _get_field(payload[1], 3):
token_str = f"{bcolors.WARNING}Token{bcolors.ENDC}: {b64encode(_get_field(payload[1], 3)).decode()}" token_str = f"{bcolors.WARNING}Token{bcolors.ENDC}: {b64encode(_get_field(payload[1], 3)).decode()}"
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Connected{bcolors.ENDC} {token_str} {bcolors.OKBLUE}{_get_field(payload[1], 1).hex()}{bcolors.ENDC}") print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Connected{bcolors.ENDC} {token_str} {bcolors.OKBLUE}{_get_field(payload[1], 1).hex()}{bcolors.ENDC}"
)
elif id == 7: elif id == 7:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Connect Request{bcolors.ENDC}", end="") print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Connect Request{bcolors.ENDC}",
end="",
)
if _get_field(payload[1], 1): if _get_field(payload[1], 1):
print(f" {bcolors.WARNING}Token{bcolors.ENDC}: {b64encode(_get_field(payload[1], 1)).decode()}", end="") print(
if _get_field(payload[1], 0x0c): f" {bcolors.WARNING}Token{bcolors.ENDC}: {b64encode(_get_field(payload[1], 1)).decode()}",
end="",
)
if _get_field(payload[1], 0x0C):
print(f" {bcolors.OKBLUE}SIGNED{bcolors.ENDC}", end="") print(f" {bcolors.OKBLUE}SIGNED{bcolors.ENDC}", end="")
if _get_field(payload[1], 0x5) and int.from_bytes(_get_field(payload[1], 0x5)) & 0x4: if (
_get_field(payload[1], 0x5)
and int.from_bytes(_get_field(payload[1], 0x5)) & 0x4
):
print(f" {bcolors.FAIL}ROOT{bcolors.ENDC}", end="") print(f" {bcolors.FAIL}ROOT{bcolors.ENDC}", end="")
print() print()
#for field in payload[1]: # for field in payload[1]:
# print(f"Field ID: {field[0]}") # print(f"Field ID: {field[0]}")
# print(f"Field Value: {field[1]}") # print(f"Field Value: {field[1]}")
# 65 (user) or 69 (root) # 65 (user) or 69 (root)
for i in range(len(payload[1])): for i in range(len(payload[1])):
#if payload[1][i][0] == 5: # if payload[1][i][0] == 5:
#if payload[1][i][1] == b'\x00\x00\x00A': # user # if payload[1][i][1] == b'\x00\x00\x00A': # user
# payload[1][i][1] = b'\x00\x00\x00E' # payload[1][i][1] = b'\x00\x00\x00E'
#elif payload[1][i][1] == b'\x00\x00\x00E': # root # elif payload[1][i][1] == b'\x00\x00\x00E': # root
# payload[1][i][1] = b'\x00\x00\x00A' # payload[1][i][1] = b'\x00\x00\x00A'
#else: # else:
# print("Unknown field value: ", payload[1][i][1]) # print("Unknown field value: ", payload[1][i][1])
if payload[1][i][0] == 1: if payload[1][i][0] == 1:
pass pass
#payload[1][i] = (None, None) # payload[1][i] = (None, None)
#payload[1][i] = (1, b64decode("D3MtN3e18QE8rve3n92wp+CwK7u/bWk/5WjQUOBN640=")) # payload[1][i] = (1, b64decode("D3MtN3e18QE8rve3n92wp+CwK7u/bWk/5WjQUOBN640="))
out = apns._serialize_payload(payload[0], payload[1]) out = apns._serialize_payload(payload[0], payload[1])
#return out # return out
elif id == 0xc: elif id == 0xC:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Keep Alive{bcolors.ENDC}") print(
elif id == 0xd: f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Keep Alive{bcolors.ENDC}"
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Keep Alive Ack{bcolors.ENDC}") )
elif id == 0xD:
print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Keep Alive Ack{bcolors.ENDC}"
)
elif id == 0x14: elif id == 0x14:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Set State{bcolors.ENDC}: {_get_field(payload[1], 1).hex()}") print(
elif id == 0x1d or id == 0x20: f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Set State{bcolors.ENDC}: {_get_field(payload[1], 1).hex()}"
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.WARNING}PubSub ??{bcolors.ENDC}") )
elif id == 0xe: elif id == 0x1D or id == 0x20:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.WARNING}Token Confirmation{bcolors.ENDC}") print(
elif id == 0xa: f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.WARNING}PubSub ??{bcolors.ENDC}"
)
elif id == 0xE:
print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.WARNING}Token Confirmation{bcolors.ENDC}"
)
elif id == 0xA:
topic = "" topic = ""
#topic = _lookup_topic(_get_field(payload[1], 1)) # topic = _lookup_topic(_get_field(payload[1], 1))
# if it has apsd -> APNs in the prefix, it's an outgoing notification # if it has apsd -> APNs in the prefix, it's an outgoing notification
if "apsd -> APNs" in prefix: if "apsd -> APNs" in prefix:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKBLUE}OUTGOING Notification{bcolors.ENDC}", end="") print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKBLUE}OUTGOING Notification{bcolors.ENDC}",
end="",
)
topic = _lookup_topic(_get_field(payload[1], 1)) topic = _lookup_topic(_get_field(payload[1], 1))
# topic = _lookup_topic(_get_field(payload[1], 1)) # topic = _lookup_topic(_get_field(payload[1], 1))
# if b"bplist" in _get_field(payload[1], 3): # if b"bplist" in _get_field(payload[1], 3):
@ -169,54 +204,68 @@ def pretty_print_payload(prefix, payload: tuple[int, list[tuple[int, bytes]]]) -
# for key in plist: # for key in plist:
# print(f" {bcolors.OKBLUE}{key}{bcolors.ENDC}: {plist[key]}", end="") # print(f" {bcolors.OKBLUE}{key}{bcolors.ENDC}: {plist[key]}", end="")
else: else:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Notification{bcolors.ENDC}", end="") print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Notification{bcolors.ENDC}",
end="",
)
topic = _lookup_topic(_get_field(payload[1], 2)) topic = _lookup_topic(_get_field(payload[1], 2))
#if b"bplist" in _get_field(payload[1], 3): # if b"bplist" in _get_field(payload[1], 3):
# print(f" {bcolors.OKBLUE}Binary{bcolors.ENDC}", end="") # print(f" {bcolors.OKBLUE}Binary{bcolors.ENDC}", end="")
#print(f" {bcolors.WARNING}Topic{bcolors.ENDC}: {_lookup_topic(_get_field(payload[1], 2))}") # print(f" {bcolors.WARNING}Topic{bcolors.ENDC}: {_lookup_topic(_get_field(payload[1], 2))}")
print(f" {bcolors.WARNING}Topic{bcolors.ENDC}: {topic}", end="") print(f" {bcolors.WARNING}Topic{bcolors.ENDC}: {topic}", end="")
if topic == "com.apple.madrid": if topic == "com.apple.madrid":
print(f" {bcolors.FAIL}Madrid{bcolors.ENDC}", end="") print(f" {bcolors.FAIL}Madrid{bcolors.ENDC}", end="")
payload = plistlib.loads(_get_field(payload[1], 3)) payload = plistlib.loads(_get_field(payload[1], 3))
#print(payload) # print(payload)
if "cT" in payload: if "cT" in payload:
# It's HTTP over APNs # It's HTTP over APNs
if "hs" in payload: if "hs" in payload:
print(f" {bcolors.WARNING}HTTP Response{bcolors.ENDC}: {payload['hs']}", end="") print(
f" {bcolors.WARNING}HTTP Response{bcolors.ENDC}: {payload['hs']}",
end="",
)
else: else:
print(f" {bcolors.WARNING}HTTP Request{bcolors.ENDC}", end="") print(f" {bcolors.WARNING}HTTP Request{bcolors.ENDC}", end="")
#print(f" {bcolors.WARNING}HTTP{bcolors.ENDC} {payload['hs']}", end="") # print(f" {bcolors.WARNING}HTTP{bcolors.ENDC} {payload['hs']}", end="")
if "u" in payload: if "u" in payload:
print(f" {bcolors.OKCYAN}URL{bcolors.ENDC}: {payload['u']}", end="") print(f" {bcolors.OKCYAN}URL{bcolors.ENDC}: {payload['u']}", end="")
print(f" {bcolors.FAIL}Content Type{bcolors.ENDC}: {payload['cT']}", end="") print(
f" {bcolors.FAIL}Content Type{bcolors.ENDC}: {payload['cT']}",
end="",
)
if "h" in payload: if "h" in payload:
print(f" {bcolors.FAIL}Headers{bcolors.ENDC}: {payload['h']}", end="") print(
f" {bcolors.FAIL}Headers{bcolors.ENDC}: {payload['h']}", end=""
)
if "b" in payload: if "b" in payload:
# What am I really supposed to put in WBITS? Got this from a random SO answer # What am I really supposed to put in WBITS? Got this from a random SO answer
#print(payload["b"]) # print(payload["b"])
body = zlib.decompress(payload['b'], 16+zlib.MAX_WBITS) body = zlib.decompress(payload["b"], 16 + zlib.MAX_WBITS)
if b'plist' in body: if b"plist" in body:
body = plistlib.loads(body) body = plistlib.loads(body)
print(f" {bcolors.FAIL}Body{bcolors.ENDC}: {body}", end="") print(f" {bcolors.FAIL}Body{bcolors.ENDC}: {body}", end="")
print()
#print(f" {bcolors.WARNING}{bcolors.ENDC}: {payload['cT']}")
#for field in payload[1]: print()
# print(f" {bcolors.WARNING}{bcolors.ENDC}: {payload['cT']}")
# for field in payload[1]:
# print(f"Field ID: {field[0]}") # print(f"Field ID: {field[0]}")
# print(f"Field Value: {field[1]}") # print(f"Field Value: {field[1]}")
elif id == 0xb: elif id == 0xB:
print(f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Notification Ack{bcolors.ENDC} {bcolors.OKBLUE}{_get_field(payload[1], 8).hex()}{bcolors.ENDC}") print(
f"{bcolors.OKGREEN}{prefix}{bcolors.ENDC}: {bcolors.OKCYAN}Notification Ack{bcolors.ENDC} {bcolors.OKBLUE}{_get_field(payload[1], 8).hex()}{bcolors.ENDC}"
)
else: else:
print(prefix, f"Payload ID: {hex(payload[0])}") print(prefix, f"Payload ID: {hex(payload[0])}")
for field in payload[1]: for field in payload[1]:
print(f"Field ID: {field[0]}") print(f"Field ID: {field[0]}")
print(f"Field Value: {field[1]}") print(f"Field Value: {field[1]}")
if __name__ == "__main__": if __name__ == "__main__":
print(f"{bcolors.OKGREEN}Enabled:{bcolors.ENDC}") print(f"{bcolors.OKGREEN}Enabled:{bcolors.ENDC}")

View file

@ -2,4 +2,4 @@
for i in range(1, 50): for i in range(1, 50):
print(f"127.0.0.1 {i}-courier.push.apple.com") print(f"127.0.0.1 {i}-courier.push.apple.com")
print(f"127.0.0.1 {i}.courier.push.apple.com") print(f"127.0.0.1 {i}.courier.push.apple.com")

View file

@ -1,46 +1,49 @@
# TLS server to proxy APNs traffic # TLS server to proxy APNs traffic
import socket import socket
import tlslite import sys
import threading import threading
import sys import tlslite
# setting path # setting path
sys.path.append('../') sys.path.append("../")
# APNs server to proxy traffic to # APNs server to proxy traffic to
APNS_HOST = "windows.courier.push.apple.com" APNS_HOST = "windows.courier.push.apple.com"
APNS_PORT = 5223 APNS_PORT = 5223
ALPN = b"apns-security-v3" ALPN = b"apns-security-v3"
#ALPN = b"apns-security-v2" # ALPN = b"apns-security-v2"
#ALPN = b"apns-pack-v1" # ALPN = b"apns-pack-v1"
global_cnt = 0 global_cnt = 0
# Connect to the APNs server # Connect to the APNs server
def connect() -> tlslite.TLSConnection: def connect() -> tlslite.TLSConnection:
# Connect to the APNs server # Connect to the APNs server
sock = socket.create_connection((APNS_HOST, APNS_PORT)) sock = socket.create_connection((APNS_HOST, APNS_PORT))
# Wrap the socket in TLS # Wrap the socket in TLS
ssock = tlslite.TLSConnection(sock) ssock = tlslite.TLSConnection(sock)
#print("Handshaking with APNs") # print("Handshaking with APNs")
# Handshake with the server # Handshake with the server
if ALPN == b"apns-security-v3": if ALPN == b"apns-security-v3":
print("Using v3") print("Using v3")
ssock.handshakeClientCert(alpn=[ALPN]) ssock.handshakeClientCert(alpn=[ALPN])
else: else:
import albert import albert
private_key, cert = albert.generate_push_cert() private_key, cert = albert.generate_push_cert()
cert = tlslite.X509CertChain([tlslite.X509().parse(cert)]) cert = tlslite.X509CertChain([tlslite.X509().parse(cert)])
private_key = tlslite.parsePEMKey(private_key, private=True) private_key = tlslite.parsePEMKey(private_key, private=True)
# Handshake with the server # Handshake with the server
ssock.handshakeClientCert(cert, private_key, alpn=[ALPN]) ssock.handshakeClientCert(cert, private_key, alpn=[ALPN])
return ssock return ssock
cert:str = None
key:str = None cert: str = None
key: str = None
import apns import apns
@ -48,27 +51,32 @@ import printer
outgoing_list = [] outgoing_list = []
incoming_list = [] incoming_list = []
#last_outgoing = b"" # last_outgoing = b""
def proxy(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection, prefix: str = ""): def proxy(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection, prefix: str = ""):
try: try:
while True: while True:
# Read data from the first connection # Read data from the first connection
data = conn1.read() data = conn1.read()
#print(prefix, "data: ", data) # print(prefix, "data: ", data)
# If there is no data, the connection has closed # If there is no data, the connection has closed
if not data: if not data:
print(prefix, "Connection closed due to no data") print(prefix, "Connection closed due to no data")
break break
try: try:
override = printer.pretty_print_payload(prefix, apns._deserialize_payload_from_buffer(data)) override = printer.pretty_print_payload(
prefix, apns._deserialize_payload_from_buffer(data)
)
except Exception as e: except Exception as e:
print(e) # Can't crash the proxy over parsing errors print(e) # Can't crash the proxy over parsing errors
if override is not None: if override is not None:
data = override data = override
print("OVERRIDE: ", end="") print("OVERRIDE: ", end="")
printer.pretty_print_payload(prefix, apns._deserialize_payload_from_buffer(data)) printer.pretty_print_payload(
prefix, apns._deserialize_payload_from_buffer(data)
)
if "apsd -> APNs" in prefix: if "apsd -> APNs" in prefix:
global outgoing_list global outgoing_list
@ -81,13 +89,13 @@ def proxy(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection, prefix: st
if len(incoming_list) > 100: if len(incoming_list) > 100:
incoming_list.pop() incoming_list.pop()
#print(prefix, data) # print(prefix, data)
# Write the data to the second connection # Write the data to the second connection
conn2.write(data) conn2.write(data)
except OSError as e: except OSError as e:
if e.errno == 9: if e.errno == 9:
print(prefix, "Connection closed due to OSError 9") print(prefix, "Connection closed due to OSError 9")
pass # Probably a connection closed error pass # Probably a connection closed error
else: else:
raise e raise e
except tlslite.TLSAbruptCloseError as e: except tlslite.TLSAbruptCloseError as e:
@ -97,7 +105,10 @@ def proxy(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection, prefix: st
conn1.close() conn1.close()
conn2.close() conn2.close()
repl_lock = False repl_lock = False
def repl(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection): def repl(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection):
global repl_lock global repl_lock
if repl_lock: if repl_lock:
@ -105,37 +116,43 @@ def repl(conn1: tlslite.TLSConnection, conn2: tlslite.TLSConnection):
return return
repl_lock = True repl_lock = True
import IPython import IPython
IPython.embed() IPython.embed()
def handle(conn: socket.socket): def handle(conn: socket.socket):
# Wrap the socket in TLS # Wrap the socket in TLS
s_conn = tlslite.TLSConnection(conn) s_conn = tlslite.TLSConnection(conn)
global cert, key global cert, key
chain = tlslite.X509CertChain() chain = tlslite.X509CertChain()
chain.parsePemList(cert) chain.parsePemList(cert)
#print(chain) # print(chain)
#cert = tlslite.X509CertChain([tlslite.X509().parse(cert)]) # cert = tlslite.X509CertChain([tlslite.X509().parse(cert)])
key_parsed = tlslite.parsePEMKey(key, private=True) key_parsed = tlslite.parsePEMKey(key, private=True)
#print(key_parsed) # print(key_parsed)
s_conn.handshakeServer(certChain=chain, privateKey=key_parsed, reqCert=False, alpn=[ALPN]) s_conn.handshakeServer(
certChain=chain, privateKey=key_parsed, reqCert=False, alpn=[ALPN]
)
print("Handling connection") print("Handling connection")
# Connect to the APNs server # Connect to the APNs server
apns = connect() apns = connect()
print("Connected to APNs") print("Connected to APNs")
threading.Thread(target=repl, args=(s_conn,apns)).start() threading.Thread(target=repl, args=(s_conn, apns)).start()
global global_cnt global global_cnt
global_cnt += 1 global_cnt += 1
# Proxy data between the connections # Proxy data between the connections
# Create a thread to proxy data from the APNs server to the client # Create a thread to proxy data from the APNs server to the client
threading.Thread(target=proxy, args=(s_conn, apns, f"{global_cnt} apsd -> APNs")).start() threading.Thread(
target=proxy, args=(s_conn, apns, f"{global_cnt} apsd -> APNs")
).start()
# Just proxy data from the client to the APNs server in this thread # Just proxy data from the client to the APNs server in this thread
proxy(apns, s_conn, f"{global_cnt} APNs -> apsd") proxy(apns, s_conn, f"{global_cnt} APNs -> apsd")
def serve():
def serve():
# Create a socket to listen for connections # Create a socket to listen for connections
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Allow the socket to be reused # Allow the socket to be reused
@ -150,7 +167,6 @@ def serve():
with open("push_certificate_chain.pem", "r") as f: with open("push_certificate_chain.pem", "r") as f:
global cert global cert
cert = f.read() cert = f.read()
# NEED TO USE OPENSSL, SEE CORETRUST CMD, MIMIC ENTRUST? OR AT LEAST SEE PUSHPROXY FOR EXTRACTION & REPLACEMENT # NEED TO USE OPENSSL, SEE CORETRUST CMD, MIMIC ENTRUST? OR AT LEAST SEE PUSHPROXY FOR EXTRACTION & REPLACEMENT
with open("push_key.pem", "r") as f: with open("push_key.pem", "r") as f:
@ -165,7 +181,7 @@ def serve():
conn, addr = sock.accept() conn, addr = sock.accept()
conns.append(conn) conns.append(conn)
# Create a thread to handle the connection # Create a thread to handle the connection
#handle(conn) # handle(conn)
thread = threading.Thread(target=handle, args=(conn,)) thread = threading.Thread(target=handle, args=(conn,))
thread.start() thread.start()
except KeyboardInterrupt: except KeyboardInterrupt:
@ -174,5 +190,6 @@ def serve():
conn.close() conn.close()
sock.close() sock.close()
if __name__ == "__main__": if __name__ == "__main__":
serve() serve()