####################################################################### Luigi Auriemma Application: Lithtech engine (new network protocol) http://www.lithtech.com Games: Contract Jack <= 1.1 F.E.A.R. <= 1.01 / 1.02 No one lives forever 2 <= 1.3 Tron 2.0 <= 1.042 ... others? Platforms: Windows and Mac Bug: socket unreachable Exploitation: remote, versus server Date: 13 December 2004 Author: Luigi Auriemma e-mail: aluigi@autistici.org web: aluigi.org ####################################################################### 1) Introduction 2) Bug 3) The Code 4) Fix ####################################################################### =============== 1) Introduction =============== The Lithtech engine is a game engine used by many games. Some of the latest games released and based on this engine use a network protocol different than all the others (probably they use a new version of the engine but naturally I don't know all these details). Just these latest games (all developed by Monolith) are those affected by the bug I'm going to describe: Contract Jack (Nov 2003), No one lives forever 2 (Oct 2002) and Tron 2.0 (Aug 2003), but possibly others too. F.E.A.R and its 1.01 patch have been released in October 2005 and are vulnerable too. ####################################################################### ====== 2) Bug ====== The new network protocol used by the Lithtech engine is composed by a loop used to handle all the UDP packets received. A select() function with a time out of 30 seconds searchs for new data into the socket's queue. If data is received or the socket goes in time out, a recvfrom() is called and its return value is checked to know if has happened an error. If there is a socket error, the game calls WSAGetLastError() to catch the error code and returns reaching a main check that is made ever before the usual select() function. This so-called "main check" simply controls that the error returned by WSAGetLastError() (and stored in a specific variable) is "Operation would block" (10035, the only type of error accepted to continue the listening loop). If an attacker sends an UDP packet of zero bytes, recvfrom() returns this length and an instruction checks just if it is equal than zero. In this case the code flow returns to the "main check" that controls the error code (not set, so equal to zero) and since it is not 10035 exits from the loop that handles the socket's data. After that, the server will be no longer able to receive packets because the loop is completely dead. A similar problem happens if an attacker sends an UDP packet with a size major/equal than 8193 bytes (max data read by recvfrom()) and minor/equal than 12280 (otherwise select() doesn't catch it). The "main check" will fail as before because the error code will be different than 10035 (in fact it will be 10040, "Message too long"). ####################################################################### =========== 3) The Code =========== http://aluigi.org/poc/lithsock.zip ####################################################################### ====== 4) Fix ====== No fix. Just some weeks ago I released an advisory about another bug in the Lithtech engine and ignored by the developers, so is useless to try to recontact them again. However I have found a work-around to avoid the problem and I have written an universal autopatcher that can fix both lithtech.exe (the server launched from the game) and server.dll (the dedicated server): http://aluigi.org/patches/lithsockfix.lpatch Update: Seems that finally F.E.A.R. has been patched with the 1.02 version #######################################################################