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 | |