Research notes
This commit is contained in:
parent
cb2d31e033
commit
558be67f61
25
notes/packet_writeup.md
Normal file
25
notes/packet_writeup.md
Normal 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
13
notes/packetlist.txt
Normal 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)
|
3
notes/reliabilityLayer.txt
Normal file
3
notes/reliabilityLayer.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
0x07 UNRELIABLE_SEQUENCED
|
||||||
|
0x0A RELIABLE_SEQUENCED
|
||||||
|
0x09 RELIABLE_ORDERED
|
|
@ -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
|
|
@ -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
67
sampy/raknet/bitstream.py
Normal 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()
|
|
@ -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
|
||||||
|
|
|
@ -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
33
sampy/struct/packet.py
Normal 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
|
|
@ -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
30
test.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user