12 Commits

Author SHA1 Message Date
Patrick Wardle 19ba380052 leverage BTM's (private) objects for serialization 2023-10-18 10:40:50 +02:00
Patrick Wardle efe7496191 support for parsing out "MDM item payloads" 2023-07-13 23:16:26 +02:00
Patrick Wardle 7c2adefa80 Update dumpBTM.m 2023-07-12 09:01:50 +02:00
Patrick Wardle 553e530104 improved handling of items 2023-07-02 23:35:45 +02:00
Patrick Wardle e35933845a improvements
added bundle id
keys for dictionary, now all #defines in library header file
2023-06-27 13:50:57 +02:00
Patrick Wardle b66337a74d Update .gitignore 2023-06-20 14:13:37 +02:00
Patrick Wardle 25f3522b92 additional project files 2023-06-20 14:13:13 +02:00
Patrick Wardle 5294417c67 converted to library
workspace now contains:
a library
a tool (that builds/links against library)

Library exposes two APIs:

//dump to stdout
NSInteger dump(NSURL* path);

//parse into a dictionary
NSDictionary* parse(NSURL* path);
2023-01-25 09:43:24 -07:00
Objective-See Foundation 0b4d083b48 Update README.md 2023-01-19 09:08:38 -07:00
Patrick Wardle 42b22a7a67 Update main.m
BTM file, now dynamically located (Issue: #1)
2023-01-18 22:30:40 -07:00
Objective-See Foundation b910b30783 Update README.md 2023-01-18 12:16:27 -07:00
Objective-See Foundation d608c1d5f8 Update README.md 2023-01-18 12:05:06 -07:00
18 changed files with 1453 additions and 548 deletions
+3 -1
View File
@@ -1,3 +1,5 @@
xcuserdata/
DerivedData/
*.xcworkspace
.DS_Store
libDumpBTM.a
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Library/library.xcodeproj">
</FileRef>
<FileRef
location = "group:tool/tool.xcodeproj">
</FileRef>
</Workspace>
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
+19 -10
View File
@@ -2,7 +2,11 @@
tl;dr: an open-source version of `% sfltool dumpbtm`
```
% ./dumpBTM
% ./dumpBTM
Dumps (unserializes) BackgroundItems-v*.btm
Opened /private/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v7.btm
...
========================
Records for UID 501 : 1CAA5D2B-A526-49E2-9A6F-58CACBDF0AFB
@@ -36,21 +40,26 @@ tl;dr: an open-source version of `% sfltool dumpbtm`
Parent Identifier: Adobe Creative Cloud
```
In macOS Ventura (13), Apple consolidated persistent items (login items, launch agents/daemons) in a new file: `BackgroundItems-v4.btm` (found in `/private/var/db/com.apple.backgroundtaskmanagement/`).
Note: If you're running the pre-built binary, though signed, it's not notarized (Apple doesn't support notarized commandline tools). So after making it executable, remove the quarantine attributue to make it runnable (via Terminal).
```
% chmod +x dumpBTM
% xattr -rc dumpBTM
```
Also, make sure you give Terminal "Full Disk Access" (a requirment to read the `BackgroundItems-v4.btm` file).
In macOS Ventura (13), Apple consolidated persistent items (login items, launch agents/daemons) in a new file: `BackgroundItems-v*.btm`, found in `/private/var/db/com.apple.backgroundtaskmanagement/`. On macOS 13.0 this file is named `BackgroundItems-v*.btm` whereas on macOS 13.1 it's `BackgroundItems-v7.btm`.
This file is a serialized binary propertly list. You can dump it via Apple's `sfltool`, specifying the `dumpbtm` command line flag.
`DumpBTM` is an open-source version of this, which has the following benefits:
* open-source
* programmatic access to enumerate items in the file
* Open-source
* Programmatic access to enumerate (persistent) items in the file
The latter point is most notable as this allow you to now add such logic into security/EDR tools. Spefically you can now easily and programmatically enumerate all (ok most) persistent items on a macOS Ventura system (which will include any persistently installed malware).
The latter point is most notable as this allow you to now add such logic into security/EDR tools. Specifically you can now easily and programmatically enumerate all (ok most) persistent items on a macOS Ventura system (which will include any persistently installed malware).
You can also then monitor this file for changes to detect new persistence events (as now you can parse/unserialize its contents via this project's code).
Note: Such monitoring was supposed to be accomplished via the Endpoint Security `ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD` event ...but this event is broken (See: "[Endpoint Security Event: ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD is ...broken?](https://developer.apple.com/forums/thread/720468)" 😓
Note: Such monitoring was supposed to be accomplished via the Endpoint Security `ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD` event ...but this event is broken (See: "[Endpoint Security Event: ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD is ...broken?](https://developer.apple.com/forums/thread/720468)" 😓).
-460
View File
@@ -1,460 +0,0 @@
//
// main.m
// dumpBTM
//
// Created by Patrick Wardle on 1/1/23.
//
#import <Foundation/Foundation.h>
#import <OpenDirectory/OpenDirectory.h>
#import <DirectoryService/DirectoryService.h>
#define DB_PATH @"/private/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v4.btm"
//helper function(s)
uid_t uidFromUUID(NSString* uuid);
//Storage obj
@interface Storage : NSObject <NSSecureCoding>
@property(nonatomic, retain)NSDictionary* items;
@end
//item record obj
@interface ItemRecord : NSObject <NSSecureCoding>
@property(nonatomic, retain)NSUUID* uuid;
@property(nonatomic, retain)NSString* identifier;
@property NSInteger generation;
@property(nonatomic, retain)NSURL* url;
@property(nonatomic, retain)NSString* executablePath;
@property(nonatomic, retain)NSString* name;
@property(nonatomic, retain)NSString* developerName;
@property(nonatomic, retain)NSString* teamIdentifier;
@property(nonatomic, retain)NSData* lightweightRequirement;
@property NSInteger type;
@property NSInteger disposition;
@property(nonatomic, retain)NSData* bookmark;
@property(nonatomic, retain)NSString* bundleIdentifier;
@property(nonatomic, retain)NSArray* associatedBundleIdentifiers;
@property(nonatomic, retain)NSString* container;
@property(nonatomic, retain)NSMutableSet* items;
-(NSString*)dumpVerboseDescription;
@end
//main
// just dump DB to stdout
// TODO: add option to print as JSON
int main(int argc, const char * argv[]) {
//status
int status = -1;
@autoreleasepool {
//error
NSError* error = nil;
//unarchiver
NSKeyedUnarchiver* keyedUnarchiver = nil;
//database (raw/unserialized) data
NSData* dbData = nil;
//database dictionary
NSMutableDictionary* dbDictionary = nil;
//database storage obj
Storage* storage = nil;
//init
dbDictionary = [NSMutableDictionary dictionary];
//dbg msg
printf("%s v0.95\nDumps (unserializes) BackgroundItems-v4.btm\n", [[NSProcessInfo.processInfo.arguments.firstObject lastPathComponent] UTF8String]);
//load database
// note: this will fail if you don't have full disk access
dbData = [NSData dataWithContentsOfURL:[[NSURL alloc] initFileURLWithPath:DB_PATH] options:0 error:&error];
if(nil == dbData)
{
//error msg
printf("ERROR: failed to load %s\n\nDo you have Full Disk Access?\n\n", DB_PATH.UTF8String);
goto bail;
}
//dbg msg
printf("Opened %s\n\n", DB_PATH.UTF8String);
//init unachiver
keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:dbData error:&error];
//set this (for debugging)
keyedUnarchiver.decodingFailurePolicy = NSDecodingFailurePolicyRaiseException;
//get version
dbDictionary[@"version"] = [NSNumber numberWithInteger:[keyedUnarchiver decodeIntegerForKey:@"version"]];
//get store
dbDictionary[@"store"] = [keyedUnarchiver decodeObjectOfClass:[Storage class] forKey:@"store"];
//grab storage obj
storage = dbDictionary[@"store"];
//process each/all items
for(NSString* key in storage.items)
{
//uid
uid_t uid = 0;
//items
NSArray* items = nil;
//item number
int itemNumber = 0;
//get uid from uuid
uid = uidFromUUID(key);
//header (w/ uid/uuid)
printf("========================\n Records for UID %d : %s\n========================\n\n", uid, key.description.UTF8String);
printf(" Items:\n\n");
//process each item
items = storage.items[key];
for(ItemRecord* item in items)
{
//display number and item details.
printf(" #%d\n", ++itemNumber);
printf(" %s\n", [[item dumpVerboseDescription] UTF8String]);
}
}
//happy
status = 0;
}
bail:
return status;
}
@implementation Storage
@synthesize items;
+(BOOL)supportsSecureCoding
{
return YES;
}
//decode object
-(id)initWithCoder:(NSCoder *)decoder
{
if(self = [super init])
{
//supported classes
NSArray* itemClasses = @[[NSMutableDictionary class], [NSString class], [NSArray class], [ItemRecord class]];
//decode items
// 'itemsByUserIdentifier'
self.items = [decoder decodeObjectOfClasses:[NSSet setWithArray:itemClasses] forKey:@"itemsByUserIdentifier"];
}
//TODO:
// "userSettingsByUserIdentifier" & "mdmPaloadsByIdentifier"
return self;
}
-(void)encodeWithCoder:(nonnull NSCoder *)coder {
return;
}
@end
@implementation ItemRecord
+(BOOL)supportsSecureCoding
{
return YES;
}
//decode object
-(id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if(nil != self)
{
self.uuid = [decoder decodeObjectOfClass:[NSUUID class] forKey:@"uuid"];
self.identifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"];
self.generation = [decoder decodeIntegerForKey:@"generation"];
self.url = [decoder decodeObjectOfClass:[NSURL class] forKey:@"url"];
self.executablePath = [decoder decodeObjectOfClass:[NSString class] forKey:@"executablePath"];
self.name = [decoder decodeObjectOfClass:[NSString class] forKey:@"name"];
self.developerName = [decoder decodeObjectOfClass:[NSString class] forKey:@"developerName"];
self.teamIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"teamIdentifier"];
self.lightweightRequirement = [decoder decodeObjectOfClass:[NSData class] forKey:@"lightweightRequirement"];
self.type = [decoder decodeIntegerForKey:@"type"];
self.disposition = [decoder decodeIntegerForKey:@"disposition"];
self.bookmark = [decoder decodeObjectOfClass:[NSData class] forKey:@"bookmark"];
self.bundleIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"bundleIdentifier"];
self.associatedBundleIdentifiers = [decoder decodeObjectOfClass:[NSArray class] forKey:@"associatedBundleIdentifiers"];
self.container = [decoder decodeObjectOfClass:[NSString class] forKey:@"container"];
NSArray* itemsArray = [decoder decodeArrayOfObjectsOfClass:[NSString class] forKey:@"items"];
if(nil != itemsArray)
{
self.items = [NSMutableSet setWithArray:itemsArray];
}
else
{
self.items = [NSMutableSet set];
}
}
return self;
}
-(void)encodeWithCoder:(nonnull NSCoder *)coder {
return;
}
//convert item record's type to string
// TODO: handle all types (e.g. managaged?)
-(NSString *)typeDetails
{
//details
NSMutableString* details = nil;
//init
details = [NSMutableString string];
//curated
if(self.type & 0x80000)
{
[details appendString:@"curated "];
}
//legacy
if(self.type & 0x10000)
{
[details appendString:@"legacy "];
}
//developer
if(self.type & 0x20)
{
[details appendString:@"developer "];
}
//daaemon
if(self.type & 0x10)
{
[details appendString:@"daemon "];
}
//agent
if(self.type & 0x8)
{
[details appendString:@"agent "];
}
//login item
if(self.type & 0x4)
{
[details appendString:@"login item "];
}
//app
if(self.type & 0x2)
{
[details appendString:@"app "];
}
return details;
}
//convert item record's disposition to string
-(NSString *)dispositionDetails
{
//details
NSMutableString* details = nil;
details = [NSMutableString string];
//enabled
if(self.disposition & 0x1)
{
[details appendString:@"enabled "];
}
//disabled
else
{
[details appendString:@"disabled "];
}
//allowed
if(self.disposition & 0x2)
{
[details appendString:@"allowed "];
}
//disallowed
else
{
[details appendString:@"disallowed "];
}
//hidden
if(self.disposition & 0x4)
{
[details appendString:@"hidden "];
}
//visibile
else
{
[details appendString:@"visible "];
}
//notified
if(self.disposition & 0x8)
{
[details appendString:@"notified"];
}
//not notified
else
{
[details appendString:@"not notified"];
}
return details;
}
//dump
-(NSString *)dumpVerboseDescription {
//desc
NSMutableString* description = nil;
//init
description = [NSMutableString string];
//build description
[description appendFormat:@" UUID: %@\r\n", self.uuid];
[description appendFormat:@" Name: %@\r\n", self.name];
[description appendFormat:@" Developer Name: %@\r\n", self.developerName];
//team id
if(nil != self.teamIdentifier)
{
[description appendFormat:@" Team Identifier: %@\r\n", self.teamIdentifier];
}
//type
[description appendFormat:@" Type: %@ (%#lx)\n", [self typeDetails], (long)self.type];
//disposition
[description appendFormat:@" Disposition: [%@] (%ld)\n", [self dispositionDetails], (long)self.disposition];
//id
[description appendFormat:@" Indentifier: %@\r\n", self.identifier];
//url
[description appendFormat:@" URL: %@\n", self.url];
//path
[description appendFormat:@" Executable Path: %@\n", self.executablePath];
//generation
[description appendFormat:@" Generation: %ld\n", self.generation];
//associated bundle ids
if(0 != self.associatedBundleIdentifiers.count)
{
[description appendFormat:@" Assoc. Bundle IDs: "];
[description appendString:@"["];
for(NSString* associatedBundleIdentifier in self.associatedBundleIdentifiers)
{
[description appendFormat:@"%@ ", associatedBundleIdentifier];
}
//remove last " "
if(YES == [[description substringFromIndex:[description length] - 1] isEqualToString:@" "])
{
description = [[description substringToIndex:[description length]-1] mutableCopy];
}
[description appendString:@"]\n"];
}
//parent id
[description appendFormat:@" Parent Identifier: %@\n", self.container];
//items
if(0 != self.items.count)
{
int count = 0;
[description appendFormat:@" Embedded Item Identifiers:"];
for(NSString* item in self.items.allObjects)
{
[description appendFormat:@"\n #%d: %@", ++count, item];
}
[description appendString:@"\n"];
}
return description;
}
@end
//helper function
//grab a UID from a UUID
uid_t uidFromUUID(NSString* uuid)
{
//uid
uid_t uid = 0;
//node
ODNode* node = nil;
//query
ODQuery* query = nil;
//return
NSArray* result = nil;
//record
ODRecord* record = nil;
//attribute (@kDS1AttrUniqueID)
NSArray* attribute = nil;
//init node
node = [ODNode nodeWithSession:ODSession.defaultSession type:0x2200 error:nil];
//perform query
query = [ODQuery queryWithNode:node forRecordTypes:kODRecordTypeUsers attribute:kODAttributeTypeGUID matchType:0x2001 queryValues:uuid returnAttributes:nil maximumResults:1 error:nil];
//result
result = [query resultsAllowingPartial:0x0 error:nil];
//grab first result
record = result.firstObject;
//grab '@kDS1AttrUniqueID'
attribute = [record valuesForAttribute:@kDS1AttrUniqueID error:nil];
//uid is first obj, as int
uid = [attribute.firstObject intValue];
return uid;
}
+39
View File
@@ -0,0 +1,39 @@
//
// dumpBTM.h
// library
//
// Created by Patrick Wardle on 1/20/23.
//
@import Foundation;
//keys for dictionary
#define KEY_BTM_PATH @"path"
#define KEY_BTM_ERROR @"error"
#define KEY_BTM_VERSION @"version"
#define KEY_BTM_MDM_PAYLOAD @"mdmPayloadsByIdentifier"
#define KEY_BTM_ITEMS_BY_USER_ID @"itemsByUserIdentifier"
//keys for item(s)
#define KEY_BTM_ITEM_ID @"id"
#define KEY_BTM_ITEM_URL @"url"
#define KEY_BTM_ITEM_UUID @"uuid"
#define KEY_BTM_ITEM_NAME @"name"
#define KEY_BTM_ITEM_TYPE @"type"
#define KEY_BTM_ITEM_TEAM_ID @"teamID"
#define KEY_BTM_ITEM_DEV_NAME @"devName"
#define KEY_BTM_ITEM_BUNDLE_ID @"bundleID"
#define KEY_BTM_ITEM_PARENT_ID @"parentID"
#define KEY_BTM_ITEM_PLIST_PATH @"plistPath"
#define KEY_BTM_ITEM_GENERATION @"generation"
#define KEY_BTM_ITEM_EXE_PATH @"executablePath"
#define KEY_BTM_ITEM_DISPOSITION @"disposition"
#define KEY_BTM_ITEM_TYPE_DETAILS @"typeDetails"
#define KEY_BTM_ITEM_EMBEDDED_IDS @"embeddedIDs"
#define KEY_BTM_ITEM_ASSOCIATED_IDS @"associatedBundleIDs"
#define KEY_BTM_ITEM_DISPOSITION_DETAILS @"dispositionDetails"
//APIs
// note: path is optional
NSInteger dumpBTM(NSURL* path);
NSDictionary* parseBTM(NSURL* path);
+727
View File
@@ -0,0 +1,727 @@
//
// dumpBTM.m
// dumpBTM (library)
//
// Created by Patrick Wardle on 1/20/23.
//
// Note: Once compiled, header file (dumpBTM.h) and library (libDumpBTM.a)
// are added to: $SRCROOT/lib/
//
// Copy this header file / link this library in your own project(s)!
#import <dlfcn.h>
#import "dumpBTM.h"
#import "dumpBT_Internal.h"
/* GLOBALS */
//handle to btm daemon
void* btmd = NULL;
/* FUNCTION DEF */
//helper
// convert ItemRecord to dictionary
NSDictionary* toDictionary(ItemRecord* record, NSArray* items);
//constructor
// load btm daemon (into our memory)
__attribute__((constructor)) static void initDumpBTM(void)
{
//load btm daemon
// its contains obj methods we need
btmd = dlopen(BTM_DAEMON, RTLD_LAZY);
return;
}
//load a btm file
// and init a NSKeyedUnarchiver for it
NSKeyedUnarchiver* load(NSURL** path)
{
//error
NSError* error = nil;
//database data
NSData* data = nil;
//unarchiver
NSKeyedUnarchiver* keyedUnarchiver = nil;
//no (user-specified) path?
// find/use system's btm path
if(nil == *path)
{
//get btm path
*path = getPath();
if(nil == *path)
{
//err msg
os_log_error(OS_LOG_DEFAULT, "ERROR: failed to find a .btm file in %{public}@", BTM_DIRECTORY);
//bail
goto bail;
}
}
//load database *path
// note: this will fail if client doesn't have full disk access
data = [NSData dataWithContentsOfURL:*path options:0 error:&error];
if(nil == data)
{
//err msg
os_log_error(OS_LOG_DEFAULT, "ERROR: failed to open/load %{public}@", *path);
//bail
goto bail;
}
//init unachiver
keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&error];
//set decoding failure policy
// will throw an exception if unserialization fails
keyedUnarchiver.decodingFailurePolicy = NSDecodingFailurePolicyRaiseException;
bail:
return keyedUnarchiver;
}
//deserialize
Storage* deserialize(NSURL* path)
{
//'Storage' class
Class StorageClass = nil;
//'Storage' object
Storage* storage = nil;
//unarchiver
NSKeyedUnarchiver* keyedUnarchiver = nil;
//lookup 'Storage' class
StorageClass = NSClassFromString(@"Storage");
if(nil == StorageClass)
{
//err msg
printf("ERROR: failed to find 'Storage' class\n\n");
//bail
goto bail;
}
//load btm file
keyedUnarchiver = load(&path);
if(nil == keyedUnarchiver)
{
//err msg
printf("ERROR: failed to open/load %s\n\n", path.path.UTF8String);
//bail
goto bail;
}
//dbg msg
printf("Opened %s\n\n", path.path.UTF8String);
//wrap unserialization
@try {
//decode storage object
// this in turn will decode all items
storage = [keyedUnarchiver decodeObjectOfClass:StorageClass forKey:@"store"];
}
//exception
@catch(NSException *exception) {
//err msg
printf("ERROR: unserializing failed with %s\n\n", exception.description.UTF8String);
//bail
goto bail;
}
//sanity check
// iVar: 'itemsByUserIdentifier' and 'mdmPayloadsByIdentifier'
if( (YES != [storage respondsToSelector:@selector(itemsByUserIdentifier)]) ||
(YES != [storage respondsToSelector:@selector(mdmPayloadsByIdentifier)]) )
{
//err msg
printf("ERROR: failed to find 'Storage' class's 'itemsByUserIdentifier' or 'mdmPayloadsByIdentifier' instance variables\n\n");
//unset
storage = nil;
//bail
goto bail;
}
bail:
return storage;
}
//API INTERFACE
// dump a btm file to stdout
// path is optional, and if nil, will be found dynamically
NSInteger dumpBTM(NSURL* path)
{
//result
NSInteger result = -1;
//'Storage' object
Storage* storage = nil;
//item #
int itemNumber = 0;
//deserialze
storage = deserialize(path);
if(nil == storage)
{
//bail
goto bail;
}
//process each/all items
for(NSString* key in storage.itemsByUserIdentifier)
{
//uid
uid_t uid = 0;
//items
NSArray* items = nil;
//get uid from uuid
uid = uidFromUUID(key);
//header (w/ uid/uuid)
printf("========================\n Records for UID %d : %s\n========================\n\n", uid, key.description.UTF8String);
printf(" Items:\n\n");
//process each item
items = storage.itemsByUserIdentifier[key];
for(ItemRecord* item in items)
{
//sanity check
if(YES != [item respondsToSelector:NSSelectorFromString(@"dumpVerboseDescription")])
{
//err msg
printf("ERROR: failed to find current 'ItemRecord' object's 'dumpVerboseDescription' method\n\n");
//bail
goto bail;
}
//display number and item details.
printf(" #%d\n", ++itemNumber);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
printf(" %s\n", [[item performSelector:NSSelectorFromString(@"dumpVerboseDescription")] UTF8String]);
#pragma clang diagnostic pop
}
}
//any mdm items?
if(0 != storage.mdmPayloadsByIdentifier.count)
{
//reset
itemNumber = 0;
//dbg output
printf("========================\n");
printf("\n MDM Payloads:\n\n");
//print out all mdm items
for(NSString* key in storage.mdmPayloadsByIdentifier)
{
//item
NSDictionary* mdmItem = storage.mdmPayloadsByIdentifier[key];
//print
printf("#%d: %s:\n%s\n", ++itemNumber, key.UTF8String, mdmItem.description.UTF8String);
}
}
//happy
result = noErr;
bail:
return result;
}
//API INTERFACE
// parse a btm file into a dictionary
// path is optional, and if nil, will be found dynamically
NSDictionary* parseBTM(NSURL* path)
{
//contents
NSMutableDictionary* contents = nil;
//'Storage' object
Storage* storage = nil;
//items
NSMutableDictionary* itemsByUserID = nil;
//init contents
contents = [NSMutableDictionary dictionary];
//init
itemsByUserID = [NSMutableDictionary dictionary];
//deserialze
storage = deserialize(path);
if(nil == storage)
{
//set error
contents[KEY_BTM_ERROR] = [NSNumber numberWithLong:EFTYPE];
//bail
goto bail;
}
//set path
contents[KEY_BTM_PATH] = path;
//process each/all items
// key is the UUID (mapping to a uid)
for(NSString* key in storage.itemsByUserIdentifier)
{
//uid
uid_t uid = 0;
//items
NSMutableArray* items = nil;
//init
items = [NSMutableArray array];
//get uid from uuid
uid = uidFromUUID(key);
//process each item
for(ItemRecord* item in storage.itemsByUserIdentifier[key])
{
//add item
[items addObject:toDictionary(item, storage.itemsByUserIdentifier[key])];
}
//add items
itemsByUserID[key] = items;
}
//save all items by user id
contents[KEY_BTM_ITEMS_BY_USER_ID] = itemsByUserID;
//save MDM payloads
if(0 != storage.mdmPayloadsByIdentifier.count)
{
//save
contents[KEY_BTM_MDM_PAYLOAD] = storage.itemsByUserIdentifier;
}
bail:
return contents;
}
//get path of the latest BTM file
// looks in "/private/var/db/com.apple.backgroundtaskmanagement/"
NSURL* getPath(void)
{
//path
NSURL* path = nil;
//directory files
NSArray* files = nil;
//btm files
NSArray* btmFiles = nil;
//error
NSError* error = nil;
//load all files in directory
files = [NSFileManager.defaultManager contentsOfDirectoryAtURL:[NSURL fileURLWithPath:BTM_DIRECTORY] includingPropertiesForKeys:nil options:0 error:&error];
if(nil == files)
{
//err msg
os_log_error(OS_LOG_DEFAULT, "ERROR: failed to enumerate files in %{public}@\nDetails: %{public}@\n\n", BTM_DIRECTORY, error);
//bail
goto bail;
}
//get files that match ".btm"
btmFiles = [files filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self.absoluteString ENDSWITH '.btm'"]];
//grab last file
path = btmFiles.lastObject;
bail:
return path;
}
/* (helper) EXTENSION FUNCTIONS for ItemRecord object */
/* ...can't extented ItemRecord class, as its defined in a daemon (vs. framework) */
//helper function
//convert Item Record's type to string
NSString* typeDetails(ItemRecord* record)
{
//details
NSMutableString* details = nil;
//init
details = [NSMutableString string];
//curated
if(record.type & 0x80000)
{
[details appendString:@"curated "];
}
//legacy
if(record.type & 0x10000)
{
[details appendString:@"legacy "];
}
//developer
if(record.type & 0x20)
{
[details appendString:@"developer "];
}
//daemon
if(record.type & 0x10)
{
[details appendString:@"daemon "];
}
//agent
if(record.type & 0x8)
{
[details appendString:@"agent "];
}
//login item
if(record.type & 0x4)
{
[details appendString:@"login item "];
}
//app
if(record.type & 0x2)
{
[details appendString:@"app "];
}
return details;
}
//helper function
// find parent of ItemRecord, via ID
ItemRecord* findParent(ItemRecord* record, NSArray* items)
{
//parent
ItemRecord* parent = nil;
//find parent
for(ItemRecord* item in items)
{
//not a parent?
if(nil == item.identifier)
{
//skip
continue;
}
//no match
if(YES != [item.identifier isEqualToString:record.container])
{
continue;
}
//match
// save parent
parent = item;
//done
break;
}
return parent;
}
//helper function
//convert ItemRecord disposition to string
NSString* dispositionDetails(ItemRecord* record)
{
//details
NSMutableString* details = nil;
//init
details = [NSMutableString string];
//enabled
if(record.disposition & 0x1)
{
[details appendString:@"enabled "];
}
//disabled
else
{
[details appendString:@"disabled "];
}
//allowed
if(record.disposition & 0x2)
{
[details appendString:@"allowed "];
}
//disallowed
else
{
[details appendString:@"disallowed "];
}
//hidden
if(record.disposition & 0x4)
{
[details appendString:@"hidden "];
}
//visibile
else
{
[details appendString:@"visible "];
}
//notified
if(record.disposition & 0x8)
{
[details appendString:@"notified"];
}
//not notified
else
{
[details appendString:@"not notified"];
}
return details;
}
//helper function
// convert ItemRecord to a dictionary
// w/ additional logic to extract plist/exe paths
NSDictionary* toDictionary(ItemRecord* record, NSArray* items)
{
//item
NSMutableDictionary* item = nil;
//bundle
NSBundle* bundle = nil;
//bundle ids
NSMutableArray* bundleIDs = nil;
//parent
ItemRecord* parent = nil;
//embedded items
NSMutableArray* embeddedItems = nil;
//init
item = [NSMutableDictionary dictionary];
//uuid
item[KEY_BTM_ITEM_UUID] = record.uuid;
//name
item[KEY_BTM_ITEM_NAME] = record.name;
//dev
item[KEY_BTM_ITEM_DEV_NAME] = record.developerName;
//team id
if(nil != record.teamIdentifier)
{
item[KEY_BTM_ITEM_TEAM_ID] = record.teamIdentifier;
}
//type
item[KEY_BTM_ITEM_TYPE] = [NSNumber numberWithLong:(long)record.type];
//type description
item[KEY_BTM_ITEM_TYPE_DETAILS] = typeDetails(record);
//disposition
item[KEY_BTM_ITEM_DISPOSITION] = [NSNumber numberWithLong:(long)record.disposition];
//disposition details
item[KEY_BTM_ITEM_DISPOSITION_DETAILS] = dispositionDetails(record);
//identifier
item[KEY_BTM_ITEM_ID] = record.identifier;
//url
item[KEY_BTM_ITEM_URL] = record.url;
//path
item[KEY_BTM_ITEM_EXE_PATH] = record.executablePath;
//generation
item[KEY_BTM_ITEM_GENERATION] = [NSNumber numberWithLong:(long)record.generation];
//bundle id
if(nil != record.bundleIdentifier)
{
//add
item[KEY_BTM_ITEM_BUNDLE_ID] = record.bundleIdentifier;
}
//associated bundle ids
if(0 != record.associatedBundleIdentifiers.count)
{
//init
bundleIDs = [NSMutableArray array];
//add each
for(NSString* associatedBundleIdentifier in record.associatedBundleIdentifiers)
{
//add
[bundleIDs addObject:associatedBundleIdentifier];
}
item[KEY_BTM_ITEM_ASSOCIATED_IDS] = bundleIDs;
}
//parent
if(0 != record.container.length)
{
//add
item[KEY_BTM_ITEM_PARENT_ID] = record.container;
}
//items
if(0 != record.embeddedItems.count)
{
//init
embeddedItems = [NSMutableArray array];
//add each
for(NSString* item in record.embeddedItems.allObjects)
{
//add
[embeddedItems addObject:item];
}
//save
item[KEY_BTM_ITEM_EMBEDDED_IDS] = embeddedItems;
}
//agents/daemons
// path: already set
// plist: is in 'url'
if( (record.type & 0x8) ||
(record.type & 0x10) )
{
//set plist
if(nil != record.url)
{
//plist
item[KEY_BTM_ITEM_PLIST_PATH] = record.url.path;
}
}
//login item
// path: construct via parent
else if(record.type & 0x4)
{
//find parent
parent = findParent(record, items);
if(nil != parent)
{
bundle = [NSBundle bundleWithPath:[NSString stringWithFormat:@"%@%@", parent.url.path, record.url.path]];
}
//add exe path
if(nil != bundle.executablePath)
{
//exe path
item[KEY_BTM_ITEM_EXE_PATH] = bundle.executablePath;
}
}
//app
// path to app bundle is in url
// ...load bundle to get exe path
else if(record.type & 0x2)
{
//load bundle from app path
bundle = [NSBundle bundleWithPath:record.url.path];
//add exe path
if(nil != bundle.executablePath)
{
//exe path
item[KEY_BTM_ITEM_EXE_PATH] = bundle.executablePath;
}
}
return item;
}
//helper function
//get a UID from a UUID
uid_t uidFromUUID(NSString* uuid)
{
//uid
uid_t uid = 0;
//node
ODNode* node = nil;
//query
ODQuery* query = nil;
//return
NSArray* result = nil;
//record
ODRecord* record = nil;
//attribute (@kDS1AttrUniqueID)
NSArray* attribute = nil;
//init node
node = [ODNode nodeWithSession:ODSession.defaultSession type:0x2200 error:nil];
//perform query
query = [ODQuery queryWithNode:node forRecordTypes:kODRecordTypeUsers attribute:kODAttributeTypeGUID matchType:0x2001 queryValues:uuid returnAttributes:nil maximumResults:1 error:nil];
//result
result = [query resultsAllowingPartial:0x0 error:nil];
//grab first result
record = result.firstObject;
//grab '@kDS1AttrUniqueID'
attribute = [record valuesForAttribute:@kDS1AttrUniqueID error:nil];
//uid is first obj, as int
uid = [attribute.firstObject intValue];
return uid;
}
+56
View File
@@ -0,0 +1,56 @@
//
// dumpBTM_Internal.h
// dumpBTM (library)
//
// Created by Patrick Wardle on 1/20/23.
//
@import OSLog;
@import Foundation;
@import OpenDirectory;
@import DirectoryService;
#define BTM_DAEMON "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Resources/backgroundtaskmanagementd"
#define BTM_DIRECTORY @"/private/var/db/com.apple.backgroundtaskmanagement/"
//helper function(s)
NSURL* getPath(void);
uid_t uidFromUUID(NSString* uuid);
/* the following objects
...dumped from backgroundtaskmanagementd */
//Storage object
@interface Storage : NSObject
//items
@property(nonatomic, retain)NSDictionary* itemsByUserIdentifier;
//mdm items
@property(nonatomic, retain)NSDictionary* mdmPayloadsByIdentifier;
@end
//Item Record object
@interface ItemRecord : NSObject
@property NSInteger type;
@property NSInteger generation;
@property NSInteger disposition;
@property(nonatomic, retain)NSURL* url;
@property(nonatomic, retain)NSUUID* uuid;
@property(nonatomic, retain)NSString* name;
@property(nonatomic, retain)NSData* bookmark;
@property(nonatomic, retain)NSString* container;
@property(nonatomic, retain)NSMutableSet* embeddedItems;
@property(nonatomic, retain)NSString* identifier;
@property(nonatomic, retain)NSString* developerName;
@property(nonatomic, retain)NSString* executablePath;
@property(nonatomic, retain)NSString* teamIdentifier;
@property(nonatomic, retain)NSString* bundleIdentifier;
@property(nonatomic, retain)NSData* lightweightRequirement;
@property(nonatomic, retain)NSArray* associatedBundleIdentifiers;
@end
+39
View File
@@ -0,0 +1,39 @@
//
// dumpBTM.h
// library
//
// Created by Patrick Wardle on 1/20/23.
//
@import Foundation;
//keys for dictionary
#define KEY_BTM_PATH @"path"
#define KEY_BTM_ERROR @"error"
#define KEY_BTM_VERSION @"version"
#define KEY_BTM_MDM_PAYLOAD @"mdmPayloadsByIdentifier"
#define KEY_BTM_ITEMS_BY_USER_ID @"itemsByUserIdentifier"
//keys for item(s)
#define KEY_BTM_ITEM_ID @"id"
#define KEY_BTM_ITEM_URL @"url"
#define KEY_BTM_ITEM_UUID @"uuid"
#define KEY_BTM_ITEM_NAME @"name"
#define KEY_BTM_ITEM_TYPE @"type"
#define KEY_BTM_ITEM_TEAM_ID @"teamID"
#define KEY_BTM_ITEM_DEV_NAME @"devName"
#define KEY_BTM_ITEM_BUNDLE_ID @"bundleID"
#define KEY_BTM_ITEM_PARENT_ID @"parentID"
#define KEY_BTM_ITEM_PLIST_PATH @"plistPath"
#define KEY_BTM_ITEM_GENERATION @"generation"
#define KEY_BTM_ITEM_EXE_PATH @"executablePath"
#define KEY_BTM_ITEM_DISPOSITION @"disposition"
#define KEY_BTM_ITEM_TYPE_DETAILS @"typeDetails"
#define KEY_BTM_ITEM_EMBEDDED_IDS @"embeddedIDs"
#define KEY_BTM_ITEM_ASSOCIATED_IDS @"associatedBundleIDs"
#define KEY_BTM_ITEM_DISPOSITION_DETAILS @"dispositionDetails"
//APIs
// note: path is optional
NSInteger dumpBTM(NSURL* path);
NSDictionary* parseBTM(NSURL* path);
+331
View File
@@ -0,0 +1,331 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
CD7B6A7D297B0CC100E59E7F /* dumpBT_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = CD7B6A7C297B0CC100E59E7F /* dumpBT_Internal.h */; };
CD7B6A7F297B0CC100E59E7F /* dumpBTM.m in Sources */ = {isa = PBXBuildFile; fileRef = CD7B6A7E297B0CC100E59E7F /* dumpBTM.m */; };
CDBFE39F2A49CF85005A9819 /* dumpBTM.h in Headers */ = {isa = PBXBuildFile; fileRef = CDBFE39E2A49CF85005A9819 /* dumpBTM.h */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
CD4D4EAD2ADFCBF000DAFFB5 /* BackgroundTaskManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = BackgroundTaskManagement.framework; path = ../../../../../System/Library/PrivateFrameworks/BackgroundTaskManagement.framework; sourceTree = "<group>"; };
CD7B6A79297B0CC100E59E7F /* libdumpBTM.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libdumpBTM.a; sourceTree = BUILT_PRODUCTS_DIR; };
CD7B6A7C297B0CC100E59E7F /* dumpBT_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = dumpBT_Internal.h; sourceTree = "<group>"; };
CD7B6A7E297B0CC100E59E7F /* dumpBTM.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = dumpBTM.m; sourceTree = "<group>"; };
CDBFE39E2A49CF85005A9819 /* dumpBTM.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dumpBTM.h; path = ../../library/code/dumpBTM.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
CD7B6A77297B0CC100E59E7F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
CD4D4EAC2ADFCBF000DAFFB5 /* Frameworks */ = {
isa = PBXGroup;
children = (
CD4D4EAD2ADFCBF000DAFFB5 /* BackgroundTaskManagement.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
CD7B6A70297B0CC100E59E7F = {
isa = PBXGroup;
children = (
CD7B6A7B297B0CC100E59E7F /* code */,
CD7B6A7A297B0CC100E59E7F /* Products */,
CD4D4EAC2ADFCBF000DAFFB5 /* Frameworks */,
);
sourceTree = "<group>";
};
CD7B6A7A297B0CC100E59E7F /* Products */ = {
isa = PBXGroup;
children = (
CD7B6A79297B0CC100E59E7F /* libdumpBTM.a */,
);
name = Products;
sourceTree = "<group>";
};
CD7B6A7B297B0CC100E59E7F /* code */ = {
isa = PBXGroup;
children = (
CDBFE39E2A49CF85005A9819 /* dumpBTM.h */,
CD7B6A7C297B0CC100E59E7F /* dumpBT_Internal.h */,
CD7B6A7E297B0CC100E59E7F /* dumpBTM.m */,
);
path = code;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
CD7B6A75297B0CC100E59E7F /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
CDBFE39F2A49CF85005A9819 /* dumpBTM.h in Headers */,
CD7B6A7D297B0CC100E59E7F /* dumpBT_Internal.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
CD7B6A78297B0CC100E59E7F /* library */ = {
isa = PBXNativeTarget;
buildConfigurationList = CD7B6A82297B0CC100E59E7F /* Build configuration list for PBXNativeTarget "library" */;
buildPhases = (
CD7B6A75297B0CC100E59E7F /* Headers */,
CD7B6A76297B0CC100E59E7F /* Sources */,
CD7B6A77297B0CC100E59E7F /* Frameworks */,
CD7B6A9A297B56C600E59E7F /* ShellScript */,
);
buildRules = (
);
dependencies = (
);
name = library;
productName = library;
productReference = CD7B6A79297B0CC100E59E7F /* libdumpBTM.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
CD7B6A71297B0CC100E59E7F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1420;
TargetAttributes = {
CD7B6A78297B0CC100E59E7F = {
CreatedOnToolsVersion = 14.2;
};
};
};
buildConfigurationList = CD7B6A74297B0CC100E59E7F /* Build configuration list for PBXProject "library" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = CD7B6A70297B0CC100E59E7F;
productRefGroup = CD7B6A7A297B0CC100E59E7F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
CD7B6A78297B0CC100E59E7F /* library */,
);
};
/* End PBXProject section */
/* Begin PBXShellScriptBuildPhase section */
CD7B6A9A297B56C600E59E7F /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#move library/h into top-level \"lib/\" folder\ncp $CODESIGNING_FOLDER_PATH $SRCROOT/lib/libDumpBTM.a\ncp $SRCROOT/code/dumpBTM.h $SRCROOT/lib/dumpBTM.h\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
CD7B6A76297B0CC100E59E7F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CD7B6A7F297B0CC100E59E7F /* dumpBTM.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
CD7B6A80297B0CC100E59E7F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
CD7B6A81297B0CC100E59E7F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
CD7B6A83297B0CC100E59E7F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = VBG97UB4TA;
EXECUTABLE_PREFIX = lib;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_NAME = dumpBTM;
SKIP_INSTALL = YES;
SYSTEM_FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
);
};
name = Debug;
};
CD7B6A84297B0CC100E59E7F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = VBG97UB4TA;
EXECUTABLE_PREFIX = lib;
PRODUCT_NAME = dumpBTM;
SKIP_INSTALL = YES;
SYSTEM_FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
);
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
CD7B6A74297B0CC100E59E7F /* Build configuration list for PBXProject "library" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CD7B6A80297B0CC100E59E7F /* Debug */,
CD7B6A81297B0CC100E59E7F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CD7B6A82297B0CC100E59E7F /* Build configuration list for PBXNativeTarget "library" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CD7B6A83297B0CC100E59E7F /* Debug */,
CD7B6A84297B0CC100E59E7F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = CD7B6A71297B0CC100E59E7F /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CD7B6A78297B0CC100E59E7F"
BuildableName = "libdumpBTM.a"
BlueprintName = "library"
ReferencedContainer = "container:library.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CD7B6A78297B0CC100E59E7F"
BuildableName = "libdumpBTM.a"
BlueprintName = "library"
ReferencedContainer = "container:library.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
+36
View File
@@ -0,0 +1,36 @@
//
// main.m
// tool
//
// Created by Patrick Wardle on 1/20/23.
//
@import Foundation;
#include "dumpBTM.h"
//example program to link/invoke 'dumpBTM' library
int main(int argc, const char * argv[]) {
//for user-specified path
NSURL* path = nil;
//user-specified path?
if(argc == 2)
{
//set
path = [[NSURL alloc] initFileURLWithPath:[NSString stringWithUTF8String:argv[1]]];
}
//just dump to stdout
// similar to: sfltool dumpbtm
dumpBTM(path);
//parse into a dictionary
// containing items (in dictionaries)
NSDictionary* contents = parseBTM(path);
//now do something with extracted/parsed out items
return 0;
}
@@ -7,11 +7,12 @@
objects = {
/* Begin PBXBuildFile section */
CDCA2856296A748A00944E6D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CDCA2855296A748A00944E6D /* main.m */; };
CD7B6A94297B564300E59E7F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CD7B6A93297B564300E59E7F /* main.m */; };
CD7B6AA3297B5B8C00E59E7F /* libDumpBTM.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD7B6AA2297B5B8C00E59E7F /* libDumpBTM.a */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
CDCA2850296A748A00944E6D /* CopyFiles */ = {
CD7B6A8E297B564300E59E7F /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = /usr/share/man/man1/;
@@ -23,84 +24,93 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
CD5A02232978734E00363786 /* dumpBTM.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = dumpBTM.entitlements; sourceTree = "<group>"; };
CD5A02242978758B00363786 /* Products */ = {isa = PBXFileReference; lastKnownFileType = text; name = Products; path = DerivedData/dumpBTM/Build/Products; sourceTree = SOURCE_ROOT; };
CDCA2852296A748A00944E6D /* dumpBTM */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dumpBTM; sourceTree = BUILT_PRODUCTS_DIR; };
CDCA2855296A748A00944E6D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
CD7B6A90297B564300E59E7F /* dumpBTM */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = dumpBTM; sourceTree = BUILT_PRODUCTS_DIR; };
CD7B6A93297B564300E59E7F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = main.m; path = code/main.m; sourceTree = "<group>"; };
CD7B6A9F297B5B2B00E59E7F /* dumpBTM.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dumpBTM.h; path = ../library/lib/dumpBTM.h; sourceTree = "<group>"; };
CD7B6AA2297B5B8C00E59E7F /* libDumpBTM.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libDumpBTM.a; path = ../library/lib/libDumpBTM.a; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
CDCA284F296A748A00944E6D /* Frameworks */ = {
CD7B6A8D297B564300E59E7F /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
CD7B6AA3297B5B8C00E59E7F /* libDumpBTM.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
CDCA2849296A748A00944E6D = {
CD7B6A87297B564300E59E7F = {
isa = PBXGroup;
children = (
CDCA2854296A748A00944E6D /* dumpBTM */,
CDCA2853296A748A00944E6D /* Products */,
CD7B6A92297B564300E59E7F /* tool */,
CD7B6A91297B564300E59E7F /* Products */,
CD7B6A9C297B5A9D00E59E7F /* Frameworks */,
);
sourceTree = "<group>";
};
CDCA2853296A748A00944E6D /* Products */ = {
CD7B6A91297B564300E59E7F /* Products */ = {
isa = PBXGroup;
children = (
CDCA2852296A748A00944E6D /* dumpBTM */,
CD7B6A90297B564300E59E7F /* dumpBTM */,
);
name = Products;
sourceTree = "<group>";
};
CDCA2854296A748A00944E6D /* dumpBTM */ = {
CD7B6A92297B564300E59E7F /* tool */ = {
isa = PBXGroup;
children = (
CD5A02232978734E00363786 /* dumpBTM.entitlements */,
CDCA2855296A748A00944E6D /* main.m */,
CD5A02242978758B00363786 /* Products */,
CD7B6A9F297B5B2B00E59E7F /* dumpBTM.h */,
CD7B6A93297B564300E59E7F /* main.m */,
);
path = dumpBTM;
name = tool;
sourceTree = "<group>";
};
CD7B6A9C297B5A9D00E59E7F /* Frameworks */ = {
isa = PBXGroup;
children = (
CD7B6AA2297B5B8C00E59E7F /* libDumpBTM.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
CDCA2851296A748A00944E6D /* dumpBTM */ = {
CD7B6A8F297B564300E59E7F /* tool */ = {
isa = PBXNativeTarget;
buildConfigurationList = CDCA2859296A748A00944E6D /* Build configuration list for PBXNativeTarget "dumpBTM" */;
buildConfigurationList = CD7B6A97297B564300E59E7F /* Build configuration list for PBXNativeTarget "tool" */;
buildPhases = (
CDCA284E296A748A00944E6D /* Sources */,
CDCA284F296A748A00944E6D /* Frameworks */,
CDCA2850296A748A00944E6D /* CopyFiles */,
CD7B6A8C297B564300E59E7F /* Sources */,
CD7B6A8D297B564300E59E7F /* Frameworks */,
CD7B6A8E297B564300E59E7F /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = dumpBTM;
productName = parseBTM;
productReference = CDCA2852296A748A00944E6D /* dumpBTM */;
name = tool;
productName = tool;
productReference = CD7B6A90297B564300E59E7F /* dumpBTM */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
CDCA284A296A748A00944E6D /* Project object */ = {
CD7B6A88297B564300E59E7F /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastUpgradeCheck = 1420;
TargetAttributes = {
CDCA2851296A748A00944E6D = {
CreatedOnToolsVersion = 14.1;
CD7B6A8F297B564300E59E7F = {
CreatedOnToolsVersion = 14.2;
};
};
};
buildConfigurationList = CDCA284D296A748A00944E6D /* Build configuration list for PBXProject "dumpBTM" */;
buildConfigurationList = CD7B6A8B297B564300E59E7F /* Build configuration list for PBXProject "tool" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
@@ -108,29 +118,29 @@
en,
Base,
);
mainGroup = CDCA2849296A748A00944E6D;
productRefGroup = CDCA2853296A748A00944E6D /* Products */;
mainGroup = CD7B6A87297B564300E59E7F;
productRefGroup = CD7B6A91297B564300E59E7F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
CDCA2851296A748A00944E6D /* dumpBTM */,
CD7B6A8F297B564300E59E7F /* tool */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
CDCA284E296A748A00944E6D /* Sources */ = {
CD7B6A8C297B564300E59E7F /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
CDCA2856296A748A00944E6D /* main.m in Sources */,
CD7B6A94297B564300E59E7F /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
CDCA2857296A748A00944E6D /* Debug */ = {
CD7B6A95297B564300E59E7F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
@@ -140,7 +150,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_OPTIMIZATION_PROFILE_FILE = "";
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -164,7 +173,6 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
@@ -182,7 +190,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 13.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
@@ -190,7 +198,7 @@
};
name = Debug;
};
CDCA2858296A748A00944E6D /* Release */ = {
CD7B6A96297B564300E59E7F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
@@ -200,7 +208,6 @@
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_OPTIMIZATION_PROFILE_FILE = "";
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
@@ -224,7 +231,6 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -236,43 +242,39 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MACOSX_DEPLOYMENT_TARGET = 13.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
CDCA285A296A748A00944E6D /* Debug */ = {
CD7B6A98297B564300E59E7F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = dumpBTM/dumpBTM.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = VBG97UB4TA;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.dumpbtm";
PRODUCT_NAME = "$(TARGET_NAME)";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/",
);
PRODUCT_NAME = dumpBTM;
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Debug;
};
CDCA285B296A748A00944E6D /* Release */ = {
CD7B6A99297B564300E59E7F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = dumpBTM/dumpBTM.entitlements;
CODE_SIGN_IDENTITY = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = VBG97UB4TA;
ENABLE_HARDENED_RUNTIME = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.objective-see.dumpbtm";
PRODUCT_NAME = "$(TARGET_NAME)";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/",
);
PRODUCT_NAME = dumpBTM;
PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Release;
@@ -280,25 +282,25 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
CDCA284D296A748A00944E6D /* Build configuration list for PBXProject "dumpBTM" */ = {
CD7B6A8B297B564300E59E7F /* Build configuration list for PBXProject "tool" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CDCA2857296A748A00944E6D /* Debug */,
CDCA2858296A748A00944E6D /* Release */,
CD7B6A95297B564300E59E7F /* Debug */,
CD7B6A96297B564300E59E7F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
CDCA2859296A748A00944E6D /* Build configuration list for PBXNativeTarget "dumpBTM" */ = {
CD7B6A97297B564300E59E7F /* Build configuration list for PBXNativeTarget "tool" */ = {
isa = XCConfigurationList;
buildConfigurations = (
CDCA285A296A748A00944E6D /* Debug */,
CDCA285B296A748A00944E6D /* Release */,
CD7B6A98297B564300E59E7F /* Debug */,
CD7B6A99297B564300E59E7F /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = CDCA284A296A748A00944E6D /* Project object */;
rootObject = CD7B6A88297B564300E59E7F /* Project object */;
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -3,8 +3,8 @@
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
parallelizeBuildables = "NO"
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
@@ -14,10 +14,24 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CDCA2851296A748A00944E6D"
BlueprintIdentifier = "CD7B6A78297B0CC100E59E7F"
BuildableName = "libdumpBTM.a"
BlueprintName = "library"
ReferencedContainer = "container:../Library/library.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CD7B6A8F297B564300E59E7F"
BuildableName = "dumpBTM"
BlueprintName = "dumpBTM"
ReferencedContainer = "container:dumpBTM.xcodeproj">
BlueprintName = "tool"
ReferencedContainer = "container:tool.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -45,10 +59,10 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CDCA2851296A748A00944E6D"
BlueprintIdentifier = "CD7B6A8F297B564300E59E7F"
BuildableName = "dumpBTM"
BlueprintName = "dumpBTM"
ReferencedContainer = "container:dumpBTM.xcodeproj">
BlueprintName = "tool"
ReferencedContainer = "container:tool.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
@@ -62,10 +76,10 @@
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CDCA2851296A748A00944E6D"
BlueprintIdentifier = "CD7B6A8F297B564300E59E7F"
BuildableName = "dumpBTM"
BlueprintName = "dumpBTM"
ReferencedContainer = "container:dumpBTM.xcodeproj">
BlueprintName = "tool"
ReferencedContainer = "container:tool.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>