Reading data (embedded plist) linked to executable through -sectcreate __TEXT
otool
You can use otool(1) to dump the contents of the section containing the embedded plist:
otool -s __TEXT __info_plist /path/to/executable
and then pipe its output to xxd(1) in order to obtain the corresponding ASCII representation:
otool -X -s __TEXT __info_plist /path/to/executable | xxd -r
However, otool is only available in machines where Xcode has been installed.
NSBundle
For the cases where a program needs to read its own embedded plist, NSBundle can be used:
id someValue = [[NSBundle mainBundle] objectForInfoDictionaryKey:someKey];
Mach-O
For the cases where a program needs to read the embedded plist of an arbitrary file without resorting to otool, the program can parse the Mach-O information in the file and extract its embedded plist as follows:
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <mach-o/loader.h>
#include <sys/mman.h>
#include <sys/stat.h>
#import <Foundation/Foundation.h>
id embeddedPlist(NSURL *executableURL) {
id plist = nil;
int fd;
struct stat stat_buf;
size_t size;
char *addr = NULL;
char *start_addr = NULL;
struct mach_header_64 *mh = NULL;
struct load_command *lc = NULL;
struct segment_command_64 *sc = NULL;
struct section_64 *sect = NULL;
// Open the file and get its size
fd = open([[executableURL path] UTF8String], O_RDONLY);
if (fd == -1) goto END_FUNCTION;
if (fstat(fd, &stat_buf) == -1) goto END_FILE;
size = stat_buf.st_size;
// Map the file to memory
addr = start_addr = mmap(0, size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) goto END_FILE;
// The first bytes are the Mach-O header
mh = (struct mach_header_64 *)addr;
// Load commands follow the header
addr += sizeof(struct mach_header_64);
for (int icmd = 0; icmd < mh->ncmds; icmd++) {
lc = (struct load_command *)addr;
if (lc->cmd != LC_SEGMENT_64) {
addr += lc->cmdsize;
continue;
}
if (lc->cmdsize == 0) continue;
// It's a 64-bit segment
sc = (struct segment_command_64 *)addr;
if (strcmp("__TEXT", sc->segname) != 0 || sc->nsects == 0) {
addr += lc->cmdsize;
continue;
}
// It's the __TEXT segment and it has at least one section
// Section data follows segment data
addr += sizeof(struct segment_command_64);
for (int isect = 0; isect < sc->nsects; isect++) {
sect = (struct section_64 *)addr;
addr += sizeof(struct section_64);
if (strcmp("__info_plist", sect->sectname) != 0) continue;
// It's the __TEXT __info_plist section
NSData *data = [NSData dataWithBytes:(start_addr + sect->offset)
length:sect->size];
plist = [NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListImmutable
format:NULL
error:NULL];
goto END_MMAP;
}
}
END_MMAP:
munmap(addr, size);
END_FILE:
close(fd);
END_FUNCTION:
return plist;
}
and:
NSURL *url = [NSURL fileURLWithPath:@"/path/to/some/file"];
id plist = embeddedPlist(url);
if ([plist isKindOfClass:[NSDictionary class]]) {
NSDictionary *info = plist;
id someValue = [info objectForKey:someKey];
}
Note that embeddedPlist()
has some limitations: it expects the file to be a thin Mach-O file (i.e., it will crash with non-Mach-O files and it won’t work with fat files containing, for example, both i386 and x86_64 Mach-O data); it only works with x86_64 files; it doesn’t report errors.
I went ahead and released BVPlistExtractor under the MIT licence. It detects whether the file is indeed a thin Mach-O file or a fat/universal file, and works with both i386 and x86_64.
User's computer would not probably have otool
installed, and I had the same problem. The solution was to use launchctl
, which is guaranteed to be present at any modern Mac.
It has a plist
subcommand which does the following:
Prints the the property list embedded in the __TEXT,__info_plist segment/section
of the target Mach-O or the specified segment/section.
If you do not specify the section, it prints __TEXT by default. The only argument to provide is the path to the executable:
launchctl plist /Library/PrivilegedHelperTools/com.sparklabs.ViscosityHelper
If you have given path, output might be something like this:
{
"CFBundleIdentifier" = "com.sparklabs.ViscosityHelper";
"SMAuthorizedClients" = (
"anchor apple generic and identifier "com.viscosityvpn.Viscosity" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "34XR7GXFPX")";
);
"CFBundleName" = "ViscosityHelper";
"CFBundleVersion" = "548";
"CFBundleInfoDictionaryVersion" = "6.0";
};
It can be used in command-line as well as from code via NSTask
(Process
in swift) class.
There's a CoreFoundation function for that: CFBundleCopyInfoDictionaryForURL()
. From the documentation:
For a directory URL, this is equivalent to
CFBundleCopyInfoDictionaryInDirectory
. For a plain file URL representing an unbundled application, this function will attempt to read an information dictionary either from the (__TEXT
,__info_plist
) section of the file (for a Mach-O file) or from aplst
resource.
It's available on Mac OS X v10.2 and later. If you use in Cocoa you can do this (provided you have an (NSURL*)url
for the bundle):
NSDictionary* infoPlist = [ (NSDictionary*) CFBundleCopyInfoDictionaryForURL( (CFURLRef) url ) autorelease];