Skip to content

Instantly share code, notes, and snippets.

@scizzydo

scizzydo/dump.c Secret

Last active April 12, 2024 23:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save scizzydo/cbd6c433782e36ee511dbaeb79485883 to your computer and use it in GitHub Desktop.
Save scizzydo/cbd6c433782e36ee511dbaeb79485883 to your computer and use it in GitHub Desktop.
macOS x86_64 executable dylib dumper
/*
* 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, &section_start, &section_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");
}
@ChrisKader
Copy link

Thanks!

@scizzydo
Copy link
Author

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