implement message correlation

This commit is contained in:
JJTech0130 2023-05-02 20:51:02 -04:00
parent 27528bf1da
commit b3ead0cc34
No known key found for this signature in database
GPG key ID: 23C92EBCCF8F93D6
3 changed files with 116 additions and 36 deletions

98
apns.py
View file

@ -29,13 +29,53 @@ def _connect(private_key: str, cert: str) -> tlslite.TLSConnection:
return sock
class IncomingQueue:
def __init__(self):
self.queue = []
self.lock = threading.Lock()
def append(self, item):
with self.lock:
self.queue.append(item)
def pop(self, index):
with self.lock:
return self.queue.pop(index)
def __getitem__(self, index):
with self.lock:
return self.queue[index]
def __len__(self):
with self.lock:
return len(self.queue)
def find(self, finder):
with self.lock:
return next((i for i in self.queue if finder(i)), None)
def pop_find(self, finder):
with self.lock:
found = next((i for i in self.queue if finder(i)), None)
if found is not None:
# We have the lock, so we can safely remove it
self.queue.remove(found)
return found
def wait_pop_find(self, finder, delay=0.1):
found = None
while found is None:
found = self.pop_find(finder)
if found is None:
time.sleep(delay)
return found
class APNSConnection:
incoming_queue = []
incoming_queue = IncomingQueue()
# Sink everything in the queue
def sink(self):
self.incoming_queue = []
self.incoming_queue = IncomingQueue()
def _queue_filler(self):
while True and not self.sock.closed:
@ -47,23 +87,33 @@ class APNSConnection:
# print("QUEUE: Got payload?")
if payload is not None:
# print("QUEUE: Received payload: " + str(payload))
#print("QUEUE: Received payload: " + str(payload))
print("QUEUE: Received payload type: " + hex(payload[0]))
self.incoming_queue.append(payload)
# print("QUEUE: Thread ended")
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))
for i in range(len(self.incoming_queue)):
if self.incoming_queue[i][0] == id:
return self.incoming_queue.pop(i)
return None
# def _pop_by_id(self, id: int) -> tuple[int, list[tuple[int, bytes]]] | None:
# def finder(item):
# return item[0] == id
# return self.incoming_queue.find(finder)
# # print("QUEUE: Looking for id " + str(id) + " in " + str(self.incoming_queue))
# #for i in range(len(self.incoming_queue)):
# # if self.incoming_queue[i][0] == id:
# # return self.incoming_queue.pop(i)
# #return None
def wait_for_packet(self, id: int) -> tuple[int, list[tuple[int, bytes]]]:
payload = self._pop_by_id(id)
while payload is None:
payload = self._pop_by_id(id)
time.sleep(0.1)
return payload
# def wait_for_packet(self, id: int) -> tuple[int, list[tuple[int, bytes]]]:
# found = None
# while found is None:
# found = self._pop_by_id(id)
# if found is None:
# time.sleep(0.1)
# return found
# def find_packet(self, finder) ->
#def replace_packet(self, payload: tuple[int, list[tuple[int, bytes]]]):
# self.incoming_queue.append(payload)
def __init__(self, private_key=None, cert=None):
# Generate the private key and certificate if they're not provided
@ -96,7 +146,7 @@ class APNSConnection:
self.sock.write(payload)
payload = self.wait_for_packet(8)
payload = self.incoming_queue.wait_pop_find(lambda i: i[0] == 8)
if (
payload == None
@ -141,7 +191,8 @@ class APNSConnection:
self.sock.write(payload)
payload = self.wait_for_packet(0x0B)
# Wait for ACK
payload = self.incoming_queue.wait_pop_find(lambda i: i[0] == 0x0B)
if payload[1][0][1] != 0x00.to_bytes(1, 'big'):
raise Exception("Failed to send message")
@ -156,6 +207,19 @@ class APNSConnection:
def keep_alive(self):
self.sock.write(_serialize_payload(0x0C, []))
# def _send_ack(self, id: bytes):
# print(f"Sending ACK for message {id}")
# payload = _serialize_payload(0x0B, [(1, self.token), (4, id), (8, b"\x00")])
# self.sock.write(payload)
# #self.sock.write(_serialize_payload(0x0B, [(4, id)])
# #pass
# def recieve_message(self):
# payload = self.incoming_queue.wait_pop_find(lambda i: i[0] == 0x0A)
# # Send ACK
# self._send_ack(_get_field(payload[1], 4))
# return _get_field(payload[1], 3)
# TODO: Find a way to make this non-blocking
# def expect_message(self) -> tuple[int, list[tuple[int, bytes]]] | None:
# return _deserialize_payload(self.sock)

24
demo.py
View file

@ -140,6 +140,7 @@ def lookup(topic:str, users: list[str]):
print(f"Looking up users {users} for topic {topic}...")
resp = ids.lookup(conn, CONFIG['username'], ids_keypair, topic, users)
#print(resp)
#r = list(resp['results'].values())[0]
for k, v in resp['results'].items():
print(f"Result for user {k} topic {topic}:")
@ -156,20 +157,23 @@ def lookup(topic:str, users: list[str]):
# Hack to make sure that the requests and responses match up
# This filter MUST contain all the topics you are looking up
conn.filter(['com.apple.madrid', 'com.apple.private.alloy.facetime.multi', 'com.apple.private.alloy.multiplex1'])
import time
print("...waiting for queued messages... (this is a hack)")
time.sleep(5) # Let the server send us any messages it was holding
conn.sink() # Dump the messages
#conn.filter(['com.apple.madrid', 'com.apple.private.alloy.facetime.multi', 'com.apple.private.alloy.multiplex1', 'com.apple.private.alloy.screensharing'])
#import time
#print("...waiting for queued messages... (this is a hack)")
#time.sleep(5) # Let the server send us any messages it was holding
#conn.sink() # Dump the messages
lookup("com.apple.madrid", ["mailto:jjtech@jjtech.dev"])
lookup("com.apple.private.alloy.facetime.multi", ["mailto:jjtech@jjtech.dev"])
#lookup("com.apple.madrid", ["mailto:jjtech@jjtech.dev"])
#lookup("com.apple.private.alloy.facetime.multi", ["mailto:jjtech@jjtech.dev"])
lookup("com.apple.private.alloy.facetime.multi", ["mailto:user_test2@icloud.com"])
lookup("com.apple.madrid", ["mailto:user_test2@icloud.com"])
# lookup("com.apple.private.alloy.facetime.multi", ["mailto:user_test2@icloud.com"])
# lookup("com.apple.madrid", ["mailto:user_test2@icloud.com"])
lookup("com.apple.private.alloy.multiplex1", ["mailto:user_test2@icloud.com"])
# lookup("com.apple.private.alloy.multiplex1", ["mailto:user_test2@icloud.com"])
lookup("com.apple.private.alloy.screensharing", ["mailto:user_test2@icloud.com"])
#time.sleep(4)
# Save config
with open("config.json", "w") as f:
json.dump(CONFIG, f, indent=4)

30
ids.py
View file

@ -100,9 +100,11 @@ def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: byt
#print(headers)
msg_id = random.randbytes(16)
req = {
"cT": "application/x-apple-plist",
"U": b"\x16%C\xd5\xcd:D1\xa1\xa7z6\xa9\xe2\xbc\x8f", # Just random bytes?
"U": msg_id,
"c": 96,
"ua": USER_AGENT,
"u": bags.ids_bag()[bag_key],
@ -112,14 +114,23 @@ def _send_request(conn: apns.APNSConnection, bag_key: str, topic: str, body: byt
}
conn.send_message(topic, plistlib.dumps(req, fmt=plistlib.FMT_BINARY))
resp = conn.wait_for_packet(0x0A)
#resp = conn.wait_for_packet(0x0A)
resp_body = apns._get_field(resp[1], 3)
if resp_body is None:
raise (Exception(f"Got invalid response: {resp}"))
return resp_body
def check_response(x):
if x[0] != 0x0A:
return False
resp_body = apns._get_field(x[1], 3)
if resp_body is None:
return False
resp_body = plistlib.loads(resp_body)
return resp_body['U'] == msg_id
# Lambda to check if the response is the one we want
#conn.incoming_queue.find(check_response)
payload = conn.incoming_queue.wait_pop_find(check_response)
#conn._send_ack(apns._get_field(payload[1], 4))
resp = apns._get_field(payload[1], 3)
return plistlib.loads(resp)
# Performs an IDS lookup
@ -132,7 +143,8 @@ def lookup(conn: apns.APNSConnection, self: str, keypair: KeyPair, topic: str, q
conn.filter([topic])
query = {"uris": query}
resp = _send_request(conn, "id-query", topic, plistlib.dumps(query), keypair, self)
resp = plistlib.loads(resp)
#resp = plistlib.loads(resp)
#print(resp)
resp = gzip.decompress(resp["b"])
resp = plistlib.loads(resp)
return resp