Skip to content

Instantly share code, notes, and snippets.

@cymruu
Last active September 13, 2024 20:35
Show Gist options
  • Save cymruu/544dee66f40579cf576c95dd218894e6 to your computer and use it in GitHub Desktop.
Save cymruu/544dee66f40579cf576c95dd218894e6 to your computer and use it in GitHub Desktop.
tibia.py
'''based on volf ram code'''
import random
import struct
import zlib
import socket
OT_RSA = 109120132967399429278860960508995541528237502902798129123468757937266291492576446330739696001110603907230888610072655818825358503429057592827629436413108566029093628212635953836686562675849720620786279431090218017681061521755056710823876476444260558147179707119674283982419152118103759076030616683978566631413
def rsa_encrypt(m):
m = sum(x*pow(256, i) for i, x in enumerate(reversed(m)))
c = pow(m, 65537, OT_RSA)
return bytes((c >> i) & 255 for i in reversed(range(0, 1024, 8)))
def rsa_encrypt(m):
m = sum(x*pow(256, i) for i, x in enumerate(reversed(m)))
c = pow(m, 65537, OT_RSA)
return bytes((c >> i) & 255 for i in reversed(range(0, 1024, 8)))
def xtea_decrypt_block(block, key):
v0, v1 = struct.unpack('=2I', block)
k = struct.unpack('=4I', key)
delta, mask, rounds = 0x9E3779B9, 0xFFFFFFFF, 32
sum = (delta * rounds) & mask
for round in range(rounds):
v1 = (v1 - (((v0 << 4 ^ v0 >> 5) + v0) ^ (sum + k[sum >> 11 & 3]))) & mask
sum = (sum - delta) & mask
v0 = (v0 - (((v1 << 4 ^ v1 >> 5) + v1) ^ (sum + k[sum & 3]))) & mask
return struct.pack('=2I', v0, v1)
def xtea_decrypt(data, key):
return b''.join(xtea_decrypt_block(data[i:i + 8], key) for i in range(0, len(data), 8))
def make_login_request(xtea_key, acc_name, acc_password):
login_request = struct.pack('=B16sH%isH%is' % (len(acc_name), len(acc_password)), 0, xtea_key, len(acc_name), acc_name, len(acc_password), acc_password)
login_request += bytes(random.randint(0, 255) for i in range(len(login_request), 128))
login_request = struct.pack('=BHHIIIIB', 1, 2, 1098, 1098, 0x4E12DAFF, 0x4E12DB27, 0x4E119CBF, 0) + rsa_encrypt(login_request)
login_request = struct.pack('=HI', len(login_request) + 4, zlib.adler32(login_request)) + login_request
return login_request
def make_entergame_request(session_key, charname, timestamp, randomNumber):
print('sessionkey', session_key, charname)
'''
It's a bit outdated, but still helpful.
Recent specification can be found here:
https://github.com/edubart/otclient and here
https://github.com/otland/forgottenserver
10 8 packet ID
1 or 2 16 operating system: linux 1, windows 2
854 16 version without dot
RSA 128*8 RSA encrypted block
{
0 8 it must be 0
(random) 128 XTEA key
0 or 1 8 GM flag: normal character 0, GM 1
length of accname 16
"accname" length*8 account name; string in ASCII without null byte
length of character 16
"character" length*8 character name; string in ASCII without null byte
length of pass 16
"pass" length*8 password; string in ASCII without null byte
(security bytes) 5*8 security bytes received from server
(any(?)) ? padding; RSA is a block cipher - it must encrypt 128 bytes or 256 bytes etc.
}'''
#RSA encrypted part
entergame_request = struct.pack('=B16sBH%isH%isIB' % (len(session_key), len(charname)), 0, xtea_key, 0, len(session_key), session_key, len(charname), charname, timestamp, randomNumber)
entergame_request += bytes(random.randint(0,255) for i in range(len(entergame_request), 128))
entergame_request = struct.pack('=BHHIHB', 10, 2, 1098, 1098, 65, 0) + rsa_encrypt(entergame_request)
entergame_request = struct.pack('=HI', len(entergame_request)+4, zlib.adler32(entergame_request)) + entergame_request
return entergame_request
def get_string(packet_bytes):
return bytes(next(packet_bytes) for i in range(next(packet_bytes) + 256*next(packet_bytes)))
def get_int(packet_bytes, bits):
return sum(next(packet_bytes)*pow(256, i) for i in range(bits//8))
def recv_packets(s):
#decode xtea
packet = xtea_decrypt(s.recv(4 + struct.unpack('=H', s.recv(2))[0])[4:], xtea_key)
packet_bytes = iter(packet[2:2 + struct.unpack('=H', packet[:2])[0]])
for packet_code in packet_bytes:
if packet_code == 11:#LoginServerErrorNew
error = get_string(packet_bytes)
yield 'LoginServerErrorNew', error
elif packet_code == 20:#LoginServerMotd
motd = get_string(packet_bytes)
yield 'LoginServerMotd', motd
elif packet_code == 30: #LoginServerUpdateNeeded
yield 'LoginServerUpdateNeeded', 'Clients need update'
elif packet_code == 12: #LoginServerTokenSuccess
print('unknown: ', get_int(packet_bytes, 8))
yield 'LoginServerTokenSuccess', None
elif packet_code == 100: #Character list
worlds = {}
worldsCount = get_int(packet_bytes, 8)
for world in range(worldsCount):
worldId = get_int(packet_bytes, 8)
worlds[worldId] = {}
worlds[worldId]['name'] = get_string(packet_bytes)
worlds[worldId]['ip'] = get_string(packet_bytes)
worlds[worldId]['port'] = get_int(packet_bytes, 16)
worlds[worldId]['previewState'] = get_int(packet_bytes, 8)
charactersCount = get_int(packet_bytes, 8)
characters = {}
for character in range(charactersCount):
worldId = get_int(packet_bytes, 8)
characters[character] = {}
characters[character]['name'] = get_string(packet_bytes)
characters[character]['worldName'] = worlds[worldId]['name']
characters[character]['worldIp'] = worlds[worldId]['ip']
characters[character]['port'] = worlds[worldId]['port']
characters[character]['previewState'] = worlds[worldId]['previewState']
account = {}
account['premDays'] = get_int(packet_bytes, 16)
yield 'Characters list', characters
elif packet_code == 101: #character list extened
yield 'Character list extened', None
elif packet_code == 40: #Session key
session_key = get_string(packet_bytes)
yield 'Session key', session_key
else:
yield ('unknown packet code %d (0x%x)' % (packet_code, packet_code), None)
def recv_game_packets(s):
packet = s.recv(4 + struct.unpack('=H', s.recv(2))[0])[4:]
packet_bytes = iter(packet[2:2 + struct.unpack('=H', packet[:2])[0]])
for packet_code in packet_bytes:
if packet_code == 31: #GameServerChallenge
timestamp = get_int(packet_bytes, 32)
randomNumber = get_int(packet_bytes, 8)
print(timestamp, randomNumber)
yield 'GameServerChallenge', [timestamp, randomNumber]
else:
yield ('unknown packet code %d (0x%x)' % (packet_code, packet_code), None)
xtea_key = bytes(random.randint(0,255) for i in range(16))
print('xtea_key', xtea_key)
acc_name = b'bot1xd'
acc_password = b'dupa123'
session_key = ''
with socket.socket() as s:
s.connect(('144.217.149.144', 7171))
s.sendall(make_login_request(xtea_key, acc_name, acc_password))
for packet_name, packet_data in recv_packets(s):
print(packet_name, packet_data)
if(packet_name == 'Characters list'):
chars = packet_data
print(chars)
if packet_name == 'Session key':
session_key = packet_data
with socket.socket() as c:
c.connect((chars[0]['worldIp'], chars[0]['port']))
while True:
for packet_name, packet_data in recv_game_packets(c):
print(packet_name, packet_data)
if(packet_name == 'GameServerChallenge'):
print('sending enteragame packet')
c.sendall(make_entergame_request(session_key, chars[0]['name'], packet_data[0], packet_data[1]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment