Reading data (embedded plist) linked to executable through -sectcreate __TEXT
I am linking a executable with a plist using -sectcrea开发者_JAVA技巧te __TEXT
linker flags. Reason for this is mainly to use the SMJobBless() method. But I need to read plist linked from another application. This is only because I need to install the same privileged application on a 10.5 system and I can’t use SMJobBless() on 10.5.
How do I read this linked plist using Objective-C so I can copy it to /Library/LaunchDaemons/ myself?
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.
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];
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.
A much simpler approach:
#include <mach-o/getsect.h>
unsigned long *len;
char *data = getsectdata("__TEXT", "__info_plist");
man getsectdata. There's lots of examples on how to access various pieces (current executable, arbitrary executable, framework, etc).
Since the otool command from the accepted answer does not seem to work anymore (produces gibberish on macOS Monterey), here is how you can still read an embedded Info.plist from a mach-o on the command line:
otool -P ./binary
From the man pages:
-P Print the info plist section, (__TEXT,__info_plist), as strings.
Similarly to the original question I needed to read info and launchd property lists embedded in an executable with -sectcreate __TEXT
. As it's 2021 I'm using Swift, but couldn't find anything existing to meet my needs. It took me a bit to figure out how, so I decided to package it up: EmbeddedPropertyList.
If you run into any issues using it, either reply here on open an issue on Github.
精彩评论