#include #include #include #include #include #include #include #include #include #include using namespace std; [[noreturn]] void print_help(void) { fprintf(stderr, "Append an LC_NOTE to a corefile. Usage: \n"); fprintf(stderr, " -i|--input \n"); fprintf(stderr, " -o|--output \n"); fprintf(stderr, " -n|--name \n"); fprintf( stderr, " -r|--remove-dups remove existing LC_NOTEs with this same name\n"); fprintf(stderr, " One of:\n"); fprintf(stderr, " -f|--file \n"); fprintf(stderr, " -s|--str \n"); exit(1); } void parse_args(int argc, char **argv, string &infile, string &outfile, string ¬e_name, vector &payload, bool &remove_dups) { const char *const short_opts = "i:o:n:f:s:hr"; const option long_opts[] = {{"input", required_argument, nullptr, 'i'}, {"output", required_argument, nullptr, 'o'}, {"name", required_argument, nullptr, 'n'}, {"file", required_argument, nullptr, 'f'}, {"str", required_argument, nullptr, 's'}, {"remove-dups", no_argument, nullptr, 'r'}, {"help", no_argument, nullptr, 'h'}, {nullptr, no_argument, nullptr, 0}}; optional infile_str, outfile_str, name_str, payload_file_str, payload_str; remove_dups = false; while (true) { const auto opt = getopt_long(argc, argv, short_opts, long_opts, nullptr); if (opt == -1) break; switch (opt) { case 'i': infile_str = optarg; break; case 'o': outfile_str = optarg; break; case 'n': name_str = optarg; break; case 'f': payload_file_str = optarg; break; case 's': payload_str = optarg; break; case 'r': remove_dups = true; break; case 'h': print_help(); } } if (!infile_str || !outfile_str || !name_str || (!payload_file_str && !payload_str)) print_help(); infile = *infile_str; outfile = *outfile_str; note_name = *name_str; if (payload_str) { payload.resize(payload_str->size(), 0); memcpy(payload.data(), payload_str->c_str(), payload_str->size()); } else { struct stat sb; if (stat(payload_file_str->c_str(), &sb)) { fprintf(stderr, "File '%s' does not exist.\n", payload_file_str->c_str()); exit(1); } payload.resize(sb.st_size, 0); FILE *f = fopen(payload_file_str->c_str(), "r"); fread(payload.data(), 1, sb.st_size, f); fclose(f); } } struct all_image_infos_header { uint32_t version; // currently 1 uint32_t imgcount; // number of binary images uint64_t entries_fileoff; // file offset in the corefile of where the array of // struct entry's begin. uint32_t entry_size; // size of 'struct entry'. uint32_t unused; // set to 0 }; struct image_entry { uint64_t filepath_offset; // corefile offset of the c-string filepath, // if available, else this should be set // to UINT64_MAX. uuid_t uuid; // uint8_t[16]. should be set to all zeroes if // uuid is unknown. uint64_t load_address; // virtual addr of mach-o header, UINT64_MAX if unknown. uint64_t seg_addrs_offset; // corefile offset to the array of struct // segment_vmaddr's, UINT64_MAX if none. uint32_t segment_count; // The number of segments for this binary, 0 if none. uint32_t executing; // Set to 0 if executing status is unknown by corefile // creator. // Set to 1 if this binary was executing on any thread, // so it can be force-loaded by the corefile reader. // Set to 2 if this binary was not executing on any thread. }; int count_lc_notes_with_name(FILE *in, std::string name) { fseeko(in, 0, SEEK_SET); uint8_t magic[4]; if (fread(magic, 1, 4, in) != 4) { printf("Failed to read magic number\n"); return 0; } uint8_t magic_32_le[] = {0xce, 0xfa, 0xed, 0xfe}; uint8_t magic_64_le[] = {0xcf, 0xfa, 0xed, 0xfe}; if (memcmp(magic, magic_32_le, 4) != 0 && memcmp(magic, magic_64_le, 4) != 0) { return 0; } fseeko(in, 0, SEEK_SET); int number_of_load_cmds = 0; size_t size_of_mach_header = 0; if (memcmp(magic, magic_64_le, 4) == 0) { struct mach_header_64 mh; size_of_mach_header = sizeof(mh); if (fread(&mh, sizeof(mh), 1, in) != 1) { fprintf(stderr, "unable to read mach header\n"); return 0; } number_of_load_cmds = mh.ncmds; } else { struct mach_header mh; size_of_mach_header = sizeof(mh); if (fread(&mh, sizeof(mh), 1, in) != 1) { fprintf(stderr, "unable to read mach header\n"); return 0; } number_of_load_cmds = mh.ncmds; } int notes_seen = 0; fseeko(in, size_of_mach_header, SEEK_SET); for (int i = 0; i < number_of_load_cmds; i++) { off_t cmd_start = ftello(in); uint32_t cmd, cmdsize; fread(&cmd, sizeof(uint32_t), 1, in); fread(&cmdsize, sizeof(uint32_t), 1, in); fseeko(in, cmd_start, SEEK_SET); off_t next_cmd = cmd_start + cmdsize; if (cmd == LC_NOTE) { struct note_command note; fread(¬e, sizeof(note), 1, in); if (strncmp(name.c_str(), note.data_owner, 16) == 0) notes_seen++; } fseeko(in, next_cmd, SEEK_SET); } return notes_seen; } void copy_and_add_note(FILE *in, FILE *out, std::string lc_note_name, vector payload_data, bool remove_dups) { int number_of_load_cmds = 0; off_t header_start = ftello(in); int notes_to_remove = 0; if (remove_dups) notes_to_remove = count_lc_notes_with_name(in, lc_note_name); fseeko(in, header_start, SEEK_SET); uint8_t magic[4]; if (fread(magic, 1, 4, in) != 4) { printf("Failed to read magic number\n"); return; } uint8_t magic_32_le[] = {0xce, 0xfa, 0xed, 0xfe}; uint8_t magic_64_le[] = {0xcf, 0xfa, 0xed, 0xfe}; if (memcmp(magic, magic_32_le, 4) != 0 && memcmp(magic, magic_64_le, 4) != 0) { return; } fseeko(in, header_start, SEEK_SET); off_t end_of_infine_loadcmds; size_t size_of_mach_header = 0; if (memcmp(magic, magic_64_le, 4) == 0) { struct mach_header_64 mh; size_of_mach_header = sizeof(mh); if (fread(&mh, sizeof(mh), 1, in) != 1) { fprintf(stderr, "unable to read mach header\n"); return; } number_of_load_cmds = mh.ncmds; end_of_infine_loadcmds = sizeof(mh) + mh.sizeofcmds; mh.ncmds += 1; mh.ncmds -= notes_to_remove; mh.sizeofcmds += sizeof(struct note_command); mh.sizeofcmds -= notes_to_remove * sizeof(struct note_command); fseeko(out, header_start, SEEK_SET); fwrite(&mh, sizeof(mh), 1, out); } else { struct mach_header mh; size_of_mach_header = sizeof(mh); if (fread(&mh, sizeof(mh), 1, in) != 1) { fprintf(stderr, "unable to read mach header\n"); return; } number_of_load_cmds = mh.ncmds; end_of_infine_loadcmds = sizeof(mh) + mh.sizeofcmds; mh.ncmds += 1; mh.ncmds -= notes_to_remove; mh.sizeofcmds += sizeof(struct note_command); mh.sizeofcmds -= notes_to_remove * sizeof(struct note_command); fseeko(out, header_start, SEEK_SET); fwrite(&mh, sizeof(mh), 1, out); } off_t start_of_infile_load_cmds = ftello(in); fseek(in, 0, SEEK_END); off_t infile_size = ftello(in); // LC_SEGMENT may be aligned to 4k boundaries, let's maintain // that alignment by putting 4096 minus the size of the added // LC_NOTE load command after the output file's load commands. off_t end_of_outfile_loadcmds = end_of_infine_loadcmds - (notes_to_remove * sizeof(struct note_command)) + 4096 - sizeof(struct note_command); off_t slide = end_of_outfile_loadcmds - end_of_infine_loadcmds; off_t all_image_infos_infile_offset = 0; fseek(in, start_of_infile_load_cmds, SEEK_SET); fseek(out, start_of_infile_load_cmds, SEEK_SET); // Copy all the load commands from IN to OUT, updating any file offsets by // SLIDE. for (int cmd_num = 0; cmd_num < number_of_load_cmds; cmd_num++) { off_t cmd_start = ftello(in); uint32_t cmd, cmdsize; fread(&cmd, sizeof(uint32_t), 1, in); fread(&cmdsize, sizeof(uint32_t), 1, in); fseeko(in, cmd_start, SEEK_SET); off_t next_cmd = cmd_start + cmdsize; switch (cmd) { case LC_SEGMENT: { struct segment_command segcmd; fread(&segcmd, sizeof(segcmd), 1, in); segcmd.fileoff += slide; fwrite(&segcmd, cmdsize, 1, out); } break; case LC_SEGMENT_64: { struct segment_command_64 segcmd; fread(&segcmd, sizeof(segcmd), 1, in); segcmd.fileoff += slide; fwrite(&segcmd, cmdsize, 1, out); } break; case LC_NOTE: { struct note_command notecmd; fread(¬ecmd, sizeof(notecmd), 1, in); if ((strncmp(lc_note_name.c_str(), notecmd.data_owner, 16) == 0) && remove_dups) { fseeko(in, next_cmd, SEEK_SET); continue; } if (strncmp("all image infos", notecmd.data_owner, 16) == 0) all_image_infos_infile_offset = notecmd.offset; notecmd.offset += slide; fwrite(¬ecmd, cmdsize, 1, out); } break; default: { vector buf(cmdsize); fread(buf.data(), cmdsize, 1, in); fwrite(buf.data(), cmdsize, 1, out); } } fseeko(in, next_cmd, SEEK_SET); } // Now add our additional LC_NOTE load command. struct note_command note; note.cmd = LC_NOTE; note.cmdsize = sizeof(struct note_command); memset(¬e.data_owner, 0, 16); // data_owner may not be nul terminated if all 16 characters // are used, intentionally using strncpy here. strncpy(note.data_owner, lc_note_name.c_str(), 16); note.offset = infile_size + slide; note.size = payload_data.size(); fwrite(¬e, sizeof(struct note_command), 1, out); fseeko(in, end_of_infine_loadcmds, SEEK_SET); fseeko(out, end_of_outfile_loadcmds, SEEK_SET); // Copy the rest of the corefile contents vector data_buf(1024 * 1024); while (!feof(in)) { size_t read_bytes = fread(data_buf.data(), 1, data_buf.size(), in); if (read_bytes > 0) { fwrite(data_buf.data(), read_bytes, 1, out); } else { break; } } fwrite(payload_data.data(), payload_data.size(), 1, out); // The "all image infos" LC_NOTE payload has file offsets hardcoded // in it, unfortunately. We've shifted the contents of the corefile // and these offsets need to be updated in the ouput file. // Re-copy them into the outfile with corrected file offsets. off_t infile_image_entry_base = 0; if (all_image_infos_infile_offset != 0) { off_t all_image_infos_outfile_offset = all_image_infos_infile_offset + slide; fseeko(in, all_image_infos_infile_offset, SEEK_SET); struct all_image_infos_header header; fread(&header, sizeof(header), 1, in); infile_image_entry_base = header.entries_fileoff; header.entries_fileoff += slide; fseeko(out, all_image_infos_outfile_offset, SEEK_SET); fwrite(&header, sizeof(header), 1, out); for (int i = 0; i < header.imgcount; i++) { off_t infile_entries_fileoff = header.entries_fileoff - slide; off_t outfile_entries_fileoff = header.entries_fileoff; struct image_entry ent; fseeko(in, infile_entries_fileoff + (header.entry_size * i), SEEK_SET); fread(&ent, sizeof(ent), 1, in); ent.filepath_offset += slide; ent.seg_addrs_offset += slide; fseeko(out, outfile_entries_fileoff + (header.entry_size * i), SEEK_SET); fwrite(&ent, sizeof(ent), 1, out); } } } int main(int argc, char **argv) { string infile, outfile, name; vector payload; bool remove_dups; parse_args(argc, argv, infile, outfile, name, payload, remove_dups); FILE *in = fopen(infile.c_str(), "r"); if (!in) { fprintf(stderr, "Unable to open %s for reading\n", infile.c_str()); exit(1); } FILE *out = fopen(outfile.c_str(), "w"); if (!out) { fprintf(stderr, "Unable to open %s for reading\n", outfile.c_str()); exit(1); } copy_and_add_note(in, out, name, payload, remove_dups); fclose(in); fclose(out); }