psptool 2.6__tar.gz → 2.7__tar.gz
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.
- {psptool-2.6 → psptool-2.7}/.github/workflows/python-tests.yml +2 -3
- {psptool-2.6 → psptool-2.7}/.gitmodules +1 -1
- {psptool-2.6 → psptool-2.7}/PKG-INFO +4 -2
- {psptool-2.6 → psptool-2.7}/README.md +3 -1
- {psptool-2.6 → psptool-2.7}/psptool/__main__.py +6 -2
- {psptool-2.6 → psptool-2.7}/psptool/blob.py +3 -0
- {psptool-2.6 → psptool-2.7}/psptool/cert_tree.py +7 -4
- {psptool-2.6 → psptool-2.7}/psptool/directory.py +26 -10
- {psptool-2.6 → psptool-2.7}/psptool/entry.py +94 -42
- {psptool-2.6 → psptool-2.7}/psptool/fet.py +18 -6
- {psptool-2.6 → psptool-2.7}/psptool/psptool.py +25 -5
- {psptool-2.6 → psptool-2.7}/psptool/utils.py +19 -0
- {psptool-2.6 → psptool-2.7}/psptool.egg-info/PKG-INFO +4 -2
- {psptool-2.6 → psptool-2.7}/psptool.egg-info/SOURCES.txt +2 -0
- psptool-2.7/psptool.egg-info/requires.txt +2 -0
- {psptool-2.6 → psptool-2.7}/setup.cfg +2 -2
- psptool-2.7/tests/create_metrics.sh +20 -0
- {psptool-2.6 → psptool-2.7}/tests/integration/test_rom_files.py +1 -2
- psptool-2.7/tests/metrics.txt +449 -0
- psptool-2.6/psptool.egg-info/requires.txt +0 -2
- {psptool-2.6 → psptool-2.7}/.gitignore +0 -0
- {psptool-2.6 → psptool-2.7}/LICENSE +0 -0
- {psptool-2.6 → psptool-2.7}/psptool/__init__.py +0 -0
- {psptool-2.6 → psptool-2.7}/psptool/crypto.py +0 -0
- {psptool-2.6 → psptool-2.7}/psptool/errors.py +0 -0
- {psptool-2.6 → psptool-2.7}/psptool/firmware.py +0 -0
- {psptool-2.6 → psptool-2.7}/psptool/rom.py +0 -0
- {psptool-2.6 → psptool-2.7}/psptool.egg-info/dependency_links.txt +0 -0
- {psptool-2.6 → psptool-2.7}/psptool.egg-info/entry_points.txt +0 -0
- {psptool-2.6 → psptool-2.7}/psptool.egg-info/top_level.txt +0 -0
- {psptool-2.6 → psptool-2.7}/setup.py +0 -0
- {psptool-2.6 → psptool-2.7}/tests/__init__.py +0 -0
- {psptool-2.6 → psptool-2.7}/tests/integration/__init__.py +0 -0
- {psptool-2.6 → psptool-2.7}/tests/integration/test_psptrace.py +0 -0
- {psptool-2.6 → psptool-2.7}/tests/unit/__init__.py +0 -0
- {psptool-2.6 → psptool-2.7}/tests/unit/test_cli.py +0 -0
|
@@ -14,8 +14,7 @@ jobs:
|
|
|
14
14
|
- uses: actions/checkout@v3
|
|
15
15
|
with:
|
|
16
16
|
submodules: recursive
|
|
17
|
-
ssh-key: ${{ secrets.
|
|
18
|
-
lfs: true
|
|
17
|
+
ssh-key: ${{ secrets.PSPTOOL_FIXTURES_NEW_PRIVATE_KEY }}
|
|
19
18
|
- name: Set up Python ${{ matrix.python-version }}
|
|
20
19
|
uses: actions/setup-python@v3
|
|
21
20
|
with:
|
|
@@ -34,4 +33,4 @@ jobs:
|
|
|
34
33
|
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
35
34
|
- name: Test with unittest
|
|
36
35
|
run: |
|
|
37
|
-
python -m unittest discover -s tests -v
|
|
36
|
+
python -m unittest discover -s tests -v
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: psptool
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7
|
|
4
4
|
Summary: PSPTool is a tool for dealing with AMD binary blobs
|
|
5
5
|
Home-page: https://github.com/PSPReverse/PSPTool
|
|
6
6
|
Author: Christian Werling
|
|
@@ -23,6 +23,8 @@ It is based on reverse-engineering efforts of AMD's **proprietary filesystem** u
|
|
|
23
23
|
PSPTool favourably works with UEFI images as obtained through BIOS updates. If these updates are only available through Windows executables, tools like [innoextract](https://github.com/dscharrer/innoextract) can help.
|
|
24
24
|
|
|
25
25
|
PSPTool was developed at TU Berlin in context of the following research:
|
|
26
|
+
- 2023, talk: [Jailbreaking an Electric Vehicle in 2023 or What It Means to Hotwire Tesla's x86-Based Seat Heater](https://www.blackhat.com/us-23/briefings/schedule/#jailbreaking-an-electric-vehicle-in--or-what-it-means-to-hotwire-teslas-x-based-seat-heater-33049)
|
|
27
|
+
- 2023, paper: [faulTPM: Exposing AMD fTPMs' Deepest Secrets](https://arxiv.org/abs/2304.14717)
|
|
26
28
|
- 2021, paper: [One Glitch to Rule Them All: Fault Injection Attacks Against AMD's Secure Encrypted Virtualization](https://arxiv.org/abs/2108.04575)
|
|
27
29
|
- 2020, talk: [All You Ever Wanted to Know about the AMD Platform Security Processor and were Afraid to Emulate](https://youtu.be/KR8bPLj4nKE)
|
|
28
30
|
- 2019, talk: [Uncover, Understand, Own - Regaining Control Over Your AMD CPU](https://media.ccc.de/v/36c3-10942-uncover_understand_own_-_regaining_control_over_your_amd_cpu)
|
|
@@ -66,7 +68,7 @@ $ psptool Lenovo_Thinkpad_T495_r12uj35wd.iso
|
|
|
66
68
|
|
|
67
69
|
<details>
|
|
68
70
|
<summary>Click to expand output</summary>
|
|
69
|
-
|
|
71
|
+
|
|
70
72
|
```
|
|
71
73
|
+-----+----------+-----------+----------+--------------------------------+
|
|
72
74
|
| ROM | Addr | Size | FET | AGESA |
|
|
@@ -7,6 +7,8 @@ It is based on reverse-engineering efforts of AMD's **proprietary filesystem** u
|
|
|
7
7
|
PSPTool favourably works with UEFI images as obtained through BIOS updates. If these updates are only available through Windows executables, tools like [innoextract](https://github.com/dscharrer/innoextract) can help.
|
|
8
8
|
|
|
9
9
|
PSPTool was developed at TU Berlin in context of the following research:
|
|
10
|
+
- 2023, talk: [Jailbreaking an Electric Vehicle in 2023 or What It Means to Hotwire Tesla's x86-Based Seat Heater](https://www.blackhat.com/us-23/briefings/schedule/#jailbreaking-an-electric-vehicle-in--or-what-it-means-to-hotwire-teslas-x-based-seat-heater-33049)
|
|
11
|
+
- 2023, paper: [faulTPM: Exposing AMD fTPMs' Deepest Secrets](https://arxiv.org/abs/2304.14717)
|
|
10
12
|
- 2021, paper: [One Glitch to Rule Them All: Fault Injection Attacks Against AMD's Secure Encrypted Virtualization](https://arxiv.org/abs/2108.04575)
|
|
11
13
|
- 2020, talk: [All You Ever Wanted to Know about the AMD Platform Security Processor and were Afraid to Emulate](https://youtu.be/KR8bPLj4nKE)
|
|
12
14
|
- 2019, talk: [Uncover, Understand, Own - Regaining Control Over Your AMD CPU](https://media.ccc.de/v/36c3-10942-uncover_understand_own_-_regaining_control_over_your_amd_cpu)
|
|
@@ -50,7 +52,7 @@ $ psptool Lenovo_Thinkpad_T495_r12uj35wd.iso
|
|
|
50
52
|
|
|
51
53
|
<details>
|
|
52
54
|
<summary>Click to expand output</summary>
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
```
|
|
55
57
|
+-----+----------+-----------+----------+--------------------------------+
|
|
56
58
|
| ROM | Addr | Size | FET | AGESA |
|
|
@@ -46,6 +46,7 @@ def main():
|
|
|
46
46
|
parser.add_argument('-n', '--no-duplicates', help=SUPPRESS, action='store_true')
|
|
47
47
|
parser.add_argument('-j', '--json', help=SUPPRESS, action='store_true')
|
|
48
48
|
parser.add_argument('-t', '--key-tree', help=SUPPRESS, action='store_true')
|
|
49
|
+
parser.add_argument('-m', '--metrics', help=SUPPRESS, action='store_true')
|
|
49
50
|
parser.add_argument('-p', '--privkeystub', help=SUPPRESS)
|
|
50
51
|
parser.add_argument('-a', '--privkeypass', help=SUPPRESS)
|
|
51
52
|
|
|
@@ -60,6 +61,7 @@ def main():
|
|
|
60
61
|
'-n: list unique entries only ordered by their offset',
|
|
61
62
|
'-j: output in JSON format instead of tables',
|
|
62
63
|
'-t: print tree of all signed entities and their certifying keys',
|
|
64
|
+
'-m: print entry parsing metrics for testing',
|
|
63
65
|
'', '']), action='store_true')
|
|
64
66
|
|
|
65
67
|
action.add_argument('-X', '--extract-entry', help='\n'.join([
|
|
@@ -114,7 +116,7 @@ def main():
|
|
|
114
116
|
elif args.decrypt:
|
|
115
117
|
if not entry.encrypted:
|
|
116
118
|
ph.print_error_and_exit(f'Entry is not encrypted {entry.get_readable_type()}')
|
|
117
|
-
output = entry.
|
|
119
|
+
output = entry.to_decrypted_entry_bytes()
|
|
118
120
|
elif args.pem_key:
|
|
119
121
|
output = entry.get_pem_encoded()
|
|
120
122
|
else:
|
|
@@ -187,7 +189,7 @@ def main():
|
|
|
187
189
|
if args.privkeystub:
|
|
188
190
|
privkeys = PrivateKeyDict.read_from_files(args.privkeystub, args.privkeypass)
|
|
189
191
|
|
|
190
|
-
if entry.signed_entity:
|
|
192
|
+
if hasattr(entry, 'signed_entity') and entry.signed_entity:
|
|
191
193
|
entry.signed_entity.resign_and_replace(privkeys=privkeys, recursive=True)
|
|
192
194
|
else:
|
|
193
195
|
ph.print_warning("Did not resign anything since target entry is not signed")
|
|
@@ -203,6 +205,8 @@ def main():
|
|
|
203
205
|
psp.ls_json(verbose=args.verbose)
|
|
204
206
|
elif args.key_tree:
|
|
205
207
|
psp.cert_tree.print_key_tree()
|
|
208
|
+
elif args.metrics:
|
|
209
|
+
psp.print_metrics()
|
|
206
210
|
elif args.no_duplicates:
|
|
207
211
|
psp.ls_entries(verbose=args.verbose)
|
|
208
212
|
else:
|
|
@@ -75,6 +75,9 @@ class Blob(NestedBuffer):
|
|
|
75
75
|
if not fet_parsed:
|
|
76
76
|
self.psptool.ph.print_warning(f"Skipping FET at {hex(fet_location)} due to unknown ROM alignment")
|
|
77
77
|
|
|
78
|
+
if len(self.roms) == 0:
|
|
79
|
+
self.psptool.ph.print_warning("Could not find any Firmware Entry Table!")
|
|
80
|
+
|
|
78
81
|
self._construct_range_dict()
|
|
79
82
|
|
|
80
83
|
def __repr__(self):
|
|
@@ -106,7 +106,8 @@ class SignedEntity:
|
|
|
106
106
|
print(f' Need to rehash')
|
|
107
107
|
self.entry.update_sha256()
|
|
108
108
|
print(f' Done')
|
|
109
|
-
assert self.signature.buffer_size == privkey.signature_size
|
|
109
|
+
assert self.signature.buffer_size == privkey.signature_size, \
|
|
110
|
+
f"{self.signature.buffer_size=}, {privkey.signature_size=}"
|
|
110
111
|
signature = privkey.sign_blob(self.entry.get_signed_bytes())
|
|
111
112
|
assert len(signature) == self.signature.buffer_size, f'Could not resign {self} with {privkey}: ' \
|
|
112
113
|
f'The new signature has the wrong length ' \
|
|
@@ -116,6 +117,8 @@ class SignedEntity:
|
|
|
116
117
|
def resign_and_replace(self, privkeys: PrivateKeyDict = None, recursive: bool = False):
|
|
117
118
|
# this resigns self (multiple times!)
|
|
118
119
|
for pk in self.certifying_keys:
|
|
120
|
+
if pk in self.contained_keys:
|
|
121
|
+
continue # TODO hotfix
|
|
119
122
|
pk.replace_and_resign(privkeys, recursive=recursive)
|
|
120
123
|
|
|
121
124
|
|
|
@@ -227,13 +230,13 @@ class PublicKeyEntity:
|
|
|
227
230
|
privkey = privkeys[self.key_type.name]
|
|
228
231
|
assert self.key_type.signature_size == privkey.signature_size
|
|
229
232
|
|
|
233
|
+
# replace self
|
|
234
|
+
self.replace_only(privkey.get_public_key())
|
|
235
|
+
|
|
230
236
|
# resign children
|
|
231
237
|
for se in self.certified_entities:
|
|
232
238
|
se.resign_only(privkey)
|
|
233
239
|
|
|
234
|
-
# replace self
|
|
235
|
-
self.replace_only(privkey.get_public_key())
|
|
236
|
-
|
|
237
240
|
# check crypto
|
|
238
241
|
for se in self.certified_entities:
|
|
239
242
|
assert se.is_verified_by(self), f'Resigning {se} with {self} failed!'
|
|
@@ -32,21 +32,23 @@ class Directory(NestedBuffer):
|
|
|
32
32
|
'rsv2'
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
+
_DEFAULT_HEADER_SIZE = 2 * 4,
|
|
35
36
|
_HEADER_SIZES = {
|
|
36
37
|
b'$PSP': 4 * 4,
|
|
37
38
|
b'$PL2': 4 * 4,
|
|
38
39
|
b'$BHD': 4 * 4,
|
|
39
|
-
b'$BL2': 4 * 4
|
|
40
|
+
b'$BL2': 4 * 4,
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
_DEFAULT_ENTRY_SIZE = 2 * 4,
|
|
42
44
|
_ENTRY_SIZES = {
|
|
43
45
|
b'$PSP': 4 * 4,
|
|
44
46
|
b'$PL2': 4 * 4,
|
|
45
47
|
b'$BHD': 4 * 6,
|
|
46
|
-
b'$BL2': 4 * 6
|
|
48
|
+
b'$BL2': 4 * 6,
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
_ENTRY_TYPES_SECONDARY_DIR = [0x40, 0x70]
|
|
51
|
+
_ENTRY_TYPES_SECONDARY_DIR = [0x40, 0x48, 0x49, 0x4a, 0x70]
|
|
50
52
|
_ENTRY_TYPES_PUBKEY = [0x0, 0x9, 0xa, 0x5, 0xd]
|
|
51
53
|
|
|
52
54
|
def __init__(self, parent_rom, rom_address: int, type_: str, psptool, zen_generation='unknown'):
|
|
@@ -69,16 +71,16 @@ class Directory(NestedBuffer):
|
|
|
69
71
|
self.type = type_
|
|
70
72
|
self.entries: List[Entry] = []
|
|
71
73
|
|
|
72
|
-
self._entry_size = self._ENTRY_SIZES
|
|
74
|
+
self._entry_size = self._ENTRY_SIZES.get(self.magic, self._DEFAULT_ENTRY_SIZE)
|
|
73
75
|
|
|
74
76
|
self._parse_entries()
|
|
75
77
|
|
|
76
78
|
# check entries for a link to a secondary directory (i.e. a continuation of this directory)
|
|
77
|
-
self.
|
|
79
|
+
self.secondary_directory_addresses = []
|
|
78
80
|
for entry in self.entries:
|
|
79
81
|
if entry.type in self._ENTRY_TYPES_SECONDARY_DIR:
|
|
80
|
-
# print_warning(f"Secondary dir at 0x{entry.buffer_offset:x}")
|
|
81
|
-
self.
|
|
82
|
+
# psptool.ph.print_warning(f"Secondary dir at 0x{entry.buffer_offset:x}")
|
|
83
|
+
self.secondary_directory_addresses.append(entry.buffer_offset)
|
|
82
84
|
|
|
83
85
|
self.verify_checksum()
|
|
84
86
|
|
|
@@ -97,6 +99,11 @@ class Directory(NestedBuffer):
|
|
|
97
99
|
self.header[8:12] = struct.pack('<I', self.count)
|
|
98
100
|
self.update_checksum()
|
|
99
101
|
|
|
102
|
+
@property
|
|
103
|
+
def address_mode(self):
|
|
104
|
+
rsvd = struct.unpack('=L', self.reserved)[0]
|
|
105
|
+
return (rsvd & 0x60000000) >> 29 if rsvd != 0 else None
|
|
106
|
+
|
|
100
107
|
def _parse_header(self):
|
|
101
108
|
# ugly to do this manually, but we do not know our size yet
|
|
102
109
|
self._count = int.from_bytes(
|
|
@@ -104,15 +111,16 @@ class Directory(NestedBuffer):
|
|
|
104
111
|
'little'
|
|
105
112
|
)
|
|
106
113
|
self.magic = self.rom.get_bytes(self.buffer_offset, 4)
|
|
114
|
+
self.reserved = self.rom.get_bytes(self.buffer_offset + 12, 4)
|
|
107
115
|
|
|
108
116
|
self.header = NestedBuffer(
|
|
109
117
|
self,
|
|
110
|
-
self._HEADER_SIZES
|
|
118
|
+
self._HEADER_SIZES.get(self.magic, self._DEFAULT_HEADER_SIZE)
|
|
111
119
|
)
|
|
112
120
|
self.body = NestedBuffer(
|
|
113
121
|
self,
|
|
114
|
-
self._ENTRY_SIZES
|
|
115
|
-
buffer_offset=self._HEADER_SIZES
|
|
122
|
+
self._ENTRY_SIZES.get(self.magic, self._DEFAULT_ENTRY_SIZE) * self._count,
|
|
123
|
+
buffer_offset=self._HEADER_SIZES.get(self.magic, self._DEFAULT_HEADER_SIZE)
|
|
116
124
|
)
|
|
117
125
|
|
|
118
126
|
self.buffer_size = len(self.header) + len(self.body)
|
|
@@ -126,15 +134,23 @@ class Directory(NestedBuffer):
|
|
|
126
134
|
for key, word in zip(self.ENTRY_FIELDS, chunker(entry_bytes, 4)):
|
|
127
135
|
entry_fields[key] = struct.unpack('<I', word)[0]
|
|
128
136
|
|
|
137
|
+
entry_fields['type_flags'] = entry_fields['type'] >> 8
|
|
138
|
+
entry_fields['type'] = entry_fields['type'] & 0xFFFF
|
|
139
|
+
|
|
129
140
|
entry_fields['offset'] &= self.rom.addr_mask
|
|
130
141
|
destination = None
|
|
131
142
|
|
|
132
143
|
if entry_fields['type'] in BIOS_ENTRY_TYPES:
|
|
133
144
|
destination = struct.unpack('<Q', entry_bytes[0x10:0x18])[0]
|
|
134
145
|
|
|
146
|
+
# Seen on the Lenovo X13 for the first time
|
|
147
|
+
if self.address_mode == 2:
|
|
148
|
+
entry_fields['offset'] += self.buffer_offset
|
|
149
|
+
|
|
135
150
|
try:
|
|
136
151
|
entry = Entry.from_fields(self, self.parent_buffer,
|
|
137
152
|
entry_fields['type'],
|
|
153
|
+
entry_fields['type_flags'],
|
|
138
154
|
entry_fields['size'],
|
|
139
155
|
entry_fields['offset'],
|
|
140
156
|
self.rom,
|
|
@@ -61,11 +61,16 @@ class Entry(NestedBuffer):
|
|
|
61
61
|
0x12: 'SMU_OFF_CHIP_FW_2',
|
|
62
62
|
0x13: 'DEBUG_UNLOCK',
|
|
63
63
|
0x1A: 'PSP_S3_NV_DATA',
|
|
64
|
+
0x20: 'HARDWARE_IP_CONFIG',
|
|
64
65
|
0x21: 'WRAPPED_IKEK',
|
|
65
66
|
0x22: 'TOKEN_UNLOCK',
|
|
66
67
|
0x24: 'SEC_GASKET',
|
|
67
68
|
0x25: 'MP2_FW',
|
|
69
|
+
0x26: 'MP2_FW_2',
|
|
70
|
+
0x27: 'USER_MODE_UNIT_TEST',
|
|
68
71
|
0x28: 'DRIVER_ENTRIES',
|
|
72
|
+
0x29: 'KVM_IMAGE',
|
|
73
|
+
0x2A: 'MP5_FW',
|
|
69
74
|
0x2D: 'S0I3_DRIVER',
|
|
70
75
|
0x30: 'ABL0',
|
|
71
76
|
0x31: 'ABL1',
|
|
@@ -75,35 +80,52 @@ class Entry(NestedBuffer):
|
|
|
75
80
|
0x35: 'ABL5',
|
|
76
81
|
0x36: 'ABL6',
|
|
77
82
|
0x37: 'ABL7',
|
|
83
|
+
0x38: 'SEV_DATA',
|
|
84
|
+
0x39: 'SEV_CODE',
|
|
78
85
|
0x3A: 'FW_PSP_WHITELIST',
|
|
86
|
+
0x3C: 'VBIOS_PRELOAD',
|
|
79
87
|
# 0x40: 'FW_L2_PTR',
|
|
80
88
|
0x41: 'FW_IMC',
|
|
81
89
|
0x42: 'FW_GEC',
|
|
82
90
|
# 0x43: 'FW_XHCI',
|
|
83
91
|
0x44: 'FW_INVALID',
|
|
92
|
+
0x45: 'TOS_SECURITY_POLICY',
|
|
93
|
+
0x47: 'DRTM_TA',
|
|
94
|
+
0x51: 'TOS_PUBLIC_KEY',
|
|
95
|
+
0x54: 'PSP_NVRAM',
|
|
96
|
+
0x55: 'BL_ROLLBACK_SPL',
|
|
97
|
+
0x5a: 'MSMU_BINARY_0',
|
|
98
|
+
0x5c: 'WMOS',
|
|
99
|
+
0x71: 'DMCUB_INS',
|
|
84
100
|
0x46: 'ANOTHER_FET',
|
|
85
101
|
0x50: 'KEY_DATABASE',
|
|
86
102
|
0x5f: 'FW_PSP_SMUSCS',
|
|
87
|
-
0x60: '
|
|
88
|
-
0x61: '
|
|
103
|
+
0x60: 'APCB',
|
|
104
|
+
0x61: 'APOB',
|
|
89
105
|
0x62: 'FW_XHCI',
|
|
90
|
-
0x63: '
|
|
106
|
+
0x63: 'APOB_NV_COPY',
|
|
107
|
+
0x64: 'PMU_CODE',
|
|
108
|
+
0x65: 'PMU_DATA',
|
|
109
|
+
0x66: 'MICROCODE_PATCH',
|
|
110
|
+
0x67: 'CORE_MCE_DATA',
|
|
111
|
+
0x68: 'APCB_COPY',
|
|
112
|
+
0x69: 'EARLY_VGA_IMAGE',
|
|
113
|
+
0x6A: 'MP2_FW_CFG',
|
|
114
|
+
0x73: 'PSP_FW_BOOT_LOADER',
|
|
115
|
+
0x80: 'OEM_System_Trusted_Application',
|
|
116
|
+
0x81: 'OEM_System_TA_Signing_key',
|
|
91
117
|
0x108: 'PSP_SMU_FN_FIRMWARE',
|
|
92
118
|
0x118: 'PSP_SMU_FN_FIRMWARE2',
|
|
93
119
|
|
|
94
120
|
# Entry types named by us
|
|
95
121
|
# Custom names are denoted by a leading '!'
|
|
96
122
|
0x14: '!PSP_MCLF_TRUSTLETS', # very similiar to ~PspTrustlets.bin~ in coreboot blobs
|
|
97
|
-
0x38: '!PSP_ENCRYPTED_NV_DATA',
|
|
98
123
|
0x40: '!PL2_SECONDARY_DIRECTORY',
|
|
99
124
|
0x43: '!KEY_UNKNOWN_1',
|
|
100
125
|
0x4e: '!KEY_UNKNOWN_2',
|
|
101
126
|
0x70: '!BL2_SECONDARY_DIRECTORY',
|
|
102
127
|
0x15f: '!FW_PSP_SMUSCS_2', # seems to be a secondary FW_PSP_SMUSCS (see above)
|
|
103
128
|
0x112: '!SMU_OFF_CHIP_FW_3', # seems to tbe a tertiary SMU image (see above)
|
|
104
|
-
0x39: '!SEV_APP',
|
|
105
|
-
0x10062: '!UEFI-IMAGE',
|
|
106
|
-
0x30062: '!UEFI-IMAGE',
|
|
107
129
|
0xdead: '!KEY_NOT_IN_DIR'
|
|
108
130
|
|
|
109
131
|
}
|
|
@@ -122,19 +144,15 @@ class Entry(NestedBuffer):
|
|
|
122
144
|
pass
|
|
123
145
|
|
|
124
146
|
@classmethod
|
|
125
|
-
def from_fields(cls, parent_directory, parent_buffer, type_, size, offset, blob, psptool, destination: int = None):
|
|
147
|
+
def from_fields(cls, parent_directory, parent_buffer, type_, type_flags, size, offset, blob, psptool, destination: int = None):
|
|
126
148
|
# Try to parse these ID's as a key entry
|
|
127
149
|
# todo: consolidate these constants with Directory._ENTRY_TYPES_PUBKEY
|
|
128
150
|
PUBKEY_ENTRY_TYPES = [0x0, 0x9, 0xa, 0x5, 0xd, 0x43, 0x4e, 0xdead]
|
|
129
151
|
|
|
130
152
|
# Types known to have no PSP HDR
|
|
131
153
|
# TODO: Find a better way to identify those entries
|
|
132
|
-
NO_HDR_ENTRY_TYPES = [0x4, 0xb, 0x21, 0x40, 0x70,
|
|
133
|
-
|
|
134
|
-
0x67, 0x66, 0x100066, 0x200066, 0x300066, 0x10062,
|
|
135
|
-
0x400066, 0x500066, 0x800068, 0x61, 0x200060, 0x300060,
|
|
136
|
-
0x300068, 0x400068, 0x500068, 0x400060, 0x500060, 0x200068,
|
|
137
|
-
0x7, 0x38, 0x46, 0x54, 0x600060, 0x700060, 0x600068, 0x700068]
|
|
154
|
+
NO_HDR_ENTRY_TYPES = [0x4, 0xb, 0x21, 0x40, 0x70, 0x6, 0x61, 0x60, 0x68, 0x5f, 0x15f, 0x1a, 0x22, 0x63, 0x67,
|
|
155
|
+
0x66, 0x62, 0x61, 0x7, 0x38, 0x46, 0x54]
|
|
138
156
|
|
|
139
157
|
NO_SIZE_ENTRY_TYPES = [0xb]
|
|
140
158
|
|
|
@@ -152,6 +170,7 @@ class Entry(NestedBuffer):
|
|
|
152
170
|
parent_directory,
|
|
153
171
|
parent_buffer,
|
|
154
172
|
type_,
|
|
173
|
+
type_flags,
|
|
155
174
|
size,
|
|
156
175
|
offset,
|
|
157
176
|
blob,
|
|
@@ -164,12 +183,13 @@ class Entry(NestedBuffer):
|
|
|
164
183
|
elif type_ in PUBKEY_ENTRY_TYPES:
|
|
165
184
|
# Option 2: it's a PubkeyEntry
|
|
166
185
|
try:
|
|
167
|
-
new_entry = PubkeyEntry(parent_directory, parent_buffer, type_, size, offset, blob, psptool)
|
|
186
|
+
new_entry = PubkeyEntry(parent_directory, parent_buffer, type_, type_flags, size, offset, blob, psptool)
|
|
168
187
|
except Exception as e:
|
|
169
188
|
new_entry = Entry(
|
|
170
189
|
parent_directory,
|
|
171
190
|
parent_buffer,
|
|
172
191
|
type_,
|
|
192
|
+
type_flags,
|
|
173
193
|
size,
|
|
174
194
|
offset,
|
|
175
195
|
blob,
|
|
@@ -181,12 +201,13 @@ class Entry(NestedBuffer):
|
|
|
181
201
|
elif type_ in Entry.KEY_STORE_TYPES:
|
|
182
202
|
# Option 2: it's a KeyStoreEntry
|
|
183
203
|
try:
|
|
184
|
-
new_entry = KeyStoreEntry(parent_directory, parent_buffer, type_, size, offset, blob, psptool)
|
|
204
|
+
new_entry = KeyStoreEntry(parent_directory, parent_buffer, type_, type_flags, size, offset, blob, psptool)
|
|
185
205
|
except:
|
|
186
206
|
new_entry = Entry(
|
|
187
207
|
parent_directory,
|
|
188
208
|
parent_buffer,
|
|
189
209
|
type_,
|
|
210
|
+
type_flags,
|
|
190
211
|
size,
|
|
191
212
|
offset,
|
|
192
213
|
blob,
|
|
@@ -200,7 +221,7 @@ class Entry(NestedBuffer):
|
|
|
200
221
|
# If the size in the directory is zero, set the size to hdr len
|
|
201
222
|
size = HeaderEntry.HEADER_LEN
|
|
202
223
|
try:
|
|
203
|
-
new_entry = HeaderEntry(parent_directory, parent_buffer, type_, size, offset, blob, psptool)
|
|
224
|
+
new_entry = HeaderEntry(parent_directory, parent_buffer, type_, type_flags, size, offset, blob, psptool)
|
|
204
225
|
if size == 0:
|
|
205
226
|
psptool.ph.print_warning(f"Entry with zero size. Type: {type_}. Dir: 0x{offset:x}")
|
|
206
227
|
except:
|
|
@@ -208,6 +229,7 @@ class Entry(NestedBuffer):
|
|
|
208
229
|
parent_directory,
|
|
209
230
|
parent_buffer,
|
|
210
231
|
type_,
|
|
232
|
+
type_flags,
|
|
211
233
|
size,
|
|
212
234
|
offset,
|
|
213
235
|
blob,
|
|
@@ -269,7 +291,7 @@ class Entry(NestedBuffer):
|
|
|
269
291
|
# Set zlib_size
|
|
270
292
|
blob[0x54:0x58] = zlib_size.to_bytes(4, 'little')
|
|
271
293
|
|
|
272
|
-
entry = HeaderEntry(None, blob, id_, total_size, 0x0, blob, psptool)
|
|
294
|
+
entry = HeaderEntry(None, blob, id_, None, total_size, 0x0, blob, psptool)
|
|
273
295
|
|
|
274
296
|
if signed:
|
|
275
297
|
entry.signature[:] = private_key.sign(entry.get_signed_bytes())
|
|
@@ -278,7 +300,7 @@ class Entry(NestedBuffer):
|
|
|
278
300
|
else:
|
|
279
301
|
raise Entry.TypeError()
|
|
280
302
|
|
|
281
|
-
def __init__(self, parent_directory, parent_buffer, type_, buffer_size, buffer_offset: int, blob, psptool,
|
|
303
|
+
def __init__(self, parent_directory, parent_buffer, type_, type_flags, buffer_size, buffer_offset: int, blob, psptool,
|
|
282
304
|
destination: int = None):
|
|
283
305
|
super().__init__(parent_buffer, buffer_size, buffer_offset=buffer_offset)
|
|
284
306
|
|
|
@@ -286,6 +308,7 @@ class Entry(NestedBuffer):
|
|
|
286
308
|
self.blob = blob
|
|
287
309
|
self.psptool = psptool
|
|
288
310
|
self.type = type_
|
|
311
|
+
self.type_flags = type_flags
|
|
289
312
|
self.destination = destination
|
|
290
313
|
# todo: deduplicate Entry objects pointing to the same address (in `from_fields`?)
|
|
291
314
|
self.references = [parent_directory] if parent_directory is not None else []
|
|
@@ -459,12 +482,13 @@ class KeyStoreEntryHeader(NestedBuffer):
|
|
|
459
482
|
if self.has_sha256_checksum:
|
|
460
483
|
self.sha256_checksum = NestedBuffer(self, 0x20, buffer_offset=0xd0)
|
|
461
484
|
|
|
485
|
+
# Based on KeyStore entries seen in: Lenovo X13 and Lenovo Ideapad 5 Pro 16ACH6
|
|
462
486
|
zero_ranges = {
|
|
463
|
-
(0x00, 0x10),
|
|
464
487
|
(0x18, 0x18),
|
|
465
488
|
(0x48, 0x04),
|
|
466
489
|
(0x50, 0x08),
|
|
467
|
-
(0x5c,
|
|
490
|
+
(0x5c, 0x04),
|
|
491
|
+
(0x64, 0x08),
|
|
468
492
|
(0x70, 0x0c),
|
|
469
493
|
(0x80, 0x50),
|
|
470
494
|
(0xf0, 0x10),
|
|
@@ -514,7 +538,7 @@ class KeyStoreEntryHeader(NestedBuffer):
|
|
|
514
538
|
@property
|
|
515
539
|
def has_sha256_checksum(self) -> bool:
|
|
516
540
|
# assert self.sha256_checksum_flag_1 == self.sha256_checksum_flag_2
|
|
517
|
-
assert self.sha256_checksum_flag_1 in {0, 1}
|
|
541
|
+
assert self.sha256_checksum_flag_1 in {0, 1, 2}, f"unknown {self.sha256_checksum_flag_1=}"
|
|
518
542
|
return self.sha256_checksum_flag_1 == 1
|
|
519
543
|
|
|
520
544
|
|
|
@@ -649,7 +673,7 @@ class PubkeyEntry(Entry):
|
|
|
649
673
|
|
|
650
674
|
# misc info
|
|
651
675
|
self._version = NestedBuffer(self, 4)
|
|
652
|
-
if self.version
|
|
676
|
+
if self.version not in {1, 2}:
|
|
653
677
|
raise UnknownPubkeyEntryVersion
|
|
654
678
|
self._key_usage = NestedBuffer(self, 4, 0x24)
|
|
655
679
|
|
|
@@ -657,6 +681,9 @@ class PubkeyEntry(Entry):
|
|
|
657
681
|
self.key_id = KeyId(self, 0x10, 0x4)
|
|
658
682
|
self.certifying_id = KeyId(self, 0x10, 0x14)
|
|
659
683
|
|
|
684
|
+
# security features
|
|
685
|
+
self._security_features = NestedBuffer(self, 2, 0x2A)
|
|
686
|
+
|
|
660
687
|
# crypto material
|
|
661
688
|
self._pubexp_bits = NestedBuffer(self, 4, 0x38)
|
|
662
689
|
self._modulus_bits = NestedBuffer(self, 4, 0x3c)
|
|
@@ -716,6 +743,10 @@ class PubkeyEntry(Entry):
|
|
|
716
743
|
def modulus(self) -> int:
|
|
717
744
|
return int.from_bytes(self._modulus.get_bytes(), 'little')
|
|
718
745
|
|
|
746
|
+
@property
|
|
747
|
+
def security_features(self) -> int:
|
|
748
|
+
return int.from_bytes(self._security_features.get_bytes(), 'little')
|
|
749
|
+
|
|
719
750
|
def get_signed_bytes(self):
|
|
720
751
|
return self.get_bytes(0, self.buffer_size - self.signature_size)
|
|
721
752
|
|
|
@@ -729,6 +760,26 @@ class PubkeyEntry(Entry):
|
|
|
729
760
|
def get_readable_version(self):
|
|
730
761
|
return str(self.version)
|
|
731
762
|
|
|
763
|
+
def get_readable_key_usage(self):
|
|
764
|
+
if self.key_usage == 0:
|
|
765
|
+
return 'AMD_CODE_SIGN'
|
|
766
|
+
if self.key_usage == 1:
|
|
767
|
+
return 'BIOS_CODE_SIGN'
|
|
768
|
+
if self.key_usage == 2:
|
|
769
|
+
return 'AMD_AND_BIOS_CODE_SIGN'
|
|
770
|
+
if self.key_usage == 8:
|
|
771
|
+
return 'PLATFORM_SECURE_BOOT'
|
|
772
|
+
return f'unknown_key_usage({self.key_usage})'
|
|
773
|
+
|
|
774
|
+
def get_readable_security_features(self):
|
|
775
|
+
features = []
|
|
776
|
+
if self.security_features & 0b001:
|
|
777
|
+
features.append('DISABLE_BIOS_KEY_ANTI_ROLLBACK')
|
|
778
|
+
if self.security_features & 0b010:
|
|
779
|
+
features.append('DISABLE_AMD_BIOS_KEY_USE')
|
|
780
|
+
if self.security_features & 0b100:
|
|
781
|
+
features.append('DISABLE_SECURE_DEBUG_UNLOCK')
|
|
782
|
+
return ', '.join(features)
|
|
732
783
|
|
|
733
784
|
class HeaderEntry(Entry):
|
|
734
785
|
|
|
@@ -890,14 +941,14 @@ class HeaderEntry(Entry):
|
|
|
890
941
|
return self._sha256_checksum_flag_2 == 1
|
|
891
942
|
|
|
892
943
|
def verify_sha256(self, print_warning=True) -> bool:
|
|
893
|
-
if self._sha256_checksum.get_bytes() == sha256(self.
|
|
944
|
+
if self._sha256_checksum.get_bytes() == sha256(self.get_decrypted_decompressed_body()).digest():
|
|
894
945
|
return True
|
|
895
946
|
if print_warning:
|
|
896
947
|
self.psptool.ph.print_warning(f"Could not verify sha256 checksum for {self}")
|
|
897
948
|
return False
|
|
898
949
|
|
|
899
950
|
def update_sha256(self):
|
|
900
|
-
self._sha256_checksum[:] = sha256(self.
|
|
951
|
+
self._sha256_checksum[:] = sha256(self.get_decrypted_decompressed_body()).digest()
|
|
901
952
|
self.verify_sha256()
|
|
902
953
|
|
|
903
954
|
def get_readable_version(self):
|
|
@@ -929,30 +980,31 @@ class HeaderEntry(Entry):
|
|
|
929
980
|
return readable_magic
|
|
930
981
|
|
|
931
982
|
def get_readable_signed_by(self):
|
|
932
|
-
return
|
|
983
|
+
return self.signed_entity.certifying_id.magic
|
|
933
984
|
|
|
934
985
|
def get_signed_bytes(self) -> bytes:
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
# Truncate to actually signed portion
|
|
938
|
-
return full_decompressed[:self.header_len + self.size_signed]
|
|
939
|
-
elif self.encrypted:
|
|
940
|
-
return self.get_decrypted()[:self.size_signed + self.header_len]
|
|
941
|
-
else:
|
|
942
|
-
return self.get_bytes()[:self.size_signed + self.header_len]
|
|
986
|
+
entry_bytes = self.header.get_bytes() + self.get_decrypted_decompressed_body()
|
|
987
|
+
return entry_bytes[:self.header_len + self.size_signed]
|
|
943
988
|
|
|
944
|
-
def
|
|
945
|
-
if
|
|
946
|
-
|
|
989
|
+
def get_decrypted_decompressed_body(self) -> bytes:
|
|
990
|
+
if self.encrypted:
|
|
991
|
+
output = self.get_decrypted_body()
|
|
947
992
|
else:
|
|
993
|
+
output = self.body.get_bytes()
|
|
994
|
+
if self.compressed:
|
|
948
995
|
try:
|
|
949
|
-
return zlib_decompress(
|
|
996
|
+
return zlib_decompress(output[:self.zlib_size])
|
|
950
997
|
except:
|
|
951
998
|
self.psptool.ph.print_warning(f"ZLIB decompression failed on entry {self.get_readable_type()}")
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
def
|
|
955
|
-
|
|
999
|
+
return output
|
|
1000
|
+
|
|
1001
|
+
def to_decrypted_entry_bytes(self) -> bytes:
|
|
1002
|
+
"""Returns the bytes of the same entry, just with the encryption removed"""
|
|
1003
|
+
header = bytearray(self.header.get_bytes())
|
|
1004
|
+
header[0x18:0x1c] = bytes(4)
|
|
1005
|
+
header[0x20:0x30] = bytes(0x10)
|
|
1006
|
+
signature = self.signature.get_bytes() if self.signed else b''
|
|
1007
|
+
return bytes(header) + self.get_decrypted_body() + signature
|
|
956
1008
|
|
|
957
1009
|
def get_decrypted_body(self) -> bytes:
|
|
958
1010
|
if not self.encrypted:
|
|
@@ -76,10 +76,23 @@ class Fet(NestedBuffer):
|
|
|
76
76
|
return
|
|
77
77
|
dir_ = Directory(self.rom, addr, type_, self.psptool, zen_generation)
|
|
78
78
|
self.directories.append(dir_)
|
|
79
|
-
|
|
80
|
-
self.
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
for secondary_directory_address in dir_.secondary_directory_addresses:
|
|
80
|
+
secondary_directory_magic = self.rom.get_bytes(secondary_directory_address, 4)
|
|
81
|
+
if secondary_directory_magic in [b'*\rY/', b'j\x8d+1', b'&\r=/', b'd\x8d\x011']:
|
|
82
|
+
weird_new_directory_body = self.rom.get_bytes(secondary_directory_address+16, 8)
|
|
83
|
+
try:
|
|
84
|
+
secondary_dir = Directory(self.rom, int.from_bytes(weird_new_directory_body[:4], 'little'), 'tertiary', self.psptool)
|
|
85
|
+
except:
|
|
86
|
+
self.psptool.ph.print_warning(f"Couldn't create secondary directory at 0x{secondary_directory_address:x}")
|
|
87
|
+
self.directories.append(secondary_dir)
|
|
88
|
+
else:
|
|
89
|
+
secondary_dir = Directory(self.rom, secondary_directory_address, 'secondary', self.psptool, zen_generation)
|
|
90
|
+
self.directories.append(secondary_dir)
|
|
91
|
+
|
|
92
|
+
# Tertiary directories are a thing, apparently
|
|
93
|
+
for tertiary_directory_address in secondary_dir.secondary_directory_addresses:
|
|
94
|
+
tertiary_dir = Directory(self.rom, tertiary_directory_address, 'secondary', self.psptool, zen_generation)
|
|
95
|
+
self.directories.append(tertiary_dir)
|
|
83
96
|
|
|
84
97
|
def _parse_entry_table(self):
|
|
85
98
|
entries = self.get_chunks(4, 4)
|
|
@@ -109,8 +122,7 @@ class Fet(NestedBuffer):
|
|
|
109
122
|
|
|
110
123
|
def _parse_combo_dir(self, dir_addr):
|
|
111
124
|
results = []
|
|
112
|
-
no_of_entries = int.from_bytes(self.rom[dir_addr + 8: dir_addr + 0xc],
|
|
113
|
-
'little')
|
|
125
|
+
no_of_entries = int.from_bytes(self.rom[dir_addr + 8: dir_addr + 0xc], 'little')
|
|
114
126
|
combo_dir = self.rom[dir_addr: dir_addr + 16 * (no_of_entries + 2)]
|
|
115
127
|
|
|
116
128
|
# Combo dir entries seem to begin at offset 0x20, make sure we don't
|