Donnerstag, 17. Dezember 2009

How to start a LOTRO client

I'm currently looking into understanding the network protocol better. I use the following command to see what the client is doing:


lotroclient.exe
-a 2
-h 192.168.1.123:9000
--glsticketdirect 1
--chatserver 192.168.1.124:2900
--language fr
--bugurl http://192.168.1.125/bug.php
--supporturl http://192.168.1.126/TSSTrowser/Trowser.aspx
-GameType LOTROEU
--authserverurl https://lotroeugls.com/gls.authserver/service.asmx
--glsticketlifetime 86400


Note that this assumes you are running on a system with a hacked /etc/hosts file so you don't actually connect to lotroeugls.com!

I'll explain the options I used in detail to show you what I am doing:
-a 2
: I just a invalid username (I don't want to use a real one for privacy reasons). You can change this to whatever you like. This allows you to find the name in the network traffic.
-h 192.168.1.123
: This is bogus IP on the same local network as I am using. This is necessary to filter packets by IP to identify which host a client sends packets to
--glsticketdirect 1
: A bogus GLS ticket. Could be whatever you like. I chose a short one to minimize the logs.
--chatserver 192.168.1.124:2900
: Another bogus host to be used as a chatserver.

The other options don't really matter right now (as we don't have another side to talk to this will never be used). This generates the inital network traffic the client is performing to setup a connection to the server.

Once you trace the crypt and networking code (using wine as described below) you are able to trace what ever the client was doing.

Samstag, 11. Juli 2009

Listing the available options

While playing around with the bits and pieces to launch the client I accidentially got it to print all options. Here is a short list of all available commandline options of the game client.


You must specify an account name

-a, --account : <string>: Specifies the account name to logon with.
--authserverurl : Auth server URL for refreshing the GLS ticket.
--bugurl : <url> : Specifies the url that should be used for reporting bugs.
--chatserver : Specify the chat server to use.
--connspeed : <0.0-640.0>:Connection speed in Kilobits/sec for the server-client connection. 0 Defaults to speed searching
-r, --create : <name> : Character Name you would like to create/play
--dbwlib : <wlib DID> : Specifies the exact wlib did contained in the datfiles to use.
--debug : <32 bitfield>: Controls what kinds of debug outputs are enabled.
-f, --franchise : <string>: Specifies the franchise name.
--gametype : <string> : Specifies the game type used for supporturl and supportserviceurl.
--glsticket : Load gls ticket data from specified registry key.
-z, --glsticketdirect: <string>: Specify ticket data.
--glsticketlifetime: The lifetime of GLS tickets.
--HighResOutOfDate: Tells the client that the high resolution texture dat file was not updated. We will not switch into very high texture detail..
-h, --host : [host/IP]:Specifies where to find the server to talk to.
--keymap : <file> : Base file for the keymap. Will also look for <filename>c.extension and <filename>s.extension for meta keys
--language : <string>: Language to run the client in.
-m, --mps : <monster play session>.wc : Monster Play Session to start with
--nodbwlib : Tells the client not to use the database cache for locating the wlib. Instead, the wlib will be loaded directly from the hard drive.
--outport : <1-65535>: Specify the outgoing network port to use.
-p, --port : <1-65535>: Specify the server port to contact. See 'host'
--prefs : <string>: Specify the preferences file to use.
--resource-set : <string>: A comma separated list of available resource sets. The last set is the default.
--rodat : Opens the DAT files readonly
--safe : Force SAFE display settings.
-s, --specify : <race>,<class> : Race Class pair for a character you would like to create
--supportserviceurl: <url> : Specifies the url that should be used for auto submission of in game support tickets.
--supporturl : <url> : Specifies the url that should be used for in game support.
--surveyurl : <url> : URL to use for quest surveys
--usechat : Specify that the client should connect to a chat server.
--useexiturl : launch a browser using an exit url
-u, --user : <name> : Character Name you would like to play
--usetdm : Enable the download manager
--voicenetdelay : <int>: Specifies the voice network delay threshold in milliseconds.
--voiceoff : Disables the Voice chat system.
--webjournalurl : <url> : URL to use for the web journal
--wfilelog : <64-bitmask> : activates file logging for the specified weenie event types. Alternately, logtype enums seperated by ',' are enummapped and or'ed together.
--wprintlog : <64-bitmask> : activates print logging for the specified weenie event types. Alternately, logtype enums seperated by ',' are enummapped and or'ed together.

Samstag, 21. Februar 2009

Tracing the crypt API

I managed to get lotro to run on wine on linux. I pretty much used the steps described at lotros appdb entry and also did "winetricks directx9 because of "d3dx9_36.dll".

