Class: MachO::MachOFile
- Inherits:
-
Object
- Object
- MachO::MachOFile
- Defined in:
- lib/macho/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.
Class Method Summary collapse
Instance Method Summary collapse
-
#bundle? ⇒ Boolean
is the file a dynamically bound bundle?.
-
#change_dylib!(old_path, new_path) ⇒ Object
stub.
-
#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.
- #initialize_from_bin(bin) ⇒ Object
-
#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.
- #serialize ⇒ Object
-
#sizeofcmds ⇒ Object
size of all load commands.
- #write(filename) ⇒ Object
- #write! ⇒ Object
Constructor Details
#initialize(filename) ⇒ MachOFile
Returns a new instance of MachOFile.
12 13 14 15 16 17 18 19 |
# File 'lib/macho/macho_file.rb', line 12 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/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/macho_file.rb', line 3 def load_commands @load_commands end |
Class Method Details
.new_from_bin(bin) ⇒ Object
5 6 7 8 9 10 |
# File 'lib/macho/macho_file.rb', line 5 def self.new_from_bin(bin) instance = allocate instance.initialize_from_bin(bin) instance end |
Instance Method Details
#bundle? ⇒ Boolean
is the file a dynamically bound bundle?
51 52 53 |
# File 'lib/macho/macho_file.rb', line 51 def bundle? header[:filetype] == MH_BUNDLE end |
#change_dylib!(old_path, new_path) ⇒ Object
stub
221 222 223 |
# File 'lib/macho/macho_file.rb', line 221 def change_dylib!(old_path, new_path) raise DylibUnknownError.new(old_path) unless linked_dylibs.include?(old_path) end |
#command(name) ⇒ Object Also known as: []
get load commands by name
95 96 97 |
# File 'lib/macho/macho_file.rb', line 95 def command(name) load_commands.select { |lc| lc.to_s == name } end |
#cpusubtype ⇒ Object
string representation of the header’s cpusubtype field
75 76 77 |
# File 'lib/macho/macho_file.rb', line 75 def cpusubtype CPU_SUBTYPES[header[:cpusubtype]] end |
#cputype ⇒ Object
string representation of the header’s cputype field
70 71 72 |
# File 'lib/macho/macho_file.rb', line 70 def cputype CPU_TYPES[header[:cputype]] end |
#dylib? ⇒ Boolean
is the file a dynamically bound shared object?
46 47 48 |
# File 'lib/macho/macho_file.rb', line 46 def dylib? header[:filetype] == MH_DYLIB end |
#dylib_id ⇒ Object
get the file’s dylib id, if it is a dylib
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/macho/macho_file.rb', line 111 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
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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/macho/macho_file.rb', line 127 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
is the file executable?
41 42 43 |
# File 'lib/macho/macho_file.rb', line 41 def executable? header[:filetype] == MH_EXECUTE end |
#filetype ⇒ Object
string representation of the header’s filetype field
65 66 67 |
# File 'lib/macho/macho_file.rb', line 65 def filetype MH_FILETYPES[header[:filetype]] end |
#flags ⇒ Object
various execution flags
90 91 92 |
# File 'lib/macho/macho_file.rb', line 90 def flags header[:flags] end |
#initialize_from_bin(bin) ⇒ Object
21 22 23 24 25 26 |
# File 'lib/macho/macho_file.rb', line 21 def initialize_from_bin(bin) @filename = nil @raw_data = bin @header = get_mach_header @load_commands = get_load_commands end |
#linked_dylibs ⇒ Object
get a list of dylib paths linked to this file
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/macho/macho_file.rb', line 203 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
55 56 57 |
# File 'lib/macho/macho_file.rb', line 55 def magic header[:magic] end |
#magic32? ⇒ Boolean
32 33 34 |
# File 'lib/macho/macho_file.rb', line 32 def magic32? MachO.magic32?(header[:magic]) end |
#magic64? ⇒ Boolean
36 37 38 |
# File 'lib/macho/macho_file.rb', line 36 def magic64? MachO.magic64?(header[:magic]) end |
#magic_string ⇒ Object
string representation of the header’s magic bytes
60 61 62 |
# File 'lib/macho/macho_file.rb', line 60 def magic_string MH_MAGICS[header[:magic]] end |
#ncmds ⇒ Object
number of load commands in the header
80 81 82 |
# File 'lib/macho/macho_file.rb', line 80 def ncmds header[:ncmds] end |
#sections(segment) ⇒ Object
get all sections in a segment by name
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/macho/macho_file.rb', line 226 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
102 103 104 105 106 107 108 |
# File 'lib/macho/macho_file.rb', line 102 def segments if magic32? command("LC_SEGMENT") else command("LC_SEGMENT_64") end end |
#serialize ⇒ Object
28 29 30 |
# File 'lib/macho/macho_file.rb', line 28 def serialize @raw_data end |
#sizeofcmds ⇒ Object
size of all load commands
85 86 87 |
# File 'lib/macho/macho_file.rb', line 85 def sizeofcmds header[:sizeofcmds] end |
#write(filename) ⇒ Object
252 253 254 |
# File 'lib/macho/macho_file.rb', line 252 def write(filename) File.open(filename, "wb") { |f| f.write(@raw_data) } end |
#write! ⇒ Object
256 257 258 259 260 261 262 |
# File 'lib/macho/macho_file.rb', line 256 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 |