The following warnings occurred: | ||||||||||||||||||||||||||||||
Warning [2] Undefined variable $captcha - Line: 15 - File: global.php(587) : eval()'d code PHP 8.2.24 (Linux)
|
Reversing Spellborn
|
UPDATED DOC IS HERE => https://github.com/TCoS-Rebirth/Documentation
if you want to update it, ask to join the TCoS-Rebirth organization by giving your github username.  Hi everybody,  (First of all, sorry for the big post. :/)  I don't know if there is still someone here except JW-NL haha ! But still I think maybe people could be interested in what I have done so far. So some weeks ago I saw a thread about Star Wars Galaxies Emu, and I told myself "that would be so great for TCoS" and remembered the thread from acidburn and JW-NL reversing the files. And then began the envy to try to reverse the packets allowing me to login into hawksmouth.  I don't know if I will be able to go that far, but still, I managed to go farest than I thought I was capable of. I learned a lot in reverse engineering these last weeks and still have a lot to learn, but I wanted to share what I learned with this passionnate community. As I love the team who made Spellborn, I don't want to do something illegal or that would bother them. So I won't share source code (and there is no source code really exploitable right now), but just documentation about the data and packets.  (this part is a copy paste of what I answered in RageZone forums to someone wanting to do a TCoS server emulation). Well, I am a noob in reverse engineering but I do programming for a few years now (computer science student for 5 years) and I am an old fan of The Chronicles of Spellborn (discovered the game in 2005 and immediatly fell in love with its visual and music! Saltiel is not the pseudo I used on the game forums). I don't know why but two weeks ago I suddenly had the envy to reverse it, at least to be able to explore a map, even empty (without npc, mobs etc).  Little disclaimer : I don't want to engage myself in anything because I have a lot of projects, but currently it's something I am working hard on. I have to say that digging through assembly is pretty hard, and a big programm like a MMO is not an easy one to start with. I started to spot some interesting places in the code, but reverse engineering them is not easy.  What I reversed so far: <ul class="bbc">[*]connect/disconnect packet[*]login packet (client/server) : can validate login or send error (bad login/password or wrong client version)[*]universes list packet (client/server) (send universe list with name, population etc)[*]a good part of the universe selected packet (sending ip/port etc of the game server)[*]How to get packet ID[*]What is the header of every packets[*]Low level packet writing / reading in d_mmos.dll[*]Some parts of the write/read functions of the packets mentionned previously in SBPacket.dll[*]I also start to have an understanding of the packet management architecture that allows me to find quickly basics information and place to investigate when starting to reverse a packet.</ul>The process is still very slow and "hand crafted". I will try to document what I did when I have the time. I will edit this post with much details later.  I still have some things to add. I will do when I have the time. I currently have a dummy server which can answer to one connection. I use the client beta 0.9 because it does not have GameGuard. I know there are ways to deactivate GameGuard but I am currently not skilled enough to do that so I prefer to focus on packet reversing. Also if someone have the files to make the update to the last version of TCoS, I am very, very interested.  Have a good day !  PS: Sorry for my bad english spoken, need to work on that too ><  Edit: current version of the doc, I am sorry this doc is originally in markdown language on a wiki but this forum does not understand this syntax:  General  All reversing documented here is currently based on the client beta 0.9.  Sb_client.exe arguments  --show_console: show the console displaying the log (you can also take control of the console and use some functions/load packages) --packet_log: enable logging of the packets sended et received --world: not really sure what this option means... seems like bypassing universe selection but does not seem to work -- uc_debugger: (check syntax) attach unreal engine debugger to the process. Sadly scripts files are not reachable. --unreal_log: seems to do nothing. Test  Encoding  TCoS client encodes its strings in UTF16-LE.  Connect to custom login server  In etc/client/sb_client.rc, modifie the line with client/net/login_addr with desired IP and port.  Network  d_mmos.dll  Contains low level functions for sending packets. Interesting places:
 d_message::read (void* dataOut, uint dataSize)  void* dataOut is a pointer to a memory location where the value read will be stored. Sometimes this memory is in stack, sometime in heap. It totally depends of the caller. For instance if the client tries to read a string, dataOut will be a pointer to the std:tring::data () (or equivalent). uint dataSize is the size in bytes of the value you want to read. For instance if you want to read a DWORD (double word = 4 bytes) its value will be 4. The d_message object keeps track of the data you've read in it. So each time you call its read method, it updates an internal value with the size of data already read (it simply adds dataSize to its current value). This is also an offset allowing it to know where to begin the reading of the data in the message. Each time you call read, you actually ask to read the next data in the d_message object. If there is no more data, or not enough (there is still 2 bytes and you ask to read 4 bytes), the method will raise an exception.  d_message::write (const void* dataIn, uint dataSize)  Work the same way than read but write dataSize bytes of data pointed by dataIn inside the d_message. Same mechanism checking that not too much data is written.  SBPacket.dll  Contains packet high level stuff. Use templates a lot, which means that the window 'Names' in IDA won't show you all the methods because the same actual method can be exported with multiples names (not apparing in 'Names' but only in 'Exports'). So, look in 'Exports' instead. The interesting classes are d_packet<struct PACKET> and d_serializable<struct PACKET> where 'struct PACKET' is the template parameter. Interesting methods in d_packet:
