├── README.md
├── secure_emu
├── main.py
├── py.typed
├── symbol_client.py
├── machine_state.py
├── image_loader.py
└── __init__.py
├── tests
└── __init__.py
├── .idea
├── sonarlint
│ └── issuestore
│ │ └── index.pb
├── vcs.xml
├── misc.xml
├── inspectionProfiles
│ └── profiles_settings.xml
├── discord.xml
├── .gitignore
├── modules.xml
├── secure_emu.iml
└── aws.xml
├── .gitmodules
├── pyproject.toml
└── poetry.lock
/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/secure_emu/main.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/secure_emu/py.typed:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/index.pb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/secure_emu/symbol_client.py:
--------------------------------------------------------------------------------
1 | class SymbolClient:
2 | pass
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "ext/roms"]
2 | path = ext/roms
3 | url = https://github.com/hekapooios/hekapooios.github.io.git
4 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 | # Zeppelin ignored files
10 | /ZeppelinRemoteNotebooks/
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/secure_emu.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/aws.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "secure-emu"
3 | version = "0.1.0"
4 | description = ""
5 | authors = ["Rick Mark "]
6 | readme = "README.md"
7 | packages = [{include = "secure_emu"}]
8 |
9 | [tool.poetry.dependencies]
10 | python = "^3.10"
11 | apple-data = "^1.0.522"
12 | unicorn = "^2.0.0"
13 | capstone = "^4.0.2"
14 | keystone-engine = "^0.9.2"
15 |
16 |
17 | [tool.poetry.group.dev.dependencies]
18 | pytest = "^7.1.3"
19 | mypy = "^0.971"
20 |
21 | [build-system]
22 | requires = ["poetry-core"]
23 | build-backend = "poetry.core.masonry.api"
24 |
--------------------------------------------------------------------------------
/secure_emu/machine_state.py:
--------------------------------------------------------------------------------
1 | import apple_data
2 | import unicorn
3 |
4 |
5 | class MachineState:
6 | def __init__(self):
7 | self.msr_data = apple_data.load_file("registers")
8 |
9 | def friendly_name(self, cp_reg: unicorn.unicorn.uc_arm64_cp_reg) -> str:
10 | msr_map = self.msr_data["aarch64"]["msr"]
11 | apple_map = self.msr_data["aarch64"]["apple_system_registers"]
12 | reg_descriptor = (
13 | f"S{cp_reg.op0}_{cp_reg.op1}_c{cp_reg.crn}_c{cp_reg.crm}_{cp_reg.op2}"
14 | )
15 | if reg_descriptor in msr_map:
16 | return msr_map[reg_descriptor]
17 | if reg_descriptor in apple_map:
18 | return apple_map[reg_descriptor]
19 |
20 | return "Unknown"
21 |
--------------------------------------------------------------------------------
/secure_emu/image_loader.py:
--------------------------------------------------------------------------------
1 | import pathlib
2 | import os
3 | import typing
4 | import apple_data
5 | import hashlib
6 |
7 |
8 | class Core:
9 | pass
10 |
11 |
12 | class ImageLoader:
13 | ROMS: dict[str, pathlib.Path] = {}
14 | _cores: dict[str, typing.Any] = {}
15 | _loaded = False
16 |
17 | @staticmethod
18 | def get_securerom(path: str) -> bytes:
19 | rom_path = pathlib.Path(os.path.dirname(__file__)).joinpath(path)
20 | with open(rom_path, mode="rb") as file:
21 | return file.read()
22 |
23 | @staticmethod
24 | def get_core(core: int) -> Core:
25 | pass
26 |
27 | @staticmethod
28 | def _prepare():
29 | if ImageLoader._loaded:
30 | return
31 |
32 | for image_path in pathlib.Path(os.path.join(os.path.dirname(__file__), '../ext/roms/resources/APROM')).glob(
33 | "*"):
34 | with open(image_path, 'rb') as f:
35 | data = f.read()
36 | ImageLoader.ROMS[hashlib.sha256(data).hexdigest()] = image_path
37 |
38 | ImageLoader._cores = apple_data.load_file('cores')
39 |
40 | ImageLoader._loaded = True
41 |
42 |
--------------------------------------------------------------------------------
/secure_emu/__init__.py:
--------------------------------------------------------------------------------
1 | import typing
2 |
3 | import unicorn
4 | import machine_state
5 | import image_loader
6 |
7 | RegisterHook = typing.Callable[[int], int]
8 |
9 |
10 | class SecureEMU:
11 | _core: image_loader.Core
12 | register_bank = {}
13 | register_hooks: dict[str, RegisterHook]
14 |
15 | def __init__(self, core: int):
16 | self._core = image_loader.ImageLoader.get_core(core)
17 | self._machine_state = machine_state.MachineState()
18 |
19 | @staticmethod
20 | def cp_reg_to_id(cp_reg: unicorn.unicorn.uc_arm64_cp_reg) -> str:
21 | return f"c{cp_reg.crn}_c{cp_reg.crm}_{cp_reg.op1}_{cp_reg.op2}"
22 |
23 | @staticmethod
24 | def _hook_block(uc, address, size, user_data):
25 | print(">>> Tracing basic block at 0x%x, block size = 0x%x" % (address, size))
26 |
27 | @staticmethod
28 | def _hook_code(uc, address, size, user_data):
29 | print(
30 | ">>> Tracing instruction at 0x%x, instruction size = 0x%x" % (address, size)
31 | )
32 |
33 | @staticmethod
34 | def _hook_mrs(uc: unicorn.Uc, reg, cp_reg, reg_file) -> bool:
35 | pc = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC)
36 | reg_friendly = msr_util.friendly_name(cp_reg)
37 | print(
38 | f">>> Hook MRS read instruction ({pc:x}): reg = 0x{reg:x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}\n>>>\t{reg_friendly}"
39 | )
40 | reg_id = _cp_reg_to_id(cp_reg)
41 | if reg_id not in reg_file:
42 | reg_file[reg_id] = 0
43 |
44 | uc.reg_write(reg, reg_file[reg_id])
45 | uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, pc + 4)
46 | # Skip MRS instruction
47 |
48 | return True
49 |
50 | def _hook_msr(self, uc: unicorn.Uc, reg, cp_reg, reg_file) -> bool:
51 | pc = uc.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC)
52 | reg_friendly = msr_util.friendly_name(cp_reg)
53 | print(
54 | f">>> Hook MSR store instruction ({pc:x}): reg = 0x{reg:x}(UC_ARM64_REG_X2) cp_reg = {cp_reg}\n>>>\t{reg_friendly}"
55 | )
56 | reg_id = SecureEMU._cp_reg_to_id(cp_reg)
57 | reg_value = uc.reg_read(reg)
58 | if reg_id in self.register_hooks:
59 | value_to_store = self.register_hooks[reg_id](reg_value)
60 | reg_file[reg_id] = value_to_store
61 | else:
62 | reg_file[reg_id] = reg_value
63 | uc.reg_write(unicorn.arm64_const.UC_ARM64_REG_PC, pc + 4)
64 | # Skip MRS instruction
65 |
66 | return True
67 |
68 | @staticmethod
69 | def _hook_mem_invalid(mu: unicorn.Uc, access, address, size, value, user_data):
70 | ip = mu.reg_read(unicorn.arm64_const.UC_ARM64_REG_PC)
71 | match access:
72 | case unicorn.UC_MEM_FETCH:
73 | access_type = "FETCH"
74 | case unicorn.UC_MEM_READ:
75 | access_type = "READ"
76 | case unicorn.UC_MEM_WRITE:
77 | access_type = "WRITE"
78 | case other:
79 | access_type = f"UNKNOWN <{access:x}>"
80 |
81 | error = f">>> {access_type} ACCESS at 0x{address:016x} from IP = 0x{ip:016x}, data size = {size}, data value = 0x{value:x}"
82 | print(error)
83 |
84 | def run(self):
85 | mu = unicorn.Uc(unicorn.UC_ARCH_ARM64, unicorn.UC_MODE_ARM)
86 | mu.mem_map(self._core.image_base, self._core.image_size, unicorn.UC_PROT_EXEC | unicorn.UC_PROT_READ)
87 |
88 | mu.mem_write(self._core.image_base, self._core.secure_rom)
89 |
90 | mu.mem_map(self._core.sram_base, self._core.sram_size, unicorn.UC_PROT_ALL)
91 |
92 | def cache_as_ram_helper(value) -> int:
93 | if value & 0x01:
94 | return 0x8000000000000000 | value
95 | else:
96 | return value
97 |
98 | self.register_hooks["c15_c7_3_0"] = cache_as_ram_helper
99 |
100 |
101 |
102 |
103 |
104 |
105 | # mu.hook_add(unicorn.UC_HOOK_BLOCK, hook_block)
106 | mu.hook_add(unicorn.UC_HOOK_MEM_FETCH_UNMAPPED, hook_mem_invalid)
107 | mu.hook_add(unicorn.UC_HOOK_MEM_READ_UNMAPPED, hook_mem_invalid)
108 | mu.hook_add(unicorn.UC_HOOK_MEM_WRITE_UNMAPPED, hook_mem_invalid)
109 |
110 | mu.hook_add(
111 | unicorn.UC_HOOK_INSN,
112 | hook_mrs,
113 | self.register_bank,
114 | 1,
115 | 0,
116 | unicorn.arm64_const.UC_ARM64_INS_MRS,
117 | )
118 | mu.hook_add(
119 | unicorn.UC_HOOK_INSN,
120 | hook_msr,
121 | self.register_bank,
122 | 1,
123 | 0,
124 | unicorn.arm64_const.UC_ARM64_INS_MSR,
125 | )
126 |
127 | try:
128 | mu.emu_start(0x100000000, 0x100000000 + len(rom), 0, 100000)
129 | except unicorn.UcError as e:
130 | print(e)
131 |
132 |
133 |
134 |
135 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "apple-data"
3 | version = "1.0.522"
4 | description = "Static data from https://docs.hackdiffe.rent"
5 | category = "main"
6 | optional = false
7 | python-versions = ">=3.8,<4.0"
8 |
9 | [[package]]
10 | name = "attrs"
11 | version = "22.1.0"
12 | description = "Classes Without Boilerplate"
13 | category = "dev"
14 | optional = false
15 | python-versions = ">=3.5"
16 |
17 | [package.extras]
18 | dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
19 | docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
20 | tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"]
21 | tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"]
22 |
23 | [[package]]
24 | name = "capstone"
25 | version = "4.0.2"
26 | description = "Capstone disassembly engine"
27 | category = "main"
28 | optional = false
29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
30 |
31 | [[package]]
32 | name = "colorama"
33 | version = "0.4.5"
34 | description = "Cross-platform colored terminal text."
35 | category = "dev"
36 | optional = false
37 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
38 |
39 | [[package]]
40 | name = "iniconfig"
41 | version = "1.1.1"
42 | description = "iniconfig: brain-dead simple config-ini parsing"
43 | category = "dev"
44 | optional = false
45 | python-versions = "*"
46 |
47 | [[package]]
48 | name = "keystone-engine"
49 | version = "0.9.2"
50 | description = "Keystone assembler engine"
51 | category = "main"
52 | optional = false
53 | python-versions = "*"
54 |
55 | [[package]]
56 | name = "mypy"
57 | version = "0.971"
58 | description = "Optional static typing for Python"
59 | category = "dev"
60 | optional = false
61 | python-versions = ">=3.6"
62 |
63 | [package.dependencies]
64 | mypy-extensions = ">=0.4.3"
65 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
66 | typing-extensions = ">=3.10"
67 |
68 | [package.extras]
69 | dmypy = ["psutil (>=4.0)"]
70 | python2 = ["typed-ast (>=1.4.0,<2)"]
71 | reports = ["lxml"]
72 |
73 | [[package]]
74 | name = "mypy-extensions"
75 | version = "0.4.3"
76 | description = "Experimental type system extensions for programs checked with the mypy typechecker."
77 | category = "dev"
78 | optional = false
79 | python-versions = "*"
80 |
81 | [[package]]
82 | name = "packaging"
83 | version = "21.3"
84 | description = "Core utilities for Python packages"
85 | category = "dev"
86 | optional = false
87 | python-versions = ">=3.6"
88 |
89 | [package.dependencies]
90 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
91 |
92 | [[package]]
93 | name = "pluggy"
94 | version = "1.0.0"
95 | description = "plugin and hook calling mechanisms for python"
96 | category = "dev"
97 | optional = false
98 | python-versions = ">=3.6"
99 |
100 | [package.extras]
101 | dev = ["pre-commit", "tox"]
102 | testing = ["pytest", "pytest-benchmark"]
103 |
104 | [[package]]
105 | name = "py"
106 | version = "1.11.0"
107 | description = "library with cross-python path, ini-parsing, io, code, log facilities"
108 | category = "dev"
109 | optional = false
110 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
111 |
112 | [[package]]
113 | name = "pyparsing"
114 | version = "3.0.9"
115 | description = "pyparsing module - Classes and methods to define and execute parsing grammars"
116 | category = "dev"
117 | optional = false
118 | python-versions = ">=3.6.8"
119 |
120 | [package.extras]
121 | diagrams = ["jinja2", "railroad-diagrams"]
122 |
123 | [[package]]
124 | name = "pytest"
125 | version = "7.1.3"
126 | description = "pytest: simple powerful testing with Python"
127 | category = "dev"
128 | optional = false
129 | python-versions = ">=3.7"
130 |
131 | [package.dependencies]
132 | attrs = ">=19.2.0"
133 | colorama = {version = "*", markers = "sys_platform == \"win32\""}
134 | iniconfig = "*"
135 | packaging = "*"
136 | pluggy = ">=0.12,<2.0"
137 | py = ">=1.8.2"
138 | tomli = ">=1.0.0"
139 |
140 | [package.extras]
141 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
142 |
143 | [[package]]
144 | name = "tomli"
145 | version = "2.0.1"
146 | description = "A lil' TOML parser"
147 | category = "dev"
148 | optional = false
149 | python-versions = ">=3.7"
150 |
151 | [[package]]
152 | name = "typing-extensions"
153 | version = "4.3.0"
154 | description = "Backported and Experimental Type Hints for Python 3.7+"
155 | category = "dev"
156 | optional = false
157 | python-versions = ">=3.7"
158 |
159 | [[package]]
160 | name = "unicorn"
161 | version = "2.0.0"
162 | description = "Unicorn CPU emulator engine"
163 | category = "main"
164 | optional = false
165 | python-versions = "*"
166 |
167 | [metadata]
168 | lock-version = "1.1"
169 | python-versions = "^3.10"
170 | content-hash = "13917463298515dcfcb76b2d1bf244d0aea172795bfcc7e379644c45c6eee62b"
171 |
172 | [metadata.files]
173 | apple-data = [
174 | {file = "apple-data-1.0.522.tar.gz", hash = "sha256:bdb737414706a02158b66c648dc45adf396af0d357ecf89e41359d477eead397"},
175 | {file = "apple_data-1.0.522-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:e45337d06eb7757ce1ffda9ef99670c2438c33163ba4984d86903684270ec897"},
176 | ]
177 | attrs = [
178 | {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
179 | {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
180 | ]
181 | capstone = [
182 | {file = "capstone-4.0.2-py2.py3-none-manylinux1_i686.whl", hash = "sha256:da442f979414cf27e4621e70e835880878c858ea438c4f0e957e132593579e37"},
183 | {file = "capstone-4.0.2-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:9d1a9096c5f875b11290317722ed44bb6e7c52e50cc79d791f142bce968c49aa"},
184 | {file = "capstone-4.0.2-py2.py3-none-win32.whl", hash = "sha256:c3d9b443d1adb40ee2d9a4e7341169b76476ddcf3a54c03793b16cdc7cd35c5a"},
185 | {file = "capstone-4.0.2-py2.py3-none-win_amd64.whl", hash = "sha256:0d65ffe8620920976ceadedc769f22318f6f150a592368d8a735612367ac8a1a"},
186 | {file = "capstone-4.0.2.tar.gz", hash = "sha256:2842913092c9b69fd903744bc1b87488e1451625460baac173056e1808ec1c66"},
187 | ]
188 | colorama = [
189 | {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
190 | {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
191 | ]
192 | iniconfig = [
193 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
194 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
195 | ]
196 | keystone-engine = [
197 | {file = "keystone-engine-0.9.2.tar.gz", hash = "sha256:2f7af62dab0ce6c2732dbb4f31cfa2184a8a149e280b96b92ebc0db84c6e50f5"},
198 | {file = "keystone_engine-0.9.2-py2.py3-none-macosx_10_14_x86_64.whl", hash = "sha256:dafcc3d9450c239cbc54148855b79c4b387777099c6d054005c835768cf955f2"},
199 | {file = "keystone_engine-0.9.2-py2.py3-none-manylinux1_i686.whl", hash = "sha256:9e04dea5a2b50509b7b707abdb395de42772c40faa36131ea94482fba8dd5d9f"},
200 | {file = "keystone_engine-0.9.2-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:5a5316a34323620b1bba31dcfe9e4b4ca6f0c030e82fc7a151da7c8fbe81a379"},
201 | {file = "keystone_engine-0.9.2-py2.py3-none-win32.whl", hash = "sha256:9f81e480904a405ef008f1d9f0e4a05e37d2bd83c5218a27136e1a294b02c1f6"},
202 | {file = "keystone_engine-0.9.2-py2.py3-none-win_amd64.whl", hash = "sha256:c91db1ff16d9d094e00d1827107d1b4afd5e63ce19b491a0140e660635000e8b"},
203 | ]
204 | mypy = [
205 | {file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"},
206 | {file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"},
207 | {file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"},
208 | {file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"},
209 | {file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"},
210 | {file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"},
211 | {file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"},
212 | {file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"},
213 | {file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"},
214 | {file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"},
215 | {file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"},
216 | {file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"},
217 | {file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"},
218 | {file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"},
219 | {file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"},
220 | {file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"},
221 | {file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"},
222 | {file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"},
223 | {file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"},
224 | {file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"},
225 | {file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"},
226 | {file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"},
227 | {file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"},
228 | ]
229 | mypy-extensions = [
230 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
231 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
232 | ]
233 | packaging = [
234 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
235 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
236 | ]
237 | pluggy = [
238 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
239 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
240 | ]
241 | py = [
242 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
243 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
244 | ]
245 | pyparsing = [
246 | {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
247 | {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
248 | ]
249 | pytest = [
250 | {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
251 | {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
252 | ]
253 | tomli = [
254 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
255 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
256 | ]
257 | typing-extensions = [
258 | {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
259 | {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
260 | ]
261 | unicorn = [
262 | {file = "unicorn-2.0.0-py2.py3-none-macosx_10_15_x86_64.whl", hash = "sha256:6a28c53d5aa034db24ced440be1ff5d33061b96a7c9b14bed624b071e8925bc3"},
263 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1efd78f49c3d14fa83159d876fb2cafd58c1ba8d3f61dacbc033f8b77c121774"},
264 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_i686.whl", hash = "sha256:4a4ed8c325d77ac051c00d7c6912fdee9a0d48daaecadd34107ec649f728d7a7"},
265 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6053ec25481dac82e5a92b0676885c9f526c41415bf55b652ee3919f02ad513e"},
266 | {file = "unicorn-2.0.0-py2.py3-none-manylinux1_x86_64.whl", hash = "sha256:10bfc2e45a4bc31bc6cd4325977e18335733ec8fadd85cf35b056ee939f4f046"},
267 | {file = "unicorn-2.0.0-py2.py3-none-win32.whl", hash = "sha256:6798197cc1204ded0cd6286cc207ed1d2c017aed868e9dee53c5808ae86e8ddb"},
268 | {file = "unicorn-2.0.0-py2.py3-none-win_amd64.whl", hash = "sha256:98d4630a67b19e2840e2a6423b8537c5ead9123bb8db0e6ff92c38701b95d62c"},
269 | {file = "unicorn-2.0.0.tar.gz", hash = "sha256:9394b83798080e25a3921e0a80196f902ddd1391f1bf4a6b1fb6b8ccb63b41e8"},
270 | ]
271 |
--------------------------------------------------------------------------------