Android Boot Image: Ruby parsing library

File extension

img

KS implementation details

License: CC0-1.0

This page hosts a formal specification of Android Boot Image using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.

The Ruby runtime library can be installed from RubyGems:

gem install kaitai-struct

Code

Parse a local file and get structure in memory:

data = AndroidImg.from_file("path/to/local/file.img")

Or parse structure from a string of bytes:

bytes = "\x00\x01\x02..."
data = AndroidImg.new(Kaitai::Struct::Stream.new(bytes))

After that, one can get various attributes from the structure by invoking getter methods like:

data.base # => base loading address

Ruby source code to parse Android Boot Image

android_img.rb

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

require 'kaitai/struct/struct'

unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
  raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end


##
# @see https://source.android.com/docs/core/architecture/bootloader/boot-image-header Source
class AndroidImg < Kaitai::Struct::Struct
  def initialize(_io, _parent = nil, _root = self)
    super(_io, _parent, _root)
    _read
  end

  def _read
    @magic = @_io.read_bytes(8)
    raise Kaitai::Struct::ValidationNotEqualError.new([65, 78, 68, 82, 79, 73, 68, 33].pack('C*'), magic, _io, "/seq/0") if not magic == [65, 78, 68, 82, 79, 73, 68, 33].pack('C*')
    @kernel = Load.new(@_io, self, @_root)
    @ramdisk = Load.new(@_io, self, @_root)
    @second = Load.new(@_io, self, @_root)
    @tags_load = @_io.read_u4le
    @page_size = @_io.read_u4le
    @header_version = @_io.read_u4le
    @os_version = OsVersion.new(@_io, self, @_root)
    @name = (Kaitai::Struct::Stream::bytes_terminate(@_io.read_bytes(16), 0, false)).force_encoding("ASCII")
    @cmdline = (Kaitai::Struct::Stream::bytes_terminate(@_io.read_bytes(512), 0, false)).force_encoding("ASCII")
    @sha = @_io.read_bytes(32)
    @extra_cmdline = (Kaitai::Struct::Stream::bytes_terminate(@_io.read_bytes(1024), 0, false)).force_encoding("ASCII")
    if header_version > 0
      @recovery_dtbo = SizeOffset.new(@_io, self, @_root)
    end
    if header_version > 0
      @boot_header_size = @_io.read_u4le
    end
    if header_version > 1
      @dtb = LoadLong.new(@_io, self, @_root)
    end
    self
  end
  class Load < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @size = @_io.read_u4le
      @addr = @_io.read_u4le
      self
    end
    attr_reader :size
    attr_reader :addr
  end
  class LoadLong < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @size = @_io.read_u4le
      @addr = @_io.read_u8le
      self
    end
    attr_reader :size
    attr_reader :addr
  end
  class SizeOffset < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @size = @_io.read_u4le
      @offset = @_io.read_u8le
      self
    end
    attr_reader :size
    attr_reader :offset
  end
  class OsVersion < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @version = @_io.read_u4le
      self
    end
    def month
      return @month unless @month.nil?
      @month = (version & 15)
      @month
    end
    def patch
      return @patch unless @patch.nil?
      @patch = ((version >> 11) & 127)
      @patch
    end
    def year
      return @year unless @year.nil?
      @year = (((version >> 4) & 127) + 2000)
      @year
    end
    def major
      return @major unless @major.nil?
      @major = ((version >> 25) & 127)
      @major
    end
    def minor
      return @minor unless @minor.nil?
      @minor = ((version >> 18) & 127)
      @minor
    end
    attr_reader :version
  end
  def kernel_img
    return @kernel_img unless @kernel_img.nil?
    _pos = @_io.pos
    @_io.seek(page_size)
    @kernel_img = @_io.read_bytes(kernel.size)
    @_io.seek(_pos)
    @kernel_img
  end

  ##
  # tags offset from base
  def tags_offset
    return @tags_offset unless @tags_offset.nil?
    @tags_offset = (tags_load - base)
    @tags_offset
  end

  ##
  # ramdisk offset from base
  def ramdisk_offset
    return @ramdisk_offset unless @ramdisk_offset.nil?
    @ramdisk_offset = (ramdisk.addr > 0 ? (ramdisk.addr - base) : 0)
    @ramdisk_offset
  end

  ##
  # 2nd bootloader offset from base
  def second_offset
    return @second_offset unless @second_offset.nil?
    @second_offset = (second.addr > 0 ? (second.addr - base) : 0)
    @second_offset
  end

  ##
  # kernel offset from base
  def kernel_offset
    return @kernel_offset unless @kernel_offset.nil?
    @kernel_offset = (kernel.addr - base)
    @kernel_offset
  end

  ##
  # dtb offset from base
  def dtb_offset
    return @dtb_offset unless @dtb_offset.nil?
    if header_version > 1
      @dtb_offset = (dtb.addr > 0 ? (dtb.addr - base) : 0)
    end
    @dtb_offset
  end
  def dtb_img
    return @dtb_img unless @dtb_img.nil?
    if  ((header_version > 1) && (dtb.size > 0)) 
      _pos = @_io.pos
      @_io.seek(((((((((page_size + kernel.size) + ramdisk.size) + second.size) + recovery_dtbo.size) + page_size) - 1) / page_size) * page_size))
      @dtb_img = @_io.read_bytes(dtb.size)
      @_io.seek(_pos)
    end
    @dtb_img
  end
  def ramdisk_img
    return @ramdisk_img unless @ramdisk_img.nil?
    if ramdisk.size > 0
      _pos = @_io.pos
      @_io.seek((((((page_size + kernel.size) + page_size) - 1) / page_size) * page_size))
      @ramdisk_img = @_io.read_bytes(ramdisk.size)
      @_io.seek(_pos)
    end
    @ramdisk_img
  end
  def recovery_dtbo_img
    return @recovery_dtbo_img unless @recovery_dtbo_img.nil?
    if  ((header_version > 0) && (recovery_dtbo.size > 0)) 
      _pos = @_io.pos
      @_io.seek(recovery_dtbo.offset)
      @recovery_dtbo_img = @_io.read_bytes(recovery_dtbo.size)
      @_io.seek(_pos)
    end
    @recovery_dtbo_img
  end
  def second_img
    return @second_img unless @second_img.nil?
    if second.size > 0
      _pos = @_io.pos
      @_io.seek(((((((page_size + kernel.size) + ramdisk.size) + page_size) - 1) / page_size) * page_size))
      @second_img = @_io.read_bytes(second.size)
      @_io.seek(_pos)
    end
    @second_img
  end

  ##
  # base loading address
  def base
    return @base unless @base.nil?
    @base = (kernel.addr - 32768)
    @base
  end
  attr_reader :magic
  attr_reader :kernel
  attr_reader :ramdisk
  attr_reader :second
  attr_reader :tags_load
  attr_reader :page_size
  attr_reader :header_version
  attr_reader :os_version
  attr_reader :name
  attr_reader :cmdline
  attr_reader :sha
  attr_reader :extra_cmdline
  attr_reader :recovery_dtbo
  attr_reader :boot_header_size
  attr_reader :dtb
end