1 #include <mach-o/loader.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <mach/machine.h>
5 #include <string.h>
6 #include <mach/machine/thread_state.h>
7 #include <inttypes.h>
8 #include <sys/syslimits.h>
9 #include <uuid/uuid.h>
10 
11 // Given an executable binary with
12 //   "fmain" (a function pointer to main)
13 //   "main"
14 // symbols, create a fake arm64e corefile that
15 // contains a memory segment for the fmain
16 // function pointer, with the value of the
17 // address of main() with ptrauth bits masked on.
18 //
19 // The corefile does not include the "addrable bits"
20 // LC_NOTE, so lldb will need to fall back on its
21 // default value from the Darwin arm64 ABI.
22 
main(int argc,char ** argv)23 int main(int argc, char **argv)
24 {
25   if (argc != 3) {
26     fprintf (stderr, "usage: %s executable-binary output-file\n", argv[0]);
27     exit(1);
28   }
29   FILE *exe = fopen(argv[1], "r");
30   if (!exe) {
31     fprintf (stderr, "Unable to open executable %s for reading\n", argv[1]);
32     exit(1);
33   }
34   FILE *out = fopen(argv[2], "w");
35   if (!out) {
36     fprintf (stderr, "Unable to open %s for writing\n", argv[2]);
37     exit(1);
38   }
39 
40   char buf[PATH_MAX + 6];
41   sprintf (buf, "nm '%s'", argv[1]);
42   FILE *nm = popen(buf, "r");
43   if (!nm) {
44     fprintf (stderr, "Unable to run nm on '%s'", argv[1]);
45     exit (1);
46   }
47   uint64_t main_addr = 0;
48   uint64_t fmain_addr = 0;
49   while (fgets (buf, sizeof(buf), nm)) {
50     if (strstr (buf, "_fmain")) {
51       fmain_addr = strtoul (buf, NULL, 16);
52     }
53     if (strstr (buf, "_main")) {
54       main_addr = strtoul (buf, NULL, 16);
55     }
56   }
57   pclose (nm);
58 
59   sprintf (buf, "dwarfdump -u '%s'", argv[1]);
60   FILE *dwarfdump = popen(buf, "r");
61   if (!dwarfdump) {
62     fprintf (stderr, "Unable to run dwarfdump -u on '%s'\n", argv[1]);
63     exit (1);
64   }
65   uuid_t uuid;
66   uuid_clear (uuid);
67   while (fgets (buf, sizeof(buf), dwarfdump)) {
68     if (strncmp (buf, "UUID: ", 6) == 0) {
69       buf[6 + 36] = '\0';
70       if (uuid_parse (buf + 6, uuid) != 0) {
71         fprintf (stderr, "Unable to parse UUID in '%s'\n", buf);
72         exit (1);
73       }
74     }
75   }
76   if (uuid_is_null(uuid)) {
77     fprintf (stderr, "Got a null uuid for the binary\n");
78     exit (1);
79   }
80 
81   if (main_addr == 0 || fmain_addr == 0) {
82     fprintf(stderr, "Unable to find address of main or fmain in %s.\n",
83         argv[1]);
84     exit (1);
85   }
86 
87   // Write out a corefile with contents in this order:
88   //    1. mach header
89   //    2. LC_THREAD load command
90   //    3. LC_SEGMENT_64 load command
91   //    4. LC_NOTE load command
92   //    5. memory segment contents
93   //    6. "load binary" note contents
94 
95   // struct thread_command {
96   //       uint32_t        cmd;
97   //       uint32_t        cmdsize;
98   //       uint32_t flavor
99   //       uint32_t count
100   //       struct XXX_thread_state state
101   int size_of_thread_cmd = 4 + 4 + 4 + 4 + sizeof (arm_thread_state64_t);
102 
103   struct mach_header_64 mh;
104   mh.magic = 0xfeedfacf;
105   mh.cputype = CPU_TYPE_ARM64;
106   mh.cpusubtype = CPU_SUBTYPE_ARM64E;
107   mh.filetype = MH_CORE;
108   mh.ncmds = 3; // LC_THREAD, LC_SEGMENT_64, LC_NOTE
109   mh.sizeofcmds = size_of_thread_cmd + sizeof(struct segment_command_64) + sizeof(struct note_command);
110   mh.flags = 0;
111   mh.reserved = 0;
112 
113   fwrite(&mh, sizeof (mh), 1, out);
114 
115   struct note_command lcnote;
116   struct segment_command_64 seg;
117   seg.cmd = LC_SEGMENT_64;
118   seg.cmdsize = sizeof(seg);
119   memset (&seg.segname, 0, 16);
120   seg.vmaddr = fmain_addr;
121   seg.vmsize = 8;
122   // Offset to segment contents
123   seg.fileoff = sizeof (mh) + size_of_thread_cmd + sizeof(seg) + sizeof(lcnote);
124   seg.filesize = 8;
125   seg.maxprot = 3;
126   seg.initprot = 3;
127   seg.nsects = 0;
128   seg.flags = 0;
129 
130   fwrite (&seg, sizeof (seg), 1, out);
131 
132   uint32_t cmd = LC_THREAD;
133   fwrite (&cmd, sizeof (cmd), 1, out);
134   uint32_t cmdsize = size_of_thread_cmd;
135   fwrite (&cmdsize, sizeof (cmdsize), 1, out);
136   uint32_t flavor = ARM_THREAD_STATE64;
137   fwrite (&flavor, sizeof (flavor), 1, out);
138   // count is number of uint32_t's of the register context
139   uint32_t count = sizeof (arm_thread_state64_t) / 4;
140   fwrite (&count, sizeof (count), 1, out);
141   arm_thread_state64_t regstate;
142   memset (&regstate, 0, sizeof (regstate));
143   fwrite (&regstate, sizeof (regstate), 1, out);
144 
145   lcnote.cmd = LC_NOTE;
146   lcnote.cmdsize = sizeof (lcnote);
147   strcpy (lcnote.data_owner, "load binary");
148 
149   // 8 is the size of the LC_SEGMENT contents
150   lcnote.offset = sizeof (mh) + size_of_thread_cmd + sizeof(seg) + sizeof(lcnote) + 8;
151 
152   // struct load_binary
153   // {
154   // uint32_t version;        // currently 1
155   // uuid_t   uuid;           // all zeroes if uuid not specified
156   // uint64_t load_address;   // virtual address where the macho is loaded, UINT64_MAX if unavail
157   // uint64_t slide;          // slide to be applied to file address to get load address, 0 if unavail
158   // char     name_cstring[]; // must be nul-byte terminated c-string, '\0' alone if name unavail
159   // } __attribute__((packed));
160   lcnote.size = 4 + 16 + 8 + 8 + sizeof("a.out");
161 
162   fwrite (&lcnote, sizeof(lcnote), 1, out);
163 
164   // Write the contents of the memory segment
165 
166   // Or together a random PAC value from a system using 39 bits
167   // of addressing with the address of main().  lldb will need
168   // to correctly strip off the high bits to find the address of
169   // main.
170   uint64_t segment_contents = 0xe46bff0000000000 | main_addr;
171   fwrite (&segment_contents, sizeof (segment_contents), 1, out);
172 
173   // Now write the contents of the "load binary" LC_NOTE.
174   {
175     uint32_t version = 1;
176     fwrite (&version, sizeof (version), 1, out);
177     fwrite (&uuid, sizeof (uuid), 1, out);
178     uint64_t load_address = UINT64_MAX;
179     fwrite (&load_address, sizeof (load_address), 1, out);
180     uint64_t slide = 0;
181     fwrite (&slide, sizeof (slide), 1, out);
182     strcpy (buf, "a.out");
183     fwrite (buf, 6, 1, out);
184   }
185 
186   fclose (out);
187 
188   exit (0);
189 }
190