| 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> |
| 3 | #include <mach-o/loader.h> |
| 4 | #include <vector> |
| 5 | #include <string> |
| 6 | #include <mach/thread_status.h> |
| 7 | #include <string.h> |
| 8 | #include <uuid/uuid.h> |
| 9 | |
| 10 | // Create an empty corefile with a "kern ver str" LC_NOTE. |
| 11 | // If an existing binary is given as an optional 2nd argument on the cmd line, |
| 12 | // the UUID from that binary will be encoded in the corefile. |
| 13 | // Otherwise a pre-set UUID will be put in the corefile that |
| 14 | // is created. |
| 15 | |
| 16 | |
| 17 | union uint32_buf { |
| 18 | uint8_t bytebuf[4]; |
| 19 | uint32_t val; |
| 20 | }; |
| 21 | |
| 22 | union uint64_buf { |
| 23 | uint8_t bytebuf[8]; |
| 24 | uint64_t val; |
| 25 | }; |
| 26 | |
| 27 | void |
| 28 | add_uint64(std::vector<uint8_t> &buf, uint64_t val) |
| 29 | { |
| 30 | uint64_buf conv; |
| 31 | conv.val = val; |
| 32 | for (int i = 0; i < 8; i++) |
| 33 | buf.push_back(x: conv.bytebuf[i]); |
| 34 | } |
| 35 | |
| 36 | void |
| 37 | add_uint32(std::vector<uint8_t> &buf, uint32_t val) |
| 38 | { |
| 39 | uint32_buf conv; |
| 40 | conv.val = val; |
| 41 | for (int i = 0; i < 4; i++) |
| 42 | buf.push_back(x: conv.bytebuf[i]); |
| 43 | } |
| 44 | |
| 45 | std::vector<uint8_t> |
| 46 | x86_lc_thread_load_command () |
| 47 | { |
| 48 | std::vector<uint8_t> data; |
| 49 | add_uint32 (data, LC_THREAD); // thread_command.cmd |
| 50 | add_uint32 (buf&: data, val: 184); // thread_command.cmdsize |
| 51 | add_uint32 (data, x86_THREAD_STATE64); // thread_command.flavor |
| 52 | add_uint32 (data, x86_THREAD_STATE64_COUNT); // thread_command.count |
| 53 | add_uint64 (buf&: data, val: 0x0000000000000000); // rax |
| 54 | add_uint64 (buf&: data, val: 0x0000000000000400); // rbx |
| 55 | add_uint64 (buf&: data, val: 0x0000000000000000); // rcx |
| 56 | add_uint64 (buf&: data, val: 0x0000000000000000); // rdx |
| 57 | add_uint64 (buf&: data, val: 0x0000000000000000); // rdi |
| 58 | add_uint64 (buf&: data, val: 0x0000000000000000); // rsi |
| 59 | add_uint64 (buf&: data, val: 0xffffff9246e2ba20); // rbp |
| 60 | add_uint64 (buf&: data, val: 0xffffff9246e2ba10); // rsp |
| 61 | add_uint64 (buf&: data, val: 0x0000000000000000); // r8 |
| 62 | add_uint64 (buf&: data, val: 0x0000000000000000); // r9 |
| 63 | add_uint64 (buf&: data, val: 0x0000000000000000); // r10 |
| 64 | add_uint64 (buf&: data, val: 0x0000000000000000); // r11 |
| 65 | add_uint64 (buf&: data, val: 0xffffff7f96ce5fe1); // r12 |
| 66 | add_uint64 (buf&: data, val: 0x0000000000000000); // r13 |
| 67 | add_uint64 (buf&: data, val: 0x0000000000000000); // r14 |
| 68 | add_uint64 (buf&: data, val: 0xffffff9246e2bac0); // r15 |
| 69 | add_uint64 (buf&: data, val: 0xffffff8015a8f6d0); // rip |
| 70 | add_uint64 (buf&: data, val: 0x0000000000011111); // rflags |
| 71 | add_uint64 (buf&: data, val: 0x0000000000022222); // cs |
| 72 | add_uint64 (buf&: data, val: 0x0000000000033333); // fs |
| 73 | add_uint64 (buf&: data, val: 0x0000000000044444); // gs |
| 74 | return data; |
| 75 | } |
| 76 | |
| 77 | void |
| 78 | add_lc_note_kern_ver_str_load_command (std::vector<std::vector<uint8_t> > &loadcmds, |
| 79 | std::vector<uint8_t> &payload, |
| 80 | int payload_file_offset, |
| 81 | std::string ident) |
| 82 | { |
| 83 | std::vector<uint8_t> loadcmd_data; |
| 84 | |
| 85 | add_uint32 (loadcmd_data, LC_NOTE); // note_command.cmd |
| 86 | add_uint32 (buf&: loadcmd_data, val: 40); // note_command.cmdsize |
| 87 | char lc_note_name[16]; |
| 88 | memset (s: lc_note_name, c: 0, n: 16); |
| 89 | strcpy (dest: lc_note_name, src: "kern ver str" ); |
| 90 | |
| 91 | // lc_note.data_owner |
| 92 | for (int i = 0; i < 16; i++) |
| 93 | loadcmd_data.push_back (x: lc_note_name[i]); |
| 94 | |
| 95 | // we start writing the payload at payload_file_offset to leave |
| 96 | // room at the start for the header & the load commands. |
| 97 | uint64_t current_payload_offset = payload.size() + payload_file_offset; |
| 98 | |
| 99 | add_uint64 (buf&: loadcmd_data, val: current_payload_offset); // note_command.offset |
| 100 | add_uint64 (buf&: loadcmd_data, val: 4 + ident.size() + 1); // note_command.size |
| 101 | |
| 102 | loadcmds.push_back (x: loadcmd_data); |
| 103 | |
| 104 | add_uint32 (buf&: payload, val: 1); // kerneL_version_string.version |
| 105 | for (int i = 0; i < ident.size() + 1; i++) |
| 106 | { |
| 107 | payload.push_back (x: ident[i]); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | void |
| 112 | add_lc_segment (std::vector<std::vector<uint8_t> > &loadcmds, |
| 113 | std::vector<uint8_t> &payload, |
| 114 | int payload_file_offset) |
| 115 | { |
| 116 | std::vector<uint8_t> loadcmd_data; |
| 117 | struct segment_command_64 seg; |
| 118 | seg.cmd = LC_SEGMENT_64; |
| 119 | seg.cmdsize = sizeof (struct segment_command_64); // no sections |
| 120 | memset (seg.segname, 0, 16); |
| 121 | seg.vmaddr = 0xffffff7f96400000; |
| 122 | seg.vmsize = 4096; |
| 123 | seg.fileoff = payload.size() + payload_file_offset; |
| 124 | seg.filesize = 0; |
| 125 | seg.maxprot = 1; |
| 126 | seg.initprot = 1; |
| 127 | seg.nsects = 0; |
| 128 | seg.flags = 0; |
| 129 | |
| 130 | uint8_t *p = (uint8_t*) &seg; |
| 131 | for (int i = 0; i < sizeof (struct segment_command_64); i++) |
| 132 | { |
| 133 | loadcmd_data.push_back (*(p + i)); |
| 134 | } |
| 135 | loadcmds.push_back (x: loadcmd_data); |
| 136 | } |
| 137 | |
| 138 | std::string |
| 139 | get_uuid_from_binary (const char *fn) |
| 140 | { |
| 141 | FILE *f = fopen(filename: fn, modes: "r" ); |
| 142 | if (f == nullptr) |
| 143 | { |
| 144 | fprintf (stderr, format: "Unable to open binary '%s' to get uuid\n" , fn); |
| 145 | exit(status: 1); |
| 146 | } |
| 147 | uint32_t num_of_load_cmds = 0; |
| 148 | uint32_t size_of_load_cmds = 0; |
| 149 | std::string uuid; |
| 150 | off_t file_offset = 0; |
| 151 | |
| 152 | uint8_t magic[4]; |
| 153 | if (::fread (ptr: magic, size: 1, n: 4, stream: f) != 4) |
| 154 | { |
| 155 | fprintf (stderr, format: "Failed to read magic number from input file %s\n" , fn); |
| 156 | exit (status: 1); |
| 157 | } |
| 158 | uint8_t magic_32_be[] = {0xfe, 0xed, 0xfa, 0xce}; |
| 159 | uint8_t magic_32_le[] = {0xce, 0xfa, 0xed, 0xfe}; |
| 160 | uint8_t magic_64_be[] = {0xfe, 0xed, 0xfa, 0xcf}; |
| 161 | uint8_t magic_64_le[] = {0xcf, 0xfa, 0xed, 0xfe}; |
| 162 | |
| 163 | if (memcmp (s1: magic, s2: magic_32_be, n: 4) == 0 || memcmp (s1: magic, s2: magic_64_be, n: 4) == 0) |
| 164 | { |
| 165 | fprintf (stderr, format: "big endian corefiles not supported\n" ); |
| 166 | exit (status: 1); |
| 167 | } |
| 168 | |
| 169 | ::fseeko (stream: f, off: 0, SEEK_SET); |
| 170 | if (memcmp (s1: magic, s2: magic_32_le, n: 4) == 0) |
| 171 | { |
| 172 | struct mh; |
| 173 | if (::fread (ptr: &mh, size: 1, n: sizeof (mh), stream: f) != sizeof (mh)) |
| 174 | { |
| 175 | fprintf (stderr, format: "error reading mach header from input file\n" ); |
| 176 | exit (status: 1); |
| 177 | } |
| 178 | if (mh.cputype != CPU_TYPE_X86_64) |
| 179 | { |
| 180 | fprintf (stderr, format: "This tool creates an x86_64 corefile but " |
| 181 | "the supplied binary '%s' is cputype 0x%x\n" , |
| 182 | fn, (uint32_t) mh.cputype); |
| 183 | exit (status: 1); |
| 184 | } |
| 185 | num_of_load_cmds = mh.ncmds; |
| 186 | size_of_load_cmds = mh.sizeofcmds; |
| 187 | file_offset += sizeof (struct mach_header); |
| 188 | } |
| 189 | else |
| 190 | { |
| 191 | struct mh; |
| 192 | if (::fread (ptr: &mh, size: 1, n: sizeof (mh), stream: f) != sizeof (mh)) |
| 193 | { |
| 194 | fprintf (stderr, format: "error reading mach header from input file\n" ); |
| 195 | exit (status: 1); |
| 196 | } |
| 197 | if (mh.cputype != CPU_TYPE_X86_64) |
| 198 | { |
| 199 | fprintf (stderr, format: "This tool creates an x86_64 corefile but " |
| 200 | "the supplied binary '%s' is cputype 0x%x\n" , |
| 201 | fn, (uint32_t) mh.cputype); |
| 202 | exit (status: 1); |
| 203 | } |
| 204 | num_of_load_cmds = mh.ncmds; |
| 205 | size_of_load_cmds = mh.sizeofcmds; |
| 206 | file_offset += sizeof (struct mach_header_64); |
| 207 | } |
| 208 | |
| 209 | off_t load_cmds_offset = file_offset; |
| 210 | |
| 211 | for (int i = 0; i < num_of_load_cmds && (file_offset - load_cmds_offset) < size_of_load_cmds; i++) |
| 212 | { |
| 213 | ::fseeko (stream: f, off: file_offset, SEEK_SET); |
| 214 | uint32_t cmd; |
| 215 | uint32_t cmdsize; |
| 216 | ::fread (ptr: &cmd, size: sizeof (uint32_t), n: 1, stream: f); |
| 217 | ::fread (ptr: &cmdsize, size: sizeof (uint32_t), n: 1, stream: f); |
| 218 | if (cmd == LC_UUID) |
| 219 | { |
| 220 | struct uuid_command uuidcmd; |
| 221 | ::fseeko (stream: f, off: file_offset, SEEK_SET); |
| 222 | if (::fread (ptr: &uuidcmd, size: 1, n: sizeof (uuidcmd), stream: f) != sizeof (uuidcmd)) |
| 223 | { |
| 224 | fprintf (stderr, format: "Unable to read LC_UUID load command.\n" ); |
| 225 | exit (status: 1); |
| 226 | } |
| 227 | uuid_string_t uuidstr; |
| 228 | uuid_unparse (uuidcmd.uuid, uuidstr); |
| 229 | uuid = uuidstr; |
| 230 | break; |
| 231 | } |
| 232 | file_offset += cmdsize; |
| 233 | } |
| 234 | return uuid; |
| 235 | } |
| 236 | |
| 237 | int main (int argc, char **argv) |
| 238 | { |
| 239 | if (argc != 2 && argc != 3) |
| 240 | { |
| 241 | fprintf (stderr, format: "usage: create-empty-corefile <output-core-name> [binary-to-copy-uuid-from]\n" ); |
| 242 | fprintf (stderr, format: "Create a Mach-O corefile with an LC_NOTE 'kern ver str' load command/payload\n" ); |
| 243 | fprintf (stderr, format: "If a binary is given as a second argument, the Mach-O UUID of that file will\n" ); |
| 244 | fprintf (stderr, format: "be read and used in the corefile's LC_NOTE payload.\n" ); |
| 245 | exit (status: 1); |
| 246 | } |
| 247 | |
| 248 | std::string ident = "EFI UUID=3F9BA21F-55EA-356A-A349-BBA6F51FE8B1" ; |
| 249 | if (argc == 3) |
| 250 | { |
| 251 | std::string uuid_from_file = get_uuid_from_binary (fn: argv[2]); |
| 252 | if (!uuid_from_file.empty()) |
| 253 | { |
| 254 | ident = "EFI UUID=" ; |
| 255 | ident += uuid_from_file; |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | // An array of load commands (in the form of byte arrays) |
| 260 | std::vector<std::vector<uint8_t> > load_commands; |
| 261 | |
| 262 | // An array of corefile contents (page data, lc_note data, etc) |
| 263 | std::vector<uint8_t> payload; |
| 264 | |
| 265 | // First add all the load commands / payload so we can figure out how large |
| 266 | // the load commands will actually be. |
| 267 | load_commands.push_back (x: x86_lc_thread_load_command()); |
| 268 | add_lc_note_kern_ver_str_load_command (loadcmds&: load_commands, payload, payload_file_offset: 0, ident); |
| 269 | add_lc_segment (loadcmds&: load_commands, payload, payload_file_offset: 0); |
| 270 | |
| 271 | int size_of_load_commands = 0; |
| 272 | for (const auto &lc : load_commands) |
| 273 | size_of_load_commands += lc.size(); |
| 274 | |
| 275 | int header_and_load_cmd_room = sizeof (struct mach_header_64) + size_of_load_commands; |
| 276 | |
| 277 | // Erase the load commands / payload now that we know how much space is needed, |
| 278 | // redo it. |
| 279 | load_commands.clear(); |
| 280 | payload.clear(); |
| 281 | |
| 282 | load_commands.push_back (x: x86_lc_thread_load_command()); |
| 283 | add_lc_note_kern_ver_str_load_command (loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room, ident); |
| 284 | add_lc_segment (loadcmds&: load_commands, payload, payload_file_offset: header_and_load_cmd_room); |
| 285 | |
| 286 | struct mach_header_64 mh; |
| 287 | mh.magic = MH_MAGIC_64; |
| 288 | mh.cputype = CPU_TYPE_X86_64; |
| 289 | mh.cpusubtype = CPU_SUBTYPE_X86_64_ALL; |
| 290 | mh.filetype = MH_CORE; |
| 291 | mh.ncmds = load_commands.size(); |
| 292 | mh.sizeofcmds = size_of_load_commands; |
| 293 | mh.flags = 0; |
| 294 | mh.reserved = 0; |
| 295 | |
| 296 | |
| 297 | FILE *f = fopen (filename: argv[1], modes: "w" ); |
| 298 | |
| 299 | if (f == nullptr) |
| 300 | { |
| 301 | fprintf (stderr, format: "Unable to open file %s for writing\n" , argv[1]); |
| 302 | exit (status: 1); |
| 303 | } |
| 304 | |
| 305 | fwrite (&mh, sizeof (struct mach_header_64), 1, f); |
| 306 | |
| 307 | for (const auto &lc : load_commands) |
| 308 | fwrite (ptr: lc.data(), size: lc.size(), n: 1, s: f); |
| 309 | |
| 310 | fseek (stream: f, off: header_and_load_cmd_room, SEEK_SET); |
| 311 | |
| 312 | fwrite (ptr: payload.data(), size: payload.size(), n: 1, s: f); |
| 313 | |
| 314 | fclose (stream: f); |
| 315 | } |
| 316 | |