Class: MachO::MachOFile
- Inherits:
-
Object
- Object
- MachO::MachOFile
- Defined in:
- lib/macho/file.rb
Instance Attribute Summary collapse
-
#header ⇒ Object
readonly
Returns the value of attribute header.
-
#load_commands ⇒ Object
readonly
Returns the value of attribute load_commands.
Instance Method Summary collapse
-
#bundle? ⇒ Boolean
is the file a dynamically bound bundle?.
-
#command(name) ⇒ Object
(also: #[])
get load commands by name.
-
#cpusubtype ⇒ Object
string representation of the header’s cpusubtype field.
-
#cputype ⇒ Object
string representation of the header’s cputype field.
-
#dylib? ⇒ Boolean
is the file a dynamically bound shared object?.
-
#dylib_id ⇒ Object
get the file’s dylib id, if it is a dylib.
- #dylib_id=(new_id) ⇒ Object
-
#executable? ⇒ Boolean
is the file executable?.
-
#filetype ⇒ Object
string representation of the header’s filetype field.
-
#flags ⇒ Object
various execution flags.
-
#initialize(filename) ⇒ MachOFile
constructor
A new instance of MachOFile.
-
#linked_dylibs ⇒ Object
get a list of dylib paths linked to this file.
- #magic ⇒ Object
- #magic32? ⇒ Boolean
- #magic64? ⇒ Boolean
-
#magic_string ⇒ Object
string representation of the header’s magic bytes.
-
#ncmds ⇒ Object
number of load commands in the header.
-
#sections(segment) ⇒ Object
get all sections in a segment by name.
-
#segments ⇒ Object
get all segment commands.
-
#sizeofcmds ⇒ Object
size of all load commands.
- #write(filename) ⇒ Object
- #write! ⇒ Object
Constructor Details
#initialize(filename) ⇒ MachOFile
Returns a new instance of MachOFile.
5 6 7 8 9 10 11 12 |
# File 'lib/macho/file.rb', line 5 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 ⇒ Object (readonly)
Returns the value of attribute header.
3 4 5 |
# File 'lib/macho/file.rb', line 3 def header @header end |
#load_commands ⇒ Object (readonly)
Returns the value of attribute load_commands.
3 4 5 |
# File 'lib/macho/file.rb', line 3 def load_commands @load_commands end |
Instance Method Details
#bundle? ⇒ Boolean
is the file a dynamically bound bundle?
33 34 35 |
# File 'lib/macho/file.rb', line 33 def bundle? header[:filetype] == MH_BUNDLE end |
#command(name) ⇒ Object Also known as: []
get load commands by name
77 78 79 |
# File 'lib/macho/file.rb', line 77 def command(name) load_commands.select { |lc| lc.to_s == name } end |
#cpusubtype ⇒ Object
string representation of the header’s cpusubtype field
57 58 59 |
# File 'lib/macho/file.rb', line 57 def cpusubtype CPU_SUBTYPES[header[:cpusubtype]] end |
#cputype ⇒ Object
string representation of the header’s cputype field
52 53 54 |
# File 'lib/macho/file.rb', line 52 def cputype CPU_TYPES[header[:cputype]] end |
#dylib? ⇒ Boolean
is the file a dynamically bound shared object?
28 29 30 |
# File 'lib/macho/file.rb', line 28 def dylib? header[:filetype] == MH_DYLIB end |
#dylib_id ⇒ Object
get the file’s dylib id, if it is a dylib
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/macho/file.rb', line 93 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) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/macho/file.rb', line 109 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 = Utils.round(new_id.size, cmd_round) - new_id.size old_pad = Utils.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
is the file executable?
23 24 25 |
# File 'lib/macho/file.rb', line 23 def executable? header[:filetype] == MH_EXECUTE end |
#filetype ⇒ Object
string representation of the header’s filetype field
47 48 49 |
# File 'lib/macho/file.rb', line 47 def filetype MH_FILETYPES[header[:filetype]] end |
#flags ⇒ Object
various execution flags
72 73 74 |
# File 'lib/macho/file.rb', line 72 def flags header[:flags] end |
#linked_dylibs ⇒ Object
get a list of dylib paths linked to this file
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/macho/file.rb', line 185 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 ⇒ Object
37 38 39 |
# File 'lib/macho/file.rb', line 37 def magic header[:magic] end |
#magic32? ⇒ Boolean
14 15 16 |
# File 'lib/macho/file.rb', line 14 def magic32? Utils.magic32?(header[:magic]) end |
#magic64? ⇒ Boolean
18 19 20 |
# File 'lib/macho/file.rb', line 18 def magic64? Utils.magic64?(header[:magic]) end |
#magic_string ⇒ Object
string representation of the header’s magic bytes
42 43 44 |
# File 'lib/macho/file.rb', line 42 def magic_string MH_MAGICS[header[:magic]] end |
#ncmds ⇒ Object
number of load commands in the header
62 63 64 |
# File 'lib/macho/file.rb', line 62 def ncmds header[:ncmds] end |
#sections(segment) ⇒ Object
get all sections in a segment by name
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 |
# File 'lib/macho/file.rb', line 203 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 ⇒ Object
get all segment commands
84 85 86 87 88 89 90 |
# File 'lib/macho/file.rb', line 84 def segments if magic32? command("LC_SEGMENT") else command("LC_SEGMENT_64") end end |
#sizeofcmds ⇒ Object
size of all load commands
67 68 69 |
# File 'lib/macho/file.rb', line 67 def sizeofcmds header[:sizeofcmds] end |
#write(filename) ⇒ Object
229 230 231 |
# File 'lib/macho/file.rb', line 229 def write(filename) File.open(filename, "wb") { |f| f.write(@raw_data) } end |
#write! ⇒ Object
233 234 235 |
# File 'lib/macho/file.rb', line 233 def write! File.open(@filename, "wb") { |f| f.write(@raw_data) } end |