Here is the complete dump of that run (stripped down to the relevant trace to the crypt api):
Code:
trace:crypt:CryptAcquireContextA (0x5acfb4c, C982860F-EB79-4CFB-8EFC-6F0426F796B6, Microsoft Enhanced Cryptographic Provider v1.0, 1, 00000008)
trace:crypt:CryptAcquireContextW (0x5acfb4c, L"C982860F-EB79-4CFB-8EFC-6F0426F796B6", L"Microsoft Enhanced Cryptographic Provider v1.0", 1, 00000008)
trace:crypt:RSAENH_CPAcquireContext (phProv=0x4f4b008, pszContainer="C982860F-EB79-4CFB-8EFC-6F0426F796B6", dwFlags=00000008, pVTable=0x4f4b088)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000001)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000001)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000000)
trace:crypt:CryptImportKey (0x4f4aff8, 0x1020b30, 308, 0x0, 00000001, 0x582feac)
trace:crypt:RSAENH_CPImportKey (hProv=00000001, pbData=0x1020b30, dwDataLen=308, hPubKey=00000000, dwFlags=00000001, phKey=0x4f4b624)
trace:crypt:import_private_key installing key exchange key
trace:crypt:RSAENH_CPDestroyKey (hProv=00000001, hKey=ffffffff)
trace:crypt:CryptProtectData called
trace:crypt:report pPromptStruct: (nil)
trace:crypt:report dwFlags: 0x0000
trace:crypt:report pDataIn cbData: 308
trace:crypt:report pDataIn pbData @ 0x4f501c0:07,02,00,00,00,a4,00,00,52,53,41,32,00,02,00,00,01,00,00,00,ab,ef,fa,c6,7d,e8,de,fb,68,38,09,92,d9,42,7e,6b,89,9e,21,d7,52,1c,99,3c,17,48,4e,3a,44,02,f2,fa,74,57,da,e4,d3,c0,35,67,fa,6e,df,78,4c,75,3
trace:crypt:CryptProtectData szDataDescr: (nil)
trace:crypt:CryptAcquireContextW (0x33f98c, (null), L"Microsoft Enhanced Cryptographic Provider v1.0", 1, f0000000)
trace:crypt:RSAENH_CPAcquireContext (phProv=0x4f4b160, pszContainer=(null), dwFlags=f0000000, pVTable=0x4f4b170)
trace:crypt:fill_protect_data called
trace:crypt:CryptGenRandom (0x4f4b150, 0, 0x4f4b0e8)
trace:crypt:RSAENH_CPGenRandom (hProv=00000004, dwLen=0, pbBuffer=0x4f4b0e8)
trace:crypt:fill_protect_data &pInfo->salt cbData: 16
trace:crypt:fill_protect_data &pInfo->salt pbData @ 0x4f4b0e8:c8,00,11,00,b8,00,11,00,6f,00,73,00,6f,00,66,00
trace:crypt:CryptCreateHash (0x4f4b150, 0x8004, 0x0, 00000000, 0x33f86c)
trace:crypt:RSAENH_CPCreateHash (hProv=00000004, Algid=00008004, hKey=00000000, dwFlags=00000000, phHash=0x4f4b104)
trace:crypt:CryptHashData (0x4f4b100, 0x4f4b118, 7, 00000000)
trace:crypt:RSAENH_CPHashData (hProv=00000004, hHash=00000005, pbData=0x4f4b118, dwDataLen=7, dwFlags=00000000)
trace:crypt:CryptHashData (0x4f4b100, 0x7bfe56f4, 19, 00000000)
trace:crypt:RSAENH_CPHashData (hProv=00000004, hHash=00000005, pbData=0x7bfe56f4, dwDataLen=19, dwFlags=00000000)
trace:crypt:CryptHashData (0x4f4b100, 0x4f4b0e8, 16, 00000000)
trace:crypt:RSAENH_CPHashData (hProv=00000004, hHash=00000005, pbData=0x4f4b0e8, dwDataLen=16, dwFlags=00000000)
trace:crypt:CryptDeriveKey (0x4f4b150, 0x00006603, 0x4f4b100, 0x00a80001, 0x33f984)
trace:crypt:RSAENH_CPDeriveKey (hProv=00000004, Algid=26115, hBaseData=00000005, dwFlags=00a80001 phKey=0x4f4b134)
trace:crypt:RSAENH_CPGetHashParam (hProv=00000004, hHash=00000005, dwParam=00000002, pbData=0x33f618, pdwDataLen=0x33f7d0, dwFlags=00000000)
trace:crypt:CryptDestroyHash (0x4f4b100)
trace:crypt:RSAENH_CPDestroyHash (hProv=00000004, hHash=00000005)
trace:crypt:CryptCreateHash (0x4f4b150, 0x8004, 0x0, 00000000, 0x33f988)
trace:crypt:RSAENH_CPCreateHash (hProv=00000004, Algid=00008004, hKey=00000000, dwFlags=00000000, phHash=0x4f4b104)
trace:crypt:CryptEncrypt (0x4f4b130, 0x0, 1, 00000000, 0x4f501c0, 0x33f980, 0)
trace:crypt:RSAENH_CPEncrypt (hProv=00000004, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=0x4f501c0, pdwDataLen=0x33f980, dwBufLen=0)
trace:crypt:CryptProtectData required encrypted storage: 312
trace:crypt:CryptEncrypt (0x4f4b130, 0x4f4b100, 1, 00000000, 0x4f50a28, 0x33f980, 312)
trace:crypt:RSAENH_CPEncrypt (hProv=00000004, hKey=00000006, hHash=00000005, Final=1, dwFlags=00000000, pbData=0x4f50a28, pdwDataLen=0x33f980, dwBufLen=312)
trace:crypt:RSAENH_CPHashData (hProv=00000004, hHash=00000005, pbData=0x4f50a28, dwDataLen=308, dwFlags=00000000)
trace:crypt:CryptProtectData &protect_data.cipher cbData: 312
trace:crypt:CryptProtectData &protect_data.cipher pbData @ 0x4f50a28:75,ed,86,25,73,e8,fb,9c,fa,98,dc,41,d2,0c,81,98,bf,ec,43,4b,48,95,af,ce,ec,19,11,80,17,ce,56,9c,04,7b,bd,03,cb,cd,a4,7f,43,50,e4,cb,43,b4,26,cb,44,41,b5,42,84,69,3b,e4,00,de,37,62,32,37,64,46,1a,d6,7
trace:crypt:convert_hash_to_blob called
trace:crypt:CryptGetHashParam (0x4f4b100, 4, 0x33f974, 0x33f86c, 00000000)
trace:crypt:RSAENH_CPGetHashParam (hProv=00000004, hHash=00000005, dwParam=00000004, pbData=0x33f974, pdwDataLen=0x33f86c, dwFlags=00000000)
trace:crypt:CryptGetHashParam (0x4f4b100, 2, 0x4f4af78, 0x33f86c, 00000000)
trace:crypt:RSAENH_CPGetHashParam (hProv=00000004, hHash=00000005, dwParam=00000002, pbData=0x4f4af78, pdwDataLen=0x33f86c, dwFlags=00000000)
trace:crypt:serialize called
trace:crypt:CryptDestroyHash (0x4f4b100)
trace:crypt:RSAENH_CPDestroyHash (hProv=00000004, hHash=00000005)
trace:crypt:CryptDestroyKey (0x4f4b130)
trace:crypt:RSAENH_CPDestroyKey (hProv=00000004, hKey=00000006)
trace:crypt:free_protect_data called
trace:crypt:CryptReleaseContext (0x4f4b150, 00000000)
trace:crypt:RSAENH_CPReleaseContext (hProv=00000004, dwFlags=00000000)
trace:crypt:CryptProtectData pDataOut cbData: 450
trace:crypt:CryptProtectData pDataOut pbData @ 0x4f50b68:01,00,00,00,57,69,6e,65,20,43,72,79,70,74,33,32,20,6f,6b,00,01,00,00,00,57,69,6e,65,20,43,72,79,70,74,33,32,20,6f,6b,00,00,00,00,00,02,00,00,00,00,00,03,66,00,00,a8,00,00,00,10,00,00,00,57,69,6e,65,2
trace:crypt:CryptProtectData returning ok
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000001)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000001)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb28, pdwDataLen=0x33fb84, dwFlags=00000000)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb24, 0x33fb80, 00000001)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb24, pdwDataLen=0x33fb80, dwFlags=00000001)
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb24, 0x33fb80, 00000000)
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb24, pdwDataLen=0x33fb80, dwFlags=00000000)
trace:crypt:CryptImportKey (0x4f4aff8, 0xe5f6b8, 148, 0x0, 00000001, 0x582fec0)
trace:crypt:RSAENH_CPImportKey (hProv=00000001, pbData=0xe5f6b8, dwDataLen=148, hPubKey=00000000, dwFlags=00000001, phKey=0x4f4b6ec)
trace:crypt:import_public_key installing public key
trace:crypt:RSAENH_CPDestroyKey (hProv=00000001, hKey=00000003)
trace:crypt:CryptProtectData called
trace:crypt:report pPromptStruct: (nil)
trace:crypt:report dwFlags: 0x0000
trace:crypt:report pDataIn cbData: 596
trace:crypt:report pDataIn pbData @ 0x4f50598:07,02,00,00,00,a4,00,00,52,53,41,32,00,04,00,00,01,00,01,00,4d,03,8d,d7,d8,a8,85,97,2d,04,de,57,e2,77,c4,bf,c6,13,15,0e,26,72,99,11,a1,cd,f3,8f,d5,5d,63,4c,f3,75,b7,62,9d,ee,e8,d5,c5,a7,0e,3c,9c,51,d
trace:crypt:CryptProtectData szDataDescr: (nil)
trace:crypt:CryptAcquireContextW (0x33f9b0, (null), L"Microsoft Enhanced Cryptographic Provider v1.0", 1, f0000000)
trace:crypt:RSAENH_CPAcquireContext (phProv=0x4f4b180, pszContainer=(null), dwFlags=f0000000, pVTable=0x4f4b190)
trace:crypt:fill_protect_data called
trace:crypt:CryptGenRandom (0x4f4b170, 0, 0x4f4b118)
trace:crypt:RSAENH_CPGenRandom (hProv=00000006, dwLen=0, pbBuffer=0x4f4b118)
trace:crypt:fill_protect_data &pInfo->salt cbData: 16
trace:crypt:fill_protect_data &pInfo->salt pbData @ 0x4f4b118:c8,00,11,00,b8,00,11,00,70,00,68,00,69,00,63,00
trace:crypt:CryptCreateHash (0x4f4b170, 0x8004, 0x0, 00000000, 0x33f890)
trace:crypt:RSAENH_CPCreateHash (hProv=00000006, Algid=00008004, hKey=00000000, dwFlags=00000000, phHash=0x4f4b134)
trace:crypt:CryptHashData (0x4f4b130, 0x4f4b148, 7, 00000000)
trace:crypt:RSAENH_CPHashData (hProv=00000006, hHash=00000005, pbData=0x4f4b148, dwDataLen=7, dwFlags=00000000)
trace:crypt:CryptHashData (0x4f4b130, 0x7bfe56f4, 19, 00000000)
trace:crypt:RSAENH_CPHashData (hProv=00000006, hHash=00000005, pbData=0x7bfe56f4, dwDataLen=19, dwFlags=00000000)
trace:crypt:CryptHashData (0x4f4b130, 0x4f4b118, 16, 00000000)
trace:crypt:RSAENH_CPHashData (hProv=00000006, hHash=00000005, pbData=0x4f4b118, dwDataLen=16, dwFlags=00000000)
trace:crypt:CryptDeriveKey (0x4f4b170, 0x00006603, 0x4f4b130, 0x00a80001, 0x33f9a8)
trace:crypt:RSAENH_CPDeriveKey (hProv=00000006, Algid=26115, hBaseData=00000005, dwFlags=00a80001 phKey=0x4f4af7c)
trace:crypt:RSAENH_CPGetHashParam (hProv=00000006, hHash=00000005, dwParam=00000002, pbData=0x33f63c, pdwDataLen=0x33f7f4, dwFlags=00000000)
trace:crypt:CryptDestroyHash (0x4f4b130)
trace:crypt:RSAENH_CPDestroyHash (hProv=00000006, hHash=00000005)
trace:crypt:CryptCreateHash (0x4f4b170, 0x8004, 0x0, 00000000, 0x33f9ac)
trace:crypt:RSAENH_CPCreateHash (hProv=00000006, Algid=00008004, hKey=00000000, dwFlags=00000000, phHash=0x4f4b134)
trace:crypt:CryptEncrypt (0x4f4af78, 0x0, 1, 00000000, 0x4f50598, 0x33f9a4, 0)
trace:crypt:RSAENH_CPEncrypt (hProv=00000006, hKey=00000007, hHash=00000000, Final=1, dwFlags=00000000, pbData=0x4f50598, pdwDataLen=0x33f9a4, dwBufLen=0)
trace:crypt:CryptProtectData required encrypted storage: 600
trace:crypt:CryptEncrypt (0x4f4af78, 0x4f4b130, 1, 00000000, 0x4f50f20, 0x33f9a4, 600)
trace:crypt:RSAENH_CPEncrypt (hProv=00000006, hKey=00000007, hHash=00000005, Final=1, dwFlags=00000000, pbData=0x4f50f20, pdwDataLen=0x33f9a4, dwBufLen=600)
trace:crypt:RSAENH_CPHashData (hProv=00000006, hHash=00000005, pbData=0x4f50f20, dwDataLen=596, dwFlags=00000000)
trace:crypt:CryptProtectData &protect_data.cipher cbData: 600
trace:crypt:CryptProtectData &protect_data.cipher pbData @ 0x4f50f20:37,4d,86,43,7b,79,12,11,08,e0,7d,84,98,81,59,51,c1,ca,16,8e,11,99,c0,b5,18,52,ab,94,43,9e,33,7a,69,67,e8,96,1b,22,35,2a,19,5f,5b,d3,de,b9,c2,0d,c8,d8,2f,24,19,5f,55,9c,08,aa,87,da,17,b6,9c,55,6d,e6,1
trace:crypt:convert_hash_to_blob called
trace:crypt:CryptGetHashParam (0x4f4b130, 4, 0x33f998, 0x33f890, 00000000)
trace:crypt:RSAENH_CPGetHashParam (hProv=00000006, hHash=00000005, dwParam=00000004, pbData=0x33f998, pdwDataLen=0x33f890, dwFlags=00000000)
trace:crypt:CryptGetHashParam (0x4f4b130, 2, 0x4f4b148, 0x33f890, 00000000)
trace:crypt:RSAENH_CPGetHashParam (hProv=00000006, hHash=00000005, dwParam=00000002, pbData=0x4f4b148, pdwDataLen=0x33f890, dwFlags=00000000)
trace:crypt:serialize called
trace:crypt:CryptDestroyHash (0x4f4b130)
trace:crypt:RSAENH_CPDestroyHash (hProv=00000006, hHash=00000005)
trace:crypt:CryptDestroyKey (0x4f4af78)
trace:crypt:RSAENH_CPDestroyKey (hProv=00000006, hKey=00000007)
trace:crypt:free_protect_data called
trace:crypt:CryptReleaseContext (0x4f4b170, 00000000)
trace:crypt:RSAENH_CPReleaseContext (hProv=00000006, dwFlags=00000000)
trace:crypt:CryptProtectData pDataOut cbData: 738
trace:crypt:CryptProtectData pDataOut pbData @ 0x4f51180:01,00,00,00,57,69,6e,65,20,43,72,79,70,74,33,32,20,6f,6b,00,01,00,00,00,57,69,6e,65,20,43,72,79,70,74,33,32,20,6f,6b,00,00,00,00,00,02,00,00,00,00,00,03,66,00,00,a8,00,00,00,10,00,00,00,57,69,6e,65,2
trace:crypt:CryptProtectData returning ok
trace:crypt:CryptGenKey (0x4f4aff8, 26625, 00000001, 0x582fed4)
trace:crypt:RSAENH_CPGenKey (hProv=00000001, aiAlgid=26625, dwFlags=00000001, phKey=0x4f4b704)
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, (nil), 0x79f1c89c)
trace:crypt:RSAENH_CPExportKey (hProv=00000001, hKey=00000006, hPubKey=00000004, dwBlobType=00000001, dwFlags=00000000, pbData=(nil),pdwDataLen=0x79f1c89c)
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, 0x6e15be0, 0x79f1c89c)
trace:crypt:RSAENH_CPExportKey (hProv=00000001, hKey=00000006, hPubKey=00000004, dwBlobType=00000001, dwFlags=00000000, pbData=0x6e15be0,pdwDataLen=0x79f1c89c)
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, (nil), 0x79f1c87c, 265)
trace:crypt:RSAENH_CPEncrypt (hProv=00000001, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=(nil), pdwDataLen=0x79f1c87c, dwBufLen=265)
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, 0x6e1fe34, 0x79f1c880, 265)
trace:crypt:RSAENH_CPEncrypt (hProv=00000001, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=0x6e1fe34, pdwDataLen=0x79f1c880, dwBufLen=265)
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, (nil), 0x79f1c944)
trace:crypt:RSAENH_CPExportKey (hProv=00000001, hKey=00000006, hPubKey=00000004, dwBlobType=00000001, dwFlags=00000000, pbData=(nil),pdwDataLen=0x79f1c944)
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, 0x6e4ea48, 0x79f1c944)
trace:crypt:RSAENH_CPExportKey (hProv=00000001, hKey=00000006, hPubKey=00000004, dwBlobType=00000001, dwFlags=00000000, pbData=0x6e4ea48,pdwDataLen=0x79f1c944)
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, (nil), 0x79f1c924, 265)
trace:crypt:RSAENH_CPEncrypt (hProv=00000001, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=(nil), pdwDataLen=0x79f1c924, dwBufLen=265)
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, 0x6e58c9c, 0x79f1c928, 265)
trace:crypt:RSAENH_CPEncrypt (hProv=00000001, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=0x6e58c9c, pdwDataLen=0x79f1c928, dwBufLen=265)
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, (nil), 0x79f1c944)
trace:crypt:RSAENH_CPExportKey (hProv=00000001, hKey=00000006, hPubKey=00000004, dwBlobType=00000001, dwFlags=00000000, pbData=(nil),pdwDataLen=0x79f1c944)
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, 0x6e4ea48, 0x79f1c944)
trace:crypt:RSAENH_CPExportKey (hProv=00000001, hKey=00000006, hPubKey=00000004, dwBlobType=00000001, dwFlags=00000000, pbData=0x6e4ea48,pdwDataLen=0x79f1c944)
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, (nil), 0x79f1c924, 265)
trace:crypt:RSAENH_CPEncrypt (hProv=00000001, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=(nil), pdwDataLen=0x79f1c924, dwBufLen=265)
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, 0x6e58c9c, 0x79f1c928, 265)
trace:crypt:RSAENH_CPEncrypt (hProv=00000001, hKey=00000006, hHash=00000000, Final=1, dwFlags=00000000, pbData=0x6e58c9c, pdwDataLen=0x79f1c928, dwBufLen=265)
So, I hope you brought some time sine we are going through these calls ... one by one.

