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