/* Copyright (C) 2017 Jason A. Donenfeld . All Rights Reserved. * * This module is nonsense. There is no good reason to use it, unless you really * understand for whom it is useful and for what it's good. Chances are that it * spectacularly fails to accomplish what you were hoping it would. In short, it's * only useful if you don't know how to subvert it, and if all you're trying to * accomplish is taking away your own ability to do something, so that if somebody * asks you, "do you know how to do it?" you can say "no." It's kind of like * when you forget where you put your car keys. * * written by zx2c4 in the year 2017 * greetz to pipacs for remembering to barrier all cr0 modifications */ #define pr_fmt(fmt) "blind operator mode: " fmt #include #include #include #include #include #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0) #include #endif #include #include #include #include #include #include #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) #define nlmsg_parse(a, b, c, d, e, f) nlmsg_parse(a, b, c, d, e) #define nla_parse_nested(a, b, c, d, e) nla_parse_nested(a, b, c, d) #endif #include "wireguard.h" static int *modules_disabled_sysctl; static const struct proto_ops *netlink_ops; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) static struct security_operations *security_ops; #else static struct security_hook_heads *security_hooks; static struct security_hook_list socket_sock_rcv_skb_entry; static struct security_hook_list socket_create_entry; static struct security_hook_list inode_permission_entry; static struct security_hook_list ptrace_access_check_entry; static struct security_hook_list ptrace_traceme_entry; #endif static void install_delayed_hooks(struct work_struct *work); static DECLARE_DELAYED_WORK(install_delayed_hooks_work, install_delayed_hooks); #define modify_ro_page(STATEMENTS) do { \ unsigned long cr0; \ \ preempt_disable(); \ barrier(); \ cr0 = read_cr0(); \ write_cr0(cr0 & ~X86_CR0_WP); \ barrier(); \ \ STATEMENTS \ \ barrier(); \ write_cr0(cr0); \ barrier(); \ preempt_enable(); \ } while (0) #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) #define install_lsm_hook(__hook) #define init_lsm_hook(__hook) security_ops->__hook = (__hook##_callback); #else #define install_lsm_hook(__hook) list_add_tail_rcu(&(__hook##_entry).list, (__hook##_entry).head); #define init_lsm_hook(__hook) do { \ (__hook##_entry).head = &security_hooks->__hook; \ (__hook##_entry).hook.__hook = (__hook##_callback); \ } while (0) #endif static int socket_sock_rcv_skb_callback(struct sock *sk, struct sk_buff *skb) { static u16 wg_id; size_t hdrlen; struct nlmsghdr *nlh; int ret; if (!sk || !sk->sk_socket || sk->sk_socket->ops != netlink_ops || sk->sk_protocol != NETLINK_GENERIC) return 0; nlh = nlmsg_hdr(skb); if (nlh->nlmsg_type == GENL_ID_CTRL) { struct nlattr *attrs[CTRL_ATTR_MAX + 1]; hdrlen = GENL_HDRLEN; if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) return 0; ret = nlmsg_parse(nlh, hdrlen, attrs, CTRL_ATTR_MAX, NULL, NULL); if (ret < 0) return 0; if (!attrs[CTRL_ATTR_FAMILY_NAME] || !attrs[CTRL_ATTR_VERSION] || !attrs[CTRL_ATTR_FAMILY_ID]) return 0; if (strcmp(nla_data(attrs[CTRL_ATTR_FAMILY_NAME]), WG_GENL_NAME) || nla_get_u32(attrs[CTRL_ATTR_VERSION]) != WG_GENL_VERSION) return 0; wg_id = nla_get_u16(attrs[CTRL_ATTR_FAMILY_ID]); } else if (wg_id && nlh->nlmsg_type == wg_id) { struct nlattr *attr, *device[WGDEVICE_A_MAX + 1], *peer[WGPEER_A_MAX + 1]; int rem; hdrlen = GENL_HDRLEN; if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen)) return -EINVAL; ret = nlmsg_parse(nlh, hdrlen, device, WGDEVICE_A_MAX, NULL, NULL); if (ret < 0) return -EINVAL; if (!device[WGDEVICE_A_PEERS]) return 0; nla_for_each_nested(attr, device[WGDEVICE_A_PEERS], rem) { ret = nla_parse_nested(peer, WGPEER_A_MAX, attr, NULL, NULL); if (ret < 0) return -EINVAL; if (peer[WGPEER_A_ENDPOINT]) memset(nla_data(peer[WGPEER_A_ENDPOINT]), 0, nla_len(peer[WGPEER_A_ENDPOINT])); if (peer[WGPEER_A_ALLOWEDIPS]) memset(nla_data(peer[WGPEER_A_ALLOWEDIPS]), 0, nla_len(peer[WGPEER_A_ALLOWEDIPS])); } } return 0; } static int socket_create_callback(int family, int type, int protocol, int kern) { if (family == AF_PACKET) return -EPERM; if (family == AF_INET && type == SOCK_RAW) return -EPERM; if (family == AF_INET6 && type == SOCK_RAW) return -EPERM; return 0; } static int inode_permission_callback(struct inode *inode, int mask) { if ((inode->i_mode & S_IFCHR) && imajor(inode) == 1) { switch (iminor(inode)) { case 2: /* /dev/kmem */ case 1: /* /dev/mem */ case 4: /* /dev/port */ return -EPERM; } } if (!strcmp(inode->i_sb->s_type->name, "proc")) { struct dentry *dentry; char *buffer, *path; int ret; /* Maybe not the most iron clad way of doing this... A better * way would be to just traverse proc's structures and nop out * the kcore fops, but who has the patience for that? */ dentry = d_find_alias(inode); if (!dentry) return -EPERM; buffer = (char *)__get_free_page(GFP_KERNEL); ret = -ENOMEM; if (!buffer) goto err_dentry; path = dentry_path_raw(dentry, buffer, PAGE_SIZE); ret = PTR_ERR(path); if (IS_ERR(path)) goto err_page; ret = 0; if (!strcmp(path, "/kcore")) ret = -EPERM; if (!strcmp(path + strlen(path) - 4, "/mem")) ret = -EPERM; err_page: free_page((unsigned long)buffer); err_dentry: dput(dentry); return ret; } return 0; } static int ptrace_access_check_callback(struct task_struct *child, unsigned int mode) { return -EPERM; } static int ptrace_traceme_callback(struct task_struct *parent) { return -EPERM; } static void install_delayed_hooks(struct work_struct *work) { init_lsm_hook(socket_create); modify_ro_page({ install_lsm_hook(socket_create); }); pr_info("hooked raw socket creation\n"); if (modules_disabled_sysctl) *modules_disabled_sysctl = 1; else { ssize_t ret; loff_t off = 0; struct file *file; file = filp_open("/proc/sys/kernel/modules_disabled", O_WRONLY, 0); if (IS_ERR(file)) { pr_err("unable to disallow module loading/unloading: %ld\n", PTR_ERR(file)); return; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) ret = kernel_write(file, "1\n", 2, off); #else ret = kernel_write(file, "1\n", 2, &off); #endif filp_close(file, 0); if (ret != 2) { pr_err("unable to disallow module loading/unloading: %ld\n", ret); return; } } pr_info("disabled module loading/unloading\n"); pr_warn("enabled\n"); } static int __init mod_init(void) { u8 *do_coredump; #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0) u8 *search; void(*reset_security_ops)(void) = (void(*)(void))kallsyms_lookup_name("reset_security_ops"); if (!reset_security_ops) { pr_err("unable to lookup reset_security_ops\n"); goto err; } reset_security_ops(); for (search = (u8 *)reset_security_ops; search < (u8 *)reset_security_ops + 0x1ff; ++search) { /* REX.W + MOV... this is the worst thing ever. */ if (search[0] == 0x48 && search[1] == 0xc7) { security_ops = (struct security_operations *)((~0UL << 32) | *(unsigned int *)(search + 7)); break; } } if (!security_ops) { pr_err("unable to lookup security_ops\n"); goto err; } #else security_hooks = (struct security_hook_heads *)kallsyms_lookup_name("security_hook_heads"); if (!security_hooks) { pr_err("unable to lookup security_hook_heads\n"); goto err; } #endif if (!init_net.genl_sock || !init_net.genl_sock->sk_socket || !init_net.genl_sock->sk_socket->ops) { pr_err("unable to lookup netlink proto ops\n"); goto err; } netlink_ops = init_net.genl_sock->sk_socket->ops; do_coredump = (u8 *)kallsyms_lookup_name("do_coredump"); if (!do_coredump) { pr_err("unable to lookup do_coredump\n"); goto err; } modules_disabled_sysctl = (int *)kallsyms_lookup_name("modules_disabled"); init_lsm_hook(socket_sock_rcv_skb); init_lsm_hook(inode_permission); init_lsm_hook(ptrace_access_check); init_lsm_hook(ptrace_traceme); modify_ro_page({ install_lsm_hook(socket_sock_rcv_skb); install_lsm_hook(inode_permission); install_lsm_hook(ptrace_access_check); install_lsm_hook(ptrace_traceme); do_coredump[0] = 0xc3; /* RET */ }); pr_info("hooked wireguard netlink responses\n"); pr_info("hooked kernel memory permissions\n"); pr_info("hooked ptrace\n"); pr_info("disabled coredumps\n"); schedule_delayed_work(&install_delayed_hooks_work, HZ * 60); pr_info("other mechanisms set to deploy in 60 seconds\n"); return 0; err: pr_err("disabled\n"); return -EOPNOTSUPP; } module_init(mod_init); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Jason A. Donenfeld"); MODULE_DESCRIPTION("Rancid monkey-patcher for snake-oil blind operator mode");