Code:
trace:crypt:CryptAcquireContextA (0x5acfb4c, C982860F-EB79-4CFB-8EFC-6F0426F796B6, Microsoft Enhanced Cryptographic Provider v1.0, 1, 00000008)
  • 0x5acfb4c: memory offset where the generated crypt provider will be placed, uninteresting
  • C982860F-EB79-4CFB-8EFC-6F0426F796B6: the key containers name ... a GUID which I have no info about. I think it's a GUID chosen by Codemasters to identify their container.
  • Microsoft Enhanced Cryptographic Provider v1.0: this is MS_ENHANCED_PROV
  • 1: this is PROV_RSA_FULL
  • 00000008: this is CRYPT_NEWKEYSET

Code:
trace:crypt:CryptAcquireContextW (0x5acfb4c, L"C982860F-EB79-4CFB-8EFC-6F0426F796B6", L"Microsoft Enhanced Cryptographic Provider v1.0", 1, 00000008)
Wine internally forwards ASCII calls to theid unicode equivalents. you can ignore this.

Code:
trace:crypt:RSAENH_CPAcquireContext (phProv=0x4f4b008, pszContainer="C982860F-EB79-4CFB-8EFC-6F0426F796B6", dwFlags=00000008, pVTable=0x4f4b088)
This is wines internal call the actual cryptographic provider, RSA. Not much of an interest here.

