1import sys 2import macholib 3from macholib import MachO as macho 4from collections import namedtuple 5import re 6 7# some fixups in macholib that are required for kext support 8macholib.mach_o.MH_KEXT_BUNDLE = 0xB 9 10macholib.mach_o.MH_FILETYPE_NAMES[macholib.mach_o.MH_KEXT_BUNDLE] = "kext bundle" 11macholib.mach_o.MH_FILETYPE_SHORTNAMES[macholib.mach_o.MH_KEXT_BUNDLE] = "kext" 12 13_old_MachOHeader_load = macho.MachOHeader.load 14def new_load(s, fh): 15 try: 16 _old_MachOHeader_load(s, fh) 17 except ValueError as e: 18 if str(e.message).find('total_size > low_offset') >= 0: 19 pass 20 else: 21 raise 22 except Exception as e: 23 raise 24macho.MachOHeader.load = new_load 25 26class MemFile(object): 27 def __init__(self, memory, size): 28 self._start = 0 29 self._readp = 0 30 self._end = size 31 self._mem = memory 32 33 def tell(self): 34 return self._readp 35 36 def check_bounds(self, seek_position, operation): 37 if not (self._start <= seek_position <= self._end): 38 raise IOError("%s to offset %d failed bounds check [%d, %d]" % ( 39 operation, seek_position, self._start, self._end)) 40 41 def seek(self, offset, whence=0): 42 seekto = offset 43 if whence == 0: 44 seekto += self._start 45 elif whence == 1: 46 seekto += self.tell() 47 elif whence == 2: 48 seekto += self._end 49 else: 50 raise IOError("Invalid whence argument to seek: %r" % (whence,)) 51 self.check_bounds(seekto, 'seek') 52 self._readp = seekto 53 54 def write(self, bytes): 55 raise NotImplementedError('write is not supported') 56 57 def read(self, size=sys.maxsize): 58 if size < 0: 59 raise ValueError("Invalid size {} while reading from {}".format(size, self._fileobj)) 60 here = self.tell() 61 self.check_bounds(here, 'read') 62 bytes = min(size, self._end - here) 63 retval = self._mem[self._readp:self._readp + bytes] 64 self._readp += bytes 65 return retval 66 67MachOSegment = namedtuple('MachOSegment', 'name vmaddr vmsize fileoff filesize') 68 69class MemMacho(macho.MachO): 70 71 def __init__(self, memdata, size=None): 72 if size is None: 73 super(MemMacho,self).__init__(memdata) 74 return 75 # 76 # supports the ObjectGraph protocol 77 self.graphident = 'mem:%d//'.format(size) 78 self.filename = 'mem:%d//'.format(size) 79 80 # initialized by load 81 self.fat = None 82 self.headers = [] 83 fp = MemFile(memdata, size) 84 self.load(fp) 85 86 87 def get_segments_with_name(self, filter_re): 88 """ param: filter_re is a compiled re which will be matched against segment name. 89 Use: '' to match anything and everything 90 returns: [ MachOSegment, MachOSegment, ... ] 91 """ 92 if type(filter_re) is str: 93 filter_re = re.compile(filter_re) 94 retval = [] 95 for h in self.headers: 96 for cmd in h.commands: 97 # cmds is [(load_command, segment, [sections..])] 98 (lc, segment, sections) = cmd 99 if isinstance(segment, SEGMENT_TYPES): 100 segname = segment.segname[:segment.segname.find('\x00')] 101 if filter_re.match(segname): 102 retval.append(MachOSegment(segname, segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize)) 103 return retval 104 105 def get_sections_with_name(self, filter_re): 106 """ param: filter_re is a compiled re which will be matched against <segment_name>.<section_name> 107 Use: '' to match anything and everything 108 returns: [ MachOSegment, MachOSegment, ... ] 109 where each MachOSegment.name is <segment_name>.<section_name> 110 """ 111 if type(filter_re) is str: 112 filter_re = re.compile(filter_re) 113 retval = [] 114 for h in self.headers: 115 for cmd in h.commands: 116 # cmds is [(load_command, segment, [sections..])] 117 (lc, segment, sections) = cmd 118 if isinstance(segment, SEGMENT_TYPES): 119 segname = segment.segname[:segment.segname.find('\x00')] 120 for section in sections: 121 section_name = section.sectname[:section.sectname.find('\x00')] 122 full_section_name= "{}.{}".format(segname, section_name) 123 if filter_re.match(full_section_name): 124 retval.append(MachOSegment(full_section_name, section.addr, section.size, section.offset, section.size)) 125 return retval 126 127 128 def get_uuid(self): 129 retval = '' 130 for h in self.headers: 131 for cmd in h.commands: 132 # cmds is [(load_command, segment, [sections..])] 133 (lc, segment, sections) = cmd 134 if isinstance(segment, macholib.mach_o.uuid_command): 135 retval = GetUUIDSummary(segment.uuid) 136 return retval 137 138def get_text_segment(segments): 139 retval = None 140 for s in segments: 141 if s.name == '__TEXT_EXEC': 142 return s 143 for s in segments: 144 if s.name == '__TEXT': 145 return s 146 return retval 147 148def get_segment_with_addr(segments, addr): 149 """ param: segments [MachOSegment, ...] 150 return: None or MachOSegment where addr is in vmaddr...(vmaddr+vmsize) 151 """ 152 for s in segments: 153 if addr >= s.vmaddr and addr < (s.vmaddr + s.vmsize): 154 return s 155 return None 156 157def GetUUIDSummary(arr): 158 data = [] 159 for i in range(16): 160 data.append(ord(arr[i])) 161 return "{a[0]:02X}{a[1]:02X}{a[2]:02X}{a[3]:02X}-{a[4]:02X}{a[5]:02X}-{a[6]:02X}{a[7]:02X}-{a[8]:02X}{a[9]:02X}-{a[10]:02X}{a[11]:02X}{a[12]:02X}{a[13]:02X}{a[14]:02X}{a[15]:02X}".format(a=data) 162 163SEGMENT_TYPES = (macholib.mach_o.segment_command_64, macholib.mach_o.segment_command) 164 165def get_load_command_human_name(cmd): 166 """ return string name of LC_LOAD_DYLIB => "load_dylib" 167 "<unknown>" if not found 168 """ 169 retval = "<unknown>" 170 if cmd in macho.LC_REGISTRY: 171 retval = macho.LC_REGISTRY[cmd].__name__ 172 retval = retval.replace("_command","") 173 return retval 174 175class VisualMachoMap(object): 176 KB_1 = 1024 177 KB_16 = 16 * 1024 178 MB_1 = 1 * 1024 * 1024 179 GB_1 = 1 * 1024 * 1024 * 1024 180 181 def __init__(self, name, width=40): 182 self.name = name 183 self.width = 40 184 self.default_side_padding = 2 185 186 def get_header_line(self): 187 return '+' + '-' * (self.width - 2) + '+' 188 189 def get_space_line(self): 190 return '|' + ' ' * (self.width - 2) + '|' 191 192 def get_dashed_line(self): 193 return '|' + '-' * (self.width - 2) + '|' 194 195 def get_dotted_line(self): 196 return '|' + '.' * (self.width - 2) + '|' 197 198 def center_text_in_line(self, line, text): 199 even_length = bool(len(text) % 2 == 0) 200 if len(text) > len(line) - 2: 201 raise ValueError("text is larger than line of text") 202 203 lbreak_pos = len(line)/2 - len(text)/2 204 if not even_length: 205 lbreak_pos -= 1 206 out = line[:lbreak_pos] + text 207 return out + line[len(out):] 208 209 def get_separator_lines(self): 210 return ['/' + ' ' * (self.width - 2) + '/', '/' + ' ' * (self.width - 2) + '/'] 211 212 def printMachoMap(self, mobj): 213 MapBlock = namedtuple('MapBlock', 'name vmaddr vmsize fileoff filesize extra_info is_segment') 214 outstr = self.name + '\n' 215 other_cmds = '' 216 blocks = [] 217 for hdr in mobj.headers: 218 cmd_index = 0 219 for cmd in hdr.commands: 220 # cmds is [(load_command, segment, [sections..])] 221 (lc, segment, sections) = cmd 222 lc_cmd_str = get_load_command_human_name(lc.cmd) 223 lc_str_rep = "\n\t LC: {:s} size:{:d} nsects:{:d}".format(lc_cmd_str, lc.cmdsize, len(sections)) 224 # print lc_str_rep 225 if isinstance(segment, SEGMENT_TYPES): 226 segname = segment.segname[:segment.segname.find('\x00')] 227 # print "\tsegment: {:s} vmaddr: {:x} vmsize:{:d} fileoff: {:x} filesize: {:d}".format( 228 # segname, segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize) 229 blocks.append(MapBlock(segname, segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize, 230 ' LC:{} : {} init:{:#0X} max:{:#0X}'.format(lc_cmd_str, segname, segment.initprot, segment.maxprot), 231 True)) 232 for section in sections: 233 section_name = section.sectname[:section.sectname.find('\x00')] 234 blocks.append(MapBlock(section_name, section.addr, section.size, section.offset, 235 section.size, 'al:{} flags:{:#0X}'.format(section.align, section.flags), False)) 236 #print "\t\tsection:{:s} addr:{:x} off:{:x} size:{:d}".format(section_name, section.addr, section.offset, section.size) 237 elif isinstance(segment, macholib.mach_o.uuid_command): 238 other_cmds += "\n\t uuid: {:s}".format(GetUUIDSummary(segment.uuid)) 239 elif isinstance(segment, macholib.mach_o.rpath_command): 240 other_cmds += "\n\t rpath: {:s}".format(segment.path) 241 elif isinstance(segment, macholib.mach_o.dylib_command): 242 other_cmds += "\n\t dylib: {:s} ({:s})".format(str(sections[:sections.find('\x00')]), str(segment.current_version)) 243 else: 244 other_cmds += lc_str_rep 245 cmd_index += 1 246 247 # fixup the self.width param 248 for _b in blocks: 249 if self.default_side_padding + len(_b.name) + 2 > self.width: 250 self.width = self.default_side_padding + len(_b.name) + 2 251 if self.width % 2 != 0: 252 self.width += 1 253 254 sorted_blocks = sorted(blocks, key=lambda b: b.vmaddr) 255 mstr = [self.get_header_line()] 256 prev_block = MapBlock('', 0, 0, 0, 0, '', False) 257 for b in sorted_blocks: 258 # TODO add separator blocks if vmaddr is large from prev_block 259 if b.is_segment: 260 s = self.get_dashed_line() 261 else: 262 s = self.get_dotted_line() 263 s = self.center_text_in_line(s, b.name) 264 line = "{:s} {: <#020X} ({: <10d}) floff:{: <#08x} {}".format(s, b.vmaddr, b.vmsize, b.fileoff, b.extra_info) 265 if (b.vmaddr - prev_block.vmaddr) > VisualMachoMap.KB_16: 266 mstr.append(self.get_space_line()) 267 mstr.append(self.get_space_line()) 268 269 mstr.append(line) 270 271 if b.vmsize > VisualMachoMap.MB_1: 272 mstr.append(self.get_space_line()) 273 mstr.extend(self.get_separator_lines()) 274 mstr.append(self.get_space_line()) 275 #mstr.append(self.get_space_line()) 276 prev_block = b 277 mstr.append(self.get_space_line()) 278 if prev_block.vmsize > VisualMachoMap.KB_16: 279 mstr.append(self.get_space_line()) 280 mstr.append(self.get_header_line()) 281 print outstr 282 print "\n".join(mstr) 283 print "\n\n=============== Other Load Commands ===============" 284 print other_cmds 285 286 287if __name__ == '__main__': 288 import sys 289 if len(sys.argv) < 2: 290 print "Usage: {} /path/to/macho_binary".format(sys.argv[0]) 291 sys.exit(1) 292 with open(sys.argv[-1], 'rb') as fp: 293 data = fp.read() 294 mobject = MemMacho(data, len(data)) 295 296 p = VisualMachoMap(sys.argv[-1]) 297 p.printMachoMap(mobject) 298 sys.exit(0) 299 300