LOAD_NAME / LOAD_CONST opcode OOB Read
Last updated
Last updated
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Te informacje zostały wzięte z tego opisu.
Możemy użyć funkcji OOB read w opcode LOAD_NAME / LOAD_CONST, aby uzyskać symbol w pamięci. Oznacza to użycie sztuczki takiej jak (a, b, c, ... setki symboli ..., __getattribute__) if [] else [].__getattribute__(...)
, aby uzyskać symbol (taki jak nazwa funkcji), którego chcesz.
Następnie po prostu stwórz 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 pustą krotką przed eval tego obiektu kodu.
W ten sposób wszystkie wyrażenia zawierające stałe (np. liczby, ciągi itp.) lub nazwy (np. zmienne, funkcje) mogą ostatecznie spowodować błąd segmentacji.
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ę pustą krotką? Opcode LOAD_NAME 2
nadal jest wykonywany i próbuje odczytać wartość z tego adresu pamięci, z którego pierwotnie powinien. Tak, to jest "cecha" odczytu poza zakresem.
Podstawowa koncepcja rozwiązania jest prosta. Niektóre opcodes w CPython, na przykład LOAD_NAME
i LOAD_CONST
, są podatne (?) na odczyt poza zakresem.
Odbierają obiekt z indeksu oparg
z krotki consts
lub names
(to jest to, co co_consts
i co_names
nazywają pod maską). Możemy odwołać się do poniższego krótkiego fragmentu dotyczącego LOAD_CONST
, aby zobaczyć, co CPython robi, gdy przetwarza opcode LOAD_CONST
.
W ten sposób możemy użyć funkcji OOB, aby uzyskać "name" z dowolnego przesunięcia pamięci. Aby upewnić się, jaką ma nazwę i jakie jest jej przesunięcie, po prostu próbuj LOAD_NAME 0
, LOAD_NAME 1
... LOAD_NAME 99
... A możesz znaleźć coś przy 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ż odzyskamy te przydatne przesunięcia dla nazw / consts, jak zdobijemy nazwę / const z tego przesunięcia i użyjemy jej? 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 zrób następujące rzeczy:
Zauważ, że nie jest konieczne nazywanie tego
__getattribute__
, możesz nadać mu krótszą lub bardziej dziwną nazwę
Możesz zrozumieć powód, po prostu oglądając jego bajtowy kod:
Zauważ, że LOAD_ATTR
również pobiera nazwę z co_names
. Python ładuje nazwy z tej samej pozycji, jeśli nazwa jest taka sama, więc drugi __getattribute__
jest nadal ładowany z offsetu=5. Używając tej funkcji, możemy użyć dowolnej nazwy, gdy tylko nazwa znajduje się w pamięci w pobliżu.
Generowanie liczb powinno być trywialne:
0: not [[]]
1: not []
2: (not []) + (not [])
...
Nie użyłem consts z powodu limitu długości.
Najpierw oto skrypt, który pomoże nam znaleźć te offsety nazw.
A poniżej znajduje się generowanie prawdziwego exploita w Pythonie.
To basically robi następujące rzeczy, dla tych ciągów uzyskujemy je z metody __dir__
:
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)