-
-
Save scizzydo/cbd6c433782e36ee511dbaeb79485883 to your computer and use it in GitHub Desktop.
macOS x86_64 executable dylib dumper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Create the x86_64 version: | |
* clang -arch x86_64 -dynamiclib dump.c -o libdumpx86.dylib -Wno-deprecated-declarations | |
* | |
* Create the arm64 version: | |
* clang -arch arm64 -dynamiclib dump.c -o libdumparm.dylib -Wno-deprecated-declarations | |
* | |
* Create the universal binary: | |
* lipo -create -output libdump.dylib libdumpx86.dylib libdumparm.dylib | |
* | |
* Added the following entitlements to the executable for DYLD_INSERT_LIBRARIES | |
* com.apple.security.cs.allow-dyld-environment-variables | |
* com.apple.security.cs.disable-library-validation | |
* | |
* DYLD_INSERT_LIBRARIES=/path/to/libdump.dylib /path/to/executable | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <mach-o/loader.h> | |
#include <mach-o/dyld.h> | |
#include <mach-o/fat.h> | |
#include <mach-o/swap.h> | |
#include <sys/syslimits.h> | |
const struct mach_header_64* get_mach_header_64() { | |
for (uint32_t i = 0; i < _dyld_image_count(); ++i) { | |
const struct mach_header_64* mach_header_64 = (const struct mach_header_64*)_dyld_get_image_header(i); | |
if (mach_header_64->filetype == MH_EXECUTE) { | |
#ifdef __x86_64__ | |
if ((mach_header_64->cputype & CPU_TYPE_X86_64) != CPU_TYPE_X86_64) { | |
fprintf(stderr, "[ERROR] Executable is not an x86_64 file\n"); | |
#else | |
if ((mach_header_64->cputype & CPU_TYPE_ARM64) != CPU_TYPE_ARM64) { | |
fprintf(stderr, "[ERROR] Executable is not an arm64 file\n"); | |
#endif | |
exit(1); | |
} | |
fprintf(stdout, "[INFO] Executable header for '%s' found.\n", _dyld_get_image_name(i)); | |
return mach_header_64; | |
} | |
} | |
return NULL; | |
} | |
uint8_t* get_file_from_disk(const char* path, size_t* size, size_t* section_start, size_t* section_size) { | |
fprintf(stdout, "[INFO] Loading '%s' from disk.\n", path); | |
FILE* file = fopen(path, "r"); | |
if (file == NULL) { | |
fprintf(stderr, "[ERROR] Could not open executable path '%s'\n", path); | |
exit(1); | |
} | |
fseek(file, 0, SEEK_END); | |
long file_size = ftell(file); | |
fseek(file, 0, SEEK_SET); | |
*size = file_size; | |
uint8_t* buffer = (uint8_t*)calloc(file_size, 1); | |
if (buffer == NULL) { | |
fclose(file); | |
fprintf(stderr, "[ERROR] Could not allocate buffer\n"); | |
exit(1); | |
} | |
if (fread(buffer, 1, file_size, file) != file_size) { | |
fclose(file); | |
free(buffer); | |
fprintf(stderr, "[ERROR] Could not read the file '%s' to buffer\n", path); | |
exit(1); | |
} | |
fclose(file); | |
struct fat_header* fat_header = (struct fat_header*)buffer; | |
if (fat_header->magic == FAT_CIGAM || fat_header->magic == FAT_MAGIC || fat_header->magic == FAT_CIGAM_64 || fat_header->magic == FAT_MAGIC_64) { | |
bool byteswap = fat_header->magic == FAT_CIGAM || fat_header->magic == FAT_CIGAM_64; | |
if (byteswap) swap_fat_header(fat_header, 0); | |
struct fat_arch* fat_arch = (struct fat_arch*)(fat_header + 1); | |
for (uint32_t i = 0; i < fat_header->nfat_arch; ++i) { | |
if (byteswap) swap_fat_arch(fat_arch, 1, 0); | |
#ifdef __x86_64__ | |
bool our_architecture = (fat_arch->cputype & CPU_TYPE_X86_64) == CPU_TYPE_X86_64; | |
#else | |
bool our_architecture = (fat_arch->cputype & CPU_TYPE_ARM64) == CPU_TYPE_ARM64; | |
#endif | |
if (our_architecture) { | |
*section_size = fat_arch->size; | |
*section_start = fat_arch->offset; | |
break; | |
} | |
++fat_arch; | |
} | |
} | |
return buffer; | |
} | |
void __attribute__((constructor)) constructor() { | |
fprintf(stdout, " $$$$$$\\ $$$$$$\\ $$$$$$$\\\n" | |
" $$ __$$\\$$ __$$\\ $$ __$$\\\n" | |
" $$$$$$\\$$$$\\ $$$$$$\\ $$$$$$$\\$$ / $$ $$ / \\__| $$ | $$ $$\\ $$\\$$$$$$\\$$$$\\ $$$$$$\\\n" | |
" $$ _$$ _$$\\ \\____$$\\$$ _____$$ | $$ \\$$$$$$\\ $$ | $$ $$ | $$ $$ _$$ _$$\\$$ __$$\\\n" | |
" $$ / $$ / $$ |$$$$$$$ $$ / $$ | $$ |\\____$$\\ $$ | $$ $$ | $$ $$ / $$ / $$ $$ / $$ |\n" | |
" $$ | $$ | $$ $$ __$$ $$ | $$ | $$ $$\\ $$ | $$ | $$ $$ | $$ $$ | $$ | $$ $$ | $$ |\n" | |
" $$ | $$ | $$ \\$$$$$$$ \\$$$$$$$\\ $$$$$$ \\$$$$$$ | $$$$$$$ \\$$$$$$ $$ | $$ | $$ $$$$$$$ |\n" | |
" \\__| \\__| \\__|\\_______|\\_______|\\______/ \\______/ \\_______/ \\______/\\__| \\__| \\__$$ ____/\n" | |
" $$ |\n" | |
" $$ |\n" | |
" \\__|\n\n" | |
"[INFO] Close process to dump the binary with a deobfuscated __text section.\n"); | |
} | |
void __attribute__((destructor)) destructor() { | |
char destination_path[PATH_MAX]; | |
fprintf(stdout, "[INFO] Starting the dump process\n"); | |
const struct mach_header_64* mach_header_64 = get_mach_header_64(); | |
if (mach_header_64 == NULL) { | |
fprintf(stderr, "[ERROR] Failed to get executable header\n"); | |
exit(1); | |
} | |
char executable_path[PATH_MAX]; | |
uint32_t len = sizeof(executable_path); | |
if (_NSGetExecutablePath(executable_path, &len) != 0) { | |
fprintf(stderr, "[ERROR] executable_path buffer is not large enough\n"); | |
exit(1); | |
} | |
char* canonical_path = realpath(executable_path, NULL); | |
if (canonical_path != NULL) { | |
strlcpy(executable_path, canonical_path, sizeof(executable_path)); | |
free(canonical_path); | |
} | |
size_t buffer_size = 0, section_start = 0, section_size = 0; | |
uint8_t* buffer = get_file_from_disk(executable_path, &buffer_size, §ion_start, §ion_size); | |
fprintf(stdout, "[INFO] File loaded at %p with size of 0x%lx", buffer, buffer_size); | |
if (section_start != 0) | |
fprintf(stdout, " (%s mach_header_64 starting at 0x%lx)\n", ((mach_header_64->cputype & CPU_TYPE_X86_64) == CPU_TYPE_X86_64 ? "x86_64" : "arm64"), section_start); | |
else | |
fprintf(stdout, "\n"); | |
size_t offset = sizeof(struct mach_header_64); | |
for (uint32_t i = 0; i < mach_header_64->ncmds; ++i) { | |
struct load_command* load_command = (struct load_command*)((uint8_t*)mach_header_64 + offset); | |
if (load_command->cmd == LC_SEGMENT_64) { | |
struct segment_command_64* seg_command = (struct segment_command_64*)load_command; | |
struct section_64* section = (struct section_64*)(seg_command + 1); | |
for (uint32_t nsect = 0; nsect < seg_command->nsects; ++nsect) { | |
if (strncmp(seg_command->segname, SEG_TEXT, 16) == 0) { | |
if (strncmp(section->sectname, SECT_TEXT, 16) == 0) { | |
fprintf(stdout, "[INFO] Found __TEXT, __text and writing to buffer\n"); | |
memcpy(buffer + section_start + section->offset, (uint8_t*)mach_header_64 + section->offset, section->size); | |
goto start_dump; | |
} | |
} | |
++section; | |
} | |
} | |
offset += load_command->cmdsize; | |
} | |
start_dump: | |
strlcpy(destination_path, executable_path, sizeof(destination_path)); | |
#ifdef __x86_64__ | |
strlcat(destination_path, "_x86_64_deobfuscated", sizeof(destination_path)); | |
#else | |
strlcat(destination_path, "_arm64_deobfuscated", sizeof(destination_path)); | |
#endif | |
FILE* destination_file = fopen(destination_path, "w"); | |
if (destination_file == NULL) { | |
free(buffer); | |
fprintf(stderr, "[ERROR] Couldn't create the output file '%s'\n", destination_path); | |
exit(1); | |
} | |
if (fwrite(buffer + section_start, 1, section_size, destination_file) != section_size) { | |
free(buffer); | |
fclose(destination_file); | |
fprintf(stderr, "[ERROR] Couldn't write the output file\n"); | |
exit(1); | |
} | |
free(buffer); | |
fclose(destination_file); | |
fprintf(stdout, "[INFO] Dump completed\n"); | |
} |
Updated again to dump just only x86_64 or arm64, instead of packing whatever running one back into the universal binary
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks!