LOAD_NAME / LOAD_CONST opcode OOB Read
Last updated
Last updated
Naucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Naucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Te informacje zostały zaczerpnięte z tego opisu.
Możemy wykorzystać funkcję OOB read w operacjach LOAD_NAME / LOAD_CONST, aby uzyskać pewien symbol w pamięci. Oznacza to wykorzystanie sztuczki takiej jak (a, b, c, ... setki symboli ..., __getattribute__) if [] else [].__getattribute__(...)
aby uzyskać symbol (takie jak nazwa funkcji), którego chcesz.
Następnie wystarczy opracować swój exploit.
Kod źródłowy jest dość krótki, zawiera tylko 4 linie!
Możesz wprowadzić dowolny kod Pythona, a zostanie on skompilowany do obiektu kodu Pythona. Jednak co_consts
i co_names
tego obiektu kodu zostaną zastąpione pustym krotką przed ewaluacją tego obiektu kodu.
W ten sposób wszystkie wyrażenia zawierające stałe (np. liczby, ciągi znaków itp.) lub nazwy (np. zmienne, funkcje) mogą spowodować błąd segmentacji na końcu.
Jak dochodzi do błędu segmentacji?
Zacznijmy od prostego przykładu, [a, b, c]
może zostać skompilowane do następującego kodu bajtowego.
Ale co jeśli co_names
stanie się pustym krotką? Opcodes LOAD_NAME 2
i tak są wykonywane, próbując odczytać wartość z tego adresu pamięci, z którego początkowo powinna pochodzić. Tak, to funkcja odczytu spoza zakresu.
Podstawowa koncepcja rozwiązania jest prosta. Niektóre operacje w CPython, na przykład LOAD_NAME
i LOAD_CONST
, są podatne (?) na odczyt spoza zakresu.
Pobierają one obiekt z indeksu oparg
z krotki consts
lub names
(tak są nazwane co_consts
i co_names
pod spodem). Możemy odnieść się do poniższego krótkiego fragmentu dotyczącego LOAD_CONST
, aby zobaczyć, co robi CPython podczas przetwarzania operacji LOAD_CONST
.
W ten sposób możemy użyć funkcji OOB, aby uzyskać "nazwę" z dowolnego przesunięcia pamięci. Aby upewnić się, jaką nazwę ma i jakie ma przesunięcie, wystarczy próbować LOAD_NAME 0
, LOAD_NAME 1
... LOAD_NAME 99
... I możesz znaleźć coś w okolicach oparg > 700. Możesz także spróbować użyć gdb, aby przyjrzeć się układowi pamięci oczywiście, ale nie sądzę, że byłoby to łatwiejsze?
Gdy już pozyskamy te przydatne przesunięcia dla nazw / stałych, jak uzyskać nazwę / stałą z tego przesunięcia i jej użyć? Oto sztuczka dla Ciebie:
Załóżmy, że możemy uzyskać nazwę __getattribute__
z przesunięcia 5 (LOAD_NAME 5
) z co_names=()
, wtedy po prostu wykonaj następujące czynności:
Zauważ, że nie jest konieczne nazwanie tego jako
__getattribute__
, możesz nazwać to jako coś krótszego lub bardziej dziwnego
Możesz zrozumieć powód po prostu przeglądając jego bytecode:
Zauważ, że LOAD_ATTR
również pobiera nazwę z co_names
. Python ładuje nazwy z tego samego przesunięcia, jeśli nazwa jest taka sama, dlatego drugie __getattribute__
jest nadal ładowane z przesunięcia=5. Korzystając z tej funkcji, możemy użyć dowolnej nazwy, gdy nazwa jest w pamięci w pobliżu.
Generowanie liczb powinno być trywialne:
0: not [[]]
1: not []
2: (not []) + (not [])
...
Nie użyłem stałych ze względu na limit długości.
Najpierw oto skrypt, który pozwala nam znaleźć te przesunięcia nazw.
I poniżej znajduje się kod generujący rzeczywisty exploit w Pythonie.
To po prostu wykonuje następujące czynności dla tych ciągów, które otrzymujemy z metody __dir__
:
Ucz się i praktykuj Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i praktykuj Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)