This is how I try to reverse packets, by studying these two methods and their context (who calls them, what structure do they fill etc). To find quickly which message has a specific ID, in OllyDBG use "find command" and type move ax, ID where ID is a short number. It will point you the good GetID () method. The template parameter gives you the message name.  Note: packet's names follow a simple convention. They have a prefix specifying if its a packet sended by the client to the server or by the server to the client. C2L: Client to Login server L2C: Login server to Client C2S: Client to Server S2C: Server to Client  There are other prefix but currently I did not find what they mean.  Packets layout  Every packet I have seen in the login process (login+ select universe) have this header: <pre>struct PacketHeader (32 bits) { WORD PacketID; WORD PacketSize; } </pre>If the PacketSize attribute does not match the actual packet size, the client will not trigger the ReadMessage or will trigger an error. If the client wants to read more data than you give to him, it will sayin the logs: "PACKET_NAME READ BUFFER OVERFLOW".  Note: For what I've seen so far, the client does not check the size in its read algorithm to verify if its is consistent with the type of packet you send. The client will just try to read everything it has to read and if you send not enough data, triggers the BUFFER OVERFLOW error seen above. This behavior is due to the d_mmos.dll: _message::read (const void* outData, uint dataSize) method which check the message size everytime you ask it to read a value in the packet. If the size alreday read + the size of the data you ask to read is greater than the message size: error.  Note: The following sizes does not take the packet's header into account, because the size contained in the packet's header do the same. <pre>struct CONNECT (4 bytes) { struct PacketHeader header; DWORD unknownDword; //statuscode? }; </pre><pre>struct DISCONNECT (various size) { struct PacketHeader header; DWORD unknownDword; //statuscode? DWORD reasonNumChars;//num chars in the reason string char [reasonNumChars*2] reason;//in UTF16-LE }; </pre><pre>struct C2L_USER_LOGIN (various size) { struct PacketHeader header; DWORD clientVersion;//svn build version DWORD loginNumCharacters; char[loginNumCharacters*2] loginString;//in UTF-16LE DWORD passwordNumCharacters; char[passwordNumCharacters * 2] passwordString;//in UTF16-LE }; </pre><pre>struct L2C_USER_LOGIN_ACK (2 DWORDS = 8 bytes) { struct PacketHeader header; DWORD zeroDword;//unknown data, its value does nothing DWORD statusCode; }; </pre>statusCode possible values:
{ struct PacketHeader header; }; </pre><pre>struct Universe (various size) { DWORD universeID; DWORD universeNameNumChars; char[universeNameNumChars*2] universeName; //UTF16-LE DWORD universeLanguageNumChars; char[universeLanguageNumChars*2] universeLanguage; //UTF16-LE DWORD universeTypeNumChars; char[universeTypeNumChars*2] universeType; //UTF16-LE DWORD universePopulationNumChars; char[universePopulationNumChars*2] universePopulation; //UTF16-LE }; struct L2C_QUERY_UNIVERSE_LIST_ACK (various size) { struct PacketHeader header; DWORD unknownDWORD; // MUST BE 0 to work, integrity check? DWORD universesNumber; //number of universes available struct Universe[universesNumber] universesList; // universesNumber times the attributes in the struct Universe }; </pre><pre>struct C2L_UNIVERSE_SELECTED (size = 1 DWORD) { struct PacketHeader header; DWORD universeID; //id of the selected universe }; </pre><pre>struct L2C_UNIVERSE_SELECTED_ACK (size = 1 DWORD) { struct PacketHeader header; DWORD unknownDword#1; //MUST BE 0 DWORD unknownDword#2; DWORD universePackageNameNumChars; char[universePackageNameNumChars*2] universePackageName; DWORD tkey;//to investigate DWORD gameServerIP; WORD gameServerPort; }; </pre>Note: universePackageName is the name of the file in data/universe with the SBU sufixe removed from its name (I am not speeking of the extension but the sufix), and without the extension. The tkey may be for "transport key" seems to be (not sure) a security allowing the game world to detect if a client did the login part before connecting to it. I think it works this way: the Login Server generate a tkey for a given player when it tries to login, send this key to the game world (by storing it in a commune DB or whatever) and to the client through this packet. When the client log into the game world, it sends back the key, the game world check if the key is correct and allow connection or not. <pre>struct C2S_TRAVEL_CONNECT (size = 1 DWORD) { struct PacketHeader header; DWORD tkey; //the tkey the login server sended in the L2C_UNIVERSE_SELECTED_ACK packet }; </pre><pre>struct S2C_WORLD_PRE_LOGIN (size = 2 DWORD) { struct PacketHeader header; DWORD unknownDWord;//MUST BE 0 DWORD worldId; //must be 1 for character selection }; </pre>Note: the worldId seems to be an ID identifying the map to load but I'm not sure. In the log if the number you send is not 1, you see an error like "failed to load game world (worldId)". Should test with other IDs to see if it loads other maps file than baseCharacterSelection.sbw. <pre>struct C2S_WORLD_PRE_LOGIN_ACK (size = 1 DWORD) { struct PacketHeader header; DWORD unknownDWord;// = 0; to investigate }; </pre>Protocol Here is the dialog between client and server, don't know if it's very useful. For detailed info about the packets structure, see the section above. Â Client sends CONNECT to Login Server (status code?) Â Client sends C2L_USER_LOGIN (login + password) Login Server answers L2C_USER_LOGIN_ACK (status code) Â Client sends C2L_QUERY_UNIVERSE_LIST (no data) Login Server answers L2C_QUERY_UNIVERSE_LIST_ACK (status code?, universes list with infos) Â Client sends C2L_UNIVERSE_SELECTED (universe selected id) Login Server answers L2C_UNIVERSE_SELECTED_ACK (universe package's name, universe (game server) IP + port, and tkey) Â Client sends CONNECT to Game Server Beginning of the conversatiom between Client and Game Server Client sends C2L_TRAVEL_CONNECT to Game Server (with tkey) Client sends DISCONNECT to Login Server (disconnection reason "connect to game world") Conversation between Client and Login Server is now over. Â Game Server sends S2C-WORLD_PRE_LOGIN (world setup id, must be 1, to investigate) Client answers C2S_WORLD_PRE_LOGIN_ACK Â Game Server sends S2C_WORLD_LOGIN Â List of Packets ID Â CONNECT = 0xFFFD; DISCONNECT = 0xFFFE; Â Client to Login packets ID Â C2L_USER_LOGIN = 0; C2L_QUERY_UNIVERSE_LIST = 2; C2L_UNIVERSE_SELECTED = 4; Â Login Server packets ID Â L2C_USER_LOGIN_ACK = 1; L2C_QUERY_UNIVERSE_LIST_ACK = 3; L2C_UNIVERSE_SELECTED_ACK = 5; Â Client to Game Server packets ID Â C2S_TRAVEL_CONNECT = 0; C2S_WORLD_PRE_LOGIN_ACK = 2; C2S_WORLD_LOGIN_ACK = 4; Â Game Server packets ID Â S2C_WORLD_PRE_LOGIN = 1; S2C_WORLD_LOGIN = 3; Â Â Â To be continued (other packet layout etc). If you have specific questions do not hesitate to ask, maybe I could answer it. This post was last modified: 15-03-2015, 10:10 PM by Saltiel.
|
« Next Oldest | Next Newest »
|