PE function hints from the exception directory are x86-64 only, so an ARM64 or ARMNT image gives CFGFast none
- Dominant language
- Python
- Stars
- 485
- Forks
- 135
- Avg merge
- 2d 2h
- Merged PRs (30d)
- 15
Description
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
### Description
`PE._handle_seh` turns the exception directory into `FunctionHint`s, which is
where `CFGBase._load_func_addrs_from_eh_frame` gets the addresses `CFGFast` seeds
its scan with on a Windows image:
```python
def _handle_seh(self):
if hasattr(self._pe, "DIRECTORY_ENTRY_EXCEPTION"):
for entry in self._pe.DIRECTORY_ENTRY_EXCEPTION:
self.function_hints.append(
FunctionHint(
entry.struct.BeginAddress + self.linked_base,
entry.struct.EndAddress - entry.struct.BeginAddress,
FunctionHintSource.EH_FRAME,
)
)
```
pefile only ever sets that attribute for two machines:
```python
def parse_exceptions_directory(self, rva, size):
# "For x64 and Itanium platforms; the format is different for other platforms"
if (
self.FILE_HEADER.Machine != MACHINE_TYPE["IMAGE_FILE_MACHINE_AMD64"]
and self.FILE_HEADER.Machine != MACHINE_TYPE["IMAGE_FILE_MACHINE_IA64"]
):
return None
```
So on ARM64 and ARMNT — where the ABI requires an unwind entry for every
non-leaf function just as it does on x86-64 — the attribute is absent, the loop
never runs, and `CFGFast` gets nothing. The entries are there in the file; only
the parse is missing. `entry.struct.EndAddress` in the loop above is also
x86-64-specific: the ARM64 and ARMNT entry is eight bytes, `BeginAddress` and a
packed `UnwindData` word, with the length either packed into that word or held in
the first word of the `.xdata` record it points at.
### Measurement
Parsing the exception directory out of the bytes and comparing with
`len(loader.main_object.function_hints)` after
`cle.Loader(path, auto_load_libs=False, main_opts={"backend": "pe"})`:
| image | `Machine` | entries in the file's exception directory | function hints cle built |
|---|---|---|---|
| aarch64 executable | `0xaa64` | 1,931 | 0 |
| ARMNT driver | `0x01c4` | 1,466 | 0 |
| x86-64 executable | `0x8664` | 1,613 | 1,613 |
On the x86-64 row the two agree exactly, which is what the ARM rows would look
like if the directory were parsed.
### Reproduction
This repository's test corpus has no ARM64 or ARMNT PE, so a recipe:
```console
$ clang --target=aarch64-pc-windows-msvc -o hello.exe hello.c
```
```python
import cle
ld = cle.Loader("hello.exe", auto_load_libs=False, main_opts={"backend": "pe"})
print(len(ld.main_object.function_hints)) # 0, however many .pdata entries the image has
```
### Options
1. Parse the ARM64 and ARMNT layout in cle rather than waiting on pefile. It is
an eight-byte entry: `BeginAddress`, then `UnwindData` whose low two bits are
a flag. Flag 0 means the rest is an `.xdata` RVA whose first word holds the
function length in its low 18 bits; flag 1 or 2 means the length is packed
into bits 2–12. The unit is 4 bytes on ARM64 and 2 on ARMNT, and on ARMNT bit
0 of `BeginAddress` is the Thumb bit and has to be masked off. Flag 2 marks a
*fragment* of a function that lives elsewhere, so it is not a function start.
2. Send it upstream to pefile and gate `_handle_seh` on the attribute as now.
Slower, and it leaves `entry.struct.EndAddress` to be dealt with either way,
since the ARM entry has no such field.
3. Leave it, and note in `_handle_seh` that the hints are x86-64 only.
Which of these is right is a maintainer's call, so this is an issue.
Two things worth knowing whichever way it goes. `angr.Project` on an ARM64 or
ARMNT Windows binary currently raises `KeyError: 'Win32'` before any of this
matters — angr/angr#6794 is the open fix — so the effect of this gap is latent
until that lands. And on the x86-64 side, an entry whose `UNWIND_INFO` has
`UNW_FLAG_CHAININFO` describes a *fragment* rather than a function: on one image
328 of 1,613 hints are such fragments, and their `BeginAddress` is frequently in
the middle of a basic block, so seeding a function there is a different thing
from seeding one at a function entry.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.