The extern object is mapped over address zero when the image sits in the top half of the address space
- 主要语言
- Python
- 星标
- 485
- 派生
- 135
- 平均合并
- 2 天 2 小时
- 30 天内合并 PR
- 15
描述
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
`Loader._find_safe_rebase_addr` begins its search at address 0 whenever the
architecture is narrower than 32 bits, or the main image reaches into the top
half of the address space:
```python
limit = 2**self.main_object.arch.bits
if self.main_object.arch.bits < 32 or self.main_object.max_addr >= 2 ** (self.main_object.arch.bits - 1):
# HACK: On small arches, we should be more aggressive in packing stuff in. An image
# reaching into the top half of the address space leaves its free space underneath it.
start = 0
else:
start = self.main_object.max_addr + 1
```
On those targets address 0 is free, so the first object that asks for a rebase
address gets it. That object is normally `cle##externs`. Loading the same file
five ways, with nothing but the recipe changed:
```python
import angr, archinfo
for name, opts in [
("z80 blob @0x4000", {"backend": "blob", "arch": archinfo.ArchPcode("z80:LE:16:default"), "base_addr": 0x4000}),
("hcs08 blob @0x8000", {"backend": "blob", "arch": archinfo.ArchPcode("HCS08:BE:16:default"), "base_addr": 0x8000}),
("armel blob @0x1000", {"backend": "blob", "arch": "ARMEL", "base_addr": 0x1000}),
("armel blob @0x80000000", {"backend": "blob", "arch": "ARMEL", "base_addr": 0x80000000}),
("amd64 blob @0x400000", {"backend": "blob", "arch": "AMD64", "base_addr": 0x400000}),
]:
p = angr.Project(path, auto_load_libs=False, main_opts=opts)
p.loader.extern_object
print(name, [(type(o).__name__, hex(o.min_addr), hex(o.max_addr)) for o in p.loader.all_objects])
```
```
z80 blob @0x4000 [('ExternObject', '0x0', '0x1ff'), ('Blob', '0x4000', '0x4056')]
hcs08 blob @0x8000 [('ExternObject', '0x0', '0x1ff'), ('Blob', '0x8000', '0x8056')]
armel blob @0x1000 [('Blob', '0x1000', '0x1056'), ('ExternObject', '0x100000', '0x107fff')]
armel blob @0x80000000 [('ExternObject', '0x0', '0x7fff'), ('Blob', '0x80000000', '0x80000056')]
amd64 blob @0x400000 [('Blob', '0x400000', '0x400056'), ('ExternObject', '0x500000', '0x57ffff')]
```
`path` is any file; only the recipe decides the layout. The behaviour predates
#735 — the pre-#735 `_find_safe_rebase_addr` set `gap_start = 0` under the same
condition — so this is long-standing rather than a regression.
Two things follow.
The first is that page zero becomes mapped, readable and executable. On the z80
recipe above, `loader.find_object_containing(0)` returns the extern object,
`loader.memory.load(0, 8)` returns eight zero bytes, and a blank state reads
`` at address 0 instead of raising. A null dereference on these
targets does not fault.
The second is that page zero is exactly where 8- and 16-bit code transfers
control. `RST n` on z80 targets 0x00 through 0x38; a CP/M or MSX style image
calls into a BIOS entry with `CALL 0x000c`; HC08 and HCS08 `JMP dd` reaches the
direct page. Those addresses are outside the image on purpose: the ROM or RAM
holding them is not part of the file. With the extern object underneath them
they are mapped zero fill, and CFGFast decodes it. On a z80 image
(sha256 `70bd24c239fe4104229b97b5c5d134f1abd40a0b6ac60cbb557e29d3b5b6d01c`,
`main_opts={"backend": "blob", "arch": archinfo.ArchPcode("z80:LE:16:default"), "base_addr": 0x4000, "entry_point": 0x4010}`)
the byte at 0x4b89 is `ef`, which is `RST 28h`, and the bytes at 0x54f3 are
`cd 0c 00`, which is `CALL 0x000c`. Both are real instructions in the image;
both end in a block inside `cle##externs`. On HCS08 the entry is a decode of
data rather than real code, but it lands in the same place: on
`827bb4052a4c3cd2e268b2ad3b39bb8d9ba22d11133db20932af0455662a90cc`
(`base_addr=0x2000`) the block at 0x2754 ends in `JMP 0x29`, and from 0x29 the
extern object's zero fill decodes as a chain of three-byte `BRSET 0,0,rel`
conditional branches to the end of the object, 157 blocks.
Reserving the lowest page for the search removes all of it. Scoring each object
twice in the same process, once as the loader places things today and once with
`_free_gaps` clamped to start at 0x1000, with the same CFGFast options
(`normalize=True, data_references=False, resolve_indirect_jumps=True,
force_complete_scan=False`):
| object (sha256) | recipe | blocks | blocks in invented memory |
| --- | --- | --- | --- |
| `a4060543e6e29c1eee99a75649b08a78b743fbef3ff904510c307651f6daf3a4` | z8401x blob @0x1000 | 20 → 14 | 6 → 0 |
| `4f578ba40161fa16944c8832ba4efe2deb837089b768788d1b64fbe0d5b571ed` | z182 blob @0x1000 | 11 → 8 | 3 → 0 |
| `70bd24c239fe4104229b97b5c5d134f1abd40a0b6ac60cbb557e29d3b5b6d01c` | z80 blob @0x4000 | 12415 → 12378 | 41 → 0 |
| `99110d4d8e03e4a4630c0abce5bcf03e5c0daf069eb9086762378cb7e39cff7c` | HCS08 blob @0x4000 | 682 → 523 | 159 → 0 |
| `827bb4052a4c3cd2e268b2ad3b39bb8d9ba22d11133db20932af0455662a90cc` | HCS08 blob @0x2000 | 570 → 413 | 157 → 0 |
| `4b4822e787092fa7d225cc28b909363f25cc3badc3a0a5c14f8d5e902474e511` | HCS08 blob @0x8000 | 919 → 917 | 2 → 0 |
| `f8507794c80a187b957d52fd5f9ea00103cbcb4a69ebe38761c9663dd19eeeae` | HC08 blob @0x2000 | 19171 → 18816 | 355 → 0 |
| `7dfe142d66bcb35696e72f35656212d66258d8ad96e2e9fff49ed12d36dd20a5` | 6502 blob @0xf000 | 223 → 221 | 2 → 0 |
| `69ea58b0697107ae59fdeb6ea6a383a74716db3998817df2d217bc6da0cd38e2` | MIPS64 blob @0x80000400 | 26664 → 26664 | 665 → 665 |
Seven of the eight lose exactly the blocks that were in the extern object and
nothing else; the z80 image at 0x4000 loses 37 rather than 41, because four
blocks elsewhere are still reached once the extern-object functions are gone.
The MIPS64 image is the control: its extern object already sits above the image
at 0x80200000, so reserving page zero changes nothing there, and its 665 blocks
are the separate problem in angr/angr#6834.
Across a block-level sweep of a large mixed corpus, 48 of the 102 objects that
place CFG blocks in loader-invented memory are this case — z80, hc08, hcs08,
6502, 65c02, 8085, cp1600 and pic images loaded as blobs — accounting for 3688
of 6973 such blocks.
Anyone can reproduce the placement without the images: any file loaded with the
recipes at the top shows it, and a z80 image that executes `RST 28h` can be
assembled in a few lines.
Two decisions make this a report rather than a pull request. The first is what
to do when page zero is the only free space, which happens on a 16-bit target
whose image starts below the size of the extern object — refusing to place
anything there would turn a load that works today into "Ran out of room in
address space". The second is how much to reserve, and whether the kernel and
TLS objects should be held to the same rule. Both are placement policy.
贡献指南
这个仓库没有索引到贡献指南
评估
这个 Issue 还没有评估数据。