This article and supporting repository present a naive but effective method of scanning the stack for the auxiliary vector.
Background
The order of first several auxiliary vector entries as defined in the linux
kernel source (create_elf_tables() in fs/binfmt_fs.c) has not changed since
v2.6.12 (3a93e40326c8f470e71d20b4c42d36767450f38f last time I checked):
NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
NEW_AUX_ENT(AT_PHDR, phdr_addr);
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
NEW_AUX_ENT(AT_BASE, interp_load_addr);
if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0)
flags |= AT_FLAGS_PRESERVE_ARGV0;
NEW_AUX_ENT(AT_FLAGS, flags);
NEW_AUX_ENT(AT_ENTRY, e_entry);
NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid));
NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid));
NEW_AUX_ENT(AT_GID, from_kgid_munged(cred->user_ns, cred->gid));
NEW_AUX_ENT(AT_EGID, from_kgid_munged(cred->user_ns, cred->egid));
NEW_AUX_ENT(AT_SECURE, bprm->secureexec);
That means that a predictable order of struct aux_entry_64 id values can be
found somewhere on the stack. This pattern likely marks the beginning of the
auxiliary vector.
Why not use getauxval() like a normal person?
The point of this code is to be able to find the auxiliary vector without depending on libc or knowing the stack state. This can be useful as part of elf parasite code or shellcode.
Usage
ASM Implementation
Run make and you can link the auxvector64.o with your program. For a 32-bit
object run make 32bit=true (it will produce auxvector32.o).
The asm version doesn’t take any arguments and returns the address of the
auxiliary vector in rax (or eax in the 32 bit version).
C Implementation
Run make c=true and you can link the auxvector.o with your program. For a 32-bit
object run make c=true 32bit=true.
Call get_beg_auxvector() from your code:
struct aux_entry *get_beg_auxvector(unsigned long int rsp, unsigned long int max_stack);
rsp and max_stack represent the lower and upper bound of the stack scan respectively.
Both addresses will be QWORD/DWORD aligned (depending on bitness).
The function returns a pointer to AT_HWCAP auxiliary vector entry or NULL on error.
Examples
Check out code in test32.asm and test64.asm for an example.
You can compile and link these examples with make test 32bit=true and
make test respectively.
Both examples will grab the AT_PHNUM value and pass it to the exit syscall.
[ab@gibson]$ make test c=true
nasm -o test64.o test64.asm -felf64
gcc -o auxvector.o auxvector.c -c -Wall -nostdlib -pie -fpic -O3 -D__WORDSIZE=64
ld -o test.elf test64.o auxvector.o
...
[ab@gibson]$ strace ./test.elf
execve("./test.elf", ["./test.elf"], 0x7ffff5364500 /* 46 vars */) = 0
exit(5) = ?
+++ exited with 0 +++