efivalidate 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d7ad40ad286efe0a7da67d7ba34b4abe48ec7a5
4
- data.tar.gz: 0d1703778e05b919fb07b6cf1dd53a1386f071b4
3
+ metadata.gz: 037ca593ce0ba5a08920d051c191e5f9c4d3a71a
4
+ data.tar.gz: cd59412dc988879399f553b91a965cdca1a59a77
5
5
  SHA512:
6
- metadata.gz: b2f62cb2753b799902811831043bae5875be6628ea27db53a8ba1331287e85f2706c012a07af6b06738496264ece9b9c318d8c86ae0762852aad696180352359
7
- data.tar.gz: f257b58ad2c6ffb3bb3e709afeea22d73ec437e415ca37c5c67124e05fd6bf25336fd4fff99e9e0d7261c69e503c57958207f4d0c7ab8537727622a1c7a7c3e5
6
+ metadata.gz: 156c354cdf536fbc2a09b9a820cf29c023ebdcbd3042241f4286e0689d0b37441ef5b187fea248a6bd218b64d874e726923b2747412356263ce7543457976281
7
+ data.tar.gz: 8496a21db212539db45a5e6547e361c16a6b25a581855e899ec7c45ad7a73c3a13f543c93d3bba114745709d784cd8e5a218598f294cb288262712ca5513a9de
data/.gitignore CHANGED
@@ -11,3 +11,5 @@
11
11
 
12
12
  # rspec failure tracking
13
13
  .rspec_status
