aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docbook/release-notes.asciidoc1
-rw-r--r--epan/dissectors/CMakeLists.txt1
-rw-r--r--epan/dissectors/Makefile.am1
-rw-r--r--epan/dissectors/packet-tibia.c1939
4 files changed, 1942 insertions, 0 deletions
diff --git a/docbook/release-notes.asciidoc b/docbook/release-notes.asciidoc
index 122cefee99..ebcd2f4626 100644
--- a/docbook/release-notes.asciidoc
+++ b/docbook/release-notes.asciidoc
@@ -60,6 +60,7 @@ AMT (Automatic Multicast Tunneling)
QUIC (IETF)
Wi-Fi Device Provisioning Protocol
PFCP (Packet Forwarding Control Protocol)
+Tibia
--sort-and-group--
=== Updated Protocol Support
diff --git a/epan/dissectors/CMakeLists.txt b/epan/dissectors/CMakeLists.txt
index 0993f54337..e75730dcb7 100644
--- a/epan/dissectors/CMakeLists.txt
+++ b/epan/dissectors/CMakeLists.txt
@@ -1281,6 +1281,7 @@ set(DISSECTOR_SRC
packet-tftp.c
packet-thread.c
packet-thrift.c
+ packet-tibia.c
packet-time.c
packet-tipc.c
packet-tivoconnect.c
diff --git a/epan/dissectors/Makefile.am b/epan/dissectors/Makefile.am
index c9cb38667b..49f3a8ca9f 100644
--- a/epan/dissectors/Makefile.am
+++ b/epan/dissectors/Makefile.am
@@ -1303,6 +1303,7 @@ DISSECTOR_SRC = \
packet-tftp.c \
packet-thread.c \
packet-thrift.c \
+ packet-tibia.c \
packet-time.c \
packet-tipc.c \
packet-tivoconnect.c \
diff --git a/epan/dissectors/packet-tibia.c b/epan/dissectors/packet-tibia.c
new file mode 100644
index 0000000000..e8c2d67a8c
--- /dev/null
+++ b/epan/dissectors/packet-tibia.c
@@ -0,0 +1,1939 @@
+/* packet-tibia.c
+ * Routines for Tibia/OTServ login and game protocol dissection
+ *
+ * Copyright 2017, Ahmad Fatoum <ahmad[AT]a3f.at>
+ *
+ * A dissector for:
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * 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 2
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+/* Tibia (https://tibia.com) is a Massively Multiplayer Online Role-Playing
+ * Game (MMORPG) by Cipsoft GmbH.
+ *
+ * Three official clients exist: The current Qt-based 11.0+ client,
+ * the old C++ client used from Tibia 7.0 till 10.99 and the Flash client.
+ * The latter two are being phased out. They use the same protocol,
+ * except that the session key for the Flash client is transported alongside
+ * the character list over HTTPS. It's possible this is done in the same manner
+ * as in the native client from 10.74 up. We don't support the Flash client.
+ *
+ * The dissector supports Tibia versions from 7.0 (2001) till current
+ * 11.42 (2017-08-12). Tibia has an active open source server emulator
+ * community (OTServ) that still makes use of older versions and surpasses
+ * the official servers in popularity, therefore compatibility with older
+ * protocol iterations should be maintained.
+ *
+ * Transport is over TCP, with recent versions encrypting player interaction
+ * with XTEA. Authentication and key exchange is done with a hard-coded
+ * RSA public key in the client.
+ *
+ * Two protocols are dissected: The Tibia login protocol and the Tibia game
+ * protocol. Traditionally, login servers were stateless and only responsible
+ * for providing the addresses of the game servers alongside the character
+ * list upon successful authentication. Then a new authentication request
+ * (this time with character selection) is sent to the game server.
+ * That way, a client who knows the game server address can very well skip
+ * the login server entirely. Starting with 10.61, this is no longer possible,
+ * as the login server provides a session key that needs to be sent to the
+ * game server.
+ *
+ * Starting with Tibia 7.61, login server requests can't be reliably
+ * differentiated from game server requests. Therefore we apply some heuristics
+ * to classify packets.
+ *
+ * Packets from and to the game server contain commands. Commands are
+ * identified by the first octet and are variable in length. The dissector has
+ * most command names hard-coded. However, a complete implementation of the
+ * game protocol is unlikely.
+ *
+ * The RSA private key usually used by OTServ is hard-coded in. Server
+ * administrators may add their own private key in PEM or PKCS#12 format over
+ * an UAT. For servers where the private key is indeed private (like
+ * for official servers), the symmetric XTEA key (retrievable by memory
+ * peeking or MitM) may be provided to the dissector via UAT.
+ *
+ * Unsurprisingly, no official specification of the protocol exist, following
+ * resources have been written by the community:
+ *
+ * - OTServ: Community effort to replicate a Tibia Server.
+ * - Outcast: A Tibia client implementation of the game protocol as of 2006.
+ * Comes with a PDF spec written by Khaos
+ * - TibiaAPI: Bot framework, containing a listing of commands as of 2009
+ * - TFS: OTServ-Fork which is kept up-to-date with most of the official protocol
+ * - otclient: Open Source implementation of an up-to-date Tibia client
+ *
+ * An official slide set by Cipsoft detailing the architecture of Tibia
+ * from Game Developers Conference Europe 2011 is also available:
+ * http://www.gdcvault.com/play/1014908/Inside-Tibia-The-Technical-Infrastructure
+ *
+ * The protocol, as implemented here, has been inferred from network footage
+ * and game client execution traces and was written from scratch. Especially,
+ * no code of Cipsoft GmbH was used.
+ *
+ * Tibia is a registered trademark of Cipsoft GmbH.
+ */
+
+#include "config.h"
+#include <epan/packet.h>
+#include "packet-tcp.h"
+#include <wsutil/adler32.h>
+#include <epan/address.h>
+#include <epan/to_str.h>
+#include <epan/prefs.h>
+#include <epan/uat.h>
+#include <epan/conversation.h>
+#include <epan/value_string.h>
+#include <epan/expert.h>
+#include <epan/address.h>
+#include <wsutil/filesystem.h>
+#include <wsutil/file_util.h>
+#include <wsutil/wsgcrypt.h>
+#include <wsutil/report_message.h>
+#include <wsutil/xtea.h>
+#include <wsutil/strtoi.h>
+#include <wsutil/rsa.h>
+#include <errno.h>
+#include <wsutil/ws_printf.h>
+#include <epan/ptvcursor.h>
+
+void proto_register_tibia(void);
+void proto_reg_handoff_tibia(void);
+
+/* preferences */
+static gboolean try_otserv_key = TRUE,
+ show_char_name = TRUE,
+ show_acc_info = TRUE,
+ show_xtea_key = FALSE,
+ dissect_game_commands = FALSE,
+ reassemble_tcp_segments = TRUE;
+
+/* User Access Tables */
+struct rsakeys_assoc {
+ char *ipaddr;
+ char *port;
+
+ char *keyfile;
+ char *password;
+};
+
+UAT_CSTRING_CB_DEF(rsakeylist_uats, ipaddr, struct rsakeys_assoc)
+UAT_CSTRING_CB_DEF(rsakeylist_uats, port, struct rsakeys_assoc)
+UAT_FILENAME_CB_DEF(rsakeylist_uats, keyfile, struct rsakeys_assoc)
+UAT_CSTRING_CB_DEF(rsakeylist_uats, password, struct rsakeys_assoc)
+
+#define XTEA_KEY_LEN 16
+
+struct xteakeys_assoc {
+ guint32 framenum;
+
+ char *key;
+};
+
+static void *xteakeys_copy_cb(void *, const void *, size_t);
+static void xteakeys_free_cb(void *);
+static void xtea_parse_uat(void);
+static gboolean xteakeys_uat_fld_key_chk_cb(void *, const char *, guint, const void *, const void *, char **);
+
+UAT_DEC_CB_DEF(xteakeylist_uats, framenum, struct xteakeys_assoc)
+UAT_CSTRING_CB_DEF(xteakeylist_uats, key, struct xteakeys_assoc)
+
+
+/* The login server has been traditionally on 7171,
+ * For OTServ, the game server often listens on the same IP/port,
+ * but occasionally on 7172. Official Tibia doesn't host login and
+ * game servers on the same IP address
+ */
+
+#define TIBIA_DEFAULT_TCP_PORT_RANGE "7171,7172"
+
+static gint proto_tibia = -1;
+static uat_t *rsakeys_uat = NULL, *xteakeys_uat = NULL;
+static struct rsakeys_assoc *rsakeylist_uats = NULL;
+static struct xteakeys_assoc *xteakeylist_uats = NULL;
+static guint nrsakeys = 0, nxteakeys = 0;
+
+static gint hf_tibia_len = -1;
+static gint hf_tibia_nonce = -1;
+static gint hf_tibia_adler32 = -1;
+static gint hf_tibia_adler32_status = -1;
+static gint hf_tibia_os = -1;
+static gint hf_tibia_proto_version = -1;
+static gint hf_tibia_client_version = -1;
+static gint hf_tibia_file_versions = -1;
+static gint hf_tibia_file_version_spr = -1;
+static gint hf_tibia_file_version_dat = -1;
+static gint hf_tibia_file_version_pic = -1;
+static gint hf_tibia_game_preview_state = -1;
+static gint hf_tibia_content_revision = -1;
+static gint hf_tibia_undecoded_rsa_data = -1;
+static gint hf_tibia_undecoded_xtea_data = -1;
+static gint hf_tibia_unknown = -1;
+static gint hf_tibia_xtea_key = -1;
+static gint hf_tibia_loginflags_gm = -1;
+static gint hf_tibia_acc_name = -1;
+static gint hf_tibia_acc_number = -1;
+static gint hf_tibia_session_key = -1;
+static gint hf_tibia_char_name = -1;
+static gint hf_tibia_acc_pass = -1;
+static gint hf_tibia_char_name_convo = -1;
+static gint hf_tibia_acc_name_convo = -1;
+static gint hf_tibia_acc_pass_convo = -1;
+static gint hf_tibia_session_key_convo = -1;
+
+static gint hf_tibia_client_info = -1;
+static gint hf_tibia_client_locale = -1;
+static gint hf_tibia_client_locale_id = -1;
+static gint hf_tibia_client_locale_name = -1;
+static gint hf_tibia_client_ram = -1;
+static gint hf_tibia_client_cpu = -1;
+static gint hf_tibia_client_cpu_name = -1;
+static gint hf_tibia_client_clock = -1;
+static gint hf_tibia_client_clock2 = -1;
+static gint hf_tibia_client_gpu = -1;
+static gint hf_tibia_client_vram = -1;
+static gint hf_tibia_client_resolution = -1;
+static gint hf_tibia_client_resolution_x = -1;
+static gint hf_tibia_client_resolution_y = -1;
+static gint hf_tibia_client_resolution_hz = -1;
+
+static gint hf_tibia_payload_len = -1;
+static gint hf_tibia_loginserv_command = -1;
+static gint hf_tibia_gameserv_command = -1;
+static gint hf_tibia_client_command = -1;
+
+static gint hf_tibia_motd = -1;
+static gint hf_tibia_dlg_error = -1;
+static gint hf_tibia_dlg_info = -1;
+
+static gint hf_tibia_charlist = -1;
+static gint hf_tibia_charlist_length = -1;
+static gint hf_tibia_charlist_entry_name = -1;
+static gint hf_tibia_charlist_entry_world = -1;
+static gint hf_tibia_charlist_entry_ip = -1;
+static gint hf_tibia_charlist_entry_port = -1;
+
+static gint hf_tibia_worldlist = -1;
+static gint hf_tibia_worldlist_length = -1;
+static gint hf_tibia_worldlist_entry_name = -1;
+static gint hf_tibia_worldlist_entry_ip = -1;
+static gint hf_tibia_worldlist_entry_port = -1;
+static gint hf_tibia_worldlist_entry_preview = -1;
+static gint hf_tibia_worldlist_entry_id = -1;
+static gint hf_tibia_pacc_days = -1;
+
+static gint ett_tibia = -1;
+static gint ett_command = -1;
+static gint ett_file_versions = -1;
+static gint ett_client_info = -1;
+static gint ett_locale = -1;
+static gint ett_cpu = -1;
+static gint ett_resolution = -1;
+static gint ett_charlist = -1;
+static gint ett_worldlist = -1;
+static gint ett_char = -1;
+static gint ett_world = -1;
+
+static expert_field ei_xtea_len_toobig = EI_INIT;
+static expert_field ei_adler32_checksum_bad = EI_INIT;
+static expert_field ei_rsa_plaintext_no_leading_zero = EI_INIT;
+static expert_field ei_rsa_ciphertext_too_short = EI_INIT;
+static expert_field ei_rsa_decrypt_failed = EI_INIT;
+
+
+struct rsakey {
+ address addr;
+ guint16 port;
+
+ gcry_sexp_t privkey;
+};
+GHashTable *rsakeys, *xteakeys;
+
+struct proto_traits {
+ guint32 adler32:1, rsa:1, xtea:1, acc_name:1, nonce:1,
+ extra_gpu_info:1, gmbyte:1, hwinfo:1;
+ guint32 outfit_addons:1, stamina:1, lvl_on_msg:1;
+ guint32 ping:1, client_version:1, game_preview:1, auth_token:1, session_key:1;
+ guint32 game_content_revision:1, worldlist_in_charlist:1;
+ guint string_enc;
+};
+
+struct tibia_convo {
+ guint32 xtea_key[XTEA_KEY_LEN / sizeof (guint32)];
+ guint32 xtea_framenum;
+ const guint8 *acc, *pass, *char_name, *session_key;
+ struct proto_traits has;
+
+ guint16 proto_version;
+ guint8 loginserv_is_peer :1;
+ guint16 clientport;
+ guint16 servport;
+
+ gcry_sexp_t privkey;
+};
+
+static struct proto_traits
+get_version_traits(guint16 version)
+{
+ struct proto_traits has;
+ memset(&has, 0, sizeof has);
+ has.gmbyte = TRUE; /* Not sure when the GM byte first appeared */
+ has.string_enc = ENC_ISO_8859_1;
+
+ if (version >= 761) /* 761 was a test client. 770 was the first release */
+ has.xtea = has.rsa = TRUE;
+ if (version >= 780)
+ has.outfit_addons = has.stamina = has.lvl_on_msg = TRUE;
+ if (version >= 830)
+ has.adler32 = has.acc_name = TRUE;
+ if (version >= 841)
+ has.hwinfo = has.nonce = TRUE;
+ if (version >= 953)
+ has.ping = TRUE;
+ if (version >= 980)
+ has.client_version = has.game_preview = TRUE;
+ if (version >= 1010)
+ has.worldlist_in_charlist = TRUE;
+ if (version >= 1061)
+ has.extra_gpu_info = TRUE;
+ if (version >= 1071)
+ has.game_content_revision = TRUE;
+ if (version >= 1072)
+ has.auth_token = TRUE;
+ if (version >= 1074)
+ has.session_key = TRUE;
+#if 0 /* With the legacy client being phased out, maybe Unicode support incoming? */
+ if (version >= 11xy)
+ has.string_enc = ENC_UTF_8;
+#endif
+
+ return has;
+}
+
+static guint16
+get_version_get_charlist_packet_size(struct proto_traits *has)
+{
+ guint16 size = 2;
+ if (has->adler32)
+ size += 4;
+ size += 17;
+ if (has->extra_gpu_info)
+ size += 222;
+ if (has->rsa)
+ size += 128;
+
+ return size;
+}
+static guint16
+get_version_char_login_packet_size(struct proto_traits *has)
+{
+ guint16 size = 2;
+ if (has->adler32)
+ size += 4;
+ size += 5;
+ if (has->client_version)
+ size += 4;
+ if (has->game_content_revision)
+ size += 2;
+ if (has->game_preview)
+ size += 1;
+ if (has->rsa)
+ size += 128;
+
+ return size;
+}
+
+
+#define XTEA_FROM_UAT 0
+#define XTEA_UNKNOWN 0xFFFFFFFF
+
+static struct tibia_convo *
+tibia_get_convo(packet_info *pinfo)
+{
+ conversation_t *epan_conversation = find_or_create_conversation(pinfo);
+
+ struct tibia_convo *convo = (struct tibia_convo*)conversation_get_proto_data(epan_conversation, proto_tibia);
+
+ if (!convo) {
+ struct rsakey rsa_key;
+ convo = wmem_new0(wmem_file_scope(), struct tibia_convo);
+
+ /* FIXME there gotta be a cleaner way... */
+ if (pinfo->srcport >= 0xC000) {
+ convo->clientport = pinfo->srcport;
+
+ convo->servport = pinfo->destport;
+ rsa_key.addr = pinfo->dst;
+ } else {
+ convo->clientport = pinfo->destport;
+
+ convo->servport = pinfo->srcport;
+ rsa_key.addr = pinfo->src;
+ }
+ rsa_key.port = convo->servport;
+ convo->privkey = (gcry_sexp_t)g_hash_table_lookup(rsakeys, &rsa_key);
+ convo->xtea_framenum = XTEA_UNKNOWN;
+
+ conversation_add_proto_data(epan_conversation, proto_tibia, (void *)convo);
+ }
+
+ if (convo->xtea_framenum == XTEA_UNKNOWN) {
+ guint8 *xtea_key = (guint8*)g_hash_table_lookup(xteakeys, GUINT_TO_POINTER(pinfo->num));
+ if (xtea_key) {
+ memcpy(convo->xtea_key, xtea_key, XTEA_KEY_LEN);
+ convo->xtea_framenum = XTEA_FROM_UAT;
+ }
+ }
+
+ return convo;
+}
+
+static guint32
+ipv4tonl(const char *str)
+{
+ guint32 ipaddr = 0;
+ for (int octet = 0; octet < 4; octet++) {
+ ws_strtou8(str, &str, &((guint8*)&ipaddr)[octet]);
+ str++;
+ }
+ return ipaddr;
+}
+
+static void rsakey_free(void *_rsakey);
+
+static void
+register_gameserv_addr(struct tibia_convo *convo, guint32 ipaddr, guint16 port)
+{
+ /* Game servers in the list inherit the same RSA key as the login server */
+ if (convo->has.rsa) {
+ struct rsakey *entry = g_new(struct rsakey, 1);
+ alloc_address_wmem(NULL, &entry->addr, AT_IPv4, sizeof ipaddr, &ipaddr);
+ entry->port = port;
+ entry->privkey = NULL;
+ if (!g_hash_table_contains(rsakeys, entry)) {
+ entry->privkey = convo->privkey;
+ g_hash_table_insert(rsakeys, entry, entry->privkey);
+ } else {
+ rsakey_free(entry);
+ }
+ }
+
+ /* TODO Mark all communication with the IP/Port pair above
+ * as Tibia communication. How?
+ */
+}
+
+static gcry_sexp_t otserv_key;
+static gcry_sexp_t
+convo_get_privkey(struct tibia_convo *convo)
+{
+ return convo->privkey ? convo->privkey
+ : try_otserv_key ? otserv_key
+ : NULL;
+}
+
+enum client_cmd {
+ C_GET_CHARLIST = 0x01,
+ C_LOGIN_CHAR = 0x0A,
+ C_LOGOUT = 0x14, /* I think this is a 7.7+ thing */
+ C_PONG = 0x1E
+};
+static const value_string from_client_packet_types[] = {
+ { C_GET_CHARLIST, "Charlist request" },
+ { C_LOGIN_CHAR, "Character login" },
+ { C_LOGOUT, "Logout" },
+ { C_PONG, "Pong" },
+
+ { 0, NULL }
+};
+
+enum loginserv_cmd {
+ LOGINSERV_DLG_ERROR = 0x0A,
+ LOGINSERV_DLG_ERROR2 = 0x0B,
+ LOGINSERV_DLG_MOTD = 0x14,
+ LOGINSERV_SESSION_KEY = 0x28,
+ LOGINSERV_DLG_CHARLIST = 0x64
+};
+static const value_string from_loginserv_packet_types[] = {
+ { LOGINSERV_DLG_ERROR, "Error" },
+ { LOGINSERV_DLG_ERROR2, "Error" },
+ { LOGINSERV_DLG_MOTD, "MOTD" },
+ { LOGINSERV_SESSION_KEY, "Session key" },
+ { LOGINSERV_DLG_CHARLIST, "Charlist" },
+
+ { 0, NULL }
+};
+
+enum gameserv_cmd {
+ S_DLG_ERROR = 0x14,
+ S_DLG_INFO = 0x15,
+ S_DLG_TOOMANYPLAYERS = 0x16,
+ S_PING = 0x1E,
+ S_NONCE = 0x1F
+};
+static const value_string from_gameserv_packet_types[] = {
+
+ { S_DLG_ERROR, "Error" },
+ { S_DLG_INFO, "Info" },
+ { S_DLG_TOOMANYPLAYERS, "Too many players" },
+ { S_PING, "Ping" },
+ { S_NONCE, "Nonce" },
+
+ { 0, NULL }
+};
+
+static const unit_name_string mb_unit[] = {{"MB", NULL}};
+
+static int
+dissect_loginserv_packet(struct tibia_convo *convo, tvbuff_t *tvb, int offset, int len, packet_info *pinfo, proto_tree *tree, gboolean first_fragment )
+{
+ ptvcursor_t *ptvc = ptvcursor_new(tree, tvb, offset);
+
+ col_append_str(pinfo->cinfo, COL_INFO, first_fragment ? " commands:" : ",");
+ len += offset;
+
+ if (ptvcursor_current_offset(ptvc) < len) {
+ for (;;) {
+ int cmd = tvb_get_guint8(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add_with_subtree(ptvc, hf_tibia_loginserv_command, 1, convo->has.string_enc, ett_command);
+ ptvcursor_advance(ptvc, 1);
+
+ switch ((enum loginserv_cmd)cmd) {
+ case LOGINSERV_DLG_ERROR:
+ case LOGINSERV_DLG_ERROR2:
+ ptvcursor_add(ptvc, hf_tibia_dlg_error, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+ break;
+ case LOGINSERV_DLG_MOTD:
+ ptvcursor_add(ptvc, hf_tibia_motd, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+ break;
+ case LOGINSERV_SESSION_KEY:
+ ptvcursor_add(ptvc, hf_tibia_session_key, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+ break;
+ case LOGINSERV_DLG_CHARLIST:
+ if (convo->has.worldlist_in_charlist) {
+ guint8 world_count = tvb_get_guint8(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add(ptvc, hf_tibia_worldlist_length, 1, ENC_NA);
+ /* Empty character list? */
+ if (world_count) {
+ ptvcursor_add_with_subtree(ptvc, hf_tibia_worldlist, SUBTREE_UNDEFINED_LENGTH, ENC_NA, ett_worldlist);
+ while (world_count--) {
+ proto_item *it = ptvcursor_add(ptvc, hf_tibia_worldlist_entry_id, 1, ENC_NA);
+ ptvcursor_push_subtree(ptvc, it, ett_world);
+
+ ptvcursor_add(ptvc, hf_tibia_worldlist_entry_name, 2, convo->has.string_enc | ENC_LITTLE_ENDIAN);
+ guint ipv4addr_len = tvb_get_letohs(tvb, ptvcursor_current_offset(ptvc));
+ char *ipv4addr_str = (char*)tvb_get_string_enc(wmem_packet_scope(), tvb, ptvcursor_current_offset(ptvc) + 2, ipv4addr_len, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+ guint32 ipv4addr = ipv4tonl(ipv4addr_str);
+ ptvcursor_add(ptvc, hf_tibia_worldlist_entry_ip, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+ guint16 port = tvb_get_letohs(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add(ptvc, hf_tibia_worldlist_entry_port, 2, ENC_LITTLE_ENDIAN);
+ ptvcursor_add(ptvc, hf_tibia_worldlist_entry_preview, 1, ENC_NA);
+
+ ptvcursor_pop_subtree(ptvc);
+
+ register_gameserv_addr(convo, ipv4addr, port);
+ }
+ ptvcursor_pop_subtree(ptvc);
+ }
+
+ guint8 char_count = tvb_get_guint8(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add(ptvc, hf_tibia_charlist_length, 1, ENC_NA);
+ if (char_count) {
+ ptvcursor_add_with_subtree(ptvc, hf_tibia_charlist, SUBTREE_UNDEFINED_LENGTH, ENC_NA, ett_charlist);
+ while (char_count--) {
+ proto_item *it = ptvcursor_add(ptvc, hf_tibia_worldlist_entry_id, 1, ENC_NA);
+ ptvcursor_push_subtree(ptvc, it, ett_char);
+ ptvcursor_add(ptvc, hf_tibia_charlist_entry_name, 2, convo->has.string_enc | ENC_LITTLE_ENDIAN);
+
+
+ ptvcursor_pop_subtree(ptvc);
+ }
+ ptvcursor_pop_subtree(ptvc);
+ }
+ } else {
+ guint8 char_count = tvb_get_guint8(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add(ptvc, hf_tibia_charlist_length, 1, ENC_NA);
+ if (char_count) {
+ ptvcursor_add_with_subtree(ptvc, hf_tibia_charlist, SUBTREE_UNDEFINED_LENGTH, ENC_NA, ett_charlist);
+
+ while (char_count--) {
+ proto_item *it = ptvcursor_add(ptvc, hf_tibia_charlist_entry_name, 2, convo->has.string_enc | ENC_LITTLE_ENDIAN);
+ ptvcursor_push_subtree(ptvc, it, ett_char);
+
+ ptvcursor_add(ptvc, hf_tibia_charlist_entry_world, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+
+ guint32 ipv4addr = tvb_get_ipv4(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add(ptvc, hf_tibia_charlist_entry_ip, 4, ENC_BIG_ENDIAN);
+
+ guint16 port = tvb_get_letohs(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add(ptvc, hf_tibia_charlist_entry_port, 2, ENC_BIG_ENDIAN);
+
+
+ ptvcursor_pop_subtree(ptvc);
+
+ register_gameserv_addr(convo, ipv4addr, port);
+ }
+
+ ptvcursor_pop_subtree(ptvc);
+ }
+
+ ptvcursor_add(ptvc, hf_tibia_pacc_days, 2, ENC_LITTLE_ENDIAN);
+ }
+ break;
+ default:
+ offset = ptvcursor_current_offset(ptvc);
+ call_data_dissector(tvb_new_subset_length(tvb, offset, len - offset), pinfo, ptvcursor_tree(ptvc));
+ ptvcursor_advance(ptvc, len - offset);
+ }
+
+ ptvcursor_pop_subtree(ptvc);
+
+ col_append_fstr(pinfo->cinfo, COL_INFO, " %s (0x%x)",
+ val_to_str(cmd, from_loginserv_packet_types, "Unknown"), cmd);
+
+ if (ptvcursor_current_offset(ptvc) >= len)
+ break;
+
+ col_append_str(pinfo->cinfo, COL_INFO, ",");
+ }
+ }
+
+ offset = ptvcursor_current_offset(ptvc);
+ ptvcursor_free(ptvc);
+
+ return offset;
+}
+
+
+static int
+dissect_gameserv_packet(struct tibia_convo *convo, tvbuff_t *tvb, int offset, int len, packet_info *pinfo, proto_tree *tree, gboolean first_fragment)
+{
+ ptvcursor_t *ptvc = ptvcursor_new(tree, tvb, offset);
+
+ col_append_str(pinfo->cinfo, COL_INFO, first_fragment ? " commands:" : ",");
+ len += offset;
+
+ if (ptvcursor_current_offset(ptvc) < len) {
+ for (;;) {
+ int cmd = tvb_get_guint8(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add_with_subtree(ptvc, hf_tibia_gameserv_command, 1, convo->has.string_enc, ett_command);
+ ptvcursor_advance(ptvc, 1);
+
+ switch ((enum gameserv_cmd)cmd) {
+ case S_DLG_INFO:
+ case S_DLG_ERROR:
+ case S_DLG_TOOMANYPLAYERS:
+ ptvcursor_add(ptvc, cmd == S_DLG_ERROR ? hf_tibia_dlg_error : hf_tibia_dlg_info, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc);
+ break;
+ case S_PING:
+ break;
+ case S_NONCE:
+ ptvcursor_add(ptvc, hf_tibia_nonce, 5, ENC_NA);
+ break;
+ default:
+ offset = ptvcursor_current_offset(ptvc);
+ call_data_dissector(tvb_new_subset_length(tvb, offset, len - offset), pinfo, ptvcursor_tree(ptvc));
+ ptvcursor_advance(ptvc, len - offset);
+ }
+
+ ptvcursor_pop_subtree(ptvc);
+
+ col_append_fstr(pinfo->cinfo, COL_INFO, " %s (0x%x)",
+ val_to_str(cmd, from_gameserv_packet_types, "Unknown"), cmd);
+
+ if (ptvcursor_current_offset(ptvc) >= len)
+ break;
+
+ col_append_str(pinfo->cinfo, COL_INFO, ",");
+ }
+ }
+
+ offset = ptvcursor_current_offset(ptvc);
+ ptvcursor_free(ptvc);
+
+ return offset;
+}
+
+static int
+dissect_client_packet(struct tibia_convo *convo, tvbuff_t *tvb, int offset, int len, packet_info *pinfo, proto_tree *tree, gboolean first_fragment)
+{
+ ptvcursor_t *ptvc = ptvcursor_new(tree, tvb, offset);
+
+ col_append_str(pinfo->cinfo, COL_INFO, first_fragment ? " commands:" : ",");
+ len += offset;
+
+ if (ptvcursor_current_offset(ptvc) < len) {
+ for (;;) {
+ int cmd = tvb_get_guint8(tvb, ptvcursor_current_offset(ptvc));
+ ptvcursor_add_with_subtree(ptvc, hf_tibia_client_command, 1, convo->has.string_enc, ett_command);
+ ptvcursor_advance(ptvc, 1);
+
+ switch ((enum client_cmd)cmd) {
+ case C_PONG:
+ break;
+ default:
+ offset = ptvcursor_current_offset(ptvc);
+ call_data_dissector(tvb_new_subset_length(tvb, offset, len - offset), pinfo, ptvcursor_tree(ptvc));
+ ptvcursor_advance(ptvc, len - offset);
+ }
+
+ ptvcursor_pop_subtree(ptvc);
+
+ col_append_fstr(pinfo->cinfo, COL_INFO, " %s (0x%x)",
+ val_to_str(cmd, from_client_packet_types, "Unknown"), cmd);
+
+ if (ptvcursor_current_offset(ptvc) >= len)
+ break;
+
+ col_append_str(pinfo->cinfo, COL_INFO, ",");
+ }
+ }
+
+ offset = ptvcursor_current_offset(ptvc);
+ ptvcursor_free(ptvc);
+
+ return offset;
+}
+
+static int
+dissect_game_packet(struct tibia_convo *convo, tvbuff_t *tvb, int offset, packet_info *pinfo, proto_tree *tree, gboolean is_xtea_encrypted, gboolean first_fragment)
+{
+ proto_item *ti = NULL;
+ int len = tvb_captured_length_remaining(tvb, offset);
+
+ if (show_acc_info) {
+ if (convo->has.session_key) {
+ if (convo->session_key) {
+ ti = proto_tree_add_string(tree, hf_tibia_session_key_convo, tvb, offset, 0, (const char*)convo->session_key);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+ } else {
+ if (convo->acc) {
+ ti = proto_tree_add_string(tree, hf_tibia_acc_name_convo, tvb, offset, 0, (const char*)convo->acc);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+
+ if (convo->pass) {
+ ti = proto_tree_add_string(tree, hf_tibia_acc_pass_convo, tvb, offset, 0, (const char*)convo->pass);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+ }
+ }
+
+ if (show_char_name && convo->char_name) {
+ ti = proto_tree_add_string(tree, hf_tibia_char_name_convo, tvb, offset, 0, (const char*)convo->char_name);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+
+ if (is_xtea_encrypted) {
+ if (pinfo->num > convo->xtea_framenum) {
+ if (show_xtea_key && convo->has.xtea) {
+ ti = proto_tree_add_bytes_with_length(tree, hf_tibia_xtea_key, tvb, 0, 0, (guint8*)convo->xtea_key, XTEA_KEY_LEN);
+ PROTO_ITEM_SET_GENERATED(ti);
+ }
+
+ int end = offset + len;
+
+ if (len % 8 != 0)
+ return -1;
+
+ guint8 *decrypted_buffer = (guint8*)g_malloc(len);
+
+ for (guint8 *dstblock = decrypted_buffer; offset < end; offset += 8) {
+ decrypt_xtea_le_ecb(dstblock, tvb_get_ptr(tvb, offset, 8), convo->xtea_key, 32);
+ dstblock += 8;
+ }
+
+ tvb = tvb_new_child_real_data(tvb, decrypted_buffer, len, len);
+ tvb_set_free_cb(tvb, g_free);
+ add_new_data_source(pinfo, tvb, "Decrypted Game Data");
+
+ offset = 0;
+ } else {
+ proto_tree_add_item(tree, hf_tibia_undecoded_xtea_data, tvb, offset, len, ENC_NA);
+ return offset;
+ }
+ }
+ if (convo->has.xtea) {
+ len = tvb_get_letohs(tvb, offset);
+ ti = proto_tree_add_item(tree, hf_tibia_payload_len, tvb, offset, 2, ENC_LITTLE_ENDIAN);
+ offset += 2;
+ if (len > tvb_captured_length_remaining(tvb, offset)) {
+ expert_add_info(pinfo, ti, &ei_xtea_len_toobig);
+ return offset;
+ }
+ }
+
+
+ if (pinfo->srcport == convo->servport && convo->loginserv_is_peer)
+ return dissect_loginserv_packet(convo, tvb, offset, len, pinfo, tree, first_fragment);
+
+ if (!dissect_game_commands) {
+ call_data_dissector(tvb_new_subset_length(tvb, offset, len), pinfo, tree);
+ return offset + len;
+ }
+
+ if (pinfo->srcport == convo->servport)
+ return dissect_gameserv_packet(convo, tvb, offset, len, pinfo, tree, first_fragment);
+ else
+ return dissect_client_packet(convo, tvb, offset, len, pinfo, tree, first_fragment);
+}
+
+static int
+dissect_tibia(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *fragment_num)
+{
+ tvbuff_t *tvb_decrypted = tvb;
+ gboolean is_xtea_encrypted = FALSE;
+ enum { TIBIA_GAMESERV, TIBIA_LOGINSERV } serv = TIBIA_GAMESERV;
+ guint16 plen = tvb_get_letohs(tvb, 0) + sizeof(guint16);
+
+ /* if announced length != real length it's not a tibia packet */
+ if (tvb_reported_length_remaining(tvb, 0) != plen)
+ return 0;
+
+ struct tibia_convo *convo = tibia_get_convo(pinfo);
+
+ int offset = 2;
+ int a32len = tvb_reported_length_remaining(tvb, offset + 4);
+ guint32 packet_cksum = tvb_get_letohl(tvb, offset);
+ guint32 computed_cksum = GUINT32_TO_LE(adler32_bytes(tvb_get_ptr(tvb, offset + 4, a32len), a32len));
+ convo->has.adler32 = packet_cksum == computed_cksum;
+ if (convo->has.adler32)
+ offset += sizeof(guint32);
+
+ /* Is it a nonce? */
+ if (tvb_get_letohs(tvb, offset) == plen - offset - sizeof(guint16)
+ && tvb_get_guint8(tvb, offset+sizeof(guint16)) == S_NONCE) {
+ /* Don't do anything. We'll handle it as unencrypted game command later */
+ } else {
+ guint8 cmd;
+ guint16 version;
+ struct proto_traits version_has;
+ cmd = tvb_get_guint8(tvb, offset);
+ offset += sizeof(guint8);
+ offset += sizeof(guint16); /* OS */
+ version = tvb_get_letohs(tvb, offset);
+ version_has = get_version_traits(version);
+
+ switch(cmd) {
+ case C_GET_CHARLIST:
+ if ((700 <= version && version <= 760 && !convo->has.adler32 && 25 <= plen && plen <= 54)
+ || get_version_get_charlist_packet_size(&version_has) == plen) {
+ serv = TIBIA_LOGINSERV;
+ convo->loginserv_is_peer = TRUE;
+ }
+ break;
+ case C_LOGIN_CHAR:
+ /* The outcast client I tried, zero-pads the 760 login request.
+ * I don't think the Cipsoft client ever did this.
+ */
+ if ((700 <= version && version <= 760 && !convo->has.adler32 && 25 <= plen && plen <= 54)
+ || get_version_char_login_packet_size(&version_has) == plen)
+ serv = TIBIA_LOGINSERV;
+ break;
+ default:
+ is_xtea_encrypted = convo->has.xtea;
+ }
+ }
+
+
+ offset = 0; /* With the version extracted, let's build the tree */
+
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "Tibia");
+ if (GPOINTER_TO_UINT(fragment_num) == 1) {
+ /* We don't want to repeat ourselves in the info column if there are fragments */
+ if (serv == TIBIA_LOGINSERV)
+ col_set_str(pinfo->cinfo, COL_INFO, "Login");
+ else if (pinfo->srcport == convo->servport)
+ col_set_str(pinfo->cinfo, COL_INFO, "Server");
+ else
+ col_set_str(pinfo->cinfo, COL_INFO, "Client");
+
+ }
+
+ proto_item *ti = proto_tree_add_item(tree, proto_tibia, tvb, 0, -1, ENC_NA);
+ proto_tree *tibia_tree = proto_item_add_subtree(ti, ett_tibia);
+
+ proto_tree_add_item(tibia_tree, hf_tibia_len, tvb, offset, 2, ENC_LITTLE_ENDIAN);
+ offset += 2;
+ if (convo->has.adler32) {
+ proto_tree_add_checksum(tibia_tree, tvb, offset, hf_tibia_adler32, hf_tibia_adler32_status, &ei_adler32_checksum_bad, pinfo, computed_cksum, ENC_LITTLE_ENDIAN, PROTO_CHECKSUM_VERIFY);
+ offset += 4;
+ }
+
+ if (serv == TIBIA_GAMESERV)
+ return dissect_game_packet(convo, tvb, offset, pinfo, tibia_tree, is_xtea_encrypted, GPOINTER_TO_UINT(fragment_num) == 1);
+
+ proto_tree_add_item(tibia_tree, hf_tibia_client_command, tvb, offset, 1, ENC_LITTLE_ENDIAN);
+ offset += 1;
+ proto_tree_add_item(tibia_tree, hf_tibia_os, tvb, offset, 2, ENC_LITTLE_ENDIAN);
+ offset += 2;
+
+ convo->proto_version = tvb_get_letohs(tvb, offset);
+ convo->has = get_version_traits(convo->proto_version);
+ proto_tree_add_item(tibia_tree, hf_tibia_proto_version, tvb, offset, 2, ENC_LITTLE_ENDIAN);
+ offset += 2;
+ if (convo->has.client_version) {
+ proto_tree_add_item(tibia_tree, hf_tibia_client_version, tvb, offset, 4, ENC_LITTLE_ENDIAN);
+ offset += 4;
+ }
+ if (convo->loginserv_is_peer) {
+ proto_tree *vertree;
+ /* The first 4 bytes of the client's tibia.pic, tibia.dat and tibia.spr files */
+ proto_item *subti = proto_tree_add_item(tibia_tree, hf_tibia_file_versions, tvb, offset, 12, ENC_NA);
+ vertree = proto_item_add_subtree(subti, ett_file_versions);
+ proto_tree_add_item(vertree, hf_tibia_file_version_spr, tvb, offset, 4, ENC_BIG_ENDIAN);
+ offset += 4;
+ proto_tree_add_item(vertree, hf_tibia_file_version_dat, tvb, offset, 4, ENC_BIG_ENDIAN);
+ offset += 4;
+ proto_tree_add_item(vertree, hf_tibia_file_version_pic, tvb, offset, 4, ENC_BIG_ENDIAN);
+ offset += 4;
+ } else if (convo->has.game_content_revision) {
+ proto_tree_add_item(tibia_tree, hf_tibia_content_revision, tvb, offset, 4, ENC_LITTLE_ENDIAN);
+ offset += 2;
+ }
+
+ if (convo->has.game_preview) {
+ proto_tree_add_item(tibia_tree, hf_tibia_game_preview_state, tvb, offset, 1, ENC_NA);
+ offset += 1;
+ }
+
+ int rsa1_end = 0; /* End of first RSA block */
+ if (convo->has.rsa) {
+ gcry_sexp_t privkey;
+ if (!(privkey = convo_get_privkey(convo))) {
+ proto_tree_add_item(tibia_tree, hf_tibia_undecoded_rsa_data, tvb, offset, plen - offset, ENC_NA);
+ return offset;
+ }
+
+ guint ciphertext_len = tvb_captured_length_remaining(tvb, offset);
+ if (ciphertext_len < 128) {
+ expert_add_info(pinfo, ti, &ei_rsa_ciphertext_too_short);
+ return offset;
+ }
+ rsa1_end = offset + 128;
+ guint8 *payload = (guint8*)tvb_memdup(pinfo->pool, tvb, offset, 128);
+
+ char *err = NULL;
+ size_t payload_len;
+ if (!(payload_len = rsa_decrypt_inplace(128, payload, privkey, FALSE, &err))) {
+ expert_add_info_format(pinfo, ti, &ei_rsa_decrypt_failed, "Decrypting RSA block failed: %s", err);
+ g_free(err);
+ return offset;
+ }
+ size_t leading_zeroes = 128 - payload_len;
+ memmove(payload + leading_zeroes, payload, payload_len);
+ memset(payload, 0x00, leading_zeroes);
+
+ tvb_decrypted = tvb_new_child_real_data(tvb, payload, 128, 128);
+ add_new_data_source(pinfo, tvb_decrypted, "Decrypted Login Data");
+
+ if (tvb_get_guint8(tvb_decrypted, 0) != 0x00) {
+ expert_add_info(pinfo, ti, &ei_rsa_plaintext_no_leading_zero);
+ return offset;
+ }
+
+ if (tibia_tree) /* don't reset offset if we are exiting anyway */
+ offset = 1;
+
+ tvb_memcpy(tvb_decrypted, convo->xtea_key, 1, XTEA_KEY_LEN);
+ proto_tree_add_item(tibia_tree, hf_tibia_xtea_key, tvb_decrypted, 1, XTEA_KEY_LEN, ENC_NA);
+ offset += XTEA_KEY_LEN;
+ convo->xtea_framenum = pinfo->num;
+ }
+
+ if (!convo->loginserv_is_peer && convo->has.gmbyte) {
+ proto_tree_add_item(tibia_tree, hf_tibia_loginflags_gm, tvb_decrypted, offset, 1, ENC_NA);
+ offset += 1;
+ }
+
+ int len;
+ if (convo->has.session_key && !convo->loginserv_is_peer) {
+ /* OTServs I tested against use "$acc\n$pacc" as session key */
+ if (convo->session_key) {
+ proto_tree_add_item_ret_length(tibia_tree, hf_tibia_session_key, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, &len);
+ } else {
+ proto_tree_add_item_ret_string_and_length(tibia_tree, hf_tibia_session_key, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, wmem_file_scope(), &convo->session_key, &len);
+ }
+ offset += len;
+ } else if (convo->has.acc_name) {
+ if (convo->acc) {
+ proto_tree_add_item_ret_length(tibia_tree, hf_tibia_acc_name, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, &len);
+ } else {
+ proto_tree_add_item_ret_string_and_length(tibia_tree, hf_tibia_acc_name, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, wmem_file_scope(), &convo->acc, &len);
+ }
+ offset += len;
+ } else /* account number */ {
+ char *accnum = wmem_strdup_printf(wmem_packet_scope(), "%" G_GUINT32_FORMAT, tvb_get_letohl(tvb_decrypted, offset));
+ proto_tree_add_string(tibia_tree, hf_tibia_acc_number, tvb_decrypted, offset, 4, accnum);
+ if (!convo->acc)
+ convo->acc = (guint8*)wmem_strdup(wmem_file_scope(), accnum);
+ offset += 4;
+ }
+
+ if (!convo->loginserv_is_peer) {
+ if (convo->char_name) {
+ proto_tree_add_item_ret_length(tibia_tree, hf_tibia_char_name, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, &len);
+ } else {
+ proto_tree_add_item_ret_string_and_length(tibia_tree, hf_tibia_char_name, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, wmem_file_scope(), &convo->char_name, &len);
+ }
+ offset += len;
+ }
+
+ if (!convo->has.session_key || convo->loginserv_is_peer) {
+ if (convo->pass) {
+ proto_tree_add_item_ret_length(tibia_tree, hf_tibia_acc_pass, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, &len);
+ } else {
+ proto_tree_add_item_ret_string_and_length(tibia_tree, hf_tibia_acc_pass, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN | convo->has.string_enc, wmem_file_scope(), &convo->pass, &len);
+ }
+ offset += len;
+ }
+
+ if (convo->loginserv_is_peer && convo->has.hwinfo) {
+ proto_item *item;
+ proto_tree *infotree, *subtree;
+
+ item = proto_tree_add_item(tibia_tree, hf_tibia_client_info, tvb_decrypted, offset, 47, ENC_NA);
+ infotree = proto_item_add_subtree(item, ett_client_info);
+
+ /* Subtree { */
+ guint locale_id;
+ const guint8 *locale_name;
+
+ item = proto_tree_add_item(infotree, hf_tibia_client_locale, tvb_decrypted, offset, 4, ENC_NA);
+ subtree = proto_item_add_subtree(item, ett_locale);
+
+ proto_tree_add_item_ret_uint(subtree, hf_tibia_client_locale_id, tvb_decrypted, offset, 1, ENC_NA, &locale_id);
+ offset += 1;
+
+ proto_tree_add_item_ret_string(subtree, hf_tibia_client_locale_name, tvb_decrypted, offset, 3, convo->has.string_enc|ENC_NA, wmem_packet_scope(), &locale_name);
+ offset += 3;
+ proto_item_set_text(item, "Locale: %s (0x%X)", locale_name, locale_id);
+ /* } */
+
+ proto_tree_add_item(infotree, hf_tibia_client_ram, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN);
+ offset += 2;
+
+ proto_tree_add_item(infotree, hf_tibia_unknown, tvb_decrypted, offset, 6, ENC_NA);
+ offset += 6;
+
+ /* Subtree { */
+ guint clock1, clock2;
+ const guint8 *cpu;
+
+ item = proto_tree_add_item(infotree, hf_tibia_client_cpu, tvb_decrypted, offset, 15, ENC_NA);
+ subtree = proto_item_add_subtree(item, ett_cpu);
+
+ proto_tree_add_item_ret_string(subtree, hf_tibia_client_cpu_name, tvb_decrypted, offset, 9, convo->has.string_enc|ENC_NA, wmem_packet_scope(), &cpu);
+ offset += 9;
+
+ proto_tree_add_item(subtree, hf_tibia_unknown, tvb_decrypted, offset, 2, ENC_NA);
+ offset += 2;
+
+ proto_tree_add_item_ret_uint(subtree, hf_tibia_client_clock, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN, &clock1);
+ offset += 2;
+
+ proto_tree_add_item_ret_uint(subtree, hf_tibia_client_clock2, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN, &clock2);
+ offset += 2;
+
+ proto_item_set_text(item, "CPU: %s (%uMhz/%uMhz)", cpu, clock2, clock1);
+ /* } */
+
+
+ proto_tree_add_item(infotree, hf_tibia_unknown, tvb_decrypted, offset, 4, ENC_NA);
+ offset += 4;
+
+ proto_tree_add_item(infotree, hf_tibia_client_gpu, tvb_decrypted, offset, 9, convo->has.string_enc|ENC_NA);
+ offset += 9;
+
+ proto_tree_add_item(infotree, hf_tibia_client_vram, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN);
+ offset += 2;
+
+ /* Subtree { */
+ guint x, y, hz;
+
+ item = proto_tree_add_item(infotree, hf_tibia_client_resolution, tvb_decrypted, offset, 5, ENC_NA);
+ subtree = proto_item_add_subtree(item, ett_resolution);
+
+ proto_tree_add_item_ret_uint(subtree, hf_tibia_client_resolution_x, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN, &x);
+ offset += 2;
+ proto_tree_add_item_ret_uint(subtree, hf_tibia_client_resolution_y, tvb_decrypted, offset, 2, ENC_LITTLE_ENDIAN, &y);
+ offset += 2;
+ proto_tree_add_item_ret_uint(subtree, hf_tibia_client_resolution_hz, tvb_decrypted, offset, 1, ENC_LITTLE_ENDIAN, &hz);
+ offset += 1;
+
+ proto_item_set_text(item, "Resolution: %ux%u @ %uHz", x, y, hz);
+ /* } */
+
+ } else if (!convo->loginserv_is_peer && convo->has.nonce) {
+ proto_tree_add_item(tibia_tree, hf_tibia_nonce, tvb_decrypted, offset, 5, ENC_NA);
+ offset += 5;
+ }
+
+ if (convo->has.rsa) {
+ /* Undecoded hardware info maybe */
+ call_data_dissector(tvb_new_subset_length(tvb_decrypted, offset, 128 - offset), pinfo, tibia_tree);
+ }
+
+ if (rsa1_end)
+ offset = rsa1_end;
+
+ if (offset != plen) {
+ /* TODO Extended GPU info and authentication token (RSA-encrypted again) */
+ call_data_dissector(tvb_new_subset_length(tvb, offset, plen - offset), pinfo, tibia_tree);
+ }
+ return plen;
+}
+
+static const value_string operating_systems[] = {
+ { 2, "Windows" },
+ { 0, NULL }
+};
+
+static guint
+rsakey_hash(gconstpointer _rsakey)
+{
+ const struct rsakey *rsakey = (const struct rsakey *)_rsakey;
+ return add_address_to_hash(rsakey->port, &rsakey->addr);
+}
+
+static gboolean
+rsakey_equal(gconstpointer _a, gconstpointer _b)
+{
+ const struct rsakey *a = (const struct rsakey *)_a,
+ *b = (const struct rsakey *)_b;
+ return a->port == b->port && addresses_equal(&a->addr, &b->addr);
+}
+static void
+rsakey_free(void *_rsakey)
+{
+ struct rsakey *rsakey = (struct rsakey *)_rsakey;
+
+ /* gcry_sexp_release(rsakey->privkey); */ /* private key may be shared. */
+ free_address_wmem(NULL, &rsakey->addr);
+ g_free(rsakey);
+}
+
+#if defined(HAVE_LIBGNUTLS)
+static void
+rsa_parse_uat(void)
+{
+ g_hash_table_remove_all(rsakeys);
+
+ for (guint i = 0; i < nrsakeys; i++) {
+ struct rsakeys_assoc *uats = &rsakeylist_uats[i];
+
+ /* try to load keys file first */
+ FILE *fp = ws_fopen(uats->keyfile, "rb");
+ if (!fp) {
+ report_open_failure(uats->keyfile, errno, FALSE);
+ return;
+ }
+
+ gnutls_x509_privkey_t priv_key;
+ char *err = NULL;
+ if (*uats->password) {
+ priv_key = rsa_load_pkcs12(fp, uats->password, &err);
+ if (err) {
+ report_failure("%s\n", err);
+ g_free(err);
+ }
+ } else {
+ priv_key = rsa_load_pem_key(fp, &err);
+ if (err) {
+ report_failure("%s\n", err);
+ g_free(err);
+ }
+ }
+ fclose(fp);
+
+ if (!priv_key) {
+ report_failure("Can't load private key from %s\n", uats->keyfile);
+ return;
+ }
+
+ struct rsakey *entry;
+ guint32 ipaddr;
+ gcry_sexp_t private_key = rsa_privkey_to_sexp(priv_key, &err);
+ if (!private_key) {
+ g_free(err);
+ report_failure("Can't extract private key parameters for %s", uats->keyfile);
+ goto end;
+ }
+
+ entry = g_new(struct rsakey, 1);
+ ws_strtou16(uats->port, NULL, &entry->port);
+ ipaddr = ipv4tonl(uats->ipaddr);
+ alloc_address_wmem(NULL, &entry->addr, AT_IPv4, sizeof ipaddr, &ipaddr);
+ entry->privkey = private_key;
+
+
+ g_hash_table_insert(rsakeys, entry, entry->privkey);
+
+end:
+ gnutls_x509_privkey_deinit(priv_key);
+ }
+}
+#else
+static void
+rsa_parse_uat(void)
+{
+ report_failure("Can't load private key files, GnuTLS support is not compiled in.");
+}
+#endif
+
+static void
+rsakeys_free_cb(void *r)
+{
+ struct rsakeys_assoc *h = (struct rsakeys_assoc *)r;
+
+ g_free(h->ipaddr);
+ g_free(h->port);
+ g_free(h->keyfile);
+ g_free(h->password);
+}
+
+static void*
+rsakeys_copy_cb(void *dst_, const void *src_, size_t len _U_)
+{
+ const struct rsakeys_assoc *src = (const struct rsakeys_assoc *)src_;
+ struct rsakeys_assoc *dst = (struct rsakeys_assoc *)dst_;
+
+ dst->ipaddr = g_strdup(src->ipaddr);
+ dst->port = g_strdup(src->port);
+ dst->keyfile = g_strdup(src->keyfile);
+ dst->password = g_strdup(src->password);
+
+ return dst;
+}
+
+static gboolean
+rsakeys_uat_fld_ip_chk_cb(void* r _U_, const char* ipaddr, guint len _U_, const void* u1 _U_, const void* u2 _U_, char** err)
+{
+ /* There are no Tibia IPv6 servers, although Tibia 11.0+'s Protocol in theory supports it */
+ if (ipaddr && g_hostname_is_ip_address(ipaddr) && strchr(ipaddr, '.')) {
+ *err = NULL;
+ return TRUE;
+ }
+
+ *err = g_strdup_printf("No IPv4 address given.");
+ return FALSE;
+}
+
+static gboolean
+rsakeys_uat_fld_port_chk_cb(void *_record _U_, const char *str, guint len _U_, const void *chk_data _U_, const void *fld_data _U_, char **err)
+{
+ guint16 val;
+ if (!ws_strtou16(str, NULL, &val)) {
+ *err = g_strdup("Invalid argument. Expected a decimal between [0-65535]");
+ return FALSE;
+ }
+ *err = NULL;
+ return TRUE;
+}
+
+static gboolean
+rsakeys_uat_fld_fileopen_chk_cb(void* r _U_, const char* p, guint len _U_, const void* u1 _U_, const void* u2 _U_, char** err)
+{
+ if (p && *p) {
+ ws_statb64 st;
+ if (ws_stat64(p, &st) != 0) {
+ *err = g_strdup_printf("File '%s' does not exist or access is denied.", p);
+ return FALSE;
+ }
+ } else {
+ *err = g_strdup("No filename given.");
+ return FALSE;
+ }
+
+ *err = NULL;
+ return TRUE;
+}
+
+#ifdef HAVE_LIBGNUTLS
+static gboolean
+rsakeys_uat_fld_password_chk_cb(void *r, const char *p, guint len _U_, const void *u1 _U_, const void *u2 _U_, char **err)
+{
+ if (p && *p) {
+ struct rsakeys_assoc *f = (struct rsakeys_assoc *)r;
+ FILE *fp = ws_fopen(f->keyfile, "rb");
+ if (fp) {
+ char *msg = NULL;
+ gnutls_x509_privkey_t priv_key = rsa_load_pkcs12(fp, p, &msg);
+ if (!priv_key) {
+ fclose(fp);
+ *err = g_strdup_printf("Could not load PKCS#12 key file: %s", msg);
+ g_free(msg);
+ return FALSE;
+ }
+ g_free(msg);
+ gnutls_x509_privkey_deinit(priv_key);
+ fclose(fp);
+ } else {
+ *err = g_strdup_printf("Leave this field blank if the keyfile is not PKCS#12.");
+ return FALSE;
+ }
+ }
+
+ *err = NULL;
+ return TRUE;
+}
+#else
+static gboolean
+rsakeys_uat_fld_password_chk_cb(void *r _U_, const char *p _U_, guint len _U_, const void *u1 _U_, const void *u2 _U_, char **err)
+{
+ *err = g_strdup("Cannot load key files, support is not compiled in.");
+ return FALSE;
+}
+#endif
+
+static void
+xtea_parse_uat(void)
+{
+ g_hash_table_remove_all(xteakeys);
+
+ for (guint i = 0; i < nxteakeys; i++) {
+ guint key_idx = 0;
+ guint8 *key = (guint8*)g_malloc(XTEA_KEY_LEN);
+
+ for (const char *str = xteakeylist_uats[i].key; str[0] && str[1] && key_idx < XTEA_KEY_LEN; str++) {
+ if (g_ascii_ispunct(*str))
+ continue;
+
+ key[key_idx++] = (g_ascii_xdigit_value(str[0]) << 4)
+ + g_ascii_xdigit_value(str[1]);
+ str++;
+ }
+
+ g_hash_table_insert(xteakeys, GUINT_TO_POINTER(xteakeylist_uats[i].framenum), key);
+ }
+}
+
+static void
+xteakeys_free_cb(void *r)
+{
+ struct xteakeys_assoc *h = (struct xteakeys_assoc *)r;
+
+ g_free(h->key);
+}
+
+static void*
+xteakeys_copy_cb(void *dst_, const void *src_, size_t len _U_)
+{
+ const struct xteakeys_assoc *src = (const struct xteakeys_assoc *)src_;
+ struct xteakeys_assoc *dst = (struct xteakeys_assoc *)dst_;
+
+ dst->framenum = src->framenum;
+ dst->key = g_strdup(src->key);
+
+ return dst;
+}
+
+static gboolean
+xteakeys_uat_fld_key_chk_cb(void *r _U_, const char *key, guint len, const void *u1 _U_, const void *u2 _U_, char **err)
+{
+ if (len >= XTEA_KEY_LEN*2) {
+ gsize i = 0;
+
+ do {
+ if (g_ascii_ispunct(*key))
+ continue;
+ if (!g_ascii_isxdigit(*key))
+ break;
+ i++;
+ } while (*++key);
+
+ if (*key == '\0' && i == 2*XTEA_KEY_LEN) {
+ *err = NULL;
+ return TRUE;
+ }
+ }
+
+ *err = g_strdup_printf("XTEA keys are 32 character long hex strings.");
+ return FALSE;
+}
+
+
+void
+proto_register_tibia(void)
+{
+ static hf_register_info hf[] = {
+ { &hf_tibia_len,
+ { "Packet length", "tibia.len",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_adler32,
+ { "Adler32 checksum", "tibia.checksum",
+ FT_UINT32, BASE_HEX,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_adler32_status,
+ { "Checksum status", "tibia.checksum.status",
+ FT_UINT8, BASE_NONE,
+ VALS(proto_checksum_vals), 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_nonce,
+ { "Game server nonce", "tibia.nonce",
+ FT_BYTES, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_os,
+ { "Operating system", "tibia.os",
+ FT_UINT16, BASE_HEX,
+ VALS(operating_systems), 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_proto_version,
+ { "Protocol version", "tibia.version",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_version,
+ { "Client version", "tibia.client_version",
+ FT_UINT32, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_file_versions,
+ { "File versions", "tibia.version.files",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_file_version_spr,
+ { "Tibia.spr version", "tibia.version.spr",
+ FT_UINT32, BASE_HEX,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_file_version_dat,
+ { "Tibia.dat version", "tibia.version.dat",
+ FT_UINT32, BASE_HEX,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_file_version_pic,
+ { "Tibia.pic version", "tibia.version.pic",
+ FT_UINT32, BASE_HEX,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_content_revision,
+ { "Content revision", "tibia.version.content",
+ FT_UINT16, BASE_HEX,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_undecoded_rsa_data,
+ { "RSA-encrypted login data", "tibia.rsa_data",
+ FT_BYTES, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_undecoded_xtea_data,
+ { "XTEA-encrypted game data", "tibia.xtea_data",
+ FT_BYTES, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_unknown,
+ { "Unknown Data", "tibia.unknown",
+ FT_BYTES, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_xtea_key,
+ { "Symmetric key (XTEA)", "tibia.xtea",
+ FT_BYTES, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_loginflags_gm,
+ { "Gamemaster", "tibia.login.flags.gm",
+ FT_BOOLEAN, 8,
+ NULL, 0x1,
+ NULL, HFILL }
+ },
+ { &hf_tibia_game_preview_state,
+ { "Game Preview State", "tibia.login.flags.preview",
+ FT_BOOLEAN, 8,
+ NULL, 0x1,
+ NULL, HFILL }
+ },
+ { &hf_tibia_acc_name,
+ { "Account", "tibia.acc",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_acc_number,
+ { "Account", "tibia.acc",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_session_key,
+ { "Session key", "tibia.session_key",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_char_name,
+ { "Character name", "tibia.char",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_acc_pass,
+ { "Password", "tibia.pass",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_char_name_convo,
+ { "Character name", "tibia.char",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_acc_name_convo,
+ { "Account", "tibia.acc",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_acc_pass_convo,
+ { "Password", "tibia.pass",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_session_key_convo,
+ { "Session key", "tibia.session_key",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_info,
+ { "Client information", "tibia.client.info",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_locale,
+ { "Locale", "tibia.client.locale",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_locale_id,
+ { "Locale ID", "tibia.client.locale.id",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_locale_name,
+ { "Locale", "tibia.client.locale.name",
+ FT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_ram,
+ { "Total RAM", "tibia.client.ram",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_cpu,
+ { "CPU", "tibia.client.cpu",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_cpu_name,
+ { "CPU", "tibia.client.cpu.name",
+ FT_STRINGZ, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_clock,
+ { "CPU clock", "tibia.client.cpu.clock",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_clock2,
+ { "CPU clock2", "tibia.client.cpu.clock2",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_gpu,
+ { "GPU", "tibia.client.gpu",
+ FT_STRINGZ, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_vram,
+ { "Video RAM", "tibia.client.vram",
+ FT_UINT8, BASE_DEC|BASE_UNIT_STRING,
+ &mb_unit, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_resolution,
+ { "Screen resolution", "tibia.client.resolution",
+ FT_NONE, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_resolution_x,
+ { "Horizontal resolution", "tibia.client.resolution.x",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_resolution_y,
+ { "Vertical resolution", "tibia.client.resolution.y",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_resolution_hz,
+ { "Refresh rate", "tibia.client.resolution.hz",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_payload_len,
+ { "Payload length", "tibia.payload.len",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_loginserv_command,
+ { "Command", "tibia.cmd",
+ FT_UINT8, BASE_HEX,
+ VALS(from_loginserv_packet_types), 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_gameserv_command,
+ { "Command", "tibia.cmd",
+ FT_UINT8, BASE_HEX,
+ VALS(from_gameserv_packet_types), 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_client_command,
+ { "Command", "tibia.cmd",
+ FT_UINT8, BASE_HEX,
+ VALS(from_client_packet_types), 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_motd,
+ { "Message of the day", "tibia.motd",
+ FT_UINT_STRING, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_dlg_error,
+ { "Error message", "tibia.login.err",
+ FT_UINT_STRING, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_dlg_info,
+ { "Info message", "tibia.login.info",
+ FT_UINT_STRING, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_charlist,
+ { "Character list", "tibia.charlist",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_charlist_length,
+ { "Character count", "tibia.charlist.count",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_charlist_entry_name,
+ { "Character name", "tibia.charlist.name",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_charlist_entry_world,
+ { "World", "tibia.charlist.world",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_charlist_entry_ip,
+ { "IP", "tibia.charlist.ip",
+ FT_IPv4, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_charlist_entry_port,
+ { "Port", "tibia.charlist.port",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist,
+ { "World list", "tibia.worldlist",
+ FT_NONE, BASE_NONE, NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist_entry_name,
+ { "World", "tibia.worldlist.name",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist_length,
+ { "World count", "tibia.worldlist.count",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist_entry_id,
+ { "World ID", "tibia.worldlist.id",
+ FT_UINT8, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist_entry_ip,
+ { "IP", "tibia.worldlist.ip",
+ FT_UINT_STRING, BASE_NONE,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist_entry_port,
+ { "Port", "tibia.worldlist.port",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ { &hf_tibia_worldlist_entry_preview,
+ { "Preview State", "tibia.worldlist.preview",
+ FT_BOOLEAN, 8,
+ NULL, 0x1,
+ NULL, HFILL }
+ },
+ { &hf_tibia_pacc_days,
+ { "Premium days left", "tibia.pacc",
+ FT_UINT16, BASE_DEC,
+ NULL, 0x0,
+ NULL, HFILL }
+ },
+ };
+
+ static uat_field_t rsakeylist_uats_flds[] = {
+ UAT_FLD_CSTRING_OTHER(rsakeylist_uats, ipaddr, "IP address", rsakeys_uat_fld_ip_chk_cb, "IPv4 address"),
+ UAT_FLD_CSTRING_OTHER(rsakeylist_uats, port, "Port", rsakeys_uat_fld_port_chk_cb, "Port Number"),
+ UAT_FLD_FILENAME_OTHER(rsakeylist_uats, keyfile, "Key File", rsakeys_uat_fld_fileopen_chk_cb, "Private keyfile."),
+ UAT_FLD_CSTRING_OTHER(rsakeylist_uats, password,"Password", rsakeys_uat_fld_password_chk_cb, "Password (for keyfile)"),
+ UAT_END_FIELDS
+ };
+
+ static uat_field_t xteakeylist_uats_flds[] = {
+ UAT_FLD_DEC(xteakeylist_uats, framenum, "Frame Number", "XTEA key"),
+ UAT_FLD_CSTRING_OTHER(xteakeylist_uats, key, "XTEA Key", xteakeys_uat_fld_key_chk_cb, "Symmetric (XTEA) key"),
+ UAT_END_FIELDS
+ };
+
+ /* Setup protocol subtree array */
+ static gint *ett[] = {
+ &ett_tibia,
+ &ett_command,
+ &ett_file_versions,
+ &ett_client_info,
+ &ett_locale,
+ &ett_cpu,
+ &ett_resolution,
+ &ett_charlist,
+ &ett_char,
+ &ett_worldlist,
+ &ett_world,
+ };
+
+ static ei_register_info ei[] = {
+ { &ei_xtea_len_toobig,
+ { "tibia.error.xtea.length.toobig", PI_DECRYPTION, PI_ERROR,
+ "XTEA-encrypted length exceeds packet", EXPFILL }
+ },
+ { &ei_adler32_checksum_bad, { "tibia.error.checksum_bad", PI_CHECKSUM, PI_ERROR,
+ "Bad checksum", EXPFILL }
+ },
+ { &ei_rsa_plaintext_no_leading_zero,
+ { "tibia.error.rsa", PI_DECRYPTION, PI_ERROR,
+ "First byte after RSA decryption must be zero", EXPFILL }
+ },
+ { &ei_rsa_ciphertext_too_short,
+ { "tibia.error.rsa.length.tooshort", PI_DECRYPTION, PI_ERROR,
+ "RSA-encrypted data is at least 128 byte long", EXPFILL }
+ },
+ { &ei_rsa_decrypt_failed,
+ { "tibia.error.rsa.failed", PI_DECRYPTION, PI_ERROR,
+ "Decrypting RSA block failed", EXPFILL }
+ },
+ };
+
+ proto_tibia = proto_register_protocol (
+ "Tibia Protocol", /* name */
+ "Tibia", /* short name */
+ "tibia" /* abbrev */
+ );
+ proto_register_field_array(proto_tibia, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ expert_module_t *expert_tibia = expert_register_protocol(proto_tibia);
+ expert_register_field_array (expert_tibia, ei, array_length (ei));
+
+ module_t *tibia_module = prefs_register_protocol(proto_tibia, proto_reg_handoff_tibia);
+
+ prefs_register_bool_preference(tibia_module, "try_otserv_key", "Try OTServ's RSA key",
+ "Try the default RSA key in use by nearly all Open Tibia servers", &try_otserv_key);
+
+ prefs_register_bool_preference(tibia_module, "show_char_name", "Show character name for each packet",
+ "Shows active character for every packet", &show_char_name);
+ prefs_register_bool_preference(tibia_module, "show_acc_info", "Show account info for each packet",
+ "Shows account name/password or session key for every packet", &show_acc_info);
+ prefs_register_bool_preference(tibia_module, "show_xtea_key", "Show symmetric key used for each packet",
+ "Shows which XTEA key was applied for a packet", &show_xtea_key);
+ prefs_register_bool_preference(tibia_module, "dissect_game_commands", "Attempt dissection of game packet commands",
+ "Only decrypt packets and dissect login packets. Pass game commands to the data dissector", &dissect_game_commands);
+ prefs_register_bool_preference(tibia_module, "reassemble_tcp_segments",
+ "Reassemble Tibia packets spanning multiple TCP segments",
+ "Whether the Tibia dissector should reassemble packets spanning multiple TCP segments."
+ " To use this option, you must also enable \"Allow subdissectors to reassemble TCP streams\" in the TCP protocol settings.",
+ &reassemble_tcp_segments);
+
+
+ rsakeys = g_hash_table_new_full(rsakey_hash, rsakey_equal, rsakey_free, NULL);
+
+ rsakeys_uat = uat_new("RSA Keys",
+ sizeof(struct rsakeys_assoc),
+ "tibia_rsa_keys", /* filename */
+ TRUE, /* from_profile */
+ &rsakeylist_uats, /* data_ptr */
+ &nrsakeys, /* numitems_ptr */
+ UAT_AFFECTS_DISSECTION,
+ NULL,
+ rsakeys_copy_cb,
+ NULL,
+ rsakeys_free_cb,
+ rsa_parse_uat,
+ NULL,
+ rsakeylist_uats_flds);
+ prefs_register_uat_preference(tibia_module, "rsakey_table",
+ "RSA keys list",
+ "A table of RSA keys for decrypting protocols newer than 7.61",
+ rsakeys_uat
+ );
+
+ xteakeys = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+
+ xteakeys_uat = uat_new("XTEA Keys",
+ sizeof(struct xteakeys_assoc),
+ "tibia_xtea_keys", /* filename */
+ TRUE, /* from_profile */
+ &xteakeylist_uats, /* data_ptr */
+ &nxteakeys, /* numitems_ptr */
+ UAT_AFFECTS_DISSECTION,
+ NULL,
+ xteakeys_copy_cb,
+ NULL,
+ xteakeys_free_cb,
+ xtea_parse_uat,
+ NULL,
+ xteakeylist_uats_flds);
+ prefs_register_uat_preference(tibia_module, "xteakey_table",
+ "XTEA keys list",
+ "A table of XTEA keys for decrypting protocols newer than 7.61",
+ xteakeys_uat
+ );
+
+
+ /* TODO best way to store this in source? */
+ const char sexp[] =
+ "(private-key (rsa"
+ "(n #9b646903b45b07ac956568d87353bd7165139dd7940703b03e6dd079399661b4a837aa60561d7ccb9452fa0080594909882ab5bca58a1a1b35f8b1059b72b1212611c6152ad3dbb3cfbee7adc142a75d3d75971509c321c5c24a5bd51fd460f01b4e15beb0de1930528a5d3f15c1e3cbf5c401d6777e10acaab33dbe8d5b7ff5#)"
+ "(e #010001#)"
+ "(d #428bd3b5346daf71a761106f71a43102f8c857d6549c54660bb6378b52b0261399de8ce648bac410e2ea4e0a1ced1fac2756331220ca6db7ad7b5d440b7828865856e7aa6d8f45837feee9b4a3a0aa21322a1e2ab75b1825e786cf81a28a8a09a1e28519db64ff9baf311e850c2bfa1fb7b08a056cc337f7df443761aefe8d81#)"
+ "(p #91b37307abe12c05a1b78754746cda444177a784b035cbb96c945affdc022d21da4bd25a4eae259638153e9d73c97c89092096a459e5d16bcadd07fa9d504885#)"
+ "(q #0111071b206bafb9c7a2287d7c8d17a42e32abee88dfe9520692b5439d9675817ff4f8c94a4abcd4b5f88e220f3a8658e39247a46c6983d85618fd891001a0acb1#)"
+ "(u #6b21cd5e373fe462a22061b44a41fd01738a3892e0bd8728dbb5b5d86e7675235a469fea3266412fe9a659f486144c1e593d56eb3f6cfc7b2edb83ba8e95403a#)"
+ "))";
+
+ gcry_error_t err = gcry_sexp_new(&otserv_key, sexp, 0, 1);
+ if (err)
+ report_failure("Loading OTServ RSA key failed: %s/%s\n", gcry_strerror(err), gcry_strsource(err));
+}
+
+static guint
+get_dissect_tibia_len(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_)
+{
+ return tvb_get_letohs(tvb, offset) + sizeof(guint16);
+}
+
+static int
+dissect_tibia_tcp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
+{
+ static guint32 packet_num, fragment_num;
+
+ if (!packet_num) packet_num = pinfo->num;
+ if (packet_num != pinfo->num) {
+ fragment_num = 0;
+ packet_num = pinfo->num;
+ }
+
+ fragment_num++;
+
+
+ tcp_dissect_pdus(tvb, pinfo, tree, reassemble_tcp_segments, 2,
+ get_dissect_tibia_len, dissect_tibia, GUINT_TO_POINTER(fragment_num));
+ return tvb_reported_length(tvb);
+}
+
+void
+proto_reg_handoff_tibia(void)
+{
+ dissector_handle_t tibia_handle = create_dissector_handle(dissect_tibia_tcp, proto_tibia);
+
+ dissector_add_uint_range_with_preference("tcp.port", TIBIA_DEFAULT_TCP_PORT_RANGE, tibia_handle);
+}
+
+
+/*
+ * Editor modelines - http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */