Each row below represents one memory region: code (the executable), stack (local variables and return addresses), and heap (dynamic allocations). Press New run to simulate a fresh program launch — ASLR picks a new random base for each region, just as a real OS would.
<!-- ASLR demo — shows how code/heap/stack base addresses change on each run -->
<div class="controls">
<button id="btn-run" type="button">New run</button>
<label class="entropy-label">
Entropy:
<input id="entropy-slider" type="range" min="8" max="28" value="16" step="4">
<span id="entropy-val">16</span> bits
</label>
</div>
<div class="map-wrap">
<div class="map" id="memory-map">
<!-- region rows injected by JS -->
</div>
<div class="axis" id="addr-axis">
<!-- address axis injected by JS -->
</div>
</div>
<div class="history" id="history">
<!-- run history table injected by JS -->
</div>
/* ASLR demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
.entropy-label { display: flex; align-items: center; gap: .4rem; font-size: .9rem; color: #444; }
#entropy-slider { width: 120px; accent-color: #1d3557; }
/* memory map layout */
.map-wrap { display: flex; gap: 0; align-items: stretch; }
.map { flex: 1; display: flex; flex-direction: column; gap: 4px; padding: .4rem 0; }
.region { display: flex; align-items: center; border-radius: 6px; padding: 0 .8rem;
font-size: .85rem; font-weight: 600; color: #fff; transition: background .2s; }
.region span.addr { font: 600 12px ui-monospace, monospace; margin-right: .5rem; opacity: .85; min-width: 90px; }
.region span.name { flex: 1; }
.region span.size { font-size: .75rem; opacity: .7; margin-left: .4rem; }
.region.code { background: #1d6fa4; min-height: 48px; }
.region.heap { background: #2a9d8f; min-height: 36px; }
.region.stack { background: #e76f51; min-height: 44px; }
.axis { display: flex; flex-direction: column; justify-content: space-between;
padding: .4rem 0 .4rem .5rem; font: 11px ui-monospace, monospace; color: #888; width: 70px; }
/* run history table */
.history { margin-top: .8rem; border-top: 1px solid #dde; padding-top: .5rem; }
.history h4 { margin: 0 0 .4rem; font-size: .82rem; color: #555; font-weight: 600; }
.history table { border-collapse: collapse; font: 12px ui-monospace, monospace; width: 100%; }
.history th { font-weight: 700; color: #333; border-bottom: 1px solid #ccc; padding: 2px 6px; text-align: left; }
.history td { padding: 2px 6px; color: #444; }
.history tr:nth-child(even) td { background: #f4f6f8; }
.history .new-row td { animation: flash .4s ease; }
@keyframes flash { from { background: #d0eaff; } to { background: inherit; } }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ASLR demo: randomize base addresses of code, heap and stack on each run
// Each region gets BASE + random offset quantised to page size (0x1000)
const ENTROPY_SLIDER = document.getElementById('entropy-slider');
const ENTROPY_VAL = document.getElementById('entropy-val');
const BTN_RUN = document.getElementById('btn-run');
const MAP_EL = document.getElementById('memory-map');
const HISTORY_EL = document.getElementById('history');
// Memory regions: key = region id, cls = CSS class, size = fixed region size in bytes
const REGIONS = [
{ key: 'code', cls: 'code', size: 0x200000 },
{ key: 'heap', cls: 'heap', size: 0x100000 },
{ key: 'stack', cls: 'stack', size: 0x100000 },
];
// Lowest plausible base address for the code segment
const BASE = 0x400000;
const GAP = 0x200000;
let runCount = 0;
const runs = [];
function randOffset(bits) {
// Pick a uniformly random multiple of the page size within 2^bits pages
const range = Math.pow(2, bits);
return Math.floor(Math.random() * range) * 0x1000;
}
function toHex(n) {
return '0x' + n.toString(16).toUpperCase().padStart(8, '0');
}
function formatSize(bytes) {
return (bytes / 1024).toFixed(0) + ' KB';
}
function newRun() {
runCount++;
const bits = parseInt(ENTROPY_SLIDER.value, 10);
// Stack lives above heap; each region gets its own random offset
const codeBase = BASE + randOffset(bits);
const heapBase = codeBase + REGIONS[0].size + GAP + randOffset(bits);
const stackBase = heapBase + REGIONS[1].size + GAP + randOffset(bits);
const addrs = [codeBase, heapBase, stackBase];
// Render the three rows in the memory map
MAP_EL.innerHTML = '';
REGIONS.forEach((r, i) => {
const row = document.createElement('div');
row.className = 'region ' + r.cls;
row.innerHTML =
'<span class="addr">' + toHex(addrs[i]) + '</span>' +
'<span class="name">{{lbl_region_' + r.key + '}}</span>' +
'<span class="size">' + formatSize(r.size) + '</span>';
MAP_EL.appendChild(row);
});
// Render the address axis ticks
const axisEl = document.getElementById('addr-axis');
axisEl.innerHTML = '';
const ticks = [codeBase, heapBase, stackBase];
ticks.forEach(a => {
const t = document.createElement('div');
t.textContent = toHex(a);
axisEl.appendChild(t);
});
// Record this run in the history list, keep at most 5
runs.unshift({ run: runCount, bits, codeBase, heapBase, stackBase });
if (runs.length > 5) runs.pop();
renderHistory();
}
function renderHistory() {
// Rebuild the history table
const rows = runs.map((r, idx) => {
const isNew = idx === 0 ? ' class="new-row"' : '';
return '<tr' + isNew + '><td>' + i18nFmt('Run %n%', { n: r.run }) +
'</td><td>' + r.bits + ' bits</td>' +
'<td>' + toHex(r.codeBase) + '</td>' +
'<td>' + toHex(r.heapBase) + '</td>' +
'<td>' + toHex(r.stackBase) + '</td></tr>';
}).join('');
HISTORY_EL.innerHTML =
'<h4>Last runs — base addresses</h4>' +
'<table><thead><tr>' +
'<th>Run</th><th>Entropy</th>' +
'<th>Code base</th><th>Heap base</th><th>Stack base</th>' +
'</tr></thead><tbody>' + rows + '</tbody></table>';
}
ENTROPY_SLIDER.addEventListener('input', () => {
ENTROPY_VAL.textContent = ENTROPY_SLIDER.value;
});
BTN_RUN.addEventListener('click', newRun);
// Fire the first run immediately on page load
newRun();
<!-- ASLR demo — shows how code/heap/stack base addresses change on each run -->
<div class="controls">
<button id="btn-run" type="button">New run</button>
<label class="entropy-label">
Entropy:
<input id="entropy-slider" type="range" min="8" max="28" value="16" step="4">
<span id="entropy-val">16</span> bits
</label>
</div>
<div class="map-wrap">
<div class="map" id="memory-map">
<!-- region rows injected by JS -->
</div>
<div class="axis" id="addr-axis">
<!-- address axis injected by JS -->
</div>
</div>
<div class="history" id="history">
<!-- run history table injected by JS -->
</div>
/* ASLR demo styles */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-bottom: .8rem; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
.entropy-label { display: flex; align-items: center; gap: .4rem; font-size: .9rem; color: #444; }
#entropy-slider { width: 120px; accent-color: #1d3557; }
/* memory map layout */
.map-wrap { display: flex; gap: 0; align-items: stretch; }
.map { flex: 1; display: flex; flex-direction: column; gap: 4px; padding: .4rem 0; }
.region { display: flex; align-items: center; border-radius: 6px; padding: 0 .8rem;
font-size: .85rem; font-weight: 600; color: #fff; transition: background .2s; }
.region span.addr { font: 600 12px ui-monospace, monospace; margin-right: .5rem; opacity: .85; min-width: 90px; }
.region span.name { flex: 1; }
.region span.size { font-size: .75rem; opacity: .7; margin-left: .4rem; }
.region.code { background: #1d6fa4; min-height: 48px; }
.region.heap { background: #2a9d8f; min-height: 36px; }
.region.stack { background: #e76f51; min-height: 44px; }
.axis { display: flex; flex-direction: column; justify-content: space-between;
padding: .4rem 0 .4rem .5rem; font: 11px ui-monospace, monospace; color: #888; width: 70px; }
/* run history table */
.history { margin-top: .8rem; border-top: 1px solid #dde; padding-top: .5rem; }
.history h4 { margin: 0 0 .4rem; font-size: .82rem; color: #555; font-weight: 600; }
.history table { border-collapse: collapse; font: 12px ui-monospace, monospace; width: 100%; }
.history th { font-weight: 700; color: #333; border-bottom: 1px solid #ccc; padding: 2px 6px; text-align: left; }
.history td { padding: 2px 6px; color: #444; }
.history tr:nth-child(even) td { background: #f4f6f8; }
.history .new-row td { animation: flash .4s ease; }
@keyframes flash { from { background: #d0eaff; } to { background: inherit; } }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// ASLR demo: randomize base addresses of code, heap and stack on each run
// Each region gets BASE + random offset quantised to page size (0x1000)
const ENTROPY_SLIDER = document.getElementById('entropy-slider');
const ENTROPY_VAL = document.getElementById('entropy-val');
const BTN_RUN = document.getElementById('btn-run');
const MAP_EL = document.getElementById('memory-map');
const HISTORY_EL = document.getElementById('history');
// Memory regions: key = region id, cls = CSS class, size = fixed region size in bytes
const REGIONS = [
{ key: 'code', cls: 'code', size: 0x200000 },
{ key: 'heap', cls: 'heap', size: 0x100000 },
{ key: 'stack', cls: 'stack', size: 0x100000 },
];
// Lowest plausible base address for the code segment
const BASE = 0x400000;
const GAP = 0x200000;
let runCount = 0;
const runs = [];
function randOffset(bits) {
// Pick a uniformly random multiple of the page size within 2^bits pages
const range = Math.pow(2, bits);
return Math.floor(Math.random() * range) * 0x1000;
}
function toHex(n) {
return '0x' + n.toString(16).toUpperCase().padStart(8, '0');
}
function formatSize(bytes) {
return (bytes / 1024).toFixed(0) + ' KB';
}
function newRun() {
runCount++;
const bits = parseInt(ENTROPY_SLIDER.value, 10);
// Stack lives above heap; each region gets its own random offset
const codeBase = BASE + randOffset(bits);
const heapBase = codeBase + REGIONS[0].size + GAP + randOffset(bits);
const stackBase = heapBase + REGIONS[1].size + GAP + randOffset(bits);
const addrs = [codeBase, heapBase, stackBase];
// Render the three rows in the memory map
MAP_EL.innerHTML = '';
REGIONS.forEach((r, i) => {
const row = document.createElement('div');
row.className = 'region ' + r.cls;
row.innerHTML =
'<span class="addr">' + toHex(addrs[i]) + '</span>' +
'<span class="name">{{lbl_region_' + r.key + '}}</span>' +
'<span class="size">' + formatSize(r.size) + '</span>';
MAP_EL.appendChild(row);
});
// Render the address axis ticks
const axisEl = document.getElementById('addr-axis');
axisEl.innerHTML = '';
const ticks = [codeBase, heapBase, stackBase];
ticks.forEach(a => {
const t = document.createElement('div');
t.textContent = toHex(a);
axisEl.appendChild(t);
});
// Record this run in the history list, keep at most 5
runs.unshift({ run: runCount, bits, codeBase, heapBase, stackBase });
if (runs.length > 5) runs.pop();
renderHistory();
}
function renderHistory() {
// Rebuild the history table
const rows = runs.map((r, idx) => {
const isNew = idx === 0 ? ' class="new-row"' : '';
return '<tr' + isNew + '><td>' + i18nFmt('Run %n%', { n: r.run }) +
'</td><td>' + r.bits + ' bits</td>' +
'<td>' + toHex(r.codeBase) + '</td>' +
'<td>' + toHex(r.heapBase) + '</td>' +
'<td>' + toHex(r.stackBase) + '</td></tr>';
}).join('');
HISTORY_EL.innerHTML =
'<h4>Last runs — base addresses</h4>' +
'<table><thead><tr>' +
'<th>Run</th><th>Entropy</th>' +
'<th>Code base</th><th>Heap base</th><th>Stack base</th>' +
'</tr></thead><tbody>' + rows + '</tbody></table>';
}
ENTROPY_SLIDER.addEventListener('input', () => {
ENTROPY_VAL.textContent = ENTROPY_SLIDER.value;
});
BTN_RUN.addEventListener('click', newRun);
// Fire the first run immediately on page load
newRun();
Notice that the relative order of the regions stays the same (code below stack, heap between them), but the absolute addresses are different every time. An attacker who hardcodes 0x400000 as the code base will almost certainly jump to garbage — or crash — instead of reaching their payload. The larger the entropy bits, the bigger the random space and the harder the blind guess.
Comments
Loading comments...