efivalidate 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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