Code:
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000001)
  • 0x4f4aff8: This is the provider aquired by CryptAcquireContextA. Interesting is that in CryptAcquireContextA it was 0x5acfb4c while here it is 0x4f4aff8. Looks like it was copied in memory to be safe of modifications (or I'm just misreading the log here).
  • 22: is 0x16 in hex, so PP_ENUMALGS_EX
  • 0x33fb00: an instance of the PP_ENUMALGS_EX structure
  • 0x33fb5c: pointer to the length of the PP_ENUMALGS_EX structure, out param in this case
  • 00000001: first call, so CRYPT_FIRST must be set

Code:
trace:crypt:RSAENH_CPGetProvParam (hProv=00000001, dwParam=00000016, pbData=0x33fb00, pdwDataLen=0x33fb5c, dwFlags=00000001)
  • 00000001: internal handle to the provider
  • 16: PP_PROVTYPE
  • 0x33fb00: out pointer for data
  • 0x33fb5c: out pointer for data length
  • 00000001: CRYPT_FIRST
Forward call to the RSA provider. These calls are pretty much straight forward and not really relevant for our output. I'll skip these from now on and only come back to them if I think they are interesting.

Code:
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb00, 0x33fb5c, 00000000)
Exactly the same call as the CryptGetProvParam above without the CRYPT_FIRST flag set. No idea why one would call that multiple times. Maybe CryptGetProvParam returns information about a different algo each time? And maybe they search for a specific one? No idea.