14
+
15
+ *.gem
data/FORMAT.md ADDED
@@ -0,0 +1,27 @@
1
+
2
+ NOTE: In progress
3
+
4
+ # Header
5
+
6
+ * 0-4: "magic" = "EALF"
7
+ * 5-8: number of hash rows
8
+ * 9-12: file size in bytes
9
+ * 13: ?? Hash function, 00 = sha1, 01 = sha256
10
+ * Zeros?
11
+ * 30: UInt16 Offset to hash table
12
+ * 32: UInt16 Number of Unicode-16 characters following
13
+ * 34: - NULL = UTF-16 null terminated EFI version string
14
+
15
+ # 60 byte "rows"
16
+ * 1 byte FD region (see `fdutil`)
17
+ * 0 = FD header / region
18
+ * 1 = BIOS / EFI
19
+ * 2 = Intel ME (Code + Data)
20
+ * 3 = Gigabit Ethernet (Unused)
21
+ * 4 = Platform Data & Bootloader
22
+ * 1 byte sub-region
23
+ * 2 byte index
24
+ * 4 byte offset
25
+ * 4 byte size
26
+ * 16 bytes GUID for section or "FF"
27
+ * 32 bytes SHA256 for section
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
- # Efivalidate
1
+ # `efivalidate`
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/efivalidate`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Important: See `FORMAT.md`
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ `efivalidate` is a ruby utility to take a given input EFI payload from macOS and to compare it against
6
+ Apple's validation schema. Being written in ruby this can occur off-box to ensure that the utility itself
7
+ hasn't been compromised
6
8
 
7
9
  ## Installation
8
10
 
@@ -22,7 +24,20 @@ Or install it yourself as:
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ $ efivalidate
28
+
29
+ Usage: efivalidate {input.bin} [PARAMS]
30
+
31
+ {input.bin} is a EFI payload saved using either `eficheck` or `BootRomFlash.efi`
32
+
33
+ --download-signatures
34
+
35
+ Attempts to download Apple's EFI signatures from their update service
36
+
37
+ --signatures-path
38
+
39
+ Points to a path on disk where known EFI signatures exist
40
+
26
41
 
27
42
  ## Development
28
43
 
data/efivalidate.gemspec CHANGED
@@ -5,12 +5,12 @@ require "efivalidate/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "efivalidate"
8
- spec.version = Efivalidate::VERSION
8
+ spec.version = EFIValidate::VERSION
9
9
  spec.authors = ["Rick Mark"]
10
- spec.email = ["rickmark@outlook.com"]
10
+ spec.email = ["rickmark@dropbox.com"]
11
11
 
12
12
  spec.summary = %q{Validate Apple EFI images offline}
13
- spec.description = %q{Validate Apple EFI images offline}
13
+ spec.description = %q{Implements an algorithm to compare Apple EFI against their EALF baselines.}
14
14
  spec.homepage = "https://github.com/rickmark/efivalidate"
15
15
  spec.license = "MIT"
16
16
 
@@ -30,6 +30,9 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
+ spec.add_runtime_dependency 'iostruct', '~> 0.0.4'
34
+ spec.add_runtime_dependency 'uuidtools', '~> 2.1'
35
+
33
36
  spec.add_development_dependency "bundler", "~> 1.15"
34
37
  spec.add_development_dependency "rake", "~> 10.0"
35
38
  spec.add_development_dependency "rspec", "~> 3.0"
data/exe/efivalidate CHANGED
@@ -1,3 +1,29 @@
1
1
  #!/usr/bin/env ruby
2
+ require 'bundler/setup'
2
3
 
3
4
  require "efivalidate"
5
+ require 'optionparser'
6
+
7
+ if ARGV.count != 2
8
+ puts "efivalidate EFI_FILE EALF_FILE"
9
+ exit -1
10
+ end
11
+
12
+ efi = ARGV[0]
13
+ ealf = ARGV[1]
14
+
15
+ parser = EFIValidate::EALFParser.read ealf
16
+ validator = EFIValidate::EFIValidator.new parser, File.open(efi)
17
+
18
+ validator.validate
19
+
20
+ if validator.is_valid?
21
+ puts "EFI file matches EALF baseline. (#{parser.rows.count} hashes match)"
22
+ else
23
+ puts "EFI file matched #{parser.rows.count - validator.errors.count} hashes from EALF.\n"
24
+ puts "EFI file has #{validator.errors.count} hash errors:\n\n"
25
+ validator.errors.each do |error|
26
+ puts "Actual Hash: #{error.hash}"
27
+ puts "Expected Hash from EALF:\n#{error.row.to_s}\n\n"
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ module EFIValidate
2
+ class EALFHeader < IOStruct.new 'a4LLC',
3
+ :ealf_magic,
4
+ :ealf_rows,
5
+ :ealf_size,
6
+ :ealf_hash_function,
7
+ :ealf_zeros,
8
+ :ealf_row_offset,
9
+ :ealf_signature_size
10
+
11
+ EALF_MAGIC = 'EALF'.freeze
12
+
13
+ EALF_ROW_SIZE = 28
14
+
15
+ EALF_HASH_FUNCTIONS = { 0 => { size: 20, function: -> { Digest::SHA1.new } }, 1 => { size: 32, funciton: -> { Digest::SHA2.new(256)} } }
16
+
17
+ EALF_HASH_SHA1 = 0
18
+ EALF_HASH_SHA256 = 1
19
+
20
+ def row_size
21
+ EALF_ROW_SIZE + self.hash_size
22
+ end
23
+
24
+ def hash_size
25
+ EFIValidate::EALFHeader::EALF_HASH_FUNCTIONS[self.ealf_hash_function][:size]
26
+ end
27
+
28
+ def create_hash
29
+ EFIValidate::EALFHeader::EALF_HASH_FUNCTIONS[self.ealf_hash_function][:funciton].call
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ module EFIValidate
2
+ class EALFParser
3
+ class EALFParseError < Exception; end
4
+
5
+ attr_reader :header, :rows
6
+
7
+ def initialize(data)
8
+ @header = EFIValidate::EALFHeader.read(data, 97)
9
+
10
+ raise EALFParseError, 'File header magic mismatch.' unless @header.ealf_magic == EFIValidate::EALFHeader::EALF_MAGIC
11
+ raise EALFParseError, 'Only SHA256 EALF is supported.' unless @header.ealf_hash_function == EFIValidate::EALFHeader::EALF_HASH_SHA256
12
+
13
+ @rows = []
14
+ @header.ealf_rows.times do
15
+ @rows << EFIValidate::EALFRow.read(data, @header.row_size).tap { |row| row.header = @header }
16
+ end
17
+ end
18
+
19
+ def self.read(file)
20
+ EFIValidate::EALFParser.new(File.open(file))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ require 'uuidtools'
2
+
3
+ module EFIValidate
4
+ # A class that represents a region of an EFI firmware with a valid hash for the region
5
+ #
6
+ # A EALF file is a collection of regions an
7
+ class EALFRow < IOStruct.new 'CCvLLa16a*',
8
+ :ealf_component,
9
+ :ealf_region,
10
+ :ealf_master,
11
+ :ealf_offset,
12
+ :ealf_length,
13
+ :ealf_uuid,
14
+ :ealf_hash
15
+
16
+ attr_accessor :header
17
+
18
+ def hash
19
+ self.ealf_hash.each_byte.map { |b| '%02x' % b }.join
20
+ end
21
+
22
+ def uuid
23
+ UUIDTools::UUID.parse_raw(self.ealf_uuid)
24
+ end
25
+
26
+ def to_s
27
+ "<#{'%02x' % ealf_component}:#{ '%02x' % self.ealf_region }:#{ "%04x" % self.ealf_master }:#{ "%08x" % self.ealf_offset }:#{ "%08x" % self.ealf_length}:#{self.uuid}:#{self.hash}>"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module EFIValidate
2
+ class EFIValidationError
3
+ attr_reader :row, :data, :hash
4
+
5
+ def initialize(row, data, hash)
6
+ @row = row
7
+ @data = data
8
+ @hash = hash
9
+ end
10
+
11
+ def to_s
12
+ @row.to_s
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ module EFIValidate
2
+ class EFIValidator
3
+ attr_reader :parser, :data, :errors
4
+
5
+ def initialize(parser, data)
6
+ @parser = parser
7
+ @data = data
8
+ end
9
+
10
+
11
+ def validate!
12
+ @errors = []
13
+
14
+ @parser.rows.each do |row|
15
+ @data.seek row.ealf_offset
16
+
17
+ section_data = @data.read row.ealf_length
18
+
19
+ calculated_hash = @parser.header.create_hash.hexdigest section_data
20
+
21
+ @errors << EFIValidationError.new(row, section_data, calculated_hash) unless calculated_hash == row.hash
22
+ end
23
+ end
24
+
25
+ def validate
26
+ self.validate! unless @errors
27
+ end
28
+
29
+ def is_valid?
30
+ self.validate
31
+
32
+ self.errors.count == 0
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ module EFIValidate
2
+ class IntelFirmwareDescriptor
3
+
4
+
5
+ def initalize(data)
6
+
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
- module Efivalidate
2
- VERSION = "0.1.0"
1
+ module EFIValidate
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/efivalidate.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  require "efivalidate/version"
2
+ require 'iostruct'
2
3
 
3
- module Efivalidate
4
+ module EFIValidate
4
5
  # Your code goes here...
6
+ autoload :EALFHeader, 'efivalidate/ealf_header'
7
+ autoload :EALFRow, 'efivalidate/ealf_row'
8
+ autoload :EALFParser, 'efivalidate/ealf_parser'
9
+ autoload :EFIValidator, 'efivalidate/efi_validator'
10
+ autoload :EFIValidationError, 'efivalidate/efi_validation_error'
5
11
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: efivalidate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rick Mark
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-25 00:00:00.000000000 Z
11
+ date: 2017-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: iostruct
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: uuidtools
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.1'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -52,9 +80,9 @@ dependencies:
52
80
  - - "~>"
53
81
  - !ruby/object:Gem::Version
54
82
  version: '3.0'
55
- description: Validate Apple EFI images offline
83
+ description: Implements an algorithm to compare Apple EFI against their EALF baselines.
56
84
  email:
57
- - rickmark@outlook.com
85
+ - rickmark@dropbox.com
58
86
  executables:
59
87
  - efivalidate
60
88
  extensions: []
@@ -64,6 +92,7 @@ files:
64
92
  - ".rspec"
65
93
  - ".travis.yml"
66
94
  - CODE_OF_CONDUCT.md
95
+ - FORMAT.md
67
96
  - Gemfile
68
97
  - LICENSE.txt
69
98
  - README.md
@@ -73,6 +102,12 @@ files:
73
102
  - efivalidate.gemspec
74
103
  - exe/efivalidate
75
104
  - lib/efivalidate.rb
105
+ - lib/efivalidate/ealf_header.rb
106
+ - lib/efivalidate/ealf_parser.rb
107
+ - lib/efivalidate/ealf_row.rb
108
+ - lib/efivalidate/efi_validation_error.rb
109
+ - lib/efivalidate/efi_validator.rb
110
+ - lib/efivalidate/intel_firmware_descriptor.rb
76
111
  - lib/efivalidate/version.rb
77
112
  homepage: https://github.com/rickmark/efivalidate
78
113
  licenses:
@@ -95,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
130
  version: '0'
96
131
  requirements: []
97
132
  rubyforge_project:
98
- rubygems_version: 2.6.13
133
+ rubygems_version: 2.6.14
99
134
  signing_key:
100
135
  specification_version: 4
101
136
  summary: Validate Apple EFI images offline