„Śmieciowe” wartości zmiennych w C/CPP

Jak się okazuje nie są do końca ani losowe, ani śmieciowe. Spróbujemy nad nimi zapanować…

Hej, dzisiaj chcę wspomnieć parę słów o tzw. „śmieciowych”wartościach zmiennychw C/C++ , które mają zmienne na start w takiej sytuacji:

W każdym tutorialu z C ostrzega się przed użyciem zmiennej, która jest utworzona ale nie ma przypisanej wartości. Często mówi się, że są tam „śmieci” lub co jest akurat błędne: wartość pod variable jest „losowa”.

Dzisiaj chciałbym pokazać, że można zapanować nad takimi wartościami, tzn. jak można przewidzieć ich wartość.

Uwaga: ten artykuł należy traktować jako ciekawostkę, a nie użyteczny trick w programowaniu. Działanie tego tricku jest bardzo łatwo zaburzyć, co też sobie pokażemy.

PoC


Na wstępie warto zaznaczyć z jakiego środowiska korzystam, a więc:

  • Linux x86_64
  • GCC 5.4.0

Kod do testów:

Widzimy, że powyższy listing jest oczywiście „niepoprawny”, bo korzysta z śmieciowej (nieprzypisanej) wartości zmiennej a. Zobaczmy sobie co się stanie gdy skompilujemy sobie powyższy listing:

Dla pewności możemy uruchomić sobie powyższy kod kilka razy:

Widzimy, więc że powyższa wartość nie jest przypadkowa, a na pewno nie „losowa” jak twierdzą niektórzy prowadzący zajęcia z C/C++. Co więcej: ta wartość jest równa dokładnie tyle ile sami ustaliliśmy w funkcji  foo1().

Zatem mamy kontrolę nad tym co się znajduje w zmiennej  bar, mimo że nic do niej nie przypisujemy. Jak to się dzieje?

Wyjaśnienie


Prześledźmy Listing2 jeszcze raz, ale z dodatkowym komentarzem:

Aby zrozumieć co tu się stało należy najpierw zejść nieco niżej, a więc do tego jak to wygląda z poziomu assemblera.

W funkcji fun1 alokacja zmiennej foo to nic innego jak odjęcie 8 bajtów od wskaźnika stosu (dla niewtajemniczonych: stos rośnie „w dół” – stąd odejmowanie od wskaźnika; wskaźnik stosu kryje się pod rejestrem RSP).

Następnie w to miejsce przypisywana jest wartość 0xc0ffebabe, po czym zwalniamy pamięć, a więc tym razem dodajemy 8 bajtów to RSP. Należy zauważyć, że nigdzie nie zerujemy tej wartości przed usunięciem, ona wciąż jest w tym miejscu.

W pseudo-kodzie assembly, gdzie stos jest tablicą funkcja fun1 może wyglądać następująco:

Stos obrazkowo:

Z kolei fun2 to alokacja zmiennej (odjęcie wartości o 8),  wydrukowanie zmiennej spod adresu w którym za alokowana jest zmienna i zwolnienie pamięci:

Znowu obrazki:

Dla upewnienia można zobaczyć do samo przy zżucie kodu assembly:

Jak widzimy jeden z mechanizmów, który tu zachodzi to „przypadek” dzięki, któremu nowa zmienna (bar) wskazuje na to samo miejsce na stosie co stara zmienna (foo). Na ten przypadek składa się kilka rzeczy, m.in:

  1. Typ (wielkość) zmiennych,
  2. Ilość zmiennych zarówno wewnątrz funkcji, jak i ilości argumentów,
  3. Sposób wywoła (t.j. wywołanie fun2 z funkcji fun1 da nam zupełnie inny rezultat),
  4. Proces kompilacji.

Kiedy to nie działa?


Jak widzimy takie „zgadnięcie”, czy też kontrola nad wartością nieprzypisanej zmiennej wymaga dość specyficznego środowiska wynikającego głównie z tego jak zmienne ułożone są na stosie. Nie zadziała to także, gdy zmienne są różnej długości, tzn zadziała, ale wartości mogą być różnie interpretowane zależnie od typu.

Inną ciekawą sytuacją jest moment gdy skompilujemy powyższy kod z inną opcją:

Zadaniem domowym jest dowiedzenie się co się właściwie stało i dlaczego w wyniku jest ;)

Podsumowanie


Podsumowując: istnieją specyficzne sytuacje kiedy jesteśmy w stanie przewidzieć „śmieciowe” wartości zmiennych. To co możemy na pewno powiedzieć, to to że te wartości nie są losowe.

Jeżeli chodzi o wykorzystanie tej własności, to korzysta się z niej od czas do czasu w bezpieczeństwie, a konkretniej eksploitacji kodu. Trzeba mieć też świadomość, że ja pokazałem jedynie najprostszą z możliwych wersji takiego programistycznego błędu (bo tym właśnie jest korzystanie z niezainicjalizowanych zmiennych), osobiście spotkałem się z takim mechanizmem na kilku CTF’ach ;)

Zachęcam do eksperymentów z bogatszymi wersjami kodu powyżej (np. mamy zmienne ale różnych typów, więcej różnych zmiennych, …).

Dajcie znać czy nie chcielibyście więcej ciekawostek i tricków tego typu. Na koniec tradycyjnie zapraszam do komentarzy, a także do śledzenia bloga przez social-media.

Code ON!