Code:
trace:crypt:CryptImportKey (0x4f4aff8, 0x1020b30, 308, 0x0, 00000001, 0x582feac)
  • 0x4f4aff8: handle to the CSP, we already know from the calls before
  • 0x1020b30: dang, the public key as a blob. need to figure out where it comes from. really interesting call. Definitely need to find out what is in there.
  • 308: length of the key
  • 0x0: hmm, really interesting! is set to zero which means: they key in not encrypted. plain public key in memory.
  • 00000001: looks like CRYPT_EXPORTABLE
  • 0x582feac: handle to the imported key.

Code:
trace:crypt:CryptProtectData called
trace:crypt:report pPromptStruct: (nil)
trace:crypt:report dwFlags: 0x0000
trace:crypt:report pDataIn cbData: 308
trace:crypt:report pDataIn pbData @ 0x4f501c0:07,02,00,00,00,a4,00,00,52,53,41,32,00,02,00,00,01,00,00,00,ab,ef,fa,c6,7d,e8,de,fb,68,38,09,92,d9,42,7e,6b,89,9e,21,d7,52,1c,99,3c,17,48,4e,3a,44,02,f2,fa,74,57,da,e4,d3,c0,35,67,fa,6e,df,78,4c,75,3
trace:crypt:CryptProtectData szDataDescr: (nil)
Strange looking trace, i guess.
  • pDataIn: data to be encrypted.
  • szDataDescr: no description of the pDataIn given. totally ok.
  • pOptionalEntropy: missing this one .... means no extra password or entropy given
  • pPromptStruct: no prompts given. totally ok.
  • dwFlags: no special crypt protect, good.
  • pDataOut: encrypted data ... pretty much irrelevant
So at this point the valuable data from pbData was symetric encrypted. The wine source states that this is only for host encryption where you cant trust other users, see dlls/crypt32/protectdata.c. It's worth noting that this call ends with
Code:
trace:crypt:CryptProtectData returning ok
while the stuff in between is wines interal calls.

Code:
trace:crypt:CryptGetProvParam (0x4f4aff8, 22, 0x33fb28, 0x33fb84, 00000001)
Looks like they are searching a algo or a key again.

Code:
trace:crypt:CryptImportKey (0x4f4aff8, 0xe5f6b8, 148, 0x0, 00000001, 0x582fec0)
  • 0x4f4aff8: handle to the CSP, as always
  • 0xe5f6b8: again, the key to import
  • 148: short one this time
  • 0x0: not encrypted
  • 00000001: CRYPT_EXPORTABLE
  • 0x582fec0: handle to the imported key.

Code:
trace:crypt:CryptProtectData called
trace:crypt:report pPromptStruct: (nil)
trace:crypt:report dwFlags: 0x0000
trace:crypt:report pDataIn cbData: 596
trace:crypt:report pDataIn pbData @ 0x4f50598:07,02,00,00,00,a4,00,00,52,53,41,32,00,04,00,00,01,00,01,00,4d,03,8d,d7,d8,a8,85,97,2d,04,de,57,e2,77,c4,bf,c6,13,15,0e,26,72,99,11,a1,cd,f3,8f,d5,5d,63,4c,f3,75,b7,62,9d,ee,e8,d5,c5,a7,0e,3c,9c,51,d
trace:crypt:CryptProtectData szDataDescr: (nil)
Again protects the key against access from other users.

