Last active
September 13, 2024 20:35
-
-
Save cymruu/544dee66f40579cf576c95dd218894e6 to your computer and use it in GitHub Desktop.
tibia.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'''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