3.0 KiB
ASLR bypass analysis
This repository contains an ASLR-on exploit for a local heap/libio profile. It
does not contain a deterministic universal ASLR bypass for the unmodified
objdump process.
What works
The payload set can reach the helper command P with ASLR enabled when the
heap and libc low-word layout matches one of the generated profiles.
The current generator emits:
orig: the first measured profile.wsl2404: offsets measured against the pinneddlx-elfbuild on WSL/Ubuntu 24.04.
The wsl2404 profile uses:
off_io=-0x3690
off_sec=0xbb0
rbase=0x220
buf_delta=0x702fff00 or 0x6f300000
system_delta=0x7042e500 or 0x7043e4ff
Why argv two-stage is not enough
A deterministic leak-then-exploit route would need this sequence in one
objdump process:
- Process file 1 and leak libc.
- Generate file 2 with the exact
systemdelta. - Process file 2 with the same ASLR layout.
That path did not hold for this trigger. In local GDB measurements with
objdump -W -r file1 file2, the DWARF relocation side effect that mutates
.debug_info ran for the first object only. The second object's relocation
table printed unmodified symbol names (s0, s1, and so on).
FIFO staging is also blocked in unmodified objdump: display_file() calls
get_file_size(), and get_file_size() rejects non-regular files before
bfd_openr().
Why a fixed single-file transform is not universal
The useful dynamic value in the corrupted FILE object is the existing libc
pointer at FILE+0x68:
_IO_2_1_stderr_ offset = 0x2044e0
system offset = 0x58750
For a page-aligned libc base, converting P = base + 0x2044e0 into
S = base + 0x58750 requires carry/borrow information from lower
little-endian bytes.
Counterexample over low 32 bits:
base_low = 0x0000: P bytes = e0 44 20 00, S bytes = 50 87 05 00
base_low = 0x8000: P bytes = e0 c4 20 00, S bytes = 50 07 06 00
The original byte 2 and byte 3 are identical in both cases (20 00), but the
desired byte 2 differs (05 versus 06) based on original byte 1.
The DLX relocation additions available to the payload operate on big-endian 8/16/32-bit fields in the target byte stream. Their carries flow from higher memory offsets toward lower memory offsets. They cannot make final byte 2 depend on original byte 1. PC-relative writes can set constants, but they do not introduce the missing dependency.
tools/search_pointer_transform.py is a sanity check for this reasoning. It
asks Z3 for fixed sequences consisting of one 32-bit relocation plus up to a
chosen number of 8/16-bit correction relocations, then brute-verifies any
candidate over all page-aligned low-32-bit bases.
Example:
python3 tools/search_pointer_transform.py --mode r32-first --max-extra 4
python3 tools/search_pointer_transform.py --mode r32-last --max-extra 4
Both forms found no universal transform up to four correction relocations in the tested operation class.
Result
The PoC should be described as ASLR-on and profile-dependent. It is not a universal single-file ASLR bypass.