Code:
trace:crypt:CryptGenKey (0x4f4aff8, 26625, 00000001, 0x582fed4)
  • 0x4f4aff8: handle to the CSP
  • 26625: this is CALG_RC4, stream encryption
  • 00000001: CRYPT_EXPORTABLE
  • 0x582fed4: handle where the key will be stored

Code:
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, (nil), 0x79f1c89c)
  • 0x4f4b700: handle to the key that will be exported.
  • 0x4f4b6e8: this is the handle to a public key used for encryption of 0x4f4b700
  • 1: SIMPLEBLOB
  • 00000000: no dwParam
  • (nil): ok, we have no idea how large the exported key will be. this is a dry run to determine the size
  • 0x79f1c89c: size will be put here
This was only a dry run to find out how large the exported key will be. Now the client will allocate a buffer and really export the key.

Code:
trace:crypt:CryptExportKey (0x4f4b700, 0x4f4b6e8, 1, 00000000, 0x6e15be0, 0x79f1c89c)
  • 0x4f4b700: same as above
  • 0x4f4b6e8: same as above
  • 1: same as above
  • 00000000: same as above
  • 0x6e15be0: buffer where the encrypted key will end in
  • 0x79f1c89c: same as above

Code:
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, (nil), 0x79f1c87c, 265)
  • 0x4f4b700: handle to the key that will be used for encryption
  • 0x0: encrypted data will not be hashed
  • 1: final is set so the data passed in this call is the complete data
  • 00000000: no dwFlags
  • (nil): again, dry run. will put the size into the next param
  • 0x79f1c87c: size of the buffer necessary to store the encrypted result
  • 265: means we want to encrypt 265 bytes
Dry run is through, now on the the encryption with the buffer.

Code:
trace:crypt:CryptEncrypt (0x4f4b700, 0x0, 1, 00000000, 0x6e1fe34, 0x79f1c880, 265)
  • 0x4f4b700: same as a above
  • 0x0: same as a above
  • 1: same as a above
  • 00000000: same as a above
  • 0x6e1fe34: buffer containing the plain text and containing the ciphered text after this function passed
  • 0x79f1c87c: size of ciphered data
  • 265: same as above

Ok, that's pretty much it. I'll try to explain in normal text what happens here.
  • The client is loading 2 stored keys here. I'm not sure why there are 2 but I guess that at least on of them is the public key RSA of the server.
  • The client creates a key that can be used for fast encryption (RC4 based).
  • This key is exported and encrypted with the public key of the server. The result is transfered to the server where it decrypts the RC4 key with his private RSA key.
  • I guess the next logical step would be the server creating so "welcome" packet and encrypting it with RC4. The client would then check if the welcome message from the server was valid (which means: the server had the private RSA key necessary for decrypting the RC4).

The big issue: We are not the server and we don't own the private key. This means there is no possibility to get this private key. So where is the point we could attack to break the encrpytion? Well, what if the client wouldn't use the original public key for encryption, but one that we created ourselves? This way we would have the public and the private key. Once we have the RC4 key we can encrypt and decrypt any of the data.

Next to that we need the possibility to decrypt the traffic between client and server of a production system. We could hack the CryptGenKey to dump the key it creates. This would enable us to look into the traffic that is happening between the client and the server.

Hope I didn't bore you to death. Thanks for reading.

Donnerstag, 19. Februar 2009

Here are a few packet analysis for:
1st packet from the client to the chatserver
Code:
0x0000 -> 0x0003: spacer sequence 00 00 00 00
0x0004 -> 0x0005: length 2c01 = 1, 2e01 = 2, 3001 = 3, 3201 = 4, 3601 = 6, 3801 = 7, 3e01 = 10
0x0006 -> 0x000d: spacer sequence 00 00 01 00 00 00 00 00
0x000e -> 0x0011: seems to be a weird encoding of unix time
0x0012 -> 0x0015: spacer sequence 00 00 00 00
0x0016 -> 0x0028: 061004_chat_fe_2_2
0x0029 -> 0x002A: length 1501 = 1, 1701 = 2, 1901 = 3, 1b01 = 4, 1f01 = 6, 2101 = 7, 2701 = 10
0x002B -> 0x0034: spacer sequence 00 00 02 00 00 40 04 00 00 00
0x0035 -> 0x0037: seems to be another? a weird encoding of unix time
0x0038 -> 0x0038: I(49) = start of subscription id
0x0039 -> 0x003a: subscription id in UTF-16
0x003b -> 0x0041: spacer sequence 00 02 01 00 00 81 00
0x0042 -> 0x0141: token we got from the login meta server
The encoded unix time looks like that:
Code:
2009-01-26   23:44:xx   469037df   d43c
2009-01-26 23:45:xx 46903705 fa3c
2009-01-26 23:46:xx 47903746 3b3d
2009-01-26 23:47:xx 47903780 753d
2009-01-26 23:47:xx 479037a1 963d
2009-01-26 23:48:xx 479037be b33d
2009-01-26 23:52:xx 489037c4 b93e
2009-01-26 23:53:xx 48903705 fa3e
2009-01-26 23:56:xx 49903790 853f
2009-01-27 00:00:xx 4a9037a6 9b40
2009-01-27 00:02:xx 4a9037ec e140
2009-01-27 00:03:xx 4b90373f 3441
2009-01-27 00:04:xx 4b903790 8541

