psptool 2.8__tar.gz → 3.0__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.8 → psptool-3.0}/PKG-INFO +41 -37
- {psptool-2.8 → psptool-3.0}/README.md +36 -35
- {psptool-2.8 → psptool-3.0}/psptool/__main__.py +61 -60
- {psptool-2.8 → psptool-3.0}/psptool/blob.py +60 -66
- {psptool-2.8 → psptool-3.0}/psptool/cert_tree.py +51 -49
- {psptool-2.8 → psptool-3.0}/psptool/crypto.py +2 -3
- psptool-3.0/psptool/directory.py +205 -0
- psptool-3.0/psptool/entry.py +96 -0
- {psptool-2.8 → psptool-3.0}/psptool/errors.py +1 -2
- {psptool-2.8 → psptool-3.0}/psptool/fet.py +13 -43
- psptool-3.0/psptool/file.py +295 -0
- psptool-3.0/psptool/header_file.py +214 -0
- psptool-3.0/psptool/key_store_file.py +253 -0
- {psptool-2.8 → psptool-3.0}/psptool/psptool.py +81 -77
- psptool-3.0/psptool/pubkey_file.py +178 -0
- {psptool-2.8 → psptool-3.0}/psptool/rom.py +22 -21
- {psptool-2.8 → psptool-3.0}/psptool.egg-info/PKG-INFO +41 -37
- {psptool-2.8 → psptool-3.0}/psptool.egg-info/SOURCES.txt +4 -0
- {psptool-2.8 → psptool-3.0}/setup.cfg +1 -1
- {psptool-2.8 → psptool-3.0}/tests/integration/test_rom_files.py +4 -4
- {psptool-2.8 → psptool-3.0}/tests/metrics.txt +187 -173
- psptool-2.8/psptool/directory.py +0 -200
- psptool-2.8/psptool/entry.py +0 -1040
- {psptool-2.8 → psptool-3.0}/.github/workflows/python-tests.yml +0 -0
- {psptool-2.8 → psptool-3.0}/.gitignore +0 -0
- {psptool-2.8 → psptool-3.0}/.gitmodules +0 -0
- {psptool-2.8 → psptool-3.0}/LICENSE +0 -0
- {psptool-2.8 → psptool-3.0}/psptool/__init__.py +0 -0
- {psptool-2.8 → psptool-3.0}/psptool/firmware.py +0 -0
- {psptool-2.8 → psptool-3.0}/psptool/utils.py +0 -0
- {psptool-2.8 → psptool-3.0}/psptool.egg-info/dependency_links.txt +0 -0
- {psptool-2.8 → psptool-3.0}/psptool.egg-info/entry_points.txt +0 -0
- {psptool-2.8 → psptool-3.0}/psptool.egg-info/requires.txt +0 -0
- {psptool-2.8 → psptool-3.0}/psptool.egg-info/top_level.txt +0 -0
- {psptool-2.8 → psptool-3.0}/setup.py +0 -0
- {psptool-2.8 → psptool-3.0}/tests/__init__.py +0 -0
- {psptool-2.8 → psptool-3.0}/tests/create_metrics.sh +0 -0
- {psptool-2.8 → psptool-3.0}/tests/integration/__init__.py +0 -0
- {psptool-2.8 → psptool-3.0}/tests/integration/test_psptrace.py +0 -0
- {psptool-2.8 → psptool-3.0}/tests/unit/__init__.py +0 -0
- {psptool-2.8 → psptool-3.0}/tests/unit/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: psptool
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0
|
|
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
|
|
@@ -16,6 +16,9 @@ License-File: LICENSE
|
|
|
16
16
|
Requires-Dist: setuptools
|
|
17
17
|
Requires-Dist: cryptography>=38.0.0
|
|
18
18
|
Requires-Dist: prettytable
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: license-file
|
|
19
22
|
|
|
20
23
|
# PSPTool
|
|
21
24
|
|
|
@@ -798,40 +801,41 @@ usage: psptool [-V | -E | -X | -R] [file]
|
|
|
798
801
|
Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.
|
|
799
802
|
|
|
800
803
|
positional arguments:
|
|
801
|
-
file
|
|
804
|
+
file Binary file to be parsed for PSP firmware
|
|
802
805
|
|
|
803
806
|
optional arguments:
|
|
804
807
|
-V, --version
|
|
805
|
-
-E, --entries
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
808
|
+
-E, --entries Default: Parse and display PSP firmware entries.
|
|
809
|
+
[-n] [-j] [-t]
|
|
810
|
+
|
|
811
|
+
-n: list unique entries only ordered by their offset
|
|
812
|
+
-j: output in JSON format instead of tables
|
|
813
|
+
-t: print tree of all signed entities and their certifying keys
|
|
814
|
+
-m: print parsing metrics for testing
|
|
815
|
+
|
|
816
|
+
-X, --extract-file Extract one or more PSP firmware files.
|
|
817
|
+
[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
|
|
818
|
+
|
|
819
|
+
-r idx: specifies rom_index (default: 0)
|
|
820
|
+
-d idx: specifies directory_index (default: all directories)
|
|
821
|
+
-e idx: specifies file_index (default: all files)
|
|
822
|
+
-n: skip duplicate files and extract unique files only
|
|
823
|
+
-u: uncompress compressed files
|
|
824
|
+
-c: try to decrypt files
|
|
825
|
+
-k: convert pubkeys into PEM format
|
|
826
|
+
-o file: specifies outfile/outdir (default: stdout/{file}_extracted)
|
|
827
|
+
|
|
828
|
+
-R, --replace-file Copy a new file (including header and signature) into the
|
|
829
|
+
ROM file and update file and other metadata accordingly.
|
|
830
|
+
-d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
|
|
831
|
+
|
|
832
|
+
-r idx: specifies rom_index (default: 0)
|
|
833
|
+
-d idx: specifies directory_index
|
|
834
|
+
-e idx: specifies file_index
|
|
835
|
+
-s file: specifies subfile (i.e. the new file contents)
|
|
836
|
+
-o file: specifies outfile
|
|
837
|
+
-p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
|
|
838
|
+
-a pass: specifies password for the re-signing keys
|
|
835
839
|
```
|
|
836
840
|
|
|
837
841
|
## Python Usage
|
|
@@ -867,13 +871,13 @@ PSPTool can be **used as a Python module**, e.g. in an interactive IPython sessi
|
|
|
867
871
|
| | 14 | 0xeba00 | 0xbd60 | 0x36 | | AR6B | 17.9.18.12 | OEM_PSP_FW_PUBLIC_KEY |
|
|
868
872
|
| | 15 | 0x149000 | 0x400 | 0x40 | !PL2_SECONDARY_DIRECTORY | | | |
|
|
869
873
|
+---+-------+----------+---------+------+-----------------------------+-------+------------+-----------------------+
|
|
870
|
-
> psp.blob.roms[0].directories[0].
|
|
871
|
-
|
|
872
|
-
> psp.blob.directories[0].
|
|
874
|
+
> psp.blob.roms[0].directories[0].files[0]
|
|
875
|
+
PubkeyFile(type=0x0, address=0x77400, size=0x240, len(references)=1)
|
|
876
|
+
> psp.blob.directories[0].files[0].get_bytes()
|
|
873
877
|
b'\x01\x00\x00\x00\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
874
878
|
[...]
|
|
875
879
|
> my_stuff = [...]
|
|
876
|
-
> psp.blob.roms[0].directories[0].
|
|
880
|
+
> psp.blob.roms[0].directories[0].files[1].move_buffer(0x60000, 0x1000)
|
|
877
881
|
> psp.blob.roms[0].set_bytes(0x60000, 0x1000, my_stuff)
|
|
878
882
|
> psp.to_file('my_modified_bios.bin')
|
|
879
883
|
```
|
|
@@ -779,40 +779,41 @@ usage: psptool [-V | -E | -X | -R] [file]
|
|
|
779
779
|
Display, extract, and manipulate AMD PSP firmware inside BIOS ROMs.
|
|
780
780
|
|
|
781
781
|
positional arguments:
|
|
782
|
-
file
|
|
782
|
+
file Binary file to be parsed for PSP firmware
|
|
783
783
|
|
|
784
784
|
optional arguments:
|
|
785
785
|
-V, --version
|
|
786
|
-
-E, --entries
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
786
|
+
-E, --entries Default: Parse and display PSP firmware entries.
|
|
787
|
+
[-n] [-j] [-t]
|
|
788
|
+
|
|
789
|
+
-n: list unique entries only ordered by their offset
|
|
790
|
+
-j: output in JSON format instead of tables
|
|
791
|
+
-t: print tree of all signed entities and their certifying keys
|
|
792
|
+
-m: print parsing metrics for testing
|
|
793
|
+
|
|
794
|
+
-X, --extract-file Extract one or more PSP firmware files.
|
|
795
|
+
[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]
|
|
796
|
+
|
|
797
|
+
-r idx: specifies rom_index (default: 0)
|
|
798
|
+
-d idx: specifies directory_index (default: all directories)
|
|
799
|
+
-e idx: specifies file_index (default: all files)
|
|
800
|
+
-n: skip duplicate files and extract unique files only
|
|
801
|
+
-u: uncompress compressed files
|
|
802
|
+
-c: try to decrypt files
|
|
803
|
+
-k: convert pubkeys into PEM format
|
|
804
|
+
-o file: specifies outfile/outdir (default: stdout/{file}_extracted)
|
|
805
|
+
|
|
806
|
+
-R, --replace-file Copy a new file (including header and signature) into the
|
|
807
|
+
ROM file and update file and other metadata accordingly.
|
|
808
|
+
-d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]
|
|
809
|
+
|
|
810
|
+
-r idx: specifies rom_index (default: 0)
|
|
811
|
+
-d idx: specifies directory_index
|
|
812
|
+
-e idx: specifies file_index
|
|
813
|
+
-s file: specifies subfile (i.e. the new file contents)
|
|
814
|
+
-o file: specifies outfile
|
|
815
|
+
-p file: specifies file-stub (e.g. 'keys/id') for the re-signing keys
|
|
816
|
+
-a pass: specifies password for the re-signing keys
|
|
816
817
|
```
|
|
817
818
|
|
|
818
819
|
## Python Usage
|
|
@@ -848,13 +849,13 @@ PSPTool can be **used as a Python module**, e.g. in an interactive IPython sessi
|
|
|
848
849
|
| | 14 | 0xeba00 | 0xbd60 | 0x36 | | AR6B | 17.9.18.12 | OEM_PSP_FW_PUBLIC_KEY |
|
|
849
850
|
| | 15 | 0x149000 | 0x400 | 0x40 | !PL2_SECONDARY_DIRECTORY | | | |
|
|
850
851
|
+---+-------+----------+---------+------+-----------------------------+-------+------------+-----------------------+
|
|
851
|
-
> psp.blob.roms[0].directories[0].
|
|
852
|
-
|
|
853
|
-
> psp.blob.directories[0].
|
|
852
|
+
> psp.blob.roms[0].directories[0].files[0]
|
|
853
|
+
PubkeyFile(type=0x0, address=0x77400, size=0x240, len(references)=1)
|
|
854
|
+
> psp.blob.directories[0].files[0].get_bytes()
|
|
854
855
|
b'\x01\x00\x00\x00\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x1b\xb9\x87\xc3YIF\x06\xb1t\x94V\x01\xc9\xea[\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x00\x00\x08\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
855
856
|
[...]
|
|
856
857
|
> my_stuff = [...]
|
|
857
|
-
> psp.blob.roms[0].directories[0].
|
|
858
|
+
> psp.blob.roms[0].directories[0].files[1].move_buffer(0x60000, 0x1000)
|
|
858
859
|
> psp.blob.roms[0].set_bytes(0x60000, 0x1000, my_stuff)
|
|
859
860
|
> psp.to_file('my_modified_bios.bin')
|
|
860
861
|
```
|
|
@@ -20,7 +20,8 @@ import pkg_resources
|
|
|
20
20
|
|
|
21
21
|
from .psptool import PSPTool
|
|
22
22
|
from .utils import ObligingArgumentParser, PrintHelper
|
|
23
|
-
from .
|
|
23
|
+
from .header_file import HeaderFile
|
|
24
|
+
from .pubkey_file import PubkeyFile
|
|
24
25
|
from .crypto import PrivateKeyDict
|
|
25
26
|
|
|
26
27
|
from argparse import RawTextHelpFormatter, SUPPRESS
|
|
@@ -37,7 +38,7 @@ def main():
|
|
|
37
38
|
|
|
38
39
|
parser.add_argument('-r', '--rom-index', help=SUPPRESS, type=int, default=0)
|
|
39
40
|
parser.add_argument('-d', '--directory-index', help=SUPPRESS, type=int)
|
|
40
|
-
parser.add_argument('-e', '--
|
|
41
|
+
parser.add_argument('-e', '--file-index', help=SUPPRESS, type=int)
|
|
41
42
|
parser.add_argument('-s', '--subfile', help=SUPPRESS)
|
|
42
43
|
parser.add_argument('-o', '--outfile', help=SUPPRESS)
|
|
43
44
|
parser.add_argument('-u', '--decompress', help=SUPPRESS, action='store_true')
|
|
@@ -61,32 +62,32 @@ def main():
|
|
|
61
62
|
'-n: list unique entries only ordered by their offset',
|
|
62
63
|
'-j: output in JSON format instead of tables',
|
|
63
64
|
'-t: print tree of all signed entities and their certifying keys',
|
|
64
|
-
'-m: print
|
|
65
|
+
'-m: print parsing metrics for testing',
|
|
65
66
|
'', '']), action='store_true')
|
|
66
67
|
|
|
67
|
-
action.add_argument('-X', '--extract-
|
|
68
|
-
'Extract one or more PSP firmware
|
|
68
|
+
action.add_argument('-X', '--extract-file', help='\n'.join([
|
|
69
|
+
'Extract one or more PSP firmware files.',
|
|
69
70
|
'[-d idx [-e idx]] [-n] [-u] [-c] [-k] [-o outfile]',
|
|
70
71
|
'',
|
|
71
72
|
'-r idx: specifies rom_index (default: 0)',
|
|
72
73
|
'-d idx: specifies directory_index (default: all directories)',
|
|
73
|
-
'-e idx: specifies
|
|
74
|
-
'-n: skip duplicate
|
|
75
|
-
'-u: uncompress compressed
|
|
76
|
-
'-c: try to decrypt
|
|
74
|
+
'-e idx: specifies file_index (default: all files)',
|
|
75
|
+
'-n: skip duplicate files and extract unique files only',
|
|
76
|
+
'-u: uncompress compressed files',
|
|
77
|
+
'-c: try to decrypt files',
|
|
77
78
|
'-k: convert pubkeys into PEM format',
|
|
78
79
|
'-o file: specifies outfile/outdir (default: stdout/{file}_extracted)',
|
|
79
80
|
'', '']), action='store_true')
|
|
80
81
|
|
|
81
|
-
action.add_argument('-R', '--replace-
|
|
82
|
-
'Copy a new
|
|
83
|
-
'ROM file and update metadata accordingly.',
|
|
82
|
+
action.add_argument('-R', '--replace-file', help='\n'.join([
|
|
83
|
+
'Copy a new file (including header and signature) into the',
|
|
84
|
+
'ROM file and update file and other metadata accordingly.',
|
|
84
85
|
'-d idx -e idx -s subfile -o outfile [-p file-stub] [-a pass]',
|
|
85
86
|
'',
|
|
86
87
|
'-r idx: specifies rom_index (default: 0)',
|
|
87
88
|
'-d idx: specifies directory_index',
|
|
88
|
-
'-e idx: specifies
|
|
89
|
-
'-s file: specifies subfile (i.e. the new
|
|
89
|
+
'-e idx: specifies file_index',
|
|
90
|
+
'-s file: specifies subfile (i.e. the new file contents)',
|
|
90
91
|
'-o file: specifies outfile',
|
|
91
92
|
'-p file: specifies file-stub (e.g. \'keys/id\') for the re-signing keys',
|
|
92
93
|
'-a pass: specifies password for the re-signing keys'
|
|
@@ -105,25 +106,25 @@ def main():
|
|
|
105
106
|
psp = PSPTool.from_file(args.file, verbose=args.verbose)
|
|
106
107
|
output = None
|
|
107
108
|
|
|
108
|
-
if args.
|
|
109
|
-
if args.directory_index is not None and args.
|
|
110
|
-
|
|
109
|
+
if args.extract_file:
|
|
110
|
+
if args.directory_index is not None and args.file_index is not None:
|
|
111
|
+
file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
|
|
111
112
|
|
|
112
113
|
if args.decompress:
|
|
113
|
-
if not
|
|
114
|
-
ph.print_error_and_exit(f'
|
|
115
|
-
output =
|
|
114
|
+
if not file.compressed:
|
|
115
|
+
ph.print_error_and_exit(f'File is not compressed {file.get_readable_type()}')
|
|
116
|
+
output = file.get_signed_bytes()
|
|
116
117
|
elif args.decrypt:
|
|
117
|
-
if not
|
|
118
|
-
ph.print_error_and_exit(f'
|
|
119
|
-
output =
|
|
118
|
+
if not file.encrypted:
|
|
119
|
+
ph.print_error_and_exit(f'File is not encrypted {file.get_readable_type()}')
|
|
120
|
+
output = file.to_decrypted_file_bytes()
|
|
120
121
|
elif args.pem_key:
|
|
121
|
-
output =
|
|
122
|
+
output = file.get_pem_encoded()
|
|
122
123
|
else:
|
|
123
|
-
output =
|
|
124
|
+
output = file.get_bytes()
|
|
124
125
|
|
|
125
126
|
else:
|
|
126
|
-
if args.
|
|
127
|
+
if args.file_index is None: # if neither directory_index nor file_index are specified
|
|
127
128
|
if args.directory_index is not None:
|
|
128
129
|
directories = [psp.blob.roms[args.rom_index].directories[args.directory_index]]
|
|
129
130
|
else:
|
|
@@ -132,40 +133,40 @@ def main():
|
|
|
132
133
|
if args.no_duplicates is False:
|
|
133
134
|
outdir = args.outfile or f'./{psp.filename}_extracted'
|
|
134
135
|
for dir_index, directory in enumerate(directories):
|
|
135
|
-
for
|
|
136
|
-
if args.decompress and type(
|
|
137
|
-
out_bytes =
|
|
138
|
-
elif args.decrypt and type(
|
|
139
|
-
out_bytes =
|
|
140
|
-
elif args.pem_key and type(
|
|
141
|
-
out_bytes =
|
|
136
|
+
for file_index, file in enumerate(directory.files):
|
|
137
|
+
if args.decompress and type(file) is HeaderFile:
|
|
138
|
+
out_bytes = file.get_signed_bytes()
|
|
139
|
+
elif args.decrypt and type(file) is HeaderFile:
|
|
140
|
+
out_bytes = file.get_decrypted()
|
|
141
|
+
elif args.pem_key and type(file) is PubkeyFile:
|
|
142
|
+
out_bytes = file.get_pem_encoded()
|
|
142
143
|
else:
|
|
143
|
-
out_bytes =
|
|
144
|
+
out_bytes = file.get_bytes()
|
|
144
145
|
|
|
145
|
-
outpath = outdir + '/d%.2d_e%.2d_%s' % (dir_index,
|
|
146
|
-
if type(
|
|
147
|
-
outpath += f'_{
|
|
146
|
+
outpath = outdir + '/d%.2d_e%.2d_%s' % (dir_index, file_index, file.get_readable_type())
|
|
147
|
+
if type(file) is HeaderFile:
|
|
148
|
+
outpath += f'_{file.get_readable_version()}'
|
|
148
149
|
|
|
149
150
|
os.makedirs(os.path.dirname(outpath), exist_ok=True)
|
|
150
151
|
with open(outpath, 'wb') as f:
|
|
151
152
|
f.write(out_bytes)
|
|
152
|
-
ph.print_info(f"Extracted all
|
|
153
|
+
ph.print_info(f"Extracted all files to {outdir}")
|
|
153
154
|
else: # no_duplicates is True
|
|
154
|
-
for
|
|
155
|
-
if args.decompress and type(
|
|
156
|
-
out_bytes =
|
|
157
|
-
elif args.decrypt and type(
|
|
158
|
-
out_bytes =
|
|
159
|
-
elif args.pem_key and type(
|
|
160
|
-
out_bytes =
|
|
155
|
+
for file in psp.blob.roms[args.rom_index].unique_files:
|
|
156
|
+
if args.decompress and type(file) is HeaderFile:
|
|
157
|
+
out_bytes = file.get_signed_bytes()
|
|
158
|
+
elif args.decrypt and type(file) is HeaderFile:
|
|
159
|
+
out_bytes = file.get_decrypted()
|
|
160
|
+
elif args.pem_key and type(file) is PubkeyFile:
|
|
161
|
+
out_bytes = file.get_pem_encoded()
|
|
161
162
|
else:
|
|
162
|
-
out_bytes =
|
|
163
|
+
out_bytes = file.get_bytes()
|
|
163
164
|
|
|
164
165
|
outdir = args.outfile or f'./{psp.filename}_unique_extracted'
|
|
165
|
-
outpath = outdir + '/%s' % (
|
|
166
|
+
outpath = outdir + '/%s' % (file.get_readable_type())
|
|
166
167
|
|
|
167
|
-
if type(
|
|
168
|
-
outpath += f'_{
|
|
168
|
+
if issubclass(type(file), HeaderFile):
|
|
169
|
+
outpath += f'_{file.get_readable_version()}'
|
|
169
170
|
|
|
170
171
|
os.makedirs(os.path.dirname(outpath), exist_ok=True)
|
|
171
172
|
with open(outpath, 'wb') as f:
|
|
@@ -173,26 +174,26 @@ def main():
|
|
|
173
174
|
else:
|
|
174
175
|
parser.print_help(sys.stderr)
|
|
175
176
|
|
|
176
|
-
elif args.
|
|
177
|
-
if args.directory_index is not None and args.
|
|
178
|
-
|
|
177
|
+
elif args.replace_file:
|
|
178
|
+
if args.directory_index is not None and args.file_index is not None and args.outfile is not None:
|
|
179
|
+
file = psp.blob.roms[args.rom_index].directories[args.directory_index].files[args.file_index]
|
|
179
180
|
|
|
180
|
-
# Substituting an
|
|
181
|
+
# Substituting an file is actually optional to allow plain re-signs
|
|
181
182
|
if args.subfile is not None:
|
|
182
183
|
with open(args.subfile, 'rb') as f:
|
|
183
184
|
sub_binary = f.read()
|
|
184
|
-
# Keep the existing
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
# Keep the existing file's address, but adapt its size
|
|
186
|
+
file.move_buffer(file.get_address(), len(sub_binary))
|
|
187
|
+
file.set_bytes(0, len(sub_binary), sub_binary)
|
|
187
188
|
|
|
188
189
|
privkeys = None
|
|
189
190
|
if args.privkeystub:
|
|
190
191
|
privkeys = PrivateKeyDict.read_from_files(args.privkeystub, args.privkeypass)
|
|
191
192
|
|
|
192
|
-
if hasattr(
|
|
193
|
-
|
|
193
|
+
if hasattr(file, 'signed_entity') and file.signed_entity:
|
|
194
|
+
file.signed_entity.resign_and_replace(privkeys=privkeys, recursive=True)
|
|
194
195
|
else:
|
|
195
|
-
ph.print_warning("Did not resign anything since target
|
|
196
|
+
ph.print_warning("Did not resign anything since target file is not signed")
|
|
196
197
|
|
|
197
198
|
psp.to_file(args.outfile)
|
|
198
199
|
|
|
@@ -208,7 +209,7 @@ def main():
|
|
|
208
209
|
elif args.metrics:
|
|
209
210
|
psp.print_metrics()
|
|
210
211
|
elif args.no_duplicates:
|
|
211
|
-
psp.
|
|
212
|
+
psp.ls_files(verbose=args.verbose)
|
|
212
213
|
else:
|
|
213
214
|
psp.ls(verbose=args.verbose)
|
|
214
215
|
|
|
@@ -22,13 +22,14 @@ from typing import List
|
|
|
22
22
|
from .fet import EmptyFet
|
|
23
23
|
from .rom import Rom
|
|
24
24
|
from .utils import NestedBuffer, RangeDict
|
|
25
|
-
from .
|
|
25
|
+
from .file import File
|
|
26
|
+
from .pubkey_file import PubkeyFile, InlinePubkeyFile
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
class Blob(NestedBuffer):
|
|
29
30
|
_FIRMWARE_ENTRY_MAGIC = b'\xAA\x55\xAA\x55'
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
# All structures per Rom must be in 16MB windows
|
|
32
|
+
_MAX_PAGE_SIZE = 16 * 1024 * 1024
|
|
32
33
|
class NoFirmwareEntryTableError(Exception):
|
|
33
34
|
pass
|
|
34
35
|
|
|
@@ -38,7 +39,7 @@ class Blob(NestedBuffer):
|
|
|
38
39
|
self.psptool = psptool
|
|
39
40
|
self.roms: List[Rom] = []
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
possible_fet_offsets = [
|
|
42
43
|
# as seen by a PSPTrace Zen 1 boot
|
|
43
44
|
0x020000,
|
|
44
45
|
0xfa0000,
|
|
@@ -48,24 +49,25 @@ class Blob(NestedBuffer):
|
|
|
48
49
|
0x820000,
|
|
49
50
|
]
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
if self.buffer_size
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
possible_rom_sizes = [32, 16, 8]
|
|
53
|
+
_rom_size = max(value for value in possible_rom_sizes if value * 1024 * 1024 <= self.buffer_size)
|
|
54
|
+
rom_size = _rom_size * 1024 * 1024
|
|
55
|
+
self.psptool.ph.print_warning(f"Input file is {self.buffer_size:#x}, will assume ROM size of {_rom_size}M")
|
|
55
56
|
|
|
56
57
|
# For each FET, we try to create a 16MB ROM starting at `FET - offset`
|
|
57
58
|
for fet_location in self._find_fets():
|
|
58
59
|
fet_parsed = False
|
|
59
|
-
for fet_offset in
|
|
60
|
+
for fet_offset in possible_fet_offsets:
|
|
60
61
|
if fet_location < fet_offset:
|
|
61
62
|
# would lead to Blob underflow
|
|
62
63
|
continue
|
|
63
|
-
|
|
64
|
+
rom_page = int(fet_location / self._MAX_PAGE_SIZE)
|
|
65
|
+
if fet_location - fet_offset + rom_size - rom_page * self._MAX_PAGE_SIZE > self.buffer_size:
|
|
64
66
|
# would lead to Blob overflow
|
|
65
67
|
continue
|
|
66
68
|
try:
|
|
67
69
|
rom_offset = fet_location - fet_offset # e.g. 0x20800 - 0x20000 = 0x0800
|
|
68
|
-
potential_rom = Rom(self, rom_size, rom_offset, fet_offset, psptool)
|
|
70
|
+
potential_rom = Rom(self, min(rom_size, self._MAX_PAGE_SIZE), rom_offset, fet_offset, psptool)
|
|
69
71
|
self.roms.append(potential_rom)
|
|
70
72
|
fet_parsed = True
|
|
71
73
|
break # found correct fet_offset!
|
|
@@ -84,16 +86,16 @@ class Blob(NestedBuffer):
|
|
|
84
86
|
return f'Blob({self.roms=})'
|
|
85
87
|
|
|
86
88
|
def _construct_range_dict(self):
|
|
87
|
-
|
|
89
|
+
all_files = self.unique_files()
|
|
88
90
|
|
|
89
91
|
# create RangeDict in order to find entries, directories and fets for a given address
|
|
90
92
|
directories = [directory for rom in self.roms for directory in rom.directories]
|
|
91
93
|
self.range_dict = RangeDict({
|
|
92
94
|
**{
|
|
93
|
-
range(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
for
|
|
95
|
+
range(file.get_address(),
|
|
96
|
+
file.get_address() + file.buffer_size): # key is start and end address of the file
|
|
97
|
+
file
|
|
98
|
+
for file in all_files if file.buffer_size != 0xffffffff # value is its type
|
|
97
99
|
}, **{
|
|
98
100
|
range(directory.get_address(), directory.get_address() + len(directory)):
|
|
99
101
|
directory
|
|
@@ -105,73 +107,65 @@ class Blob(NestedBuffer):
|
|
|
105
107
|
}
|
|
106
108
|
})
|
|
107
109
|
|
|
108
|
-
def
|
|
110
|
+
def unique_files(self) -> set:
|
|
109
111
|
directories = [directory for rom in self.roms for directory in rom.directories]
|
|
110
|
-
|
|
112
|
+
directory_files = [directory.files for directory in directories]
|
|
111
113
|
# flatten list of lists
|
|
112
|
-
|
|
114
|
+
all_files = [file for sublist in directory_files for file in sublist]
|
|
113
115
|
# filter duplicates through set
|
|
114
|
-
|
|
115
|
-
return
|
|
116
|
+
unique_files = set(all_files)
|
|
117
|
+
return unique_files
|
|
116
118
|
|
|
117
119
|
def _find_fets(self):
|
|
118
120
|
# AA55AA55 is to unspecific, so we require a word of padding before (to be tested)
|
|
119
121
|
for m in re.finditer(b'\xff\xff\xff\xff' + self._FIRMWARE_ENTRY_MAGIC, self.get_buffer()):
|
|
120
122
|
fet_offset = m.start() + 4
|
|
121
123
|
yield fet_offset
|
|
124
|
+
for m in re.finditer(b'\x00\x00\x00\x00' + self._FIRMWARE_ENTRY_MAGIC, self.get_buffer()):
|
|
125
|
+
fet_offset = m.start() + 4
|
|
126
|
+
yield fet_offset
|
|
122
127
|
|
|
123
128
|
def _find_inline_pubkeys(self, fp):
|
|
124
129
|
|
|
125
|
-
""" Try to find a pubkey
|
|
130
|
+
""" Try to find a pubkey in any of the found files.
|
|
126
131
|
The pubkey is identified by its fingerprint. If found, the pubkey is
|
|
127
132
|
added to the list of pubkeys of the blob """
|
|
128
133
|
found_pubkeys = []
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
size = 0x440
|
|
141
|
-
else:
|
|
142
|
-
continue
|
|
143
|
-
|
|
144
|
-
key_id = self[start + 0x04: start + 0x14]
|
|
145
|
-
cert_id = self[start + 0x14: start + 0x24]
|
|
146
|
-
|
|
147
|
-
if key_id != cert_id and cert_id != b'\0' * 0x10:
|
|
135
|
+
for file in self.unique_files():
|
|
136
|
+
if type(file) == PubkeyFile:
|
|
137
|
+
continue # Pubkeys don't have inline keys but will only produce false positives
|
|
138
|
+
m = re.finditer(re.escape(binascii.a2b_hex(fp)), file.get_bytes())
|
|
139
|
+
for index in m:
|
|
140
|
+
start_offset = index.start() - 4
|
|
141
|
+
if int.from_bytes(self[start_offset:start_offset + 4], 'little') in PubkeyFile.KNOWN_VERSIONS:
|
|
142
|
+
# Maybe a pubkey. Determine its size:
|
|
143
|
+
pub_exp_size = int.from_bytes(self[start_offset + 0x38: start_offset + 0x3c],
|
|
144
|
+
'little')
|
|
148
145
|
if pub_exp_size == 2048:
|
|
149
|
-
size
|
|
146
|
+
size = 0x240
|
|
147
|
+
elif pub_exp_size == 4096:
|
|
148
|
+
size = 0x440
|
|
150
149
|
else:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
self.psptool.ph.print_warning(f"_find_pubkey: Entry parse error at 0x{start:x}")
|
|
171
|
-
self.psptool.ph.print_warning(f'{e}')
|
|
172
|
-
except Exception as e:
|
|
173
|
-
self.psptool.ph.print_warning(f"_find_pubkey: Error couldn't convert key at: 0x{start:x}")
|
|
174
|
-
self.psptool.ph.print_warning(f'{e}')
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
key_id = self[start_offset + 0x04: start_offset + 0x14]
|
|
153
|
+
cert_id = self[start_offset + 0x14: start_offset + 0x24]
|
|
154
|
+
|
|
155
|
+
if key_id != cert_id and cert_id != b'\0' * 0x10:
|
|
156
|
+
if pub_exp_size == 2048:
|
|
157
|
+
size += 0x100
|
|
158
|
+
else:
|
|
159
|
+
size += 0x200
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
file = InlinePubkeyFile(file, start_offset, size, self, self.psptool)
|
|
163
|
+
assert isinstance(file, PubkeyFile)
|
|
164
|
+
file.inline_keys.add(file)
|
|
165
|
+
found_pubkeys.append(file)
|
|
166
|
+
except File.ParseError as e:
|
|
167
|
+
self.psptool.ph.print_warning(f"_find_pubkey: File parse error at 0x{start_offset:x}")
|
|
168
|
+
self.psptool.ph.print_warning(f'{e}')
|
|
175
169
|
|
|
176
170
|
return found_pubkeys
|
|
177
171
|
|
|
@@ -181,7 +175,7 @@ class Blob(NestedBuffer):
|
|
|
181
175
|
found_pkes += self._find_inline_pubkeys(key_id)
|
|
182
176
|
return found_pkes
|
|
183
177
|
|
|
184
|
-
def get_entries_by_type(self, type_) -> List[
|
|
178
|
+
def get_entries_by_type(self, type_) -> List[File]:
|
|
185
179
|
entries = []
|
|
186
180
|
|
|
187
181
|
for rom in self.roms:
|