clientbot/spykhanat.py

416 lines
24 KiB
Python
Executable file

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# script to read pcap file (communcation with khanat)
#
# Copyright (C) 2019 AleaJactaEst
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Ex.:
# sudo tcpdump -i docker0 -w capture2.pcap
# ./spykhanat.py --pcap-file=../capture2.pcap --msg-xml=../khanat-opennel-code/code/ryzom/common/data_common/msg.xml
import argparse
import logging
from pcapfile import savefile
from pcapfile.protocols.linklayer import ethernet
from pcapfile.protocols.network import ip
from pcapfile.protocols.transport import udp
from pcapfile.protocols.transport import tcp
import binascii
import re
from tools import BitStream
from tools import Enum
from tools import CActionFactory
from tools import CBitSet
from tools import DecodeImpulse
from tools import World
from tools import CGenericMultiPartTemp
from tools import CImpulseDecoder
from tools import CStringManager
from tools import CAction
import xml.etree.ElementTree as ET
LOGGER = 'SpyKhanat'
#file = open('capture2.pcap' , 'rb')
#pcapfile = savefile.load_savefile(file,verbose=True)
#pkt = pcapfile.packets[0]
#print(pkt.raw())
#print(pkt.timestamp)
#eth_frame = ethernet.Ethernet(pkt.raw())
#print(eth_frame)
#ip_packet = ip.IP(binascii.unhexlify(eth_frame.payload))
#print(ip_packet)
class SpyPcap():
def __init__(self, khanat_host_service, pcap_file, msg_xml, filter_host_service, show_raw_packet, show_message_decoded):
if khanat_host_service:
self.khanat_host_service = re.compile(khanat_host_service)
else:
self.khanat_host_service = None
self.pcap_file = pcap_file
self.msg_xml = msg_xml
if filter_host_service:
self.filter_host_service = re.compile(filter_host_service)
else:
self.filter_host_service = None
self.show_raw_packet = show_raw_packet
self.show_message_decoded = show_message_decoded
self.actionFactory = CActionFactory.CActionFactory(None)
self.client_state = {}
self.decodeImpulse = DecodeImpulse.DecodeImpulse()
fp = open(msg_xml , 'rt')
msgRawXml = fp.read()
self.msgXml = ET.fromstring(msgRawXml)
self.decodeImpulse.loadMsg(self.msgXml)
def readRaw(self):
file = open( self.pcap_file , 'rb')
pcapfile = savefile.load_savefile(file,verbose=False)
for pkt in pcapfile.packets:
print("pkt:", dir(pkt))
print("pkt.header:", dir(pkt.header))
print("pkt.packet:", dir(pkt.packet))
logging.getLogger(LOGGER).debug("raw: %s" % pkt.raw())
logging.getLogger(LOGGER).debug("timestamp: %s" % pkt.timestamp)
eth_frame = ethernet.Ethernet(pkt.raw())
logging.getLogger(LOGGER).debug("eth_frame: %s" % eth_frame)
if eth_frame.type == 2048:
ip_packet = ip.IP(binascii.unhexlify(eth_frame.payload))
logging.getLogger(LOGGER).debug("ip packet: %s" % ip_packet)
print("ip_packet:", dir(ip_packet))
logging.getLogger(LOGGER).debug("ip packet: %s ->%s" % (ip_packet.src.decode(), ip_packet.dst.decode()) )
if ip_packet.p == 17:
# UDP
logging.getLogger(LOGGER).debug("ip packet: protocol UDP (%s)" % ip_packet.p)
udp_packet = udp.UDP(binascii.unhexlify(ip_packet.payload))
print("udp_packet:", dir(udp_packet))
logging.getLogger(LOGGER).debug("UDP packet: %s" % udp_packet)
data = udp_packet.payload
print("data:", dir(data))
logging.getLogger(LOGGER).debug("data packet: %s" % data)
data = udp_packet.payload
logging.getLogger(LOGGER).info("data packet: timestamp:%s src:%s:%d dst:%s:%d data:%s" % (pkt.timestamp,
ip_packet.src.decode(), udp_packet.src_port,
ip_packet.dst.decode(), udp_packet.dst_port,
data.decode()))
elif ip_packet.p == 6:
# TCP
logging.getLogger(LOGGER).debug("ip packet: protocol TCP (%s)" % ip_packet.p)
tcp_packet = tcp.TCP(binascii.unhexlify(ip_packet.payload))
data = tcp_packet.payload
logging.getLogger(LOGGER).info("data packet: timestamp:%s src:%s:%d dst:%s:%d data:%s" % (pkt.timestamp,
ip_packet.src.decode(), tcp_packet.src_port,
ip_packet.dst.decode(), tcp_packet.dst_port,
data.decode()))
else:
logging.getLogger(LOGGER).debug("ip packet: protocol (%s)" % ip_packet.p)
def detect_khanat_server(self, packets):
hostdetected = {}
for pkt in packets:
eth_frame = ethernet.Ethernet(pkt.raw())
if eth_frame.type == 2048:
ip_packet = ip.IP(binascii.unhexlify(eth_frame.payload))
if ip_packet.p == 17: # UDP
udp_packet = udp.UDP(binascii.unhexlify(ip_packet.payload))
data = udp_packet.payload
host = "%s:%d" % (ip_packet.src.decode(), udp_packet.src_port)
hostdetected.setdefault(host, 0)
if len(data) == 20:
hostdetected[host] += 1
id = None
max = 0
for host in hostdetected:
if id:
if max < hostdetected[host]:
max = hostdetected[host]
id = host
else:
max = hostdetected[host]
id = host
logging.getLogger(LOGGER).info("khanat host :%s" % id)
return id
def initialize_client(self, clientid):
self.client_state.setdefault(clientid, {'CurrentReceivedNumber': 0,
'CurrentSendNumber': 0,
'LastReceivedAck': 0,
'RegisteredAction': {},
'world': World.World(),
'GenericMultiPartTemp': CGenericMultiPartTemp.GenericMultiPartTemp(),
'CImpulseDecoder': None,
'LastAck0': [-1],
'LastAck1': [-1, -1],
'LastAck2': [-1, -1, -1, -1]})
self.client_state[clientid]['CImpulseDecoder'] = CImpulseDecoder.CImpulseDecoder(self.client_state[clientid]['world'])
def add_registered_action(self, clientid, action):
self.client_state[clientid]['RegisteredAction'].setdefault(action.Code, [])
self.client_state[clientid]['RegisteredAction'][action.Code].append(action)
def decode_server(self, clientid, msgin, receivedPacket, receivedAck, nextSentPacket=0):
actions = []
for level in range(0, 3):
if level == 0:
lAck = self.client_state[clientid]['LastAck0']
channel = 0
elif level == 1:
lAck = self.client_state[clientid]['LastAck1']
channel = receivedPacket & 1
elif level == 2:
lAck = self.client_state[clientid]['LastAck2']
channel = receivedPacket & 3
keep = True
checkOnce = False
num = 0
#logging.getLogger(LOGGER).debug("level:%d channel:%d lAck:%s" %(level, channel, ':'.join([str(x) for x in lAck])))
# lastAck = lAck[channel]
while True:
next = msgin.readBool('next:' + str(level) + ':' + str(channel))
if not next:
break
if not checkOnce:
checkOnce = True
keep = receivedAck >= lAck[channel]
logging.getLogger(LOGGER).debug("keep:%s [%d => %d]" % (str(keep), receivedAck, lAck[channel]))
if keep:
lAck[channel] = nextSentPacket
logging.getLogger(LOGGER).debug("lAck:%s" % ':'.join([str(x) for x in lAck]))
pass
num += 1
#actionFactory = CAction.CActionFactory(None)
action = self.actionFactory.unpack(msgin)
logging.getLogger(LOGGER).debug("action:%s" % action)
#action = self._CActionFactory.unpack(msgin)
if keep:
logging.getLogger(LOGGER).debug("keep Code:%s" % str(action.Code))
actions.append(action)
elif action:
logging.getLogger(LOGGER).debug("append Code:%s" % str(action.Code))
self.add_registered_action(clientid, action)
return actions
def decode_client_receive_normal_message(self, msgin, clientid, dst):
actions = self.client_state[clientid]['CImpulseDecoder'].decode(msgin, self.client_state[clientid]['CurrentReceivedNumber'], self.client_state[clientid]['LastReceivedAck'], self.client_state[clientid]['CurrentSendNumber'] )
logging.getLogger(LOGGER).info("[Client -> Khanat] actions:%s" % str(actions))
# decodeVisualProperties( msgin );
def decode_client_message(self, msgin, clientid, dst):
CurrentReceivedNumber = msgin.readSint32('CurrentReceivedNumber')
SystemMode = msgin.readBool('SystemMode')
logging.getLogger(LOGGER).debug("[Client -> Khanat] {CurrentReceivedNumber:%d, SystemMode:%d, src:%s, dst:%s}" % (CurrentReceivedNumber, SystemMode, clientid, dst))
self.initialize_client(clientid)
self.client_state[clientid]['CurrentReceivedNumber'] = CurrentReceivedNumber
if not SystemMode:
LastReceivedAck = msgin.readSint32('LastReceivedAck')
self.client_state[clientid]['LastReceivedAck'] = LastReceivedAck
logging.getLogger(LOGGER).info("[Client -> Khanat] Normal Mode {CurrentReceivedNumber:%d, src:%s, dst:%s, LastReceivedAck:%d}" % (CurrentReceivedNumber, clientid, dst, LastReceivedAck))
#self.decode_server(msgin, _CurrentReceivedNumber, _CurrentReceivedNumber-1)
self.decode_client_receive_normal_message(msgin, clientid, dst)
else:
message = msgin.readUint8('message')
try:
typeMessage = Enum.CLFECOMMON(message).name
except ValueError:
typeMessage = "Unknown"
if message == Enum.CLFECOMMON.SYSTEM_LOGIN_CODE:
UserAddr = msgin.readUint32('UserAddr')
UserKey = msgin.readUint32('UserKey')
UserId = msgin.readUint32('UserId')
LanguageCode = msgin.readString('LanguageCode')
logging.getLogger(LOGGER).info("[Client -> Khanat] System Mode:%s {CurrentReceivedNumber:%d, src:%s, dst:%s, UserAddr:%d, UserId:%d, UserAddr:%d LanguageCode:%s}" %
(typeMessage, CurrentReceivedNumber, clientid, dst, UserAddr, UserKey, UserId, LanguageCode))
elif message == Enum.CLFECOMMON.SYSTEM_PROBE_CODE:
LatestProbe = msgin.readSint32('LatestProbe')
logging.getLogger(LOGGER).info("[Client -> Khanat] System Mode:%s probe:%d {CurrentReceivedNumber:%d, src:%s, dst:%s}" % (typeMessage,
LatestProbe, CurrentReceivedNumber, clientid, dst))
elif message == Enum.CLFECOMMON.SYSTEM_SYNC_CODE:
Synchronize = msgin.readUint32('Synchronize')
stime = msgin.readSint64('stime')
LatestSync = msgin.readUint32('LatestSync')
MsgData = msgin.readArrayUint8(16, 'MsgData')
md5Msg = bytes(MsgData)
DatabaseData = msgin.readArrayUint8(16, 'DatabaseData')
md5Database = bytes(DatabaseData)
logging.getLogger(LOGGER).info("[Client -> Khanat] System Mode:%s {CurrentReceivedNumber:%d, src:%s, dst:%s, Synchronize:%d, stime:%d, LatestSync:%d, MsgData:%s, DatabaseData:%s}" % (
typeMessage, CurrentReceivedNumber, clientid, dst, Synchronize, stime, LatestSync, binascii.hexlify(md5Msg).decode(), binascii.hexlify(md5Database).decode()))
elif message == Enum.CLFECOMMON.SYSTEM_ACK_SYNC_CODE:
LastReceivedNumber = msgin.readSint32('LastReceivedNumber')
LastAckInLongAck = msgin.readSint32('LastAckInLongAck')
LongAckBitField = CBitSet.CBitSet()
LongAckBitField.readSerial(msgin, 'LongAckBitField')
LatestSync = msgin.readSint32('LatestSync')
logging.getLogger(LOGGER).info("[Client -> Khanat] System Mode:%s {CurrentReceivedNumber:%d, src:%s, dst:%s, LastReceivedNumber:%d, LastAckInLongAck:%d, LatestSync:%d, LongAckBitField:%s}" % (
typeMessage, CurrentReceivedNumber, clientid, dst, LastReceivedNumber, LastAckInLongAck, LatestSync, LongAckBitField))
elif message == Enum.CLFECOMMON.SYSTEM_ACK_PROBE_CODE:
SizeLatestProbes = msgin.readSint32('SizeLatestProbes')
LatestProbes = []
for data in range(0, SizeLatestProbes):
LatestProbes.append(msgin.readSint32('LatestProbes'))
logging.getLogger(LOGGER).info("[Client -> Khanat] System Mode:%s (%d) {CurrentReceivedNumber:%d, src:%s, dst:%s, SizeLatestProbes:%d, LatestProbes:%s}" % (typeMessage, message, CurrentReceivedNumber, clientid, dst, SizeLatestProbes, str(LatestProbes)))
else:
logging.getLogger(LOGGER).info("[Client -> Khanat] System Mode:%s (%d) {CurrentReceivedNumber:%d, src:%s, dst:%s}" % (typeMessage, message, CurrentReceivedNumber, clientid, dst))
logging.getLogger(LOGGER).debug("[Client -> Khanat] msg:%s" % msgin.showAllData())
def decode_khanat_message(self, msgin, src, dst):
CurrentSendNumber = msgin.readSint32('CurrentSendNumber')
logging.getLogger(LOGGER).debug("[Khanat -> Client] {CurrentSendNumber:%d, src:%s, dst:%s}" % (CurrentSendNumber, src, dst))
SystemMode = msgin.readBool('SystemMode')
self.initialize_client(dst)
self.client_state[dst]['CurrentSendNumber'] = CurrentSendNumber
if not SystemMode:
_LastReceivedAck = msgin.readSint32('LastReceivedAck');
logging.getLogger(LOGGER).debug("[Khanat -> Client] Normal Mode {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d}" % (CurrentSendNumber, src, dst, _LastReceivedAck))
actions = self.decode_server(dst, msgin, CurrentSendNumber, CurrentSendNumber-1)
if actions:
logging.getLogger(LOGGER).debug('list actions: [' + str(len(actions)) + '] ' +','.join( [ str(x) for x in actions] ) )
else:
logging.getLogger(LOGGER).debug('list actions: None')
# Decode the actions received in the impulsions
logging.getLogger(LOGGER).debug('=' * 80)
for action in actions:
logging.getLogger(LOGGER).debug('-' * 80)
logging.getLogger(LOGGER).debug('Analyse actions:%s', action)
if action.Code == Enum.TActionCode.ACTION_DISCONNECTION_CODE:
logging.getLogger(LOGGER).info("Action : ACTION_DISCONNECTION_CODE")
elif action.Code == Enum.TActionCode.ACTION_GENERIC_CODE:
action.genericAction(self.decodeImpulse, self.client_state[dst]['world'], self.client_state[dst]['GenericMultiPartTemp'])
logging.getLogger(LOGGER).info("[Khanat -> Client] ACTION_GENERIC_CODE : {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d, action:%s}" % (CurrentSendNumber, src, dst, _LastReceivedAck, action))
elif action.Code == Enum.TActionCode.ACTION_GENERIC_MULTI_PART_CODE:
action.genericAction(self.decodeImpulse, self.client_state[dst]['world'], self.client_state[dst]['GenericMultiPartTemp'])
logging.getLogger(LOGGER).debug("[Khanat -> Client] ACTION_GENERIC_MULTI_PART_CODE : %s" % action)
for id in self.client_state[dst]['GenericMultiPartTemp'].data:
if self.client_state[dst]['GenericMultiPartTemp'].data[id].isAvailable():
logging.getLogger(LOGGER).info("[Khanat -> Client] ACTION_GENERIC_MULTI_PART_CODE {CurrentSendNumber:%d, src:%s, dst:%s, _LastReceivedAck:%d, id:%d, msg:%s}" % (CurrentSendNumber, src, dst, _LastReceivedAck, id, self.client_state[dst]['GenericMultiPartTemp'].data[id].read().showAllData()))
elif action.Code == Enum.TActionCode.ACTION_DUMMY_CODE:
logging.getLogger(LOGGER).info("Action : ACTION_DUMMY_CODE")
self.add_registered_action(dst, action)
# # remove all old actions that are acked
# while self._Actions and self._Actions[0].FirstPacket != 0 and self._Actions[0].FirstPacket < self._LastReceivedAck:
# logging.getLogger(LOGGER).debug("remove old action [%d/%d] : %s" % (self._Actions[0].FirstPacket, self._LastReceivedAck, self._Actions[0]))
# self._Actions.pop(0)
else:
message = msgin.readUint8('message')
logging.getLogger(LOGGER).debug("[Khanat -> Client] System Mode {CurrentSendNumber:%d, src:%s, dst:%s, message:%d" % (CurrentSendNumber, src, dst, message))
if message == Enum.CLFECOMMON.SYSTEM_SYNC_CODE:
logging.getLogger(LOGGER).debug("[Khanat -> Client] Synchronize")
Synchronize = msgin.readUint32('Synchronize')
stime = msgin.readSint64('stime')
LatestSync = msgin.readUint32('LatestSync')
MsgData = msgin.readArrayUint8(16, 'MsgData')
DatabaseData = msgin.readArrayUint8(16, 'DatabaseData')
logging.getLogger(LOGGER).info("[Khanat -> Client] System Mode / Synchronize {CurrentSendNumber:%d, src:%s, dst:%s, message:%d, Synchronize:%d, stime:%d, LatestSync:%d, MsgData:%s, DatabaseData:%s}" % (CurrentSendNumber, src, dst, message, Synchronize, stime, LatestSync, str(MsgData), str(DatabaseData)))
elif message == Enum.CLFECOMMON.SYSTEM_STALLED_CODE:
logging.getLogger(LOGGER).debug("[Khanat -> Client] Stalled")
logging.getLogger(LOGGER).info("[Khanat -> Client] System Mode / Stalled {CurrentSendNumber:%d, src:%s, dst:%s, message:%d}" % (CurrentSendNumber, src, dst, message))
elif message == Enum.CLFECOMMON.SYSTEM_PROBE_CODE:
logging.getLogger(LOGGER).debug("[Khanat -> Client] Probe")
LatestProbe = msgin.readSint32('LatestProbe')
logging.getLogger(LOGGER).info("[Khanat -> Client] System Mode / Probe {CurrentSendNumber:%d, src:%s, dst:%s, message:%d, LatestProbe:%d}" % (CurrentSendNumber, src, dst, message, LatestProbe))
elif message == Enum.CLFECOMMON.SYSTEM_SERVER_DOWN_CODE:
logging.getLogger(LOGGER).info("[Khanat -> Client] System Mode / BACK-END DOWN {CurrentSendNumber:%d, src:%s, dst:%s, message:%d}" % (CurrentSendNumber, src, dst, message))
else:
logging.getLogger(LOGGER).warning("CNET: received system %d in state Login" % message)
#cActionFactory = CAction.CActionFactory(None)
#cActionFactory.unpack(msgin)
logging.getLogger(LOGGER).debug("[Khanat -> Client] msg:%s" % msgin.showAllData())
def read(self):
file = open( self.pcap_file , 'rb')
pcapfile = savefile.load_savefile(file,verbose=False)
khanat_host = self.detect_khanat_server(pcapfile.packets)
for pkt in pcapfile.packets:
eth_frame = ethernet.Ethernet(pkt.raw())
if eth_frame.type == 2048:
ip_packet = ip.IP(binascii.unhexlify(eth_frame.payload))
if ip_packet.p == 17:
# UDP
udp_packet = udp.UDP(binascii.unhexlify(ip_packet.payload))
data = udp_packet.payload
if udp_packet.src_port == 53 or udp_packet.dst_port == 53:
continue
if not self.filter_host_service or self.filter_host_service.match("%s:%d" % (ip_packet.src.decode(), udp_packet.src_port)) or self.filter_host_service.match("%s:%d" % (ip_packet.dst.decode(), udp_packet.dst_port)):
logging.getLogger(LOGGER).debug("-" * 80)
if self.show_raw_packet:
logging.getLogger(LOGGER).debug("[raw packet] timestamp:%s src:%s:%d dst:%s:%d data:%s" % (pkt.timestamp,
ip_packet.src.decode(), udp_packet.src_port,
ip_packet.dst.decode(), udp_packet.dst_port,
data.decode()))
msgin = BitStream.BitStream()
msgin.fromBytes(binascii.unhexlify(data))
src = "%s:%d" % (ip_packet.src.decode(), udp_packet.src_port)
dst = "%s:%d" % (ip_packet.dst.decode(), udp_packet.dst_port)
if (self.khanat_host_service and self.khanat_host_service.match(src)) or ( not self.khanat_host_service and khanat_host == src):
self.decode_khanat_message(msgin, src, dst)
else:
self.decode_client_message(msgin, src, dst)
if self.show_message_decoded:
logging.getLogger(LOGGER).debug("[message decoded] %s" % msgin.showAllData())
for client in self.client_state:
logging.getLogger(LOGGER).debug("%s [server tick:%d, client tick:%d]" %(client, self.client_state[client]['CurrentSendNumber'], self.client_state[client]['CurrentReceivedNumber']))
def main():
FORMAT = '%(asctime)-15s %(levelname)s %(filename)s:%(lineno)d %(message)s'
logging.basicConfig(format=FORMAT)
logger = []
logger.append(logging.getLogger(LOGGER))
logger.append(logging.getLogger(CImpulseDecoder.LOGGER))
#logger.append(logging.getLogger(DecodeImpuls.LOGGER))
#logger.append(logging.getLogger(BitStream.LOGGER))
logger.append(logging.getLogger(CStringManager.LOGGER))
logger.append(logging.getLogger(CAction.LOGGER))
logger.append(logging.getLogger(CActionFactory.LOGGER))
CImpulseDecoder
# logger.append(logging.getLogger('CGenericMultiPartTemp'))
parser = argparse.ArgumentParser()
parser.add_argument("--khanat-host-service", help="filter to detect khanat host:service (FES)")
parser.add_argument("--filter-host-service", help="filter host:service")
parser.add_argument("-d", "--debug", help="show debug message", action='store_true')
parser.add_argument("-p", "--pcap-file", help="file pcap to read", required=True)
parser.add_argument("-m", "--msg-xml", help="file msg.xml (from server khanat)", required=True)
parser.add_argument("-r", "--raw", help="show message raw", action='store_true')
parser.add_argument("--show-raw-packet", help="show packet (raw data)", action='store_true')
parser.add_argument("--show-message-decoded", help="show packet (raw data)", action='store_true')
args = parser.parse_args()
if args.debug:
level = logging.getLevelName('DEBUG')
else:
level = logging.getLevelName('INFO')
for logid in logger:
logid.setLevel(level)
logging.getLogger(LOGGER).info("Begin")
spy = SpyPcap(khanat_host_service=args.khanat_host_service, pcap_file=args.pcap_file, msg_xml=args.msg_xml, filter_host_service=args.filter_host_service,
show_raw_packet=args.show_raw_packet, show_message_decoded=args.show_message_decoded)
if args.raw:
spy.readRaw()
else:
spy.read()
logging.getLogger(LOGGER).info("End")
if __name__ == "__main__":
main()