1st packet from the client to the game server
Code:
0x0000-0x0029: UDP/IP connection from 192.168.1.4 TO 192.168.1.1:9000
0x002A-0x00AD: Spacer 00 00 00 00
0x00AE-0x00AF: Opcode for connect, I guess
0x0030-0x0037: pretty constant 00 00 01 00 00 00 00 00 ... maybe packet number to simulate TCP packet ordering?
0x0038-0x003B: seems pretty much a random number, needs investigation
0x003C-0x003F: Spacer 00 00 00 00
0x0040-0x009F: Pretty much constant, only changing bytes: 0x0080 and 0x008C-0x008E ... looks like these crappy timestamps we also see in the connect to the chat server
0x00A0-0x011F: No idea. Changes completely on every request. Something cyphered. Or the key. High entropy anyway
0x0120-0x0123: length of the following cyphered gls ticket ... 0x08 + len for all length <= 80, 0x09 after that. looks like the heading key gets one byte longer? little endian 8 bit atomic 0x0124-0x022C: cyphered gls ticket
So, what are the next logical steps? As I said earlier the chat server is pretty much irrelevant at this early stage. So we (or to be honest I) should focus on looking into the cyphered stuff. From what I can tell lotroclient.exe uses the crypt API provided by advapi32.dll. I think that we need to intercept the calls to this API to find out what exactly is happening. I tried to get uhooker to run but failed. Now I'm thinking about Wine on Windows or running LOTRO under Linux. This would enable us to run "WINEDEBUG=+crypt lotroclient.exe" to dump the method calls to advapi32.dll. The issue is: I could not get wine to run on windows or lotro to run on linux. And then emupedia went down and I got disappointed and stoped working on that for the last weeks :-)

Relavant DLL calls are:
CryptReleaseContext
CryptDecrypt
CryptEncrypt
CryptGenKey
CryptAcquireContextA
CryptGenRandom
CryptGetKeyParam
CryptExportKey
CryptImportKey
CryptGetProvParam
CryptDestroyKey

As you can see they are using a varity of functions. What I assume is the following:
  1. they are calling CryptAcquireContextA (note: This is the ASCII version!) to get access to the crypto apo
  2. they use CryptGenKey or CryptImportKey to get hold of a key to be used for the session
  3. they CryptEncrypt the payload of the package

I think that the most important point is: do they generate a key? or are they using a precomputed key? If the key is precomputed we are pretty much busted. I think they'd use a secure algorihm and a public key, meaning we'd have no possibility to generate the private key. We'd have to use an advapi32.dll that disables encryption and just returns the unecrypted payload. But replacing advapi32.dll is not trivial. The worst case would be: the generate a random key pair (symetric or asymetric) and encrypt that using the public key of the server and use the generated key for the communication in game. But that's most likely the case. This way you can safely exchange a simple symetric key for blazing fast encryption over a secure inital layer. The later communication would be done using the symetric key.
Whatever it is, we'll need to find out by tracing the calls to the crypto api.

Samstag, 24. Januar 2009

I had quite a journey today. I wanted to try the official client against my Java 6 based webservice server. I started it and it wouldn't work :-( After going forward and backward through the wireshark logs I found that the server wasn't closing the connection causing the client not to start. I thought: Let's do it in C# again. So I downloaded Visual Studio 2008 Express for C#. This version was not able to build Web Applications/Web Services. So I downloaded the version for Web Developers. After that I had to learn C# and Web Services in .NET. Both was really simple IMO.

Once I got the WSDL to 99% equality to the originial ones (I only have a nillable="true" where it's not in the originial WSDL) I started looking on how to host this. The "web server" coming with Visual Studio was randomly choosing ports when starting and server only one webservice at once. I don't have (and don't want to have) IIS. I tried XSP from the mono project ... but it was unable to host the service giving me a meaningless error code. So I picked apache with mod_aspdotnet. Took me quite some time to configure it but got it running.

Last but not least fiddeled with the client to use my meta server instead of the official one.

To make a long story short: Here are the instructions on getting your own meta server to run.

1.) Download http://www.megaupload.com/de/?d=XRQJJLD0
2.) Exctract it to C:\GLS (so that a directory C:\GLS\GLS.DataCenterServer exists)
3.) Download and install apache 2.2
4.) Download and install .NET 1.1
5.) Download and install mod_aspdotnet
6.) Append the following lines to the httpd.conf file form apache:

#ASP.NET

LoadModule aspdotnet_module modules/mod_aspdotnet.so
AddHandler asp.net asmx

AspNetMount /GLS.AuthServer "C:/GLS/GLS.AuthServer"
Alias /GLS.AuthServer "C:/GLS/GLS.AuthServer"

Options FollowSymlinks Indexes
AspNet files
Order allow,deny
Allow from all
DirectoryIndex default.htm default.aspx


AspNetMount /GLS.DataCenterServer "C:/GLS/GLS.DataCenterServer"
Alias /GLS.DataCenterServer "C:/GLS/GLS.DataCenterServer"

Options FollowSymlinks Indexes
AspNet files
Order allow,deny
Allow from all
DirectoryIndex default.htm default.aspx


7.) Change the section from
Deny from all
to
Allow from all
(Note that this is an insecure hack!)

8.) Download http://www.megaupload.com/de/?d=1D6BISD0 and extract it into the htdocs folder of your apache
9.) Start apache
10.) Add the following line (containing the IP of your server first) to C:\WINDOWS\system32\drivers\etc\hosts

123.456.789.012 lotroeugls.com ddoeugls.com

11.) Optional: Open the firewall port 80 on your server
12.) Start your client
13.) Wait if it will ask you for a username/password
14.) Enter whatever you like as long as it's both longer than 6 chars
15.) See "[XY] My Own"
16.) Be happy

Downsides right now:
- There is no in game! I repeat: NO INGAME.
- Im totally new to C# so the code could be really, really, really bad
- The only server available right now is hardwired to 192.168.1.145. You can change it to whatever you like.
- I haven't tested to get in game, as there is no in game yet.
- It's easy to break things.
- Not shipping a complete htdocs, news for most languages are missing.

Freitag, 23. Januar 2009

Initial steps

The game is at least split up into 2 major parts.

1.) The authentication part
2.) The game itself

Let's look at what each of it is.

The first part is what you see in the launcher. The launcher is written in .NET. It uses web services over HTTPS to authenticate. Let's call this first part meta server, as it provides access to the real servers. To identify the funtionality of the web services I'd like to redirect you to

https://gls.lotro.com/GLS.AuthServer/Service.asmx

and

https://gls.lotro.com/GLS.DataCenterServer/Service.asmx

