Czy adres 0000000C jest adresem specjalnym?

32

Podczas programowania czasami coś się psuje. Popełniłeś błąd i twój program próbuje odczytać z niewłaściwego adresu.

Jedna rzecz, która wyróżnia mnie, że często te wyjątki to:

Access violation at address 012D37BC in module 'myprog.exe'. Read of address 0000000C.

Teraz widzę wiele dzienników błędów, a wyróżnia mnie: 0000000C. Czy to adres „specjalny”? Widzę inne naruszenia praw dostępu ze złymi odczytami, ale adresy wydają się losowe, ale ten ciągle powraca w zupełnie innych sytuacjach.

Pieter B.
źródło
1
Zauważyłam również, że 0000000Cjest sposób bardziej powszechne niż 00000008, ale żadna z odpowiedzi wydaje się, że adres w ogóle: /
mooing Duck
2
Być może System.Runtime.CompilerServices.RuntimeHelpers.OffsetToStringDatato 12=0x0Cjest powód, dlaczego to przesunięcie jest bardziej powszechne.
Mark Hurd
1
@MarkHurd To przerażające. Czy naprawdę uważasz, że istnieje tak wiele niezarządzanych aplikacji, które celowo odczytują / zapisują ciągi .NET, że byłoby to głównym źródłem naruszeń dostępu?
Luaan

Odpowiedzi:

57

00000000to specjalny adres (wskaźnik zerowy). 0000000Cjest dokładnie tym, co dostajesz, gdy dodasz przesunięcie 12 do wskaźnika zerowego, najprawdopodobniej dlatego, że ktoś próbował uzyskać zelement struktury taki jak ten poniżej za pomocą wskaźnika, który w rzeczywistości był zerowy.

struct Foo {
    int w, x, y; // or anything else that takes 12 bytes including padding
    // such as: uint64_t w; char x;
    // or: void *w; char padding[8];
    // all assuming an ordinary 32 bit x86 system
    int z;
}

źródło
29
A może dlatego, że jakaś mała wartość całkowa została omyłkowo wyodrębniona, jakby była wskaźnikiem. Małe wartości są znacznie bardziej powszechne niż duże, więc zwykle powoduje to powstanie nielegalnych adresów, takich jak 0X0000000C, a nie np. 0x43FCC893.
Kilian Foth,
3
Powodem, dla którego zadałem to pytanie, jest to, że 0000000C tak często wraca w porównaniu do innych adresów. Dlaczego przesunięcie 12 jest bardziej powszechne niż przesunięcie 4, 8 lub 16?
Pieter B
5
Po dalszych badaniach odpowiedź jest całkowicie poprawna. W moim źródle właściwość „tag” klas jest szeroko używana (dobra lub zła, muszę się z tym pogodzić). Właściwość tag w moim przypadku jest częścią klasy podstawowej niskiego poziomu i zawsze jest tworzona z tym przesunięciem.
Pieter B
1
Doskonały punkt Być może uwzględniono przypadek wskaźnika zerowego, ale wskaźnik zerowy ++ jest po prostu normalnym (aw tym przypadku niepoprawnym) adresem, więc nie działa tylko po uzyskaniu do niego dostępu.
Neil,
8
@Leushenko Tak, ochrona pamięci zwykle działa na całych stronach, a nawet jeśli można było złapać tylko 0, lepiej jest również zabezpieczyć następujące adresy, ponieważ prawdopodobnie będą one dostępne, jeśli wystąpi arytmetyka wskaźnika ze wskaźnikiem zerowym (jak w Sprawa OP).
11

W systemie Windows jest nielegalne dereference z całą pierwsza i ostatnia strona , innymi słowy pierwsza lub ostatnia 64 KiB pamięci procesu (zakresy 0x00000000do 0x0000ffffi 0xffff0000do 0xffffffffw aplikacji 32-bitowych).

Ma to na celu uwięzienie niezdefiniowanego zachowania dereferencji wskaźnika zerowego lub indeksu w tablicy zerowej. Rozmiar strony to 64 KiB, więc system Windows musi po prostu uniemożliwić przypisanie pierwszej lub ostatniej stronie prawidłowego zakresu.

