Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions docs/machostructure-dsl.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Internal MachOStructure DSL
## Documentation
The MachOStructure class makes it easy to describe binary chunks by using the #field method. This method generates the byte size and format strings necessary to parse a chunk of binary data. It also automatically generates the constructor and readers for all fields as well.

The fields are created in order so you will be expected to pass those arguements to the constructor in the same order. Fields with no arguments should be defined last and fields with default arguments should be defined right before them.

The type and options of inherited fields can be changed but their argument position and the number of arguments (used to calculate min_args) will also not change.

Usually, endianness is handled by the Utils#specialize_format method but occasionally a field needs to specifiy that beforehand. That is what the :endian option is for. If not specified, a placeholder is used so that can be specified later.

## Syntax
```ruby
field [field name], [field type], [option1 => value1], [option2 => value2], ...
```

## Example
```ruby
class AllFields < MachO::MachOStructure
field :name1, :string, :size => 16
field :name3, :int32
field :name4, :uint32
field :name5, :uint64
field :name6, :view
field :name7, :lcstr
field :name8, :two_level_hints_table
field :name9, :tool_entries
end
```

## Field Types
- `:string` [requires `:size` option] [optional `:padding` option]
- a string
- `:int32 `
- a signed 32 bit integer
- `:uint32 `
- an unsigned 32 bit integer
- `:uint64 `
- an unsigned 64 bit integer
- `:view` [initalized]
- an instance of the MachOView class (lib/macho/view.rb)
- `:lcstr` [NOT initalized]
- an instance of the LCStr class (lib/macho/load_commands.rb)
- `:two_level_hints_table` [NOT initalized] [NO argument]
- an instance of the TwoLevelHintsTable class (lib/macho/load_commands.rb)
- `:tool_entries` [NOT initalized]
- an instance of the ToolEntries class (lib/macho/load_commands.rb)

## Option Types
- Exclusive (only one can be used at a time)
- `:mask` [Integer] bitmask to be applied to field
- `:unpack` [String] binary unpack string used for further unpacking of :string
- `:default` [Value] default field value
- Inclusive (can be used with other options)
- `:to_s` [Boolean] generate `#to_s` method based on field
- Used with Integer field types
- `:endian` [Symbol] optionally specify `:big` or `:little` endian
- Used with `:string` field type
- `:size` [Integer] size in bytes
- `:padding` [Symbol] optionally specify `:null` padding

## More Infomation
Hop over to lib/macho/structure.rb to see the class itself.
2 changes: 1 addition & 1 deletion lib/macho.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "open3"

require_relative "macho/utils"
require_relative "macho/structure"
require_relative "macho/view"
require_relative "macho/headers"
Expand All @@ -10,7 +11,6 @@
require_relative "macho/macho_file"
require_relative "macho/fat_file"
require_relative "macho/exceptions"
require_relative "macho/utils"
require_relative "macho/tools"

# The primary namespace for ruby-macho.
Expand Down
171 changes: 33 additions & 138 deletions lib/macho/headers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -505,30 +505,14 @@ module Headers
# @see MachO::FatArch
class FatHeader < MachOStructure
# @return [Integer] the magic number of the header (and file)
attr_reader :magic
field :magic, :uint32, :endian => :big

# @return [Integer] the number of fat architecture structures following the header
attr_reader :nfat_arch

# always big-endian
# @see MachOStructure::FORMAT
# @api private
FORMAT = "N2"

# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 8

# @api private
def initialize(magic, nfat_arch)
super()
@magic = magic
@nfat_arch = nfat_arch
end
field :nfat_arch, :uint32, :endian => :big

# @return [String] the serialized fields of the fat header
def serialize
[magic, nfat_arch].pack(FORMAT)
[magic, nfat_arch].pack(self.class.format)
end

# @return [Hash] a hash representation of this {FatHeader}
Expand All @@ -548,42 +532,23 @@ def to_h
# @see MachO::Headers::FatHeader
class FatArch < MachOStructure
# @return [Integer] the CPU type of the Mach-O
attr_reader :cputype
field :cputype, :uint32, :endian => :big

# @return [Integer] the CPU subtype of the Mach-O
attr_reader :cpusubtype
field :cpusubtype, :uint32, :endian => :big, :mask => CPU_SUBTYPE_MASK

# @return [Integer] the file offset to the beginning of the Mach-O data
attr_reader :offset
field :offset, :uint32, :endian => :big

# @return [Integer] the size, in bytes, of the Mach-O data
attr_reader :size
field :size, :uint32, :endian => :big

# @return [Integer] the alignment, as a power of 2
attr_reader :align

# @note Always big endian.
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L>5"

# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 20

# @api private
def initialize(cputype, cpusubtype, offset, size, align)
super()
@cputype = cputype
@cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
@offset = offset
@size = size
@align = align
end
field :align, :uint32, :endian => :big

# @return [String] the serialized fields of the fat arch
def serialize
[cputype, cpusubtype, offset, size, align].pack(FORMAT)
[cputype, cpusubtype, offset, size, align].pack(self.class.format)
end