These contain pretty good definitions of what needs to be done to authenticate. I'll explain to you in a few words what client does.

1.) It starts up and and reads TurbineLauncher.exe.config. This file contains information where the web services are located.
2.) It verifies the client. I'm not exactly sure what happens in this step and I don't think that it's relevant for us.
3.) It asks you for username/password and sends this using HTTPS-POST to the LoginAccount from https://gls.lotro.com/GLS.AuthServer/Service.asmx. This will generate you a 256 byte long ticket. This will be used later. It will also return information about your account (which games do you own, when do you need to pay next time, etc.)
4.) It gets the list of data servers from GetDatacenters from https://gls.lotro.com/GLS.DataCenterServer/Service.asmx This list will be presented in the client.
5.) It updates the status of the servers by querying a xml file for each of these.
6.) You select one server and start the actual game. The job of meta server does not end here, but the client won't talk to it again, but the game server (verifying the ticket). Maybe the client is doing ticket refreshes, but I'm not sure about that.

Reverse engineering this is pretty trivial. Most of my information is based on http://ubuntuforums.org/showpost.php...8&postcount=30 and my personal experiments. You just need to implement a dummy client for them and invoke the webservices to get some details. Then you write your own meta server and use the official client to test it. I did that in about 4 hours using Java 6 and JAX-WS RI. As I said: This part is trivial. You can even use the linux launcher from http://www.lotrolinux.com/ to see how to write a C# client for it.


The interesting part is the actual game. From what I found out so far the following happens:

1.) The client sends encrypted data to "/stateless" using HTTP1.1, port 5015 of the server you are going to connect to. The server replies some encrypted data. This ping pong goes on for quite about half a second. I have no idea what they are doing. From my manual timings this is the actual login. I'm not sure how the data is encrypted. I'm totally unsure what happens there and need to further look at it.

2.) After the login (which I think it is) the client initiates a connection to the chat server, UDP port 2900. From what I understood so far the initial packet contains the ticket you got from the meta server. The following packets don't seem to be encrypted. They seem to contain mostly plain text in UTF-16. For example I could find the name of my character in it, or the name of my guild. Or there was a request from my client containing "Friends" that returned several packets in which I could find the names of my friends. I guess the protocol should be rather simple. It basically looks like they are mimicing something like TCP windows over UDP for data. Simple requests can return many UDP packets. This might be interrupted by the server waiting for a single and simple packet. Which results the server in continuing. The client and the server start to play ping pong after a while which looks like a keep alive to me. The ping pong is sometimes interrupted by chat messages. These messages always have an indicator which channel they belong to, like "Guild" followed by a HEX number after them like 0x0123456789ABCDE to indicate which guild it was (maybe). There are also "welcome" packates indicating a friend of you got online. These packets contain the name of your friend followed by an "[F]" which might indicate the friend status.

3.) The game protocol seems to be encrypted. It does seem to start with valid IP protocol followed by invalid packets (which might be caused by the encryption) which don't contain a correct source or destination IP. I'm not sure if this is the fault of my dump or the dumping tool I used or because of encryption or because of efficient network programming or because of my router or because of whatever. It seems to use a different IP as the chat server and use UDP ports like 9000 or 9004. I really need to find out what is happening there as I don't really have a clue right now.


Anyway I think this information is a good starting point of understanding the fundamental concepts and how the work together. It will hopefully clear some confusion of what is real and what is wrong. Please note: NO, I DON'T HAVE A WORKING LOTRO SERVER.


Note:
But yes, I have a working dummy meta server (that is allowing me to login in using hard coded credentials and displaying my news and showing my server as the only available game server)


Let's look at some concrete info. I used http://www.megaupload.com/?d=JQZQC3ZU and opened it using wireshark. I'll write down my analysis of it here referering the line numbers and what they mean.

1-12: Get list of data centers
13-24: Get the launcher configuration
25-37: Get the news for the client (26 is garbage)
38: garbage
39-156: Get data necessary for displaying in the launcher
157-166: Get the list of necessary apps that need to be installed to run the game
167-361: Just figured out that this is about patching the client! It used the server specified in the GetDatacenters webservice call. The value is the "94.75.194.24:5015". I think we can skip over this as there are few possibilities that we can reverse engineer the patch server.
362-369: Read the server status before connecting to it.
370-386: This must be the HTTPS login to get the ticket.
387-489: Read the status of all the servers
490-502: Register the user in the login queue (look at 495). It uses the info where the queue url is from http://10.64.102.84:7080/LoginQueue;http://10.64.102.85:7080/LoginQueue; from the file /status/cache_SERVERNAME.xml
503-517: Major headache ... what does it do?
518: garbage
519-521: These packets are sent against an IP from "94.75.194.139:9000;94.75.194.140:90 00;" from /status/cache_SERVERNAME.xml. I think it's a kind of handshake.
522-568: These seem to be the login packets. These are the really interesting packets we need to figure out.
569-...: This is starting the communication to the chat server. The IP is taken from "94.75.194.43:2900" of GetDatacenters webservice reply.

All following communication to 94.75.194.43:2900 is the chat. If we can believe what the videos at http://www.wowemu.net/lotro/ it is not necessary to reverse engineer the chat server first. We should focus on packages from/to 94.75.194.140:9000 (and the other packets to bogus IP addresses with port 9000 as source or destination). The relevant packages are marked as protocol "IP" in the log. The chat is marked as "UDP". This helped me finding my way faster through the packets.

I highlighted the sections that we really need to figure out. My current issue is that I'm not sure how the malformed IP packets should be read. From my understanding a packet can only reach my system (especially through a router) if the IP protocol is valid. And it really wonders me how these malformed packets can reach my system.

I think this log ends somewhere in the middle of the game. At least my logs show more packets after the game was shut down from the chat server.

Mittwoch, 21. Januar 2009

What it's all about

Its about reverse engineering lotro. Not the client part, but the server. So this blog is about the attempt to document enough of the internals of the LOTRO (lord of the rings online) server to be able to reverse engineer it. Right now there is no valuable information about the game so I will post it here.