Class: AppInfo::Android::Signature::Info

Inherits:
Object
  • Object
show all
Includes:
Helper::IOBlock
Defined in:
lib/app_info/android/signatures/info.rb

Overview

APK signature scheme signurate info

FORMAT: OFFSET DATA TYPE DESCRIPTION

  • @+0 bytes uint64: size in bytes (excluding this field)

  • @+8 bytes payload

  • @-24 bytes uint64: size in bytes (same as the one above)

  • @-16 bytes uint128: magic value

Constant Summary collapse

SIG_SIZE_OF_BLOCK_SIZE =

Signature block information

8
SIG_MAGIC_BLOCK_SIZE =
16
SIG_BLOCK_MIN_SIZE =
32
SIG_MAGIC =

Magic value: APK Sig Block 42

[
  0x41, 0x50, 0x4b, 0x20, 0x53, 0x69,
  0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63,
  0x6b, 0x20, 0x34, 0x32
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helper::IOBlock

#left_bytes_check, #length_prefix_block, #loop_length_prefix_io

Constructor Details

#initialize(version, parser, logger) ⇒ Info

Returns a new instance of Info.



34
35
36
37
38
39
40
# File 'lib/app_info/android/signatures/info.rb', line 34

def initialize(version, parser, logger)
  @version = version
  @parser = parser
  @logger = logger

  pares_signatures_pairs
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



32
33
34
# File 'lib/app_info/android/signatures/info.rb', line 32

def logger
  @logger
end

#magicObject (readonly)

Returns the value of attribute magic.



32
33
34
# File 'lib/app_info/android/signatures/info.rb', line 32

def magic
  @magic
end

#pairsObject (readonly)

Returns the value of attribute pairs.



32
33
34
# File 'lib/app_info/android/signatures/info.rb', line 32

def pairs
  @pairs
end

#total_sizeObject (readonly)

Returns the value of attribute total_size.



32
33
34
# File 'lib/app_info/android/signatures/info.rb', line 32

def total_size
  @total_size
end

Instance Method Details

#cdir_offsetObject



137
138
139
140
141
142
# File 'lib/app_info/android/signatures/info.rb', line 137

def cdir_offset
  @cdir_offset ||= lambda {
    eocd_buffer = zip_io.get_e_o_c_d(start_buffer)
    eocd_buffer[12..16].unpack1('V')
  }.call
end

#file_ioObject



152
153
154
# File 'lib/app_info/android/signatures/info.rb', line 152

def file_io
  @file_io ||= ::File.open(@parser.file, 'rb')
end

#pares_signatures_pairsObject



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/app_info/android/signatures/info.rb', line 93

def pares_signatures_pairs
  block = signature_block
  block.rewind
  # get pairs size
  @total_size = block.size - (SIG_SIZE_OF_BLOCK_SIZE + SIG_MAGIC_BLOCK_SIZE)

  # get pairs block
  @pairs = StringIO.new(block.read(@total_size))

  # get magic value
  block.seek(block.pos + SIG_SIZE_OF_BLOCK_SIZE)
  @magic = block.read(SIG_MAGIC_BLOCK_SIZE)
end

#signature_blockObject



107
108
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
# File 'lib/app_info/android/signatures/info.rb', line 107

def signature_block
  @signature_block ||= lambda {
    logger.debug "cdir_offset: #{cdir_offset}"

    file_io.seek(cdir_offset - (Info::SIG_MAGIC_BLOCK_SIZE + Info::SIG_SIZE_OF_BLOCK_SIZE))
    footer_block = file_io.read(Info::SIG_SIZE_OF_BLOCK_SIZE)
    if footer_block.size < Info::SIG_SIZE_OF_BLOCK_SIZE
      raise NotFoundError, "APK Signing Block size out of range: #{footer_block.size}"
    end

    footer = footer_block.unpack1('Q')
    total_size = footer
    offset = cdir_offset - total_size - Info::SIG_SIZE_OF_BLOCK_SIZE
    if offset.negative?
      raise NotFoundError, "APK Signing Block offset out of range: #{offset}"
    end

    file_io.seek(offset)
    header = file_io.read(Info::SIG_SIZE_OF_BLOCK_SIZE).unpack1('Q')

    if header != footer
      raise NotFoundError,
            "APK Signing Block header and footer mismatch: #{header} != #{footer}"
    end

    io = file_io.read(total_size)
    StringIO.new(io)
  }.call
end

#signers(block_id) ⇒ Object

Find singers

FORMAT: OFFSET DATA TYPE DESCRIPTION

  • @+0 bytes uint64: size in bytes

  • @+8 bytes payload block

    • @+0 bytes uint32: id

    • @+4 bytes payload: value

Raises:



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/app_info/android/signatures/info.rb', line 50

def signers(block_id)
  count = 0
  until @pairs.eof?
    left_bytes = left_bytes_check(
      @pairs, UINT64_SIZE, NotFoundError,
      "Insufficient data to read size of APK Signing Block ##{count}"
    )

    pair_buf = @pairs.read(UINT64_SIZE)
    pair_size = pair_buf.unpack1('Q')
    if pair_size < UINT32_SIZE || pair_size > UINT32_MAX_VALUE
      raise NotFoundError,
            "APK Signing Block ##{count} size out of range: #{pair_size} > #{UINT32_MAX_VALUE}"
    end

    if pair_size > left_bytes
      raise NotFoundError,
            "APK Signing Block ##{count} size out of range: #{pair_size} > #{left_bytes}"
    end

    # fetch next signer block position
    next_pos = @pairs.pos + pair_size.to_i

    id_block = @pairs.read(UINT32_SIZE)
    id_bytes = id_block.unpack('C*')
    if id_bytes == block_id
      logger.debug "Signature block id v#{@version} scheme (0x#{id_block.unpack1('H*')}) found"
      value = @pairs.read(pair_size - UINT32_SIZE)
      return StringIO.new(value)
    end

    @pairs.seek(next_pos)
    count += 1
  end

  block_id_hex = block_id.reverse.pack('C*').unpack1('H*')
  raise NotFoundError, "Not found block id 0x#{block_id_hex} in APK Signing Block."
end

#start_bufferObject



144
145
146
# File 'lib/app_info/android/signatures/info.rb', line 144

def start_buffer
  @start_buffer ||= zip_io.start_buf(file_io)
end

#zip64?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/app_info/android/signatures/info.rb', line 89

def zip64?
  zip_io.zip64_file?(start_buffer)
end

#zip_ioObject



148
149
150
# File 'lib/app_info/android/signatures/info.rb', line 148

def zip_io
  @zip_io ||= @parser.zip
end