Add content from: Research Update: Enhanced src/binary-exploitation/basic-stac...

This commit is contained in:
HackTricks News Bot 2025-08-15 08:44:45 +00:00
parent 9f02ef0154
commit d11409018b

View File

@ -49,6 +49,8 @@ Contains the program header tables and metadata itself.
Indicates the path of the loader to use to load the binary into memory.
> Tip: Statically linked or static-PIE binaries wont have an `INTERP` entry. In those cases there is no dynamic loader involved, which disables techniques that rely on it (e.g., `ret2dlresolve`).
### LOAD
These headers are used to indicate **how to load a binary into memory.**\
@ -64,6 +66,12 @@ This header helps to link programs to their library dependencies and apply reloc
This stores vendor metadata information about the binary.
- On x86-64, `readelf -n` will show `GNU_PROPERTY_X86_FEATURE_1_*` flags inside `.note.gnu.property`. If you see `IBT` and/or `SHSTK`, the binary was built with CET (Indirect Branch Tracking and/or Shadow Stack). This impacts ROP/JOP because indirect branch targets must start with an `ENDBR64` instruction and returns are checked against a shadow stack. See the CET page for details and bypass notes.
{{#ref}}
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
{{#endref}}
### GNU_EH_FRAME
Defines the location of the stack unwind tables, used by debuggers and C++ exception handling-runtime functions.
@ -72,6 +80,8 @@ Defines the location of the stack unwind tables, used by debuggers and C++ excep
Contains the configuration of the stack execution prevention defense. If enabled, the binary won't be able to execute code from the stack.
- Check with `readelf -l ./bin | grep GNU_STACK`. To forcibly toggle it during tests you can use `execstack -s|-c ./bin`.
### GNU_RELRO
Indicates the RELRO (Relocation Read-Only) configuration of the binary. This protection will mark as read-only certain sections of the memory (like the `GOT` or the `init` and `fini` tables) after the program has loaded and before it begins running.
@ -80,6 +90,12 @@ In the previous example it's copying 0x3b8 bytes to 0x1fc48 as read-only affecti
Note that RELRO can be partial or full, the partial version do not protect the section **`.plt.got`**, which is used for **lazy binding** and needs this memory space to have **write permissions** to write the address of the libraries the first time their location is searched.
> For exploitation techniques and up-to-date bypass notes, check the dedicated page:
{{#ref}}
../common-binary-protections-and-bypasses/relro.md
{{#endref}}
### TLS
Defines a table of TLS entries, which stores info about thread-local variables.
@ -200,6 +216,10 @@ Each symbol entry contains:
- **Value** (address sin memory)
- **Size**
#### GNU Symbol Versioning (dynsym/dynstr/gnu.version)
Modern glibc uses symbol versions. You will see entries in `.gnu.version` and `.gnu.version_r` and symbol names like `strlen@GLIBC_2.17`. The dynamic linker can require a specific version when resolving a symbol. When crafting manual relocations (e.g. ret2dlresolve) you must supply the correct version index, otherwise resolution fails.
## Dynamic Section
```
@ -239,6 +259,23 @@ Dynamic section at offset 0xfc58 contains 28 entries:
The NEEDED directory indicates that the program **needs to load the mentioned library** in order to continue. The NEEDED directory completes once the shared **library is fully operational and ready** for use.
### Dynamic loader search order (RPATH/RUNPATH, $ORIGIN)
The entries `DT_RPATH` (deprecated) and/or `DT_RUNPATH` influence where the dynamic loader searches for dependencies. Rough order:
- `LD_LIBRARY_PATH` (ignored for setuid/sgid or otherwise "secure-execution" programs)
- `DT_RPATH` (only if `DT_RUNPATH` absent)
- `DT_RUNPATH`
- `ld.so.cache`
- default directories like `/lib64`, `/usr/lib64`, etc.
`$ORIGIN` can be used inside RPATH/RUNPATH to refer to the directory of the main object. From an attacker perspective this matters when you control the filesystem layout or environment. For hardened binaries (AT_SECURE) most environment variables are ignored by the loader.
- Inspect with: `readelf -d ./bin | egrep -i 'r(path|unpath)'`
- Quick test: `LD_DEBUG=libs ./bin 2>&1 | grep -i find` (shows search path decisions)
> Priv-esc tip: Prefer abusing writable RUNPATHs or misconfigured `$ORIGIN`-relative paths owned by you. LD_PRELOAD/LD_AUDIT are ignored in secure-execution (setuid) contexts.
## Relocations
The loader also must relocate dependencies after having loaded them. These relocations are indicated in the relocation table in formats REL or RELA and the number of relocations is given in the dynamic sections RELSZ or RELASZ.
@ -283,7 +320,6 @@ Relocation section '.rela.plt' at offset 0xcc8 contains 40 entries:
00000001fea0 000900000402 R_AARCH64_JUMP_SL 0000000000000000 perror@GLIBC_2.17 + 0
00000001fea8 000b00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@GLIBC_2.17 + 0
00000001feb0 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 putc@GLIBC_2.17 + 0
00000001feb8 000d00000402 R_AARCH64_JUMP_SL 0000000000000000 opendir@GLIBC_2.17 + 0
00000001fec0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 fputc@GLIBC_2.17 + 0
00000001fec8 001100000402 R_AARCH64_JUMP_SL 0000000000000000 snprintf@GLIBC_2.17 + 0
00000001fed0 001200000402 R_AARCH64_JUMP_SL 0000000000000000 __snprintf_chk@GLIBC_2.17 + 0
@ -332,6 +368,24 @@ The PLT section allows to perform lazy binding, which means that the resolution
So when a program calls to malloc, it actually calls the corresponding location of `malloc` in the PLT (`malloc@plt`). The first time it's called it resolves the address of `malloc` and stores it so next time `malloc` is called, that address is used instead of the PLT code.
#### Modern linking behaviors that impact exploitation
- `-z now` (Full RELRO) disables lazy binding; PLT entries still exist but GOT/PLT is mapped read-only, so techniques like **GOT overwrite** and **ret2dlresolve** wont work against the main binary (libraries may still be partially RELRO). See:
{{#ref}}
../common-binary-protections-and-bypasses/relro.md
{{#endref}}
- `-fno-plt` makes the compiler call external functions through the **GOT entry directly** instead of going through the PLT stub. You will see call sequences like `mov reg, [got]; call reg` instead of `call func@plt`. This reduces speculative-execution abuse and slightly changes ROP gadget hunting around PLT stubs.
- PIE vs static-PIE: PIE (ET_DYN with `INTERP`) needs the dynamic loader and supports the usual PLT/GOT machinery. Static-PIE (ET_DYN without `INTERP`) has relocations applied by the kernel loader and no `ld.so`; expect no PLT resolution at runtime.
> If GOT/PLT is not an option, pivot to other writeable code-pointers or use classic ROP/SROP into libc.
{{#ref}}
../arbitrary-write-2-exec/aw2exec-got-plt.md
{{#endref}}
## Program Initialization
After the program has been loaded it's time for it to run. However, the first code that is run i**sn't always the `main`** function. This is because for example in C++ if a **global variable is an object of a class**, this object must be **initialized** **before** main runs, like in:
@ -372,6 +426,16 @@ The other option, as mentioned, is to reference the lists **`__CTOR_LIST__`** an
Moreover, it's also possible to have a **`PREINIT_ARRAY`** with **pointers** that will be executed **before** the **`INIT_ARRAY`** pointers.
#### Exploitation note
- Under Partial RELRO these arrays live in pages that are still writable before `ld.so` flips `PT_GNU_RELRO` to read-only. If you get an arbitrary write early enough or you can target a librarys writable arrays, you can hijack control flow by overwriting an entry with a function of your choice. Under Full RELRO they are read-only at runtime.
- For lazy binding abuse of the dynamic linker to resolve arbitrary symbols at runtime, see the dedicated page:
{{#ref}}
../rop-return-oriented-programing/ret2dlresolve.md
{{#endref}}
### Initialization Order
1. The program is loaded into memory, static global variables are initialized in **`.data`** and unitialized ones zeroed in **`.bss`**.
@ -393,7 +457,35 @@ Each variable will hace an entry in the TLS header specifying the size and the T
The `__TLS_MODULE_BASE` is a symbol used to refer to the base address of the thread local storage and points to the area in memory that contains all the thread-local data of a module.
## Auxiliary Vector (auxv) and vDSO
The Linux kernel passes an auxiliary vector to processes containing useful addresses and flags for the runtime:
- `AT_RANDOM`: points to 16 random bytes used by glibc for the stack canary and other PRNG seeds.
- `AT_SYSINFO_EHDR`: base address of the vDSO mapping (handy to find `__kernel_*` syscalls and gadgets).
- `AT_EXECFN`, `AT_BASE`, `AT_PAGESZ`, etc.
As an attacker, if you can read memory or files under `/proc`, you can often leak these without an infoleak in the target process:
```bash
# Show the auxv of a running process
cat /proc/$(pidof target)/auxv | xxd
# From your own process (helper snippet)
#include <sys/auxv.h>
#include <stdio.h>
int main(){
printf("AT_RANDOM=%p\n", (void*)getauxval(AT_RANDOM));
printf("AT_SYSINFO_EHDR=%p\n", (void*)getauxval(AT_SYSINFO_EHDR));
}
```
Leaking `AT_RANDOM` gives you the canary value if you can dereference that pointer; `AT_SYSINFO_EHDR` gives you a vDSO base to mine for gadgets or to call fast syscalls directly.
## References
- ld.so(8) Dynamic Loader search order, RPATH/RUNPATH, secure-execution rules (AT_SECURE): https://man7.org/linux/man-pages/man8/ld.so.8.html
- getauxval(3) Auxiliary vector and AT_* constants: https://man7.org/linux/man-pages/man3/getauxval.3.html
{{#include ../../banners/hacktricks-training.md}}