import { Reader } from './endian-reader' import { constants } from './constants' import { MAGIC } from '../../../macho/macho.magic.js' function determineMagicBits ( magic ) { for ( const [ key, value ] of Object.entries( MAGIC ) ) { if ( value === magic ) { return key.includes( '64' ) ? 64 : 32 } } return null } export class Parser extends Reader { constructor () { super() } execute (buf) { var hdr = this.parseHead(buf); // console.log( 'hdr', hdr ) if (!hdr) throw new Error('File not in a mach-o format'); hdr.cmds = this.parseCommands(hdr, hdr.body, buf); delete hdr.body; return hdr; } mapFlags (value, map) { var res = {}; for (var bit = 1; (value < 0 || bit <= value) && bit !== 0; bit <<= 1) if (value & bit) res[map[bit]] = true; return res; } parseHead(buf) { if (buf.length < 7 * 4) return false; // console.log( 'Has proper length' ) var magic = buf.readUInt32LE(0); var bits = determineMagicBits( magic ) if ( !bits ) return false // console.log( 'Has proper bits' ) if ( magic & 0xff == 0xfe ) // Big Endian this.setEndian('be'); else // Little Endian this.setEndian('le'); if (bits === 64 && buf.length < 8 * 4) return false; // console.log( 'Has proper length' ) var cputype = constants.cpuType[this.readInt32(buf, 4)]; var cpusubtype = this.readInt32(buf, 8); var filetype = this.readUInt32(buf, 12); var ncmds = this.readUInt32(buf, 16); var sizeofcmds = this.readUInt32(buf, 20); var flags = this.readUInt32(buf, 24); // Get endian var endian; if ((cpusubtype & constants.endian.multiple) === constants.endian.multiple) endian = 'multiple'; else if (cpusubtype & constants.endian.be) endian = 'be'; else endian = 'le'; cpusubtype &= constants.cpuSubType.mask; // Get subtype var subtype; if (endian === 'multiple') subtype = 'all'; else if (cpusubtype === 0) subtype = 'none'; else subtype = constants.cpuSubType[cputype][cpusubtype]; // Stringify flags var flagMap = this.mapFlags(flags, constants.flags); return { bits: bits, magic: magic, cpu: { type: cputype, subtype: subtype, endian: endian }, filetype: constants.fileType[filetype], ncmds: ncmds, sizeofcmds: sizeofcmds, flags: flagMap, cmds: null, hsize: bits === 32 ? 28 : 32, body: bits === 32 ? buf.slice(28) : buf.slice(32) }; } parseCommands(hdr, buf, file) { var cmds = []; var align; if (hdr.bits === 32) align = 4; else align = 8; for (var offset = 0, i = 0; offset + 8 < buf.length, i < hdr.ncmds; i++) { var type = constants.cmdType[this.readUInt32(buf, offset)]; var size = this.readUInt32(buf, offset + 4) - 8; var fileoff = offset + hdr.hsize; offset += 8; if (offset + size > buf.length) throw new Error('Command body OOB'); var body = buf.slice(offset, offset + size); offset += size; if (offset & align) offset += align - (offset & align); var cmd = this.parseCommand(type, body, file); cmd.fileoff = fileoff; cmds.push(cmd); } return cmds; } parseCStr(buf) { for (var i = 0; i < buf.length; i++) if (buf[i] === 0) break; return buf.slice(0, i).toString(); } parseLCStr (buf, off) { if (off + 4 > buf.length) throw new Error('lc_str OOB'); var offset = this.readUInt32(buf, off) - 8; if (offset > buf.length) throw new Error('lc_str offset OOB'); return this.parseCStr(buf.slice(offset)); } parseCommand(type, buf, file) { if (type === 'segment') return this.parseSegmentCmd(type, buf, file); else if (type === 'segment_64') return this.parseSegmentCmd(type, buf, file); else if (type === 'symtab') return this.parseSymtab(type, buf); else if (type === 'symseg') return this.parseSymseg(type, buf); else if (type === 'encryption_info') return this.parseEncryptionInfo(type, buf); else if (type === 'encryption_info_64') return this.parseEncryptionInfo64(type, buf); else if (type === 'rpath') return this.parseRpath(type, buf); else if (type === 'dysymtab') return this.parseDysymtab(type, buf); else if (type === 'load_dylib' || type === 'id_dylib') return this.parseLoadDylib(type, buf); else if (type === 'load_weak_dylib') return this.parseLoadDylib(type, buf); else if (type === 'load_dylinker' || type === 'id_dylinker') return this.parseLoadDylinker(type, buf); else if (type === 'version_min_macosx' || type === 'version_min_iphoneos') return this.parseVersionMin(type, buf); else if (type === 'code_signature' || type === 'segment_split_info') return this.parseLinkEdit(type, buf); else if (type === 'function_starts') return this.parseFunctionStarts(type, buf, file); else if (type === 'data_in_code') return this.parseLinkEdit(type, buf); else if (type === 'dylib_code_sign_drs') return this.parseLinkEdit(type, buf); else if (type === 'main') return this.parseMain(type, buf); else return { type: type, data: buf }; } parseSegmentCmd (type, buf, file) { var total = type === 'segment' ? 48 : 64; if (buf.length < total) throw new Error('Segment command OOB'); var name = this.parseCStr(buf.slice(0, 16)); if (type === 'segment') { var vmaddr = this.readUInt32(buf, 16); var vmsize = this.readUInt32(buf, 20); var fileoff = this.readUInt32(buf, 24); var filesize = this.readUInt32(buf, 28); var maxprot = this.readUInt32(buf, 32); var initprot = this.readUInt32(buf, 36); var nsects = this.readUInt32(buf, 40); var flags = this.readUInt32(buf, 44); } else { var vmaddr = this.readUInt64(buf, 16); var vmsize = this.readUInt64(buf, 24); var fileoff = this.readUInt64(buf, 32); var filesize = this.readUInt64(buf, 40); var maxprot = this.readUInt32(buf, 48); var initprot = this.readUInt32(buf, 52); var nsects = this.readUInt32(buf, 56); var flags = this.readUInt32(buf, 60); } function prot(p) { var res = { read: false, write: false, exec: false }; if (p !== constants.prot.none) { res.read = (p & constants.prot.read) !== 0; res.write = (p & constants.prot.write) !== 0; res.exec = (p & constants.prot.execute) !== 0; } return res; } var sectSize = type === 'segment' ? 32 + 9 * 4 : 32 + 8 * 4 + 2 * 8; var sections = []; for (var i = 0, off = total; i < nsects; i++, off += sectSize) { if (off + sectSize > buf.length) throw new Error('Segment OOB'); var sectname = this.parseCStr(buf.slice(off, off + 16)); var segname = this.parseCStr(buf.slice(off + 16, off + 32)); if (type === 'segment') { var addr = this.readUInt32(buf, off + 32); var size = this.readUInt32(buf, off + 36); var offset = this.readUInt32(buf, off + 40); var align = this.readUInt32(buf, off + 44); var reloff = this.readUInt32(buf, off + 48); var nreloc = this.readUInt32(buf, off + 52); var flags = this.readUInt32(buf, off + 56); } else { var addr = this.readUInt64(buf, off + 32); var size = this.readUInt64(buf, off + 40); var offset = this.readUInt32(buf, off + 48); var align = this.readUInt32(buf, off + 52); var reloff = this.readUInt32(buf, off + 56); var nreloc = this.readUInt32(buf, off + 60); var flags = this.readUInt32(buf, off + 64); } sections.push({ sectname: sectname, segname: segname, addr: addr, size: size, offset: offset, align: align, reloff: reloff, nreloc: nreloc, type: constants.segType[flags & constants.segTypeMask], attributes: { usr: this.mapFlags(flags & constants.segAttrUsrMask, constants.segAttrUsr), sys: this.mapFlags(flags & constants.segAttrSysMask, constants.segAttrSys) }, data: file.slice(offset, offset + size) }); } return { type: type, name: name, vmaddr: vmaddr, vmsize: vmsize, fileoff: fileoff, filesize: filesize, maxprot: prot(maxprot), initprot: prot(initprot), nsects: nsects, flags: this.mapFlags(flags, constants.segFlag), sections: sections }; } parseSymtab (type, buf) { if (buf.length !== 16) throw new Error('symtab OOB'); return { type: type, symoff: this.readUInt32(buf, 0), nsyms: this.readUInt32(buf, 4), stroff: this.readUInt32(buf, 8), strsize: this.readUInt32(buf, 12) }; } parseSymseg(type, buf) { if (buf.length !== 8) throw new Error('symseg OOB'); return { type: type, offset: this.readUInt32(buf, 0), size: this.readUInt32(buf, 4) }; } parseEncryptionInfo (type, buf) { if (buf.length !== 12) throw new Error('encryptinfo OOB'); return { type: type, offset: this.readUInt32(buf, 0), size: this.readUInt32(buf, 4), id: this.readUInt32(buf, 8), }; } parseEncryptionInfo64 (type, buf) { if (buf.length !== 16) throw new Error('encryptinfo64 OOB'); return this.parseEncryptionInfo(type, buf.slice(0, 12)); }; parseDysymtab (type, buf) { if (buf.length !== 72) throw new Error('dysymtab OOB'); return { type: type, ilocalsym: this.readUInt32(buf, 0), nlocalsym: this.readUInt32(buf, 4), iextdefsym: this.readUInt32(buf, 8), nextdefsym: this.readUInt32(buf, 12), iundefsym: this.readUInt32(buf, 16), nundefsym: this.readUInt32(buf, 20), tocoff: this.readUInt32(buf, 24), ntoc: this.readUInt32(buf, 28), modtaboff: this.readUInt32(buf, 32), nmodtab: this.readUInt32(buf, 36), extrefsymoff: this.readUInt32(buf, 40), nextrefsyms: this.readUInt32(buf, 44), indirectsymoff: this.readUInt32(buf, 48), nindirectsyms: this.readUInt32(buf, 52), extreloff: this.readUInt32(buf, 56), nextrel: this.readUInt32(buf, 60), locreloff: this.readUInt32(buf, 64), nlocrel: this.readUInt32(buf, 68) }; } parseLoadDylinker (type, buf) { return { type: type, cmd: this.parseLCStr(buf, 0) }; } parseRpath (type, buf) { if (buf.length < 8) throw new Error('lc_rpath OOB'); return { type: type, name: this.parseLCStr(buf, 0), }; } parseLoadDylib (type, buf) { if (buf.length < 16) throw new Error('load_dylib OOB'); return { type: type, name: this.parseLCStr(buf, 0), timestamp: this.readUInt32(buf, 4), current_version: this.readUInt32(buf, 8), compatibility_version: this.readUInt32(buf, 12) }; } parseVersionMin (type, buf) { if (buf.length !== 8) throw new Error('min version OOB'); return { type: type, version: this.readUInt16(buf, 2) + '.' + buf[1] + '.' + buf[0], sdk: this.readUInt16(buf, 6) + '.' + buf[5] + '.' + buf[4] }; } parseLinkEdit (type, buf) { if (buf.length !== 8) throw new Error('link_edit OOB'); return { type: type, dataoff: this.readUInt32(buf, 0), datasize: this.readUInt32(buf, 4) }; } // NOTE: returned addresses are relative to the "base address", i.e. // the vmaddress of the first "non-null" segment [e.g. initproto!=0] // (i.e. __TEXT ?) parseFunctionStarts (type, buf, file) { if (buf.length !== 8) throw new Error('function_starts OOB'); var dataoff = this.readUInt32(buf, 0); var datasize = this.readUInt32(buf, 4); var data = file.slice(dataoff, dataoff + datasize); var addresses = []; var address = 0; // TODO? use start address / "base address" // read array of uleb128-encoded deltas var delta = 0, shift = 0; for (var i = 0; i < data.length; i++) { delta |= (data[i] & 0x7f) << shift; if ((data[i] & 0x80) !== 0) { // delta value not finished yet shift += 7; if (shift > 24) throw new Error('function_starts delta too large'); else if (i + 1 === data.length) throw new Error('function_starts delta truncated'); } else if (delta === 0) { // end of table break; } else { address += delta; addresses.push(address); delta = 0; shift = 0; } } return { type: type, dataoff: dataoff, datasize: datasize, addresses: addresses }; } parseMain (type, buf) { if (buf.length < 16) throw new Error('main OOB'); return { type: type, entryoff: this.readUInt64(buf, 0), stacksize: this.readUInt64(buf, 8) }; } }