Prośba o wytłumaczenie mechanizmu: *(long*) &stuffing[i] = 0x80484bb;

0

Jak w temacie, jest taka pętla i instrukcja puts do wyświetlenia:

for(i = 0; i <= 40; i+=4)
    *(long*) &stuffing[i] = 0x80484bb;
puts(stuffing);

To fragment z książki "Shellcoders handbook". Chodzi o nadpisanie adresu powrotu adresem funkcji, którą chcemy wywołać.
Mam rozumieć, że &stuffing[i] rzutowane jest na (long*), gdyż typ long ma 32 bity tyle samo co rejestr na x86 i zajmuje 4 elementy tablicy?
Jak właściwie to odczytać:

*(long*)&stuffing[i] = 0x80484bb;

Adres elementu [i] tablicy stuffing jest rzutowany na typ wskaźnika na long po czym wartość tego adresu zostaje wyłuskana i pod ten adres zapisany jest adres 0x80484bb?
Nie do końca jest to dla mnie jasne. Co w końcu zawierają elementy stuffing[i]? Czy będą to części adresu 0x80484bb rozbite na pojedyncze bajty ze względu na to, że char ma rozmiar jednego bajta?

1

Przede wszystkim, to jest UB, więc w poprawnym kodzie nigdy nic takiego nie rób ;​)

Ale rozumiesz dobrze. Nie zamieściłeś czym jest stuffing, ale pod każdym elementem zostaje przypisana sizeof(long)-bajtowa wartość 0x80484bb w takim endianess w jakim masz procesor.

0

Jasne, zapomniałem napisać, że stuffing to tablica charów:

char stuffing[40];
1

Najlepiej ustaw sobie memory view na stuffing w debuggerze i zobacz co się dzieje.

Ale rozumiesz dobrze, tak mi się wydaje.

1

@haracz: zanim wrócisz do swojej książki warto żebyś poświęcił trochę czasu żeby zrozumieć jak działają wskaźniki w C/C++.

Na poziomie CPU twoja tablica znaków typu char to tylko 40 bajtów w pamięci i nic więcej.
Zmienna stuffing to dla kompilatora po prostu wskaźnik który zawiera adres tego obszaru pamięci. W bardzo starym C (K&R) nie było nawet operatora indeksowania, wszystko robiło się wskaźnikami.

Operacje na tablicach w języku wskaźników:

char a = abc[i];
abc[i] = 'b';

to

a = *(abc + i);
*(abc + i) = 'b';

Jak już pisałem pierwsze wersje jezyka C nie zawierały nawet operatora indeksowania. Jeżeli traktowalibyśmy wskaźniki jak czyste adresy w pamięci to musielibyśmy pamiętać żeby przesunąć wskaźnik o 4 pozycje żeby dobrać się do następnego elementu tablicy int'ów, a o 8 dla double'i. Jest to bardzo niewygodne, dlatego wymyślono że wskaźniki będą miały wsłasną arytmentykę. abc + 1 to nie adres abc plus jeden, ale następny element tablicy abc. Czyli:

int* p = 0x100;
p++;
printf("%p\n", p); // 0x104

double* d = 0x100;
d++;
printf("%p\n", d); // 0x108

Dla CPU 40 bajtów to 40 bajtów, czyli:

char stuffing[40];

równie dobrze można traktować jak tablicę 10 intów 4-bajtowych, lub 5 longów 8-bajtowych (x84_64).

for(i = 0; i <= 40; i+=4)
    *(long*) &stuffing[i] = 0x80484bb;
puts(stuffing);

Powyższy kod traktuje stuffing jako tablicę 10 intów 4-bajtowych.

Ponieważ stuffing jest tablicą typu char, to &stuffing zwróci adres bajtu. Ale dla longów tutaj potrzebujemy adresu całych 4-bajtowych grup, stąd i+=4 a nie 1.
Jak już mamy adres takiej 4-bajtowej grupy to możemy ją traktować dowolnie np. jako long'a tak jak tu w przykładzie, ale moża też zrobić:

*(float *) &stuff ... = 3.14f;

Ale uwaga, nie wszystkie typy lubią być umiejszczane pod dowolnymi adresami. double i float (o ile dobrze pamiętam) trzeba umiejszczać pod adresami będącymi wielokrotonością 8 i 4.
Inaczej albo program działa wolno albo jest błąd (w zależności od CPU).

Kolejna sprawa to endianness, na x84 jak masz inta 0xaabbccdd to w pamięci bajty ułożone są odwrotnie a więc dd cc bb aa (od najmniejszego do najwiekszego adresu pamięci).

Jest już późno ale wydaje mi się że ta pętla powinna mieć warunke i < 40 a nie i <= 40. Jeżeli chodzi o puts to wygląda na to że będą się wypisywać smieci aż trafimy na pierwsze zero.

0

@0xmarcin: Dzięki za dokładne objaśnienie. Czy równie dobrze stuffing mogłaby być być tablicą longów i wtedy rzutowanie nie byłoby potrzebne?
Wtedy wystarczyłby zapis stuffing[i] = 0x80484bb;?

0

Tak, i miałbyś gwarantowany dobry alignment gratis.

1 użytkowników online, w tym zalogowanych: 0, gości: 1