# @return [Hash] a hash representation of this {FatArch}
Expand All @@ -606,27 +571,18 @@ def to_h
# Mach-Os that it points to necessarily *are* 64-bit.
# @see MachO::Headers::FatHeader
class FatArch64 < FatArch
# @return [void]
attr_reader :reserved

# @note Always big endian.
# @see MachOStructure::FORMAT
# @api private
FORMAT = "L>2Q>2L>2"
# @return [Integer] the file offset to the beginning of the Mach-O data
field :offset, :uint64, :endian => :big

# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 32
# @return [Integer] the size, in bytes, of the Mach-O data
field :size, :uint64, :endian => :big

# @api private
def initialize(cputype, cpusubtype, offset, size, align, reserved = 0)
super(cputype, cpusubtype, offset, size, align)
@reserved = reserved
end
# @return [void]
field :reserved, :uint32, :endian => :big, :default => 0

# @return [String] the serialized fields of the fat arch
def serialize
[cputype, cpusubtype, offset, size, align, reserved].pack(FORMAT)
[cputype, cpusubtype, offset, size, align, reserved].pack(self.class.format)
end

# @return [Hash] a hash representation of this {FatArch64}
Expand All @@ -640,48 +596,25 @@ def to_h
# 32-bit Mach-O file header structure
class MachHeader < MachOStructure
# @return [Integer] the magic number
attr_reader :magic
field :magic, :uint32

# @return [Integer] the CPU type of the Mach-O
attr_reader :cputype
field :cputype, :uint32

# @return [Integer] the CPU subtype of the Mach-O
attr_reader :cpusubtype
field :cpusubtype, :uint32, :mask => CPU_SUBTYPE_MASK

# @return [Integer] the file type of the Mach-O
attr_reader :filetype
field :filetype, :uint32

# @return [Integer] the number of load commands in the Mach-O
attr_reader :ncmds
field :ncmds, :uint32

# @return [Integer] the size of all load commands, in bytes, in the Mach-O
attr_reader :sizeofcmds
field :sizeofcmds, :uint32

# @return [Integer] the header flags associated with the Mach-O
attr_reader :flags

# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=7"

# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 28

# @api private
def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
flags)
super()
@magic = magic
@cputype = cputype
# For now we're not interested in additional capability bits also to be
# found in the `cpusubtype` field. We only care about the CPU sub-type.
@cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK
@filetype = filetype
@ncmds = ncmds
@sizeofcmds = sizeofcmds
@flags = flags
end
field :flags, :uint32

# @example
# puts "this mach-o has position-independent execution" if header.flag?(:MH_PIE)
Expand Down Expand Up @@ -787,22 +720,7 @@ def to_h
# 64-bit Mach-O file header structure
class MachHeader64 < MachHeader
# @return [void]
attr_reader :reserved

# @see MachOStructure::FORMAT
# @api private
FORMAT = "L=8"

# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 32

# @api private
def initialize(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds,
flags, reserved)
super(magic, cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags)
@reserved = reserved
end
field :reserved, :uint32

# @return [Hash] a hash representation of this {MachHeader64}
def to_h
Expand All @@ -815,54 +733,31 @@ def to_h
# Prelinked kernel/"kernelcache" header structure
class PrelinkedKernelHeader < MachOStructure
# @return [Integer] the magic number for a compressed header ({COMPRESSED_MAGIC})
attr_reader :signature
field :signature, :uint32, :endian => :big

# @return [Integer] the type of compression used
attr_reader :compress_type
field :compress_type, :uint32, :endian => :big

# @return [Integer] a checksum for the uncompressed data
attr_reader :adler32
field :adler32, :uint32, :endian => :big

# @return [Integer] the size of the uncompressed data, in bytes
attr_reader :uncompressed_size
field :uncompressed_size, :uint32, :endian => :big

# @return [Integer] the size of the compressed data, in bytes
attr_reader :compressed_size
field :compressed_size, :uint32, :endian => :big

# @return [Integer] the version of the prelink format
attr_reader :prelink_version
field :prelink_version, :uint32, :endian => :big

# @return [void]
attr_reader :reserved
field :reserved, :string, :size => 40, :unpack => "L>10"

# @return [void]
attr_reader :platform_name
field :platform_name, :string, :size => 64

# @return [void]
attr_reader :root_path

# @see MachOStructure::FORMAT
# @api private
FORMAT = "L>6a40a64a256"

# @see MachOStructure::SIZEOF
# @api private
SIZEOF = 384

# @api private
def initialize(signature, compress_type, adler32, uncompressed_size, compressed_size, prelink_version, reserved, platform_name, root_path)
super()

@signature = signature
@compress_type = compress_type
@adler32 = adler32
@uncompressed_size = uncompressed_size
@compressed_size = compressed_size
@prelink_version = prelink_version
@reserved = reserved.unpack("L>10")
@platform_name = platform_name
@root_path = root_path
end
field :root_path, :string, :size => 256

# @return [Boolean] whether this prelinked kernel supports KASLR
def kaslr?
Expand Down
Loading