Class: MachO::MachOFile
- Inherits:
-
Object
- Object
- MachO::MachOFile
- Defined in:
- lib/macho/macho_file.rb
Overview
Represents a Mach-O file, which contains a header and load commands as well as binary executable instructions. Mach-O binaries are architecture specific.
Instance Attribute Summary collapse
- #header ⇒ MachO::MachHeader, MachO::MachHeader64 readonly
-
#load_commands ⇒ Array<MachO::LoadCommand>
readonly
An array of the file’s load commands.
Class Method Summary collapse
-
.new_from_bin(bin) ⇒ MachO::MachOFile
Creates a new MachOFile instance from a binary string.
Instance Method Summary collapse
-
#bundle? ⇒ Boolean
True if the Mach-O is of type ‘MH_BUNDLE`, false otherwise.
- #change_install_name(old_name, new_name) ⇒ void (also: #change_dylib)
-
#command(name) ⇒ Array<MachO::LoadCommand>
(also: #[])
All load commands of a given name.
-
#cpusubtype ⇒ String
A string representation of the Mach-O’s CPU subtype.
-
#cputype ⇒ String
A string representation of the Mach-O’s CPU type.
-
#dylib? ⇒ Boolean
True if the Mach-O is of type ‘MH_DYLIB`, false otherwise.
-
#dylib_id ⇒ String
The Mach-O’s dylib ID, or ‘nil` if not a dylib.
-
#dylib_id=(new_id) ⇒ void
Changes the Mach-O’s dylib ID to ‘new_id`.
-
#executable? ⇒ Boolean
True if the Mach-O is of type ‘MH_EXECUTE`, false otherwise.
-
#filetype ⇒ String
A string representation of the Mach-O’s filetype.
-
#flags ⇒ Fixnum
Execution flags set by the linker.
-
#initialize(filename) ⇒ MachOFile
constructor
Creates a new FatFile from the given filename.
- #initialize_from_bin(bin) ⇒ Object
-
#linked_dylibs ⇒ Array<String>
All shared libraries linked to the Mach-O.
-
#magic ⇒ Fixnum
The Mach-O’s magic number.
-
#magic32? ⇒ Boolean
True if the Mach-O has 32-bit magic, false otherwise.
-
#magic64? ⇒ Boolean
True if the Mach-O has 64-bit magic, false otherwise.
-
#magic_string ⇒ String
A string representation of the Mach-O’s magic number.
-
#ncmds ⇒ Fixnum
The number of load commands in the Mach-O’s header.
-
#sections(segment) ⇒ Array<MachO::Section>, Array<MachO::Section64>
All sections of the segment ‘segment`.
-
#segments ⇒ Array<MachO::SegmentCommand>, Array<MachO::SegmentCommand64>
All segment load commands in the Mach-O.
-
#serialize ⇒ String
The file’s raw Mach-O data.
-
#sizeofcmds ⇒ Fixnum
The size of all load commands, in bytes.
-
#write(filename) ⇒ void
Write all Mach-O data to the given filename.
-
#write! ⇒ void
Write all Mach-O data to the file used to initialize the instance.
Constructor Details
#initialize(filename) ⇒ MachOFile
document all the exceptions propagated here
Creates a new FatFile from the given filename.
28 29 30 31 32 33 34 35 |
# File 'lib/macho/macho_file.rb', line 28 def initialize(filename) raise ArgumentError.new("filename must be a String") unless filename.is_a? String @filename = filename @raw_data = open(@filename, "rb") { |f| f.read } @header = get_mach_header @load_commands = get_load_commands end |
Instance Attribute Details
#header ⇒ MachO::MachHeader, MachO::MachHeader64 (readonly)
10 11 12 |
# File 'lib/macho/macho_file.rb', line 10 def header @header end |
#load_commands ⇒ Array<MachO::LoadCommand> (readonly)
Returns an array of the file’s load commands.
13 14 15 |
# File 'lib/macho/macho_file.rb', line 13 def load_commands @load_commands end |
Class Method Details
.new_from_bin(bin) ⇒ MachO::MachOFile
Creates a new MachOFile instance from a binary string.
18 19 20 21 22 23 |
# File 'lib/macho/macho_file.rb', line 18 def self.new_from_bin(bin) instance = allocate instance.initialize_from_bin(bin) instance end |
Instance Method Details
#bundle? ⇒ Boolean
Returns true if the Mach-O is of type ‘MH_BUNDLE`, false otherwise.
72 73 74 |
# File 'lib/macho/macho_file.rb', line 72 def bundle? header[:filetype] == MH_BUNDLE end |
#change_install_name(old_name, new_name) ⇒ void Also known as: change_dylib
refactor
This method returns an undefined value.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/macho/macho_file.rb', line 259 def change_install_name(old_name, new_name) idx = linked_dylibs.index(old_name) raise DylibUnknownError.new(old_name) if idx.nil? # this is a bit of a hack - since there is a 1-1 ordered association # between linked_dylibs and command('LC_LOAD_DYLIB'), we can use # their indices interchangeably to avoid having to loop. dylib_cmd = command('LC_LOAD_DYLIB')[idx] if magic32? cmd_round = 4 else cmd_round = 8 end new_sizeofcmds = header[:sizeofcmds] old_install_name = old_name.dup new_install_name = new_name.dup old_pad = MachO.round(old_install_name.size, cmd_round) - old_install_name.size new_pad = MachO.round(new_install_name.size, cmd_round) - new_install_name.size old_install_name << "\x00" * old_pad new_install_name << "\x00" * new_pad new_size = DylibCommand.bytesize + new_install_name.size new_sizeofcmds += new_size - dylib_cmd.cmdsize low_fileoff = 2**64 segments.each do |seg| sections(seg).each do |sect| if sect.size != 0 && !sect.flag?(S_ZEROFILL) && !sect.flag?(S_THREAD_LOCAL_ZEROFILL) && sect.offset < low_fileoff low_fileoff = sect.offset end end end if new_sizeofcmds + header.bytesize > low_fileoff raise HeaderPadError.new(@filename) end set_sizeofcmds(new_sizeofcmds) @raw_data[dylib_cmd.offset + 4, 4] = [new_size].pack("V") @raw_data.slice!(dylib_cmd.offset + dylib_cmd.name...dylib_cmd.offset + dylib_cmd.class.bytesize + old_install_name.size) @raw_data.insert(dylib_cmd.offset + dylib_cmd.name, new_install_name) null_pad = old_install_name.size - new_install_name.size if null_pad < 0 @raw_data.slice!(new_sizeofcmds + header.bytesize, null_pad.abs) else @raw_data.insert(new_sizeofcmds + header.bytesize, "\x00" * null_pad) end header = get_mach_header load_commands = get_load_commands end |
#command(name) ⇒ Array<MachO::LoadCommand> Also known as: []
All load commands of a given name.
121 122 123 |
# File 'lib/macho/macho_file.rb', line 121 def command(name) load_commands.select { |lc| lc.to_s == name } end |
#cpusubtype ⇒ String
Returns a string representation of the Mach-O’s CPU subtype.
97 98 99 |
# File 'lib/macho/macho_file.rb', line 97 def cpusubtype CPU_SUBTYPES[header[:cpusubtype]] end |
#cputype ⇒ String
Returns a string representation of the Mach-O’s CPU type.
92 93 94 |
# File 'lib/macho/macho_file.rb', line 92 def cputype CPU_TYPES[header[:cputype]] end |
#dylib? ⇒ Boolean
Returns true if the Mach-O is of type ‘MH_DYLIB`, false otherwise.
67 68 69 |
# File 'lib/macho/macho_file.rb', line 67 def dylib? header[:filetype] == MH_DYLIB end |
#dylib_id ⇒ String
The Mach-O’s dylib ID, or ‘nil` if not a dylib.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/macho/macho_file.rb', line 142 def dylib_id if !dylib? return nil end dylib_id_cmd = command('LC_ID_DYLIB').first cmdsize = dylib_id_cmd.cmdsize offset = dylib_id_cmd.offset stroffset = dylib_id_cmd.name dylib_id = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first dylib_id.delete("\x00") end |
#dylib_id=(new_id) ⇒ void
refactor
This method returns an undefined value.
Changes the Mach-O’s dylib ID to ‘new_id`. Does nothing if not a dylib.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/macho/macho_file.rb', line 163 def dylib_id=(new_id) if !new_id.is_a?(String) raise ArgumentError.new("argument must be a String") end if !dylib? return nil end if magic32? cmd_round = 4 else cmd_round = 8 end new_sizeofcmds = header[:sizeofcmds] dylib_id_cmd = command('LC_ID_DYLIB').first old_id = dylib_id new_id = new_id.dup new_pad = MachO.round(new_id.size, cmd_round) - new_id.size old_pad = MachO.round(old_id.size, cmd_round) - old_id.size # pad the old and new IDs with null bytes to meet command bounds old_id << "\x00" * old_pad new_id << "\x00" * new_pad # calculate the new size of the DylibCommand and sizeofcmds in MH new_size = DylibCommand.bytesize + new_id.size new_sizeofcmds += new_size - dylib_id_cmd.cmdsize # calculate the low file offset (offset to first section data) low_fileoff = 2**64 # ULLONGMAX segments.each do |seg| sections(seg).each do |sect| if sect.size != 0 && !sect.flag?(S_ZEROFILL) && !sect.flag?(S_THREAD_LOCAL_ZEROFILL) && sect.offset < low_fileoff low_fileoff = sect.offset end end end if new_sizeofcmds + header.bytesize > low_fileoff raise HeaderPadError.new(@filename) end # update sizeofcmds in mach_header set_sizeofcmds(new_sizeofcmds) # update cmdsize in the dylib_command @raw_data[dylib_id_cmd.offset + 4, 4] = [new_size].pack("V") # delete the old id @raw_data.slice!(dylib_id_cmd.offset + dylib_id_cmd.name...dylib_id_cmd.offset + dylib_id_cmd.class.bytesize + old_id.size) # insert the new id @raw_data.insert(dylib_id_cmd.offset + dylib_id_cmd.name, new_id) # pad/unpad after new_sizeofcmds until offsets are corrected null_pad = old_id.size - new_id.size if null_pad < 0 @raw_data.slice!(new_sizeofcmds + header.bytesize, null_pad.abs) else @raw_data.insert(new_sizeofcmds + header.bytesize, "\x00" * null_pad) end # synchronize fields with the raw data header = get_mach_header load_commands = get_load_commands end |
#executable? ⇒ Boolean
Returns true if the Mach-O is of type ‘MH_EXECUTE`, false otherwise.
62 63 64 |
# File 'lib/macho/macho_file.rb', line 62 def executable? header[:filetype] == MH_EXECUTE end |
#filetype ⇒ String
Returns a string representation of the Mach-O’s filetype.
87 88 89 |
# File 'lib/macho/macho_file.rb', line 87 def filetype MH_FILETYPES[header[:filetype]] end |
#flags ⇒ Fixnum
Returns execution flags set by the linker.
112 113 114 |
# File 'lib/macho/macho_file.rb', line 112 def flags header[:flags] end |
#initialize_from_bin(bin) ⇒ Object
38 39 40 41 42 43 |
# File 'lib/macho/macho_file.rb', line 38 def initialize_from_bin(bin) @filename = nil @raw_data = bin @header = get_mach_header @load_commands = get_load_commands end |
#linked_dylibs ⇒ Array<String>
All shared libraries linked to the Mach-O.
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/macho/macho_file.rb', line 240 def linked_dylibs dylibs = [] dylib_cmds = command('LC_LOAD_DYLIB') dylib_cmds.each do |dylib_cmd| cmdsize = dylib_cmd.cmdsize offset = dylib_cmd.offset stroffset = dylib_cmd.name dylib = @raw_data.slice(offset + stroffset...offset + cmdsize).unpack("Z*").first dylibs << dylib end dylibs end |
#magic ⇒ Fixnum
Returns the Mach-O’s magic number.
77 78 79 |
# File 'lib/macho/macho_file.rb', line 77 def magic header[:magic] end |
#magic32? ⇒ Boolean
Returns true if the Mach-O has 32-bit magic, false otherwise.
52 53 54 |
# File 'lib/macho/macho_file.rb', line 52 def magic32? MachO.magic32?(header[:magic]) end |
#magic64? ⇒ Boolean
Returns true if the Mach-O has 64-bit magic, false otherwise.
57 58 59 |
# File 'lib/macho/macho_file.rb', line 57 def magic64? MachO.magic64?(header[:magic]) end |
#magic_string ⇒ String
Returns a string representation of the Mach-O’s magic number.
82 83 84 |
# File 'lib/macho/macho_file.rb', line 82 def magic_string MH_MAGICS[header[:magic]] end |
#ncmds ⇒ Fixnum
Returns the number of load commands in the Mach-O’s header.
102 103 104 |
# File 'lib/macho/macho_file.rb', line 102 def ncmds header[:ncmds] end |
#sections(segment) ⇒ Array<MachO::Section>, Array<MachO::Section64>
All sections of the segment ‘segment`.
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/macho/macho_file.rb', line 329 def sections(segment) sections = [] if !segment.is_a?(SegmentCommand) && !segment.is_a?(SegmentCommand64) raise ArgumentError.new("not a valid segment") end if segment.nsects.zero? return sections end offset = segment.offset + segment.class.bytesize segment.nsects.times do if segment.is_a? SegmentCommand sections << Section.new_from_bin(@raw_data.slice(offset, Section.bytesize)) offset += Section.bytesize else sections << Section64.new_from_bin(@raw_data.slice(offset, Section64.bytesize)) offset += Section64.bytesize end end sections end |
#segments ⇒ Array<MachO::SegmentCommand>, Array<MachO::SegmentCommand64>
All segment load commands in the Mach-O.
130 131 132 133 134 135 136 |
# File 'lib/macho/macho_file.rb', line 130 def segments if magic32? command("LC_SEGMENT") else command("LC_SEGMENT_64") end end |
#serialize ⇒ String
The file’s raw Mach-O data.
47 48 49 |
# File 'lib/macho/macho_file.rb', line 47 def serialize @raw_data end |
#sizeofcmds ⇒ Fixnum
Returns the size of all load commands, in bytes.
107 108 109 |
# File 'lib/macho/macho_file.rb', line 107 def sizeofcmds header[:sizeofcmds] end |
#write(filename) ⇒ void
This method returns an undefined value.
Write all Mach-O data to the given filename.
358 359 360 |
# File 'lib/macho/macho_file.rb', line 358 def write(filename) File.open(filename, "wb") { |f| f.write(@raw_data) } end |
#write! ⇒ void
Overwrites all data in the file!
This method returns an undefined value.
Write all Mach-O data to the file used to initialize the instance.
366 367 368 369 370 371 372 |
# File 'lib/macho/macho_file.rb', line 366 def write! if @filename.nil? raise MachOError.new("cannot write to a default file when initialized from a binary string") else File.open(@filename, "wb") { |f| f.write(@raw_data) } end end |