Hegemons Login Analysis
This page is intended to share/organize the information I learn while tinkering with the Second Life client now that its been GPLed.
I am not a guru, developer etc... I'm just playing around as a learning experience.
I am attempting to create an extremely basic server that just allows the Client to login, hopefully to a empty null world and maybe send some very basic information such as the suns position, area name, amount of money, 1 or 2 prims, flat terrain etc... I want to create it from scratch without relying on the SL viewer code or the libsecondlife stuff to ensure i understand everything. I would also like the server to handle all the elements of the login, ie not using a web server for the login stuff.
So far the server allows the completion of step 1 of the Authentication Flow
Its not very advanced or prwdy and works by parroting the clients requests back to it, this is enough of a hack to trick the client into continuing however incorrect sequence numbers etc are sent.
Stuff used: netcat, wireshark(Ethereal), C#/Mono/Monodevelop
I'm doing this under Ubuntu but any form of Linux should be fine (theres also a port of SL on Solaris now apparently), netcat and wireshark have windows ports also. I have never actually learned C#, but i have done Java, C and C++ so with the help of Google its fairly easy to work it out as I go. I chose C# as I haven't used it much previously, its also being incorporate into SL for the scripting language and libsecondlife appears to be written in it.
Step 1.
The Authentication Flow just describes this as:
Viewer(client)--------->User Server * Secure Message checkum request - Protocol Level Supported? Yes/No. * Port 12036
which doesn't really give us an in depth description of everything thats happening or enough to make a server that does what is required, probably the quickest way is to analyses what the client does and try to emulate the servers responses.
Redirecting the client
Firstly the client needs to be started with the correct command line parameter (found here). This tells the client to send the initial UDP packets to the localhost.
./secondlife -user localhost
Then netcat need to be set to listen on UDP port 12036:
hegemon@ender:~$ sudo nc -u -l -p 12036 @������+U_{q�Y)$�ͫ `������+U_{q�Y)$�ͫ @������+U_{q�Y)$�ͫ ����`������+U_{q�Y)$�ͫ `������+U_{q�Y)$�ͫ @������+U_{q�Y)$�ͫ ���`������+U_{q�Y)$�ͫ @������+U_{q�Y)$�ͫ ���`������+U_{q�Y)$�ͫ `������+U_{q�Y)$�ͫ `������+U_{q�Y)$�ͫ �����+U_{q�Y)$�ͫ ���`������+U_{q�Y)$�ͫ �����+U_{q�Y)$�ͫ[[]]
This successfully makes the client send its response to localhost, its then not receiving anything back and disconnecting. The outputted information isn't very usefull so the next step is to fireup wireshark, set it to listen on the loopback interface and find out whats being sent (Alternatively netcat has the ability to output a hexdump also with the -o flag, its also possible to pipe the output to the 'hexdump' unix command however wireshark is probably nicer and can be used to analyze the client connecting to the actual server for comparison later).
A timeout analysis
Raw Dump
Wireshark gives us:
Client>Server 0000 00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00 ........ ......E. 0010 00 34 00 00 40 00 40 11 b7 62 c0 a8 01 03 c0 a8 .4..@.@. .b...... 0020 01 03 94 ad 2f 04 00 20 83 88 40 00 00 01 ff ff ..../.. ..@..... 0030 ff fa 2f c7 ac c1 79 ca f6 24 2a bc 12 e6 36 48 ../...y. .$*...6H 0040 be 04 .. 0000 00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00 ........ ......E. 0010 00 34 00 00 40 00 40 11 b7 62 c0 a8 01 03 c0 a8 .4..@.@. .b...... 0020 01 03 94 ad 2f 04 00 20 83 88 60 00 00 01 ff ff ..../.. ..`..... 0030 ff fa 2f c7 ac c1 79 ca f6 24 2a bc 12 e6 36 48 ../...y. .$*...6H 0040 be 04 .. 0000 00 00 00 00 00 00 00 00 00 00 00 00 08 00 45 00 ........ ......E. 0010 00 34 00 00 40 00 40 11 b7 62 c0 a8 01 03 c0 a8 .4..@.@. .b...... 0020 01 03 94 ad 2f 04 00 20 83 88 40 00 00 02 ff ff ..../.. ..@..... 0030 ff fa 2f c7 ac c1 79 ca f6 24 2a bc 12 e6 36 48 ../...y. .$*...6H 0040 be 04 ..
Description
These are the 1st 3 packets, there where around 16 in total but its the same thing over and over.
So now we begin the packet analysis. We are only interested in the Data part of the packets, the first 42 bytes contains information such as source and destination ports/addresses, protocol information etc... which is what routers use to determin where to push these things around the internet to get it to your computer. This is shown in wireshark, so just select the data which highlights the following data packets.
Packet info
40 00 00 01 ff ff ff fa 2f c7 ac c1 79 ca f6 24 2a bc 12 e6 36 48 be 04 60 00 00 01 ff ff ff fa 2f c7 ac c1 79 ca f6 24 2a bc 12 e6 36 48 be 04 40 00 00 02 ff ff ff fa 2f c7 ac c1 79 ca f6 24 2a bc 12 e6 36 48 be 04
You will notice there are only 2 bytes that are changing, the 1st and 4th, this is because there is no response so the client will keep resending the same data until there is one or it gives up.
By looking at the packet layout we can start to see what is being sent. There is also the libsecondlife analysis.
The first byte is the byte contains the bitmask flags, LL_ZERO_CODE_FLAG 0x80, LL_RELIABLE_FLAG 0x40, LL_RESENT_FLAG 0x20, LL_ACK_FLAG 0x10. To understand exactly whats happening in the first byte its best to look at it in binary. In the first packet we have 0x40, 4 in binary is 1000 and 0 is obviously 0000, which is 10000000, the first bit is 1 which means it wants an ack packet back so that it knows it was successfully received, if none is returned it will resend the packet, which is what packet 2 is.
The first byte in packet 2 is 0x60, in binary 10100000, in this packet the first and 3rd bits are set, so it wants an ack and it is a packet that has been resent. The 2rd packet is 0x40 again so its a new packet. not a resent one.
The next 3 bytes are marked as the sequence number, 000001, the first packet sent, then it is repeated in the resent packed and when the client decides to send a new packet rather then resend it increments it to 2. When an ack is sent back I believe it uses this sequence number for the ID, which tells the client it was received and doesn't need it to be resent.
The next bytes are listed as the 'frequency', its really means the messageID so the system knows the difference between a request for the version information and a request to close the connection, they are defined in the message template file /linden/scripts/messages/message_template.msg in the viewers source code, they are also available from libsecondlife here and you can also find the individual ones on this wiki here, there are 6 'fixed' ones shown near the top, we are encountering 0xFFFFFFFA which is the RequestSecureChecksum, we can check the template which shows that the packet only contains a LLUUID, that is the next 16 bytes and is a unique number for our client which is the remaining bytes. This is the client check to make sure the server is talking the same protocol version.
A majority of the messages are not 'fixed' ones. The number of bytes that makes the frequency is variable, so if a byte isn't 0xFF then that byte is the last byte in the frequency. Currently I'm not sure how to convert the frequency number to the specific message, or workout the checksum as it appears the client loads the template file in (probably at compile time since it doesn't appear to be in the compiled client), and docs say it assigns the numbers at runtime, but the fixed ones are enough for the first login step and we can use what we know is a valid checksum number by asking the client for its checksum.
Valid Response
Lets looks at a valid response by sniffing an actual connection to the server
Raw
Client: 0000 00 e0 a0 a6 66 70 00 18 f3 04 90 4c 08 00 45 00 ....fp.....L..E. 0010 00 34 00 00 40 00 40 11 41 e0 c0 a8 01 03 42 96 .4..@.@.A.....B. 0020 f4 97 94 6e 2f 04 00 20 2f e6 40 00 00 01 ff ff ...n/.. /.@..... 0030 ff fa 16 89 02 b0 75 38 50 f1 8f 41 fe a7 6e 9a ......u8P..A..n. 0040 f7 98 .. Server: 0000 00 18 f3 04 90 4c 00 e0 a0 a6 66 70 08 00 45 00 .....L....fp..E. 0010 00 40 72 bf 40 00 2d 11 e2 14 42 96 f4 97 c0 a8 .@r.@.-...B..... 0020 01 03 2f 04 94 6e 00 2c 14 38 00 00 00 01 ff ff ../..n.,.8...... 0030 ff ff 00 02 c4 e4 95 9d 00 00 00 00 01 0d 16 89 ................ 0040 02 b0 75 38 50 f1 8f 41 fe a7 6e 9a f7 98 ..u8P..A..n... Server: 0000 00 18 f3 04 90 4c 00 e0 a0 a6 66 70 08 00 45 00 .....L....fp..E. 0010 00 29 72 c3 40 00 2d 11 e2 27 42 96 f4 97 c0 a8 .)r.@.-..'B..... 0020 01 03 2f 04 94 6e 00 15 42 79 00 00 00 02 ff ff ../..n..By...... 0030 ff fb 01 01 00 00 00 00 00 00 00 00 ............ Client: 0000 00 e0 a0 a6 66 70 00 18 f3 04 90 4c 08 00 45 00 ....fp.....L..E. 0010 00 24 00 00 40 00 40 11 41 f0 c0 a8 01 03 42 96 .$..@.@.A.....B. 0020 f4 97 94 6e 2f 04 00 10 43 82 00 00 00 02 ff ff ...n/...C....... 0030 ff fd ..
Info
In a slightly more readible form:
/-Flags | | /-Sequence Number | | | | /-Frequency | | | +- +--+---+ +----+----+ <<<<<<<<<<<<<<<<<<<<<<<<< DATA >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 40 00 00 01 ff ff ff fa 16 89 02 b0 75 38 50 f1 8f 41 fe a7 6e 9a f7 98 <--- DATA is LLUID 00 00 00 01 ff ff ff ff 00 02 c4 e4 95 9d 00 00 00 00 01 0d 16 89 02 b0 75 38 50 f1 8f 41 fe a7 6e 9a f7 98 00 00 00 01 ff ff ff fb 01 01 00 00 00 00 00 00 00 00 \--\ \-------------------------------------------/ 00 00 00 02 ff ff ff fd \ \--------\ \--\--\--\--\ \ LLUUID-/ \ \ \ \-Version info at this end? \ \-ACK_ID \ \ (reversed) \-5 Trailing Zeros, not actually part of data \ \-Number of ACKs in message
Description
In english please?:
Very simple C# server
// This just listens on the port and sends back the packets to the client. It done some basic analysis of the packets // Its very horrible code using System; using System.Net; using System.Net.Sockets; namespace SLLoginTest { public class Slog { public Slog() { int recv; byte[] data = new byte[1024]; // Create the listening server socket... Socket socServer = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp); IPEndPoint ipServer = new IPEndPoint(IPAddress.Any, 12036); // Bind to local Address... socServer.Bind(ipServer); IPEndPoint ipClient = new IPEndPoint(IPAddress.Any, 0); EndPoint Remote = (EndPoint)(ipClient); // FIXME: These should be defines or static finals or whatever C# has byte MASK_LL_ZERO_CODE_FLAG = 0x80; byte MASK_LL_RELIABLE_FLAG = 0x40; byte MASK_LL_RESENT_FLAG = 0x20; byte MASK_LL_ACK_FLAG = 0x10; while(true) { data = new byte[1024]; Console.WriteLine("Awaiting packets..."); recv = socServer.ReceiveFrom(data, ref Remote); Console.WriteLine("{0} bytes recived...", recv); int seek = 0; int i = 0; // Dump the raw data Console.Write("DATA:"); for(int j = 0; j<recv; j++) { Console.Write(" 0x{0:X2} ", data[j]); } Console.WriteLine(); // This bit checks the packet flags bool LL_ZERO_CODE_FLAG, LL_RELIABLE_FLAG, LL_RESENT_FLAG, LL_ACK_FLAG; if((byte)(data[0] & MASK_LL_ZERO_CODE_FLAG) != 0) { LL_ZERO_CODE_FLAG = true; Console.WriteLine("Zero encoded"); } if((byte)(data[0] & MASK_LL_RELIABLE_FLAG) != 0) { LL_RELIABLE_FLAG = true; Console.WriteLine("Reliable"); } if((byte)(data[0] & MASK_LL_RESENT_FLAG) != 0) { LL_RESENT_FLAG = true; Console.WriteLine("Resent"); } if((byte)(data[0] & MASK_LL_ACK_FLAG) != 0) { LL_ACK_FLAG = true; Console.WriteLine("ACK"); } seek++; // Move to the next byte // Read the sequence number into a 3 byte array byte[] sequence = new byte[3]; sequence[0] = data[seek++]; sequence[1] = data[seek++]; sequence[2] = data[seek++]; Console.WriteLine("Sequence: 0x{0:X2}{1:X2}{2:X2}", sequence[0], sequence[1], sequence[2]); byte[] frequency = new byte[4]; byte freqByte = data[1]; // Add each 0xFF byte until we reach something thats not 0xFF // FIXME: Should stop at 4 bytes do { frequency[i] = data[seek]; i++; seek++; } while(data[seek] == 0xFF); // We add the final frequency byte, unless it was already added in the case of 0xFFFFFFFF if(i < 4) { frequency[i++] = data[seek++]; } // Print the frequency Console.Write("Frequency: 0x"); for(int j = 0; j < i; j++) { Console.Write("{0:X2}", frequency[j]); } Console.WriteLine(); // These are the basic templaces (Theres 2 more not listed here) byte[] SecureTemplateChecksumRequest = {0xFF, 0xFF, 0xFF, 0xFA}; byte[] TemplateChecksumReply = {0xFF, 0xFF, 0xFF, 0xFF}; byte[] Ack = {0xFF, 0xFF, 0xFF, 0xFB}; byte[] CloseCircuit = {0xFF, 0xFF, 0xFF, 0xFD}; Console.Write("ST: 0x"); for(int j = 0; j < 4; j++) { Console.Write("{0:X2}", SecureTemplateChecksumRequest[j]); } Console.WriteLine(); // Check to see which message was send // This isn't a good way to handle it, it assumes a 4byte frequency and may crash // I haven't bothered to check them all either if(frequency[3] == SecureTemplateChecksumRequest[3]) { Console.WriteLine("SecureTemplateChecksumRequest"); } else if(frequency[3] == CloseCircuit[3]) { Console.WriteLine("CloseCircuit"); } // By sending the packet back to the client we can fake the start of the login // // 1. V>S SecureTemplateChecksumRequest? - Viewer asks for the servers checksum // 2. S>V SecureTemplateChecksumRequest? - Server asks for the viewers checksum // 3. V>S TemplaceChecksumReply - Client replys to request 2 // 4. S>V TemplaceChecksumReply - Sending the clients on checksum back, answers request in 1 // 5. V>S CloseCircuit - The client has checked it matches the checksum and closes // 6. S>V CloseCircuit - Parroted back (probably useless/ignored by client) Console.WriteLine("Sending parrot response."); socServer.SendTo(data, recv, SocketFlags.None, Remote); } } } }
Step 2
Now that the client passes step 1, it will now attempt to access the login page using XML-RPC calls.
We can override the website with the -loginuri command. Normally the SL client will want to use encrypted TSL 1.0 but we can specify a nonencrypted url.
./secondlife -user ender -loginuri http://ender/
Then netcat can listen on port 80 and we see the following
POST / HTTP/1.1 Host: localhost Pragma: no-cache Accept: */* Accept-Encoding: deflate, gzip Content-Type: text/xml Content-Length: 1825 <?xml version="1.0"?><methodCall><methodName>login_to_simulator</methodName><params><param><value><struct><member><name>first</name><value><string>Hegemon</string></value></member><member><name>last</name><value><string>Skall</string></value></member><member><name>passwd</name><value><string>$1$4534d5a3257d801ba927ce39bca33aab</string></value></member><member><name>start</name><value><string>last</string></value></member><member><name>major</name><value><string>1</string></value></member><member><name>minor</name><value><string>13</string></value></member><member><name>patch</name><value><string>2</string></value></member><member><name>build</name><value><string>57209</string></value></member><member><name>platform</name><value><string>Win</string></value></member><member><name>mac</name><value><string>b5fcfb83e74517f2a8be6092995c51c7</string></value></member><member><name>id0</name><value><string/></value></member><member><name>viewer_digest</name><value><string>2bd23799-b279-8665-500a-ce27025b315e</string></value></member><member><name>options</name><value><array><data><value><string>inventory-root</string></value><value><string>inventory-skeleton</string></value><value><string>inventory-lib-root</string></value><value><string>inventory-lib-owner</string></value><value><string>inventory-skel-lib</string></value><value><string>initial-outfit</string></value><value><string>gestures</string></value><value><string>event_categories</string></value><value><string>event_notifications</string></value><value><string>classified_categories</string></value><value><string>buddy-list</string></value><value><string>ui-config</string></value><value><string>login-flags</string></value><value><string>global-textures</string></value></data></array></value></member></struct></value></param></params></methodCall>
No thats not my real password :)
The libsecondlife login page has more information about the valid responses, pasting one of those into netcat should yield some results, looks like you could use it to make custom messages pop up :)
To be continued
How to get the checksum/non fixed messageIDs
Step 3
This is a server>server bit, it basically checks the password is correct, initially it won't have any security and will just auto accept the client or lookup a hardcoded value. If i get things more advanced maybe a SQL database lookup.
Another idea would be to use OpenID, the firstname would be the id and the last would be the OpenID server (the password could be anything). OpenID would be a good choice for 3rd party SL servers as it wouldn't be to hard to transfer the player from one server to another when the servers don't trust each other.
Step 4,5
Another Server>Server, This is intended to find which sim in the grid to use and autorize, since this is just a basic self contained server it will be itself and this can just be skipped. It will need to tell the client that though.
Step 6
Tell the client the sim info and other basic information.
Step 7,8
Client sends server information, if possible skip most of it.