// SPDX-License-Identifier: GPL-2.0-or-later // Copyright 2022 BitWagon Software LLC. All rights reserved. /* clang-format off */ #include #include #include #include #include #include #include #include #ifdef __APPLE__ //{ // /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/loader.h #include #include #else //}{ enum { // cmd LC_REQ_DYLD = 0x80000000, // OR'ed ==> must not ignore LC_SEGMENT = 0x1, LC_SYMTAB = 0x2, LC_THREAD = 0x4, LC_UNIXTHREAD = 0x5, LC_DYSYMTAB = 0xb, LC_LOAD_DYLIB = 0xc, LC_ID_DYLIB = 0xd, LC_LOAD_DYLINKER = 0xe, LC_ID_DYLINKER = 0xf, LC_ROUTINES = 0x11, LC_TWOLEVEL_HINTS= 0x16, LC_LOAD_WEAK_DYLIB= (0x18 | LC_REQ_DYLD), LC_SEGMENT_64 = 0x19, LC_ROUTINES_64 = 0x1a, LC_UUID = 0x1b, LC_RPATH = 0x1c, LC_CODE_SIGNATURE = 0x1d, LC_SEGMENT_SPLIT_INFO = 0x1e, LC_REEXPORT_DYLIB = (0x1f | LC_REQ_DYLD), LC_LAZY_LOAD_DYLIB= 0x20, LC_ENCRYPTION_INFO= 0x21, LC_DYLD_INFO = 0x22, // compressed dyld information (10.6.x) LC_DYLD_INFO_ONLY = (0x22|LC_REQ_DYLD), LC_VERSION_MIN_MACOSX= 0x24, LC_VERSION_MIN_IPHONEOS= 0x25, LC_FUNCTION_STARTS= 0x26, LC_DYLD_ENVIRONMENT= 0x27, // string as environment variable LC_MAIN = (0x28|LC_REQ_DYLD), LC_DATA_IN_CODE = 0x29, LC_SOURCE_VERSION = 0x2a, LC_DYLIB_CODE_SIGN_DRS= 0x2B, LC_ENCRYPTION_INFO_64= 0x2C, LC_LINKER_OPTIMIZATION_HINT = 0x2E, LC_VERSION_MIN_TVOS= 0x2F, LC_VERSION_MIN_WATCHOS= 0x30, LC_NOTE = 0x31, LC_BUILD_VERSION = 0x32, LC_DYLD_EXPORTS_TRIE = (0x33|LC_REQ_DYLD), LC_DYLD_CHAINED_FIXUPS = (0x34|LC_REQ_DYLD), LC_FILESET_ENTRY = (0x35|LC_REQ_DYLD), }; #include struct mach_header_64 { uint32_t magic; uint32_t cputype; uint32_t cpusubtype; uint32_t filetype; uint32_t ncmds; uint32_t sizeofcmds; uint32_t flags; uint32_t reserved; }; struct load_command { uint32_t cmd; uint32_t cmdsize; }; struct segment_command_64 { uint32_t cmd; uint32_t cmdsize; char segname[16]; uint64_t vmaddr; uint64_t vmsize; uint64_t fileoff; uint64_t filesize; uint32_t maxprot; uint32_t initprot; uint32_t nsects; uint32_t flags; }; struct linkedit_data_command { uint32_t cmd; uint32_t cmdsize; uint32_t dataoff; uint32_t datasize; }; struct symtab_command { uint32_t cmd; uint32_t cmdsize; uint32_t symoff; uint32_t nsyms; uint32_t stroff; uint32_t strsize; }; struct dysymtab_command { uint32_t cmd; uint32_t cmdsize; uint32_t ilocalsym; uint32_t nlocalsym; uint32_t iextdefsym; uint32_t nextdefsym; uint32_t iundefsym; uint32_t nundefsym; uint32_t tocoff; uint32_t ntoc; uint32_t modtaboff; uint32_t nmodtab; uint32_t extrefsymoff; uint32_t nextrefsyms; uint32_t indirectsymoff; uint32_t nindirectsyms; uint32_t extreloff; uint32_t nextrel; uint32_t locreloff; uint32_t nlocrel; }; struct nlist_64 { union { uint32_t n_strx; } n_un; uint8_t n_type; uint8_t n_sect; uint16_t n_desc; uint64_t n_value; }; #endif //} struct Cmd_names { unsigned char val; char name[23]; } const cmd_names[] = { [ 0x1] = { 0x1, "LC_SEGMENT"}, [ 0x2] = { 0x2, "LC_SYMTAB"}, [ 0x4] = { 0x4, "LC_THREAD"}, [ 0x5] = { 0x5, "LC_UNIXTHREAD"}, [ 0xb] = { 0xb, "LC_DYSYMTAB"}, [ 0xc] = { 0xc, "LC_LOAD_DYLIB"}, [ 0xd] = { 0xd, "LC_ID_DYLIB"}, [ 0xe] = { 0xe, "LC_LOAD_DYLINKER"}, [ 0xf] = { 0xf, "LC_ID_DYLINKER"}, [ 0x11] = { 0x11, "LC_ROUTINES"}, [ 0x16] = { 0x16, "LC_TWOLEVEL_HINTS"}, [ (0x18 /*|LC_REQ_DYLD*/ )] = {0x18, "LC_LOAD_WEAK_DYLIB"}, [ 0x19] = { 0x19, "LC_SEGMENT_64"}, [ 0x1a] = { 0x1a, "LC_ROUTINES_64"}, [ 0x1b] = { 0x1b, "LC_UUID"}, [ 0x1c] = { 0x1c, "LC_RPATH"}, [ 0x1d] = { 0x1d, "LC_CODE_SIGNATURE"}, [ 0x1e] = { 0x1e, "LC_SEGMENT_SPLIT_INFO"}, [ (0x1f /*|LC_REQ_DYLD*/ )] = { 0x1f, "LC_REEXPORT_DYLIB"}, [ 0x20] = { 0x20, "LC_LAZY_LOAD_DYLIB"}, [ 0x21] = { 0x21, "LC_ENCRYPTION_INFO"}, // [ 0x22] = { 0x22, "LC_DYLD_INFO" // compressed dyld information (10.6.x) [ (0x22 /*|LC_REQ_DYLD*/ )] = { 0x22, "LC_DYLD_INFO_ONLY"}, [ 0x24] = { 0x24, "LC_VERSION_MIN_MACOSX"}, [ 0x25] = { 0x25, "LC_VERSION_MIN_IPHONEOS"}, [ 0x26] = { 0x26, "LC_FUNCTION_STARTS"}, [ 0x27] = { 0x27, "LC_DYLD_ENVIRONMENT"}, // string as environment variable [ (0x28 /*|LC_REQ_DYLD*/ )] = { 0x28, "LC_MAIN"}, [ 0x29] = { 0x29, "LC_DATA_IN_CODE"}, [ 0x2a] = { 0x2a, "LC_SOURCE_VERSION"}, [ 0x2B] = { 0x2B, "LC_DYLIB_CODE_SIGN_DRS"}, [ 0x2C] = { 0x2C, "LC_ENCRYPTION_INFO_64"}, [ 0x2F] = { 0x2F, "LC_VERSION_MIN_TVOS"}, [ 0x30] = { 0x30, "LC_VERSION_MIN_WATCHOS"}, [ 0x31] = { 0x31, "LC_NOTE"}, [ 0x32] = { 0x32, "LC_BUILD_VERSION"}, [(0x33 /*|LC_REQ_DYLD*/ )] = {0x33, "LC_DYLD_EXPORTS_TRIE"}, [(0x34 /*|LC_REQ_DYLD*/ )] = {0x34, "LC_DYLD_CHAINED_FIXUPS"}, [(0x35 /*|LC_REQ_DYLD*/ )] = {0x35, "LC_FILESET_ENTRY"}, }; // Remove (cut out, "snip") named loader_commands from a Macho-O file. // Try to enable success of running "codesign -s - file" afterwards. // Note that LC_CODE_SIGNATURE should be removed before snipping: // codesign --remove-signature file // // This is EXPERIMENTAL to aid in finding a "minimal" executable // on Apple MacOS Big Sur, particularly Apple M1 hardware (aarch64). int main(int argc, char const * /*const*/ *const argv, char const *const *const envp) { struct stat st; int fd; int err1; int res2; int err2; int prot = PROT_READ | PROT_WRITE; int flags = MAP_FIXED | MAP_SHARED; void *const awant = (void *)(0x18L << 28); // above user, below dylibs char *addr; if (argc < 3) { fprintf(stderr, "Usage: macho-snip file loader_cmd...\n"); exit(1); } fd = open(argv[1], O_RDWR, 0); err1 = errno; if (fd < 0) { perror(argv[1]); fprintf(stderr, "Trying readonly...\n"); flags = MAP_FIXED | MAP_PRIVATE; fd = open(argv[1], O_RDONLY, 0); } res2 = fstat(fd, &st); err2 = errno; if (fd < 0) { errno = err1; perror(argv[1]); exit(1); } if (0!=res2) { errno = err2; perror(argv[1]); exit(1); } addr = mmap(awant, st.st_size, prot, flags, fd, 0); if (awant!=addr) { perror(argv[1]); exit(1); } fprintf(stderr,"%zd (%#zx) bytes at %p\n", (long)st.st_size, (long)st.st_size, addr); unsigned long argv_done = 0; // set of bits struct segment_command_64 *linkedit = 0; struct mach_header_64 *const mhdr = (struct mach_header_64 *)addr; unsigned ncmds = mhdr->ncmds; unsigned headway = mhdr->sizeofcmds; struct load_command *cmd = (struct load_command *)(1+ mhdr); struct load_command *cmd_next; unsigned delta_dataoff = 0; for (; ncmds; --ncmds, cmd = cmd_next) { unsigned end_dataoff = 0; unsigned end_datasize = 0; again: ; fprintf(stderr, "cmd@%p %s %d(%#x)\n", cmd, cmd_names[cmd->cmd&0xFF].name, cmd->cmd&0xFF, cmd->cmd); unsigned const cmdsize = cmd->cmdsize; if (headway < cmdsize ) { } else { headway -= cmdsize; cmd_next = (struct load_command *)(cmdsize + (void *)cmd); } switch (cmd->cmd &~ LC_REQ_DYLD) { int jargv; case LC_SEGMENT_64: { struct segment_command_64 *seg = (struct segment_command_64 *)cmd; if (!strcmp("__LINKEDIT", seg->segname)) { linkedit = seg; } } break; case LC_CODE_SIGNATURE: { fprintf(stderr, "macho-snip: use 'codesign --remove-signature' to remove LC_CODE_SIGNATURE\n"); } break; //struct nlist_64 { // union { // uint32_t n_strx; /* index into the string table */ // } n_un; // uint8_t n_type; /* type flag, see below */ // uint8_t n_sect; /* section number or NO_SECT */ // uint16_t n_desc; /* see */ // uint64_t n_value; /* value of this symbol (or stab offset) */ //}; // // The string table has an extra entry at the front: " " (one space) // so that the first actual string has an index of 2. // See the comment which follows the definition of struct nlist_64 in // /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/mach-o/nlist.h // // The string table must be zero-padded to a multiple of 8 bytes. // codesign requires that the string table must be last in __LINKEDIT: // (__LINKEDIT.filesize + .fileoff) == (LC_SYMTAB.strsize + .stroff) // [The LC_CODE_SIGNATURE.dataoff also aligns itself to (0 mod 16), // which is peculiar because the data array of sha256 values (one 32-byte // value per 4KB page) has offset (4 mod 16) instead of (0 mod 16).] case LC_SYMTAB: { fprintf(stderr, "macho-snip: LC_SYMTAB skipped\n"); continue; struct symtab_command *symcmd = (struct symtab_command *)cmd; if (( symcmd->strsize + symcmd->stroff) != (linkedit->filesize + linkedit->fileoff)) { fprintf(stderr,"macho-snip: bad LC_SYMTAB string table\n"); } // find beginning of last name string unsigned j; struct nlist_64 *const symp = (struct nlist_64 *)(symcmd->symoff + addr); char *const namp0 = symcmd->stroff + addr; char *namp; for (j=0, namp = namp0; j < symcmd->nsyms; ++j) { namp += 1+ strlen(namp); if (symp[j].n_un.n_strx != (namp - namp0)) { fprintf(stderr, "macho-snip: bad .n_strx\n"); } } unsigned pad = 7& -(unsigned long)namp; memset(namp, 0, pad); namp += pad; // zero pad to (0 mod 8) symcmd->strsize = namp - namp0; linkedit->filesize = (namp - addr) - linkedit->fileoff; symcmd->nsyms -= 1; // lop last symbol FIXME: generalize } break; case LC_DYSYMTAB: { fprintf(stderr, "macho-snip: LD_DYSYMTAB skipped\n"); continue; struct dysymtab_command *dysym = (struct dysymtab_command *)cmd; if (0==(dysym->nundefsym -= 1)) { // FIXME: generalize dysym->iundefsym = 0; } } break; case LC_BUILD_VERSION: case LC_DYLD_INFO: // also LC_DYLD_INFO_ONLY because low 8 bits case LC_LOAD_DYLIB: case LC_LOAD_DYLINKER: case LC_MAIN: case LC_SOURCE_VERSION: case LC_UUID: { for (jargv = 2; jargv < argc; ++jargv) { if (argv[jargv] && !strcmp(cmd_names[cmd->cmd & 0xFF].name, argv[jargv])) { argv_done |= 1uL << jargv; fprintf(stderr, "macho-snip: %#x, %s\n", cmd_names[cmd->cmd & 0xFF].val, cmd_names[cmd->cmd & 0xFF].name); // EXPERIMENT: if (cmd->cmd == LC_DYLD_INFO_ONLY) { // the "must process" case struct dyld_info_command *dyldcmd = (struct dyld_info_command *)cmd; dyldcmd->export_off = 0; dyldcmd->export_size = 0; goto next; // EXPERIMENT } goto snip; } } } break; case LC_DATA_IN_CODE: { case LC_DYLD_EXPORTS_TRIE: case LC_DYLD_CHAINED_FIXUPS: case LC_DYLIB_CODE_SIGN_DRS: case LC_FUNCTION_STARTS: case LC_LINKER_OPTIMIZATION_HINT: case LC_SEGMENT_SPLIT_INFO: { for (jargv = 2; jargv < argc; ++jargv) { if (argv[jargv] && !strcmp(cmd_names[cmd->cmd & 0xFF].name, argv[jargv])) { argv_done |= 1uL << jargv; fprintf(stderr, "macho-snip: %#x, %s\n", cmd_names[cmd->cmd & 0xFF].val, cmd_names[cmd->cmd & 0xFF].name); goto snip_linkedit_data_command; } } } break; } continue; // no changes ==> advance snip_linkedit_data_command: ; struct linkedit_data_command *ldc = (struct linkedit_data_command *)cmd; end_datasize = ldc->datasize; end_dataoff = ldc->datasize + ldc->dataoff; memset(addr + ldc->dataoff, 0, end_datasize); // the linkedit_data if ((linkedit->fileoff + linkedit->filesize) == end_dataoff) { linkedit->filesize -= end_datasize; // trim } snip: ; memmove(cmd, cmd_next, headway); memset(headway + (char *)cmd, 0, cmdsize); // space that was vacated cmd_next = cmd; // we moved tail at *cmd_next to *cmd mhdr->sizeofcmds -= cmdsize; mhdr->ncmds -= 1; argv[jargv] = 0; // snip only once per argv[] } // switch next: ; } // ncmds argv_done |= (1<<1) | (1<<0); // argv[0,1] do not name linker_commands if (~(~0uL << argc) != argv_done) { int j; for (j=2; j < argc; ++j) { if (!((1uL << j) & argv_done)) { fprintf(stderr, "macho-snip warning: %s not processed\n", argv[j]); } } } if (!(MAP_SHARED & flags)) { write(1, addr, st.st_size); } return 0; // success } /* vim:set ts=4 sw=4 et: */