Files
2022-09-12 20:59:52 +03:00

244 lines
8.1 KiB
Python

import hashlib
import hmac
import json
from pathlib import Path
from frida_tools.application import Reactor
import frida
ENABLE_CONTROL_INTERFACE = True
class Application:
def __init__(self):
self._reactor = Reactor(run_until_return=self._process_input)
cluster_params = frida.EndpointParameters(
address="unix:/Users/oleavr/src/cluster",
certificate="/Users/oleavr/src/identity2.pem",
authentication=("token", "wow-such-secret"),
)
if ENABLE_CONTROL_INTERFACE:
www = Path(__file__).parent.resolve() / "web_client" / "dist"
control_params = frida.EndpointParameters(
address="::1", port=27042, authentication=("callback", self._authenticate), asset_root=www
)
else:
control_params = None
service = frida.PortalService(cluster_params, control_params)
self._service = service
self._device = service.device
self._peers = {}
self._nicks = set()
self._channels = {}
service.on("node-connected", lambda *args: self._reactor.schedule(lambda: self._on_node_connected(*args)))
service.on("node-joined", lambda *args: self._reactor.schedule(lambda: self._on_node_joined(*args)))
service.on("node-left", lambda *args: self._reactor.schedule(lambda: self._on_node_left(*args)))
service.on("node-disconnected", lambda *args: self._reactor.schedule(lambda: self._on_node_disconnected(*args)))
service.on(
"controller-connected", lambda *args: self._reactor.schedule(lambda: self._on_controller_connected(*args))
)
service.on(
"controller-disconnected",
lambda *args: self._reactor.schedule(lambda: self._on_controller_disconnected(*args)),
)
service.on("authenticated", lambda *args: self._reactor.schedule(lambda: self._on_authenticated(*args)))
service.on("subscribe", lambda *args: self._reactor.schedule(lambda: self._on_subscribe(*args)))
service.on("message", lambda *args: self._reactor.schedule(lambda: self._on_message(*args)))
def run(self):
self._reactor.schedule(self._start)
self._reactor.run()
def _start(self):
self._service.start()
self._device.enable_spawn_gating()
def _stop(self):
self._service.stop()
def _process_input(self, reactor):
while True:
try:
command = input("Enter command: ").strip()
except KeyboardInterrupt:
self._reactor.cancel_io()
return
if len(command) == 0:
print("Processes:", self._device.enumerate_processes())
continue
if command == "stop":
self._reactor.schedule(self._stop)
break
def _authenticate(self, raw_token):
try:
token = json.loads(raw_token)
nick = str(token["nick"])
secret = token["secret"].encode("utf-8")
except:
raise ValueError("invalid request")
provided = hashlib.sha1(secret).digest()
expected = hashlib.sha1(b"knock-knock").digest()
if not hmac.compare_digest(provided, expected):
raise ValueError("get outta here")
return {
"nick": nick,
}
def _on_node_connected(self, connection_id, remote_address):
print("on_node_connected()", connection_id, remote_address)
def _on_node_joined(self, connection_id, application):
print("on_node_joined()", connection_id, application)
print("\ttags:", self._service.enumerate_tags(connection_id))
def _on_node_left(self, connection_id, application):
print("on_node_left()", connection_id, application)
def _on_node_disconnected(self, connection_id, remote_address):
print("on_node_disconnected()", connection_id, remote_address)
def _on_controller_connected(self, connection_id, remote_address):
print("on_controller_connected()", connection_id, remote_address)
self._peers[connection_id] = Peer(connection_id, remote_address)
def _on_controller_disconnected(self, connection_id, remote_address):
print("on_controller_disconnected()", connection_id, remote_address)
peer = self._peers.pop(connection_id)
for channel in list(peer.memberships):
channel.remove_member(peer)
if peer.nick is not None:
self._release_nick(peer.nick)
def _on_authenticated(self, connection_id, session_info):
print("on_authenticated()", connection_id, session_info)
peer = self._peers.get(connection_id, None)
if peer is None:
return
peer.nick = self._acquire_nick(session_info["nick"])
def _on_subscribe(self, connection_id):
print("on_subscribe()", connection_id)
self._service.post(connection_id, {"type": "welcome", "channels": list(self._channels.keys())})
def _on_message(self, connection_id, message, data):
peer = self._peers[connection_id]
mtype = message["type"]
if mtype == "join":
self._get_channel(message["channel"]).add_member(peer)
elif mtype == "part":
channel = self._channels.get(message["channel"], None)
if channel is None:
return
channel.remove_member(peer)
elif mtype == "say":
channel = self._channels.get(message["channel"], None)
if channel is None:
return
channel.post(message["text"], peer)
elif mtype == "announce":
self._service.broadcast({"type": "announce", "sender": peer.nick, "text": message["text"]})
else:
print("Unhandled message:", message)
def _acquire_nick(self, requested):
candidate = requested
serial = 2
while candidate in self._nicks:
candidate = requested + str(serial)
serial += 1
nick = candidate
self._nicks.add(nick)
return nick
def _release_nick(self, nick):
self._nicks.remove(nick)
def _get_channel(self, name):
channel = self._channels.get(name, None)
if channel is None:
channel = Channel(name, self._service)
self._channels[name] = channel
return channel
class Peer:
def __init__(self, connection_id, remote_address):
self.nick = None
self.connection_id = connection_id
self.remote_address = remote_address
self.memberships = set()
def to_json(self):
return {"nick": self.nick, "address": self.remote_address[0]}
class Channel:
def __init__(self, name, service):
self.name = name
self.members = set()
self.history = []
self._service = service
def add_member(self, peer):
if self in peer.memberships:
return
peer.memberships.add(self)
self.members.add(peer)
self._service.narrowcast(self.name, {"type": "join", "channel": self.name, "user": peer.to_json()})
self._service.tag(peer.connection_id, self.name)
self._service.post(
peer.connection_id,
{
"type": "membership",
"channel": self.name,
"members": [peer.to_json() for peer in self.members],
"history": self.history,
},
)
def remove_member(self, peer):
if self not in peer.memberships:
return
peer.memberships.remove(self)
self.members.remove(peer)
self._service.untag(peer.connection_id, self.name)
self._service.narrowcast(self.name, {"type": "part", "channel": self.name, "user": peer.to_json()})
def post(self, text, peer):
if self not in peer.memberships:
return
item = {"type": "chat", "sender": peer.nick, "text": text}
self._service.narrowcast(self.name, item)
history = self.history
history.append(item)
if len(history) == 20:
history.pop(0)
if __name__ == "__main__":
app = Application()
app.run()