Nie ochroni to przed niezainicjowanymi wskaźnikami, które mogą mieć dowolną wartość (w tym prawidłowe adresy).

maniak zapadkowy
źródło
7
Windows tak naprawdę nie może tego zrobić. Tabela stron jest strukturą zdefiniowaną i wymaganą przez x86, a małe strony są ustawione na 4KB. Jest osadzony w kamieniu (a dokładniej w krzemie). 64 KB jest prawdopodobnie dla wygody.
ElderBug,
12
W tym przypadku wolałbym napisać 64 KiB zamiast 65 kB, ponieważ ważna jest moc dwóch wielkości.
CodesInChaos
4
Zakres 64 KB jest pozostałością po wersji NT Aplha. I to nie rozmiar strony, ale szczegółowość alokacji. blogs.msdn.com/b/oldnewthing/archive/2003/10/08/55239.aspx
shf301
3
@CodesInChaos: Chociaż wielkie litery „M”, „G” i „T” są niejednoznaczne, nie widzę powodu, aby rezygnować z używania „k” dla 10 ^ 3 i „K” dla 2 ^ 10.
supercat
2
@MooingDuck Tak, właśnie dlatego sprecyzowałem małe strony. Większość procesorów x64 obsługuje także strony 1GiB. O ile mi wiadomo, system Windows zawsze ma strony ze stronami 4KB, chyba że są przypisane specjalnymi interfejsami API.
ElderBug,
2

Co do tego, dlaczego 0x0Cwydaje się bardziej powszechne niż 0x08(czy to naprawdę? Nie wiem; i w jakich aplikacjach?), Może to mieć związek z wskaźnikami wirtualnej tablicy metod. To jest naprawdę bardziej komentarz (zgadywanie dzikiej masy :), ale jest nieco większy, więc proszę ... Jeśli masz klasę z metodami wirtualnymi, jej pola zostaną przesunięte 0x04. Na przykład klasa dziedzicząca z innej klasy wirtualnej może mieć układ pamięci taki jak ten:

0x00 - VMT pointer for parent
0x04 - Field 1 in parent
0x08 - VMT pointer for child
0x0C - Field 1 in child

Czy jest to częsty scenariusz, czy nawet bliski? Nie jestem pewny. Należy jednak pamiętać, że w 64-bitowej aplikacji może to być jeszcze bardziej interesujące przesunięcie w kierunku 0x0Cwartości:

0x00 - VMT parent
0x08 - Field 1 parent
0x0C - VMT child
0x14 - Field 2 child

Tak więc w rzeczywistości istnieje wiele przypadków, w których aplikacje mogą znacznie nakładać się na przesunięcia wskaźnika zerowego. Może to być pierwsze pole w klasie podrzędnej lub wskaźnik wirtualnej tablicy metod - potrzebny za każdym razem, gdy wywołasz dowolną metodę wirtualną w instancji, więc jeśli wywołujesz metodę wirtualną na nullwskaźniku, otrzymasz naruszenie dostępu do jej Przesunięcie VMT. Występowanie tej konkretnej wartości może mieć coś wspólnego z niektórymi typowymi interfejsami API, które zapewniają klasę o podobnym wzorcu dziedziczenia lub, co bardziej prawdopodobne, określony interfejs (całkiem możliwe dla niektórych klas aplikacji, takich jak gry DirectX). Możliwe, że możliwe jest wyśledzenie prostej prostej przyczyny takiej jak ta, ale zwykle pozbywam się aplikacji, które powodują zerowe dereferencje dość szybko, więc ...

Luaan
źródło
1
Jeśli przejrzysz komentarze, możesz znacznie ograniczyć zgadywanie.
Deduplicator
@Deduplicator Cóż, uważam pomysł, że zarządzane ciągi .NET są używane w niebezpiecznym kodzie z ręcznymi operacjami wskaźnikowymi, a myśl, że byłaby to główna przyczyna naruszeń dostępu, tym bardziej. „Tak, jest to całkowicie bezpieczne dla pamięci, nie martw się, użyliśmy C #. Po prostu ręcznie modyfikujemy pamięć z C ++, ale jest bezpieczna w C #”.
Luaan,