Research notes

This commit is contained in:
Emily 2020-04-04 16:04:40 +02:00
parent cb2d31e033
commit 558be67f61
11 changed files with 248 additions and 9 deletions

25
notes/packet_writeup.md Normal file
View File

@ -0,0 +1,25 @@
# General packet
## Client to server spesific
* Encode (encrypt) data
* use first byte as checksum
* every byte ^ 0xAA added together
* xor
* Server port ^ 0xCCCC
* Every other byte
* Lookup table (use byte as indexing value)
## Shared
* Can be internal packet
# Internal packet
|condition|name|bit length|type|notes|
|---|---|---|---|---|
||messageNumber|0x10|ushort||
||reliability|0x04|uchar|Has to be over 5 (value > 5)|
|reliability in (7, 10, 9)|orderingChannel|0x05|||
|reliability in (7, 10, 9)|orderingIndex|0x10|ushort||
||isSplitPacket|0x01|bool|Drop packet as we no longer support split packet?|
|isSplitPacket == 0|dataBitLength|0x10|ushort||
|isSplitPacket == 0|data|dataBitLength|uchar*||

13
notes/packetlist.txt Normal file
View File

@ -0,0 +1,13 @@
0x07 ID_PING
0x08 ID_PING_OPEN_CONNECTIONS
0x0B ID_CONNECTION_REQUEST
0x18 ID_OPEN_CONNECTION_REQUEST
0x19 ID_OPEN_CONNECTION_REPLY
0x1D ID_CONNECTION_ATTEMPT_FAILED
0x1F ID_NO_FREE_INCOMING_CONNECTIONS
0x24 ID_CONNECTION_BANNED
0x25 ID_INVALID_PASSWORD
0x27 ID_PONG
0x37 ID_ADVERTISE_SYSTEM
0x26 ID_MODIFIED_PACKET # default behavior (did not find the packet||could not handle because not found)

View File

@ -0,0 +1,3 @@
0x07 UNRELIABLE_SEQUENCED
0x0A RELIABLE_SEQUENCED
0x09 RELIABLE_ORDERED

View File

@ -1,7 +1,8 @@
from . import env
from . import server from . import server
from . import client from . import client
from . import shared from . import shared
from . import struct from . import struct
from . import client
from . import env
#from . import logging

View File

@ -1,14 +1,72 @@
import socket import socket
import struct
from .base import BaseClient from .base import BaseClient
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
STATE_CONNECTING = 0
STATE_AUTH = 1
class PlayerClient(BaseClient): class PlayerClient(BaseClient):
def __init__(self, server: "__ServerInstance__", ip: str, port: int): def __init__(self, server: "__ServerInstance__", ip: str, port: int):
super().__init__(server, ip, port) super().__init__(server, ip, port)
logger.debug("Client resolved to PlayerClient") logger.debug("Client resolved to PlayerClient")
self.state = STATE_CONNECTING
self.handlers = {
0x18: self.on_connection_request,
#0x00: self.on_state_dependent_handle
}
async def on_packet(self, packet: bytes): async def on_packet(self, packet: bytes):
logger.debug("on_packet(%s)" % packet) logger.debug("on_packet(%s)" % packet)
try:
packet = self.server.compressor.decompress(packet)
await self.handlers.get(packet[0], self.on_unimplemented)(packet)
except Exception as err:
logger.error(err)
async def on_unimplemented(self, packet: bytes):
pass
async def on_connection_request(self, packet: bytes):
logger.debug("on_connection_request(%s)" % packet)
if self.state != STATE_CONNECTING: # Client should not send this after it has passed
return
challenge = (self.ip_uint ^ self.server.config.challenge_short) & 0xFFFF
challenge_solution = challenge ^ 0x6969 # 0x6969 being client_version?
challenge_answer, = struct.unpack_from(b"<H", packet, 1)
if challenge_answer != challenge_solution: # Did not pass challenge
logger.debug("challenge: failed")
await self.send(struct.pack(b"<BH", 0x1A, challenge))
return
logger.debug("challenge: passed")
self.state = STATE_AUTH
await self.send(b"\x19\x00") # Challenge passed
async def parse_internal_packet(self, packet: bytes):
pass
async def on_state_dependent_handle(self, packet: bytes):
switch = {
STATE_AUTH: self.on_connection_graph_request
}
await switch.get(self.state, self.on_unimplemented)(packet)
async def on_connection_graph_request(self, packet: bytes):
packet = packet[1:] # Skip first byte (0x00)

67
sampy/raknet/bitstream.py Normal file
View File

