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'2pwndbg: loaded 166 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.3pwndbg: created $rebase, $base, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)4ASLR is ON (show disable-randomization)5Downloading separate debug info for system-supplied DSO at 0x7ffdf14fe000...6
7Program stopped.80x00007f3077bc3ab0 in _start () from /nix/store/0wydilnf1c9vznywsvxqnaing4wraaxp-glibc-2.39-52/lib/ld-linux-x86-64.so.29$1 = (void *) 0x7ffdf14974e010❯ gdb ./a.out --batch --ex "aslr on" --ex "starti" --ex 'p $rsp'11pwndbg: loaded 166 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.12pwndbg: created $rebase, $base, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)13ASLR is ON (show disable-randomization)14
15Program stopped.160x00007f650ac8dab0 in _start () from /nix/store/0wydilnf1c9vznywsvxqnaing4wraaxp-glibc-2.39-52/lib/ld-linux-x86-64.so.217$1 = (void *) 0x7ffe1532a25018❯ gdb ./a.out --batch --ex "aslr on" --ex "starti" --ex 'p $rsp'19pwndbg: loaded 166 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.20pwndbg: created $rebase, $base, $bn_sym, $bn_var, $bn_eval, $ida GDB functions (can be used with print/break)21ASLR is ON (show disable-randomization)22
23Program stopped.240x00007f8c0fcf7ab0 in _start () from /nix/store/0wydilnf1c9vznywsvxqnaing4wraaxp-glibc-2.39-52/lib/ld-linux-x86-64.so.225$1 = (void *) 0x7ffdc3af4810
So, why is that?
331#define __STACK_RND_MASK(is32bit) ((is32bit) ? 0x7ff : 0x3fffff)332#define STACK_RND_MASK __STACK_RND_MASK(mmap_is_ia32())
351unsigned 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_GROWSUP361 return PAGE_ALIGN(stack_top) + random_variable;362#else363 return PAGE_ALIGN(stack_top) - random_variable;364#endif365}
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?
1001unsigned 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.