Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite: APNs from the ground up #100

Merged
merged 30 commits into from
May 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
44073df
initial structure
JJTech0130 May 11, 2024
b3374d5
restructure to use namespaces
JJTech0130 May 11, 2024
bbb7fbb
found a better way that doesn't require .vscode config
JJTech0130 May 11, 2024
c789465
implement basics of APNs with asyncio
JJTech0130 May 13, 2024
4b33e5e
add test for async with API
JJTech0130 May 13, 2024
ada147e
add folder structure for planned packages
JJTech0130 May 13, 2024
844c015
restructure under single pip package
JJTech0130 May 13, 2024
f0a99e7
readme: update installation instructions
JJTech0130 May 13, 2024
8119f54
add setuptools scm
JJTech0130 May 13, 2024
25e15cc
packaging: make setuptools generate _version.py
JJTech0130 May 13, 2024
e8c8928
apns: start anyio refactor
JJTech0130 May 14, 2024
037b477
wip: forwarding cli tool
JJTech0130 May 14, 2024
49b38ea
more refactoring
JJTech0130 May 15, 2024
866c3c4
protocol: automatically generate packet conversion
JJTech0130 May 15, 2024
354a402
depend on rich
JJTech0130 May 15, 2024
9239fc0
apns: implement more high level commands
JJTech0130 May 15, 2024
a7e59be
apns: wrap with CommandStream
JJTech0130 May 15, 2024
4dca8b6
apns: proxy at raw packet level, so can recover if parsing fails
JJTech0130 May 15, 2024
4905023
apns: try to decode topic when parsing SendMessage packet
JJTech0130 May 15, 2024
67b2a4c
cli: refactor
JJTech0130 May 16, 2024
bb727af
proxy: use SNI rather than localhost addresses
JJTech0130 May 16, 2024
897180f
apns: refactoring, new lifecycle management
JJTech0130 May 17, 2024
121c530
apns: lifecycle improvements
JJTech0130 May 17, 2024
8221d04
apns: refactor new api out of new
JJTech0130 May 17, 2024
468a65b
test: remove dep on aioapns
JJTech0130 May 17, 2024
33cc7e5
apns: _protocol.py: clean up and rename @auto_packet -> @command
JJTech0130 May 18, 2024
c1c061b
apns: protocol.py: handle unknown Type values better
JJTech0130 May 18, 2024
1973d5c
apns: fix minor type checking error
JJTech0130 May 18, 2024
72ee9a6
apns: protocol.py: suppress __repr__ for 29, 30, and 32 PubSub comman…
JJTech0130 May 18, 2024
1c10e01
bump minimum Python to 3.9 to reflect actual testing
JJTech0130 May 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
proxy: use SNI rather than localhost addresses
  • Loading branch information
JJTech0130 committed May 16, 2024
commit bb727af213110b8229c9e3203d8da2ad41083bd1
8 changes: 1 addition & 7 deletions pypush/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ def proxy(
attach: Annotated[
bool, typer.Option(help="Use Frida to attach to the running `apsd`")
] = True,
dual: Annotated[
bool,
typer.Option(
help="EXPERIMENTAL: Listen on both 127.0.0.2 and 127.0.0.3, to proxy both production and sandbox connections"
),
] = False,
):
"""
Proxy APNs traffic between the local machine and the APNs courier
Expand All @@ -27,7 +21,7 @@ def proxy(
"""
from . import apnsproxy

apnsproxy.main(attach, dual)
apnsproxy.main(attach)


