Files
Hippolyzer/addon_examples/find_packet_bugs.py
Salad Dais a39d025a04 Move Circuit and Message to lib.base
Fairly invasive, but will help make lib.base useful again. No
more Message / ProxiedMessage split!
2021-06-03 07:00:32 +00:00

102 lines
3.6 KiB
Python

"""
Test client handling of messages with malformed bodies
Serializes message, but mutates parts of the message body before the ACKs.
You don't want to use this unless you're a viewer developer trying to fix bugs.
There's a 95% chance it will crash your viewer, and maybe make you teleport random
places. Definitely don't test it while logged in with an account with access to
anything important.
"""
import copy
import datetime as dt
import logging
import random
from hippolyzer.lib.base.message.msgtypes import PacketLayout
from hippolyzer.lib.base.message.udpserializer import UDPMessageSerializer
from hippolyzer.lib.proxy.addon_utils import BaseAddon
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import Direction
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
LOG = logging.getLogger(__name__)
class PacketMutationAddon(BaseAddon):
def __init__(self):
self.serializer = UDPMessageSerializer()
def handle_lludp_message(self, session: Session, region: ProxiedRegion, message: Message):
# Only inbound messages, don't fiddle with the sim.
if message.direction != Direction.IN:
return
# Messing with these may kill your circuit
if message.name in ("PacketAck", "StartPingCheck", "CompletePingCheck"):
return
# Give login time to complete
if session.started_at + dt.timedelta(seconds=10) > dt.datetime.now():
return
# Do it randomly
if random.random() < 0.5:
return
# We need to take this message because we're going to
# send our own re-serialized version. Don't use take_message()
# because we're going to keep packet_id and acks the same.
prepared = copy.deepcopy(message)
# This message only had ACKs that were dropped.
if not region.circuit.prepare_message(prepared):
return
serialized = bytearray(self.serializer.serialize(prepared))
# Figure out where the ACKs will be so we don't mess those up
acks_size = 0
if prepared.acks:
acks_size = 1 + (len(prepared.acks) * 4)
# Give enough space for the message name, and ignore the acks at the end
# mesage name can be 5 bytes if zerocoded.
body_slice = slice(PacketLayout.PHL_NAME + 5 + message.offset, -1 - acks_size)
body_view = serialized[body_slice]
# The message is too small and we're left with nothing.
if not body_view:
return
# Can be switched out with _flip_body_bytes() or something
changed_body = self._truncate_body(body_view)
if changed_body is None:
return
serialized[body_slice] = changed_body
# Send out the raw mutated datagram
region.circuit.send_datagram(serialized, message.direction)
# Tell the proxy that we already sent the message and to short-circuit
return True
def _truncate_body(self, body_view: bytearray):
# Don't want to mess with bodies this short.
if len(body_view) < 4:
return
# Slice off the last bit of the body
del body_view[int(len(body_view) * 0.7):-1]
return body_view
def _flip_body_bytes(self, body_view: bytearray):
# Don't want to mess with bodies this short.
if len(body_view) < 4:
return
# randomly flip bytes up to 19 bytes away from the end
for i in range(-19, 0):
if random.random() < 0.3:
body_view[i] = (~body_view[i]) & 0xFF
return body_view
addons = [PacketMutationAddon()]