I'm looking at a CVE for an old thrift shop router that amounts to a stack based buffer overflow with no NULL
characters allowed. I can control the instruction pointer register as well as a few less useful registers. My goal is to perform a stack pivot to something I control. The router runs on MIPS big endian architecture so I can't leave the last 8 bits of a payload empty to get a leading NULL
in an address. e.g. 0x00XXXXXX. I'm left to look for gadgets in shared objects (or the stack if cache flushed). I find that shared objects are generally loaded at higher addresses that make use of all 32 bits of the address. I cannot use gadgets from the vulnerable process because it was not compiled with -fpie
and it loads at the same address with a leading NULL
every time.
If I consistently want to be able to use gadgets in a shared object, it will need to be loaded with the same base address most of the time. Assuming no ASLR (not enabled on my router), what determines where a shared object will be loaded?
If I run ldd
on the target binary, I get this:
$ mips-linux-ldd squashfs-root/sbin/my_binary
/lib/ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x00000000)
Not super useful. I know that ld-uClibc.so.0
doesn't get loaded at 0x00000000
and that there are several other libraries that get loaded for this process (not dlopen
'ed) which are not listed. Perhaps the non-useful output is a toolchain thing or due to the fact that I'm not running on the target architecture.
Under the hood, I'd think we're looking at mmap
logic.
I found this in an article from Oracle:
An executable or shared object file's base address is calculated during execution from three values: the memory load address, the maximum page size, and the lowest virtual address of a program's loadable segment
To me it seems like these things would never change when working with a specific firmware version where the binaries can always be expected to be the same.
My questions are:
- Will shared objects get loaded in the same place every time on a Linux system with ASLR disabled?
- Is the mapping deterministic?
- What factors, if any will make the shared object map to a different address?
- Perhaps 2 libraries conflict in a preferred address
- Iff library loading is deterministic, can I assume the shared object will always be at the same address after checking once? The word deterministic answers my own question, but I want to be clear.