@app.command()
Expand Down
2 changes: 1 addition & 1 deletion pypush/cli/_frida.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def redirect_courier(
+ """');
console.log('getaddrinfo("' + node + '", ...) => getaddrinfo("localhost", ...)');
} else {
console.log('getaddrinfo("' + node + '", ...)');
//console.log('getaddrinfo("' + node + '", ...)');
}
}
});
Expand Down
125 changes: 72 additions & 53 deletions pypush/cli/apnsproxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,31 @@ async def forward_packets(
connection_cnt = 0


async def handle(client: TLSStream, forward):
async def handle(client: TLSStream):
global connection_cnt
connection_cnt += 1

sni = client._ssl_object.server_name # type: ignore
logging.debug(f"Got SNI: {sni}")
sandbox = "sandbox" in sni

# TODO: Check what SNI the client is connecting to, use that instead of guessing based on local IP
async with client:
client_pkt = transport.PacketStream(client)
logging.debug("Client connected")

forward = (
"1-courier.push.apple.com"
if not sandbox
else "1-courier.sandbox.push.apple.com"
)
name = f"prod-{connection_cnt}" if not sandbox else f"sandbox-{connection_cnt}"

async with await transport.create_courier_connection(forward) as conn:
logging.debug("Connected to courier")
async with anyio.create_task_group() as tg:
tg.start_soon(
forward_packets, client_pkt, conn, f"client-{connection_cnt}"
)
tg.start_soon(
forward_packets, conn, client_pkt, f"server-{connection_cnt}"
)
# await anyio.sleep(4)
# await conn.send(protocol.SetStateCommand(1, 0x7FFFFFFF).to_packet())
# tg.start_soon(forward_commands, client_cmd, conn, "client")
# tg.start_soon(forward_commands, conn, client_cm, "server")
tg.start_soon(forward_packets, client_pkt, conn, f"client-{name}")
tg.start_soon(forward_packets, conn, client_pkt, f"server-{name}")
logging.debug("Started forwarding")
logging.debug("Courier disconnected")

Expand Down Expand Up @@ -101,7 +106,12 @@ def temp_certs():
return cert_path, key_path


async def courier_proxy(host, forward):
def sni_callback(conn, server_name, ssl_context):
# Set the server name in the conn so we can use it later
conn.server_name = server_name # type: ignore


async def courier_proxy(host):
# Start listening on localhost:COURIER_PORT
listener = await anyio.create_tcp_listener(
local_port=transport.COURIER_PORT, local_host=host
Expand All @@ -110,55 +120,64 @@ async def courier_proxy(host, forward):
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
context.set_alpn_protocols(["apns-security-v3"])
context.load_cert_chain(*temp_certs())
context.set_servername_callback(sni_callback)
listener = TLSListener(listener, ssl_context=context, standard_compatible=False)
logging.info(f"Listening on {host}:{transport.COURIER_PORT}")
logging.debug(f"Forwarding to {forward}")

async def handle_wrap(client: TLSStream):
await handle(client, forward)

await listener.serve(handle_wrap)
await listener.serve(handle)


async def ainput(prompt: str = "") -> str:
print(prompt, end="")
return await anyio.to_thread.run_sync(input)


async def start(attach, double_courier=False):
# Attach to the target app
if attach:
apsd = _frida.attach_to_apsd()

async with anyio.create_task_group() as tg:
if double_courier:
logging.info("Double courier mode enabled")
logging.info(
"Make sure to run `sudo ifconfig lo0 alias 127.0.0.2` and `sudo ifconfig lo0 alias 127.0.0.3` to add the extra IPs"
)
tg.start_soon(courier_proxy, "127.0.0.2", "2-courier.push.apple.com")
tg.start_soon(
courier_proxy, "127.0.0.3", "7-courier.sandbox.push.apple.com"
)
_frida.redirect_courier(apsd, "courier.push.apple.com", "127.0.0.2")
_frida.redirect_courier(
apsd, "courier.sandbox.push.apple.com", "127.0.0.3"
)
else:
tg.start_soon(courier_proxy, "localhost", "1-courier.push.apple.com")
_frida.redirect_courier(apsd, "courier.push.apple.com", "localhost")
async def start(attach):
async with anyio.create_task_group() as tg:
tg.start_soon(courier_proxy, "localhost")
if attach:
apsd = _frida.attach_to_apsd()
_frida.redirect_courier(apsd, "courier.push.apple.com", "localhost")
_frida.redirect_courier(apsd, "courier.sandbox.push.apple.com", "localhost")
_frida.trust_all_hosts(apsd)

logging.info("Press Enter to exit...")
await ainput()
tg.cancel_scope.cancel()
else:
async with anyio.create_task_group() as tg:
tg.start_soon(courier_proxy, "localhost", "1-courier.push.apple.com")
logging.info("Press Enter to exit...")
await ainput()
tg.cancel_scope.cancel()


def main(attach, double_courier=False):
anyio.run(start, attach, double_courier)
logging.info("Press Enter to exit...")
await ainput()
tg.cancel_scope.cancel()

# # Attach to the target app
# if attach:
# apsd = _frida.attach_to_apsd()

# async with anyio.create_task_group() as tg:
# if double_courier:
# logging.info("Double courier mode enabled")
# logging.info(
# "Make sure to run `sudo ifconfig lo0 alias 127.0.0.2` and `sudo ifconfig lo0 alias 127.0.0.3` to add the extra IPs"
# )
# tg.start_soon(courier_proxy, "127.0.0.2", "2-courier.push.apple.com")
# tg.start_soon(
# courier_proxy, "127.0.0.3", "7-courier.sandbox.push.apple.com"
# )
# _frida.redirect_courier(apsd, "courier.push.apple.com", "127.0.0.2")
# _frida.redirect_courier(
# apsd, "courier.sandbox.push.apple.com", "127.0.0.3"
# )
# else:
# tg.start_soon(courier_proxy, "localhost", "1-courier.push.apple.com")

# _frida.redirect_courier(apsd, "courier.push.apple.com", "localhost")
# _frida.trust_all_hosts(apsd)

# logging.info("Press Enter to exit...")
# await ainput()
# tg.cancel_scope.cancel()
# else:
# async with anyio.create_task_group() as tg:
# tg.start_soon(courier_proxy, "localhost", "1-courier.push.apple.com")
# logging.info("Press Enter to exit...")
# await ainput()
# tg.cancel_scope.cancel()


def main(attach):
anyio.run(start, attach)