mkextunpack_main.c [plain text]
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/kext/fat_util.h>
#include <System/libkern/mkext.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <libc.h>
#include <mach-o/arch.h>
#include <mach-o/fat.h>
#include <mach/mach.h>
#include <mach/mach_types.h>
#include <mach/kmod.h>
#include "kext_tools_util.h"
static const char * progname = "mkextunpack";
static Boolean gVerbose = false;
u_int32_t local_adler32(u_int8_t *buffer, int32_t length);
Boolean getMkextDataForArch(
u_int8_t * fileData,
size_t fileSize,
const NXArchInfo * archInfo,
void ** mkextStart,
void ** mkextEnd,
uint32_t * mkextVersion);
CFArrayRef copyKextsFromMkext2(
void * mkextStart,
void * mkextEnd,
const NXArchInfo * archInfo);
Boolean writeMkext2EntriesToDirectory(
CFArrayRef kexts,
char * outputDirectory);
Boolean writeMkext2ToDirectory(
OSKextRef aKext,
const char * outputDirectory,
CFMutableSetRef kextNames);
CFDictionaryRef extractEntriesFromMkext1(
void * mkextStart,
void * mkextEnd);
Boolean uncompressMkext1Entry(
void * mkext_base_address,
mkext_file * entry_address,
CFDataRef * uncompressedEntry);
Boolean writeMkext1EntriesToDirectory(CFDictionaryRef entries,
char * outputDirectory);
CFStringRef createKextNameFromPlist(
CFDictionaryRef entries, CFDictionaryRef kextPlist);
int getBundleIDAndVersion(CFDictionaryRef kextPlist, unsigned index,
char ** bundle_id_out, char ** bundle_version_out);
Boolean writeFileInDirectory(
const char * basePath,
char * subPath,
const char * fileName,
const char * fileData,
size_t fileLength);
#if 0
How to unpack all arches from an mkext:
Unpack each arch in a fat mkext to a separate array of kexts.
For each arch:
- Get kext plist
- Check {kext path, id, vers} against assembled kexts:
- Already have?
- Plist identical:
- "lipo" executable into assembled
- Add all resources IF NOT PRESENT (check for different?)
- Plist different:
- cannot handle, error; or save to separate path
- Else add kext to assembled list
- Need to save infoDict, executable, resources
#endif
void usage(int num) {
fprintf(stderr, "usage: %s [-v] [-a arch] [-d output_dir] mkextfile\n", progname);
fprintf(stderr, " -d output_dir: where to put kexts (must exist)\n");
fprintf(stderr, " -a arch: pick architecture from fat mkext file\n");
fprintf(stderr, " -v: verbose output; list kexts in mkextfile\n");
return;
}
int main (int argc, const char * argv[]) {
int exit_code = 0;
char optchar;
char * outputDirectory = NULL;
const char * mkextFile = NULL;
int mkextFileFD;
struct stat stat_buf;
uint8_t * mkextFileContents = NULL;
void * mkextStart = NULL;
void * mkextEnd = NULL;
CFDictionaryRef entries = NULL;
CFArrayRef oskexts = NULL;
const NXArchInfo * archInfo = NULL;
uint32_t mkextVersion;
progname = argv[0];
OSKextSetLogOutputFunction(&tool_log);
while ((optchar = getopt(argc, (char * const *)argv, "a:d:hv")) != -1) {
switch (optchar) {
case 'd':
if (!optarg) {
fprintf(stderr, "no argument for -d\n");
usage(0);
exit_code = 1;
goto finish;
}
outputDirectory = optarg;
break;
case 'h':
usage(1);
exit_code = 0;
goto finish;
break;
case 'v':
gVerbose = true;
break;
case 'a':
if (archInfo != NULL) {
fprintf(stderr, "architecture already specified; replacing\n");
}
if (!optarg) {
fprintf(stderr, "no argument for -a\n");
usage(0);
exit_code = 1;
goto finish;
}
archInfo = NXGetArchInfoFromName(optarg);
if (!archInfo) {
fprintf(stderr, "architecture '%s' not found\n", optarg);
exit_code = 1;
goto finish;
}
break;
}
}
argc -= optind;
argv += optind;
if (argc == 0 || argc > 1) {
usage(0);
exit_code = 1;
goto finish;
}
mkextFile = argv[0];
if (!outputDirectory && !gVerbose) {
fprintf(stderr, "no work to do; please specify -d or -v\n");
usage(0);
exit_code = 1;
goto finish;
}
if (!mkextFile) {
fprintf(stderr, "no mkext file given\n");
usage(0);
exit_code = 1;
goto finish;
}
if (outputDirectory) {
if (stat(outputDirectory, &stat_buf) < 0) {
fprintf(stderr, "can't stat directory %s\n",
outputDirectory);
exit_code = 1;
goto finish;
}
if ((stat_buf.st_mode & S_IFMT) != S_IFDIR) {
fprintf(stderr, "%s is not a directory\n",
outputDirectory);
exit_code = 1;
goto finish;
}
if (access(outputDirectory, W_OK) == -1) {
fprintf(stderr, "can't write into directory %s "
"(permission denied)\n", outputDirectory);
exit_code = 1;
goto finish;
}
}
if (stat(mkextFile, &stat_buf) < 0) {
fprintf(stderr, "can't stat file %s\n", mkextFile);
exit_code = 1;
goto finish;
}
if (access(mkextFile, R_OK) == -1) {
fprintf(stderr, "can't read file %s (permission denied)\n",
mkextFile);
exit_code = 1;
goto finish;
}
mkextFileFD = open(mkextFile, O_RDONLY, 0);
if (mkextFileFD < 0) {
fprintf(stderr, "can't open file %s\n", mkextFile);
exit_code = 1;
goto finish;
}
if ( !(stat_buf.st_mode & S_IFREG) ) {
fprintf(stderr, "%s is not a regular file\n",
mkextFile);
exit_code = 1;
goto finish;
}
if (!stat_buf.st_size) {
fprintf(stderr, "%s is an empty file\n",
mkextFile);
exit_code = 1;
goto finish;
}
mkextFileContents = mmap(0, (size_t)stat_buf.st_size, PROT_READ,
MAP_FILE|MAP_PRIVATE, mkextFileFD, 0);
if (mkextFileContents == (u_int8_t *)-1) {
fprintf(stderr, "can't map file %s\n", mkextFile);
exit_code = 1;
goto finish;
}
if (!getMkextDataForArch(mkextFileContents, (size_t)stat_buf.st_size,
archInfo, &mkextStart, &mkextEnd, &mkextVersion)) {
exit_code = 1;
goto finish;
}
if (mkextVersion == MKEXT_VERS_2) {
oskexts = copyKextsFromMkext2(mkextStart, mkextEnd, archInfo);
if (!oskexts) {
exit_code = 1;
goto finish;
}
if (outputDirectory &&
!writeMkext2EntriesToDirectory(oskexts, outputDirectory)) {
exit_code = 1;
goto finish;
}
} else if (mkextVersion == MKEXT_VERS_1) {
entries = extractEntriesFromMkext1(mkextStart, mkextEnd);
if (!entries) {
fprintf(stderr, "can't unpack file %s\n", mkextFile);
exit_code = 1;
goto finish;
}
if (outputDirectory &&
!writeMkext1EntriesToDirectory(entries, outputDirectory)) {
exit_code = 1;
goto finish;
}
}
finish:
SAFE_RELEASE(oskexts);
exit(exit_code);
return exit_code;
}
Boolean getMkextDataForArch(
u_int8_t * fileData,
size_t fileSize,
const NXArchInfo * archInfo,
void ** mkextStart,
void ** mkextEnd,
uint32_t * mkextVersion)
{
Boolean result = false;
uint32_t magic;
fat_iterator fatIterator = NULL; mkext_header * mkextHeader = NULL; uint8_t * crc_address = NULL; uint32_t checksum;
*mkextStart = *mkextEnd = NULL;
*mkextVersion = 0;
magic = MAGIC32(fileData);
if (ISFAT(magic)) {
if (!archInfo) {
archInfo = NXGetLocalArchInfo();
}
fatIterator = fat_iterator_for_data(fileData,
fileData + fileSize,
1 );
if (!fatIterator) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Can't read mkext fat header.");
goto finish;
}
*mkextStart = fat_iterator_find_arch(fatIterator,
archInfo->cputype, archInfo->cpusubtype, mkextEnd);
if (!*mkextStart) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Architecture %s not found in mkext.",
archInfo->name);
goto finish;
}
} else {
*mkextStart = fileData;
*mkextEnd = fileData + fileSize;
}
mkextHeader = (mkext_header *)*mkextStart;
if ((MKEXT_GET_MAGIC(mkextHeader) != MKEXT_MAGIC) ||
(MKEXT_GET_SIGNATURE(mkextHeader) != MKEXT_SIGN)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Bad mkext magic/signature.");
goto finish;
}
if (MKEXT_GET_LENGTH(mkextHeader) !=
(*mkextEnd - *mkextStart)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Mkext length field %d does not match mkext actual size %d.",
MKEXT_GET_LENGTH(mkextHeader),
(int)(*mkextEnd - *mkextStart));
goto finish;
}
if (archInfo && MKEXT_GET_CPUTYPE(mkextHeader) != archInfo->cputype) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Mkext cputype %d does not match requested type %d (%s).",
MKEXT_GET_CPUTYPE(mkextHeader),
archInfo->cputype,
archInfo->name);
goto finish;
}
crc_address = (uint8_t *)&mkextHeader->version;
checksum = local_adler32(crc_address,
(int32_t)((uintptr_t)mkextHeader +
MKEXT_GET_LENGTH(mkextHeader) - (uintptr_t)crc_address));
if (MKEXT_GET_CHECKSUM(mkextHeader) != checksum) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Mkext checksum error.");
goto finish;
}
*mkextVersion = MKEXT_GET_VERSION(mkextHeader);
result = true;
finish:
if (fatIterator) {
fat_iterator_close(fatIterator);
}
return result;
}
CFArrayRef copyKextsFromMkext2(
void * mkextStart,
void * mkextEnd,
const NXArchInfo * archInfo)
{
CFArrayRef result = NULL; Boolean ok = false;
CFDataRef mkextDataObject = NULL; char kextPath[PATH_MAX];
char * kextIdentifier = NULL; char kextVersion[kOSKextVersionMaxLength];
if (!archInfo) {
archInfo = NXGetLocalArchInfo();
}
OSKextSetArchitecture(archInfo);
mkextDataObject = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
(UInt8 *)mkextStart, mkextEnd - mkextStart, NULL);
if (!mkextDataObject) {
OSKextLogMemError();
goto finish;
}
result = OSKextCreateKextsFromMkextData(kCFAllocatorDefault, mkextDataObject);
if (!result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Can't read mkext2 archive.");
goto finish;
}
if (gVerbose) {
CFIndex count, i;
count = CFArrayGetCount(result);
fprintf(stdout, "Found %d kexts:\n", (int)count);
for (i = 0; i < count; i++) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(result, i);
SAFE_FREE(kextIdentifier);
if (!CFURLGetFileSystemRepresentation(OSKextGetURL(theKext),
false, (UInt8 *)kextPath, sizeof(kextPath))) {
OSKextLogStringError(theKext);
goto finish;
}
kextIdentifier = createUTF8CStringForCFString(OSKextGetIdentifier(theKext));
OSKextVersionGetString(OSKextGetVersion(theKext), kextVersion,
sizeof(kextVersion));
fprintf(stdout, "%s - %s (%s)\n", kextPath, kextIdentifier,
kextVersion);
}
}
ok = true;
finish:
if (!ok) {
SAFE_RELEASE_NULL(result);
}
SAFE_RELEASE(mkextDataObject);
return result;
}
Boolean writeMkext2EntriesToDirectory(
CFArrayRef kexts,
char * outputDirectory)
{
Boolean result = false;
CFMutableSetRef kextNames = NULL; CFIndex count, i;
if (!createCFMutableSet(&kextNames, &kCFTypeSetCallBacks)) {
OSKextLogMemError();
}
count = CFArrayGetCount(kexts);
for (i = 0; i < count; i++) {
OSKextRef theKext = (OSKextRef)CFArrayGetValueAtIndex(kexts, i);
if (!writeMkext2ToDirectory(theKext, outputDirectory, kextNames)) {
goto finish;
}
}
finish:
return result;
}
Boolean writeMkext2ToDirectory(
OSKextRef aKext,
const char * outputDirectory,
CFMutableSetRef kextNames)
{
Boolean result = false;
CFDictionaryRef infoDict = NULL; CFDataRef infoDictData = NULL; CFErrorRef error = NULL; CFDataRef executable = NULL; CFURLRef kextURL = NULL; CFStringRef kextName = NULL; CFStringRef executableName = NULL; uint32_t pathLength;
char kextPath[PATH_MAX]; char * kextNameCString = NULL; char * kextNameSuffix = NULL; char * kextNameCStringAlloced = NULL; char * executableNameCStringAlloced = NULL; const void * file_data = NULL; char subPath[PATH_MAX];
CFIndex i;
infoDict = OSKextCopyInfoDictionary(aKext);
executable = OSKextCopyExecutableForArchitecture(aKext, NULL);
if (!infoDict) {
OSKextLogMemError();
goto finish;
}
kextURL = OSKextGetURL(aKext);
if (!CFURLGetFileSystemRepresentation(kextURL, true,
(UInt8 *)kextPath, sizeof(kextPath))) {
OSKextLogStringError(aKext);
goto finish;
}
pathLength = (uint32_t)strlen(kextPath);
if (pathLength && kextPath[pathLength-1] == '/') {
kextPath[pathLength-1] = '\0';
}
kextNameCString = rindex(kextPath, '/');
if (kextNameCString) {
kextNameCString++;
}
SAFE_RELEASE_NULL(kextName);
kextName = CFStringCreateWithCString(kCFAllocatorDefault,
kextNameCString, kCFStringEncodingUTF8);
if (!kextName) {
OSKextLogMemError();
goto finish;
}
if (CFSetContainsValue(kextNames, kextName)) {
kextNameSuffix = strstr(kextNameCString, ".kext");
if (!kextNameSuffix) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Bad mkext data; kext missing suffix.");
goto finish;
}
*kextNameSuffix = '\0'; }
i = 1;
while (CFSetContainsValue(kextNames, kextName)) {
SAFE_RELEASE_NULL(kextName);
kextName = CFStringCreateWithFormat(kCFAllocatorDefault,
NULL, CFSTR("%s-%zd.kext"),
kextNameCString, i);
i++;
}
CFSetAddValue(kextNames, kextName);
kextNameCStringAlloced = createUTF8CStringForCFString(kextName);
infoDictData = CFPropertyListCreateData(kCFAllocatorDefault,
infoDict, kCFPropertyListXMLFormat_v1_0, 0, &error);
if (!infoDictData) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Can't serialize kext info dictionary.");
goto finish;
}
file_data = (u_int8_t *)CFDataGetBytePtr(infoDictData);
if (!file_data) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Internal error; info dictionary has no data.");
goto finish;
}
if (snprintf(subPath, sizeof(subPath),
"%s/Contents", kextNameCStringAlloced) >= sizeof(subPath) - 1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", subPath);
goto finish;
}
if (!writeFileInDirectory(outputDirectory, subPath, "Info.plist",
file_data, CFDataGetLength(infoDictData))) {
goto finish;
}
if (!executable) {
result = true;
goto finish;
}
file_data = (u_int8_t *)CFDataGetBytePtr(executable);
if (!file_data) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Internal error; executable has no data.");
goto finish;
}
if (snprintf(subPath, sizeof(subPath),
"%s/Contents/MacOS", kextNameCStringAlloced) >= sizeof(subPath) - 1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", subPath);
goto finish;
}
executableName = CFDictionaryGetValue(infoDict, CFSTR("CFBundleExecutable"));
if (!executableName) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Kext %s has an executable but no CFBundleExecutable property.",
kextNameCStringAlloced);
goto finish;
}
executableNameCStringAlloced = createUTF8CStringForCFString(executableName);
if (!executableNameCStringAlloced) {
OSKextLogMemError();
goto finish;
}
if (!writeFileInDirectory(outputDirectory, subPath,
executableNameCStringAlloced,
file_data, CFDataGetLength(executable))) {
goto finish;
}
result = true;
finish:
SAFE_RELEASE(infoDict);
SAFE_RELEASE(infoDictData);
SAFE_RELEASE(error);
SAFE_RELEASE(executable);
SAFE_RELEASE(kextName);
SAFE_FREE(kextNameCStringAlloced);
SAFE_FREE(executableNameCStringAlloced);
return result;
}
static Boolean CaseInsensitiveEqual(CFTypeRef left, CFTypeRef right)
{
CFComparisonResult compResult;
compResult = CFStringCompare(left, right, kCFCompareCaseInsensitive);
if (compResult == kCFCompareEqualTo) {
return true;
}
return false;
}
static CFHashCode CaseInsensitiveHash(const void *key)
{
return (CFStringGetLength(key));
}
CFDictionaryRef extractEntriesFromMkext1(
void * mkextStart,
void * mkextEnd)
{
CFMutableDictionaryRef entries = NULL; Boolean error = false;
unsigned int i;
mkext1_header * mkextHeader = NULL; mkext_kext * onekext_data = 0; mkext_file * plist_file = 0; mkext_file * module_file = 0; CFStringRef entryName = NULL; CFMutableDictionaryRef entryDict = NULL; CFDataRef kextPlistDataObject = 0; CFDictionaryRef kextPlist = 0; CFStringRef errorString = NULL; CFDataRef kextExecutable = 0; CFDictionaryKeyCallBacks keyCallBacks;
keyCallBacks = kCFTypeDictionaryKeyCallBacks;
keyCallBacks.equal = &CaseInsensitiveEqual;
keyCallBacks.hash = &CaseInsensitiveHash;
entries = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&keyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!entries) {
goto finish;
}
mkextHeader = (mkext1_header *)mkextStart;
if (gVerbose) {
fprintf(stdout, "Found %u kexts:\n", MKEXT_GET_COUNT(mkextHeader));
}
for (i = 0; i < MKEXT_GET_COUNT(mkextHeader); i++) {
if (entryName) {
CFRelease(entryName);
entryName = NULL;
}
if (entryDict) {
CFRelease(entryDict);
entryDict = NULL;
}
if (kextPlistDataObject) {
CFRelease(kextPlistDataObject);
kextPlistDataObject = NULL;
}
if (kextPlist) {
CFRelease(kextPlist);
kextPlist = NULL;
}
if (errorString) {
CFRelease(errorString);
errorString = NULL;
}
if (kextExecutable) {
CFRelease(kextExecutable);
kextExecutable = NULL;
}
entryDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!entryDict) {
fprintf(stderr, "internal error.\n");
error = true;
goto finish;
}
onekext_data = &mkextHeader->kext[i];
plist_file = &onekext_data->plist;
module_file = &onekext_data->module;
if (!uncompressMkext1Entry(mkextStart,
plist_file, &kextPlistDataObject) || !kextPlistDataObject) {
fprintf(stderr, "couldn't uncompress plist at index %d.\n", i);
continue;
}
CFErrorRef error;
kextPlist = CFPropertyListCreateWithData(kCFAllocatorDefault,
kextPlistDataObject,
kCFPropertyListImmutable,
NULL,
&error);
if (!kextPlist) {
errorString = CFErrorCopyDescription(error);
CFRelease(error);
if (errorString) {
CFIndex bufsize = CFStringGetMaximumSizeForEncoding(
CFStringGetLength(errorString), kCFStringEncodingUTF8);
char * error_string = (char *)malloc((1+bufsize) * sizeof(char));
if (!error_string) {
fprintf(stderr, "memory allocation failure\n");
error = true;
goto finish;
}
if (CFStringGetCString(errorString, error_string,
bufsize, kCFStringEncodingUTF8)) {
fprintf(stderr, "error reading plist: %s",
error_string);
}
free(error_string);
} else {
fprintf(stderr, "error reading plist");
}
goto finish;
}
entryName = createKextNameFromPlist(entries, kextPlist);
if (!entryName) {
fprintf(stderr, "internal error.\n");
error = true;
goto finish;
}
CFDictionarySetValue(entryDict, CFSTR("plistData"), kextPlistDataObject);
CFDictionarySetValue(entryDict, CFSTR("plist"), kextPlist);
if (gVerbose) {
char kext_name[PATH_MAX];
char * bundle_id = NULL; char * bundle_vers = NULL;
if (!CFStringGetCString(entryName, kext_name, sizeof(kext_name) - 1,
kCFStringEncodingUTF8)) {
fprintf(stderr, "memory or string conversion error\n");
} else {
switch (getBundleIDAndVersion(kextPlist, i, &bundle_id,
&bundle_vers)) {
case 1:
fprintf(stdout, "%s - %s (%s)\n", kext_name, bundle_id,
bundle_vers);
break;
case 0:
continue;
case -1:
default:
error = true;
goto finish;
break;
}
}
}
if (OSSwapBigToHostInt32(module_file->offset) ||
OSSwapBigToHostInt32(module_file->compsize) ||
OSSwapBigToHostInt32(module_file->realsize) ||
OSSwapBigToHostInt32(module_file->modifiedsecs)) {
if (!uncompressMkext1Entry(mkextStart,
module_file, &kextExecutable)) {
fprintf(stderr, "couldn't uncompress executable at index %d.\n",
i);
continue;
}
if (kextExecutable) {
CFDictionarySetValue(entryDict, CFSTR("executable"),
kextExecutable);
}
}
CFDictionarySetValue(entries, entryName, entryDict);
}
finish:
if (error) {
if (entries) {
CFRelease(entries);
entries = NULL;
}
}
if (entryName) CFRelease(entryName);
if (entryDict) CFRelease(entryDict);
if (kextPlistDataObject) CFRelease(kextPlistDataObject);
if (kextPlist) CFRelease(kextPlist);
if (errorString) CFRelease(errorString);
if (kextExecutable) CFRelease(kextExecutable);
return entries;
}
Boolean uncompressMkext1Entry(
void * mkext_base_address,
mkext_file * entry_address,
CFDataRef * uncompressedEntry)
{
Boolean result = true;
u_int8_t * uncompressed_data = NULL; CFDataRef uncompressedData = NULL; size_t uncompressed_size = 0;
size_t offset = OSSwapBigToHostInt32(entry_address->offset);
size_t compsize = OSSwapBigToHostInt32(entry_address->compsize);
size_t realsize = OSSwapBigToHostInt32(entry_address->realsize);
time_t modifiedsecs = OSSwapBigToHostInt32(entry_address->modifiedsecs);
*uncompressedEntry = NULL;
if (realsize == 0 ||
(offset == 0 && compsize == 0 && modifiedsecs == 0)) {
goto finish;
}
uncompressed_data = malloc(realsize);
if (!uncompressed_data) {
fprintf(stderr, "malloc failure\n");
result = false;
goto finish;
}
if (compsize != 0) {
uncompressed_size = decompress_lzss(uncompressed_data,
(u_int32_t)realsize,
mkext_base_address + offset,
(u_int32_t)compsize);
if (uncompressed_size != realsize) {
fprintf(stderr, "uncompressed file is not the length "
"recorded.\n");
result = false;
goto finish;
}
} else {
bcopy(mkext_base_address + offset, uncompressed_data,
realsize);
}
uncompressedData = CFDataCreate(kCFAllocatorDefault,
(const UInt8 *)uncompressed_data, realsize);
if (!uncompressedData) {
fprintf(stderr, "malloc failure\n");
result = false;
goto finish;
}
*uncompressedEntry = uncompressedData;
finish:
if (uncompressed_data) free(uncompressed_data);
return result;
}
Boolean writeMkext1EntriesToDirectory(CFDictionaryRef entryDict,
char * outputDirectory)
{
Boolean result = false;
CFStringRef * kextNames = NULL; CFDictionaryRef * entries = NULL; char * kext_name = NULL; char * executable_name = NULL; char subPath[PATH_MAX];
unsigned int count, i;
count = (unsigned int)CFDictionaryGetCount(entryDict);
kextNames = (CFStringRef *)malloc(count * sizeof(CFStringRef));
entries = (CFDictionaryRef *)malloc(count * sizeof(CFDictionaryRef));
if (!kextNames || !entries) {
fprintf(stderr, "malloc failure\n");
result = false;
goto finish;
}
CFDictionaryGetKeysAndValues(entryDict, (const void **)kextNames,
(const void **)entries);
for (i = 0; i < count; i++) {
CFStringRef kextName = kextNames[i];
CFDictionaryRef kextEntry = entries[i];
CFStringRef executableName = NULL; CFDataRef fileData = NULL; CFDictionaryRef plist;
const void * file_data = NULL;
SAFE_FREE_NULL(kext_name);
SAFE_FREE_NULL(executable_name);
kext_name = createUTF8CStringForCFString(kextName);
if (!kext_name) {
OSKextLogMemError();
goto finish;
}
fileData = CFDictionaryGetValue(kextEntry, CFSTR("plistData"));
if (!fileData) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Kext entry %d has no plist.", i);
continue;
}
file_data = (u_int8_t *)CFDataGetBytePtr(fileData);
if (!file_data) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Kext %s has no plist.", kext_name);
continue;
}
if (snprintf(subPath, sizeof(subPath),
"%s.kext/Contents", kext_name) >= sizeof(subPath) - 1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", subPath);
goto finish;
}
if (!writeFileInDirectory(outputDirectory, subPath, "Info.plist",
file_data, CFDataGetLength(fileData))) {
goto finish;
}
fileData = CFDictionaryGetValue(kextEntry, CFSTR("executable"));
if (!fileData) {
continue;
}
file_data = (u_int8_t *)CFDataGetBytePtr(fileData);
if (!file_data) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Executable missing from kext %s.",
kext_name);
continue;
}
if (snprintf(subPath, sizeof(subPath),
"%s.kext/Contents/MacOS", kext_name) >= sizeof(subPath) - 1) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", subPath);
goto finish;
}
plist = CFDictionaryGetValue(kextEntry, CFSTR("plist"));
executableName = CFDictionaryGetValue(plist, CFSTR("CFBundleExecutable"));
if (!executableName) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Kext %s has an executable but no CFBundleExecutable property.",
kext_name);
continue;
}
executable_name = createUTF8CStringForCFString(executableName);
if (!executable_name) {
OSKextLogMemError();
goto finish;
}
if (!writeFileInDirectory(outputDirectory, subPath, executable_name,
file_data, CFDataGetLength(fileData))) {
goto finish;
}
}
result = true;
finish:
if (kextNames) free(kextNames);
if (entries) free(entries);
return result;
}
CFStringRef createKextNameFromPlist(
CFDictionaryRef entries, CFDictionaryRef kextPlist)
{
CFStringRef result = NULL; CFStringRef bundleName = NULL; CFStringRef bundleID = NULL; static unsigned int nameUnknownIndex = 1;
unsigned int dupIndex = 1;
CFArrayRef idParts = NULL;
bundleName = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleExecutable"));
if (!bundleName) {
bundleID = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleIdentifier"));
if (bundleID) {
CFIndex length;
idParts = CFStringCreateArrayBySeparatingStrings(
kCFAllocatorDefault, bundleID, CFSTR("."));
length = CFArrayGetCount(idParts);
bundleName = (CFStringRef)CFArrayGetValueAtIndex(idParts, length - 1);
if (!CFStringGetLength(bundleName)) {
bundleName = NULL;
}
}
if (!bundleName) {
result = CFStringCreateWithFormat(kCFAllocatorDefault,
NULL, CFSTR("NameUnknown-%d"), nameUnknownIndex);
nameUnknownIndex++;
goto finish;
}
}
if (bundleName) {
if (CFDictionaryGetValue(entries, bundleName)) {
for ( ; ; dupIndex++) {
result = CFStringCreateWithFormat(kCFAllocatorDefault,
NULL, CFSTR("%@-%d"), bundleName, dupIndex);
if (!CFDictionaryGetValue(entries, result)) {
goto finish;
}
CFRelease(result);
result = NULL;
}
} else {
result = CFRetain(bundleName);
goto finish;
}
}
finish:
if (idParts) CFRelease(idParts);
return result;
}
int getBundleIDAndVersion(CFDictionaryRef kextPlist, unsigned index,
char ** bundle_id_out, char ** bundle_version_out)
{
int result = 1;
CFStringRef bundleID = NULL; CFStringRef bundleVersion = NULL; static char bundle_id[KMOD_MAX_NAME];
static char bundle_vers[KMOD_MAX_NAME];
bundleID = CFDictionaryGetValue(kextPlist, CFSTR("CFBundleIdentifier"));
if (!bundleID) {
fprintf(stderr, "kext entry %d has no CFBundleIdentifier\n", index);
result = 0;
goto finish;
} else if (CFGetTypeID(bundleID) != CFStringGetTypeID()) {
fprintf(stderr, "kext entry %d, CFBundleIdentifier is not a string\n", index);
result = 0;
goto finish;
} else {
if (!CFStringGetCString(bundleID, bundle_id, sizeof(bundle_id) - 1,
kCFStringEncodingUTF8)) {
fprintf(stderr, "memory or string conversion error\n");
result = -1;
goto finish;
}
}
bundleVersion = CFDictionaryGetValue(kextPlist,
CFSTR("CFBundleVersion"));
if (!bundleVersion) {
fprintf(stderr, "kext entry %d has no CFBundleVersion\n", index);
result = 0;
goto finish;
} else if (CFGetTypeID(bundleVersion) != CFStringGetTypeID()) {
fprintf(stderr, "kext entry %d, CFBundleVersion is not a string\n", index);
result = 0;
goto finish;
} else {
if (!CFStringGetCString(bundleVersion, bundle_vers,
sizeof(bundle_vers) - 1, kCFStringEncodingUTF8)) {
fprintf(stderr, "memory or string conversion error\n");
result = -1;
goto finish;
}
}
if (bundle_id_out) {
*bundle_id_out = bundle_id;
}
if (bundle_version_out) {
*bundle_version_out = bundle_vers;
}
finish:
return result;
}
Boolean writeFileInDirectory(
const char * basePath,
char * subPath,
const char * fileName,
const char * fileData,
size_t fileLength)
{
Boolean result = false;
char path[PATH_MAX];
char * pathComponent = NULL; char * pathComponentEnd = NULL; int fd = -1;
uint32_t bytesWritten;
if (strlcpy(path, basePath, sizeof(path)) >= sizeof(path)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", basePath);
goto finish;
}
pathComponent = subPath;
while (pathComponent) {
pathComponentEnd = index(pathComponent, '/');
if (pathComponentEnd) {
*pathComponentEnd = '\0';
}
if (strlcat(path, "/", sizeof(path)) >= sizeof(path) ||
strlcat(path, pathComponent, sizeof(path)) >= sizeof(path)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", path);
goto finish;
}
if ((mkdir(path, 0777) < 0) && (errno != EEXIST)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't create directory %s - %s", path, strerror(errno));
goto finish;
}
if (pathComponentEnd) {
*pathComponentEnd = '/';
pathComponent = pathComponentEnd + 1;
pathComponentEnd = NULL;
} else {
break;
}
}
if (strlcat(path, "/", sizeof(path)) >= sizeof(path) ||
strlcat(path, fileName, sizeof(path)) >= sizeof(path)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Output path is too long - %s.", path);
goto finish;
}
fd = open(path, O_WRONLY | O_CREAT, 0777);
if (fd < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't open %s for writing - %s.", path, strerror(errno));
goto finish;
}
bytesWritten = 0;
while (bytesWritten < fileLength) {
int writeResult;
writeResult = (int)write(fd, fileData + bytesWritten,
fileLength - bytesWritten);
if (writeResult < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Write failed for %s - %s.", path, strerror(errno));
goto finish;
}
bytesWritten += writeResult;
}
result = true;
finish:
if (fd != -1) {
close(fd);
}
return result;
}