@ -0,0 +1,67 @@
from array import array
class Bitstream:
def __init__(self):
self.offset = 0
self.buffer = array("B")
# This is not finished.. would like to implement bit_length support
def write(self, value: bool):
offset = self.offset % 8
if self.offset // 8 == len(self.buffer):
self.buffer.append(0)
self.offset += 1
if value == False:
return
mask = 1 << (7 - offset)
self.buffer[-1] |= mask
"""
def write(self, value: int, bit_length: int):
offset = self.offset % 8
shifts = offset + bit_length
while shifts > 8:
if offset == 0:
self.buffer.append(0)
v = self.buffer[buffer_index]
mask = (1 << ((self.offset % 8) + 1))
"""
def read(self, bit_length: int) -> int:
byte_read_from = self.offset // 8
byte_read_to = (self.offset + bit_length - 1) // 8
bytes_to_read = byte_read_to - byte_read_from + 1
if byte_read_to >= len(self.buffer):
raise Exception("Reading beyond the buffer")
value = 0
shifts = bytes_to_read - 1
while shifts >= 0:
value += self.buffer[byte_read_to - shifts] << (shifts * 8)
shifts -= 1
value <<= self.offset % 8
value &= (1 << (8 * bytes_to_read)) - 1
value >>= (bytes_to_read * 8) - bit_length
self.offset += bit_length
return value
def __repr__(self) -> str:
b = bytearray(" " + " ".join(" ".join(format(c, "08b")) for c in self.buffer) + " ", "ascii")
m = self.offset * 2 + (self.offset // 8) - (self.offset // (max(1, len(self.buffer)) * 8))
#b[m] = 91 # [
#b[m + 2] = 93 # ]
b[m] = 124 # |
return b.decode()

View File

@ -52,7 +52,7 @@ class Server:
if addr not in self.clients: if addr not in self.clients:
ip, port = addr ip, port = addr
self.clients[addr] = Client(self, ip, port) self.clients[addr] = Client(self, ip, port)
await self.clients[addr].on_packet(data) await (self.clients[addr].onpacket(data)) #await self.clients[addr].on_packet(data)
disconnected = [c for c in self.clients.values() if c.connected == False] disconnected = [c for c in self.clients.values() if c.connected == False]
for c in disconnected: # Remove dead connections for c in disconnected: # Remove dead connections

View File

@ -19,7 +19,7 @@ class Compressor:
Arguments: Arguments:
bytestream {bytes} -- Bytes sent by client bytestream {bytes} -- Bytes sent by client
""" """
checksum, data = bytestream[0], bytestream[1:] checksum, data = bytestream[0], bytearray(bytestream[1:])
data = self.xor_every_other_byte(self.get_port_xor_key(), data) data = self.xor_every_other_byte(self.get_port_xor_key(), data)
data = self.run_though_lookup_table(data) data = self.run_though_lookup_table(data)
@ -28,17 +28,19 @@ class Compressor:
logger.error("Checksum failed!") logger.error("Checksum failed!")
raise Exception("Checksum fail") raise Exception("Checksum fail")
return data logger.debug("decompress -> %s" % data)
def xor_every_other_byte(self, xor: int, bytestream: bytes) -> bytes: return bytes(data)
def xor_every_other_byte(self, xor: int, bytestream: bytearray) -> bytearray:
for i in range(1, len(bytestream), 2): for i in range(1, len(bytestream), 2):
bytestream[i] ^= xor bytestream[i] ^= xor
return bytestream return bytestream
def run_though_lookup_table(self, bytestream: bytes) -> bytes: def run_though_lookup_table(self, bytestream: bytearray) -> bytearray:
return bytes(LOOKUP_TABLE[b] for b in bytestream) return bytes(LOOKUP_TABLE[b] for b in bytestream)
def calc_checksum(self, bytestream: bytes) -> int: def calc_checksum(self, bytestream: bytearray) -> int:
checksum = 0 checksum = 0
for byte in bytestream: for byte in bytestream:
checksum ^= byte & 0xAA checksum ^= byte & 0xAA
@ -46,3 +48,10 @@ class Compressor:
def get_port_xor_key(self) -> int: def get_port_xor_key(self) -> int:
return (self.config.port ^ 0xCCCC) & 0xFF return (self.config.port ^ 0xCCCC) & 0xFF
class StringCompressor:
def __init__(self):
pass
def decode_string(self, data: bytes) -> (str, int):
pass

33
sampy/struct/packet.py Normal file
View File

@ -0,0 +1,33 @@
class InternalPacket:
def __init__(self):
self.message_number = message_number # ushort
self.pack_number = pack_number # uint
self.priority = priority # int? this could be a byte (actually 2 bits is enough)
self.reliability = reliability # int? this could be a byte (actually 3 bits is enough)
self.ordering_channel = ordering_channel # uchar
self.ordering_index = ordering_index # ushort
self.split_packet_id = split_packet_id # ushort
self.split_packet_index = split_packet_index # uint
self.split_packet_count = split_packet_count # uint
self.creation_time = creation_time # long long
self.next_action_time = next_action_time # long long
self.data_bit_length = data_bit_length # uint
self.data = data # char*
self.histogram_marker = histogram_marker # uint
def get_bitstream_header_length(self) -> int:
bit_length = 32 # 2 * 2 * 8
bit_length += 3 # TODO: merge ^
if self.reliability in [1, 3, 4]:
bit_length += 5
bit_length += 16 # 2 * 8 TODO: merge ^
bit_length += 1 # TODO: merge ^
if self.split_packet_count > 0:
bit_length += 80 # (2 + 4 * 2) * 8
bit_length += 16 # TODO: merge ^
return bit_length

View File

@ -16,7 +16,7 @@ class ServerConfig:
self.rcon_password = rcon_password self.rcon_password = rcon_password
self.max_players = max_players self.max_players = max_players
self.mode = mode self.mode = mode
self.language = language self.language = language

30
test.py Normal file
View File

@ -0,0 +1,30 @@
from sampy.raknet.bitstream import Bitstream
a = Bitstream()
"""
a.buffer.append(0x42)
a.buffer.append(0x13)
a.buffer.append(0xFF)
a.buffer.append(0xAC)
a.buffer.append(0x00)
a.buffer.append(0x69)
"""
"""
a.write(1)
a.write(0)
a.write(1)
a.write(1)
a.write(1)
a.write(1)
a.write(0)
a.write(1)
a.write(1)
"""
data = [int(x, 16) for x in "00 00 42 90 0b 61 61 61 61 62 62 62 62 63 63 63 63 66 66 66 66 00".split(" ")]
for b in data:
a.buffer.append(b)
print(a)