Wszyscy znamy funkcję printf
– część standardowej biblioteki C – oraz jej liczne warianty z przedrostkami i przyrostkami, służące wypisywaniu tekstów do różnych miejsc na różne sposoby. Jeśli z nich korzystamy, to czasem zdarza się, że chcemy wydrukować komunikat dany jako pojedynczy, znany już napis. A wówczas można wyprodukować coś, co w najprostszej wersji będzie wyglądało tak:
Tak się jednak stringów nie wypisuje – nawet mimo tego, iż w większości przypadków działa to bez problemów. Możemy bowiem trafić na przypadek złośliwy, a błędy objawiające się tylko czasami są, jak wiemy, jednymi z najgorszych…
Rzecz w tym, że w rodzinie funkcji printf
opodobnych za to, co wydrukujemy, odpowiadają dwie rzeczy. Drugą z nich jest lista danych, mogąca mieć dowolnie dużo elementów; stąd też funkcje te przyjmują zmienną liczbę argumentów. Ale pierwszą jest tak zwany format, który mówi, jak te elementy należy interpretować: jako liczby całkowite, zmiennoprzecinkowe czy w końcu napisy. Ten argument jest łańcuchem znaków, występuje przed pozostałymi i w odróżnieniu od nich jest obowiązkowy.
Wywołanie printf(s);
w istocie oznacza więc, że s
nie jest tekstem do wypisania, ale formatem służącym interpretacji ewentualnych dalszych parametrów. Skoro jednak kolejnych argumentów nie ma, to wydaje się, że nie ma też problemu – zwłaszcza, że w wyniku tekst spod s
faktycznie jest wypisywany. Jest tak jednak tylko momentu, gdy natrafimy na łańcuch zawierający znaczek procenta (%
).
Jak wiemy, format dla funkcji typu printf
może bowiem zawierać (i zwykle zawiera) znaczniki odnoszące się do jej dalszych argumentów. Niemal zawsze wpisujemy je ręcznie w kodzie, bo dokładnie wiemy, co chcemy wydrukować – np.:
To sprawia, że bardzo łatwo zacząć je traktować identycznie jak sekwencje ucieczki, czyli podciągi \n
, \t
, itd., zamieniane przez kompilator na odpowiadające im znaki (tutaj: końca wiersza i tabulatora). W wynikowych stringach nie ma więc po nich śladu, zamiast tego są odpowiednie znaki, których nie da się normalnie wpisać z klawiatury.
Ale znaczniki formatujące nie są interpretowane przez kompilator. Podczas działania programu w tym łańcuchu nadal siedzi %d
, %f
i każdy inny znacznik rozpoczynający się od procenta. Jeśli więc łańcuch s
z wywołania printf(s);
przypadkiem zawiera znak procenta, to funkcja mylnie potraktuje go i znaki po nim występujące jako znacznik formatujący. Zachowanie może być wtedy różne – w najlepszym wypadku ów procent i następny znak zostaną po prostu “zjedzone” – ale zawsze będzie różne od naszych oczekiwań.
Konkluzja? Jest oczywiście taka, aby zawsze pamiętać o formacie i nawet jeśli wypisujemy “tylko” łańcuch znaków, umieścić w nim %s
:
Różnica mała, lecz ważna :)
To, że zostanie “zjedzone” to najmniejszy problem. Większy to: http://en.wikipedia.org/wiki/Format_string_vulnerabilities
Ten format string attack jest podobny SQL injection ^^
Nie jest ani trochę podobny :) Format string attack uderza w całkiem inne warstwy, działa na niższym poziomie, inaczej się exploituje i umożliwia inne rzeczy :)