NOTE: THIS DEVELOPER BRANCH IS THE NEW PREGEN SLPROXY. THIS DOCUMENT MAY NOT BE UP TO DATE WITH THE NEW VERSION. Public Source - Release Canidate 1 MODIFICATIONS BY "ALPHA ZAIUS" / ANDREW ORTMAN. QODE IN EFNET: #LIBSL AND #LIBSL-DEV SPECIAL THANKS TO "mcortez" IN EFNET (#libsl-dev) FOR A HELPFUL PATCH TO ANALYST AND SLPROXY-PREGEN TO KICKSTART ITS TESTING. --------------------------------------------------------------------- SLProxy is a library that works in conjunction with libsecondlife to allow applications to wedge themselves between the official Second Life client and servers. SLProxy applications can inspect and modify any packet as it passes between the client and the servers; remove packets from the stream; and inject new packets into the stream. SLProxy automatically takes care of tracking circuits and modifying sequence numbers and acknowledgements to account for changes to the packet stream. The continued existence of this software of course rests on the good will of Linden Lab toward the Second Life reverse engineering effort. Please use common sense when designing applications and report any security holes you may find to security@lindenlab.com. To use an SLProxy application, you must first start the proxy, then start Second Life with the switch `-loginuri http://localhost:8080/'. In Windows, add this switch (without the quotes) to your Second Life shortcut. In MacOS X, this can be accomplished by sending the following commands to the Terminal (assuming Second Life is installed in /Applications): cd "/Applications/Second Life.app" "Contents/MacOS/Second Life" -loginuri http://localhost:8080/ Note that for security reasons, by default, SLProxy applications must be running on the same computer as Second Life. If you need to run a proxy on a different machine or port, start the proxy with the --proxy-help switch and see the options available. SLProxy can only handle one client using a proxy at a time. BUILDING ======== To build SLProxy, you must check out the entire libsecondlife trunk with subversion: svn co svn://svn.gna.org/svn/libsecondlife/trunk libsecondlife The libsecondlife-cs project must be built first; see libsecondlife-cs/README for instructions. Building SLProxy should be straightforward with Microsoft Visual Studio. If you're using Mono, you can build the solution with the included build script: perl build The SLProxy library and its example applications will be built in bin/Debug. In order to run the example applications, you must first add the libsecondlife-cs build directory to your MONO_PATH environment variable. For example, if your libsecondlife-cs directory is ~/libsecondlife/libsecondlife-cs and your shell is bash, you can type: export MONO_PATH=$MONO_PATH:~/libsecondlife/libsecondlife-cs/bin/Debug/ INCLUDED APPLICATIONS ===================== Included with SLProxy are a few example application, which are covered in this section. 1. Analyst ---------- Analyst makes SLProxy's packet inspection and modification functionality interactive. When connected to Second Life through Analyst, you use the following commands by saying them using in-world chat: /log Packets of type will be dumped to the console. For example, say `/log ChatFromSimulator' to get a packet dump of all incoming chat. /-log Packets of type will no longer be dumped to the console. /log * All packets will be dumped to the console. /-log * No packets will be dumped to the console. /grep [regex] Only log packets that have a field for which regex matches .. = . To stop filtering, type /grep without an argument. Matches are case insensitive. In the case of a variable field, Analyst will try to convert it into a string; if that doesn't match, it will try converting it into a hexidecimal numeral preceeded by 0x. /set All forthcoming packets of type will have the field identified by and set to . For example, if you say `/set ChatFromViewer ChatData Type 0', everything you say thereafter will be whispered. Values for variable fields will be interpreted as strings unless they begin with a 0x, in which case they will be treated as hexidecimal numerals representing the contents of the field. /-set Packets of type will no longer have the field identified by and modified. /-set * No fields will be modified. /inject [value] Inject the packet described by .packet in the working directory. The [value] is optional and may be required by some packet files. `/in' is an alias for `/inject'. The syntax of a packet file is described in section 2.1. SLProxy comes with two example packet files: god.packet allows you to enable hacked god mode by typing `/inject god', and whisper.packet allows you to whisper by typing `/inject whisper '. These commands will not be forwarded to the server, so other people won't hear you say them. Analyst accepts a --log-all command line switch, which causes the proxy to start out logging all packets as if you had typed `/log *'. This can be useful if you want to capture a complete dump of your session, including login. Analyst also accepts a --log-login command line switch, which causes the XML-RPC login request and response to dumped to the console. 2.1 Packet files - - - - - - - - A packet file describes a packet that can be injected with the /inject command. Please refer to god.packet and whisper.packet (in the bin/Debug/ directory) as examples. The first line of a packet file must contain the word `in' or `out', specifying whether the packet is incoming or outgoing, respectively, followed by the name of the packet. The remainder of the file specifies the packet's blocks and fields. A block is described by placing its name in square brackets (e.g. `[GrantData]'). Following the the line specifying the block's name, the block's fields and values are specified, separated by equal signs (e.g. `GodLevel = 255'), one per line. The value of a field can be a literal value (e.g. `255'), or one of the following special values: $Value the [value] specified by the user $UUID a random UUID $AgentID the user's AgentID $SessionID the user's SessionID 2. ChatConsole -------------- ChatConsole is a trivial SLProxy application intended as an example of how SLProxy applications can be written. When connected to Second Life through ChatConsole, all in-world chat will be echoed to the console, and anything typed in the console will be echoed to the game as in-world chat. PUBLIC INTERFACE ================ This section describes the interface that SLProxy applications will use to interact with the packet stream. Please see ChatConsole.cs for a simple example of how this interface can be used. SLProxy extends the functionality of libsecondlife, so we assume here that the reader is already familiar with libsecondlife's Packet and PacketBuilder classes. 1. ProxyConfig class -------------------- An instance of ProxyConfig represents the configuration of a Proxy object, and must be provided when constructing a Proxy. ProxyConfig has two constructors: ProxyConfig(string userAgent, string author) ProxyConfig(string userAgent, string author, string[] args) Both constructors require a user agent name and the author's email address. These are sent to Second Life's login server to identify the client, and to allow Linden Lab to get in touch with authors whose applications may inadvertantly be causing problems. The second constructor is preferred and takes an array of command-line arguments that allow the user to override certain network settings. For a list of command line arguments, start your appliation with the --proxy-help switch. 2. Proxy class -------------- The Proxy class represents an instance of an SLProxy and provides the methods necessary to modify the packet stream. Proxy's sole constructor takes an instance of ProxyConfig. 2.1 Login delegates - - - - - - - - - - You may specify that SLProxy should call a delegate method in your application when the user requests login or the server responds. delegate void XmlRpcRequestDelegate(XmlRpcRequest request) delegate void XmlRpcResponseDelegate(XmlRpcResponse response) void SetLoginRequestDelegate(XmlRpcRequestDelegate loginRequestDelegate) void SetLoginResponseDelegate(XmlRpcResponseDelegate loginResponseDelegate) A login response delegate, in particular, is useful for retrieving the agent_id and session_id, which are required when injecting certain types of packets. See ChatConsole.cs for an example of how these can be retrieved. Note that all delegates must terminate (not go into an infinite loop), and must be thread-safe. 2.2 Packet delegates - - - - - - - - - - Packet delegates allow you to inspect and modify packets as they pass between the client and the server: delegate Packet PacketDelegate(Packet packet, IPEndPoint endPoint) void AddDelegate(string packetName, Direction direction, PacketDelegate packetDelegate) void RemoveDelegate(string packetName, Direction direction) AddDelegate adds a callback delegate for packets named packetName going direction. Directions are either Direction.Incoming, meaning packets heading from the server to the client, or Direction.Outgoing, meaning packets heading from the client to the server. Only one delegate can apply to a packet at a time; if you add a new delegate with the same packetName and direction, the old one will be removed. RemoveDelegate simply removes the delegate for the specified type of packet. PacketDelegate methods are passed a copy of the packet (in the form of a libsecondlife Packet object) and the IPEndPoint of the server that sent (or will receive) the packet. PacketDelegate methods may do one of three things: 1. Return the same packet, in which case it will be passed on. 2. Return a new packet (built with libsecondlife), in which case the new packet will substitute for the original. SLProxy will automatically copy the sequence number and appended ACKs from the old packet to the new one. 3. Return null, in which case the packet will not be passed on. SLProxy automatically takes care of ensuring that sequence numbers and acknowledgements are adjusted to account for changes made by the application. When replacing a reliable packet with an unreliable packet or removing a reliable packet, a fake acknowledgement is injected. When replacing an unreliable packet with a reliable packet, SLProxy ensures delivery and intercepts its acknowledgement. Note that if a reliable packet is passed on but then lost on the network, Second Life will resend it and the delegate will be called again. You can tell if a packet is being resent by checking if (packet.Data[0] & Helpers.MSG_RESENT) is nonzero, although be advised that it's possible that the original packet never made it to the proxy and the packet will be marked RESENT the first time the proxy ever sees it. Note that all delegates must terminate (not go into an infinite loop), and must be thread-safe. 2.3 Packet injection - - - - - - - - - - New packets may be injected into the stream at any point, either during a delegate callback or by another thread in your application. Packets are injected with the InjectPacket method: void InjectPacket(Packet packet, Direction direction) This will inject a packet heading to either the client or to the active server, when direction is Direction.Incoming or Direction.Outgoing, respectively. The packet's sequence number will be set automatically, and if the packet is reliable, SLProxy will ensure its delivery and intercept its acknowledgement. Injecting a packet immediately upon (or prior to) connection is not recommended, since the client and the server won't have initialized their session yet. 2.4 Starting and stopping the proxy - - - - - - - - - - - - - - - - - - Once you've constructed a Proxy and added your delegates, you must start it with the Start method: void Start() Once started, the proxy will begin listening for connections. The Start method spawns new threads for the proxy and returns immediately. When your application is ready to shut down, you must call the Stop method: void Stop() Note that this may not actually force the proxy to stop accepting connections; it merely guarantees that all foreground threads are stopped, allowing the application to exit cleanly. 3. PacketUtility class ---------------------- The PacketUtility class provides a handful of static methods which may be useful when inspecting and modifying packets. 3.1 Hashtable Unbuild(Packet packet) - - - - - - - - - - - - - - - - - - The Unbuild method takes a Packet object and returns a table of blocks, structured in a format suitable for passing to PacketUtility's GetField and SetField methods or PacketBuilder's BuildPacket method. For example, this should make an approximate copy of a packet: Hashtable packetBlocks = PacketUtility.Unbuild(packet); Packet packetCopy = PacketBuilder.BuildPacket(packet.Layout.Name, protocolManager, packetBlocks, packet.Data[0]); 3.2 object GetField(Hashtable blocks, string block, string field) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The GetField method takes a table of blocks (produced by PacketUtility.Unbuild) and extracts the value of a particular field. If the field is part of a variable block, an arbitrary instance of the field will be returned. If the field does not exist, null will be returned. 3.3 void SetField(Hashtable blocks, string block, string field, object value) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The SetField method takes a table of blocks (produced by PacketUtility.Unbuild) and sets the value of a particular field. If the field is part of a variable block, all instances of the field will be set. If the field does not exist, SetField will have no effect. This can be used by a packet delegate method in conjunction with PacketUtility.Unbuild and PacketBuilder.BuildPacket to substitute a new packet that is a copy of the original packet with certain fields modified.