~ all posts ctf projects research
432 words
2 minutes
Why are stack addresses not constant offset from the base?
2024-10-06
No Tags

Often when writing exploits under ASLR you’ll need to calculate an address based on some leak. Usually, you’ll just calculate the base address of that section and then compute any desired address relative to the base. I’ve often noticed that this doesn’t work with stack addresses. Leaked stack addresses are constant offsets relative to each other but not constant relative to the base of the stack — even in cases where the stack is not explicitly grown downwards.

Some brief gdb investigation shows that the initial stack pointer is randomized within a page from the very first userspace instruction.

1
❯ gdb ./a.out --batch --ex "aslr on" --ex "starti" --ex 'p $rsp'
2
pwndbg: loaded 166 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
3
pwndbg: created $rebase, $base, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
4
ASLR is ON (show disable-randomization)
5
Downloading separate debug info for system-supplied DSO at 0x7ffdf14fe000...
6
7
Program stopped.
8
0x00007f3077bc3ab0 in _start () from /nix/store/0wydilnf1c9vznywsvxqnaing4wraaxp-glibc-2.39-52/lib/ld-linux-x86-64.so.2
9
$1 = (void *) 0x7ffdf14974e0
10
❯ gdb ./a.out --batch --ex "aslr on" --ex "starti" --ex 'p $rsp'
11
pwndbg: loaded 166 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
12
pwndbg: created $rebase, $base, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
13
ASLR is ON (show disable-randomization)
14
15
Program stopped.
16
0x00007f650ac8dab0 in _start () from /nix/store/0wydilnf1c9vznywsvxqnaing4wraaxp-glibc-2.39-52/lib/ld-linux-x86-64.so.2
17
$1 = (void *) 0x7ffe1532a250
18
❯ gdb ./a.out --batch --ex "aslr on" --ex "starti" --ex 'p $rsp'
19
pwndbg: loaded 166 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
20
pwndbg: created $rebase, $base, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)
21
ASLR is ON (show disable-randomization)
22
23
Program stopped.
24
0x00007f8c0fcf7ab0 in _start () from /nix/store/0wydilnf1c9vznywsvxqnaing4wraaxp-glibc-2.39-52/lib/ld-linux-x86-64.so.2
25
$1 = (void *) 0x7ffdc3af4810

So, why is that?

linux/v6.11/source/arch/x86/include/asm/elf.h
331
#define __STACK_RND_MASK(is32bit) ((is32bit) ? 0x7ff : 0x3fffff)
332
#define STACK_RND_MASK __STACK_RND_MASK(mmap_is_ia32())
/linux/v6.11/source/mm/util.c
351
unsigned long randomize_stack_top(unsigned long stack_top)
352
{
353
unsigned long random_variable = 0;
354
355
if (current->flags & PF_RANDOMIZE) {
356
random_variable = get_random_long();
357
random_variable &= STACK_RND_MASK;
358
random_variable <<= PAGE_SHIFT;
359
}
360
#ifdef CONFIG_STACK_GROWSUP
361
return PAGE_ALIGN(stack_top) + random_variable;
362
#else
363
return PAGE_ALIGN(stack_top) - random_variable;
364
#endif
365
}

randomize_stack_top will randomize the base of the stack — specifically randomizing the 22 least significant bits, shifted upwards by a page. We saw earlier that the initial stack pointer isn’t page aligned, though? Where does that come from?

linux/v6.11/source/arch/x86/kernel/process.c
1001
unsigned long arch_align_stack(unsigned long sp)
1002
{
1003
if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)
1004
sp -= get_random_u32_below(8192);
1005
return sp & ~0xf;
1006
}

Turns out Linux ASLR will randomize the initial stack pointer within a page as well! This adds an additional 8 bits of entropy for a total of 2^30 initial stack pointer values.